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 +%#--------------------------------------------------------------------------------------------------------------