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
6 changes: 4 additions & 2 deletions src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc
Original file line number Diff line number Diff line change
Expand Up @@ -846,8 +846,10 @@ void Ipv6NeighbourDiscovery::initiateDad(const Ipv6Address& tentativeAddr, Netwo
msg->setContextPointer(dadEntry);
dadEntry->timeoutMsg = msg;

// added uniform(0, IPv6_MAX_RTR_SOLICITATION_DELAY) to account for joining the solicited-node multicast
// group which is delay up to one 1 second (RFC 4862, 5.4.2)
// TODO the uniform(0, IPv6_MAX_RTR_SOLICITATION_DELAY) term was meant for the RFC 4862
// Section 5.4.2 delay before joining the solicited-node group, but it is added to the
// timeout -- the wait for an answer -- instead of delaying the first probe. The group
// itself is now joined when the address is assigned. See issue #1179.
scheduleAfter(ie->getProtocolData<Ipv6InterfaceData>()->getRetransTimer() + uniform(0, IPv6_MAX_RTR_SOLICITATION_DELAY), msg);

emit(startDadSignal, 1);
Expand Down
86 changes: 79 additions & 7 deletions src/inet/networklayer/ipv6/Ipv6InterfaceData.cc
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ void Ipv6InterfaceData::assignAddress(const Ipv6Address& addr, bool tentative,
Ipv6AddressInfo info(ownerp, addr);
m->emit(ipv6AddressAssignedSignal, &info);
}

// choosePreferredAddress() above drops an address whose valid lifetime has already
// run out, and leaves its group again; join only for one that is still held.
if (findAddress(addr) != -1)
joinSolicitedNodeMulticastGroup(addr);
}

void Ipv6InterfaceData::updateMatchingAddressExpiryTimes(const Ipv6Address& prefix, int length,
Expand Down Expand Up @@ -425,6 +430,8 @@ void Ipv6InterfaceData::removeAddress(const Ipv6Address& address)
Ipv6AddressInfo info(ownerp, address);
m->emit(ipv6AddressRemovedSignal, &info);
}

leaveSolicitedNodeMulticastGroup(address);
}

bool Ipv6InterfaceData::addrLess(const AddressData& a, const AddressData& b)
Expand All @@ -448,9 +455,11 @@ void Ipv6InterfaceData::choosePreferredAddress()
// remove expired addresses (expiryTime == 0 means infinite lifetime)
simtime_t now = simTime();
bool changed = false;
Ipv6AddressVector expired;
for (auto it = addresses.begin(); it != addresses.end(); ) {
if (it->expiryTime != SIMTIME_ZERO && it->expiryTime <= now) {
EV_INFO << "Address " << it->address << " has expired, removing\n";
expired.push_back(it->address);
it = addresses.erase(it);
changed = true;
}
Expand All @@ -460,6 +469,7 @@ void Ipv6InterfaceData::choosePreferredAddress()

if (addresses.empty()) {
preferredAddr = Ipv6Address();
leaveExpiredSolicitedNodeMulticastGroups(expired);
return;
}

Expand All @@ -473,27 +483,46 @@ void Ipv6InterfaceData::choosePreferredAddress()
// right away. Falling back to a link-local source for an off-link
// destination would be non-routable (RFC 4291 Section 2.5.6), so link-local
// is used only when no routable address exists at all.
Ipv6Address routableCandidate = Ipv6Address::UNSPECIFIED_ADDRESS;
simtime_t routableExpiry = SIMTIME_ZERO;
Ipv6Address linkLocalCandidate = Ipv6Address::UNSPECIFIED_ADDRESS;
simtime_t linkLocalExpiry = SIMTIME_ZERO;
for (auto& elem : addresses) {
if (!elem.address.isUnicast())
continue;
if (!elem.address.isLinkLocal()) {
preferredAddr = elem.address;
preferredAddrExpiryTime = elem.expiryTime;
if (changed)
changed1(F_IP_ADDRESS);
return;
routableCandidate = elem.address;
routableExpiry = elem.expiryTime;
break;
}
if (linkLocalCandidate.isUnspecified()) {
linkLocalCandidate = elem.address;
linkLocalExpiry = elem.expiryTime;
}
}
preferredAddr = linkLocalCandidate;
preferredAddrExpiryTime = linkLocalExpiry;

if (!routableCandidate.isUnspecified()) {
preferredAddr = routableCandidate;
preferredAddrExpiryTime = routableExpiry;
}
else {
preferredAddr = linkLocalCandidate;
preferredAddrExpiryTime = linkLocalExpiry;
}

if (changed)
changed1(F_IP_ADDRESS);

leaveExpiredSolicitedNodeMulticastGroups(expired);
}

