From 6e7a7e0a7e4c0b9b9944ba5b9e9cd18fd2ab0751 Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Sat, 12 Sep 2026 13:41:26 +0200 Subject: [PATCH 1/2] ipv6: add an address probe to Neighbour Discovery Ipv6NeighbourDiscovery could run Duplicate Address Detection only for an address it was about to assign: initiateDad() puts the address on the interface, and makeTentativeAddressPermanent() keeps it there when the probe succeeds. RFC 6275 Section 10.3.1 needs the other shape. Before a home agent accepts a first home registration it must run Duplicate Address Detection for the mobile node's home address on the home link -- an address it must not take for itself, because Ipv6InterfaceData::hasAddress() answers true for a tentative address as well, so assigning it would make the home agent start accepting packets that belong to the mobile node. Add startAddressProbe(), which sends the RFC 4862 Section 5.4.2 solicitations without ever touching the interface and reports the outcome through the new IAddressProbeHandler interface. cancelAddressProbe() abandons a probe, and isAddressProbeRunning() lets a caller that holds state for the duration of one notice a probe that ended without a callback -- stop() drops the probe list, so the callback is promised at most once, not exactly once. The random RFC 4862 Section 5.4.2 term delays the first solicitation, rather than being added to the timeout as initiateDad() does. The join must precede the solicitation, so delaying the join delays the solicitation with it; adding it to the timeout instead is issue #1179, which pull request #1183 repairs for initiateDad(). The total wait is the same either way, so this only decides when the solicitation leaves, and a new probe should not be written with the defect in it. Probing an address this node already holds reports it in use rather than asserting. That is the honest answer to the question the probe asks, and it is the state a home agent will be in once it proxies the address. A duplicate is reported when another node defends the address with a Neighbor Advertisement, which arrives because a defending advertisement goes to the all-nodes multicast address. The competing case -- another node running Duplicate Address Detection for the same address at the same time -- is not reported. That solicitation goes to the solicited-node multicast address of the probed address, and two gates stop it: Ipv6::routeMulticastPacket() delivers a multicast locally only if the node holds the address or joined the group, and processNsPacket() then discards a solicitation whose target the node does not hold. A proxying home agent passes both, so that case belongs with the proxy Neighbor Discovery work. There is no caller yet, so no behaviour changes and no baseline moves. --- .../icmpv6/IAddressProbeHandler.h | 37 ++++ .../icmpv6/Ipv6NeighbourDiscovery.cc | 159 ++++++++++++++++++ .../icmpv6/Ipv6NeighbourDiscovery.h | 69 ++++++++ 3 files changed, 265 insertions(+) create mode 100644 src/inet/networklayer/icmpv6/IAddressProbeHandler.h diff --git a/src/inet/networklayer/icmpv6/IAddressProbeHandler.h b/src/inet/networklayer/icmpv6/IAddressProbeHandler.h new file mode 100644 index 00000000000..e9257b24794 --- /dev/null +++ b/src/inet/networklayer/icmpv6/IAddressProbeHandler.h @@ -0,0 +1,37 @@ +// +// Copyright (C) 2026 OpenSim Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IADDRESSPROBEHANDLER_H +#define __INET_IADDRESSPROBEHANDLER_H + +#include "inet/common/INETDefs.h" +#include "inet/networklayer/contract/ipv6/Ipv6Address.h" + +namespace inet { + +class NetworkInterface; + +/** + * Receives the outcome of an address probe started with + * Ipv6NeighbourDiscovery::startAddressProbe(). + */ +class INET_API IAddressProbeHandler { +public: + virtual ~IAddressProbeHandler() = default; + + /** + * Called at most once, when the probe of addr on ie ends. It is not called for a probe + * abandoned with cancelAddressProbe(), or dropped because Neighbour Discovery stopped; + * a handler that keeps state per probe must therefore be able to discard it unprompted. + * + * @param unique true when no other node claimed the address, false when one defended it + */ + virtual void addressProbeCompleted(const Ipv6Address& addr, NetworkInterface *ie, bool unique) = 0; +}; + +} // namespace inet + +#endif diff --git a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc index 9785d3f5522..8b0338ba725 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc @@ -37,6 +37,7 @@ namespace inet { #define MK_RD_TIMEOUT 5 #define MK_NUD_TIMEOUT 6 #define MK_AR_TIMEOUT 7 +#define MK_ADDRESS_PROBE_TIMEOUT 8 Define_Module(Ipv6NeighbourDiscovery); @@ -61,6 +62,11 @@ Ipv6NeighbourDiscovery::~Ipv6NeighbourDiscovery() delete entry; } + for (auto *entry : addressProbeList) { + cancelAndDelete(entry->timeoutMsg); + delete entry; + } + for (auto *entry : rdList) { cancelAndDelete(entry->timeoutMsg); delete entry; @@ -159,6 +165,10 @@ void Ipv6NeighbourDiscovery::handleMessageWhenUp(cMessage *msg) EV_INFO << "DAD Timeout message received\n"; processDadTimeout(msg); } + else if (msg->getKind() == MK_ADDRESS_PROBE_TIMEOUT) { + EV_INFO << "Address probe timeout message received\n"; + processAddressProbeTimeout(msg); + } else if (msg->getKind() == MK_RD_TIMEOUT) { EV_INFO << "Router Discovery message received\n"; processRdTimeout(msg); @@ -1001,6 +1011,136 @@ void Ipv6NeighbourDiscovery::dadHasFailed(const Ipv6Address& duplicateAddr, Netw emit(dadFailedSignal, 1); } +Ipv6NeighbourDiscovery::AddressProbeEntry *Ipv6NeighbourDiscovery::findAddressProbe( + const Ipv6Address& addr, int interfaceId) +{ + for (auto *entry : addressProbeList) + if (entry->interfaceId == interfaceId && entry->address == addr) + return entry; + + return nullptr; +} + +void Ipv6NeighbourDiscovery::startAddressProbe(const Ipv6Address& addr, NetworkInterface *ie, + IAddressProbeHandler *handler) +{ + Enter_Method("startAddressProbe"); + + ASSERT(handler != nullptr); + + if (findAddressProbe(addr, ie->getInterfaceId()) != nullptr) + throw cRuntimeError("A probe of %s on %s is already running", + addr.str().c_str(), ie->getInterfaceName()); + + // If this node holds the address itself -- as a home agent proxying it does, RFC 6275 + // Section 10.4.1 -- then it is in use on this link, which is what the probe asks. Answer + // that rather than aborting; hasAddress() covers tentative addresses too. + if (ie->getProtocolData()->hasAddress(addr)) { + EV_INFO << addr << " is already held on " << ie->getInterfaceName() + << ", reporting it in use without probing\n"; + handler->addressProbeCompleted(addr, ie, false); + return; + } + + // RFC 4862 Section 5.4: a DupAddrDetectTransmits of zero turns Duplicate Address Detection + // off on this interface, so there is nothing to send -- report the address unique. + if (ie->getProtocolData()->getDupAddrDetectTransmits() == 0) { + EV_INFO << "Duplicate Address Detection is disabled on " << ie->getInterfaceName() + << ", reporting " << addr << " unique without probing\n"; + handler->addressProbeCompleted(addr, ie, true); + return; + } + + EV_INFO << "Probing " << addr << " on " << ie->getInterfaceName() << "\n"; + + // the entry is fully built before it is published, so that findAddressProbe() can never + // hand out one whose timeout message is not set yet + AddressProbeEntry *entry = new AddressProbeEntry(); + entry->interfaceId = ie->getInterfaceId(); + entry->address = addr; + entry->handler = handler; + entry->timeoutMsg = new cMessage("addressProbeTimeout", MK_ADDRESS_PROBE_TIMEOUT); + entry->timeoutMsg->setContextPointer(entry); + addressProbeList.push_back(entry); + + /*RFC 4862 Section 5.4.2 + Before sending a Neighbor Solicitation, an interface MUST join the all-nodes multicast + address and the solicited-node multicast address of the tentative address.*/ + /*If the Neighbor Solicitation is going to be the first message sent from an interface + after interface (re)initialization, the node SHOULD delay joining the solicited-node + multicast address by a random delay between 0 and MAX_RTR_SOLICITATION_DELAY.*/ + // The join has to precede the solicitation, so delaying the join delays the solicitation + // with it. processAddressProbeTimeout() sends this first solicitation and every later + // one, each separated by RetransTimer. initiateDad() adds this term to the timeout + // instead, which is issue #1179. + scheduleAfter(uniform(0, IPv6_MAX_RTR_SOLICITATION_DELAY), entry->timeoutMsg); +} + +bool Ipv6NeighbourDiscovery::isAddressProbeRunning(const Ipv6Address& addr, NetworkInterface *ie) +{ + Enter_Method("isAddressProbeRunning"); + return findAddressProbe(addr, ie->getInterfaceId()) != nullptr; +} + +void Ipv6NeighbourDiscovery::processAddressProbeTimeout(cMessage *msg) +{ + AddressProbeEntry *entry = (AddressProbeEntry *)msg->getContextPointer(); + NetworkInterface *ie = ift->getInterfaceById(entry->interfaceId); + + if (entry->numNSSent < ie->getProtocolData()->getDupAddrDetectTransmits()) { + /*RFC 4862 Section 5.4.2: the solicitation's Target Address is set to the address being + checked, the IP source is set to the unspecified address and the IP destination is + set to the solicited-node multicast address of the target address.*/ + EV_DETAIL << "Sending probe solicitation " << entry->numNSSent + 1 << " for " + << entry->address << "\n"; + createAndSendNsPacket(entry->address, entry->address.formSolicitedNodeMulticastAddress(), + Ipv6Address::UNSPECIFIED_ADDRESS, ie); + entry->numNSSent++; + // reuse the received msg + scheduleAfter(ie->getProtocolData()->getRetransTimer(), msg); + return; + } + + EV_INFO << "No node answered for " << entry->address << " on " << ie->getInterfaceName() + << ", address is unique\n"; + + Ipv6Address addr = entry->address; + IAddressProbeHandler *handler = entry->handler; + addressProbeList.erase(std::find(addressProbeList.begin(), addressProbeList.end(), entry)); + delete entry; + delete msg; + + handler->addressProbeCompleted(addr, ie, true); +} + +void Ipv6NeighbourDiscovery::addressProbeHasFailed(const Ipv6Address& addr, NetworkInterface *ie) +{ + AddressProbeEntry *entry = findAddressProbe(addr, ie->getInterfaceId()); + ASSERT(entry != nullptr); + + EV_WARN << "Another node defended " << addr << " on " << ie->getInterfaceName() + << ", the address is already in use on this link\n"; + + IAddressProbeHandler *handler = entry->handler; + cancelAndDelete(entry->timeoutMsg); + addressProbeList.erase(std::find(addressProbeList.begin(), addressProbeList.end(), entry)); + delete entry; + + handler->addressProbeCompleted(addr, ie, false); +} + +void Ipv6NeighbourDiscovery::cancelAddressProbe(const Ipv6Address& addr, NetworkInterface *ie) +{ + Enter_Method("cancelAddressProbe"); + + if (AddressProbeEntry *entry = findAddressProbe(addr, ie->getInterfaceId())) { + EV_INFO << "Abandoning the probe of " << addr << " on " << ie->getInterfaceName() << "\n"; + cancelAndDelete(entry->timeoutMsg); + addressProbeList.erase(std::find(addressProbeList.begin(), addressProbeList.end(), entry)); + delete entry; + } +} + void Ipv6NeighbourDiscovery::createAndSendRsPacket(NetworkInterface *ie) { ASSERT(ie->getProtocolData()->getAdvSendAdvertisements() == false); @@ -2158,6 +2298,18 @@ void Ipv6NeighbourDiscovery::processNaPacket(Packet *packet, const Ipv6Neighbour delete packet; return; } + + // A node defending an address we are probing on someone else's behalf ends that probe: + // the address is in use on this link (RFC 6275 Section 10.3.1, home agent side). Only a + // defending advertisement can end a probe this way -- see startAddressProbe() on why a + // competing solicitation never arrives. + if (findAddressProbe(naTargetAddr, ie->getInterfaceId()) != nullptr) { + EV_WARN << "Received NA for probed address " << naTargetAddr << " - address is in use\n"; + addressProbeHasFailed(naTargetAddr, ie); + delete packet; + return; + } + // Logic as defined in Section 7.2.5 Neighbour *neighbourEntry = neighbourCache.lookup(naTargetAddr, ie->getInterfaceId()); @@ -2683,6 +2835,13 @@ void Ipv6NeighbourDiscovery::stop() } dadList.clear(); + // cancel and delete all address probe entries + for (auto *entry : addressProbeList) { + cancelAndDelete(entry->timeoutMsg); + delete entry; + } + addressProbeList.clear(); + // cancel and delete all RD entries for (auto *entry : rdList) { cancelAndDelete(entry->timeoutMsg); diff --git a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h index f122b6e9399..abe1b81f17b 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h @@ -16,6 +16,7 @@ #include "inet/common/lifecycle/ModuleOperations.h" #include "inet/common/packet/Packet.h" #include "inet/networklayer/contract/ipv6/Ipv6Address.h" +#include "inet/networklayer/icmpv6/IAddressProbeHandler.h" #include "inet/networklayer/icmpv6/Ipv6NdMessage_m.h" #include "inet/networklayer/icmpv6/Ipv6NeighbourCache.h" #include "inet/common/checksum/ChecksumMode_m.h" @@ -83,6 +84,43 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe */ virtual void reachabilityConfirmed(const Ipv6Address& neighbour, int interfaceId); + /** + * Runs Duplicate Address Detection (RFC 4862 Section 5.4) on the given interface for an + * address this node does NOT own, and reports the outcome to the handler. + * + * A home agent needs exactly this. RFC 6275 Section 10.3.1 requires it to run Duplicate + * Address Detection for the mobile node's home address on the home link before it returns + * a Binding Acknowledgement, but it must not take the address for itself. initiateDad() + * cannot serve: it assigns the probed address to the interface -- and hasAddress() answers + * true for a tentative address too, so the node would start accepting packets sent to it -- + * and makes the address permanent once the probe succeeds. + * + * A duplicate is reported when another node defends the address with a Neighbor + * Advertisement, which arrives because a defending advertisement is sent to the all-nodes + * multicast address. The competing case, another node running Duplicate Address Detection + * for the same address at the same time, is not reported. That Neighbor Solicitation goes + * to the solicited-node multicast address of the probed address, and two gates stop it: + * Ipv6::routeMulticastPacket() delivers it locally only if the node holds the address or + * has joined the group (a multicast-forwarding router delivers all ICMPv6 regardless, so + * this gate alone is not enough), and processNsPacket() then discards any solicitation + * whose target this node does not hold. A home agent that proxies the address passes both, + * so this case belongs with the proxy Neighbor Discovery work. + */ + virtual void startAddressProbe(const Ipv6Address& addr, NetworkInterface *ie, IAddressProbeHandler *handler); + + /** + * Abandons a probe started with startAddressProbe() without calling the handler. + * Does nothing when no such probe is running. + */ + virtual void cancelAddressProbe(const Ipv6Address& addr, NetworkInterface *ie); + + /** + * Returns true while a probe started with startAddressProbe() is still running for the + * given address on the given interface. A caller that holds state for the duration of a + * probe can use this to notice a probe that was dropped without a callback. + */ + virtual bool isAddressProbeRunning(const Ipv6Address& addr, NetworkInterface *ie); + protected: // Packets awaiting Address Resolution or Next-Hop Determination. @@ -112,6 +150,18 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe }; typedef std::vector DadList; + // stores information about a pending probe of an address this node does not own + // (RFC 6275 Section 10.3.1: a home agent verifying a mobile node's home address on + // the home link before it accepts a home registration) + struct AddressProbeEntry { + int interfaceId = -1; // interface the probe runs on + Ipv6Address address; // address probed; never assigned to the interface + int numNSSent = 0; // number of probe solicitations sent so far + cMessage *timeoutMsg = nullptr; // the message to cancel when the probe ends + IAddressProbeHandler *handler = nullptr; // notified when the probe ends + }; + typedef std::vector AddressProbeList; + // stores information about Router Discovery for an interface struct RdEntry { int interfaceId; // interface on which Router Discovery is performed @@ -139,6 +189,9 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe // List of pending Duplicate Address Detections DadList dadList; + // List of pending probes of addresses this node does not own + AddressProbeList addressProbeList; + // List of pending Router & Prefix Discoveries RdList rdList; @@ -277,6 +330,22 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe */ virtual void dadHasFailed(const Ipv6Address& duplicateAddr, NetworkInterface *ie); + /** + * Returns the running probe of the given address on the given interface, or nullptr. + */ + virtual AddressProbeEntry *findAddressProbe(const Ipv6Address& addr, int interfaceId); + + /** + * Sends the next probe solicitation, or ends the probe and reports the address unique + * once dupAddrDetectTransmits solicitations have gone unanswered. + */ + virtual void processAddressProbeTimeout(cMessage *msg); + + /** + * Ends a running probe and reports the probed address as a duplicate. + */ + virtual void addressProbeHasFailed(const Ipv6Address& addr, NetworkInterface *ie); + /************Address Autoconfiguration Stuff***************************/ /** * as it is not possbile to explicitly define RFC 2462. ND is the next From be1c9cf907b9a12e11773ef985e0e2583b4666f1 Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Sat, 12 Sep 2026 13:44:21 +0200 Subject: [PATCH 2/2] mipv6: verify the home address before acknowledging a home registration RFC 6275 Section 10.3.1: Unless this home agent already has a binding for the given home address, the home agent MUST perform Duplicate Address Detection on the mobile node's home link before returning the Binding Acknowledgement. ... If this Duplicate Address Detection fails for the given home address or an associated link local address, then the home agent MUST reject the complete Binding Update and MUST return a Binding Acknowledgement to the mobile node, in which the Status field is set to 134. Neither half was implemented. The check was replaced by a hardcoded one second delay on the acknowledgement, sendTime = existingBinding ? 0 : 1, carrying the admission "TODO solve the HA DAD problem in a different way"; the rejection path was a bare TODO, so status 134 could never be sent. Reproduce the defect on the unfixed tree: cd examples/ipv6/mipv6 inet -u Cmdenv -c Handover -r 0 --seed-set=1 --sim-time-limit=25s \ --cmdenv-express-mode=false --cmdenv-log-level=off \ '--*.Home_Agent.ipv6.neighbourDiscovery.cmdenv-log-level=trace' \ '--*.Home_Agent.ipv6.mipv6.cmdenv-log-level=info' The home agent logs Duplicate Address Detection exactly twice, both at t=0.0436 s for its own two interfaces at boot, and never at t=21.712822 s when the Binding Update arrives and is validated. No solicitation for the home address is ever sent. The acknowledgement arrives 1.0015 s after the Binding Update on every seed set: 1.001527, 1.001727 and 1.001587 s for sets 1 to 3, one second of sendDelayed plus link latency. Run the probe instead, and hold the acknowledgement until it ends: status 0 when nobody answers, status 134 with the binding, its tunnel and its expiry timer withdrawn when another node defends the address. With no caller left passing a delay, the sendTime parameter and the delayed-send branch it fed go with it. Three deltas in behaviour, all intended. The acknowledgement delay now varies per seed, 1.036, 1.451 and 1.911 s for seed sets 1 to 3, because real Duplicate Address Detection costs retransTimer plus the RFC 4862 Section 5.4.2 group-join term. It was a constant standing in for a quantity that genuinely has spread. A first registration draws one Binding Acknowledgement instead of two. The mobile node's retransmission -- its first-registration timeout is one second, see issues #1132 and #1133 -- used to arrive after the Binding Cache entry already existed, so it was read as a re-registration and acknowledged at once, on top of the delayed acknowledgement owed to the original. A registration still waiting for its probe is no longer a binding the home agent already has. A home agent that receives a Binding Update with the home registration flag clear now acknowledges it immediately rather than one second later. Master delayed every first-time Binding Update a home agent saw; RFC 6275 Section 9.5.4 asks for no Duplicate Address Detection on a correspondent binding. Three fingerprint rows move, all in examples.csv, and each for the same reason: the mipv6 Handover, mipv6 RouteOptimizationTwoCNs and mipv6roaming Roaming scenarios are the only ones in the suite where a home agent accepts a first home registration. In each, the tplx, ~tNl and ~tND values move because the acknowledgement now waits for a real probe instead of a constant, because the home agent's probe solicitations are new packets on the home link, and because the duplicate acknowledgement is gone. No other row moves: the remaining 13 IPv6 rows matched in examples.csv are unchanged, pmipv6 among them, which shares the same Ipv6NeighbourDiscovery. mipv6-refactoring.csv is deliberately not touched. Its three MIPv6 rows already fail on origin/master 021485b99c, which was confirmed by building and running that commit unchanged; the recent IEEE 802.11 and serializer work moved them and re-recorded examples.csv without them. Re-recording them here would fold that unrelated movement into this commit. MIPv6_home_agent_dad covers the success path and pins the acknowledgement count at one. MIPv6_home_agent_dad_duplicate covers the rejection: a squatter on the home link carries the mobile node's MAC, so autoconfiguration gives them the same home address, and Duplicate Address Detection is switched off on both so that neither defends against the other while they boot. The home agent, which keeps it on, then probes, is answered, and rejects. That test also shows a limitation this change makes reachable: the mobile node logs the rejection but does not act on it, so it retries with the ordinary Binding Update backoff -- rejections at t=32.8, 33.8, 35.8, 39.8, 47.8, 63.8 s, doubling to the 32 s cap and never giving up. RFC 6275 Section 11.7.3 asks it to stop; the code says "retransmission is performed anyway as timers are not deleted". That is a separate defect on the mobile node's side, previously unreachable because status 134 could never be sent. --- src/inet/networklayer/mipv6/Mipv6.cc | 181 +++++++++++++++-- src/inet/networklayer/mipv6/Mipv6.h | 62 +++++- tests/fingerprint/examples.csv | 6 +- tests/module/MIPv6_home_agent_dad.test | 154 ++++++++++++++ .../MIPv6_home_agent_dad_duplicate.test | 188 ++++++++++++++++++ 5 files changed, 562 insertions(+), 29 deletions(-) create mode 100644 tests/module/MIPv6_home_agent_dad.test create mode 100644 tests/module/MIPv6_home_agent_dad_duplicate.test diff --git a/src/inet/networklayer/mipv6/Mipv6.cc b/src/inet/networklayer/mipv6/Mipv6.cc index c70d39b322b..a04a3b62869 100644 --- a/src/inet/networklayer/mipv6/Mipv6.cc +++ b/src/inet/networklayer/mipv6/Mipv6.cc @@ -76,6 +76,14 @@ Define_Module(Mipv6); */ Mipv6::~Mipv6() { + // A probe still running in Neighbour Discovery holds a pointer to this module and would + // call back into freed memory. Both references are already null when the referenced module + // was deleted first, which is the ordinary end-of-simulation teardown; the case that needs + // this is a Mipv6 deleted at runtime while Neighbour Discovery lives on. + if (ipv6nd.getNullable() != nullptr && ift.getNullable() != nullptr) + cancelAllPendingHomeRegistrations(); + pendingHomeRegistrations.clear(); + auto it = transmitIfList.begin(); while (it != transmitIfList.end()) { @@ -626,7 +634,7 @@ Mipv6::BuTransmitIfEntry *Mipv6::fetchBUTransmitIfEntry(NetworkInterface *ie, co } void Mipv6::sendMobilityMessageToIPv6Module(Packet *msg, const Ipv6Address& destAddr, - const Ipv6Address& srcAddr, int interfaceId, simtime_t sendTime) // overloaded for use at CN - CB + const Ipv6Address& srcAddr, int interfaceId) // overloaded for use at CN - CB { EV_INFO << "Appending ControlInfo to mobility message\n"; msg->addTagIfAbsent()->setProtocol(&Protocol::ipv6); @@ -642,12 +650,7 @@ void Mipv6::sendMobilityMessageToIPv6Module(Packet *msg, const Ipv6Address& dest << " SrcAddr=" << srcAddr << " InterfaceId=" << interfaceId << endl; - // TODO solve the HA DAD problem in a different way - // (delay currently specified via the sendTime parameter) - if (sendTime > 0) - sendDelayed(msg, sendTime, "toIPv6"); - else - send(msg, "toIPv6"); + send(msg, "toIPv6"); } void Mipv6::processBUMessage(Packet *inPacket, const Ptr& bu) @@ -718,6 +721,12 @@ void Mipv6::processBUMessage(Packet *inPacket, const Ptr& b If the home agent does not reject the Binding Update as described above, then it MUST delete any existing entry in its Binding Cache for this mobile node.*/ + // A de-registration that overtakes the probe of a still-unacknowledged + // registration cancels it. The mobile node asked for the binding to be gone and is + // acknowledged for the de-registration below, so acknowledging the registration it + // just withdrew would only tell it about a binding that no longer exists. + cancelPendingHomeRegistration(HoA); + bc->deleteEntry(HoA); /*In addition, the home agent MUST stop intercepting packets on the @@ -853,23 +862,52 @@ void Mipv6::processBUMessage(Packet *inPacket, const Ptr& b address, the home agent MUST perform Duplicate Address Detection [13] on the mobile node's home link before returning the Binding Acknowledgement.*/ - simtime_t sendTime; - if (rt6->isHomeAgent()) - // HA has to do DAD in case this is a new binding for this HoA - sendTime = existingBinding ? 0 : 1; - else - sendTime = 0; - - createAndSendBAMessage(destAddress, CoA, ifTag->getInterfaceId(), status, baSeqNumber, -// bu->getBindingAuthorizationData(), 15, sendTime); // swapped src and dest - bu->getBindingAuthorizationData(), lifeTime, sendTime); // swapped src and dest, corrected lifetime value - /*If this Duplicate Address Detection fails for the given home address or an associated link local address, then the home agent MUST reject the complete Binding Update and MUST return a Binding Acknowledgement to the mobile node, in which the Status field is set to 134 (Duplicate Address Detection failed).*/ - // TODO + // A binding still waiting for Duplicate Address Detection is not yet a binding + // this home agent "already has": a Binding Update arriving while the probe runs + // -- a retransmission, typically -- must not be acknowledged ahead of it. + bool probeRunning = isHomeRegistrationPending(HoA); + NetworkInterface *homeLink = nullptr; + + if (rt6->isHomeAgent() && homeRegistration && (!existingBinding || probeRunning)) { + // the NOT_HOME_SUBNET test above already established that some interface of + // this home agent advertises a prefix covering this home address + homeLink = findHomeLinkInterface(HoA); + ASSERT(homeLink != nullptr); + } + + if (homeLink != nullptr) { + // Hold the acknowledgement back until the probe ends; addressProbeCompleted() + // sends it, with status 134 if another node defends the home address. + PendingHomeRegistration& pending = pendingHomeRegistrations[HoA]; + pending.homeAgentAddress = destAddress; + pending.careOfAddress = CoA; + pending.interfaceId = ifTag->getInterfaceId(); + pending.baSeqNumber = baSeqNumber; + pending.bindingAuthorizationData = bu->getBindingAuthorizationData(); + pending.homeLinkInterfaceId = homeLink->getInterfaceId(); + + if (probeRunning) + EV_INFO << "Duplicate Address Detection for " << HoA << " is still running; " + << "this Binding Update will be acknowledged when it ends" << endl; + else { + EV_INFO << "New home registration for " << HoA + << ": running Duplicate Address Detection on " + << homeLink->getInterfaceName() + << " before acknowledging the Binding Update" << endl; + ipv6nd->startAddressProbe(HoA, homeLink, this); + } + } + else { + // a correspondent node runs no Duplicate Address Detection, and neither + // does a home agent that already holds a binding for this home address + createAndSendBAMessage(destAddress, CoA, ifTag->getInterfaceId(), status, baSeqNumber, + bu->getBindingAuthorizationData(), lifeTime); + } } else { // condition: ! bu->getAckFlag() EV_INFO << "BU Validated as OK: ACK FLAG NOT SET" << endl; @@ -1028,7 +1066,7 @@ bool Mipv6::validateBUderegisterMessage(Packet *inPacket, const PtrgetInterfaceId(), sendTime); + sendMobilityMessageToIPv6Module(packet, dest, src, ie->getInterfaceId()); +} + +NetworkInterface *Mipv6::findHomeLinkInterface(const Ipv6Address& homeAddress) +{ + for (int i = 0; i < ift->getNumInterfaces(); i++) { + NetworkInterface *ie = ift->getInterface(i); + const Ipv6InterfaceData *ipv6Data = ie->getProtocolData(); + + for (int j = 0; j < ipv6Data->getNumAdvPrefixes(); j++) + if (homeAddress.matches(ipv6Data->getAdvPrefix(j).prefix, ipv6Data->getAdvPrefix(j).prefixLength)) + return ie; + } + + return nullptr; +} + +void Mipv6::addressProbeCompleted(const Ipv6Address& addr, NetworkInterface *ie, bool unique) +{ + Enter_Method("addressProbeCompleted"); // can be called by the NeighbourDiscovery module + + auto it = pendingHomeRegistrations.find(addr); + if (it == pendingHomeRegistrations.end()) + return; // the binding was de-registered while the probe was running + + PendingHomeRegistration pending = it->second; + pendingHomeRegistrations.erase(it); + + if (unique) { + /*10.3.1 + When the home agent sends a successful Binding Acknowledgement to the mobile + node, the home agent assures to the mobile node that its address(es) will be + kept unique by the home agent for as long as the lifetime was granted for the + binding.*/ + EV_INFO << "Duplicate Address Detection for " << addr << " found no other claimant; " + << "acknowledging the home registration" << endl; + createAndSendBAMessage(pending.homeAgentAddress, pending.careOfAddress, pending.interfaceId, + BINDING_UPDATE_ACCEPTED, pending.baSeqNumber, pending.bindingAuthorizationData, + bc->getLifetime(addr)); + } + else { + /*10.3.1 + If this Duplicate Address Detection fails for the given home address or an + associated link local address, then the home agent MUST reject the complete + Binding Update and MUST return a Binding Acknowledgement to the mobile node, in + which the Status field is set to 134 (Duplicate Address Detection failed).*/ + EV_WARN << "Duplicate Address Detection for " << addr << " failed: another node on the " + << "home link holds the address. Rejecting the home registration" << endl; + + // reject the complete Binding Update: undo everything accepting it set up + bc->deleteEntry(addr); + destroyTunnelFromTrigger(addr); + cancelTimerIfEntry(addr, pending.interfaceId, KEY_BC_EXP); + + createAndSendBAMessage(pending.homeAgentAddress, pending.careOfAddress, pending.interfaceId, + DAD_FAILED, pending.baSeqNumber, pending.bindingAuthorizationData, 0); + } +} + +bool Mipv6::isHomeRegistrationPending(const Ipv6Address& homeAddress) +{ + auto it = pendingHomeRegistrations.find(homeAddress); + if (it == pendingHomeRegistrations.end()) + return false; + + NetworkInterface *ie = ift->getInterfaceById(it->second.homeLinkInterfaceId); + if (ie != nullptr && ipv6nd->isAddressProbeRunning(homeAddress, ie)) + return true; + + // The probe is gone without having reported, which happens when Neighbour Discovery is + // stopped on its own. Without this the record would keep every later Binding Update for + // this home address waiting for a probe that will never end. + EV_WARN << "The Duplicate Address Detection probe for " << homeAddress + << " disappeared; discarding the held-back home registration" << endl; + pendingHomeRegistrations.erase(it); + return false; +} + +void Mipv6::cancelPendingHomeRegistration(const Ipv6Address& homeAddress) +{ + auto it = pendingHomeRegistrations.find(homeAddress); + if (it == pendingHomeRegistrations.end()) + return; + + if (NetworkInterface *ie = ift->getInterfaceById(it->second.homeLinkInterfaceId)) + ipv6nd->cancelAddressProbe(homeAddress, ie); + + pendingHomeRegistrations.erase(it); +} + +void Mipv6::cancelAllPendingHomeRegistrations() +{ + while (!pendingHomeRegistrations.empty()) + cancelPendingHomeRegistration(pendingHomeRegistrations.begin()->first); } void Mipv6::processBAMessage(Packet *inPacket, const Ptr& ba) @@ -2837,6 +2968,10 @@ void Mipv6::handleBCExpiry(cMessage *msg) BcExpiryIfEntry *bcExpIfEntry = (BcExpiryIfEntry *)msg->getContextPointer(); // detaching the corresponding bulExpIfEntry pointer ASSERT(bcExpIfEntry != nullptr); + // a registration still waiting for its probe expires with the binding it belongs to, + // otherwise the probe would acknowledge a binding that no longer exists + cancelPendingHomeRegistration(bcExpIfEntry->HoA); + // remove binding from BC bc->deleteEntry(bcExpIfEntry->HoA); @@ -2907,6 +3042,8 @@ void Mipv6::handleTokenExpiry(cMessage *msg) void Mipv6::handleStopOperation(LifecycleOperation *operation) { + cancelAllPendingHomeRegistrations(); + // cancel and delete all timer entries for (auto& entry : transmitIfList) { cancelAndDelete(entry.second->timer); @@ -2923,6 +3060,8 @@ void Mipv6::handleStopOperation(LifecycleOperation *operation) void Mipv6::handleCrashOperation(LifecycleOperation *operation) { + cancelAllPendingHomeRegistrations(); + // cancel and delete all timer entries for (auto& entry : transmitIfList) { cancelAndDelete(entry.second->timer); diff --git a/src/inet/networklayer/mipv6/Mipv6.h b/src/inet/networklayer/mipv6/Mipv6.h index bb871ae9114..d185c700590 100644 --- a/src/inet/networklayer/mipv6/Mipv6.h +++ b/src/inet/networklayer/mipv6/Mipv6.h @@ -21,6 +21,7 @@ #include "inet/networklayer/contract/ipv6/Ipv6Address.h" #include "inet/networklayer/contract/INetfilter.h" +#include "inet/networklayer/icmpv6/IAddressProbeHandler.h" #include "inet/networklayer/ipv6/IIpv6ExtensionHeaderHandler.h" #include "inet/networklayer/mipv6/BindingUpdateList.h" #include "inet/networklayer/mipv6/MobilityHeader_m.h" // for HAOpt & RH2 @@ -59,7 +60,8 @@ enum TimerIfEntryType { /** * Implements RFC 3775 Mobility Support in Ipv6. */ -class INET_API Mipv6 : public OperationalBase, public IIpv6ExtensionHeaderHandler, public IIpv6TlvOptionHandler, public NetfilterBase::HookBase +class INET_API Mipv6 : public OperationalBase, public IIpv6ExtensionHeaderHandler, + public IIpv6TlvOptionHandler, public NetfilterBase::HookBase, public IAddressProbeHandler { public: virtual ~Mipv6(); @@ -71,6 +73,23 @@ class INET_API Mipv6 : public OperationalBase, public IIpv6ExtensionHeaderHandle ModuleRefByPar bc; ModuleRefByPar ipv6nd; + // + // Home registrations whose Binding Acknowledgement is waiting for Duplicate Address + // Detection to finish on the home link (RFC 6275 Section 10.3.1), keyed by the mobile + // node's home address. Everything the acknowledgement needs is kept here, because the + // Binding Update that carried it is long deleted by the time the probe ends. + // + struct PendingHomeRegistration { + Ipv6Address homeAgentAddress; // source address of the acknowledgement + Ipv6Address careOfAddress; // destination address of the acknowledgement + int interfaceId = -1; // interface the Binding Update arrived on + uint baSeqNumber = 0; // sequence number copied from the Binding Update + int bindingAuthorizationData = 0; // authenticator copied from the Binding Update + int homeLinkInterfaceId = -1; // interface the probe runs on + }; + + std::map pendingHomeRegistrations; + // // IP tunnel management (RFC 2473), moved here from the former Ipv6Tunneling // module. A "tunnel" is a dynamically created Ipv6TunnelInterface (built by @@ -305,9 +324,8 @@ class INET_API Mipv6 : public OperationalBase, public IIpv6ExtensionHeaderHandle * Append tags to the Mobility Messages (BU, BA etc) and send it out to the Ipv6 Module */ void sendMobilityMessageToIPv6Module(Packet *msg, const Ipv6Address& destAddr, - const Ipv6Address& srcAddr = Ipv6Address::UNSPECIFIED_ADDRESS, int interfaceId = -1, - simtime_t sendTime = 0); // overloaded for use at CN - CB -// void sendMobilityMessageToIPv6Module(cMessage *msg, const Ipv6Address& destAddr, simtime_t sendTime = 0); // overloaded for use at CN - CB + const Ipv6Address& srcAddr = Ipv6Address::UNSPECIFIED_ADDRESS, + int interfaceId = -1); // overloaded for use at CN - CB /** * Process a BU - only applicable to HAs and CNs. @@ -329,7 +347,41 @@ class INET_API Mipv6 : public OperationalBase, public IIpv6ExtensionHeaderHandle */ void createAndSendBAMessage(const Ipv6Address& src, const Ipv6Address& dest, int interfaceId, const BaStatus& baStatus, const uint baSeq, - const int bindingAuthorizationData, const uint lifeTime, simtime_t sendTime = 0); + const int bindingAuthorizationData, const uint lifeTime); + + /** + * Returns this home agent's interface onto the home link of the given home address -- + * the interface advertising a prefix that covers it -- or nullptr if there is none. + * + * Duplicates the loop in Ipv6RoutingTable::isOnLinkAddress(). Kept here so that this + * change does not collide with the companion proxy Neighbor Discovery work, which + * extracts that loop as Ipv6RoutingTable::findOnLinkInterface(); whichever lands second + * folds one into the other. + */ + NetworkInterface *findHomeLinkInterface(const Ipv6Address& homeAddress); + + /** + * Returns true while a home registration for the given home address is held back waiting + * for Duplicate Address Detection. Discards the held-back record if its probe has gone + * without a callback, so that a dropped probe cannot block the address forever. + */ + bool isHomeRegistrationPending(const Ipv6Address& homeAddress); + + /** + * Sends the Binding Acknowledgement that was held back for Duplicate Address Detection, + * with status 134 and the binding withdrawn when another node defended the home address. + */ + virtual void addressProbeCompleted(const Ipv6Address& addr, NetworkInterface *ie, bool unique) override; + + /** + * Abandons a held-back home registration and its running probe, if any. + */ + void cancelPendingHomeRegistration(const Ipv6Address& homeAddress); + + /** + * Abandons every held-back home registration and its running probe. + */ + void cancelAllPendingHomeRegistrations(); /** * Processes the received BA and creates tunnels or mobility header paths if appropriate. diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 696ba2cb33b..3fc69be5777 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -380,9 +380,9 @@ /examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, ec17-5cc2/tplx;55f5-0894/~tNl, PASS, wireless adhoc Ipv4 /examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, 85a0-51b8/tplx;c07a-44e1/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 -/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 17ac-0898/tplx;8bbc-d083/~tNl;fdb7-4ab1/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 19e2-a165/tplx;d96c-5e41/~tNl;ee03-5334/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 123e-b941/tplx;4e91-d6bb/~tNl;8642-5854/~tND;afae-2b3c/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 5f06-fae2/tplx;43fa-61bc/~tNl;3a38-f49b/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, bb55-54de/tplx;0eb5-7981/~tNl;5f90-0349/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 2ed5-9384/tplx;e9fe-4771/~tNl;037e-2747/~tND;afae-2b3c/tyf, PASS, wireless EthernetMac /examples/ipv6/pmipv6/, -f omnetpp.ini -c General -r 0, 60s, 8bf1-01f5/tplx;c5d4-bce4/~tNl;0f9a-c178/~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/module/MIPv6_home_agent_dad.test b/tests/module/MIPv6_home_agent_dad.test new file mode 100644 index 00000000000..873f8fd4028 --- /dev/null +++ b/tests/module/MIPv6_home_agent_dad.test @@ -0,0 +1,154 @@ +%description: +Tests that a home agent runs real Duplicate Address Detection for the mobile node's home +address on the home link before it acknowledges a first home registration, as RFC 6275 +Section 10.3.1 requires: + + "Unless this home agent already has a binding for the given home address, the home agent + MUST perform Duplicate Address Detection on the mobile node's home link before returning + the Binding Acknowledgement." + +The mobile node moves from its home network to a foreign network and registers a care-of +address. The home agent must probe the home address on its home-link interface -- a Neighbor +Solicitation with an unspecified source, sent to the solicited-node multicast address of the +home address -- and only send the Binding Acknowledgement once that probe goes unanswered. + +Before this was implemented the acknowledgement was simply delayed by a hardcoded 1 s and no +solicitation was ever sent. +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +record-vector-results = false +**.mipv6.cmdenv-log-level = trace +**.neighbourDiscovery.cmdenv-log-level = trace # the probe itself is logged here +**.app[*].cmdenv-log-level = trace +**.cmdenv-log-level = off +cmdenv-event-banners = false +ned-path = .;../../../../src;../../lib +network = inet.test.moduletest.lib.Mipv6Network +sim-time-limit = 60s +cmdenv-express-mode = false +cmdenv-log-prefix = "%C: " +num-rngs = 3 +seed-set = 1 +**.mobility.rng-0 = 2 + +# number of MNs and CNs +*.total_mn = 1 +*.total_cn = 1 + +**.neighbourDiscovery.minIntervalBetweenRAs = 0.03s +**.neighbourDiscovery.maxIntervalBetweenRAs = 0.07s +**.neighbourDiscovery.detectL2Movement = false # legacy: detect movement from periodic RAs only + +# channel physical parameters +*.radioMedium.mediumLimitCache.maxTransmissionPower = 2.0mW +*.radioMedium.mediumLimitCache.minReceptionPower = -82dBm +*.radioMedium.mediumLimitCache.minInterferencePower = -82dBm + +# access point +**.wlan*.mgmt.numAuthSteps = 4 + +# ALL APs common parameters +**.AP*.wlan*.mgmt.beaconInterval = 0.1s + +# Access Point parameters +**.AP_Home.wlan*.mgmt.ssid = "HOME" +**.AP_Home.wlan*.address = "10:AA:00:00:00:01" +**.AP_Home.eth[0].address = "10:AE:00:00:00:02" +**.AP_Home.eth[0].duplexMode = true + +**.AP_1.wlan*.mgmt.ssid = "AP1" +**.AP_1.wlan*.address = "10:AA:00:00:A1:01" +**.AP_1.eth[0].address = "10:AE:00:00:A1:02" +**.AP_1.eth[0].duplexMode = true + +# mobility +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxZ = 0m + +**.MN[0].mobility.typename = "RectangleMobility" +**.MN[0].mobility.constraintAreaMinX = 180m +**.MN[0].mobility.constraintAreaMinY = 170m +**.MN[0].mobility.constraintAreaMaxX = 630m +**.MN[0].mobility.constraintAreaMaxY = 180m +**.MN[0].mobility.startPos = 0 +**.MN[0].mobility.speed = 10mps +**.MN*.mobility.updateInterval = 0.1s + +# No apps needed - we just test the handover signaling +**.MN*.numApps = 0 +**.CN*.numApps = 0 + +# ip settings +**.forwarding = false + +# Ethernet NIC configuration +**.eth[*].queue.typename = "EthernetQosQueue" +**.eth[*].queue.dataQueue.typename = "DropTailQueue" +**.eth[*].queue.dataQueue.packetCapacity = 10 +**.eth*.duplexMode = true + +# analog model +**.analogModel.ignorePartialInterference = true + +# wireless channels +**.AP_Home.wlan*.radio.channelNumber = 1 +**.AP_1.wlan*.radio.channelNumber = 2 +**.MN*.wlan*.radio.channelNumber = 0 + +# wireless configuration +**.wlan*.agent.activeScan = true +**.wlan*.agent.defaultSsid = "" +**.wlan*.agent.channelsToScan = "1 2" +**.wlan*.agent.probeDelay = 0.1s +**.wlan*.agent.minChannelTime = 0.15s +**.wlan*.agent.maxChannelTime = 0.3s +**.wlan*.agent.authenticationTimeout = 5s +**.wlan*.agent.associationTimeout = 5s + +# nic settings +**.wlan*.bitrate = 2Mbps +**.wlan*.mac.dcf.channelAccess.cwMin = 7 +**.radio.transmitter.power = 2.0mW + +**.constraintAreaMinX = 0m +**.constraintAreaMinY = 0m +**.constraintAreaMaxX = 850m +**.constraintAreaMaxY = 850m + +%#-------------------------------------------------------------------------------------------------------------- +%subst: /omnetpp::// +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Mipv6Network.MN[0].ipv6.mipv6: Initiating Mobile Ipv6 protocol... +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Mipv6Network.Home_Agent.ipv6.mipv6: BU validation passed +%#-------------------------------------------------------------------------------------------------------------- +%# the home agent holds the acknowledgement back and probes the home link first +%contains: stdout +running Duplicate Address Detection on +%#-------------------------------------------------------------------------------------------------------------- +%# the probe is a real Neighbor Solicitation sent by Neighbour Discovery, not a delay +%contains: stdout +Mipv6Network.Home_Agent.ipv6.neighbourDiscovery: Probing +%#-------------------------------------------------------------------------------------------------------------- +%# nobody answers, so the registration is acknowledged +%contains: stdout +found no other claimant; acknowledging the home registration +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Mipv6Network.MN[0].ipv6.mipv6: Binding was accepted. +%#-------------------------------------------------------------------------------------------------------------- +%# One acknowledgement, not two. The one-second delay this replaced landed just after the mobile +%# node's own one-second retransmission timer, so the retransmitted Binding Update arrived when +%# the Binding Cache entry already existed, was read as a re-registration and was acknowledged +%# immediately -- on top of the delayed acknowledgement owed to the original. +%postrun-command: echo "accepted=$(grep -c 'Binding was accepted.' test.out)" > ba_count.out +%contains: ba_count.out +accepted=1 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- diff --git a/tests/module/MIPv6_home_agent_dad_duplicate.test b/tests/module/MIPv6_home_agent_dad_duplicate.test new file mode 100644 index 00000000000..ce0d5aa81ca --- /dev/null +++ b/tests/module/MIPv6_home_agent_dad_duplicate.test @@ -0,0 +1,188 @@ +%description: +Tests that a home agent rejects a home registration with status 134 when the mobile node's home +address turns out to be in use on the home link, as RFC 6275 Section 10.3.1 requires: + + "If this Duplicate Address Detection fails for the given home address or an associated link + local address, then the home agent MUST reject the complete Binding Update and MUST return a + Binding Acknowledgement to the mobile node, in which the Status field is set to 134 + (Duplicate Address Detection failed)." + +Arranging a duplicate takes some care. A node that holds the home address from the start would +defend it against the mobile node's own address autoconfiguration at boot, and the mobile node +would never get a home address to register. So the squatter carries the same MAC address as the +mobile node -- stateless autoconfiguration then gives both of them the same address on the home +link -- and Duplicate Address Detection is switched off on both, so that neither probes and +neither defends while they boot. The home agent keeps Duplicate Address Detection enabled, which +is the behaviour under test. + +The mobile node then moves to the foreign network and registers. The home agent probes the home +link, the squatter answers for the address, and the registration is rejected. + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +record-vector-results = false +**.mipv6.cmdenv-log-level = trace +**.neighbourDiscovery.cmdenv-log-level = trace # the probe itself is logged here +**.app[*].cmdenv-log-level = trace +**.cmdenv-log-level = off +cmdenv-event-banners = false +ned-path = .;../../../../src;../../lib +network = HomeAddressDuplicateNetwork +sim-time-limit = 60s +cmdenv-express-mode = false +cmdenv-log-prefix = "%C: " +num-rngs = 3 +seed-set = 1 +**.mobility.rng-0 = 2 + +# number of MNs and CNs +*.total_mn = 1 +*.total_cn = 1 + +**.neighbourDiscovery.minIntervalBetweenRAs = 0.03s +**.neighbourDiscovery.maxIntervalBetweenRAs = 0.07s +**.neighbourDiscovery.detectL2Movement = false # legacy: detect movement from periodic RAs only + +# channel physical parameters +*.radioMedium.mediumLimitCache.maxTransmissionPower = 2.0mW +*.radioMedium.mediumLimitCache.minReceptionPower = -82dBm +*.radioMedium.mediumLimitCache.minInterferencePower = -82dBm + +# access point +**.wlan*.mgmt.numAuthSteps = 4 + +# ALL APs common parameters +**.AP*.wlan*.mgmt.beaconInterval = 0.1s + +# Access Point parameters +**.AP_Home.wlan*.mgmt.ssid = "HOME" +**.AP_Home.wlan*.address = "10:AA:00:00:00:01" +**.AP_Home.eth[0].address = "10:AE:00:00:00:02" +**.AP_Home.eth[0].duplexMode = true + +**.AP_1.wlan*.mgmt.ssid = "AP1" +**.AP_1.wlan*.address = "10:AA:00:00:A1:01" +**.AP_1.eth[0].address = "10:AE:00:00:A1:02" +**.AP_1.eth[0].duplexMode = true + +# mobility +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxZ = 0m + +**.MN[0].mobility.typename = "RectangleMobility" +**.MN[0].mobility.constraintAreaMinX = 180m +**.MN[0].mobility.constraintAreaMinY = 170m +**.MN[0].mobility.constraintAreaMaxX = 630m +**.MN[0].mobility.constraintAreaMaxY = 180m +**.MN[0].mobility.startPos = 0 +**.MN[0].mobility.speed = 10mps +**.MN*.mobility.updateInterval = 0.1s + +# No apps needed - we just test the handover signaling +**.MN*.numApps = 0 +**.CN*.numApps = 0 + +# ip settings +**.forwarding = false + +# Ethernet NIC configuration +**.eth[*].queue.typename = "EthernetQosQueue" +**.eth[*].queue.dataQueue.typename = "DropTailQueue" +**.eth[*].queue.dataQueue.packetCapacity = 10 +**.eth*.duplexMode = true + +# analog model +**.analogModel.ignorePartialInterference = true + +# wireless channels +**.AP_Home.wlan*.radio.channelNumber = 1 +**.AP_1.wlan*.radio.channelNumber = 2 +**.MN*.wlan*.radio.channelNumber = 0 + +# wireless configuration +**.wlan*.agent.activeScan = true +**.wlan*.agent.defaultSsid = "" +**.wlan*.agent.channelsToScan = "1 2" +**.wlan*.agent.probeDelay = 0.1s +**.wlan*.agent.minChannelTime = 0.15s +**.wlan*.agent.maxChannelTime = 0.3s +**.wlan*.agent.authenticationTimeout = 5s +**.wlan*.agent.associationTimeout = 5s + +# nic settings +**.wlan*.bitrate = 2Mbps +**.wlan*.mac.dcf.channelAccess.cwMin = 7 +**.radio.transmitter.power = 2.0mW + +**.constraintAreaMinX = 0m +**.constraintAreaMinY = 0m +**.constraintAreaMaxX = 850m +**.constraintAreaMaxY = 850m + + +# --- the duplicate --- +# Same MAC on the mobile node's wireless interface and the squatter's Ethernet interface, so +# stateless autoconfiguration derives the same interface identifier for both, and the same +# address under the home prefix. +**.MN[0].wlan[0].address = "0A:AA:00:00:00:08" +**.squatter.eth[0].address = "0A:AA:00:00:00:08" + +# Neither of them probes, so neither defends against the other and the collision survives the +# boot. The home agent is deliberately left at the default of 1. +**.MN[0].ipv6.neighbourDiscovery.dupAddrDetectTransmits = 0 +**.squatter.ipv6.neighbourDiscovery.dupAddrDetectTransmits = 0 +**.squatter.numApps = 0 + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned +import inet.node.ipv6.StandardHost6; +import inet.test.moduletest.lib.Mipv6Network; + +network HomeAddressDuplicateNetwork extends Mipv6Network +{ + types: + channel ethline extends ned.DatarateChannel + { + delay = 0.1us; + datarate = 100Mbps; + } + submodules: + squatter: StandardHost6 { + @display("p=150,300"); + } + connections: + squatter.ethg++ <--> ethline <--> AP_Home.ethg++; +} +%#-------------------------------------------------------------------------------------------------------------- +%subst: /omnetpp::// +%#-------------------------------------------------------------------------------------------------------------- +%# the home agent holds the acknowledgement back and probes the home link +%contains: stdout +running Duplicate Address Detection on +%#-------------------------------------------------------------------------------------------------------------- +%# the squatter answers for the address +%contains: stdout +HomeAddressDuplicateNetwork.squatter.ipv6.neighbourDiscovery: Address is duplicate! Inform Sender of duplicate address! +%#-------------------------------------------------------------------------------------------------------------- +%# which the home agent reads as a duplicate +%contains: stdout +HomeAddressDuplicateNetwork.Home_Agent.ipv6.neighbourDiscovery: Another node defended +%#-------------------------------------------------------------------------------------------------------------- +%# and rejects the registration for +%contains: stdout +failed: another node on the home link holds the address. Rejecting the home registration +%#-------------------------------------------------------------------------------------------------------------- +%# the mobile node sees the rejection, so status 134 really reached it +%contains: stdout +HomeAddressDuplicateNetwork.MN[0].ipv6.mipv6: Binding was rejected. +%#-------------------------------------------------------------------------------------------------------------- +%# and no registration is ever accepted +%postrun-command: echo "accepted=$(grep -c 'Binding was accepted.' test.out)" > ba_count.out +%contains: ba_count.out +accepted=0 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#--------------------------------------------------------------------------------------------------------------