diff --git a/WHATSNEW b/WHATSNEW index 03828369a2a..74be7224d57 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -293,6 +293,17 @@ Notable backward incompatible changes are the following: negotiation. The MacQosWithBlockAck example fingerprints are updated; the QoS and NonQos showcase fingerprint controls remain unchanged. +17. A Mobile IPv6 home agent intercepts packets on the home link + + While a home registration exists, a Mobile IPv6 home agent now answers + Neighbor Solicitations for the mobile node's home address with its own + link-layer address, and multicasts a Neighbor Advertisement for the home + address when it accepts a new binding, as RFC 6275 Section 10.4.1 + requires. Before, a host on the home link could not reach a mobile node + that was away. The results of Mobile IPv6 simulations change, including + the fingerprints of the Mobile IPv6 examples, because of the additional + advertisement. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics @@ -347,6 +358,15 @@ Notable backward compatible changes are the following: ResidenceTimeTag and, with it, the FlowTag and the PacketEventTag of the packet. Those two tags now survive the measurement. +6. IPv6 Neighbor Discovery proxy service + + Ipv6NeighbourDiscovery can answer Neighbor Solicitations for an address the + node does not hold, as a proxy in the sense of RFC 4861 Section 7.2.4. The + C++ methods addProxyAddress() and removeProxyAddress() start and stop the + service for an address on an interface. While it runs, the node is a member + of the address's solicited-node multicast group and answers solicitations + with its own link-layer address and a cleared Override flag. + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc index 9785d3f5522..d3e44e78c10 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc @@ -1819,8 +1819,11 @@ void Ipv6NeighbourDiscovery::processNsPacket(Packet *packet, const Ipv6Neighbour // RFC 2461:Section 7.2.3 // If target address is not a valid "unicast" or anycast address assigned to the // receiving interface, we should silently discard the packet. + // A node also answers for an address it provides proxy service for (RFC 4861, + // Section 7.2.4), which by definition is not assigned to the interface. if (validateNsPacket(packet, ns) == false - || ie->getProtocolData()->hasAddress(nsTargetAddr) == false) + || (ie->getProtocolData()->hasAddress(nsTargetAddr) == false + && !isProxyAddress(ie, nsTargetAddr))) { bubble("NS validation failed\n"); delete packet; @@ -1994,7 +1997,11 @@ void Ipv6NeighbourDiscovery::sendSolicitedNa(Packet *packet, const Ipv6Neighbour /*Furthermore, if the node is a router, it MUST set the Router flag to one; otherwise it MUST set the flag to zero.*/ - na->setRouterFlag(rt6->isRouter()); + // The flag describes the target, not the sender, so for a proxied address it follows + // what the caller said the proxied node is -- a Mobile IPv6 home agent proxies for a + // mobile host and so clears it (RFC 6275, Section 10.4.1), even though it is a router. + bool proxy = isProxyAddress(ie, ns->getTargetAddress()); + na->setRouterFlag(proxy ? isProxyTargetRouter(ie, ns->getTargetAddress()) : rt6->isRouter()); /*If the (NS)Target Address is either an anycast address or a unicast address for which the node is providing proxy service, or the Target @@ -2005,7 +2012,7 @@ void Ipv6NeighbourDiscovery::sendSolicitedNa(Packet *packet, const Ipv6Neighbour if (auto sla = check_and_cast_nullable(ns->getOptions().findOption(IPv6ND_SOURCE_LINK_LAYER_ADDR_OPTION))) sourceLinkLayerAddress = sla->getLinkLayerAddress(); - if (sourceLinkLayerAddress.isUnspecified()) + if (proxy || sourceLinkLayerAddress.isUnspecified()) // the Override flag SHOULD be set to zero. na->setOverrideFlag(false); else @@ -2058,6 +2065,66 @@ void Ipv6NeighbourDiscovery::sendSolicitedNa(Packet *packet, const Ipv6Neighbour sendPacketToIpv6Module(naPacket, naDestAddr, myIPv6Addr, ie->getInterfaceId()); } +void Ipv6NeighbourDiscovery::addProxyAddress(NetworkInterface *ie, const Ipv6Address& address, bool targetIsRouter) +{ + Enter_Method("addProxyAddress"); + + // Both preconditions come from what proxying needs to do below: hold IPv6 state on + // the interface, and join a multicast group on it. + if (ie->findProtocolData() == nullptr) + throw cRuntimeError("addProxyAddress(): interface %s has no IPv6 data", ie->getInterfaceName()); + if (!ie->isMulticast()) + throw cRuntimeError("addProxyAddress(): interface %s is not multicast capable, so solicitations " + "for %s cannot reach this node", ie->getInterfaceName(), address.str().c_str()); + + if (!proxyAddresses.insert({ { ie->getInterfaceId(), address }, targetIsRouter }).second) + return; // already proxying for it on this interface + + EV_INFO << "Providing Neighbour Discovery proxy service for " << address + << " on " << ie->getInterfaceName() << "\n"; + + // Solicitations for the address are sent to its solicited-node multicast address. + // A node that does not hold the address is not a member of that group, so Ipv6 + // would discard the solicitation before Neighbour Discovery ever saw it. + ie->getProtocolDataForUpdate()->joinMulticastGroup(address.formSolicitedNodeMulticastAddress()); +} + +void Ipv6NeighbourDiscovery::removeProxyAddress(const Ipv6Address& address) +{ + Enter_Method("removeProxyAddress"); + + for (auto it = proxyAddresses.begin(); it != proxyAddresses.end(); ) { + if (it->first.second != address) { + ++it; + continue; + } + + NetworkInterface *ie = ift->getInterfaceById(it->first.first); + EV_INFO << "No longer providing Neighbour Discovery proxy service for " << address + << " on " << (ie ? ie->getInterfaceName() : "a deleted interface") << "\n"; + + // Leave the group this entry joined. Gated on the entry having existed, because + // leaving a group that was never joined trips an assertion in Ipv6InterfaceData. + if (ie != nullptr) { + if (auto ipv6Data = ie->findProtocolDataForUpdate()) + ipv6Data->leaveMulticastGroup(address.formSolicitedNodeMulticastAddress()); + } + + it = proxyAddresses.erase(it); + } +} + +bool Ipv6NeighbourDiscovery::isProxyAddress(NetworkInterface *ie, const Ipv6Address& address) const +{ + return proxyAddresses.find({ ie->getInterfaceId(), address }) != proxyAddresses.end(); +} + +bool Ipv6NeighbourDiscovery::isProxyTargetRouter(NetworkInterface *ie, const Ipv6Address& address) const +{ + auto it = proxyAddresses.find({ ie->getInterfaceId(), address }); + return it != proxyAddresses.end() && it->second; +} + void Ipv6NeighbourDiscovery::sendUnsolicitedNa(NetworkInterface *ie, const Ipv6Address& forAddress) { // RFC 2461 @@ -2073,6 +2140,7 @@ void Ipv6NeighbourDiscovery::sendUnsolicitedNa(NetworkInterface *ie, const Ipv6A // least RetransTimer seconds. auto na = makeShared(); Ipv6Address myIPv6Addr = forAddress.isUnspecified() ? ie->getProtocolData()->getPreferredAddress() : forAddress; + bool proxy = isProxyAddress(ie, myIPv6Addr); // The Target Address field in the unsolicited advertisement is set to // an IP address of the interface, and the Target Link-Layer Address @@ -2089,7 +2157,8 @@ void Ipv6NeighbourDiscovery::sendUnsolicitedNa(NetworkInterface *ie, const Ipv6A // If the node is a router, it MUST set the Router flag to one; // otherwise it MUST set it to zero. - na->setRouterFlag(rt6->isRouter()); + // As in sendSolicitedNa(), the flag describes the target, not the sender. + na->setRouterFlag(proxy ? isProxyTargetRouter(ie, myIPv6Addr) : rt6->isRouter()); // The Override flag MAY be set to either zero or one. In either case, // neighboring nodes will immediately change the state of their Neighbor @@ -2125,10 +2194,21 @@ void Ipv6NeighbourDiscovery::sendUnsolicitedNa(NetworkInterface *ie, const Ipv6A // Neighbor Unreachability Detection algorithm ensures that all nodes // obtain a reachable link-layer address, though the delay may be // slightly longer. + // The target address doubles as the source address, which holds as long as the node + // advertises an address of its own. It does not hold when it advertises one it only + // proxies for: RFC 6275, Section 10.4.1 requires that "The Source Address in the IPv6 + // header MUST be set to the home agent's IP address on the interface used to send the + // advertisement", and a node cannot legitimately source a packet from an address it + // does not hold in any case. + Ipv6Address dgSrcAddr = proxy ? ie->getProtocolData()->getPreferredAddress() : myIPv6Addr; + + EV_INFO << "Sending unsolicited Neighbour Advertisement for " << myIPv6Addr + << " on " << ie->getInterfaceName() << (proxy ? " as a proxy" : "") << "\n"; + auto packet = new Packet("NeighbourAdvertisement"); Icmpv6::insertChecksum(checksumMode, na, packet); packet->insertAtFront(na); - sendPacketToIpv6Module(packet, Ipv6Address::ALL_NODES_2, myIPv6Addr, ie->getInterfaceId()); + sendPacketToIpv6Module(packet, Ipv6Address::ALL_NODES_2, dgSrcAddr, ie->getInterfaceId()); } void Ipv6NeighbourDiscovery::sendUnsolicitedRa(NetworkInterface *ie) @@ -2706,6 +2786,11 @@ void Ipv6NeighbourDiscovery::stop() // clear neighbour cache neighbourCache.clear(); + + // stop proxying: nothing here survives the node, and a record that did would make it + // answer for -- and defend -- an address on a live link with no binding behind it + while (!proxyAddresses.empty()) + removeProxyAddress(proxyAddresses.begin()->first.second); } } // namespace inet diff --git a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h index f122b6e9399..9ff48d6f4af 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h @@ -9,6 +9,7 @@ #define __INET_IPV6NEIGHBOURDISCOVERY_H #include +#include #include #include "inet/common/ModuleRefByPar.h" @@ -100,6 +101,12 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe #endif Ipv6NeighbourCache neighbourCache; + + // Addresses this node answers Neighbor Solicitations for although it does not hold + // them, keyed by (interface id, address); the value says whether the proxied target + // is itself a router. See addProxyAddress(). + std::map, bool> proxyAddresses; + typedef std::vector RaTimerList; // stores information about a pending Duplicate Address Detection for @@ -377,7 +384,43 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe */ virtual void sendUnsolicitedRa(NetworkInterface *ie); + /** + * Starts providing Neighbour Discovery proxy service for the given address on the + * given interface. The node joins the address's solicited-node multicast group, so + * that solicitations for an address it does not hold reach it at all, and answers + * them with its own link-layer address (RFC 4861, Section 7.2.4). It also defends + * the address against another node's Duplicate Address Detection. + * + * A Mobile IPv6 home agent uses this to intercept packets addressed to an absent + * mobile node on the home link (RFC 6275, Section 10.4.1). Taking an address over + * from a node whose neighbours still cache its old link-layer address additionally + * requires a multicast Neighbor Advertisement, which the caller sends with + * sendUnsolicitedNa(): a solicited proxy advertisement carries a cleared Override + * flag and so cannot replace a cached entry on its own. + */ + virtual void addProxyAddress(NetworkInterface *ie, const Ipv6Address& address, bool targetIsRouter = false); + + /** + * Stops providing Neighbour Discovery proxy service for the given address on every + * interface it is proxied on, and leaves its solicited-node multicast groups. Takes + * the address alone because the interface the address was on-link on may since have + * changed, and the record is what says where the group was actually joined. + * Does nothing if the address is not being proxied. + */ + virtual void removeProxyAddress(const Ipv6Address& address); + protected: + /** + * Returns true if this node provides Neighbour Discovery proxy service for the + * given address on the given interface. + */ + virtual bool isProxyAddress(NetworkInterface *ie, const Ipv6Address& address) const; + + /** + * Returns true if the proxied target is itself a router, which decides the Router + * flag of an advertisement sent for it. False for an address that is not proxied. + */ + virtual bool isProxyTargetRouter(NetworkInterface *ie, const Ipv6Address& address) const; virtual void processNaPacket(Packet *packet, const Ipv6NeighbourAdvertisement *na); virtual bool validateNaPacket(Packet *packet, const Ipv6NeighbourAdvertisement *na); diff --git a/src/inet/networklayer/ipv6/Ipv6RoutingTable.cc b/src/inet/networklayer/ipv6/Ipv6RoutingTable.cc index 0f1996eb21d..ad0cdb3422d 100644 --- a/src/inet/networklayer/ipv6/Ipv6RoutingTable.cc +++ b/src/inet/networklayer/ipv6/Ipv6RoutingTable.cc @@ -992,18 +992,23 @@ void Ipv6RoutingTable::deletePrefixes(int interfaceID) } } -bool Ipv6RoutingTable::isOnLinkAddress(const Ipv6Address& address) +NetworkInterface *Ipv6RoutingTable::findOnLinkInterface(const Ipv6Address& address) { for (int j = 0; j < ift->getNumInterfaces(); j++) { NetworkInterface *ie = ift->getInterface(j); for (int i = 0; i < ie->getProtocolData()->getNumAdvPrefixes(); i++) if (address.matches(ie->getProtocolData()->getAdvPrefix(i).prefix, ie->getProtocolData()->getAdvPrefix(i).prefixLength)) - return true; + return ie; } - return false; + return nullptr; +} + +bool Ipv6RoutingTable::isOnLinkAddress(const Ipv6Address& address) +{ + return findOnLinkInterface(address) != nullptr; } void Ipv6RoutingTable::deleteInterfaceRoutes(const NetworkInterface *entry) diff --git a/src/inet/networklayer/ipv6/Ipv6RoutingTable.h b/src/inet/networklayer/ipv6/Ipv6RoutingTable.h index f6010abe1bb..0444f9595c6 100644 --- a/src/inet/networklayer/ipv6/Ipv6RoutingTable.h +++ b/src/inet/networklayer/ipv6/Ipv6RoutingTable.h @@ -425,6 +425,12 @@ class INET_API Ipv6RoutingTable : public SimpleModule, public IRoutingTable, pro */ void setMipv6Support(bool value) { mipv6Support = value; } + /** + * Returns the interface on which the provided address is on-link with respect + * to the prefix advertisement list, or nullptr if it is on-link on none. + */ + NetworkInterface *findOnLinkInterface(const Ipv6Address& address); + /** * Checks whether the provided address is an on-link address * with respect to the prefix advertisement list. diff --git a/src/inet/networklayer/mipv6/Mipv6.cc b/src/inet/networklayer/mipv6/Mipv6.cc index c70d39b322b..7a0a43b3796 100644 --- a/src/inet/networklayer/mipv6/Mipv6.cc +++ b/src/inet/networklayer/mipv6/Mipv6.cc @@ -725,6 +725,11 @@ void Mipv6::processBUMessage(Packet *inPacket, const Ptr& b // of course this is also true for CNs destroyTunnelFromTrigger(HoA); + // ...which on the home link means giving the home address back to the + // mobile node, that being where it has just returned to + if (rt6->isHomeAgent()) + stopInterceptingForHomeAddress(HoA); + // A correspondent node inserts the Type 2 Routing Header for this home // address based on its route-optimization state (see datagramLocalOutHook), // not on the binding cache, so that state must be dropped here too -- otherwise @@ -884,6 +889,13 @@ void Mipv6::processBUMessage(Packet *inPacket, const Ptr& b destroyTunnelForEntryAndTrigger(HA, HoA); createTunnel(NORMAL, HA, CoA, HoA); + + // The tunnel only carries what already reached this home agent. Traffic + // from a host on the home link is resolved by Neighbour Discovery there + // and never routed, so intercepting it takes a second step -- and only + // for a home registration, which is what the standard keys the check on. + if (homeRegistration) + startInterceptingForHomeAddress(HoA, existingBinding); } else { // we first destroy the already existing RH2 path if @@ -2803,6 +2815,40 @@ void Mipv6::handleBULExpiry(cMessage *msg) } } +void Mipv6::startInterceptingForHomeAddress(const Ipv6Address& HoA, bool existingBinding) +{ + /*10.4.1 + While a node is serving as a home agent for some mobile node, the home agent + uses IPv6 Neighbor Discovery [18] to intercept unicast packets on the home link + addressed to the mobile node. In order to intercept packets in this way, the + home agent MUST act as a proxy for this mobile node and reply to any received + Neighbor Solicitations for it.*/ + NetworkInterface *homeLink = rt6->findOnLinkInterface(HoA); + + if (homeLink == nullptr) { + // Every home agent advertises the home prefix, so this means the home address + // does not belong to a prefix this node serves. + EV_WARN << "Home address " << HoA << " is on-link on no interface; not intercepting for it\n"; + return; + } + + ipv6nd->addProxyAddress(homeLink, HoA); + + /*10.4.1 + [...] subsequently it MUST multicast onto the home link a Neighbor Advertisement + message [18] on behalf of the mobile node.*/ + // Only for a binding that did not exist before. A binding that is merely being + // refreshed, or moved to a new care-of address, changes nothing on the home link: + // its neighbours already resolve the home address to this home agent. + if (!existingBinding) + ipv6nd->sendUnsolicitedNa(homeLink, HoA); +} + +void Mipv6::stopInterceptingForHomeAddress(const Ipv6Address& HoA) +{ + ipv6nd->removeProxyAddress(HoA); +} + void Mipv6::createBCEntryExpiryTimer(const Ipv6Address& HoA, NetworkInterface *ie, simtime_t scheduledTime) { cMessage *bcExpiryMsg = new cMessage("BCEntryExpiry", MK_BC_EXPIRY); @@ -2844,6 +2890,10 @@ void Mipv6::handleBCExpiry(cMessage *msg) destroyTunnelFromTrigger(bcExpIfEntry->HoA); removeRouteOptimizationForTrigger(bcExpIfEntry->HoA); + // an expired binding is no longer a reason to answer for the home address + if (rt6->isHomeAgent()) + stopInterceptingForHomeAddress(bcExpIfEntry->HoA); + // and remove entry from list cancelTimerIfEntry(bcExpIfEntry->dest, bcExpIfEntry->ifEntry->getInterfaceId(), KEY_BC_EXP); // deletion of the message already takes place in the cancelTimerIfEntry(.., KEY_BC_EXP); diff --git a/src/inet/networklayer/mipv6/Mipv6.h b/src/inet/networklayer/mipv6/Mipv6.h index bb871ae9114..2339ecde9a7 100644 --- a/src/inet/networklayer/mipv6/Mipv6.h +++ b/src/inet/networklayer/mipv6/Mipv6.h @@ -636,6 +636,27 @@ class INET_API Mipv6 : public OperationalBase, public IIpv6ExtensionHeaderHandle */ void handleBCExpiry(cMessage *msg); +// +// Helper functions for intercepting packets on the home link (RFC 6275, Section 10.4.1) +// + /** + * Makes this home agent answer Neighbor Solicitations for the mobile node's home + * address on the home link, so that on-link hosts reach the mobile node while it is + * away. For a binding that did not exist before, also multicasts a Neighbor + * Advertisement onto the home link on the mobile node's behalf, which takes the + * address over from neighbours that still cache the mobile node's own link-layer + * address. Does nothing if the home address is on-link on no interface. + */ + void startInterceptingForHomeAddress(const Ipv6Address& HoA, bool existingBinding); + + /** + * Stops answering Neighbor Solicitations for the mobile node's home address. Called + * when the binding is de-registered or expires, after which the mobile node defends + * its own address again. Withdrawn by address rather than by interface, so that a + * home link whose advertised prefixes changed meanwhile still leaves nothing behind. + */ + void stopInterceptingForHomeAddress(const Ipv6Address& HoA); + // // Helper functions for token expiry // diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 9fc76d91942..ed93005bae4 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, dca6-3751/tplx;a44c-17bf/~tNl;96ff-ed7c/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 1560-028f/tplx;e6db-28c0/~tNl;805b-8a25/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 342e-5e65/tplx;28ce-974e/~tNl;dcec-ae59/~tND;afae-2b3c/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, e680-ceeb/tplx;4d22-348a/~tNl;2a3d-46d8/~tND;1ccb-c62a/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, f9da-6a70/tplx;caf3-4533/~tNl;08fd-0ab2/~tND;38b5-3e5d/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 0b2e-373b/tplx;caff-f124/~tNl;e555-1e1b/~tND;0da4-da9b/tyf, PASS, wireless EthernetMac /examples/ipv6/pmipv6/, -f omnetpp.ini -c General -r 0, 60s, 081b-769c/tplx;1064-9bd4/~tNl;bd46-d85c/~tND;0277-d784/tyf, PASS, wireless EthernetMac /examples/mobility/, -f omnetpp.ini -c AnsimMobility -r 0, 10000s, 72f8-5c0b/tplx;0000-0000/~tNl;0000-0000/~tND;7dd1-18eb/tyf, PASS, diff --git a/tests/fingerprint/mipv6-refactoring.csv b/tests/fingerprint/mipv6-refactoring.csv index 4e8ebad4ab4..d3b07090ebe 100644 --- a/tests/fingerprint/mipv6-refactoring.csv +++ b/tests/fingerprint/mipv6-refactoring.csv @@ -8,9 +8,9 @@ # empty-shim step, which kept these fingerprints bit-for-bit identical. # MIPv6 example — the primary test -/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 8a43-2e1a/~tNlb, PASS, wireless EthernetMac -/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 8a20-d2eb/~tNlb, PASS, wireless EthernetMac -/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, bc7d-f706/~tNlb, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 531a-cbc4/~tNlb, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, e358-2c5e/~tNlb, PASS, wireless EthernetMac +/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 0494-7142/~tNlb, PASS, wireless EthernetMac # IPv6 examples # MLD example — new PASS row; ~tNl locks the MLD Report/Query/Done/MAS-Query packet exchange (traffic+lengths); diff --git a/tests/module/MIPv6_home_agent_proxy_nd.test b/tests/module/MIPv6_home_agent_proxy_nd.test new file mode 100644 index 00000000000..e225d92ed70 --- /dev/null +++ b/tests/module/MIPv6_home_agent_proxy_nd.test @@ -0,0 +1,220 @@ +%description: +Tests that a home agent intercepts packets addressed to an absent mobile node on the +mobile node's home link, as RFC 6275 Section 10.4.1 requires: while a home registration +binding exists, the home agent answers Neighbor Solicitations for the mobile node's home +address with its own link-layer address, and multicasts a Neighbor Advertisement on the +mobile node's behalf when it accepts the binding. + +Two hosts sit on the home link and ping the mobile node's home address. + + EarlyHost starts before the mobile node leaves, so it holds a neighbour cache entry + pointing at the mobile node's own link-layer address, and can only keep reaching it if + that entry is replaced or discarded. + + LateHost starts after the mobile node has left, so it has no cache entry at all and must + resolve the home address from scratch on the home link. Only a proxy reply to its + Neighbor Solicitation can answer it. + +The mobile node leaves the home link at about t=32s, the home agent accepts its Binding +Update at t=34.6s, and the mobile node is back on the home link from about t=76s (it +de-registers at t=78.1s). Replies are counted only for requests sent between t=36s and +t=75.5s, while the mobile node is away and registered, so a reply after its return cannot +satisfy the test. + + EarlyHost reaches the mobile node from t=36s on because the multicast takeover + advertisement carries a set Override flag (RFC 6275 Section 10.4.1 requires it) and + replaces its cached link-layer address with the home agent's. + + LateHost reaches it because the home agent answers its Neighbor Solicitations. + +Without the interception neither host is answered in that window: EarlyHost sends to a +link-layer address nobody answers to, and LateHost loops on address resolution. + +The withdrawal on the mobile node's return is asserted from the log. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned + +import inet.node.ipv6.StandardHost6; +import inet.test.moduletest.lib.Mipv6Network; +import inet.test.moduletest.lib.ethernetline; + +// +// The frozen Mipv6Network with two hosts added on the home link. The home link is the +// segment behind AP_Home, which bridges to the home agent, so the hosts, the home agent +// and the mobile node while it is at home all share one link. +// +network Mipv6ProxyNdNetwork extends Mipv6Network +{ + submodules: + EarlyHost: StandardHost6 { + @display("p=180,300"); + } + LateHost: StandardHost6 { + @display("p=180,380"); + } + connections: + EarlyHost.ethg++ <--> ethernetline <--> AP_Home.ethg++; + LateHost.ethg++ <--> ethernetline <--> AP_Home.ethg++; +} + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +record-vector-results = false +**.mipv6.cmdenv-log-level = trace +**.app[*].cmdenv-log-level = trace +**.Home_Agent.ipv6.neighbourDiscovery.cmdenv-log-level = info +**.*Host.ipv6.neighbourDiscovery.cmdenv-log-level = warn # so a failed address resolution is reported +**.cmdenv-log-level = off +cmdenv-event-banners = false +ned-path = .;../../../../src;../../lib +network = Mipv6ProxyNdNetwork +sim-time-limit = 100s +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 + + +# 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 home-link hosts ping the mobile node's home address. The address is hardcoded rather +# than resolved with "MN[0](ipv6)", which returns whichever address the mobile node happens +# to hold at resolution time -- its link-local address before autoconfiguration finishes, +# and its care-of address once it has moved away -- never reliably the stable home address. +**.MN*.numApps = 0 +**.CN*.numApps = 0 +*.EarlyHost.numApps = 1 +*.LateHost.numApps = 1 +**.EarlyHost.app[0].typename = "PingApp" +**.LateHost.app[0].typename = "PingApp" +**.*Host.app[0].destAddr = "aaaa:0:66:0:8aa:ff:fe00:8" +**.*Host.app[0].packetSize = 56B +**.*Host.app[0].hopLimit = 32 +**.*Host.app[0].sendInterval = 0.5s +**.*Host.app[0].printPing = true + +# before the mobile node leaves, so the cache entry is populated while it is still at home +**.EarlyHost.app[0].startTime = 1s +# after the home agent has accepted the binding, so address resolution starts from an empty cache +**.LateHost.app[0].startTime = 40s + +%#-------------------------------------------------------------------------------------------------------------- +%subst: /omnetpp::// +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Mipv6ProxyNdNetwork.Home_Agent.ipv6.mipv6: BU validation passed +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Mipv6ProxyNdNetwork.Home_Agent.ipv6.neighbourDiscovery: Providing Neighbour Discovery proxy service for aaaa:0:66:0:8aa:ff:fe00:8 +%#-------------------------------------------------------------------------------------------------------------- +%# LateHost starts at t=40s, 5 s after the home agent accepted the binding, so each of its +%# solicitations is answered. (EarlyHost fails once at t=4s, before any binding exists, while +%# the mobile node is still at home; that is not asserted.) +%postrun-command: grep "LateHost.ipv6.neighbourDiscovery: Address Resolution has failed" test.out > test_addrres.out || true +%not-contains: test_addrres.out +Address Resolution has failed +%#-------------------------------------------------------------------------------------------------------------- +%# The multicast takeover advertisement RFC 6275 Section 10.4.1 requires on accepting the +%# binding, sent for an address the home agent does not hold. +%contains: stdout +Mipv6ProxyNdNetwork.Home_Agent.ipv6.neighbourDiscovery: Sending unsolicited Neighbour Advertisement for aaaa:0:66:0:8aa:ff:fe00:8 on eth1 as a proxy +%#-------------------------------------------------------------------------------------------------------------- +%# ...and the withdrawal when the mobile node comes home and de-registers. +%contains: stdout +Mipv6ProxyNdNetwork.Home_Agent.ipv6.neighbourDiscovery: No longer providing Neighbour Discovery proxy service for aaaa:0:66:0:8aa:ff:fe00:8 +%#-------------------------------------------------------------------------------------------------------------- +%# EarlyHost: requests sent from t=36s to t=75.5s are icmp_seq 70 to 149 (80 requests). +%postrun-command: awk '/EarlyHost.app\[0\]: reply of/ && match($0, /icmp_seq=[0-9]+/) { q = substr($0, RSTART+9, RLENGTH-9)+0; if (q >= 70 && q <= 149) n++ } END { print (n >= 70) ? "EARLY-REACHED-WHILE-AWAY" : "EARLY-ONLY-" n+0 }' test.out > test_early.out +%contains: test_early.out +EARLY-REACHED-WHILE-AWAY +%#-------------------------------------------------------------------------------------------------------------- +%# LateHost: requests sent from t=40s to t=75.5s are icmp_seq 0 to 71 (72 requests). +%postrun-command: awk '/LateHost.app\[0\]: reply of/ && match($0, /icmp_seq=[0-9]+/) { q = substr($0, RSTART+9, RLENGTH-9)+0; if (q <= 71) n++ } END { print (n >= 60) ? "LATE-RESOLVED-WHILE-AWAY" : "LATE-ONLY-" n+0 }' test.out > test_late.out +%contains: test_late.out +LATE-RESOLVED-WHILE-AWAY +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: (