diff --git a/WHATSNEW b/WHATSNEW index d0bbfb448c7..3ec537e9faa 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. Address resolution sends no Neighbor Solicitation from a tentative address + + Address resolution sent its Neighbor Solicitation from a tentative address, for example under Optimistic Duplicate Address Detection, although RFC 4861 Section 7.2.2 takes the source from the assigned addresses, RFC 4862 does not count a tentative address as assigned, and RFC 4429 forbids an Optimistic address as the source of a Neighbor Solicitation. The solicitation now uses an address that is not tentative, keeping the previous choice when that address is assigned; if the interface has none yet, address resolution starts when Duplicate Address Detection of an address completes. If Duplicate Address Detection finds the link-local address duplicate before any other address is assigned, datagrams to unresolved neighbours now stay queued instead of being returned with an ICMPv6 error. Simulations in which a node sends IPv6 traffic to an unresolved neighbour while every address of the interface is tentative produce different results, for example examples/ipv6/mipv6, examples/ipv6/mipv6roaming and examples/inet/hierarchical99. + 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..c115247c7df 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc @@ -44,6 +44,27 @@ simsignal_t Ipv6NeighbourDiscovery::startDadSignal = registerSignal("startDad"); simsignal_t Ipv6NeighbourDiscovery::dadCompletedSignal = registerSignal("dadCompleted"); simsignal_t Ipv6NeighbourDiscovery::dadFailedSignal = registerSignal("dadFailed"); +// RFC 4861 Section 7.2.2: the source of an address-resolution NS is the source of the +// prompting packet if it is assigned to the interface, otherwise any assigned address. +// A tentative address is not assigned (RFC 4862 Section 5.4), and an Optimistic address +// MUST NOT be the source of an NS (RFC 4429 Section 3.2). Returns the unspecified +// address if the interface has no assigned address. +static Ipv6Address chooseNsSourceAddress(const Ipv6InterfaceData *ipv6Data, const Ipv6Address& dgSrcAddr) +{ + if (ipv6Data->hasAddress(dgSrcAddr) && !ipv6Data->isTentativeAddress(dgSrcAddr)) + return dgSrcAddr; + const Ipv6Address& preferredAddr = ipv6Data->getPreferredAddress(); + if (!preferredAddr.isUnspecified() && !ipv6Data->isTentativeAddress(preferredAddr)) + return preferredAddr; + const Ipv6Address& linkLocalAddr = ipv6Data->getLinkLocalAddress(); + if (!linkLocalAddr.isUnspecified() && !ipv6Data->isTentativeAddress(linkLocalAddr)) + return linkLocalAddr; + for (int i = 0; i < ipv6Data->getNumAddresses(); i++) + if (!ipv6Data->isTentativeAddress(i) && ipv6Data->getAddress(i).isUnicast()) + return ipv6Data->getAddress(i); + return Ipv6Address::UNSPECIFIED_ADDRESS; +} + Ipv6NeighbourDiscovery::Ipv6NeighbourDiscovery() : neighbourCache(*this) { @@ -670,15 +691,16 @@ void Ipv6NeighbourDiscovery::initiateAddressResolution(const Ipv6Address& dgSrcA Ipv6Address nsSrcAddr; /*If the source address of the packet prompting the solicitation is the - same as one of the addresses assigned to the outgoing interface,*/ - if (ie->getProtocolData()->hasAddress(dgSrcAddr)) - /*that address SHOULD be placed in the IP Source Address of the outgoing - solicitation.*/ - nsSrcAddr = dgSrcAddr; - else - /*Otherwise, any one of the addresses assigned to the interface - should be used.*/ - nsSrcAddr = ie->getProtocolData()->getPreferredAddress(); + same as one of the addresses assigned to the outgoing interface, that + address SHOULD be placed in the IP Source Address of the outgoing + solicitation. Otherwise, any one of the addresses assigned to the + interface should be used.*/ + nsSrcAddr = chooseNsSourceAddress(ie->getProtocolData(), dgSrcAddr); + if (nsSrcAddr.isUnspecified()) { + // makeTentativeAddressPermanent() starts address resolution when an address is assigned + EV_INFO << "No address assigned to the interface yet, address resolution waits for DAD\n"; + return; + } ASSERT(ifID != -1); // Sending NS on specified interface. createAndSendNsPacket(nsTargetAddr, nsDestAddr, nsSrcAddr, ie); @@ -706,6 +728,19 @@ void Ipv6NeighbourDiscovery::processArTimeout(cMessage *arTimeoutMsg) EV_INFO << "Max Multicast Solicitation:" << ie->getProtocolData()->_getMaxMulticastSolicit() << endl; if (nce->numOfARNSSent < ie->getProtocolData()->_getMaxMulticastSolicit()) { + // the source chosen for the first NS may have become tentative or been removed since + const auto *ipv6Data = ie->getProtocolData(); + if (!ipv6Data->hasAddress(nce->nsSrcAddr) || ipv6Data->isTentativeAddress(nce->nsSrcAddr)) { + Ipv6Address dgSrcAddr = nce->pendingPackets.empty() ? Ipv6Address::UNSPECIFIED_ADDRESS : nce->pendingPackets.front()->peekAtFront()->getSrcAddress(); + nce->nsSrcAddr = chooseNsSourceAddress(ipv6Data, dgSrcAddr); + if (nce->nsSrcAddr.isUnspecified()) { + // makeTentativeAddressPermanent() restarts address resolution + EV_INFO << "No address assigned to the interface any more, address resolution waits for DAD\n"; + nce->arTimer = nullptr; + delete arTimeoutMsg; + return; + } + } EV_INFO << "Sending another Address Resolution NS message" << endl; Ipv6Address nsDestAddr = nsTargetAddr.formSolicitedNodeMulticastAddress(); createAndSendNsPacket(nsTargetAddr, nsDestAddr, nce->nsSrcAddr, ie); @@ -896,6 +931,14 @@ void Ipv6NeighbourDiscovery::makeTentativeAddressPermanent(const Ipv6Address& te ie->getProtocolDataForUpdate()->setDadInProgress(false); + // start the address resolution that waited for an assigned address (see initiateAddressResolution()) + for (auto& elem : neighbourCache) { + Neighbour& nce = elem.second; + if (elem.first.interfaceID == ie->getInterfaceId() && nce.reachabilityState == Ipv6NeighbourCache::INCOMPLETE + && nce.arTimer == nullptr && !nce.pendingPackets.empty()) + initiateAddressResolution(nce.pendingPackets.front()->peekAtFront()->getSrcAddress(), &nce); + } + if (!tentativeAddr.isLinkLocal()) { // DAD completed for a global address -- nothing else to do return; diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 0180b3bb5a0..20f307e55c0 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -165,7 +165,7 @@ # /examples/inet/hierarchical99/, -f .qtenv.ini -c General -r 0 /examples/inet/hierarchical99/, -f networklayer.ini -c IPv4 -r 0, 10000s, a94d-edae/tplx;1f32-e325/~tNl;b138-ee8b/~tND;66c9-cd73/tyf, PASS, EthernetMac Ipv4 -/examples/inet/hierarchical99/, -f networklayer.ini -c IPv6 -r 0, 10000s, 0001-0108/tplx;3960-479e/~tNl;add1-e848/~tND;2c03-abef/tyf, PASS, EthernetMac +/examples/inet/hierarchical99/, -f networklayer.ini -c IPv6 -r 0, 10000s, acf6-4224/tplx;af7d-b669/~tNl;710c-34d1/~tND;d96c-23d2/tyf, PASS, EthernetMac /examples/inet/hierarchical99/, -f networklayer.ini -c Generic -r 0, 10000s, 6ae4-6e75/tplx;2209-80ac/~tNl;93bf-6657/tyf, PASS, EthernetMac /examples/inet/hierarchical99/, -f networklayer.ini -c Flooding -r 0, 10000s, 7ba7-5b91/tplx;b0f0-411e/~tNl;2b94-8906/tyf, PASS, EthernetMac /examples/inet/hierarchical99/, -f networklayer.ini -c ProbabilisticBroadcast -r 0, 10000s, 5a54-9779/tplx;1165-a70b/~tNl;760b-abfc/tyf, PASS, EthernetMac @@ -380,9 +380,9 @@ /examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, 55c4-44c4/tplx;92b9-416a/~tNl, PASS, wireless adhoc Ipv4 /examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, 5ed8-cf6b/tplx;b9f1-61b0/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 -/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 68ea-9693/tplx;a44c-17bf/~tNl;96ff-ed7c/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, c073-8d42/tplx;a457-16df/~tNl;3d28-8302/~tND;7491-bcad/tyf, PASS, wireless EthernetMac /examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, bacf-986a/tplx;e6db-28c0/~tNl;805b-8a25/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, b275-b584/tplx;28ce-974e/~tNl;dcec-ae59/~tND;afae-2b3c/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 8ea9-9430/tplx;9273-d4eb/~tNl;6c79-6458/~tND;f3a3-116d/tyf, PASS, wireless EthernetMac /examples/ipv6/pmipv6/, -f omnetpp.ini -c General -r 0, 60s, e801-4d1a/tplx;1064-9bd4/~tNl;bd46-d85c/~tND;0277-d784/tyf, PASS, wireless EthernetMac /examples/mobility/, -f omnetpp.ini -c AnsimMobility -r 0, 10000s, 72f8-5c0b/tplx;0000-0000/~tNl;0000-0000/~tND;7dd1-18eb/tyf, PASS, diff --git a/tests/fingerprint/mipv6-refactoring.csv b/tests/fingerprint/mipv6-refactoring.csv index 4e8ebad4ab4..a6c72fdb77c 100644 --- a/tests/fingerprint/mipv6-refactoring.csv +++ b/tests/fingerprint/mipv6-refactoring.csv @@ -23,7 +23,7 @@ /examples/ipv6/nclients/, -f omnetpp.ini -c PPP_SCTP -r 0, 100s, 3a7c-924c/~tNlb, PASS, # IPv6 networking examples -/examples/inet/hierarchical99/, -f networklayer.ini -c IPv6 -r 0, 10000s, f206-a0e2/~tNlb, PASS, EthernetMac +/examples/inet/hierarchical99/, -f networklayer.ini -c IPv6 -r 0, 10000s, 082b-e145/~tNlb, PASS, EthernetMac /examples/inet/udpclientserver/, -f omnetpp.ini -c udp_OK_ipv6 -r 0, 10s, 8feb-3936/~tNlb, PASS, /examples/inet/udpclientserver/, -f omnetpp.ini -c udp_Port_Unav_ipv6 -r 0, 10s, 9661-c1f5/~tNlb, PASS, /examples/inet/udpclientserver/, -f omnetpp.ini -c udp_Host_Unav_ipv6 -r 0, 10s, 6360-9ed1/~tNlb, PASS, diff --git a/tests/module/IPv6_ND_no_solicitation_from_tentative_address.test b/tests/module/IPv6_ND_no_solicitation_from_tentative_address.test new file mode 100644 index 00000000000..2a36e8535df --- /dev/null +++ b/tests/module/IPv6_ND_no_solicitation_from_tentative_address.test @@ -0,0 +1,71 @@ +%description: +A host sends no address-resolution Neighbor Solicitation from a tentative address: RFC 4861 +Section 7.2.2 takes the source from the addresses assigned to the interface, RFC 4862 +Section 5.4 does not count a tentative address as assigned, and RFC 4429 Section 3.2 forbids +an Optimistic address as the source of a Neighbor Solicitation. With no address assigned +yet, address resolution waits for Duplicate Address Detection (DAD) to complete. + +Topology: router -- host + +The host uses Optimistic DAD and pings the router's link-local address from t=0, so its only +address, the link-local one, is tentative when the first ping needs address resolution. Only +the host's Neighbour Discovery and network-layer dispatcher log. No Neighbor Solicitation +from the host's link-local address to the router's solicited-node address may come before +the host's "DAD completed" line, and one must come after it. The packet printer colours the +fields, so the patterns allow other characters between them. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned + +import inet.networklayer.configurator.ipv6.Ipv6FlatNetworkConfigurator; +import inet.node.ipv6.Router6; +import inet.node.ipv6.StandardHost6; +import ned.DatarateChannel; + +network TentativeSolicitationTest +{ + types: + channel ethline extends DatarateChannel + { + delay = 0.1us; + datarate = 10Mbps; + } + submodules: + configurator: Ipv6FlatNetworkConfigurator; + router: Router6; + host: StandardHost6; + connections: + router.ethg++ <--> ethline <--> host.ethg++; +} + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini + +[General] +network = TentativeSolicitationTest +ned-path = .;../../../../src +sim-time-limit = 3s +record-vector-results = false +cmdenv-express-mode = false + +**.neighbourDiscovery.optimisticDad = true +**.host.numApps = 1 +**.host.app[0].typename = "PingApp" +**.host.app[0].destAddr = "fe80::8aa:ff:fe00:1" +**.host.app[0].startTime = 0s +**.host.app[0].sendInterval = 0.2s + +**.host.ipv6.neighbourDiscovery.cmdenv-log-level = info +**.host.nl.cmdenv-log-level = trace +**.cmdenv-log-level = off + +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +DAD completed for address fe80::8aa:ff:fe00:2 on eth0, address is unique[\s\S]*NeighbourSolicitation[^,]*, [^,]*sourceAddress[^,]*= fe80::8aa:ff:fe00:2, [^,]*destinationAddress[^,]*= ff02::1:ff00:1, +%#-------------------------------------------------------------------------------------------------------------- +%not-contains-regex: stdout +NeighbourSolicitation[^,]*, [^,]*sourceAddress[^,]*= fe80::8aa:ff:fe00:2, [^,]*destinationAddress[^,]*= ff02::1:ff00:1,[\s\S]*DAD completed for address fe80::8aa:ff:fe00:2 +%#-------------------------------------------------------------------------------------------------------------- +%not-contains: stdout + Error +%#--------------------------------------------------------------------------------------------------------------