void Ipv6InterfaceData::leaveExpiredSolicitedNodeMulticastGroups(const Ipv6AddressVector& expired)
{
// Leaving hands control to a Multicast Listener Discovery module, which builds and
// sends a Done message, so it must not happen until this object's own state is
// consistent again: the address list erased and sorted, and preferredAddr chosen.
for (const auto& addr : expired)
leaveSolicitedNodeMulticastGroup(addr);
}

void Ipv6InterfaceData::addAdvPrefix(const AdvPrefix& advPrefix)
Expand Down Expand Up @@ -553,6 +582,48 @@ void Ipv6InterfaceData::leaveMulticastGroup(const Ipv6Address& multicastAddress)
changeMulticastGroupMembership(multicastAddress, MCAST_EXCLUDE_SOURCES, empty, MCAST_INCLUDE_SOURCES, empty);
}

void Ipv6InterfaceData::joinSolicitedNodeMulticastGroup(const Ipv6Address& addr)
{
// RFC 4861 Section 7.2.1: "When a multicast-capable interface becomes enabled, the
// node MUST join the all-nodes multicast address on that interface, as well as the
// solicited-node multicast address corresponding to each of the IP addresses assigned
// to the interface." Tentative addresses are included: RFC 4862 Section 5.4.2 requires
// the group to be joined before the first Duplicate Address Detection probe is sent.
//
// The loopback address is excluded because it is not a link address: its solicited-node
// address would collide with that of every real address ending in the same 24 bits.
if (!isSolicitedNodeGroupOwner(addr))
return;

joinMulticastGroup(addr.formSolicitedNodeMulticastAddress());
}

void Ipv6InterfaceData::leaveSolicitedNodeMulticastGroup(const Ipv6Address& addr)
{
// RFC 4861 Section 7.2.1: "a node MUST NOT leave the solicited-node multicast group
// until all assigned addresses corresponding to that multicast address have been
// removed." Several unicast addresses map to one solicited-node address -- with
// stateless autoconfiguration a link-local and a global address formed from the same
// interface identifier always do -- so this is the common case, not the exception.
// changeMulticastGroupMembership() counts the joins, so one leave per address is
// enough: the group entry only goes away with the last of them.
if (!isSolicitedNodeGroupOwner(addr))
return;

// Membership, not the interface flags, decides whether a leave is due. The flags are
// read once when the address is assigned and again here, and a leave that never had a
// matching join would drive the reference count in changeMulticastGroupMembership()
// below zero, leaving the interface a member of a group it never joined.
Ipv6Address solNodeAddr = addr.formSolicitedNodeMulticastAddress();
if (isMemberOfMulticastGroup(solNodeAddr))
leaveMulticastGroup(solNodeAddr);
}

bool Ipv6InterfaceData::isSolicitedNodeGroupOwner(const Ipv6Address& addr) const
{
return ownerp && ownerp->isMulticast() && addr.isUnicast() && !addr.isLoopback();
}

void Ipv6InterfaceData::changeMulticastGroupMembership(Ipv6Address multicastAddress,
McastSourceFilterMode oldFilterMode, const Ipv6AddressVector& oldSourceList,
McastSourceFilterMode newFilterMode, const Ipv6AddressVector& newSourceList)
Expand Down Expand Up @@ -838,6 +909,7 @@ Ipv6Address Ipv6InterfaceData::removeAddress(Ipv6InterfaceData::AddressType type
Ipv6AddressInfo info(ownerp, addr);
m->emit(ipv6AddressRemovedSignal, &info);
}
leaveSolicitedNodeMulticastGroup(addr);
}

