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