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: ( +%#--------------------------------------------------------------------------------------------------------------