From 799068748513bd7312a751615df31c74199c988e Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Wed, 9 Sep 2026 16:02:47 +0200 Subject: [PATCH 1/2] ipv6: add a getter for an address's valid lifetime expiry Ipv6InterfaceData stores a valid lifetime expiry per address and lets a caller set it, through assignAddress() and updateMatchingAddressExpiryTimes(), but offers no way to read it back. A caller that has to decide what a new lifetime should be from the lifetime that is left cannot do so. Add getAddressExpiryTime(int i), next to the getAddress(int i) and getAddressType(int i) it matches. Zero means an infinite valid lifetime, the same convention the stored field already uses. No caller yet, so no behaviour changes and no baseline moves. --- src/inet/networklayer/ipv6/Ipv6InterfaceData.cc | 6 ++++++ src/inet/networklayer/ipv6/Ipv6InterfaceData.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/inet/networklayer/ipv6/Ipv6InterfaceData.cc b/src/inet/networklayer/ipv6/Ipv6InterfaceData.cc index 9ee9a54cc13..b53266af3b3 100644 --- a/src/inet/networklayer/ipv6/Ipv6InterfaceData.cc +++ b/src/inet/networklayer/ipv6/Ipv6InterfaceData.cc @@ -362,6 +362,12 @@ Ipv6InterfaceData::AddressType Ipv6InterfaceData::getAddressType(int i) const return addresses[i].addrType; } +simtime_t Ipv6InterfaceData::getAddressExpiryTime(int i) const +{ + ASSERT(i >= 0 && i < (int)addresses.size()); + return addresses[i].expiryTime; +} + Ipv6InterfaceData::AddressType Ipv6InterfaceData::getAddressType(const Ipv6Address& addr) const { return getAddressType(findAddress(addr)); diff --git a/src/inet/networklayer/ipv6/Ipv6InterfaceData.h b/src/inet/networklayer/ipv6/Ipv6InterfaceData.h index e8389a19e35..7521eafbb69 100644 --- a/src/inet/networklayer/ipv6/Ipv6InterfaceData.h +++ b/src/inet/networklayer/ipv6/Ipv6InterfaceData.h @@ -490,6 +490,12 @@ class INET_API Ipv6InterfaceData : public InterfaceProtocolData */ AddressType getAddressType(int i) const; + /** + * Returns the time at which the valid lifetime of the ith address of the + * interface expires, or zero if that lifetime is infinite. + */ + simtime_t getAddressExpiryTime(int i) const; + /** * Returns the address type (HoA, CoA) of the provided address of the interface. */ From 7d6b5ee9db25c02127ef9d74968a34f7ce635fd2 Mon Sep 17 00:00:00 2001 From: Gyorgy Szaszko Date: Wed, 9 Sep 2026 16:09:03 +0200 Subject: [PATCH 2/2] ipv6: fix: refresh address lifetimes from a repeated prefix A host stored the valid and preferred lifetimes it was given when it first formed an address from an advertised prefix, and nothing ever updated them. The router repeating the prefix -- which is how a host is meant to keep the address it holds -- left them untouched, so the address stayed on the schedule the first advertisement had set, however long the router kept advertising. RFC 4862 Section 5.5.3 gives the rule as step (e): If the advertised prefix is equal to the prefix of an address configured by stateless autoconfiguration in the list, the preferred lifetime of the address is reset to the Preferred Lifetime in the received advertisement. The specific action to perform for the valid lifetime of the address depends on the Valid Lifetime in the received advertisement and the remaining time to the valid lifetime expiration of the previously autoconfigured address. processRaPrefixInfoForAddrAutoConf() had no branch for it. It formed an address when the prefix was new to the interface, took the MIPv6 handover path when the interface held addresses from another prefix, and did nothing at all in the case step (e) describes: the same prefix advertised again to a host already configured from it. Add that branch, with the three sub-cases the step lists. The valid lifetime follows the advertisement when it is longer than two hours or longer than what is left; it is left alone when two hours or less remain, because the advertisement is not authenticated; otherwise it is cut to two hours. Those rules are what stops a forged advertisement carrying short valid lifetimes from expiring every address of a node, while a legitimate advertisement, which a router repeats, always takes the first case and applies at once. The preferred lifetime follows the advertisement in every case, as the step says. Ipv6InterfaceData::updateMatchingAddressExpiryTimes() already did the storing; it had no caller until now. An address with an infinite valid lifetime was not autoconfigured from a Prefix Information option, so step (e) does not speak about it and the branch leaves it alone. The new Ipv6_prefix_lifetime_refresh module test advertises a prefix with a 20 s valid lifetime and a 10 s preferred lifetime every 4 s to 6 s over a 60 s run, and checks that the host forms one address and then keeps refreshing it. It fails on master, where the refresh never happens. No fingerprint moves: no shipped example advertises a lifetime short enough to be reached within its run, so no advertisement in the suite lands on a prefix the receiving host already holds an address from with a lifetime worth refreshing. --- .../icmpv6/Ipv6NeighbourDiscovery.cc | 39 ++++++++++ .../module/Ipv6_prefix_lifetime_refresh.test | 78 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tests/module/Ipv6_prefix_lifetime_refresh.test diff --git a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc index 9785d3f5522..cc8c368c16f 100644 --- a/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc +++ b/src/inet/networklayer/icmpv6/Ipv6NeighbourDiscovery.cc @@ -2489,6 +2489,7 @@ void Ipv6NeighbourDiscovery::processRaPrefixInfoForAddrAutoConf(const Ipv6NdPref // changed structure of code below, 12.9.07 - CB bool isPrefixAssignedToInterface = false; + int assignedAddrIndex = -1; bool returnedHome = false; // 4.9.07 - CB for (int i = 0; i < ie->getProtocolData()->getNumAddresses(); i++) { @@ -2516,11 +2517,49 @@ void Ipv6NeighbourDiscovery::processRaPrefixInfoForAddrAutoConf(const Ipv6NdPref returnedHome = true; else { isPrefixAssignedToInterface = true; + assignedAddrIndex = i; EV_INFO << "The received Prefix is already assigned to the interface" << endl; // Zarrar Yousaf 19.07.07 break; } } } + + /*e) If the advertised prefix is equal to the prefix of an address + configured by stateless autoconfiguration in the list, the preferred + lifetime of the address is reset to the Preferred Lifetime in the + received advertisement. The specific action to perform for the valid + lifetime of the address depends on the Valid Lifetime in the received + advertisement and the remaining time to the valid lifetime expiration + of the previously autoconfigured address.*/ + if (isPrefixAssignedToInterface) { + auto *ipv6Data = ie->getProtocolDataForUpdate(); + simtime_t expiryTime = ipv6Data->getAddressExpiryTime(assignedAddrIndex); + // An address with an infinite valid lifetime was not autoconfigured from a + // Prefix Information option, so step (e) does not speak about it. + if (expiryTime != SIMTIME_ZERO) { + // RFC 4862 Section 5.5.3 (e) writes the remaining valid lifetime of the + // address as RemainingLifetime, and caps how far an unauthenticated + // advertisement may shorten it. Without that cap a single forged + // advertisement carrying short valid lifetimes could expire every + // address of a node; a legitimate advertisement, which the router + // repeats, always passes the first case and takes effect immediately. + const simtime_t twoHours = 7200; + simtime_t remainingLifetime = expiryTime - simTime(); + simtime_t newValidLifetime; + + if (validLifetime > twoHours || validLifetime > remainingLifetime) + newValidLifetime = validLifetime; // (e)(1) + else if (remainingLifetime <= twoHours) + newValidLifetime = remainingLifetime; // (e)(2), the advertisement is not authenticated + else + newValidLifetime = twoHours; // (e)(3) + + EV_INFO << "Prefix already assigned to the interface, refreshing lifetimes: valid " + << newValidLifetime << ", preferred " << preferredLifetime << endl; + ipv6Data->updateMatchingAddressExpiryTimes(prefix, prefixLength, + simTime() + newValidLifetime, simTime() + preferredLifetime); + } + } /*d) If the prefix advertised does not match the prefix of an address already in the list, and the Valid Lifetime is not 0, form an address (and add it to the list) by combining the advertised prefix with the link's diff --git a/tests/module/Ipv6_prefix_lifetime_refresh.test b/tests/module/Ipv6_prefix_lifetime_refresh.test new file mode 100644 index 00000000000..8e58cf26627 --- /dev/null +++ b/tests/module/Ipv6_prefix_lifetime_refresh.test @@ -0,0 +1,78 @@ +%description: +Tests that a repeated Prefix Information option refreshes the lifetimes of the +address the host already holds from that prefix (RFC 4862 Section 5.5.3 step e). + +The router advertises a prefix whose valid lifetime (20 s) and preferred +lifetime (10 s) are much shorter than the 60 s run, and re-advertises it every +4 s to 6 s. The host forms one address from the prefix and must then keep it +alive from the repeated advertisements, instead of forming a second address or +letting the first one lapse at the lifetime the first advertisement carried. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned +import inet.networklayer.configurator.ipv6.Ipv6NetworkConfigurator; +import inet.node.ipv6.Router6; +import inet.node.ipv6.StandardHost6; +import ned.DatarateChannel; + +network PrefixLifetimeRefreshNetwork +{ + types: + channel ethline extends DatarateChannel + { + delay = 0.1us; + datarate = 100Mbps; + } + submodules: + configurator: Ipv6NetworkConfigurator; + router: Router6; + host: StandardHost6; + connections: + host.ethg++ <--> ethline <--> router.ethg++; +} +%#-------------------------------------------------------------------------------------------------------------- +%file: config.xml + + + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +record-vector-results = false +ned-path = ../../../../src +network = PrefixLifetimeRefreshNetwork +sim-time-limit = 60s +cmdenv-express-mode = false +cmdenv-log-prefix = "%C: " + +**.ipv6.configurator.networkConfiguratorModule = "configurator" +*.configurator.assignAddressesToHosts = false +*.configurator.config = xmldoc("config.xml") + +# Advertise far more often than the advertised lifetimes, so that the address +# would lapse many times over if the advertisements did not refresh it. +**.neighbourDiscovery.minIntervalBetweenRAs = 4s +**.neighbourDiscovery.maxIntervalBetweenRAs = 6s + +# Ethernet NIC configuration +**.eth[*].queue.typename = "EthernetQosQueue" +**.eth[*].queue.dataQueue.typename = "DropTailQueue" +**.eth[*].queue.dataQueue.packetCapacity = 10 + +%#-------------------------------------------------------------------------------------------------------------- +%subst: /omnetpp::// +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +PrefixLifetimeRefreshNetwork.host.ipv6.neighbourDiscovery: DAD completed for address aaaa:1:: +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +PrefixLifetimeRefreshNetwork.host.ipv6.neighbourDiscovery: Prefix already assigned to the interface, refreshing lifetimes: valid 20, preferred 10 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep -c "Assigning new address to: eth0" test.out > test_newaddr.out || true +%contains: test_newaddr.out +1 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#--------------------------------------------------------------------------------------------------------------