return addr;
Expand Down
4 changes: 4 additions & 0 deletions src/inet/networklayer/ipv6/Ipv6InterfaceData.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ class INET_API Ipv6InterfaceData : public InterfaceProtocolData
protected:
int findAddress(const Ipv6Address& addr) const;
void choosePreferredAddress();
void joinSolicitedNodeMulticastGroup(const Ipv6Address& addr);
void leaveSolicitedNodeMulticastGroup(const Ipv6Address& addr);
void leaveExpiredSolicitedNodeMulticastGroups(const Ipv6AddressVector& expired);
bool isSolicitedNodeGroupOwner(const Ipv6Address& addr) const;
void changed1(int fieldId) { changed(interfaceIpv6ConfigChangedSignal, fieldId); }
HostMulticastData *getHostData() { if (!hostMcastData) hostMcastData = new HostMulticastData(); return hostMcastData; }
const HostMulticastData *getHostData() const { return const_cast<Ipv6InterfaceData *>(this)->getHostData(); }
Expand Down
11 changes: 6 additions & 5 deletions src/inet/networklayer/ipv6/Ipv6RoutingTable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -267,16 +267,17 @@ void Ipv6RoutingTable::assignRequiredNodeAddresses(NetworkInterface *ie)
/*o Any additional Unicast and Anycast Addresses that have been configured
for the node's interfaces (manually or automatically).*/

// FIXME: commented out the following lines, because these addresses
// are implicitly checked for in isLocalAddress() (we don't want redundancy,
// and manually adding solicited-node mcast address for each and every address
// is very error-prone)
//
// o The All-Nodes Multicast Addresses defined in section 2.7.1.
//
// Joined below, together with the all-routers group.

/*o The Solicited-Node Multicast Address for each of its unicast and anycast
addresses.*/

// Not joined here: Ipv6InterfaceData::assignAddress() joins the solicited-node group
// of an address as it is assigned, and leaves it as it is removed, so the membership
// tracks the address list on its own (RFC 4861 Section 7.2.1).

// o Multicast Addresses of all other groups to which the node belongs.

