Skip to content

ipv6: address a pinned unicast datagram from the Neighbour Cache - #1152

Open
adamgeorge309 wants to merge 1 commit into
masterfrom
topic/gy/ipv6-nd-unicast-mac
Open

ipv6: address a pinned unicast datagram from the Neighbour Cache#1152
adamgeorge309 wants to merge 1 commit into
masterfrom
topic/gy/ipv6-nd-unicast-mac

Conversation

@adamgeorge309

@adamgeorge309 adamgeorge309 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

A locally-originated IPv6 unicast datagram that carries a pinned output interface went to the link
layer with dest = FF-FF-FF-FF-FF-FF. Every Neighbour Discovery (ND) message pins its output
interface, so a solicited Neighbour Advertisement (NA) and a Neighbour Unreachability Detection (NUD)
probe both left the node as an Ethernet broadcast, although the neighbour was in the Neighbour Cache.
This addresses them from the cache instead.

Closes #1151

One commit, and it carries its own baseline update as
change-a-baseline.md requires.

The problem

Ipv6::datagramLocalOut() took the destIE != nullptr branch — the pinned-interface branch — and
used MacAddress::BROADCAST_ADDRESS for every non-multicast destination. The multicast half of the
same if was corrected in 3be7618; the unicast half was not.

RFC 4861 Section 5.2 says where the link-layer address comes from:

Once the IP address of the next-hop node is known, the sender examines the Neighbor Cache for
link-layer information about that neighbor.

Measured on a switched IPv6 link (one EthernetSwitch, one Router6, three StandardHost6,
Ipv6FlatNetworkConfigurator, one host sending UDP to another), 120 s:

frames before after
Neighbour Advertisement to FF-FF-FF-FF-FF-FF 112 0
Neighbour Solicitation (NUD probe) to FF-FF-FF-FF-FF-FF 84 0
broadcast frames of any kind 196 0
UDP packets delivered 115 115

Correlating the two headers shows the NUD probe, which is a unicast Neighbour Solicitation:

before:  dest = FF-FF-FF-FF-FF-FF  ->  destinationAddress = fe80::8aa:ff:fe00:7
after:   dest = 0A-AA-00-00-00-07  ->  destinationAddress = fe80::8aa:ff:fe00:7

The fix

Pinning an output interface makes the Ipv6 module skip next-hop determination, so at that point it
has no next-hop address to look up. Two halves, neither of which changes anything on its own:

  • Ipv6NeighbourDiscovery::sendPacketToIpv6Module() states the next hop it already knows, in the
    NextHopAddressReq tag that GPSR and the netfilter hooks already use. An ND message is by
    definition addressed to a neighbour on the link it goes out on, so its next hop is its destination.
  • Ipv6::datagramLocalOut() takes the link-layer address from the Neighbour Cache when a next hop is
    stated, and keeps the previous behaviour when none is.

Why resolving the destination instead would be wrong

A pinned output interface does not imply an on-link destination, so looking the destination up is
not equivalent. Routing this branch through resolveMACAddressAndSendPacket() — the obvious fix —
breaks 13 module tests:

  • PIM-SM pins an interface for the unicast it sends toward the rendezvous point
    (PimSm.cc:1692). Address resolution then fails and ND emits an ICMPv6 Destination Unreachable
    that PimSplitter cannot handle: check_and_cast(): Cannot cast (inet::Indication*)ICMPv6-error to type 'inet::Packet *'.
  • MIPv6 loses route optimization entirely (100% ping loss in
    MIPv6_route_optimization.test), and resolveMACAddressAndSendPacket() additionally applies a
    mobile-node guard that drops Home-Address-sourced datagrams, which the pinned path had never been
    subject to.
  • Local delivery reaches this branch with the loopback interface pinned, and would run Neighbour
    Discovery on loopback.

Looking the destination up in the cache without resolving is not safe either: a mobile node keeps its
cache entry for the home agent after it has roamed, so a Binding Update pinned to the same interface
would be unicast to a node that is no longer on the link. MIPv6_return_home_route_optimization.test
catches exactly that — the home agent never receives the Binding Update, and the mobile node
retransmits with a doubling timeout.

Requiring the sender to state the next hop avoids all three: PIM-SM and MIPv6 do not state one, so
their behaviour is unchanged.

Verification

