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
42 changes: 42 additions & 0 deletions WHATSNEW
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,48 @@ Notable backward incompatible changes are the following:
negotiation. The MacQosWithBlockAck example fingerprints are updated; the
QoS and NonQos showcase fingerprint controls remain unchanged.

17. An IPv6 router sends a Redirect only to a neighbour

Ipv6::routePacket sent an Internet Control Message Protocol for IPv6
(ICMPv6) Redirect for every datagram it forwarded back out of the
interface it had arrived on, whatever its source. RFC 4861
section 8.2 also requires that the source of the datagram is a neighbour, so
that the better first hop is on the sender's link. A router now sends the
Redirect only when the source is a link-local address or matches an on-link
prefix of the arrival interface. A home agent that forwards a decapsulated,
reverse-tunneled datagram from a mobile node away from home is one of the
routers that no longer sends one.

Simulations where a router forwards datagrams from a distant source back out
of their arrival interface send fewer Redirects, so their fingerprints may
change.

18. An IPv6 Redirect names a router by its link-local address

Ipv6NeighbourDiscovery::sendRedirect copied the next hop of the route into
the Target Address field as it was, so a route whose gateway was configured
as a global address produced a Redirect with a global Target Address. RFC
4861 sections 4.5 and 8.2 require the router's link-local address there, and
the receiving side of INET discarded every such Redirect. A router now sends
no Redirect in that case, and the Target Address is either the destination
itself or a link-local address.

Simulations with configured routes through a global gateway on a shared
link send fewer Redirects, so their fingerprints may change.

19. IPv6 Redirects are rate limited

A router sent one Internet Control Message Protocol for IPv6 (ICMPv6)
Redirect for every datagram that qualified, with no limit, although RFC
4861 section 8.2 requires one. Ipv6NeighbourDiscovery now
meters them with the token bucket that RFC 4443 section 2.4 (f) recommends.
The two new parameters redirectRate (default 10Hz) and redirectBurst (default
10) are the rate N and the burst size B of that bucket, and the defaults are
the example values of that section.

Simulations where a source keeps sending through a router that redirects it
send fewer Redirects, so their fingerprints may change.

Notable backward compatible changes are the following:

1. IEEE 802.11 per-station rate statistics
Expand Down
27 changes: 27 additions & 0 deletions src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ void Ipv6NeighbourDiscovery::initialize(int stage)

sendGratuitousNa = par("sendGratuitousNa");

redirectRate = par("redirectRate");
redirectBurst = par("redirectBurst");
redirectTokens = redirectBurst;