/*A router is required to recognize all addresses that a host is
Expand Down
6 changes: 3 additions & 3 deletions tests/fingerprint/examples.csv
Original file line number Diff line number Diff line change
Expand Up @@ -465,15 +465,15 @@
/examples/ospfv3/v3_square_2_areas/, -f omnetpp.ini -c General -r 0, 200s, bbee-ed95/tplx;ac7a-261c/~tNl;025f-56b5/tyf;c1f1-6687/~tND, PASS, EthernetMac

/examples/pim/dm/, -f omnetpp.ini -c General -r 0, 300s, 5542-465a/tplx;320d-ec2e/~tNl;7540-425c/~tND;ed5b-ae9d/tyf, PASS, igmp EthernetMac Ipv4
/examples/pim/dm_ipv6/, -f omnetpp.ini -c General -r 0, 120s, 3c32-3dca/~tNlb, PASS, MLD EthernetMac Ipv6
/examples/pim/dm_ipv6/, -f omnetpp.ini -c General -r 0, 120s, 3c6e-baaf/~tNlb, PASS, MLD EthernetMac Ipv6
/examples/pim/iptv/, -f omnetpp.ini -c PIM_SM -r 0, 35s, 9fdf-a54f/tplx;c356-8a7d/~tNl;fddd-28bc/~tND;11d1-62c7/tyf, PASS, igmp ospf EthernetMac Ipv4
/examples/pim/iptv/, -f omnetpp.ini -c PIM_DM -r 0, 35s, 25e4-e1ff/tplx;280f-57ab/~tNl;6e8e-78ba/~tND;96bd-4dca/tyf, PASS, igmp ospf EthernetMac Ipv4
/examples/pim/sm/, -f omnetpp.ini -c Scenario1 -r 0, 100s, 3f92-7b84/tplx;1cf9-ee08/~tNl;7bc2-d697/~tND;ba86-0da1/tyf, PASS, igmp EthernetMac Ipv4
/examples/pim/sm/, -f omnetpp.ini -c Scenario2 -r 0, 150s, b64f-022b/tplx;5022-893e/~tNl;9384-a10d/~tND;ce2a-7241/tyf, PASS, igmp EthernetMac Ipv4
/examples/pim/sm/, -f omnetpp.ini -c Scenario3 -r 0, 300s, a4d4-39b0/tplx;2e76-520f/~tNl;eb91-fabc/~tND;4f37-e2c3/tyf, PASS, igmp EthernetMac Ipv4
/examples/pim/sm/, -f omnetpp.ini -c Scenario4 -r 0, 100s, 1bbc-ce82/tplx;90c6-efdf/~tNl;3c6f-feb9/~tND;015c-b31f/tyf, PASS, igmp EthernetMac Ipv4
/examples/pim/sm_ipv6/, -f omnetpp.ini -c General -r 0, 120s, 0a21-bad8/~tNlb, PASS, MLD EthernetMac Ipv6
/examples/pim/ssm_ipv6/, -f omnetpp.ini -c General -r 0, 60s, 6eba-9db0/~tNl, PASS, MLD EthernetMac Ipv6
/examples/pim/sm_ipv6/, -f omnetpp.ini -c General -r 0, 120s, 1dcf-20a5/~tNlb, PASS, MLD EthernetMac Ipv6
/examples/pim/ssm_ipv6/, -f omnetpp.ini -c General -r 0, 60s, 094d-904b/~tNl, PASS, MLD EthernetMac Ipv6

/examples/seaport/, -f omnetpp.ini -c General -r 0, 1000s, 32cb-156a/tplx;fcf4-00cd/~tNl;e3e9-834d/tyf, PASS, igmp EthernetMac Ipv4

Expand Down
4 changes: 2 additions & 2 deletions tests/fingerprint/mipv6-refactoring.csv
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
# MLD example — new PASS row; ~tNl locks the MLD Report/Query/Done/MAS-Query packet exchange (traffic+lengths);
# ~tNlb excluded: EthernetHub (WireJunction) adds a pointer-typed 'originalSender' cPar to the signal which
# causes parsimPack() to abort — use ~tNl (no parsim serialization) instead; tyf excluded as unreliable
/examples/ipv6/mld/, -f omnetpp.ini -c MldDemo -r 0, 30s, f1b1-b523/~tNl, PASS, EthernetMac MLD
/examples/ipv6/mld/, -f omnetpp.ini -c MldV2Ssm -r 0, 30s, a22a-81c9/~tNl, PASS, EthernetMac MLD
/examples/ipv6/mld/, -f omnetpp.ini -c MldDemo -r 0, 30s, e72b-4cc1/~tNl, PASS, EthernetMac MLD
/examples/ipv6/mld/, -f omnetpp.ini -c MldV2Ssm -r 0, 30s, f947-4b1c/~tNl, PASS, EthernetMac MLD
/examples/ipv6/nclients/, -f omnetpp.ini -c ETH -r 0, 1000s, 50c9-9bb3/~tNlb, PASS, EthernetMac
/examples/ipv6/nclients/, -f omnetpp.ini -c PPP -r 0, 1000s, 0e0c-b800/~tNlb, PASS,
/examples/ipv6/nclients/, -f omnetpp.ini -c PPP_SCTP -r 0, 100s, 06b9-ff17/~tNlb, PASS,
Expand Down
105 changes: 105 additions & 0 deletions tests/module/IPv6_solicited_node_group_join.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
%description:
Tests that a node joins the solicited-node multicast group of every address
assigned to an interface, and announces the membership with a Multicast
Listener Report before it probes the address.

RFC 4861 Section 7.2.1 requires a node to join the solicited-node multicast
address corresponding to each of the IP addresses assigned to an interface,
and to join and leave as addresses are added and removed. RFC 4862 Section
5.4.2 requires the join to happen before the first Duplicate Address
Detection probe of a tentative address, because with an MLD-snooping switch
"no multicast reception will be available until the MLD report is sent".

A router and a host obtain their addresses by Stateless Address
Autoconfiguration, with Multicast Listener Discovery enabled so that each
join becomes a packet. The host's interface identifier is fixed by its MAC
address, so its link-local address is fe80::8aa:ff:fe00:2 and the
solicited-node group of that address is ff02::1:ff00:2.

%#--------------------------------------------------------------------------------------------------------------
%file: test.ned
import inet.networklayer.configurator.ipv6.Ipv6FlatNetworkConfigurator;
import inet.node.ipv6.Router6;
import inet.node.ipv6.StandardHost6;
import ned.DatarateChannel;

network SolicitedNodeJoinNetwork
{
types:
channel ethline extends DatarateChannel
{
delay = 0.1us;
datarate = 10Mbps;
}
submodules:
configurator: Ipv6FlatNetworkConfigurator;
router: Router6;
host: StandardHost6;
connections:
host.ethg++ <--> ethline <--> router.ethg++;
}
%#--------------------------------------------------------------------------------------------------------------
%inifile: omnetpp.ini
[General]
record-vector-results = false
ned-path = ../../../../src
network = SolicitedNodeJoinNetwork
sim-time-limit = 5s
cmdenv-express-mode = false
cmdenv-log-prefix = "%C: "

# Multicast Listener Discovery makes every join visible as a packet.
**.ipv6.hasMld = true

# One probe per address is enough, and keeps the log short.
**.neighbourDiscovery.dupAddrDetectTransmits = 1

# Ethernet NIC configuration
**.eth[*].queue.typename = "EthernetQosQueue"
**.eth[*].queue.dataQueue.typename = "DropTailQueue"
**.eth[*].queue.dataQueue.packetCapacity = 10

%#--------------------------------------------------------------------------------------------------------------
%subst: /omnetpp:://
%#--------------------------------------------------------------------------------------------------------------
%#
%# The host announces the solicited-node group of its link-local address.
%#
%contains: stdout
SolicitedNodeJoinNetwork.host.ipv6.mld: Mldv1: sending Multicast Listener Report for group=ff02::1:ff00:2 on iface=eth0

%#
%# The report precedes the host's own first Duplicate Address Detection probe
%# (RFC 4862 Section 5.4.2). Both greps are anchored on the host, so the
%# router's earlier probe cannot satisfy the ordering; grep -n keeps the file
%# order, so an ordered match over the two line numbers is enough.
%#
%postrun-command: grep -n "host\.ipv6\.mld: .*Report for group=ff02::1:ff00:2\|host\.ipv6\.neighbourDiscovery: .*INITIATING DUPLICATE ADDRESS" test.out | head -2 > order.out || true
%contains-regex: order.out
[0-9]+:.*host\.ipv6\.mld: .*Report for group=ff02::1:ff00:2.*\n[0-9]+:.*host\.ipv6\.neighbourDiscovery: .*INITIATING DUPLICATE ADDRESS

%#
%# The host holds two addresses -- the link-local one and the global one it
%# forms from the router's prefix -- and probes both, so it runs Duplicate
%# Address Detection twice.
%#
%postrun-command: grep -c "host\.ipv6\.neighbourDiscovery: .*INITIATING DUPLICATE ADDRESS" test.out > dadcount.out || true
%contains: dadcount.out
2

%#
%# Both addresses carry the same interface identifier, so both map to the same
%# solicited-node group and the second join finds the group already joined.
%# RFC 4861 Section 7.2.1 requires exactly that: "multiple unicast addresses
%# may map into the same solicited-node multicast address". The second join
%# changes no state and emits no further report, so two addresses produce one
%# report, not two.
%#
%postrun-command: grep -c "host\.ipv6\.mld: .*sending Multicast Listener Report for group=ff02::1:ff00:2" test.out > joincount.out || true
%contains: joincount.out
1
%#--------------------------------------------------------------------------------------------------------------
%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true
%not-contains: test_undisposed.out
undisposed object: (
%#--------------------------------------------------------------------------------------------------------------
11 changes: 7 additions & 4 deletions tests/module/MLDv2_host_query.test
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ network = inet.test.moduletest.lib.TestMLDNetwork
%# First check that the join/set-filter were applied to the interface state
%#
%postrun-command: grep "TestMld: .*groups =" test.out > groups.out || true
%# ff02::1:ff00:1 is the solicited-node multicast group of the interface's own address,
%# joined when that address is assigned (RFC 4861 Section 7.2.1), so it is a member of
%# every dump and of every Current-State Report below.
%contains: groups.out
TestMld: eth0: groups = ff3e::1 I 2001::1.
TestMld: eth0: groups = ff3e::1 E 2001::2.
TestMld: eth0: groups = ff02::1:ff00:1 E, ff3e::1 I 2001::1.
TestMld: eth0: groups = ff02::1:ff00:1 E, ff3e::1 E 2001::2.

%#
%# Now check that the Mldv2 module responded to each General Query with a Current-State
Expand All @@ -75,10 +78,10 @@ TestMld: eth0: groups = ff3e::1 E 2001::2.
%postrun-command: grep "TestMld: .*Received" test.out > received.out || true
%contains-regex: received.out
TestMld: Received: inet::Mldv2Report<ff3e::1=ALLOW \(2001::1\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff3e::1=IS_IN \(2001::1\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff02::1:ff00:1=IS_EX, ff3e::1=IS_IN \(2001::1\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff3e::1=ALLOW \(2001::1\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff3e::1=TO_EX \(2001::2\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff3e::1=IS_EX \(2001::2\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff02::1:ff00:1=IS_EX, ff3e::1=IS_EX \(2001::2\)>.
(?:.*\n)*?TestMld: Received: inet::Mldv2Report<ff3e::1=TO_EX \(2001::2\)>.

%#
Expand Down
Loading