[routing-manager] delay removing external route for on-link prefixes (#6878)

Per RFC 4862 section 5.5.3, the SLAAC address of a on-link prefix
will not immediately be invalidated even if the on-link prefix is
sent with zero PIO valid lifetime but we currently removes the
external route for the on-link prefix when we see zero valid lifetime.
This results in an error case that a Thread device can not reach
a valid SLAAC address of a Wi-Fi host on the same infra link because
there is no routes for this address.

This commit fixes this issue by delay removing the external route
for the discovered on-link prefix untill its valid lifetime expires.
Meanwhile, the Border Router now considers a deprecated (zero preferred
lifetime) on-link prefix not usable. Thus, the Border Router will
start advertising its own on-link prefix when current on-link prefix
is deprecated.
This commit is contained in:
kangping
2021-08-12 09:28:27 -07:00
committed by Jonathan Hui
parent 5598848980
commit 4e717060fd
9 changed files with 353 additions and 189 deletions
@@ -237,6 +237,14 @@ public:
*/
void SetPreferredLifetime(uint32_t aPreferredLifetime) { mPreferredLifetime = HostSwap32(aPreferredLifetime); }
/**
* THis method returns the preferred lifetime of the prefix in seconds.
*
* @returns The preferred lifetime in seconds.
*
*/
uint32_t GetPreferredLifetime(void) const { return HostSwap32(mPreferredLifetime); }
/**
* This method sets the prefix.
*
@@ -261,7 +269,8 @@ public:
*/
bool IsValid(void) const
{
return (GetSize() == sizeof(*this)) && (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT);
return (GetSize() == sizeof(*this)) && (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT) &&
(GetPreferredLifetime() <= GetValidLifetime());
}
private:
+168 -105
View File
@@ -63,6 +63,7 @@ RoutingManager::RoutingManager(Instance &aInstance)
, mInfraIfIsRunning(false)
, mInfraIfIndex(0)
, mAdvertisedOnLinkPrefix(nullptr)
, mOnLinkPrefixDeprecateTimer(aInstance, HandleOnLinkPrefixDeprecateTimer)
, mTimeRouterAdvMessageLastUpdate(TimerMilli::GetNow())
, mDiscoveredPrefixInvalidTimer(aInstance, HandleDiscoveredPrefixInvalidTimer)
, mDiscoveredPrefixStaleTimer(aInstance, HandleDiscoveredPrefixStaleTimer)
@@ -224,17 +225,20 @@ void RoutingManager::Stop(void)
if (mAdvertisedOnLinkPrefix != nullptr)
{
RemoveExternalRoute(*mAdvertisedOnLinkPrefix);
}
InvalidateAllDiscoveredPrefixes();
// Start deprecating the local on-link prefix to send a PIO
// with zero preferred lifetime in `SendRouterAdvertisement`.
DeprecateOnLinkPrefix();
}
// Use empty OMR & on-link prefixes to invalidate possible advertised prefixes.
SendRouterAdvertisement(OmrPrefixArray(), nullptr);
mAdvertisedOmrPrefixes.Clear();
mAdvertisedOnLinkPrefix = nullptr;
mOnLinkPrefixDeprecateTimer.Stop();
InvalidateAllDiscoveredPrefixes();
mDiscoveredPrefixes.Clear();
mDiscoveredPrefixInvalidTimer.Stop();
mDiscoveredPrefixStaleTimer.Stop();
@@ -499,7 +503,7 @@ const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void)
for (const ExternalPrefix &prefix : mDiscoveredPrefixes)
{
if (!prefix.mIsOnLinkPrefix)
if (!prefix.mIsOnLinkPrefix || prefix.IsDeprecated())
{
continue;
}
@@ -521,6 +525,8 @@ const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void)
{
newOnLinkPrefix = &mLocalOnLinkPrefix;
}
mOnLinkPrefixDeprecateTimer.Stop();
}
// When an application-specific on-link prefix is received and it is bigger than the
// advertised prefix, we will not remove the advertised prefix. In this case, there
@@ -536,14 +542,7 @@ const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void)
{
otLogInfoBr("EvaluateOnLinkPrefix: There is already smaller on-link prefix %s on interface %u",
smallestOnLinkPrefix->ToString().AsCString(), mInfraIfIndex);
// TODO: we remove the advertised on-link prefix by setting zero valid lifetime for PIO,
// But SLAAC addresses configured by PIO prefix will not be removed immediately (
// https://tools.ietf.org/html/rfc4862#section-5.5.3). This leads to a situation that
// a WiFi device keeps using the old SLAAC address but a Thread device cannot reach to
// it. One solution is to delay removing external route until the SLAAC addresses are
// actually expired/deprecated.
RemoveExternalRoute(*mAdvertisedOnLinkPrefix);
DeprecateOnLinkPrefix();
}
}
@@ -551,6 +550,26 @@ exit:
return newOnLinkPrefix;
}
void RoutingManager::HandleOnLinkPrefixDeprecateTimer(Timer &aTimer)
{
aTimer.Get<RoutingManager>().HandleOnLinkPrefixDeprecateTimer();
}
void RoutingManager::HandleOnLinkPrefixDeprecateTimer(void)
{
otLogInfoBr("Local on-link prefix %s expired", mLocalOnLinkPrefix.ToString().AsCString());
RemoveExternalRoute(mLocalOnLinkPrefix);
}
void RoutingManager::DeprecateOnLinkPrefix(void)
{
OT_ASSERT(mAdvertisedOnLinkPrefix != nullptr);
otLogInfoBr("Deprecate local on-link prefix %s", mAdvertisedOnLinkPrefix->ToString().AsCString());
mOnLinkPrefixDeprecateTimer.StartAt(mTimeAdvertisedOnLinkPrefix,
TimeMilli::SecToMsec(kDefaultOnLinkPrefixLifetime));
}
// This method evaluate the routing policy depends on prefix and route
// information on Thread Network and infra link. As a result, this
// method May send RA messages on infra link and publish/unpublish
@@ -662,6 +681,8 @@ void RoutingManager::SendRouterAdvertisement(const OmrPrefixArray &aNewOmrPrefix
if (aNewOnLinkPrefix != nullptr)
{
OT_ASSERT(aNewOnLinkPrefix == &mLocalOnLinkPrefix);
RouterAdv::PrefixInfoOption pio;
pio.SetOnLink(true);
@@ -680,27 +701,29 @@ void RoutingManager::SendRouterAdvertisement(const OmrPrefixArray &aNewOmrPrefix
aNewOnLinkPrefix->ToString().AsCString(), mInfraIfIndex);
}
otLogInfoBr("Send on-link prefix %s in PIO (valid lifetime = %u seconds)",
aNewOnLinkPrefix->ToString().AsCString(), kDefaultOnLinkPrefixLifetime);
otLogInfoBr("Send on-link prefix %s in PIO (preferred lifetime = %u seconds, valid lifetime = %u seconds)",
aNewOnLinkPrefix->ToString().AsCString(), pio.GetPreferredLifetime(), pio.GetValidLifetime());
mTimeAdvertisedOnLinkPrefix = TimerMilli::GetNow();
}
else if (mAdvertisedOnLinkPrefix != nullptr)
else if (mOnLinkPrefixDeprecateTimer.IsRunning())
{
RouterAdv::PrefixInfoOption pio;
pio.SetOnLink(true);
pio.SetAutoAddrConfig(true);
pio.SetValidLifetime(TimeMilli::MsecToSec(mOnLinkPrefixDeprecateTimer.GetFireTime() - TimerMilli::GetNow()));
// Set zero valid lifetime to immediately invalidate the advertised on-link prefix.
pio.SetValidLifetime(0);
// Set zero preferred lifetime to immediately deprecate the advertised on-link prefix.
pio.SetPreferredLifetime(0);
pio.SetPrefix(*mAdvertisedOnLinkPrefix);
pio.SetPrefix(mLocalOnLinkPrefix);
OT_ASSERT(bufferLength + pio.GetSize() <= sizeof(buffer));
memcpy(buffer + bufferLength, &pio, pio.GetSize());
bufferLength += pio.GetSize();
otLogInfoBr("Stop advertising on-link prefix %s on interface %u",
mAdvertisedOnLinkPrefix->ToString().AsCString(), mInfraIfIndex);
otLogInfoBr("Send on-link prefix %s in PIO (preferred lifetime = %u seconds, valid lifetime = %u seconds)",
mLocalOnLinkPrefix.ToString().AsCString(), pio.GetPreferredLifetime(), pio.GetValidLifetime());
}
// Invalidate the advertised OMR prefixes if they are no longer in the new OMR prefix array.
@@ -852,12 +875,19 @@ void RoutingManager::HandleRouterSolicitTimer(void)
}
else
{
// Invalidate all prefixes that are not refreshed during Router Solicitation.
for (const ExternalPrefix &prefix : mDiscoveredPrefixes)
// Invalidate/deprecate all OMR/on-link prefixes that are not refreshed during Router Solicitation.
for (ExternalPrefix &prefix : mDiscoveredPrefixes)
{
if (prefix.mTimeLastUpdate <= mTimeRouterSolicitStart)
{
InvalidateDiscoveredPrefixes(&prefix.mPrefix, prefix.mIsOnLinkPrefix);
if (prefix.mIsOnLinkPrefix)
{
prefix.mPreferredLifetime = 0;
}
else
{
InvalidateDiscoveredPrefixes(&prefix.mPrefix, prefix.mIsOnLinkPrefix);
}
}
}
@@ -1022,8 +1052,11 @@ exit:
bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption &aPio, bool aManagedAddrConfig)
{
Ip6::Prefix prefix = aPio.GetPrefix();
bool needReevaluate = false;
Ip6::Prefix prefix = aPio.GetPrefix();
bool needReevaluate = false;
TimeMilli staleTime;
ExternalPrefix onLinkPrefix;
ExternalPrefix *existingPrefix = nullptr;
if (!IsValidOnLinkPrefix(aPio, aManagedAddrConfig))
{
@@ -1036,23 +1069,87 @@ bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption
otLogInfoBr("Discovered on-link prefix (%s, %u seconds) from interface %u", prefix.ToString().AsCString(),
aPio.GetValidLifetime(), mInfraIfIndex);
if (aPio.GetValidLifetime() == 0)
onLinkPrefix.mIsOnLinkPrefix = true;
onLinkPrefix.mPrefix = prefix;
onLinkPrefix.mValidLifetime = aPio.GetValidLifetime();
onLinkPrefix.mPreferredLifetime = aPio.GetPreferredLifetime();
onLinkPrefix.mTimeLastUpdate = TimerMilli::GetNow();
staleTime = TimerMilli::GetNow();
for (ExternalPrefix &externalPrefix : mDiscoveredPrefixes)
{
needReevaluate = InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ true) > 0;
if (externalPrefix == onLinkPrefix)
{
existingPrefix = &externalPrefix;
}
staleTime = OT_MAX(staleTime, externalPrefix.GetStaleTime());
}
if (existingPrefix == nullptr)
{
if (onLinkPrefix.mValidLifetime == 0)
{
ExitNow();
}
if (!mDiscoveredPrefixes.IsFull())
{
SuccessOrExit(AddExternalRoute(prefix, OT_ROUTE_PREFERENCE_MED));
existingPrefix = mDiscoveredPrefixes.PushBack();
*existingPrefix = onLinkPrefix;
needReevaluate = true;
}
else
{
otLogWarnBr("Discovered too many prefixes, ignore new on-link prefix %s", prefix.ToString().AsCString());
ExitNow();
}
}
else
{
needReevaluate = AddDiscoveredPrefix(prefix, /* aIsOnLinkPrefix */ true, aPio.GetValidLifetime());
constexpr uint32_t kTwoHoursInSeconds = 2 * 3600;
// Per RFC 4862 section 5.5.3.e:
// 1. If the received Valid Lifetime is greater than 2 hours or
// greater than RemainingLifetime, set the valid lifetime of the
// corresponding address to the advertised Valid Lifetime.
// 2. If RemainingLifetime is less than or equal to 2 hours, ignore
// the Prefix Information option with regards to the valid
// lifetime, unless ...
// 3. Otherwise, reset the valid lifetime of the corresponding
// address to 2 hours.
if (onLinkPrefix.mValidLifetime > kTwoHoursInSeconds ||
onLinkPrefix.GetExpireTime() > existingPrefix->GetExpireTime())
{
existingPrefix->mValidLifetime = onLinkPrefix.mValidLifetime;
}
else if (existingPrefix->GetExpireTime() > TimerMilli::GetNow() + TimeMilli::SecToMsec(kTwoHoursInSeconds))
{
existingPrefix->mValidLifetime = kTwoHoursInSeconds;
}
existingPrefix->mPreferredLifetime = onLinkPrefix.mPreferredLifetime;
existingPrefix->mTimeLastUpdate = onLinkPrefix.mTimeLastUpdate;
needReevaluate = (existingPrefix->mPreferredLifetime == 0);
}
mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(existingPrefix->GetExpireTime());
staleTime = OT_MAX(staleTime, existingPrefix->GetStaleTime());
mDiscoveredPrefixStaleTimer.FireAtIfEarlier(staleTime);
exit:
return needReevaluate;
}
bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &aRio)
{
Ip6::Prefix prefix = aRio.GetPrefix();
bool needReevaluate = false;
Ip6::Prefix prefix = aRio.GetPrefix();
bool needReevaluate = false;
ExternalPrefix omrPrefix;
ExternalPrefix *existingPrefix = nullptr;
if (!IsValidOmrPrefix(prefix))
{
@@ -1078,14 +1175,51 @@ bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &
if (aRio.GetRouteLifetime() == 0)
{
needReevaluate = (InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ false) > 0);
needReevaluate = InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ false);
ExitNow();
}
else
omrPrefix.mIsOnLinkPrefix = false;
omrPrefix.mPrefix = prefix;
omrPrefix.mValidLifetime = aRio.GetRouteLifetime();
omrPrefix.mRoutePreference = aRio.GetPreference();
omrPrefix.mTimeLastUpdate = TimerMilli::GetNow();
for (ExternalPrefix &externalPrefix : mDiscoveredPrefixes)
{
needReevaluate =
AddDiscoveredPrefix(prefix, /* aIsOnLinkPrefix */ false, aRio.GetRouteLifetime(), aRio.GetPreference());
if (externalPrefix == omrPrefix)
{
existingPrefix = &externalPrefix;
}
mDiscoveredPrefixStaleTimer.FireAtIfEarlier(externalPrefix.GetStaleTime());
}
if (existingPrefix == nullptr)
{
if (omrPrefix.mValidLifetime == 0)
{
ExitNow();
}
if (!mDiscoveredPrefixes.IsFull())
{
SuccessOrExit(AddExternalRoute(prefix, omrPrefix.mRoutePreference));
existingPrefix = mDiscoveredPrefixes.PushBack();
needReevaluate = true;
}
else
{
otLogWarnBr("Discovered too many prefixes, ignore new prefix %s", prefix.ToString().AsCString());
ExitNow();
}
}
*existingPrefix = omrPrefix;
mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(existingPrefix->GetExpireTime());
mDiscoveredPrefixStaleTimer.FireAtIfEarlier(existingPrefix->GetStaleTime());
exit:
return needReevaluate;
}
@@ -1144,77 +1278,6 @@ void RoutingManager::InvalidateAllDiscoveredPrefixes(void)
OT_ASSERT(mDiscoveredPrefixes.IsEmpty());
}
static void UpdateStaleTime(TimeMilli &aonLinkPrefixStaleTime,
TimeMilli &aomrPrefixStaleTime,
bool aIsOnLink,
TimeMilli aPrefixStaleTime)
{
if (aIsOnLink)
{
aonLinkPrefixStaleTime = OT_MAX(aonLinkPrefixStaleTime, aPrefixStaleTime);
}
else
{
aomrPrefixStaleTime = OT_MIN(aomrPrefixStaleTime, aPrefixStaleTime);
}
}
// Adds a discovered prefix on infra link. If the same prefix already exists,
// only the lifetime will be updated. Returns a boolean which indicates whether
// a new prefix is added.
bool RoutingManager::AddDiscoveredPrefix(const Ip6::Prefix &aPrefix,
bool aIsOnLinkPrefix,
uint32_t aLifetime,
otRoutePreference aRoutePreference)
{
OT_ASSERT(aIsOnLinkPrefix ? IsValidOmrPrefix(aPrefix) : IsValidOnLinkPrefix(aPrefix));
OT_ASSERT(aLifetime > 0);
bool addedNewPrefix = false;
ExternalPrefix *discoveredPrefix = nullptr;
TimeMilli now = TimerMilli::GetNow();
TimeMilli onLinkPrefixStaleTime = now;
TimeMilli omrPrefixStaleTime = now.GetDistantFuture();
for (ExternalPrefix &prefix : mDiscoveredPrefixes)
{
if (aPrefix == prefix.mPrefix && aIsOnLinkPrefix == prefix.mIsOnLinkPrefix)
{
discoveredPrefix = &prefix;
}
UpdateStaleTime(onLinkPrefixStaleTime, omrPrefixStaleTime, prefix.mIsOnLinkPrefix, prefix.GetStaleTime());
}
if (discoveredPrefix == nullptr)
{
if (!mDiscoveredPrefixes.IsFull())
{
SuccessOrExit(AddExternalRoute(aPrefix, aRoutePreference));
discoveredPrefix = mDiscoveredPrefixes.PushBack();
addedNewPrefix = true;
}
else
{
otLogWarnBr("Discovered too many prefixes, ignore new prefix %s", aPrefix.ToString().AsCString());
ExitNow();
}
}
discoveredPrefix->mPrefix = aPrefix;
discoveredPrefix->mIsOnLinkPrefix = aIsOnLinkPrefix;
discoveredPrefix->mValidLifetime = aLifetime;
discoveredPrefix->mTimeLastUpdate = now;
mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(discoveredPrefix->GetExpireTime());
UpdateStaleTime(onLinkPrefixStaleTime, omrPrefixStaleTime, discoveredPrefix->mIsOnLinkPrefix,
discoveredPrefix->GetStaleTime());
mDiscoveredPrefixStaleTimer.FireAtIfEarlier(OT_MIN(onLinkPrefixStaleTime, omrPrefixStaleTime));
exit:
return addedNewPrefix;
}
bool RoutingManager::NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const
{
NetworkData::Iterator iterator = NetworkData::kIteratorInit;
+45 -12
View File
@@ -113,9 +113,9 @@ public:
* This method returns the off-mesh-routable (OMR) prefix.
*
* The randomly generated 64-bit prefix will be published
* in the Thread network if there isn't already a OMR prefix.
* in the Thread network if there isn't already an OMR prefix.
*
* @param[in] aPrefix A reference to where the prefix will be output to.
* @param[out] aPrefix A reference to where the prefix will be output to.
*
* @retval kErrorInvalidState The Border Routing Manager is not initialized yet.
* @retval kErrorNone Successfully retrieved the OMR prefix.
@@ -130,7 +130,7 @@ public:
* on the infrastructure link if there isn't already a usable
* on-link prefix being advertised on the link.
*
* @param[in] aPrefix A reference to where the prefix will be output to.
* @param[out] aPrefix A reference to where the prefix will be output to.
*
* @retval kErrorInvalidState The Border Routing Manager is not initialized yet.
* @retval kErrorNone Successfully retrieved the on-link prefix.
@@ -220,15 +220,44 @@ private:
// This struct represents an external prefix which is
// discovered on the infrastructure interface.
struct ExternalPrefix : public Clearable<ExternalPrefix>
struct ExternalPrefix : public Clearable<ExternalPrefix>, public Unequatable<ExternalPrefix>
{
Ip6::Prefix mPrefix;
uint32_t mValidLifetime;
TimeMilli mTimeLastUpdate;
bool mIsOnLinkPrefix;
TimeMilli GetExpireTime(void) const { return mTimeLastUpdate + GetPrefixExpireDelay(mValidLifetime); }
TimeMilli GetStaleTime(void) const { return mTimeLastUpdate + TimeMilli::SecToMsec(kRtrAdvStaleTime); }
union
{
// Preferred Lifetime of on-link prefix, available
// only when `mIsOnLinkPrefix` is TRUE.
uint32_t mPreferredLifetime;
// The preference of this route, available
// only when `mIsOnLinkPrefix` is FALSE.
otRoutePreference mRoutePreference;
};
TimeMilli mTimeLastUpdate;
bool mIsOnLinkPrefix;
bool operator==(const ExternalPrefix &aPrefix) const
{
return mPrefix == aPrefix.mPrefix && mIsOnLinkPrefix == aPrefix.mIsOnLinkPrefix;
}
bool IsDeprecated(void) const
{
OT_ASSERT(mIsOnLinkPrefix);
return mTimeLastUpdate + TimeMilli::SecToMsec(mPreferredLifetime) <= TimerMilli::GetNow();
}
TimeMilli GetExpireTime(void) const { return mTimeLastUpdate + GetPrefixExpireDelay(mValidLifetime); }
TimeMilli GetStaleTime(void) const
{
uint32_t delay = OT_MIN(kRtrAdvStaleTime, mIsOnLinkPrefix ? mPreferredLifetime : mValidLifetime);
return mTimeLastUpdate + TimeMilli::SecToMsec(delay);
}
static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime);
};
@@ -268,17 +297,16 @@ private:
static void HandleDiscoveredPrefixStaleTimer(Timer &aTimer);
void HandleDiscoveredPrefixStaleTimer(void);
static void HandleRoutingPolicyTimer(Timer &aTimer);
void HandleOnLinkPrefixDeprecateTimer(void);
static void HandleOnLinkPrefixDeprecateTimer(Timer &aTimer);
void DeprecateOnLinkPrefix(void);
void HandleRouterSolicit(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength);
void HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength);
bool UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption &aPio, bool aManagedAddrConfig);
bool UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &aRio);
bool InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix = nullptr, bool aIsOnLinkPrefix = true);
void InvalidateAllDiscoveredPrefixes(void);
bool AddDiscoveredPrefix(const Ip6::Prefix &aPrefix,
bool aIsOnLinkPrefix,
uint32_t aLifetime,
otRoutePreference aRoutePreference = OT_ROUTE_PREFERENCE_MED);
bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const;
bool UpdateRouterAdvMessage(const RouterAdv::RouterAdvMessage *aRouterAdvMessage);
@@ -321,6 +349,11 @@ private:
// Could only be nullptr or a pointer to mLocalOnLinkPrefix.
const Ip6::Prefix *mAdvertisedOnLinkPrefix;
// The last time when the on-link prefix is advertised with
// non-zero preferred lifetime.
TimeMilli mTimeAdvertisedOnLinkPrefix;
TimerMilli mOnLinkPrefixDeprecateTimer;
// The array of prefixes discovered on the infra link. Those
// prefixes consist of on-link prefix(es) and OMR prefixes
// advertised by BRs in another Thread Network which is connected to
@@ -28,6 +28,7 @@
#
import logging
import unittest
from ipaddress import IPv6Network
import config
import thread_cert
@@ -108,6 +109,8 @@ class MultiBorderRouters(thread_cert.TestCase):
self.simulator.go(5)
self.assertEqual('router', router1.get_state())
self.simulator.go(5)
br2.start()
self.simulator.go(5)
self.assertEqual('router', br2.get_state())
@@ -129,27 +132,29 @@ class MultiBorderRouters(thread_cert.TestCase):
logging.info("ROUTER2 addrs: %r", router2.get_addrs())
logging.info("HOST addrs: %r", host.get_addrs())
self.assertTrue(len(br1.get_prefixes()) == 1)
self.assertTrue(len(router1.get_prefixes()) == 1)
self.assertTrue(len(br2.get_prefixes()) == 1)
self.assertTrue(len(router2.get_prefixes()) == 1)
self.assertEqual(len(br1.get_prefixes()), 1)
self.assertEqual(len(router1.get_prefixes()), 1)
self.assertEqual(len(br2.get_prefixes()), 1)
self.assertEqual(len(router2.get_prefixes()), 1)
br1_omr_prefix = br1.get_prefixes()[0]
br1_omr_prefix = br1.get_omr_prefix()
self.assertEqual(br1_omr_prefix, br1.get_prefixes()[0].split(' ')[0])
# Each BR should independently register an external route for the on-link prefix.
self.assertTrue(len(br1.get_routes()) == 2)
self.assertTrue(len(router1.get_routes()) == 2)
self.assertTrue(len(br2.get_routes()) == 2)
self.assertTrue(len(router2.get_routes()) == 2)
self.assertEqual(len(br1.get_routes()), 2)
self.assertEqual(len(router1.get_routes()), 2)
self.assertEqual(len(br2.get_routes()), 2)
self.assertEqual(len(router2.get_routes()), 2)
external_route = br1.get_routes()[0]
br1_on_link_prefix = external_route.split(' ')[0]
br1_on_link_prefix = br1.get_on_link_prefix()
self.assertEqual(br1_on_link_prefix, br1.get_routes()[0].split(' ')[0])
self.assertEqual(br1_on_link_prefix, br1.get_routes()[1].split(' ')[0])
self.assertTrue(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(host.get_matched_ula_addresses(br1_on_link_prefix)) == 1)
self.assertEqual(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(host.get_matched_ula_addresses(br1_on_link_prefix)), 1)
# Router1 and Router2 can ping each other inside the Thread network.
self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
@@ -176,31 +181,35 @@ class MultiBorderRouters(thread_cert.TestCase):
logging.info("ROUTER2 addrs: %r", router2.get_addrs())
logging.info("HOST addrs: %r", host.get_addrs())
self.assertGreaterEqual(len(host.get_addrs()), 2)
self.assertGreaterEqual(len(host.get_addrs()), 3)
self.assertTrue(len(br1.get_prefixes()) == 1)
self.assertTrue(len(router1.get_prefixes()) == 1)
self.assertTrue(len(br2.get_prefixes()) == 1)
self.assertTrue(len(router2.get_prefixes()) == 1)
self.assertEqual(len(br1.get_prefixes()), 1)
self.assertEqual(len(router1.get_prefixes()), 1)
self.assertEqual(len(br2.get_prefixes()), 1)
self.assertEqual(len(router2.get_prefixes()), 1)
br2_omr_prefix = br1.get_prefixes()[0]
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
br2_omr_prefix = br2.get_omr_prefix()
self.assertEqual(br2_omr_prefix, br2.get_prefixes()[0].split(' ')[0])
# Only BR2 will register external route for the on-link prefix.
self.assertTrue(len(br1.get_routes()) == 1)
self.assertTrue(len(router1.get_routes()) == 1)
self.assertTrue(len(br2.get_routes()) == 1)
self.assertTrue(len(router2.get_routes()) == 1)
# Only BR2 will keep the route for BR1's on-link prefix
# and add route for on-link prefix of its own.
self.assertEqual(len(br1.get_routes()), 2)
self.assertEqual(len(router1.get_routes()), 2)
self.assertEqual(len(br2.get_routes()), 2)
self.assertEqual(len(router2.get_routes()), 2)
br2_external_route = br2.get_routes()[0]
br2_on_link_prefix = br2_external_route.split(' ')[0]
br2_external_routes = [route.split(' ')[0] for route in br2.get_routes()]
self.assertTrue(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
br2_on_link_prefix = br2.get_on_link_prefix()
self.assertEqual(set(map(IPv6Network, br2_external_routes)),
set(map(IPv6Network, [br1_on_link_prefix, br2_on_link_prefix])))
self.assertTrue(len(host.get_matched_ula_addresses(br2_on_link_prefix)) == 1)
self.assertEqual(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(host.get_matched_ula_addresses(br2_on_link_prefix)), 1)
# Router1 and Router2 can ping each other inside the Thread network.
self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
@@ -120,8 +120,8 @@ class MultiThreadNetworks(thread_cert.TestCase):
self.assertTrue(len(br2.get_prefixes()) == 1)
self.assertTrue(len(router2.get_prefixes()) == 1)
br1_omr_prefix = br1.get_prefixes()[0]
br2_omr_prefix = br2.get_prefixes()[0]
br1_omr_prefix = br1.get_omr_prefix()
br2_omr_prefix = br2.get_omr_prefix()
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
@@ -107,6 +107,9 @@ class SingleBorderRouter(thread_cert.TestCase):
self.assertEqual(len(br.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
self.assertEqual(len(router.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
# radvd doesn't deprecates the PIO so that the Border Router will not
# advertise its own on-link prefix immediately.
self.assertEqual(len(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)), 1)
self.assertTrue(router.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
@@ -318,12 +318,11 @@ class SingleBorderRouter(thread_cert.TestCase):
br.start_radvd_service(prefix=config.ONLINK_GUA_PREFIX, slaac=True)
self.simulator.go(5)
self.assertEqual(len(br.get_routes()), 1)
self.assertTrue(br.get_routes()[0].startswith(config.ONLINK_GUA_PREFIX.split('::/')[0]))
self.assertEqual(len(router.get_routes()), 1)
self.assertTrue(router.get_routes()[0].startswith(config.ONLINK_GUA_PREFIX.split('::/')[0]))
self.assertEqual(len(br.get_routes()), 2)
self.assertEqual(len(router.get_routes()), 2)
self.assertTrue(router.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_GUA)[0]))
self.assertTrue(router.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
self.assertTrue(host.ping(router.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True))
@@ -26,9 +26,9 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
import ipaddress
import logging
import unittest
from ipaddress import IPv6Network
import config
import thread_cert
@@ -124,7 +124,7 @@ class MultiThreadNetworks(thread_cert.TestCase):
self.assertEqual(len(br1.get_routes()), 1)
br1_on_link_prefix = br1.get_routes()[0].split(' ')[0]
self.assertEqual(ipaddress.IPv6Network(br1_on_link_prefix), ipaddress.IPv6Network(ON_LINK_PREFIX))
self.assertEqual(IPv6Network(br1_on_link_prefix), IPv6Network(ON_LINK_PREFIX))
host_on_link_addr = host.get_matched_ula_addresses(br1_on_link_prefix)[0]
self.assertTrue(router1.ping(host_on_link_addr))
@@ -156,14 +156,15 @@ class MultiThreadNetworks(thread_cert.TestCase):
self.assertTrue(len(br2.get_prefixes()) == 1)
self.assertTrue(len(router2.get_prefixes()) == 1)
br1_omr_prefix = br1.get_prefixes()[0].split(' ')[0]
br2_omr_prefix = br2.get_prefixes()[0].split(' ')[0]
br1_omr_prefix = br1.get_omr_prefix()
br2_omr_prefix = br2.get_omr_prefix()
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
# Verify that BR1 removed ON_LINK_PREFIX from its
# external routes list.
self.assertEqual(len(br1.get_routes()), 2)
self.assertEqual(len(router1.get_routes()), 2)
# Verify that the Border Routers starts advertsing new on-link prefix
# but don't remove the external routes for the radvd on-link prefix
# immediately, because the SLAAC addresses are still valid.
self.assertEqual(len(br1.get_routes()), 3)
self.assertEqual(len(router1.get_routes()), 3)
self.assertEqual(len(br2.get_routes()), 2)
self.assertEqual(len(router2.get_routes()), 2)
@@ -172,21 +173,39 @@ class MultiThreadNetworks(thread_cert.TestCase):
on_link_prefixes = list(set(br1_external_routes).intersection(br2_external_routes))
self.assertEqual(len(on_link_prefixes), 1)
on_link_prefix = on_link_prefixes[0]
self.assertEqual(IPv6Network(on_link_prefixes[0]), IPv6Network(br2.get_on_link_prefix()))
# Verify that both BR1 and BR2 are using a new on-link prefix.
# BR1 is depending on the Vicarious Router Solicitation to find
# out that the ON_LINK_PREFIX is stale and starts Router Solicitation
# on its own.
self.assertNotEqual(ipaddress.IPv6Network(on_link_prefix), ipaddress.IPv6Network(ON_LINK_PREFIX))
router1_omr_addr = router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
router2_omr_addr = router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
host_on_link_addr = host.get_matched_ula_addresses(on_link_prefix)[0]
self.assertTrue(router1.ping(host_on_link_addr))
self.assertTrue(
host.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True, interface=host_on_link_addr))
self.assertTrue(router2.ping(host.get_matched_ula_addresses(on_link_prefix)[0]))
self.assertTrue(
host.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True, interface=host_on_link_addr))
# Make sure that addresses of both the deprecated radvd `ON_LINK_PREFIX`
# and preferred Border Router on-link prefix can be reached by Thread
# devices in network of Border Router 1.
for host_on_link_addr in [
host.get_matched_ula_addresses(on_link_prefixes[0])[0],
host.get_matched_ula_addresses(ON_LINK_PREFIX)[0]
]:
self.assertTrue(router1.ping(host_on_link_addr))
self.assertTrue(host.ping(router1_omr_addr, backbone=True, interface=host_on_link_addr))
host_on_link_addr = host.get_matched_ula_addresses(ON_LINK_PREFIX)[0]
# Make sure that addresses of the deprecated radvd `ON_LINK_PREFIX`
# can't be reached by Thread devices in network of Border Router 2.
self.assertFalse(router2.ping(host_on_link_addr))
self.assertFalse(host.ping(router2_omr_addr, backbone=True, interface=host_on_link_addr))
# Wait 30 seconds for the radvd `ON_LINK_PREFIX` to be invalidated
# and make sure that Thread devices in both networks can't reach
# the on-link address.
self.simulator.go(30) # Valid Lifetime of radvd PIO is set to 60 seconds.
self.assertEqual(len(host.get_matched_ula_addresses(ON_LINK_PREFIX)), 0)
self.assertFalse(router1.ping(host_on_link_addr))
self.assertFalse(host.ping(router1_omr_addr, backbone=True, interface=host_on_link_addr))
self.assertFalse(router2.ping(host_on_link_addr))
self.assertFalse(host.ping(router2_omr_addr, backbone=True, interface=host_on_link_addr))
# Verify connectivity between the two networks.
self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
self.assertTrue(router2.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+38 -9
View File
@@ -38,6 +38,7 @@ import sys
import time
import traceback
import unittest
from ipaddress import IPv6Address, IPv6Network
from typing import Union, Dict, Optional, List
import pexpect
@@ -1149,7 +1150,7 @@ class NodeImpl:
self.send_command(cmd)
self._expect_done()
def multicast_listener_list(self) -> Dict[ipaddress.IPv6Address, int]:
def multicast_listener_list(self) -> Dict[IPv6Address, int]:
cmd = 'bbr mgmt mlr listener'
self.send_command(cmd)
@@ -1157,7 +1158,7 @@ class NodeImpl:
for line in self._expect_results("\S+ \d+"):
line = line.split()
assert len(line) == 2, line
ip = ipaddress.IPv6Address(line[0])
ip = IPv6Address(line[0])
timeout = int(line[1])
assert ip not in table
@@ -1170,9 +1171,9 @@ class NodeImpl:
self.send_command(cmd)
self._expect_done()
def multicast_listener_add(self, ip: Union[ipaddress.IPv6Address, str], timeout: int = 0):
if not isinstance(ip, ipaddress.IPv6Address):
ip = ipaddress.IPv6Address(ip)
def multicast_listener_add(self, ip: Union[IPv6Address, str], timeout: int = 0):
if not isinstance(ip, IPv6Address):
ip = IPv6Address(ip)
cmd = f'bbr mgmt mlr listener add {ip.compressed} {timeout}'
self.send_command(cmd)
@@ -1183,7 +1184,7 @@ class NodeImpl:
self.send_command(cmd)
self._expect_done()
def register_multicast_listener(self, *ipaddrs: Union[ipaddress.IPv6Address, str], timeout=None):
def register_multicast_listener(self, *ipaddrs: Union[IPv6Address, str], timeout=None):
assert len(ipaddrs) > 0, ipaddrs
ipaddrs = map(str, ipaddrs)
@@ -1198,7 +1199,7 @@ class NodeImpl:
status = int(m.group(1))
failed_num = int(m.group(2))
assert failed_num == len(lines) - 1
failed_ips = list(map(ipaddress.IPv6Address, lines[1:]))
failed_ips = list(map(IPv6Address, lines[1:]))
print(f"register_multicast_listener {ipaddrs} => status: {status}, failed ips: {failed_ips}")
return status, failed_ips
@@ -1516,7 +1517,7 @@ class NodeImpl:
return None
def get_mleid_iid(self):
ml_eid = ipaddress.IPv6Address(self.get_mleid())
ml_eid = IPv6Address(self.get_mleid())
return ml_eid.packed[8:].hex()
def get_eidcaches(self):
@@ -1606,6 +1607,22 @@ class NodeImpl:
return None
def get_ip6_address_by_prefix(self, prefix: Union[str, IPv6Network]) -> List[IPv6Address]:
"""Get addresses matched with given prefix.
Args:
prefix: the prefix to match against.
Can be either a string or ipaddress.IPv6Network.
Returns:
The IPv6 address list.
"""
if isinstance(prefix, str):
prefix = IPv6Network(prefix)
addrs = map(IPv6Address, self.get_addrs())
return [addr for addr in addrs if addr in prefix]
def get_ip6_address(self, address_type):
"""Get specific type of IPv6 address configured on thread device.
@@ -1661,6 +1678,16 @@ class NodeImpl:
self.send_command('br disable')
self._expect_done()
def get_omr_prefix(self):
cmd = 'br omrprefix'
self.send_command(cmd)
return self._expect_command_output(cmd)[0]
def get_on_link_prefix(self):
cmd = 'br onlinkprefix'
self.send_command(cmd)
return self._expect_command_output(cmd)[0]
def get_prefixes(self):
return self.get_netdata()['Prefixes']
@@ -2936,6 +2963,8 @@ interface eth0
AdvOnLink on;
AdvAutonomous %s;
AdvRouterAddr off;
AdvPreferredLifetime 40;
AdvValidLifetime 60;
};
};
EOF
@@ -3009,7 +3038,7 @@ class HostNode(LinuxHost, OtbrDocker):
addrs = []
for addr in self.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA):
if ipaddress.IPv6Address(addr) in ipaddress.IPv6Network(prefix):
if IPv6Address(addr) in IPv6Network(prefix):
addrs.append(addr)
return addrs