From 7dbb9cf666dc5e7fa69f95343947714a8008e6d2 Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Thu, 15 Oct 2026 12:55:53 +0200 Subject: [PATCH] icmpv6: fix: end address resolution when an NS sets the entry STALE A Neighbor Solicitation (NS) with a Source Link-Layer Address option that found an INCOMPLETE Neighbour Cache entry recorded the address and set the entry STALE, but left the address-resolution timer running and the packets queued for the entry waiting. The timer then went on with the retransmissions: processArTimeout() does not check the state, so it sent multicast NSs for a neighbour whose link-layer address was known and, after MAX_MULTICAST_SOLICIT NSs in all, declared address resolution failed and deleted the entry, which could be REACHABLE by then. RFC 4861 Appendix C gives this transition as "INCOMPLETE NS ... Record link-layer address. Send queued packets. STALE"; Section 7.2.2 retransmits only while awaiting a response. processNsWithSpecifiedSrcAddr() now sets an INCOMPLETE entry STALE even if a Router Advertisement already recorded the same address, and cancels the timer and sends the queued packets, as processNaForIncompleteNceState() does when a Neighbor Advertisement (NA) completes resolution. With two hosts on one link that start sending to each other at the same moment, each host's entry for the other is INCOMPLETE when the other's multicast NS arrives. Before this commit the two hosts sent 8 extra multicast NSs and deleted their entries 4 times in 6 s of traffic; after it, none. tests/module/IPv6_ND_solicitation_ends_address_resolution.test runs that case and checks that no host retransmits an NS for address resolution or declares it failed; it fails before this commit. The fingerprints of the GPSR (Greedy Perimeter Stateless Routing) rows IPv6, MultiIPv6 and DynamicIPv6 in examples.csv change, and this commit records the new ones: in IPv6, failed address resolutions go from 17 to 9. Their statistical baselines change as well; the statistics repository updates them after this merges. Change: src.networklayer.icmpv6.Ipv6NeighbourDiscovery | behavior.change.fix | test fingerprint statistical whatsnew --- WHATSNEW | 4 + .../icmpv6/Ipv6NeighbourDiscovery.cc | 11 ++- tests/fingerprint/examples.csv | 6 +- ..._solicitation_ends_address_resolution.test | 79 +++++++++++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/module/IPv6_ND_solicitation_ends_address_resolution.test diff --git a/WHATSNEW b/WHATSNEW index d0bbfb448c7..d6176db7600 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -471,6 +471,10 @@ Notable bug fixes are the following: The HT mode factory now includes band and preamble format in its cache key. A 5 GHz request after an equivalent 2.4 GHz request therefore receives the requested band and timing. Previously, that request could receive the earlier mode. Identical complete requests still reuse one object. +2. A Neighbor Solicitation ends address resolution for an INCOMPLETE entry + + A Neighbor Solicitation that set an INCOMPLETE Neighbour Cache entry STALE left the address-resolution timer running and the queued packets waiting. The timer then sent further multicast Neighbor Solicitations for a neighbour whose link-layer address was known and finally deleted the entry as a failed resolution, for example when two hosts resolve each other at the same moment. The solicitation now cancels the timer and sends the queued packets, as the state machine in RFC 4861 Appendix C gives it. Simulations in which neighbours resolve each other at the same moment produce different results, for example fewer failed address resolutions in examples/manetrouting/gpsr. + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc index 9785d3f5522..bab40aed3d3 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc @@ -1957,11 +1957,20 @@ void Ipv6NeighbourDiscovery::processNsWithSpecifiedSrcAddr(Packet *packet, const else { /*If an entry already exists, and the cached link-layer address differs from the one in the received Source Link-Layer option,*/ - if (!(entry->macAddress.equals(nsMacAddr))) { + // (an INCOMPLETE entry may already hold the address from a Router Advertisement) + bool wasIncomplete = entry->reachabilityState == Ipv6NeighbourCache::INCOMPLETE; + if (!(entry->macAddress.equals(nsMacAddr)) || wasIncomplete) { // the cached address should be replaced by the received address entry->macAddress = nsMacAddr; // and the entry's reachability state MUST be set to STALE. entry->reachabilityState = Ipv6NeighbourCache::STALE; + // RFC 4861 Appendix C: an NS ends address resolution for an INCOMPLETE + // entry, which sends the packets queued for it + if (wasIncomplete) { + cancelAndDelete(entry->arTimer); + entry->arTimer = nullptr; + sendQueuedPacketsToIpv6Module(entry); + } } } } diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 0180b3bb5a0..2b156b4c2d9 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -365,16 +365,16 @@ # /examples/manetrouting/gpsr/, -f omnetpp.ini -c _IPv6 -r 0, # abstract-config # /examples/manetrouting/gpsr/, -f omnetpp.ini -c _Generic -r 0, # abstract-config /examples/manetrouting/gpsr/, -f omnetpp.ini -c IPv4 -r 0, 20s, 1128-a3bc/tplx;9f6c-df77/~tNl;5d32-0ae4/tyf, PASS, wireless adhoc Ipv4 -/examples/manetrouting/gpsr/, -f omnetpp.ini -c IPv6 -r 0, 20s, e008-a90a/tplx;eead-fc34/~tNl;f19e-13d3/tyf, PASS, wireless adhoc +/examples/manetrouting/gpsr/, -f omnetpp.ini -c IPv6 -r 0, 20s, 846c-992a/tplx;a059-a5c2/~tNl;0bb0-d1cc/tyf, PASS, wireless adhoc /examples/manetrouting/gpsr/, -f omnetpp.ini -c Generic -r 0, 20s, 6b9d-8586/tplx;c58b-970e/~tNl;17f5-8ad3/tyf, PASS, wireless adhoc # /examples/manetrouting/gpsr/, -f omnetpp.ini -c _Multi -r 0, # abstract-config /examples/manetrouting/gpsr/, -f omnetpp.ini -c MultiIPv4 -r 0, 20s, d4a1-5cf9/tplx;c727-b58f/~tNl;bf7d-a45f/tyf, PASS, wireless adhoc Ipv4 -/examples/manetrouting/gpsr/, -f omnetpp.ini -c MultiIPv6 -r 0, 20s, e008-a90a/tplx;eead-fc34/~tNl;273d-a6b5/tyf, PASS, wireless adhoc +/examples/manetrouting/gpsr/, -f omnetpp.ini -c MultiIPv6 -r 0, 20s, 846c-992a/tplx;a059-a5c2/~tNl;0df2-3b89/tyf, PASS, wireless adhoc /examples/manetrouting/gpsr/, -f omnetpp.ini -c MultiGeneric -r 0, 20s, 5c6b-556c/tplx;e56d-c248/~tNl;c35c-205b/tyf, PASS, wireless adhoc /examples/manetrouting/gpsr/, -f omnetpp.ini -c Manual -r 0, 20s, 41d0-4325/tplx;b911-bca9/~tNl;8a0e-d569/tyf, PASS, wireless adhoc Ipv4 # /examples/manetrouting/gpsr/, -f omnetpp.ini -c _Dynamic -r 0, # abstract-config /examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicIPv4 -r 0, 20s, ce31-f8ed/tplx;8a3b-235d/~tNl;3ecd-bfef/tyf, PASS, wireless adhoc Ipv4 -/examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicIPv6 -r 0, 20s, e618-be99/tplx;edb3-6679/~tNl;2653-aa28/tyf, PASS, wireless adhoc +/examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicIPv6 -r 0, 20s, a9d2-04b7/tplx;2cde-e11d/~tNl;9909-a6b4/tyf, PASS, wireless adhoc /examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicGeneric -r 0, 20s, 732c-c576/tplx;03c2-f3ec/~tNl;fb81-3dac/tyf, PASS, wireless adhoc /examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, 55c4-44c4/tplx;92b9-416a/~tNl, PASS, wireless adhoc Ipv4 diff --git a/tests/module/IPv6_ND_solicitation_ends_address_resolution.test b/tests/module/IPv6_ND_solicitation_ends_address_resolution.test new file mode 100644 index 00000000000..92e1bdaf59e --- /dev/null +++ b/tests/module/IPv6_ND_solicitation_ends_address_resolution.test @@ -0,0 +1,79 @@ +%description: +A Neighbor Solicitation that carries a Source Link-Layer Address option and finds an +INCOMPLETE Neighbour Cache entry ends address resolution for that entry: the entry becomes +STALE (RFC 4861 Section 7.2.3), its queued packets are sent, and no further multicast +Neighbor Solicitation is sent for it (Section 7.2.2 and Appendix C). The test checks the +last: the other two are masked by the Neighbor Advertisement that follows. + +Topology: a -- b + +a and b start sending User Datagram Protocol (UDP) traffic to each other at the same moment, +so each resolves the other's address while being resolved. Each host's entry for the other +is INCOMPLETE when the other's multicast Neighbor Solicitation arrives. Neither host may +retransmit a Neighbor Solicitation for address resolution, and neither may declare address +resolution failed and delete the entry. The test also checks that address resolution starts, +so that it cannot pass without the case. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned + +import inet.networklayer.configurator.ipv6.Ipv6FlatNetworkConfigurator; +import inet.node.ipv6.StandardHost6; +import ned.DatarateChannel; + +network AddressResolutionTest +{ + types: + channel ethline extends DatarateChannel + { + delay = 0.1us; + datarate = 10Mbps; + } + submodules: + configurator: Ipv6FlatNetworkConfigurator; + a: StandardHost6; + b: StandardHost6; + connections: + a.ethg++ <--> ethline <--> b.ethg++; +} + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini + +[General] +network = AddressResolutionTest +ned-path = .;../../../../src +sim-time-limit = 10s +record-vector-results = false +cmdenv-express-mode = false + +**.numApps = 1 +**.app[0].typename = "UdpBasicApp" +**.app[0].localPort = 1000 +**.app[0].destPort = 1000 +**.app[0].messageLength = 64B +**.app[0].sendInterval = 1s +**.app[0].startTime = 6s +**.a.app[0].destAddresses = "b" +**.b.app[0].destAddresses = "a" + +**.eth[*].queue.typename = "EthernetQosQueue" +**.eth[*].queue.dataQueue.typename = "DropTailQueue" +**.eth[*].queue.dataQueue.packetCapacity = 10 + +**.neighbourDiscovery.cmdenv-log-level = detail +**.cmdenv-log-level = off + +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Initiating Address Resolution for: +%#-------------------------------------------------------------------------------------------------------------- +%not-contains: stdout +Sending another Address Resolution NS message +%#-------------------------------------------------------------------------------------------------------------- +%not-contains: stdout +Address Resolution has failed. +%#-------------------------------------------------------------------------------------------------------------- +%not-contains: stdout + Error +%#--------------------------------------------------------------------------------------------------------------