Skip to main content

8.7 Receiving IPv6 Multicast Packets

In the previous section, we traced the journey of unicast packets through the kernel. Whether locally delivered or forwarded, their final destination was clear-cut. But the networking world also features a "one-to-many" communication model—multicast. For routers, multicast packet handling is inherently more complex: they must decide not only whether to listen themselves, but also whether to forward the traffic on behalf of their neighbors.

This brings us to the ip6_mc_input() we are about to discuss.

The Multicast Packet Fork in the Road

Remember the ip6_rcv_finish() we mentioned in ipv6_rcv()? When it calls ip6_route_input() for a route lookup and discovers the destination address is a multicast address, it sets the receive callback to ip6_mc_input instead of the regular ip6_input.

This is like setting up a dedicated "express lane" in a postal sorting system. Let's see how this lane operates.

The task of ip6_mc_input() is straightforward: take the destination address from the packet and ask the network interface, "Have you subscribed to this channel?" It does this via ipv6_chk_mcast_addr():

deliver = ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, NULL);

Notice that the third parameter passed here is NULL. This is crucial—it means we only care about "whether this multicast address is subscribed to," and temporarily don't care about "who sent it" (source filtering). Source filtering is a more granular task that we will cover in detail later.

Now, the deliver variable holds the fate of this packet: if it is true, it means the local machine is interested in this multicast traffic; if it is false, this packet is just noise to us.

The Router's Responsibility: Forward or Drop?

If the kernel was compiled with CONFIG_IPV6_MROUTE (multicast routing support) enabled, things get more interesting. At this point, the machine is not just a listener, but also a "relay station."

There is a preprocessing logic in the code:

#ifdef CONFIG_IPV6_MROUTE
if (dev_net(skb->dev)->ipv6.devconf_all->mc_forwarding &&
!(ipv6_addr_type(&hdr->daddr) &
(IPV6_ADDR_LOOPBACK|IPV6_ADDR_LINKLOCAL)) &&
likely(!(IP6CB(skb)->flags & IP6SKB_FORWARDED))) {
/*
* Okay, we try to forward - split and duplicate
* packets.
*/
struct sk_buff *skb2;

if (deliver)
skb2 = skb_clone(skb, GFP_ATOMIC);
else {
skb2 = skb;
skb = NULL;
}

if (skb2) {
ip6_mr_input(skb2);
}
}
#endif

This logic is a bit tricky, so let's break it down:

  1. Prerequisite check: Forwarding is only considered if the global configuration has mc_forwarding enabled, and the destination address is neither loopback nor linklocal.
  2. Clone or repurpose:
    • If deliver is true (the local machine also wants to listen), the kernel calls skb_clone() to clone a copy of the skb2. The original skb is kept for local processing, while the cloned skb2 is handed off for forwarding. This makes perfect sense—everyone gets their own copy, without interference.
    • If deliver is false (the local machine is not interested), there is no need to copy. The skb is directly repurposed for the forwarding logic (skb2 = skb), and the original pointer is set to null (skb = NULL). This saves some CPU and memory—after all, there's no need to copy something nobody wants.

Finally, the cloned or repurposed skb2 is passed into ip6_mr_input(). This enters the IPv6 multicast routing subsystem (located at net/ipv6/ip6mr.c). The implementation of this subsystem is almost identical to IPv4 multicast routing (which we discussed in Chapter 6), so we won't expand on it here. This mechanism was introduced in kernel 2.6.26 in 2008, originating from a patch by Mickael Hoerdt.

Local Delivery and Re-confirming Source Filtering

After the forwarding logic is handled (or skipped entirely if forwarding is disabled), the remaining skb is either kept for local use or is unwanted.

if (likely(deliver))
ip6_input(skb);
else {
/* discard */
kfree_skb(skb);
}

return 0;
}

If deliver is true, the flow proceeds to the familiar ip6_input(), which in turn calls ip6_input_finish().

But there is a subtle detail here that can easily cause confusion.

Earlier, in ip6_mc_input(), we called ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, NULL) once. Now, inside ip6_input_finish(), the code calls ipv6_chk_mcast_addr() a second time, and this time the third parameter is not NULL, but rather hdr->saddr (the source address).

Why check twice?

The first check is a "coarse filter" that only looks at whether the group is subscribed to. The second check is a "fine filter" that not only looks at the group but also combines the source address to check for any filtering rules (Source Filtering). We will discuss this in detail in the next section, "Multicast Source Filtering (MSF)." You can think of it like this:

  1. First hurdle: Are you a member of this club?
  2. Second hurdle: Have you blocked a specific person from speaking in this club?

Only after passing both hurdles is the packet delivered to the upper-layer Socket (such as TCP or UDP). If it fails, ip6_input_finish() will drop it.

Summary

The design of the IPv6 multicast receive path reflects the kernel's typical approach to packet processing:

  1. Early demultiplexing: The routing subsystem (ip6_route_input) directs multicast packets to ip6_mc_input.
  2. Identity check: A quick check of whether the local machine has subscribed to the multicast address.
  3. Separation of forwarding and receiving: skb_clone cleverly reuses the packet between "needing local delivery" and "needing forwarding," avoiding unnecessary memory copies.
  4. Layered filtering: A coarse multicast address check is performed at the device layer first, followed by a fine-grained filter with source address checking right before upper-layer delivery.

This flow ensures that multicast data can be efficiently forwarded between routers while precisely reaching only those receiving processes that are genuinely interested and meet the filtering criteria. Next, we will dive into that "fine filter" stage—the MLD protocol and source filtering mechanism.