From 1faaa68f0e6a2efe8198e81ae1d862b51237e76f Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Fri, 16 Oct 2026 12:38:26 +0200 Subject: [PATCH] ipv6: fix: keep a datagram with a link-local source on its link Ipv6::routePacket() refused to forward a datagram whose destination address is link-local, but did not look at the source address, so a router forwarded a unicast datagram with a link-local source onto another link. Request for Comments (RFC) 4291 Section 2.5.6 says that routers must not forward packets with link-local source or destination addresses to other links, and RFC 4007 Section 9 discards a packet that would leave the zone of its source address and, for a unicast destination, answers with an Internet Control Message Protocol for Internet Protocol version 6 (ICMPv6) Destination Unreachable, code 2, "beyond scope of source address" (RFC 4443 Section 3.1). After choosing the output interface, routePacket() now drops a forwarded datagram whose source is link-local if the output interface differs from the arrival interface (INET has no zone model, so the interface stands for the link; a datagram sent back through it stays in the zone of its source). The drop emits packetDropped with OTHER_PACKET_DROP and sends Destination Unreachable, code 2, unless the datagram is a Redirect (RFC 4443 Section 2.4 (e.2)). Icmpv6DestUnav gains BEYOND_SCOPE_OF_SOURCE = 2, where a comment stood. tests/module/IPv6_forwarding_link_local_source.test pings an off-link host from a link-local address through a router and checks that the router forwards none of the 3 Echo Requests and that the sender receives 3 Destination Unreachable messages with code 2; before this commit, the router forwarded all 3. This commit records the changed tplx, ~tNl and ~tND fingerprints of three examples.csv rows: examples/ipv6/mipv6 Handover and RouteOptimizationTwoCNs, examples/ipv6/mipv6roaming Roaming. In Handover and Roaming, a correspondent node pings from its link-local address before it has a global address; in all three, the home agent used to tunnel its own Redirects, returned by a router, to the mobile node. Their tyf fingerprints and the mipv6-refactoring.csv rows, which fail on master, are kept. The statistical baselines of the three configurations change. Change: src.networklayer.ipv6.Ipv6 | behavior.change.fix | test fingerprint statistical whatsnew --- WHATSNEW | 4 ++ src/inet/networklayer/common/Icmpv6Type.msg | 2 +- src/inet/networklayer/ipv6/Ipv6.cc | 22 ++++++ tests/fingerprint/examples.csv | 6 +- .../IPv6_forwarding_link_local_source.test | 69 +++++++++++++++++++ 5 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 tests/module/IPv6_forwarding_link_local_source.test diff --git a/WHATSNEW b/WHATSNEW index 68c0405dbb7..371815725c9 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -490,6 +490,10 @@ Notable bug fixes are the following: The HT mode factory now includes band and preamble format in its cache key. A 5 GHz request after an equivalent 2.4 GHz request therefore receives the requested band and timing. Previously, that request could receive the earlier mode. Identical complete requests still reuse one object. +2. IPv6 routers keep link-local sources on their link + + An IPv6 router forwarded a unicast datagram whose source address is link-local onto another link; it checked only the destination address. It now drops such a datagram and, unless the datagram is a Redirect, answers with ICMPv6 Destination Unreachable, code 2, "beyond scope of source address" (RFC 4291 Section 2.5.6, RFC 4007 Section 9). Sending the datagram back out of the interface it arrived on is still allowed. The Icmpv6DestUnav enum gains the value BEYOND_SCOPE_OF_SOURCE = 2. This changes the results of simulations in which a node sends from a link-local address to an off-link destination, for example before its global address is configured, or in which a router sends a Redirect to a node that is not its neighbour. + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/src/inet/networklayer/common/Icmpv6Type.msg b/src/inet/networklayer/common/Icmpv6Type.msg index d3bb4ce9437..818ab07ab24 100644 --- a/src/inet/networklayer/common/Icmpv6Type.msg +++ b/src/inet/networklayer/common/Icmpv6Type.msg @@ -40,7 +40,7 @@ enum Icmpv6DestUnav NO_ROUTE_TO_DEST = 0; COMM_WITH_DEST_PROHIBITED = 1; - //2 - NOT ASSIGNED + BEYOND_SCOPE_OF_SOURCE = 2; ADDRESS_UNREACHABLE = 3; PORT_UNREACHABLE = 4; } diff --git a/src/inet/networklayer/ipv6/Ipv6.cc b/src/inet/networklayer/ipv6/Ipv6.cc index f3f518f1616..dd4800fe8ad 100644 --- a/src/inet/networklayer/ipv6/Ipv6.cc +++ b/src/inet/networklayer/ipv6/Ipv6.cc @@ -525,6 +525,28 @@ void Ipv6::routePacket(Packet *packet, const NetworkInterface *destIE, const Net return; // don't raise error if sent to ND or ICMP! + // RFC 4291 Section 2.5.6, RFC 4007 Section 9: a datagram with a link-local source + // must not leave the link it arrived on. Without a zone model, the link is the + // arrival interface; a datagram sent back through it stays in its source's zone. + if (!fromHL && fromIE && interfaceId != fromIE->getInterfaceId() && ipv6Header->getSrcAddress().isLinkLocal()) { + EV_INFO << "source address is link-local and the datagram would leave its link, dropping\n"; + numDropped++; + PacketDropDetails details; + details.setReason(OTHER_PACKET_DROP); + emit(packetDroppedSignal, packet, &details); + // RFC 4443 Section 2.4 (e.2): no ICMPv6 error message about a Redirect + bool isRedirect = false; + if (ipv6Header->getProtocolId() == IP_PROT_IPv6_ICMP) { + const auto& icmpHeader = packet->peekDataAt(ipv6Header->getChunkLength()); + isRedirect = icmpHeader->getType() == ICMPv6_REDIRECT; + } + if (isRedirect) + delete packet; + else + sendIcmpError(packet, ICMPv6_DESTINATION_UNREACHABLE, BEYOND_SCOPE_OF_SOURCE); + return; + } + // RFC 4861 Section 8.2: a router sends a Redirect if it is forwarding a // packet back out on the same interface it arrived on, to a different next-hop. // Disabled (sendRedirects=false) on wireless multi-hop/ad-hoc networks, where the diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 0180b3bb5a0..f25f008c902 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, 55c4-44c4/tplx;92b9-416a/~tNl, PASS, wireless adhoc Ipv4 /examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, 5ed8-cf6b/tplx;b9f1-61b0/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 -/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 68ea-9693/tplx;a44c-17bf/~tNl;96ff-ed7c/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, bacf-986a/tplx;e6db-28c0/~tNl;805b-8a25/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac -/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, b275-b584/tplx;28ce-974e/~tNl;dcec-ae59/~tND;afae-2b3c/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 22cd-7897/tplx;b7dc-9169/~tNl;c9ba-d314/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 9473-84ec/tplx;0e27-f2af/~tNl;a224-1f60/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac +/examples/ipv6/mipv6roaming/, -f omnetpp.ini -c Roaming -r 0, 70s, ea06-4284/tplx;2913-f410/~tNl;22bb-d80d/~tND;afae-2b3c/tyf, PASS, wireless EthernetMac /examples/ipv6/pmipv6/, -f omnetpp.ini -c General -r 0, 60s, e801-4d1a/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/module/IPv6_forwarding_link_local_source.test b/tests/module/IPv6_forwarding_link_local_source.test new file mode 100644 index 00000000000..c1e855ec23e --- /dev/null +++ b/tests/module/IPv6_forwarding_link_local_source.test @@ -0,0 +1,69 @@ +%description: +Tests that a router does not forward a datagram with a link-local source address +onto another link (RFC 4291 Section 2.5.6, RFC 4007 Section 9), and that it +answers with ICMPv6 Destination Unreachable, code 2, "beyond scope of source +address" (RFC 4443 Section 3.1). + +hostA pings hostB's global address from its own link-local address. The router +between them must drop the three Echo Requests instead of forwarding them to +hostB's link, and hostA must receive three Destination Unreachable messages with +code 2. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned +import inet.networklayer.configurator.ipv6.Ipv6FlatNetworkConfigurator; +import inet.node.ipv6.Router6; +import inet.node.ipv6.StandardHost6; +import ned.DatarateChannel; + +network LinkLocalSourceNetwork +{ + types: + channel ethline extends DatarateChannel + { + delay = 0.1us; + datarate = 100Mbps; + } + submodules: + configurator: Ipv6FlatNetworkConfigurator; + hostA: StandardHost6; + router: Router6; + hostB: StandardHost6; + connections: + hostA.ethg++ <--> ethline <--> router.ethg++; + router.ethg++ <--> ethline <--> hostB.ethg++; +} +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +record-vector-results = false +ned-path = ../../../../src +network = LinkLocalSourceNetwork +sim-time-limit = 6s +cmdenv-express-mode = false +cmdenv-log-prefix = "%C: " + +**.hostA.eth[0].address = "0A:AA:00:00:00:0A" +**.hostA.numApps = 1 +**.hostA.app[0].typename = "PingApp" +**.hostA.app[0].srcAddr = "fe80::8aa:ff:fe00:a" +**.hostA.app[0].destAddr = "hostB(ipv6)" +**.hostA.app[0].startTime = 4s +**.hostA.app[0].sendInterval = 0.5s +**.hostA.app[0].count = 3 +%#-------------------------------------------------------------------------------------------------------------- +%subst: /omnetpp::// +%#-------------------------------------------------------------------------------------------------------------- +%not-contains-regex: stdout +LinkLocalSourceNetwork\.router\.ipv6\.ipv6: next hop for aaaa[^\n]*, interface eth1 +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +(LinkLocalSourceNetwork\.router\.ipv6\.ipv6: source address is link-local and the datagram would leave its link, dropping[\s\S]*){3} +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +(LinkLocalSourceNetwork\.hostA\.ipv6\.lp: Dispatching packet[^\n]*Icmpv6DestUnreachableMsg[^\n]*code[^,\n]*= 2[\s\S]*){3} +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#--------------------------------------------------------------------------------------------------------------