A multicast packet is a network packet meant to be received by more than one host, but not by all hosts. This functionality is obtained by assigning special hardware addresses to groups of hosts. Packets directed to one of the special addresses should be received by all the hosts in that group. In the case of Ethernet, a multicast address has the least significant bit of the first address octet set in the destination address, while every device board has that bit clear in its own hardware address.
The tricky part of dealing with host groups and hardware addresses is performed by applications and the kernel, and the interface driver doesn’t need to deal with these problems.
Transmission of multicast packets is a simple problem because they look exactly like any other packets. The interface transmits them over the communication medium without looking at the destination address. It’s the kernel that has to assign a correct hardware destination address; the hard_header device method, if defined, doesn’t need to look in the data it arranges.
The kernel handles the job of tracking which multicast addresses are of interest at any given time. The list can change frequently, since it is a function of the applications that are running at any given time and the user’s interest. It is the driver’s job to accept the list of interesting multicast addresses and deliver to the kernel any packets sent to those addresses. How the driver implements the multicast list is somewhat dependent on how the underlying hardware works. Typically, hardware belongs to one of three classes, as far as multicast is concerned:
Interfaces that cannot deal with multicast. These interfaces either receive packets directed specifically to their hardware address (plus broadcast packets), or they receive every packet. They can receive multicast packets only by receiving every packet, thus potentially overwhelming the operating system with a huge number of “uninteresting” packets. You don’t usually count these interfaces as multicast capable, and the driver won’t set
IFF_MULTICAST
indev->flags
.Point-to-point interfaces are a special case, because they always receive every packet without performing any hardware filtering.
Interfaces that can tell multicast packets from other packets (host-to-host or broadcast). These interfaces can be instructed to receive every multicast packet and let the software determine if this host is a valid recipient. The overhead introduced in this case is acceptable, because the number of multicast packets on a typical network is low.
Interfaces that can perform hardware detection of multicast addresses. These interfaces can be passed a list of multicast addresses for which packets are to be received, and they will ignore other multicast packets. This is the optimum case for the kernel, because it doesn’t waste processor time dropping “uninteresting” packets received by the interface.
The kernel tries to exploit the capabilities of high-level interfaces by supporting at its best the third device class, which is the most versatile. Therefore, the kernel notifies the driver whenever the list of valid multicast addresses is changed, and it passes the new list to the driver so it can update the hardware filter according to the new information.
Support for multicast packets is made up of several items: a device method, a data structure and device flags.
-
void (*dev->set_multicast_list)(struct net_device *dev);
This device method is called whenever the list of machine addresses associated with the device changes. It is also called when
dev->flags
is modified, because some flags (e.g.,IFF_PROMISC
) may also require you to reprogram the hardware filter. The method receives a pointer tostruct net_device
as an argument and returnsvoid
. A driver not interested in implementing this method can leave the field set toNULL
.-
struct dev_mc_list *dev->mc_list;
This is a linked list of all the multicast addresses associated with the device. The actual definition of the structure is introduced at the end of this section.
-
int dev->mc_count;
The number of items in the linked list. This information is somewhat redundant, but checking
mc_count
against 0 is a useful shortcut for checking the list.-
IFF_MULTICAST
Unless the driver sets this flag in
dev->flags
, the interface won’t be asked to handle multicast packets. The set_multicast_list method will nonetheless be called whendev->flags
changes, because the multicast list may have changed while the interface was not active.-
IFF_ALLMULTI
This flag is set in
dev->flags
by the networking software to tell the driver to retrieve all multicast packets from the network. This happens when multicast routing is enabled. If the flag is set,dev->mc_list
shouldn’t be used to filter multicast packets.-
IFF_PROMISC
This flag is set in
dev->flags
when the interface is put into promiscuous mode. Every packet should be received by the interface, independent ofdev->mc_list
.
The last bit of information needed by the driver developer is the
definition of struct dev_mc_list
, which lives in
<linux/netdevice.h>
.
struct dev_mc_list { struct dev_mc_list *next; /* Next address in list */ __u8 dmi_addr[MAX_ADDR_LEN]; /* Hardware address */ unsigned char dmi_addrlen; /* Address length */ int dmi_users; /* Number of users */ int dmi_gusers; /* Number of groups */ };
Because multicasting and hardware addresses are independent of the
actual transmission of packets, this structure is portable across
network implementations, and each address is identified by a string of
octets and a length, just like dev->dev_addr
.
The best way to describe the design of set_multicast_list is to show you some pseudocode.
The following function is a typical implementation of the function in
a full-featured (ff
) driver. The driver is
full featured in that the interface it controls has a complex hardware
packet filter, which can hold a table of multicast addresses to be
received by this host. The maximum size of the table is
FF_TABLE_SIZE
.
All the functions prefixed with ff_
are
placeholders for hardware-specific operations.
void ff_set_multicast_list(struct net_device *dev) { struct dev_mc_list *mcptr; if (dev->flags & IFF_PROMISC) { ff_get_all_packets(); return; } /* If there's more addresses than we handle, get all multicast packets and sort them out in software. */ if (dev->flags & IFF_ALLMULTI || dev->mc_count > FF_TABLE_SIZE) { ff_get_all_multicast_packets(); return; } /* No multicast? Just get our own stuff */ if (dev->mc_count == 0) { ff_get_only_own_packets(); return; } /* Store all of the multicast addresses in the hardware filter */ ff_clear_mc_list(); for (mc_ptr = dev->mc_list; mc_ptr; mc_ptr = mc_ptr->next) ff_store_mc_address(mc_ptr->dmi_addr); ff_get_packets_in_multicast_list(); }
This implementation can be simplified if the interface cannot store a
multicast table in the hardware filter for incoming packets. In that
case, FF_TABLE_SIZE
reduces to 0 and the last four
lines of code are not needed.
As was mentioned earlier, even interfaces that can’t deal with
multicast packets need to implement the
set_multicast_list method to be notified about
changes in dev->flags
. This approach could be
called a “nonfeatured” (nf
) implementation. The
implementation is very simple, as shown by the following code:
void nf_set_multicast_list(struct net_device *dev) { if (dev->flags & IFF_PROMISC) nf_get_all_packets(); else nf_get_only_own_packets(); }
Implementing IFF_PROMISC
is important, because
otherwise the
user won’t be able to run tcpdump or any
other network analyzers. If the interface runs a point-to-point link,
on the other hand, there’s no need to implement
set_multicast_list at all, because users
receive every packet anyway.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.