Skip to content

ipv6: the solicited-node multicast group of an assigned address is never joined #1190

Description

@adamgeorge309

Summary

An IPv6 node in INET never joins the solicited-node multicast group of any address it
holds. Membership is derived at reception time instead: when a datagram arrives for a
solicited-node address, Ipv6RoutingTable::isLocalAddress() asks every interface to
recompute the solicited-node address of every address it holds and compare:

// src/inet/networklayer/ipv6/Ipv6RoutingTable.cc:478
// check for solicited-node multicast address
if (dest.matches(Ipv6Address::SOLICITED_NODE_PREFIX, 104)) {
    for (int i = 0; i < ift->getNumInterfaces(); i++) {
        NetworkInterface *ie = ift->getInterface(i);
        auto ipv6Data = ie->findProtocolData<Ipv6InterfaceData>();
        if (ipv6Data && ipv6Data->matchesSolicitedNodeMulticastAddress(dest))
            return true;
    }
}
// src/inet/networklayer/ipv6/Ipv6InterfaceData.cc:375
bool Ipv6InterfaceData::matchesSolicitedNodeMulticastAddress(const Ipv6Address& solNodeAddr) const
{
    for (const auto& elem : addresses)
        if (elem.address.formSolicitedNodeMulticastAddress() == solNodeAddr)
            return true;

    return false;
}

The only multicast groups the IPv6 layer itself joins are the three fixed ones —
ALL_NODES_2 and, for a router, ALL_ROUTERS_2 in Ipv6RoutingTable.cc:245-247, and
ALL_MLDV2_ROUTERS in Mldv2.cc:116. (An application joins its own groups through
Udp.cc:490-508; that path is unaffected.) There is no joinMulticastGroup() call for a
solicited-node address anywhere in the tree. Ipv6NeighbourDiscovery forms such
addresses in four places (Ipv6NeighbourDiscovery.cc:668, 710, 839, 868), every one
of them to address a packet, never to join.

So a node sends a Duplicate Address Detection probe to a group it has not joined, and
declares no membership for the group that would carry the answer. The code has known this
for a long time — the DAD path already models the RFC 4862 Section 5.4.2 delay that exists
only because of the join:

// src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc:850
// added uniform(0, IPv6_MAX_RTR_SOLICITATION_DELAY) to account for joining the solicited-node multicast
// group which is delay up to one 1 second (RFC 4862, 5.4.2)

What the standard says

RFC 4861 Section 7.2.1:
When a multicast-capable interface becomes enabled, the node MUST join the all-nodes
multicast address on that interface, as well as the solicited-node multicast address
corresponding to each of the IP addresses assigned to the interface.

The set of addresses assigned to an interface may change over time. New addresses might
be added and old addresses might be removed [ADDRCONF]. In such cases the node MUST join
and leave the solicited-node multicast address corresponding to the new and old
addresses, respectively. Joining the solicited-node multicast address is done using a
Multicast Listener Discovery such as [MLD] or [MLDv2] protocols. Note that multiple
unicast addresses may map into the same solicited-node multicast address; a node MUST NOT
leave the solicited-node multicast group until all assigned addresses corresponding to
that multicast address have been removed.

RFC 4862 Section 5.4.2:
Before sending a Neighbor Solicitation, an interface MUST join the all-nodes multicast
address and the solicited-node multicast address of the tentative address. The former
ensures that the node receives Neighbor Advertisements from other nodes already using the
address; the latter ensures that two nodes attempting to use the same address
simultaneously should detect each other's presence.

RFC 4862 Section 5.4.2 also says why the join is not decoration:

Note that when a node joins a multicast address, it typically sends a Multicast Listener
Discovery (MLD) report message [RFC2710] [RFC3810] for the multicast address. In the case
of Duplicate Address Detection, the MLD report message is required in order to inform
MLD-snooping switches, rather than routers, to forward multicast packets.

It should be noted, however, that in some link-layer environments, particularly with
MLD-snooping switches, no multicast reception will be available until the MLD report is
sent.

How to reproduce

examples/ipv6/mld with Duplicate Address Detection switched back on (the example turns it
off for a fast deterministic startup):

cd examples/ipv6/mld
inet -u Cmdenv -c MldDemo -f omnetpp.ini -r 0 \
     --**.neighbourDiscovery.dupAddrDetectTransmits=1 \
     --cmdenv-express-mode=false --cmdenv-log-level=info > mld.log

grep -o "Multicast Listener Report for group=[^ ]*" mld.log | sort | uniq -c
grep -c "INITIATING DUPLICATE ADDRESS" mld.log

Observed on 8ac5675c9c: 7 Duplicate Address Detection runs, and 14 Multicast
Listener Reports, not one of them for a solicited-node group:

      2 Multicast Listener Report for group=ff02::2
     12 Multicast Listener Report for group=ff0e::1

ff02::2 is the all-routers group the router joins explicitly, and ff0e::1 is the
application group the UdpSink apps join. Every one of the seven probes went to a
ff02::1:ffXX:XXXX group that no node had announced.

Why it matters

  1. The wire is wrong wherever membership is observable. A node that probes an address
    without an MLD report is, to an MLD-snooping switch, not a listener for that group. The
    switch has no reason to deliver the probe, and RFC 4862 Section 5.4.2 says so directly:
    with such a switch "no multicast reception will be available until the MLD report is
    sent". Emulation and any model that reasons about group membership rather than about
    packets sees the same gap.

  2. Membership is not inspectable. Ipv6InterfaceData::getJoinedMulticastGroup(),
    isMemberOfMulticastGroup() and Ipv6RoutingTable::isLocalMulticastAddress() all
    report the interface's joined groups. Today none of them knows about a solicited-node
    group, so the two ways of asking "does this node listen to ff02::1:ff00:9?" disagree.

Inside a pure-INET simulation without MLD nothing about Duplicate Address Detection
changes: isLocalAddress() accepts the answer through the derived path today, and accepts
it through the same path after the fix. This is a fidelity defect, not a correctness one.

Not addressed here

The derived path in matchesSolicitedNodeMulticastAddress() stays. Making
isLocalAddress() consult the joined groups instead would make membership the single
source of truth, but it would also change reception for any address assigned on a path
that cannot join — a non-multicast interface, for instance — so it is a separate decision.

RFC 4862 Section 5.4.2's SHOULD to delay the join by a random interval between 0 and
MAX_RTR_SOLICITATION_DELAY is not implemented; the join is immediate. That SHOULD applies
when the probe is the first message after interface (re)initialization, or when the address
came from a multicast Router Advertisement, and the same section notes that an interface
MUST still receive and process datagrams for the group during the delay. INET's existing
uniform(0, IPv6_MAX_RTR_SOLICITATION_DELAY) at Ipv6NeighbourDiscovery.cc:851 is added
to the Duplicate Address Detection timeout — the wait for an answer — rather than to
either the probe or the join; that is #1179.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions