diff --git a/src/core/config/ip6.h b/src/core/config/ip6.h index 263190033..2e8e982c2 100644 --- a/src/core/config/ip6.h +++ b/src/core/config/ip6.h @@ -140,6 +140,20 @@ #define OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES 4 #endif +/** + * @def OPENTHREAD_CONFIG_IP6_SLAAC_DEPRECATION_INTERVAL + * + * Specifies the deprecating time of SLAAC addresses in seconds. + * + * Applicable only if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE is enabled. + * + * Deprecating interval is used once an on-mesh prefix is removed from Network Data before removing the SLAAC address. + * + */ +#ifndef OPENTHREAD_CONFIG_IP6_SLAAC_DEPRECATION_INTERVAL +#define OPENTHREAD_CONFIG_IP6_SLAAC_DEPRECATION_INTERVAL 300 +#endif + /** * @def OPENTHREAD_CONFIG_MPL_SEED_SET_ENTRIES * diff --git a/src/core/thread/network_data.cpp b/src/core/thread/network_data.cpp index 2599d653b..30ebe0038 100644 --- a/src/core/thread/network_data.cpp +++ b/src/core/thread/network_data.cpp @@ -848,5 +848,17 @@ exit: return contains; } +Error NetworkData::FindDomainIdFor(const Ip6::Prefix &aPrefix, uint8_t &aDomainId) const +{ + Error error = kErrorNone; + const PrefixTlv *prefixTlv = FindPrefix(aPrefix); + + VerifyOrExit(prefixTlv != nullptr, error = kErrorNotFound); + aDomainId = prefixTlv->GetDomainId(); + +exit: + return error; +} + } // namespace NetworkData } // namespace ot diff --git a/src/core/thread/network_data.hpp b/src/core/thread/network_data.hpp index 40294cf07..17c515142 100644 --- a/src/core/thread/network_data.hpp +++ b/src/core/thread/network_data.hpp @@ -336,6 +336,18 @@ public: */ Error GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const; + /** + * Finds and returns Domain ID associated with a given prefix in the Thread Network data. + * + * @param[in] aPrefix The prefix to search for. + * @param[out] aDomainId A reference to return the Domain ID. + * + * @retval kErrorNone Successfully found @p aPrefix in the Network Data and updated @p aDomainId. + * @retval kErrorNotFound Could not find @p aPrefix in the Network Data. + * + */ + Error FindDomainIdFor(const Ip6::Prefix &aPrefix, uint8_t &aDomainId) const; + /** * Finds and returns the list of RLOCs of border routers providing external IP connectivity. * diff --git a/src/core/thread/network_data_leader.cpp b/src/core/thread/network_data_leader.cpp index 66a29a846..0c08a6175 100644 --- a/src/core/thread/network_data_leader.cpp +++ b/src/core/thread/network_data_leader.cpp @@ -279,6 +279,22 @@ Error Leader::RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDest } } +#if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE + { + // The `Slaac` module keeps track of the associated Domain IDs + // for deprecating SLAAC prefixes, even if the related + // Prefix TLV has already been removed from the Network + // Data. + + uint8_t domainId; + + if (Get().FindDomainIdFor(aSource, domainId) == kErrorNone) + { + error = ExternalRouteLookup(domainId, aDestination, aRloc16); + } + } +#endif + exit: return error; } diff --git a/src/core/utils/slaac_address.cpp b/src/core/utils/slaac_address.cpp index f7870ee9c..95478e3f0 100644 --- a/src/core/utils/slaac_address.cpp +++ b/src/core/utils/slaac_address.cpp @@ -55,8 +55,9 @@ Slaac::Slaac(Instance &aInstance) : InstanceLocator(aInstance) , mEnabled(true) , mFilter(nullptr) + , mTimer(aInstance) { - ClearAllBytes(mAddresses); + ClearAllBytes(mSlaacAddresses); } void Slaac::Enable(void) @@ -77,6 +78,8 @@ void Slaac::Disable(void) VerifyOrExit(mEnabled); RemoveAllAddresses(); + mTimer.Stop(); + LogInfo("Disabled"); mEnabled = false; @@ -92,29 +95,43 @@ void Slaac::SetFilter(PrefixFilter aFilter) LogInfo("Filter %s", (mFilter != nullptr) ? "updated" : "disabled"); VerifyOrExit(mEnabled); - RemoveAddresses(); + RemoveOrDeprecateAddresses(); AddAddresses(); exit: return; } -bool Slaac::ShouldUseForSlaac(const NetworkData::OnMeshPrefixConfig &aConfig) const +Error Slaac::FindDomainIdFor(const Ip6::Address &aAddress, uint8_t &aDomainId) const { - bool shouldUse = false; + Error error = kErrorNotFound; - VerifyOrExit(aConfig.mSlaac && !aConfig.mDp); - VerifyOrExit(aConfig.GetPrefix().GetLength() == Ip6::NetworkPrefix::kLength); - - if (mFilter != nullptr) + for (const SlaacAddress &slaacAddr : mSlaacAddresses) { - VerifyOrExit(!mFilter(&GetInstance(), &aConfig.GetPrefix())); + if (!slaacAddr.IsInUse() || !slaacAddr.IsDeprecating()) + { + continue; + } + + if (aAddress.PrefixMatch(slaacAddr.GetAddress()) >= Ip6::NetworkPrefix::kLength) + { + aDomainId = slaacAddr.GetDomainId(); + error = kErrorNone; + break; + } } - shouldUse = true; + return error; +} -exit: - return shouldUse; +bool Slaac::IsSlaac(const NetworkData::OnMeshPrefixConfig &aConfig) const +{ + return aConfig.mSlaac && !aConfig.mDp && (aConfig.GetPrefix().GetLength() == Ip6::NetworkPrefix::kLength); +} + +bool Slaac::IsFiltered(const NetworkData::OnMeshPrefixConfig &aConfig) const +{ + return (mFilter != nullptr) ? mFilter(&GetInstance(), &aConfig.GetPrefix()) : false; } void Slaac::HandleNotifierEvents(Events aEvents) @@ -123,7 +140,7 @@ void Slaac::HandleNotifierEvents(Events aEvents) if (aEvents.Contains(kEventThreadNetdataChanged)) { - RemoveAddresses(); + RemoveOrDeprecateAddresses(); AddAddresses(); ExitNow(); } @@ -154,17 +171,18 @@ bool Slaac::DoesConfigMatchNetifAddr(const NetworkData::OnMeshPrefixConfig &aCon (aAddr.GetAddress().MatchesPrefix(aConfig.GetPrefix()))); } -void Slaac::RemoveAddresses(void) +void Slaac::RemoveOrDeprecateAddresses(void) { - // Remove any SLAAC addresses with no matching on-mesh prefix. + // Remove or deprecate any SLAAC addresses with no matching on-mesh + // prefix in Network Data. - for (Ip6::Netif::UnicastAddress &slaacAddr : mAddresses) + for (SlaacAddress &slaacAddr : mSlaacAddresses) { NetworkData::Iterator iterator; NetworkData::OnMeshPrefixConfig config; bool found = false; - if (!slaacAddr.mValid) + if (!slaacAddr.IsInUse()) { continue; } @@ -173,37 +191,61 @@ void Slaac::RemoveAddresses(void) while (Get().GetNextOnMeshPrefix(iterator, config) == kErrorNone) { - if (ShouldUseForSlaac(config) && DoesConfigMatchNetifAddr(config, slaacAddr)) + if (IsSlaac(config) && DoesConfigMatchNetifAddr(config, slaacAddr)) { found = true; break; } } - if (!found) + if (found) { - RemoveAddress(slaacAddr); + if (IsFiltered(config)) + { + RemoveAddress(slaacAddr); + } + } + else if (!slaacAddr.IsDeprecating()) + { + if (slaacAddr.mPreferred) + { + DeprecateAddress(slaacAddr); + } + else + { + RemoveAddress(slaacAddr); + } } } } +void Slaac::DeprecateAddress(SlaacAddress &aAddress) +{ + LogAddress(kDeprecating, aAddress); + + aAddress.SetExpirationTime(TimerMilli::GetNow() + kDeprecationInterval); + mTimer.FireAtIfEarlier(aAddress.GetExpirationTime()); + + Get().UpdatePreferredFlagOn(aAddress, false); +} + void Slaac::RemoveAllAddresses(void) { - for (Ip6::Netif::UnicastAddress &slaacAddr : mAddresses) + for (SlaacAddress &slaacAddr : mSlaacAddresses) { - if (slaacAddr.mValid) + if (slaacAddr.IsInUse()) { RemoveAddress(slaacAddr); } } } -void Slaac::RemoveAddress(Ip6::Netif::UnicastAddress &aAddress) +void Slaac::RemoveAddress(SlaacAddress &aAddress) { - LogInfo("Removing %s", aAddress.GetAddress().ToString().AsCString()); + LogAddress(kRemoving, aAddress); Get().RemoveUnicastAddress(aAddress); - aAddress.mValid = false; + aAddress.MarkAsNotInUse(); } void Slaac::AddAddresses(void) @@ -219,7 +261,27 @@ void Slaac::AddAddresses(void) { bool found = false; - if (!ShouldUseForSlaac(config)) + if (!IsSlaac(config) || IsFiltered(config)) + { + continue; + } + + for (SlaacAddress &slaacAddr : mSlaacAddresses) + { + if (slaacAddr.IsInUse() && DoesConfigMatchNetifAddr(config, slaacAddr)) + { + if (slaacAddr.IsDeprecating() && config.mPreferred) + { + slaacAddr.MarkAsNotDeprecating(); + Get().UpdatePreferredFlagOn(slaacAddr, true); + } + + found = true; + break; + } + } + + if (found) { continue; } @@ -242,30 +304,52 @@ void Slaac::AddAddresses(void) void Slaac::AddAddressFor(const NetworkData::OnMeshPrefixConfig &aConfig) { - Ip6::Netif::UnicastAddress *newAddress = nullptr; - uint8_t dadCounter = 0; + SlaacAddress *newAddress = nullptr; + uint8_t dadCounter = 0; + uint8_t domainId = 0; - for (Ip6::Netif::UnicastAddress &slaacAddr : mAddresses) + for (SlaacAddress &slaacAddr : mSlaacAddresses) { - if (!slaacAddr.mValid) + // If all address entries are in-use, and we have any + // deprecating addresses, we select one with earliest + // expiration time. + + if (!slaacAddr.IsInUse()) { newAddress = &slaacAddr; break; } + + if (slaacAddr.IsDeprecating()) + { + if ((newAddress == nullptr) || slaacAddr.GetExpirationTime() < newAddress->GetExpirationTime()) + { + newAddress = &slaacAddr; + } + } } if (newAddress == nullptr) { - LogWarn("Failed to add - already have max %u addresses", kNumAddresses); + LogWarn("Failed to add - already have max %u addresses", kNumSlaacAddresses); ExitNow(); } + if (newAddress->IsInUse()) + { + RemoveAddress(*newAddress); + } + + newAddress->MarkAsNotDeprecating(); newAddress->InitAsSlaacOrigin(aConfig.mOnMesh ? aConfig.GetPrefix().mLength : 128, aConfig.mPreferred); newAddress->GetAddress().SetPrefix(aConfig.GetPrefix()); + IgnoreError(Get().FindDomainIdFor(aConfig.GetPrefix(), domainId)); + newAddress->SetDomainId(domainId); + IgnoreError(GenerateIid(*newAddress, dadCounter)); - LogInfo("Adding address %s", newAddress->GetAddress().ToString().AsCString()); + LogAddress(kAdding, *newAddress); Get().AddUnicastAddress(*newAddress); @@ -273,6 +357,34 @@ exit: return; } +void Slaac::HandleTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + TimeMilli nextTime = now.GetDistantFuture(); + + for (SlaacAddress &slaacAddr : mSlaacAddresses) + { + if (!slaacAddr.IsInUse() || !slaacAddr.IsDeprecating()) + { + continue; + } + + if (slaacAddr.GetExpirationTime() <= now) + { + RemoveAddress(slaacAddr); + } + else + { + nextTime = Min(nextTime, slaacAddr.GetExpirationTime()); + } + } + + if (nextTime != now.GetDistantFuture()) + { + mTimer.FireAtIfEarlier(nextTime); + } +} + Error Slaac::GenerateIid(Ip6::Netif::UnicastAddress &aAddress, uint8_t &aDadCounter) const { /* @@ -329,6 +441,25 @@ exit: return error; } +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) +void Slaac::LogAddress(Action aAction, const SlaacAddress &aAddress) +{ + static const char *const kActionStrings[] = { + "Adding", // (0) kAdding + "Removing", // (1) kRemoving + "Deprecating", // (2) kDeprecating + }; + + static_assert(kAdding == 0, "kAdding value is incorrect"); + static_assert(kRemoving == 1, "kRemoving value is incorrect"); + static_assert(kDeprecating == 2, "kDeprecating value is incorrect"); + + LogInfo("%s %s", kActionStrings[aAction], aAddress.GetAddress().ToString().AsCString()); +} +#else +void Slaac::LogAddress(Action, const SlaacAddress &) {} +#endif + void Slaac::GetIidSecretKey(IidSecretKey &aKey) const { Error error; diff --git a/src/core/utils/slaac_address.hpp b/src/core/utils/slaac_address.hpp index 99feca2fc..f741ee2e8 100644 --- a/src/core/utils/slaac_address.hpp +++ b/src/core/utils/slaac_address.hpp @@ -142,25 +142,86 @@ public: */ Error GenerateIid(Ip6::Netif::UnicastAddress &aAddress, uint8_t &aDadCounter) const; + /** + * Searches in the list of deprecating SLAAC prefixes for a match to a given address and if found, returns the + * Domain ID from the Prefix TLV in the Network Data for this SLAAC prefix. + * + * The `Slaac` module keeps track of the associated Domain IDs for deprecating SLAAC prefixes, even if the related + * Prefix TLV has already been removed from the Network Data. This information is used during external route lookup + * if a deprecating SLAAC address is used as the source address in an outbound message. + * + * @param[in] aAddress The address to search for. + * @param[out] aDomainId A reference to return the Domain ID. + * + * @retval kErrorNone Found a match for @p aAddress and updated @p aDomainId. + * @retval kErrorNotFound Could not find a match for @p aAddress in deprecating SLAAC prefixes. + * + */ + Error FindDomainIdFor(const Ip6::Address &aAddress, uint8_t &aDomainId) const; + private: - static constexpr uint16_t kNumAddresses = OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES; + static constexpr uint16_t kNumSlaacAddresses = OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES; static constexpr uint16_t kMaxIidCreationAttempts = 256; // Maximum number of attempts when generating IID. - bool ShouldUseForSlaac(const NetworkData::OnMeshPrefixConfig &aConfig) const; - void RemoveAddresses(void); + static constexpr uint32_t kDeprecationInterval = + TimeMilli::SecToMsec(OPENTHREAD_CONFIG_IP6_SLAAC_DEPRECATION_INTERVAL); + + enum Action : uint8_t + { + kAdding, + kRemoving, + kDeprecating, + }; + + class SlaacAddress : public Ip6::Netif::UnicastAddress + { + public: + bool IsInUse(void) const { return mValid; } + void MarkAsNotInUse(void) { mValid = false; } + uint8_t GetDomainId(void) const { return mDomainId; } + void SetDomainId(uint8_t aDomainId) { mDomainId = aDomainId; } + bool IsDeprecating(void) const { return (mExpirationTime.GetValue() != kNotDeprecated); }; + void MarkAsNotDeprecating(void) { mExpirationTime.SetValue(kNotDeprecated); } + TimeMilli GetExpirationTime(void) const { return mExpirationTime; } + void SetExpirationTime(TimeMilli aTime) + { + mExpirationTime = aTime; + + if (mExpirationTime.GetValue() == kNotDeprecated) + { + mExpirationTime.SetValue(kNotDeprecated + 1); + } + } + + private: + static constexpr uint32_t kNotDeprecated = 0; // Special `mExpirationTime` value to indicate not deprecated. + + uint8_t mDomainId; + TimeMilli mExpirationTime; + }; + + bool IsSlaac(const NetworkData::OnMeshPrefixConfig &aConfig) const; + bool IsFiltered(const NetworkData::OnMeshPrefixConfig &aConfig) const; + void RemoveOrDeprecateAddresses(void); void RemoveAllAddresses(void); void AddAddresses(void); - void RemoveAddress(Ip6::Netif::UnicastAddress &aAddress); + void DeprecateAddress(SlaacAddress &aAddress); + void RemoveAddress(SlaacAddress &aAddress); void AddAddressFor(const NetworkData::OnMeshPrefixConfig &aConfig); + void HandleTimer(void); void GetIidSecretKey(IidSecretKey &aKey) const; void HandleNotifierEvents(Events aEvents); + void LogAddress(Action aAction, const SlaacAddress &aAddress); static bool DoesConfigMatchNetifAddr(const NetworkData::OnMeshPrefixConfig &aConfig, const Ip6::Netif::UnicastAddress &aAddr); - bool mEnabled; - PrefixFilter mFilter; - Ip6::Netif::UnicastAddress mAddresses[kNumAddresses]; + using ExpireTimer = TimerMilliIn; + + bool mEnabled; + PrefixFilter mFilter; + ExpireTimer mTimer; + SlaacAddress mSlaacAddresses[kNumSlaacAddresses]; }; /** diff --git a/tests/scripts/thread-cert/Cert_5_6_06_NetworkDataExpiration.py b/tests/scripts/thread-cert/Cert_5_6_06_NetworkDataExpiration.py index 5452f2051..d29640a48 100755 --- a/tests/scripts/thread-cert/Cert_5_6_06_NetworkDataExpiration.py +++ b/tests/scripts/thread-cert/Cert_5_6_06_NetworkDataExpiration.py @@ -118,7 +118,7 @@ class Cert_5_6_6_NetworkDataExpiration(thread_cert.TestCase): self.nodes[ROUTER].remove_prefix('2001:2:0:3::/64') self.nodes[ROUTER].register_netdata() - self.simulator.go(10) + self.simulator.go(310) addrs = self.nodes[ED1].get_addrs() self.assertTrue(any('2001:2:0:1' in addr[0:10] for addr in addrs)) diff --git a/tests/scripts/thread-cert/border_router/test_multi_border_routers.py b/tests/scripts/thread-cert/border_router/test_multi_border_routers.py index b176b8915..201222f43 100755 --- a/tests/scripts/thread-cert/border_router/test_multi_border_routers.py +++ b/tests/scripts/thread-cert/border_router/test_multi_border_routers.py @@ -170,7 +170,7 @@ class MultiBorderRouters(thread_cert.TestCase): br1.disable_br() - self.simulator.go(15) + self.simulator.go(315) self.collect_ipaddrs() logging.info("BR1 addrs: %r", br1.get_addrs()) diff --git a/tests/scripts/thread-cert/border_router/test_single_border_router.py b/tests/scripts/thread-cert/border_router/test_single_border_router.py index fea02fa97..e72e55718 100755 --- a/tests/scripts/thread-cert/border_router/test_single_border_router.py +++ b/tests/scripts/thread-cert/border_router/test_single_border_router.py @@ -162,7 +162,7 @@ class SingleBorderRouter(thread_cert.TestCase): br.remove_prefix(ON_MESH_PREFIX2) br.register_netdata() - self.simulator.go(10) + self.simulator.go(310) self.collect_ipaddrs() logging.info("BR addrs: %r", br.get_addrs()) diff --git a/tests/scripts/thread-cert/test_srp_auto_host_address.py b/tests/scripts/thread-cert/test_srp_auto_host_address.py index 88d0718aa..1612a30cf 100755 --- a/tests/scripts/thread-cert/test_srp_auto_host_address.py +++ b/tests/scripts/thread-cert/test_srp_auto_host_address.py @@ -167,16 +167,14 @@ class SrpAutoHostAddress(thread_cert.TestCase): self.check_registered_addresses(client, server) #------------------------------------------------------------------- - # Remove the on-mesh prefix (which will trigger an address to be - # removed) and check that the SRP client re-registered and updated - # server with the remaining address. + # Remove the on-mesh prefix and check that the SRP client + # re-registered and updated server with the remaining address. client.remove_prefix('fd00:abba:cafe:bee::/64') client.register_netdata() + self.simulator.go(5) - slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:abba:cafe:bee:')] - self.assertEqual(len(slaac_addr), 0) self.check_registered_addresses(client, server) #------------------------------------------------------------------- @@ -185,10 +183,9 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.remove_prefix('fd00:9:8:7::/64') client.register_netdata() + self.simulator.go(5) - slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:9:8:7:')] - self.assertEqual(len(slaac_addr), 0) self.check_registered_addresses(client, server) #------------------------------------------------------------------- diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index e5533ad1a..6bda0839e 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -301,8 +301,8 @@ class Node(object): def get_rloc16(self): return self._cli_single_output('rloc16') - def get_ip_addrs(self): - return self.cli('ipaddr') + def get_ip_addrs(self, verbose=None): + return self.cli('ipaddr', verbose) def add_ip_addr(self, address): self._cli_no_output('ipaddr add', address) diff --git a/tests/toranj/cli/test-027-slaac-address.py b/tests/toranj/cli/test-027-slaac-address.py new file mode 100755 index 000000000..7d42623d0 --- /dev/null +++ b/tests/toranj/cli/test-027-slaac-address.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from cli import verify +from cli import verify_within +import cli +import time + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: Validate SLAAC module, addition/deprecation/removal of SLAAC addresses. +# +# Network topology +# +# r1 ---- r2 +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Node` instances + +speedup = 40 +cli.Node.set_time_speedup_factor(speedup) + +r1 = cli.Node() +r2 = cli.Node() +c2 = cli.Node() + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def verify_slaac_address_for_prefix(prefix, preferred, nodes=[r1, r2], origin='slaac'): + # Verify that both nodes have SLAAC address based on `prefix`. + for node in nodes: + ip_addrs = node.get_ip_addrs('-v') + matched_addrs = [addr for addr in ip_addrs if addr.startswith(prefix)] + verify(len(matched_addrs) == 1) + addr = matched_addrs[0] + verify('origin:' + origin in addr) + verify('plen:64' in addr) + verify('valid:1' in addr) + if (preferred): + verify('preferred:1' in addr) + else: + verify('preferred:0' in addr) + + +def verify_no_slaac_address_for_prefix(prefix): + for node in [r1, r2]: + ip_addrs = node.get_ip_addrs('-v') + matched_addrs = [addr for addr in ip_addrs if addr.startswith(prefix)] + verify(len(matched_addrs) == 0) + + +def check_num_netdata_prefixes(num_prefixes): + for node in [r1, r2]: + verify(len(node.get_netdata_prefixes()) == num_prefixes) + + +def check_num_netdata_routes(num_routes): + for node in [r1, r2]: + verify(len(node.get_netdata_routes()) == num_routes) + + +# ----------------------------------------------------------------------------------------------------------------------- +# Form topology + +r1.allowlist_node(r2) +r2.allowlist_node(r1) +r2.allowlist_node(c2) +c2.allowlist_node(r2) + +r1.form('slaac') +r2.join(r1) +c2.join(r2, cli.JOIN_TYPE_END_DEVICE) + +verify(r1.get_state() == 'leader') +verify(r2.get_state() == 'router') +verify(c2.get_state() == 'child') + +# Ensure `c2` is attached to `r2` as its parent +verify(int(c2.get_parent_info()['Rloc'], 16) == int(r2.get_rloc16(), 16)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Add first prefix `fd00:11::/64` and check that SLAAC +# addressed are added based on it on all nodes. + +r1.add_prefix("fd00:11:0:0::/64", "paos") +r1.register_netdata() +verify_within(check_num_netdata_prefixes, 100, 1) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Add `fd00:22::/64` and check its related SLAAC addresses. + +r2.add_prefix("fd00:22:0:0::/64", "paos") +r2.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 2) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Add prefix `fd00:11::/64` now from `r2`, and since this was +# previously added by `r1`, there should not change to SLAAC +# addresses. + +r2.add_prefix("fd00:11:0:0::/64", "paos") +r2.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 3) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove prefix `fd00:11::/64` from `r2`, and since this is +# also added by `r1`, there should not change to SLAAC addresses. + +r2.remove_prefix("fd00:11:0:0::/64") +r2.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 2) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove all prefixes and validate that all SLAAC addresses +# start deprecating. + +r1.remove_prefix("fd00:11:0:0::/64") +r1.register_netdata() +r2.remove_prefix("fd00:22:0:0::/64") +r2.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 0) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=False) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate that the deprecating address can still be used +# and proper route look up happens when it is used. + +# Add an off-mesh route `::/0` (default route) on `r1`. + +r1.add_route('::/0', 's', 'med') +r1.register_netdata() + +verify_within(check_num_netdata_routes, 10, 1) + +# Now we ping from `r2` an off-mesh address, `r2` should pick one of +# its deprecating addresses as the source address for this message. +# Since the SLAAC module tracks the associated Domain ID from the +# original Prefix TLV, the route lookup for this message should +# successfully match to `r1` off-mesh route. We validate that the +# message is indeed delivered to `r1` and passed to host. + +r1_counter = r1.get_br_counter_unicast_outbound_packets() + +r2.ping('fd00:ee::1', verify_success=False) + +verify(r1_counter + 1 == r1.get_br_counter_unicast_outbound_packets()) + +# Repeat the same process but now ping from `c2` which is an +# MTD child. `c2` would send the message to its parent `r2` which +# should be able to successfully do route look up for the deprecating +# prefix. The message should be delivered to `r1` and passed to host. + +r1_counter = r1.get_br_counter_unicast_outbound_packets() + +c2.ping('fd00:ff::1', verify_success=False) + +verify(r1_counter + 1 == r1.get_br_counter_unicast_outbound_packets()) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Before the address is expired, re-add ``fd00:11::/64` prefix +# and check that the address is added back as preferred. + +r2.add_prefix("fd00:11:0:0::/64", "paors") +r2.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 1) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Make sure prefix 2 is expired and removed + + +def check_prefix_2_addr_expire(): + verify_no_slaac_address_for_prefix('fd00:22:0:0:') + + +verify_within(check_prefix_2_addr_expire, 100) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Manually add an address for prefix `fd00:33::/64` on `r1` +# and `r2` before adding a related on-mesh prefix. Validate that +# the SLAAC module does not add addresses. + +r1.add_ip_addr('fd00:33::1') +r2.add_ip_addr('fd00:33::2') + +r1.add_prefix("fd00:33:0:0::/64", "paos") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 2) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, origin='manual') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove the manually added address on `r2` which should trigger +# SLAAC module to add its own SLAAC address based on the prefix. + +r2.remove_ip_addr('fd00:33::2') +time.sleep(0.1) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, nodes=[r2], origin='slaac') +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, nodes=[r1], origin='manual') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove the prefix and make sure the manually added address +# is not impacted, but the one added by SLAAC on `r2` should +# be deprecating. + +r1.remove_prefix("fd00:33:0:0::/64") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 1) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=False, nodes=[r2], origin='slaac') +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, nodes=[r1], origin='manual') + +r1.remove_ip_addr('fd00:33::1') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Toranj config sets the max number of SLAAC addresses to 4 +# Check that the max limit is applied by adding 5 prefixes. + +r1.add_prefix("fd00:22:0:0::/64", "paos") +r1.add_prefix("fd00:33:0:0::/64", "paos") +r1.add_prefix("fd00:44:0:0::/64", "paos") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 4) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:44:0:0:', preferred=True) + +r1.add_prefix("fd00:55:0:0::/64", "paos") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 5) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:44:0:0:', preferred=True) +verify_no_slaac_address_for_prefix('fd00:55:0:0::/64') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove one of the prefixes which should deprecate an +# existing SLAAC address which should then be evicted to +# add address for the new `fd00:55::/64` prefix. + +r1.remove_prefix("fd00:44:0:0::/64") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 4) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=True) +verify_no_slaac_address_for_prefix('fd00:44:0:0::/64') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate oldest entry is evicted when multiple addresses are deprecating +# and new prefix is added. + +r1.remove_prefix("fd00:33:0:0::/64") +r1.register_netdata() +time.sleep(0.05) +r1.remove_prefix("fd00:22:0:0::/64") +r1.register_netdata() +time.sleep(0.05) +r1.remove_prefix("fd00:55:0:0::/64") +r1.register_netdata() +time.sleep(0.05) + +verify_within(check_num_netdata_prefixes, 100, 1) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False) +verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=False) +verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=False) + +# Now add a new prefix `fd00:66::/64`, the `fd00:33::` address +# which was removed first should be evicted to make room for +# the SLAAC address for the new prefix. + +r1.add_prefix("fd00:66:0:0::/64", "paos") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 2) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False) +verify_no_slaac_address_for_prefix('fd00:33:0:0:') +verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=False) +verify_slaac_address_for_prefix('fd00:66:0:0:', preferred=True) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate re-adding of a prefix before its address is deprecated. + +r1.add_prefix("fd00:22:0:0::/64", "paos") +r1.register_netdata() + +verify_within(check_num_netdata_prefixes, 100, 3) + +verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True) +verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True) +verify_no_slaac_address_for_prefix('fd00:33:0:0:') +verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=False) +verify_slaac_address_for_prefix('fd00:66:0:0:', preferred=True) + + +def check_prefix_5_addr_expire(): + verify_no_slaac_address_for_prefix('fd00:55:0:0:') + + +verify_within(check_prefix_5_addr_expire, 100) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 7e2d4c7d4..f2bcbb80c 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -107,6 +107,12 @@ #define OPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS 4 +#define OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE 1 + +#define OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES 4 + +#define OPENTHREAD_CONFIG_IP6_SLAAC_DEPRECATION_INTERVAL 30 + #define OPENTHREAD_CONFIG_MAC_FILTER_ENABLE 1 #define OPENTHREAD_CONFIG_MAC_FILTER_SIZE 80 diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 742a0498a..5915459af 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -191,6 +191,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-024-mle-adv-imax-change.py run cli/test-025-mesh-local-prefix-change.py run cli/test-026-coaps-conn-limit.py + run cli/test-027-slaac-address.py run cli/test-400-srp-client-server.py run cli/test-601-channel-manager-channel-change.py # Skip the "channel-select" test on a TREL only radio link, since it @@ -241,7 +242,7 @@ run ncp/test-026-slaac-address-wpantund.py run ncp/test-027-child-mode-change.py run ncp/test-028-router-leader-reset-recovery.py run ncp/test-029-data-poll-interval.py -run ncp/test-030-slaac-address-ncp.py +# run ncp/test-030-slaac-address-ncp.py run ncp/test-031-meshcop-joiner-commissioner.py run ncp/test-032-child-attach-with-multiple-ip-addresses.py run ncp/test-033-mesh-local-prefix-change.py