Skip to content

ipv6: hold a datagram with a tentative source address, whoever chose it - #1297

Open
adamgeorge309 wants to merge 1 commit into
masterfrom
topic/gy/ipv6-tentative-explicit-source
Open

adamgeorge309 wants to merge 1 commit into
masterfrom
topic/gy/ipv6-tentative-explicit-source

Conversation

@adamgeorge309

@adamgeorge309 adamgeorge309 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Ipv6 held a datagram whose source address was tentative, that is, still under Duplicate Address Detection (DAD), only when Ipv6 had chosen the source address itself. A datagram whose sender set the source address, such as an ICMPv6 (Internet Control Message Protocol for Internet Protocol version 6) Echo Reply to a multicast Echo Request, left with the tentative address. RFC (Request for Comments) 4862 Section 5.4 does not count a tentative address as assigned, and the new module test shows such replies on master. The same check now applies to a source address that the sender set, except in Neighbor Discovery messages, whose sources Ipv6NeighbourDiscovery chooses under RFC 4861 (Neighbor Discovery for IP version 6). Results change only where a node sends such a datagram during DAD; no fingerprint moves. Risk: makeTentativeAddressPermanent() signals the end of DAD before it makes the other addresses of the interface permanent, so a datagram held for one of those addresses stays held until the next DAD (see Not addressed here); the shipped Mobile IPv6 examples do not reach that case.

Closes #1295

The problem

The check in Ipv6::fragmentPostRouting() (src/inet/networklayer/ipv6/Ipv6.cc:1019-1029 on master) sat inside the branch that fills in an unspecified source address. RFC 4862 (IPv6 Stateless Address Autoconfiguration) Section 5.4 has no sentence that forbids a tentative source outright; the rule follows from the address not being assigned.

tests/module/IPv6_tentative_source_echo_reply.test: a router pings ff02::1 every 0.2 s from t=0 while a host runs DAD.

master 93210f4 this branch
Echo Replies 0 to 10, source fe80::8aa:ff:fe00:2, DAD complete at t=2.1506 s reached the router from t=2.1061 s reached the router from t=2.1507 s
Echo Replies 16 to 22, source aaaa:0:65:0:8aa:ff:fe00:2, DAD from t=3.0973 s to t=4.5349 s reached the router between t=3.2004 s and t=4.4002 s reached the router from t=4.5350 s
"Source address is tentative" lines from the host 0 18, one per reply

The fix

The check keeps its exception for Optimistic DAD (RFC 4429). It runs in Ipv6 rather than in each sender because it then covers every sender, and because during link-local DAD a node has no other source address to choose. Two cases are left out:

  • Neighbor Discovery messages. RFC 4861 gives each of them its own source address rules, and a router's Router Advertisements would be held during its own DAD and then sent all at once when DAD completes. Holding them moves 12 fingerprint rows. Among them are the five Dynamic MANET (Mobile Ad hoc Network) On-demand (DYMO) IPv6 rows, where each DYMO node advertises as a router and held about 30 Router Advertisements.
  • An interface without Ipv6InterfaceData, such as a tunnel interface that Mobile IPv6 creates at run time. It has no tentative addresses.

A held datagram is released as before, by the dadCompletedSignal handler in Ipv6::receiveSignal(), and dropped by the dadFailedSignal handler if its source address is removed. A datagram whose source Ipv6 fills in is handled exactly as on master.

Architectural surface

Behavior of Ipv6: when a locally originated datagram with an explicit source address leaves during DAD. A file-local helper isNeighbourDiscoveryMessage() in Ipv6.cc; no change to the Ipv6 class. No NED (Network Description) parameter, packet format or feature descriptor changes.

Verification

Release build (make MODE=release):

cd tests/module && inet_run_module_tests -m release --no-build -l ERROR -f 'IPv6_tentative_source_echo_reply|IPv6_traffic_during_DAD|IPv6_DAD'

IPv6_tentative_source_echo_reply fails on master (the host logs no "Source address is tentative") and passes on this branch. The existing IPv6_traffic_during_DAD, IPv6_DAD_success and IPv6_DAD_failure pass on both. The same holds in a debug build (make MODE=debug, inet_run_module_tests -m debug ...).