pendingQueue.setName("pendingQueue");
}
else if (stage == INITSTAGE_NETWORK_CONFIGURATION) {
Expand Down Expand Up @@ -2386,6 +2390,29 @@ void Ipv6NeighbourDiscovery::sendRedirect(Packet *redirectedPacket, const Ipv6Ad
Ipv6Address pktSrcAddr = ipv6Header->getSrcAddress();
Ipv6Address srcAddr = ie->getProtocolData<Ipv6InterfaceData>()->getLinkLocalAddress();

// RFC 4861 Section 8.2: "If the target is a router, that router's link-local
// address MUST be used." A target that is neither the destination itself nor a
// link-local address is a router known only by another address, typically the
// gateway of a configured route; its link-local address is not known here, so
// no Redirect is sent (sending one is a SHOULD in the same section).
if (targetAddr != destAddr && !targetAddr.isLinkLocal()) {
EV_INFO << "Not sending ICMPv6 Redirect to " << pktSrcAddr << ": the target " << targetAddr
<< " is neither the destination " << destAddr << " nor a link-local address\n";
return;
}

// RFC 4861 Section 8.2: "A router MUST limit the rate at which Redirect messages
// are sent". RFC 4443 Section 2.4 (f), which it refers to for the details,
// recommends a token bucket of average rate N and burst size B.
simtime_t now = simTime();
redirectTokens = std::min((double)redirectBurst, redirectTokens + (now - redirectTokensUpdated).dbl() * redirectRate);
redirectTokensUpdated = now;
if (redirectTokens < 1) {
EV_INFO << "Not sending ICMPv6 Redirect to " << pktSrcAddr << " for " << destAddr << ": rate limit reached\n";
return;
}
redirectTokens -= 1;

EV_INFO << "Sending ICMPv6 Redirect to " << pktSrcAddr << ": use " << targetAddr
<< " as next hop for " << destAddr << "\n";

Expand Down
6 changes: 6 additions & 0 deletions src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ class INET_API Ipv6NeighbourDiscovery : public OperationalBase, protected cListe
ModuleRefByPar<Icmpv6> icmpv6;
ChecksumMode checksumMode = CHECKSUM_MODE_UNDEFINED;

// token bucket that limits the rate of sent Redirects (RFC 4861 Section 8.2)
double redirectRate = NaN; // tokens added per second
int redirectBurst = -1; // bucket size
double redirectTokens = NaN;
simtime_t redirectTokensUpdated;

#ifdef INET_WITH_MIPV6
ModuleRefByPar<Mipv6> mipv6; // in case the node has MIP support
#endif
Expand Down
2 changes: 2 additions & 0 deletions src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.ned
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ simple Ipv6NeighbourDiscovery extends SimpleModule
double baseReachableTime @unit(s) = default(30s); // RFC 4861 Section 10: base value for computing random ReachableTime
volatile double routerBootupTime @unit(s) = default(uniform(0s, 0.3s)); // random startup delay before a router assigns its link-local address; routers boot faster than hosts so that RAs are available by the time hosts solicit
volatile double hostBootupTime @unit(s) = default(uniform(0.4s, 1s)); // random startup delay before a host assigns its link-local address
double redirectRate @unit(Hz) = default(10Hz); // RFC 4861 Section 8.2 requires a router to limit the rate of the Redirects it sends; this is the average rate N of the token bucket RFC 4443 Section 2.4 (f) recommends, whose example defaults are N=10/s and B=10
int redirectBurst = default(10); // the burst size B of that token bucket: the number of Redirects that may be sent back to back
@display("i=block/network");
@signal[startDad](type=long); // Emits value=1
@signal[dadCompleted](type=long); // Emits value=1
Expand Down
10 changes: 9 additions & 1 deletion src/inet/networklayer/ipv6/Ipv6.cc
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,15 @@ void Ipv6::routePacket(Packet *packet, const NetworkInterface *destIE, const Net
// next hop is not directly reachable by the source and redirects cause a storm.
if (sendRedirects && rt->isRouter() && !fromHL && fromIE && interfaceId == fromIE->getInterfaceId()) {
Ipv6Address pktSrcAddr = ipv6Header->getSrcAddress();
if (nextHop != pktSrcAddr && !pktSrcAddr.isUnspecified() && !pktSrcAddr.isMulticast()) {
// The same section requires that "the Source Address field of the packet
// identifies a neighbor": a source reached through another router on this link
// cannot use a better first hop on it. On-link is decided as next-hop
// determination decides it (RFC 4861 Section 5.2): a link-local source, or a
// longest prefix match that gives an on-link route out of the arrival interface.
const Ipv6Route *srcRoute = pktSrcAddr.isLinkLocal() ? nullptr : rt->doLongestPrefixMatch(pktSrcAddr);
bool srcIsNeighbour = pktSrcAddr.isLinkLocal() ||
(srcRoute && srcRoute->getNextHop().isUnspecified() && srcRoute->getInterface() == fromIE);
if (nextHop != pktSrcAddr && !pktSrcAddr.isUnspecified() && !pktSrcAddr.isMulticast() && srcIsNeighbour) {
Comment on lines +539 to +542

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 On-link senders lose valid redirects

When an on-link sender has a more-specific route through a gateway, srcIsNeighbour becomes false. doLongestPrefixMatch selects that gateway over the connected prefix, so the router never redirects that sender.

Learn more

A Redirect is useful when the packet's source is on the arrival link and the router forwards the packet back out that link. doLongestPrefixMatch returns only the best matching route, so an unrelated more-specific route can hide a still-valid connected prefix. The new neighbour check then denies a Redirect to a source that is actually on-link. This can occur with manual, dynamic, or host-specific routes overlapping a connected prefix.

Example: Router R has a direct route for 2001:db8:2::/64 on eth0 and a /128 route for host A's 2001:db8:2::10 through another gateway. A sends R a packet that R forwards back onto eth0 via router B. The /128 wins the lookup, and R sends no Redirect to A, although A and B share eth0.

Recommended fix: Determine whether any valid on-link prefix on fromIE contains the source, independently of the single best forwarding route. Include directly connected static, advertised, and configured prefixes as appropriate; retain the link-local case and the original source-address guards.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

NetworkInterface *outIe = ift->getInterfaceById(interfaceId);
nd->sendRedirect(packet, nextHop, destAddress, outIe);
}
Expand Down
6 changes: 3 additions & 3 deletions tests/fingerprint/examples.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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, 6de1-de47/tplx;cf0f-5aba/~tNl;10e9-9eb9/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac
/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, f2bf-98a6/tplx;e791-2cf4/~tNl;b4c1-7888/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac
/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 144f-e03b/tplx;0c46-2b33/~tNl;3e46-e152/~tND;afae-2b3c/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,
Expand Down
6 changes: 3 additions & 3 deletions tests/fingerprint/mipv6-refactoring.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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, c4ad-103d/~tNlb, PASS, wireless EthernetMac
/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 2c04-f051/~tNlb, PASS, wireless EthernetMac
/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, 6449-5bf1/~tNlb, PASS, wireless EthernetMac

# IPv6 examples
# MLD example — new PASS row; ~tNl locks the MLD Report/Query/Done/MAS-Query packet exchange (traffic+lengths);
Expand Down
96 changes: 96 additions & 0 deletions tests/module/IPv6_redirect_rate_limit.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
%description:
Tests that a router limits the rate of the ICMPv6 Redirects it sends
(RFC 4861 Section 8.2: "A router MUST limit the rate at which Redirect
messages are sent"), with the token bucket of RFC 4443 Section 2.4 (f).

Topology: hostA, r1 and r2 on one switch; hostB behind r2. hostA's route to
hostB goes via r1, and r1's route goes via the link-local address of r2, back
out the interface the echo requests arrived on, so every echo request makes
r1 send a Redirect. hostA discards each one, because its route names r1 by
its global address, not by the link-local source of the Redirect, and keeps
sending through r1.

hostA sends 40 echo requests, one per 50 ms, from t=2s to t=3.95s. With a
burst of 5 and a rate of 2 per second, the bucket allows 5 + 2 * 1.95 = 8.9
Redirects over that time, so r1 sends 8 in all. The postrun command counts
every Redirect r1 sends.

%#--------------------------------------------------------------------------------------------------------------
%file: test.ned
import inet.networklayer.configurator.ipv6.Ipv6NetworkConfigurator;
import inet.node.ethernet.Eth100M;
import inet.node.ethernet.EthernetSwitch;
import inet.node.inet.Router;
import inet.node.inet.StandardHost;

network RedirectRateLimit
{
submodules:
configurator: Ipv6NetworkConfigurator;
hostA: StandardHost;
sw: EthernetSwitch;
r1: Router;
r2: Router;
hostB: StandardHost;
connections:
hostA.ethg++ <--> Eth100M <--> sw.ethg++;
r1.ethg++ <--> Eth100M <--> sw.ethg++;
r2.ethg++ <--> Eth100M <--> sw.ethg++;
r2.ethg++ <--> Eth100M <--> hostB.ethg++;
}
%#--------------------------------------------------------------------------------------------------------------
%file: redirect.xml
<config>
<interface hosts="hostA" names="eth0" address="2001:db8:2::10"/>
<interface hosts="r1" names="eth0" address="2001:db8:2::2"/>
<interface hosts="r2" names="eth0" address="2001:db8:2::3"/>
<interface hosts="r2" names="eth1" address="2001:db8:4::1"/>
<interface hosts="hostB" names="eth0" address="2001:db8:4::2"/>

<route hosts="hostA" destination="2001:db8:4::" prefixLength="64" gateway="2001:db8:2::2" interface="eth0"/>
<route hosts="r1" destination="2001:db8:4::" prefixLength="64" gateway="fe80::8aa:ff:fe00:6" interface="eth0"/>
<route hosts="hostB" destination="2001:db8:2::" prefixLength="64" gateway="2001:db8:4::1" interface="eth0"/>
</config>
%#--------------------------------------------------------------------------------------------------------------
%inifile: omnetpp.ini
[General]
record-vector-results = false
ned-path = ../../../../src;.
network = RedirectRateLimit
sim-time-limit = 4.1s
cmdenv-express-mode = false
cmdenv-log-prefix = "%C: "

**.hasIpv4 = false
**.hasIpv6 = true
**.ipv6.configurator.networkConfiguratorModule = "configurator"
**.ipv6.neighbourDiscovery.dupAddrDetectTransmits = 0
*.configurator.addStaticRoutes = true
*.configurator.addRemoteRoutes = false
*.configurator.addDefaultRoutes = false
*.configurator.config = xmldoc("redirect.xml")
*.r1.ipv6.routingTable.routes = xml("<routingTable><local node='r1'><interface name='eth0' AdvSendAdvertisements='off'/></local></routingTable>")
*.r2.ipv6.routingTable.routes = xml("<routingTable><local node='r2'><interface name='eth0' AdvSendAdvertisements='off'/><interface name='eth1' AdvSendAdvertisements='off'/></local></routingTable>")

*.hostA.numApps = 1
*.hostA.app[0].typename = "PingApp"
*.hostA.app[0].destAddr = "2001:db8:4::2"
*.hostA.app[0].startTime = 2s
*.hostA.app[0].stopTime = 3.99s
*.hostA.app[0].sendInterval = 50ms
*.r1.ipv6.neighbourDiscovery.redirectRate = 2Hz
*.r1.ipv6.neighbourDiscovery.redirectBurst = 5
%#--------------------------------------------------------------------------------------------------------------
%subst: /omnetpp:://
%#--------------------------------------------------------------------------------------------------------------
%postrun-command: grep -c "RedirectRateLimit.r1.ipv6.neighbourDiscovery: Sending ICMPv6 Redirect" test.out > test_redirects.out || true
%contains-regex: test_redirects.out
^8$
%#--------------------------------------------------------------------------------------------------------------
%contains: stdout
RedirectRateLimit.r1.ipv6.neighbourDiscovery: Not sending ICMPv6 Redirect to 2001:db8:2::10 for 2001:db8:4::2: rate limit reached
%#--------------------------------------------------------------------------------------------------------------
%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true
%not-contains: test_undisposed.out
undisposed object: (
%#--------------------------------------------------------------------------------------------------------------
92 changes: 92 additions & 0 deletions tests/module/IPv6_redirect_source_not_neighbour.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
%description:
Tests that a router sends no ICMPv6 Redirect to a source that is not a
neighbour (RFC 4861 Section 8.2: "the Source Address field of the packet
identifies a neighbor").

Topology: hostA -- r0 -- [switch: r0, r1, r2] -- r2 -- hostB.
hostA pings hostB. r0 forwards the echo requests to r1, whose route sends
them back out the same interface to r2. The source of each forwarded echo
request, hostA, is two hops away from r1, so r1 must not send it a Redirect.
r2 still forwards the echo requests, and hostB answers them.

%#--------------------------------------------------------------------------------------------------------------
%file: test.ned
import inet.networklayer.configurator.ipv6.Ipv6NetworkConfigurator;
import inet.node.ethernet.Eth100M;
import inet.node.ethernet.EthernetSwitch;
import inet.node.inet.Router;
import inet.node.inet.StandardHost;

network RedirectSourceNotNeighbour
{
submodules:
configurator: Ipv6NetworkConfigurator;
hostA: StandardHost;
r0: Router;
sw: EthernetSwitch;
r1: Router;
r2: Router;
hostB: StandardHost;
connections:
hostA.ethg++ <--> Eth100M <--> r0.ethg++;
r0.ethg++ <--> Eth100M <--> sw.ethg++;
r1.ethg++ <--> Eth100M <--> sw.ethg++;
r2.ethg++ <--> Eth100M <--> sw.ethg++;
r2.ethg++ <--> Eth100M <--> hostB.ethg++;
}
%#--------------------------------------------------------------------------------------------------------------
%file: redirect.xml
<config>
<interface hosts="hostA" names="eth0" address="2001:db8:1::1"/>
<interface hosts="r0" names="eth0" address="2001:db8:1::2"/>
<interface hosts="r0" names="eth1" address="2001:db8:2::1"/>
<interface hosts="r1" names="eth0" address="2001:db8:2::2"/>
<interface hosts="r2" names="eth0" address="2001:db8:2::3"/>
<interface hosts="r2" names="eth1" address="2001:db8:4::1"/>
<interface hosts="hostB" names="eth0" address="2001:db8:4::2"/>

<route hosts="hostA" destination="2001:db8:4::" prefixLength="64" gateway="2001:db8:1::2" interface="eth0"/>
<route hosts="r0" destination="2001:db8:4::" prefixLength="64" gateway="2001:db8:2::2" interface="eth1"/>
<route hosts="r1" destination="2001:db8:4::" prefixLength="64" gateway="fe80::8aa:ff:fe00:8" interface="eth0"/>
<route hosts="r2" destination="2001:db8:1::" prefixLength="64" gateway="2001:db8:2::1" interface="eth0"/>
<route hosts="hostB" destination="2001:db8:1::" prefixLength="64" gateway="2001:db8:4::1" interface="eth0"/>
</config>
%#--------------------------------------------------------------------------------------------------------------
%inifile: omnetpp.ini
[General]
record-vector-results = false
ned-path = ../../../../src;.
network = RedirectSourceNotNeighbour
sim-time-limit = 3s
cmdenv-express-mode = false
cmdenv-log-prefix = "%C: "

**.hasIpv4 = false
**.hasIpv6 = true
**.ipv6.configurator.networkConfiguratorModule = "configurator"
**.ipv6.neighbourDiscovery.dupAddrDetectTransmits = 0
*.configurator.addStaticRoutes = true
*.configurator.addRemoteRoutes = false
*.configurator.addDefaultRoutes = false
*.configurator.config = xmldoc("redirect.xml")

*.hostA.numApps = 1
*.hostA.app[0].typename = "PingApp"
*.hostA.app[0].destAddr = "2001:db8:4::2"
*.hostA.app[0].startTime = 2s
*.hostA.app[0].stopTime = 2.5s
*.hostA.app[0].sendInterval = 50ms

%#--------------------------------------------------------------------------------------------------------------
%subst: /omnetpp:://
%#--------------------------------------------------------------------------------------------------------------
%contains: stdout
RedirectSourceNotNeighbour.hostB.ipv6.ipv6: ICMPv6 packet: passing it to ICMPv6 module
%#--------------------------------------------------------------------------------------------------------------
%not-contains: stdout
Sending ICMPv6 Redirect to 2001:db8:1::1
%#--------------------------------------------------------------------------------------------------------------
%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true
%not-contains: test_undisposed.out
undisposed object: (
%#--------------------------------------------------------------------------------------------------------------
Loading
Loading