diff --git a/src/core/border_router/router_advertisement.hpp b/src/core/border_router/router_advertisement.hpp index 81f97e05a..605cc194d 100644 --- a/src/core/border_router/router_advertisement.hpp +++ b/src/core/border_router/router_advertisement.hpp @@ -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: diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index ef0836adc..29788852d 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -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().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; diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 44eb52713..9c607ce5b 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -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 + struct ExternalPrefix : public Clearable, public Unequatable { 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 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 0d3f354e4..8441ea07d 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 @@ -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])) diff --git a/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py b/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py index 4724b1c93..b0e9f92d0 100755 --- a/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py +++ b/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py @@ -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) diff --git a/tests/scripts/thread-cert/border_router/test_radvd_coexist.py b/tests/scripts/thread-cert/border_router/test_radvd_coexist.py index a201a9fb8..3c4f4845c 100755 --- a/tests/scripts/thread-cert/border_router/test_radvd_coexist.py +++ b/tests/scripts/thread-cert/border_router/test_radvd_coexist.py @@ -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])) 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 0fcdabfd2..51afe1b0a 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 @@ -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)) diff --git a/tests/scripts/thread-cert/border_router/test_vicarious_router_solicit.py b/tests/scripts/thread-cert/border_router/test_vicarious_router_solicit.py index 35f41f626..ee5684222 100755 --- a/tests/scripts/thread-cert/border_router/test_vicarious_router_solicit.py +++ b/tests/scripts/thread-cert/border_router/test_vicarious_router_solicit.py @@ -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])) diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 63c51a5e7..754261676 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -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