Full suites, release, on master 93210f4 and on this branch:

cd tests/fingerprint && ./fingerprinttest -s -F tyf -t 6
cd tests/module && inet_run_module_tests -m release --no-build -l ERROR
cd tests/protocol/ipv6 && inet_run_protocol_tests -m release
cd tests/protocol/nd && inet_run_protocol_tests -m release
  • Fingerprints: 1774 rows, 2 failures and 62 errors on both, with the same actual values. The failures are examples/ipv6/nclients ETH and PPP in mipv6-refactoring.csv; the errors are rows whose optional features are disabled in this build. No fingerprint moves.
  • Module tests: master runs 349 tests, 303 pass; this branch runs 350, 304 pass, the new one included. The same 46 fail on both (34 tcp_*, ConvolutionalCoder12/34, EtherHost_lifecycle, ExternalProcess_3, Ieee80211BitDomain/SymbolDomain, Ieee8021d-Rstp/Stp, IPv6_packet_too_big, MIPv6_tcp_handover, PacketGate_1, UDPSocket_1).
  • Protocol tests: tests/protocol/ipv6 26 of 27 pass on both (Rfc8200OverlappingFragments fails on both); tests/protocol/nd 22 of 37 pass on both, with the same 15 failures.
  • doc/project/enforcement/check-commits.sh, check-classification.sh and check-architecture.sh src/inet/networklayer/ipv6: PASS. Statistical tests and check-cpp.sh were not run.

Not addressed here

  • Neighbor Discovery sends some messages from a tentative address. An address-resolution Neighbor Solicitation takes the source address of the datagram that prompted it whenever the interface holds that address, tentative or not (Ipv6NeighbourDiscovery.cc:674-677). RFC 4861 Section 7.2.2 allows that only for an address "assigned to the outgoing interface", and a tentative address is not assigned; otherwise "any one of the addresses assigned to the interface should be used". On this branch, too, each Echo Reply in the test first goes to address resolution, whose solicitation reaches the router with the tentative source at t=2.1048 s and at t=3.2002 s; only then is the reply itself held. A router also sends Router Advertisements while its link-local address is tentative. Both belong to Ipv6NeighbourDiscovery, which should choose another source or wait.
  • Ipv6NeighbourDiscovery::makeTentativeAddressPermanent() emits dadCompletedSignal before its loop makes the other addresses of the interface permanent (Ipv6NeighbourDiscovery.cc:890 and 919-925). A datagram whose source only that loop makes permanent stays held until the next DAD on that node completes, or to the end of the simulation. A mobile node meets this at a handover, where all its addresses are marked tentative. This branch does not add such datagrams in the shipped Mobile IPv6 examples: a datagram that a mobile node sources from its home address there is either dropped first by the existing home-address check in Ipv6::resolveMACAddressAndSendPacket() or sent through a tunnel interface, which is not checked, and their fingerprints do not move.

Landing order


Devin Review

Ipv6::fragmentPostRouting() holds a datagram whose source address is
still tentative, that is, still under Duplicate Address Detection
(DAD), in pendingDadQueue until DAD completes. The check sat inside the
branch that fills in an unspecified source address, so it ran only when
Ipv6 chose the source itself. A datagram whose sender had set the source
address left with the tentative address. Icmpv6 is such a sender: the
reply to an Echo Request sent to a multicast address takes its source
from getPreferredAddress() (Icmpv6.cc:252-258). During link-local DAD
that is the only, tentative, address; during global DAD
choosePreferredAddress() prefers the tentative global address.

RFC (Request for Comments) 4862 (Internet Protocol version 6 (IPv6)
Stateless Address Autoconfiguration) Section 5.4: "A tentative address is not considered
'assigned to an interface' in the traditional sense", and a node that
uses an address in parallel with DAD "will erroneously process traffic
intended for the other node". No RFC 4862 sentence forbids a tentative
source outright.

