Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/inet/networklayer/icmpv6/IAddressProbeHandler.h
Original file line number Diff line number Diff line change
@@ -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
159 changes: 159 additions & 0 deletions src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Ipv6InterfaceData>()->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<Ipv6InterfaceData>()->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<Ipv6InterfaceData>()->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<Ipv6InterfaceData>()->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<Ipv6InterfaceData>()->getAdvSendAdvertisements() == false);
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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);
Expand Down
69 changes: 69 additions & 0 deletions src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -112,6 +150,18 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe
};
typedef std::vector<DadEntry *> 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<AddressProbeEntry *> AddressProbeList;

// stores information about Router Discovery for an interface
struct RdEntry {
int interfaceId; // interface on which Router Discovery is performed
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
Loading