From 92eb9200d0a871359a4895818e6339c3a78f722b Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Tue, 6 Oct 2026 16:21:32 +0200 Subject: [PATCH] ipv6tunneling: fix: keep each tunnel interface on a link of its own RFC (Request for Comments) 2473 (Generic Packet Tunneling in IPv6) Section 3 models a tunnel as a point-to-point virtual link between its entry point and its exit point. Ipv6NetworkConfigurator instead linked every two Ipv6TunnelInterface modules of a network as members of one wireless local area network (LAN), and computed routes over those links. With two tunnels, a border router could route its own tunnel packets back into the tunnel, and simulation time stopped advancing. Ipv6TunnelInterface now declares isWireless = default(false), as TunInterface, the other ITunnelInterface module, does. NetworkInterface::initialize() classifies an interface as wireless when it has neither a phys$i nor a phys$o gate, unless the module declares an isWireless parameter (NetworkInterface.cc:121-124). Ipv6TunnelInterface has no phys gates and declared no isWireless. The configurator's extractTopology() links every pair of wireless interfaces that share a wireless identifier, and getWirelessId() returns "default" for an interface without a radio that no element names (L3NetworkConfiguratorBase.cc:166-184, 497). That grouping is right for wireless interfaces; the tunnel interface was the one misclassified. Ipv4NetworkConfigurator and NextHopNetworkConfigurator run the same extractTopology(), so they now keep tunnel interfaces apart too. tests/module/IPv6_tunnel_configurator_routes.test reproduces it with two border routers and one tunnel each way. Before this change, borderB got the route 2001:db8:2::/64 via fe80:: dev tun0 ahead of the route via eth0. The tunnel exit point 2001:db8:2::1 is in that prefix, so borderB routed each tunnel packet into the tunnel again. Simulation time stayed at t=1.00001402 s until the test's 20 s central processing unit (CPU) time limit, and hostA and hostB each received 0 of 4 packets; now each receives 4 of 4. With explicit addresses in different prefixes on the two tunnel interfaces, the configurator stopped at initialization instead: "Explicit address 2001:db8:b::1 on Ipv6TunnelConfiguratorTestNetwork.borderB.tun0 is not within the link prefix 2001:db8:a::/64". Change: src.networklayer.ipv6tunneling | behavior.change.fix | test whatsnew --- WHATSNEW | 15 +++ .../ipv6tunneling/Ipv6TunnelInterface.ned | 1 + .../IPv6_tunnel_configurator_routes.test | 122 ++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 tests/module/IPv6_tunnel_configurator_routes.test diff --git a/WHATSNEW b/WHATSNEW index 31e8e7a0b71..19446d22ec6 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -325,6 +325,21 @@ Notable backward incompatible changes are the following: The method identifies both the wait before transmission and the transmission itself. The migration guide explains the return values and the order of the completion callback. +19. The IPv6 network configurator keeps each IPv6 tunnel interface on a link of its own + + Ipv6TunnelInterface did not declare the isWireless parameter, so every tunnel + interface counted as a wireless interface. Ipv6NetworkConfigurator then joined + all tunnel interfaces of the network into one wireless local area network + (LAN), linked each of them with every other, and computed routes over those + links. A tunnel entry point could get a route to its own exit point through + the tunnel, and then encapsulated each packet again and again without + advancing simulation time. + Explicit addresses on two tunnel interfaces also had to share one prefix. + Each tunnel interface is now a link of its own, as a TunInterface already was, + also for Ipv4NetworkConfigurator and NextHopNetworkConfigurator, which share + the topology extraction. The results of simulations that use one of these + configurators with more than one Ipv6TunnelInterface change. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics diff --git a/src/inet/networklayer/ipv6tunneling/Ipv6TunnelInterface.ned b/src/inet/networklayer/ipv6tunneling/Ipv6TunnelInterface.ned index 8f2adff86b7..0fa54419211 100644 --- a/src/inet/networklayer/ipv6tunneling/Ipv6TunnelInterface.ned +++ b/src/inet/networklayer/ipv6tunneling/Ipv6TunnelInterface.ned @@ -18,6 +18,7 @@ import inet.networklayer.common.NetworkInterface; module Ipv6TunnelInterface extends NetworkInterface like ITunnelInterface { parameters: + bool isWireless = default(false); string interfaceTableModule; string source; // tunnel entry point (an address of this node) string destination; // tunnel exit point diff --git a/tests/module/IPv6_tunnel_configurator_routes.test b/tests/module/IPv6_tunnel_configurator_routes.test new file mode 100644 index 00000000000..35ac6ed1517 --- /dev/null +++ b/tests/module/IPv6_tunnel_configurator_routes.test @@ -0,0 +1,122 @@ +%description: +Tests that Ipv6NetworkConfigurator treats an Ipv6TunnelInterface as a wired, +point-to-point interface, not as a member of a wireless LAN. + +Topology: hostA --- borderA --- transitA --- transitB --- borderB --- hostB. +borderA and borderB each have an Ipv6TunnelInterface whose exit point is the +other border router, and a manual route sends the far host's prefix into the +tunnel. The configurator computes all other routes. + +An interface without phys gates counts as wireless unless it declares +isWireless. The configurator puts all wireless interfaces that have no radio +into one wireless LAN and links them all with each other, so the two tunnel +interfaces got a link between them. borderB then routed borderA's prefix, which +contains the tunnel exit point, into its own tunnel, and every packet from +hostB to hostA was encapsulated again and again at t=1s. Traffic in both +directions must arrive. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned + +import inet.networklayer.configurator.ipv6.Ipv6NetworkConfigurator; +import inet.node.ipv6.Router6; +import inet.node.ipv6.StandardHost6; +import ned.DatarateChannel; + +network Ipv6TunnelConfiguratorTestNetwork +{ + types: + channel ethline extends DatarateChannel { + delay = 0.1us; + datarate = 100Mbps; + } + submodules: + configurator: Ipv6NetworkConfigurator; + hostA: StandardHost6; + borderA: Router6; + transitA: Router6; + transitB: Router6; + borderB: Router6; + hostB: StandardHost6; + connections: + hostA.ethg++ <--> ethline <--> borderA.ethg++; + borderA.ethg++ <--> ethline <--> transitA.ethg++; + transitA.ethg++ <--> ethline <--> transitB.ethg++; + transitB.ethg++ <--> ethline <--> borderB.ethg++; + borderB.ethg++ <--> ethline <--> hostB.ethg++; +} + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini + +[General] +record-vector-results = false +network = Ipv6TunnelConfiguratorTestNetwork +ned-path = ../../../../src;. +sim-time-limit = 3s +cmdenv-express-mode = false +cmdenv-log-prefix = "%C: " +cpu-time-limit = 20s +**.app[*].cmdenv-log-level = info +**.cmdenv-log-level = off + +**.ipv6.configurator.networkConfiguratorModule = "configurator" +**.configurator.config = xml(" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ") + +**.neighbourDiscovery.dupAddrDetectTransmits = 0 +**.neighbourDiscovery.hostBootupTime = 0s +**.neighbourDiscovery.routerBootupTime = 0s + +*.border*.numTunInterfaces = 1 +*.border*.tun[0].typename = "Ipv6TunnelInterface" +*.borderA.tun[0].source = "2001:db8:2::1" +*.borderA.tun[0].destination = "2001:db8:4::2" +*.borderB.tun[0].source = "2001:db8:4::2" +*.borderB.tun[0].destination = "2001:db8:2::1" + +*.hostA.numApps = 2 +*.hostA.app[0].typename = "UdpBasicApp" +*.hostA.app[0].destAddresses = "2001:db8:5::b" +*.hostA.app[0].destPort = 5000 +*.hostA.app[0].messageLength = 100B +*.hostA.app[0].startTime = 1s +*.hostA.app[0].sendInterval = 0.5s +*.hostA.app[0].stopTime = 2.9s +*.hostA.app[1].typename = "UdpSink" +*.hostA.app[1].localPort = 5000 + +*.hostB.numApps = 2 +*.hostB.app[0].typename = "UdpSink" +*.hostB.app[0].localPort = 5000 +*.hostB.app[1].typename = "UdpBasicApp" +*.hostB.app[1].destAddresses = "2001:db8:1::a" +*.hostB.app[1].destPort = 5000 +*.hostB.app[1].messageLength = 100B +*.hostB.app[1].startTime = 1s +*.hostB.app[1].sendInterval = 0.5s +*.hostB.app[1].stopTime = 2.9s + +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Ipv6TunnelConfiguratorTestNetwork.hostB.app[0]: received 4 packets +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Ipv6TunnelConfiguratorTestNetwork.hostA.app[1]: received 4 packets +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#--------------------------------------------------------------------------------------------------------------