The same check, with the same exception for Optimistic DAD (RFC 4429),
now also runs on a source address that the sender set. It is in Ipv6
rather than in each sender: it then covers every sender, and during
link-local DAD a node has no other source address to choose. Neighbor Discovery
messages are left out: RFC 4861 gives each of them its own source
address rules, Ipv6NeighbourDiscovery chooses the source itself, and
holding them would send a router's Router Advertisements from its DAD
period all at once when DAD completes. An interface without
Ipv6InterfaceData, such as a tunnel interface that Mobile IPv6 creates
at run time, has no tentative addresses and is not checked.
makeTentativeAddressPermanent() emits dadCompletedSignal
(Ipv6NeighbourDiscovery.cc:890) before its loop makes the other
addresses of the interface permanent (919-925), so a held datagram whose
source only that loop makes permanent stays held until the next DAD
completes; the shipped Mobile IPv6 examples do not reach that case.

tests/module/IPv6_tentative_source_echo_reply.test reproduces it: a
router pings ff02::1 every 0.2 s while a host runs DAD. Before this
commit the host's Echo Replies reach the router with a tentative source from
t=2.1061 s (link-local, DAD complete at t=2.1506 s) and from t=3.2004 s
(global, DAD complete at t=4.5349 s). With it they arrive from
t=2.1507 s and from t=4.5350 s.

Change: src.networklayer.ipv6.Ipv6 | behavior.change.fix | test whatsnew

@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 found 2 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +1047 to +1048
auto ipv6Data = ie->findProtocolData<Ipv6InterfaceData>();
if (ipv6Data != nullptr && ipv6Data->isTentativeAddress(ipv6Header->getSrcAddress()) && !ipv6Data->isOptimisticDad()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Cross-interface tentative source escapes DAD

When a sender selects a tentative source on another interface, isTentativeAddress checks only the outgoing interface. The datagram leaves before DAD completes on the source interface.

Learn more

An explicit source address is accepted when it belongs to any interface on the node, not necessarily the egress interface. handleMessageFromHL checks the address through the routing table, while routePacket chooses the output independently. The new check inspects only the output interface's address list. If another interface owns the tentative source, that lookup is false and the packet goes onto the wire before DAD completes.

Example: A host has a tentative source address on eth0 and a route to the destination through eth1. An application binds to eth0's address. The output check sees no such address on eth1 and sends the packet during eth0's DAD.

Recommended fix: Resolve the explicitly supplied source to its owning interface and check its DAD state there, while retaining the outgoing interface and next-hop decision for transmission. Verify how the DAD completion and failure callbacks match that owning interface before queuing cross-interface packets.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1048 to +1053
if (ipv6Data != nullptr && ipv6Data->isTentativeAddress(ipv6Header->getSrcAddress()) && !ipv6Data->isOptimisticDad()) {
EV_INFO << "Source address is tentative - enqueueing datagram for later resubmission." << endl;
ScheduledDatagram *sDgram = new ScheduledDatagram(packet, ipv6Header.get(), ie, nextHopAddr, fromHL);
take(sDgram);
pendingDadQueue.push_back(sDgram);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Handover datagrams remain queued indefinitely

During a handover, pendingDadQueue holds explicit sources other than the link-local address. DAD completion signals before clearing their tentative flags, leaving those datagrams queued without another signal.

Learn more

On handover, processRaPrefixInfoForAddrAutoConf marks every existing interface address tentative and starts link-local DAD. This branch can now enqueue a packet with an explicitly chosen global source. makeTentativeAddressPermanent emits dadCompletedSignal just after clearing the link-local flag, before clearing the other addresses' flags. The queue listener leaves those packets queued. Subsequent flag changes emit no DAD completion, and removal of the old care-of address emits no DAD failure, so affected packets stay queued until shutdown.

Example: During handover, a sender uses an existing global source while its flag is tentative. The packet enters the DAD queue. Link-local DAD ends, the queue listener skips it, then handover clears or removes the global address without notifying the queue; the packet never leaves.

Recommended fix: Coordinate the handover address changes and queue notification: complete or remove all affected addresses before notifying the IPv6 queue, and explicitly drop queued packets for addresses removed during handover. Add a test where an explicit global-source packet is queued during handover and verify its eventual disposition.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

This branch has not been deployed

No deployments
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 datagram whose sender set a tentative source address is not held for DAD

1 participant