[slaac] implement address deprecation mechanism (#9585)

This commit implements address deprecation mechanism in `Slaac` class.
When a prefix is removed from Network Data, its corresponding SLAAC
address is not removed immediately. Instead, it is marked as
deprecated and its "preferred" flag is set to false. After a
deprecation interval (300 seconds), the deprecated address is
removed. If the prefix is re-added to Network Data before the
deprecation time elapses, the SLAAC address is also reinstated.

Since the number of SLAAC address entries is limited, non-deprecated
addresses are prioritized. This means that if a new entry is required
for a new prefix, the earliest deprecating entry can be evicted to
accommodate the new entry.

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 in `NetworkData::Leader::RouteLookup()`
if a deprecating SLAAC address is used as the source address in
an outbound message, ensuring that the message is not dropped and
can be delivered.

This commit also adds a detailed test `test-027-slaac-address.py`
validating various behaviors of SLAAC module.
This commit is contained in:
Abtin Keshavarzian
2024-02-13 14:40:55 -08:00
committed by GitHub
parent 0f00846e7d
commit e0294176c1
14 changed files with 683 additions and 52 deletions
+14
View File
@@ -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
*
+12
View File
@@ -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
+12
View File
@@ -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.
*
+16
View File
@@ -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<Utils::Slaac>().FindDomainIdFor(aSource, domainId) == kErrorNone)
{
error = ExternalRouteLookup(domainId, aDestination, aRloc16);
}
}
#endif
exit:
return error;
}
+163 -32
View File
@@ -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<NetworkData::Leader>().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<ThreadNetif>().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<ThreadNetif>().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<ThreadNetif>().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<NetworkData::Leader>().FindDomainIdFor(aConfig.GetPrefix(), domainId));
newAddress->SetDomainId(domainId);
IgnoreError(GenerateIid(*newAddress, dadCounter));
LogInfo("Adding address %s", newAddress->GetAddress().ToString().AsCString());
LogAddress(kAdding, *newAddress);
Get<ThreadNetif>().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;
+68 -7
View File
@@ -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<Slaac, &Slaac::HandleTimer>;
bool mEnabled;
PrefixFilter mFilter;
ExpireTimer mTimer;
SlaacAddress mSlaacAddresses[kNumSlaacAddresses];
};
/**
@@ -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))
@@ -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())
@@ -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())
@@ -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)
#-------------------------------------------------------------------
+2 -2
View File
@@ -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)
+381
View File
@@ -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))
@@ -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
+2 -1
View File
@@ -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