The baseline was recorded on unmodified origin/master before any edit, and every suite was rerun
against it.

  • New module test tests/module/IPv6_nd_unicast_mac.test: a router and three hosts on one
    switched Ethernet link, 120 s so that Neighbour Unreachability Detection probes as well. An
    IPv6-only Ethernet link carries multicast and unicast frames only, so the test asserts that no
    frame is addressed to the broadcast address. It passes with this change and fails on unmodified
    master
    .
  • Module tests (-f '.*([Ii][Pp][Vv]6|MLD|ospfv3).*'): baseline 61 total, 59 PASS, 2 FAIL; after
    62 total (the new test), 60 PASS, 2 FAIL. The per-test diff is empty apart from the added test.
    MIPv6_tcp_handover.test and IPv6_packet_too_big.test fail identically on unmodified master, so
    they are pre-existing and out of scope.
  • Fingerprints (./fingerprinttest -s -F tyf over the IPv6 entries of examples.csv and all of
    mipv6-refactoring.csv): baseline 97/97 PASS; 14 entries moved; 97/97 PASS again after
    re-recording.

Baseline updates

The 14 entries below are the IPv6 configurations that perform address resolution. Their Neighbour
Discovery frames are now addressed to one neighbour instead of to the whole link, so the switch
forwards them to one port and the nodes that no longer receive them lose their reception events.
Only the trajectory and content ingredients move — tplx, ~tNl, ~tND, ~tNlb. No configuration
without IPv6 address resolution moves.

tests/fingerprint/examples.csv:

  • /examples/bgpv4/BgpIpv6Basic/ -c General
  • /examples/bgpv4/BgpAndOspfv3/ -c General
  • /examples/bgpv4/BgpDiamondFailover6/ -c General
  • /examples/bgpv4/BgpWithdrawal6/ -c General and -c Restart
  • /examples/ipv6/nclients/ -c ETH
  • /examples/ipv6/mipv6/ -c Handover and -c RouteOptimizationTwoCNs
  • /examples/ipv6/mipv6roaming/ -c Roaming
  • /examples/ipv6/pmipv6/ -c General
  • /examples/manetrouting/gpsr/ -c IPv6, -c MultiIPv6 and -c DynamicIPv6
  • /examples/rip/simpletest/ -c IPv6

tests/fingerprint/mipv6-refactoring.csv: the ~tNlb values of the four configurations it shares
with the list above.

tyf was excluded from the run, as the harness itself recommends, so those values are carried over
untouched and are not claimed to be verified.

Not addressed here

  • An advertisement answering a unicast solicitation that carried no Source Link-Layer Address option
    still goes out as a broadcast, because the responder has no cache entry to use. RFC 4861
    Section 7.2.4 says such a node has to resolve the neighbour first; the TODO in sendSolicitedNa()
    records that this is not implemented. Six such frames remain in
    examples/manetrouting/gpsr -c IPv6, down from 158.
  • PIM-SM pinning an output interface for an off-link unicast destination is left as it is.

Devin Review

A locally-originated unicast datagram that carries a pinned output interface
went to the link layer with dest = FF-FF-FF-FF-FF-FF. Every Neighbour
Discovery message pins its output interface, so a solicited Neighbour
Advertisement and a Neighbour Unreachability Detection probe both left the
node as an Ethernet broadcast, although the neighbour was in the Neighbour
Cache. Every node on the link then received them, and a router forwarded the
advertisement and answered it with a spurious ICMPv6 Redirect.

RFC 4861 Section 7.2.4 requires the solicited advertisement to be unicast to
the soliciting node, and Section 5.2 says where the link-layer address comes
from: "Once the IP address of the next-hop node is known, the sender examines
the Neighbor Cache for link-layer information about that neighbor." The
multicast half of the same branch was corrected in 3be7618; the unicast
half was left on the broadcast address.

Pinning an output interface makes the Ipv6 module skip next-hop
determination, so it has no next-hop address to look up. Neighbour Discovery
therefore states the next hop it already knows -- an ND message is by
definition addressed to a neighbour on the link it goes out on -- and
datagramLocalOut() takes the link-layer address from the cache when a next
hop is stated.

Resolving the destination instead would be wrong. A pinned interface does not
imply an on-link destination: PIM-SM pins one for the unicast it sends toward
the rendezvous point, and a mobile node keeps a cache entry for its home agent
after it has roamed, so the entry addresses a node that is no longer on the
link. Both keep the previous behaviour, because neither states a next hop.

Not repaired here: an advertisement answering a unicast solicitation that
carried no Source Link-Layer Address option still goes out as a broadcast,
because the responder has no cache entry to use. RFC 4861 Section 7.2.4 says
such a node has to resolve the neighbour first, and the TODO in
sendSolicitedNa() records that this is not implemented.

The 14 fingerprints below are the IPv6 configurations that perform address
resolution. Their Neighbour Discovery frames are now addressed to one
neighbour instead of to the whole link, so the switch forwards them to one
port and the nodes that no longer receive them lose their reception events;
tplx, ~tNl, ~tND and ~tNlb move accordingly. tyf was excluded from the run
and is carried over unchanged. No configuration without IPv6 address
resolution moves.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ipv6: a unicast datagram with a pinned output interface goes to the Ethernet broadcast address

1 participant