diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index e33697e2c..5cd6d3ee0 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -65,9 +65,12 @@ RoutingManager::RoutingManager(Instance &aInstance) , mAdvertisedOmrPrefixNum(0) , mAdvertisedOnLinkPrefix(nullptr) , mDiscoveredPrefixNum(0) + , mTimeRouterAdvMessageLastUpdate(TimerMilli::GetNow()) , mDiscoveredPrefixInvalidTimer(aInstance, HandleDiscoveredPrefixInvalidTimer) + , mDiscoveredPrefixStaleTimer(aInstance, HandleDiscoveredPrefixStaleTimer) , mRouterAdvertisementTimer(aInstance, HandleRouterAdvertisementTimer) , mRouterAdvertisementCount(0) + , mVicariousRouterSolicitTimer(aInstance, HandleVicariousRouterSolicitTimer) , mRouterSolicitTimer(aInstance, HandleRouterSolicitTimer) , mRouterSolicitCount(0) , mRoutingPolicyTimer(aInstance, HandleRoutingPolicyTimer) @@ -216,10 +219,12 @@ void RoutingManager::Stop(void) memset(mDiscoveredPrefixes, 0, sizeof(mDiscoveredPrefixes)); mDiscoveredPrefixNum = 0; mDiscoveredPrefixInvalidTimer.Stop(); + mDiscoveredPrefixStaleTimer.Stop(); mRouterAdvertisementTimer.Stop(); mRouterAdvertisementCount = 0; + mVicariousRouterSolicitTimer.Stop(); mRouterSolicitTimer.Stop(); mRouterSolicitCount = 0; @@ -492,7 +497,7 @@ const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void) const Ip6::Prefix *smallestOnLinkPrefix = nullptr; // We don't evaluate on-link prefix if we are doing Router Solicitation. - VerifyOrExit(!mRouterSolicitTimer.IsRunning()); + VerifyOrExit(!mRouterSolicitTimer.IsRunning(), newOnLinkPrefix = mAdvertisedOnLinkPrefix); for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) { @@ -536,7 +541,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 removes the advertised on-link prefix by setting valid lifetime for PIO. + // 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 @@ -617,19 +622,21 @@ void RoutingManager::StartRoutingPolicyEvaluationDelay(void) // between 0 and kMaxRtrSolicitationDelay. void RoutingManager::StartRouterSolicitationDelay(void) { - OT_ASSERT(mAdvertisedOnLinkPrefix == nullptr); - uint32_t randomDelay; - mRouterAdvMessage.SetToDefault(); + VerifyOrExit(!mRouterSolicitTimer.IsRunning() && mRouterSolicitCount == 0); - mRouterSolicitCount = 0; + mVicariousRouterSolicitTimer.Stop(); static_assert(kMaxRtrSolicitationDelay > 0, "invalid maximum Router Solicitation delay"); randomDelay = Random::NonCrypto::GetUint32InRange(0, Time::SecToMsec(kMaxRtrSolicitationDelay)); otLogInfoBr("start Router Solicitation, scheduled in %u milliseconds", randomDelay); + mTimeRouterSolicitStart = TimerMilli::GetNow(); mRouterSolicitTimer.Start(randomDelay); + +exit: + return; } Error RoutingManager::SendRouterSolicitation(void) @@ -805,6 +812,27 @@ void RoutingManager::HandleRouterAdvertisementTimer(void) EvaluateRoutingPolicy(); } +void RoutingManager::HandleVicariousRouterSolicitTimer(Timer &aTimer) +{ + aTimer.Get().HandleVicariousRouterSolicitTimer(); +} + +void RoutingManager::HandleVicariousRouterSolicitTimer(void) +{ + otLogInfoBr("vicarious router solicitation time out"); + + for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) + { + ExternalPrefix &prefix = mDiscoveredPrefixes[i]; + + if (prefix.mTimeLastUpdate <= mTimeVicariousRouterSolicitStart) + { + StartRouterSolicitationDelay(); + break; + } + } +} + void RoutingManager::HandleRouterSolicitTimer(Timer &aTimer) { aTimer.Get().HandleRouterSolicitTimer(); @@ -839,11 +867,40 @@ void RoutingManager::HandleRouterSolicitTimer(void) } else { + // Invalidate all prefixes that are not refreshed during Router Solicitation. + for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) + { + ExternalPrefix &prefix = mDiscoveredPrefixes[i]; + + if (prefix.mTimeLastUpdate <= mTimeRouterSolicitStart) + { + InvalidateDiscoveredPrefixes(&prefix.mPrefix, prefix.mIsOnLinkPrefix); + } + } + + // Invalidate the learned RA message if it is not refreshed during Router Solicitation. + if (mTimeRouterAdvMessageLastUpdate <= mTimeRouterSolicitStart) + { + UpdateRouterAdvMessage(/* aRouterAdvMessage */ nullptr); + } + // Re-evaluate our routing policy and send Router Advertisement if necessary. EvaluateRoutingPolicy(); + mRouterSolicitCount = 0; } } +void RoutingManager::HandleDiscoveredPrefixStaleTimer(Timer &aTimer) +{ + aTimer.Get().HandleDiscoveredPrefixStaleTimer(); +} + +void RoutingManager::HandleDiscoveredPrefixStaleTimer(void) +{ + otLogInfoBr("all on-link prefixes are stale"); + StartRouterSolicitationDelay(); +} + void RoutingManager::HandleDiscoveredPrefixInvalidTimer(Timer &aTimer) { aTimer.Get().HandleDiscoveredPrefixInvalidTimer(); @@ -870,12 +927,17 @@ void RoutingManager::HandleRouterSolicit(const Ip6::Address &aSrcAddress, OT_UNUSED_VARIABLE(aBufferLength); VerifyOrExit(!mRouterSolicitTimer.IsRunning()); - otLogInfoBr("received Router Solicitation from %s on interface %u", aSrcAddress.ToString().AsCString(), mInfraIfIndex); - randomDelay = Random::NonCrypto::GetUint32InRange(0, kMaxRaDelayTime); + if (!mVicariousRouterSolicitTimer.IsRunning()) + { + mTimeVicariousRouterSolicitStart = TimerMilli::GetNow(); + mVicariousRouterSolicitTimer.Start(Time::SecToMsec(kVicariousSolicitationTime)); + } + // Schedule Router Advertisements with random delay. + randomDelay = Random::NonCrypto::GetUint32InRange(0, kMaxRaDelayTime); otLogInfoBr("Router Advertisement scheduled in %u milliseconds", randomDelay); mRouterAdvertisementTimer.Start(randomDelay); @@ -883,7 +945,7 @@ exit: return; } -uint32_t RoutingManager::GetPrefixExpireDelay(uint32_t aValidLifetime) +uint32_t RoutingManager::ExternalPrefix::GetPrefixExpireDelay(uint32_t aValidLifetime) { uint32_t delay; @@ -965,7 +1027,7 @@ void RoutingManager::HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, // initiated from the infra interface. if (otPlatInfraIfHasAddress(mInfraIfIndex, &aSrcAddress)) { - needReevaluate |= UpdateRouterAdvMessage(*routerAdvMessage); + needReevaluate |= UpdateRouterAdvMessage(routerAdvMessage); } if (needReevaluate) @@ -1048,50 +1110,42 @@ exit: bool RoutingManager::InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix, bool aIsOnLinkPrefix) { - uint8_t removedNum = 0; - ExternalPrefix *keptPrefix = mDiscoveredPrefixes; - TimeMilli now = TimerMilli::GetNow(); - TimeMilli earliestExpireTime = now.GetDistantFuture(); - uint8_t keptOnLinkPrefixNum = 0; + uint8_t removedNum = 0; + ExternalPrefix *remainingPrefix = mDiscoveredPrefixes; + TimeMilli now = TimerMilli::GetNow(); + uint8_t remainingOnLinkPrefixNum = 0; + + mDiscoveredPrefixInvalidTimer.Stop(); for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) { ExternalPrefix &prefix = mDiscoveredPrefixes[i]; if ((aPrefix != nullptr && prefix.mPrefix == *aPrefix && prefix.mIsOnLinkPrefix == aIsOnLinkPrefix) || - (prefix.mExpireTime <= now)) + (prefix.GetExpireTime() <= now)) { RemoveExternalRoute(prefix.mPrefix); ++removedNum; } else { - earliestExpireTime = OT_MIN(earliestExpireTime, prefix.mExpireTime); - *keptPrefix = prefix; - ++keptPrefix; + mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(prefix.GetExpireTime()); + *remainingPrefix = prefix; + ++remainingPrefix; if (prefix.mIsOnLinkPrefix) { - ++keptOnLinkPrefixNum; + ++remainingOnLinkPrefixNum; } } } mDiscoveredPrefixNum -= removedNum; - if (keptOnLinkPrefixNum == 0) + if (remainingOnLinkPrefixNum == 0 && mAdvertisedOnLinkPrefix == nullptr) { - mDiscoveredPrefixInvalidTimer.Stop(); - - if (mAdvertisedOnLinkPrefix == nullptr) - { - // There are no valid on-link prefixes on infra link now, start Router Solicitation - // To find out more on-link prefixes or timeout to advertise my local on-link prefix. - StartRouterSolicitationDelay(); - } - } - else - { - mDiscoveredPrefixInvalidTimer.FireAt(earliestExpireTime); + // There are no valid on-link prefixes on infra link now, start Router Solicitation + // To discover more on-link prefixes or timeout to advertise my local on-link prefix. + StartRouterSolicitationDelay(); } return (removedNum != 0); // If anything was removed we need to reevaluate. @@ -1099,11 +1153,9 @@ bool RoutingManager::InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix, bo void RoutingManager::InvalidateAllDiscoveredPrefixes(void) { - TimeMilli past = TimerMilli::GetNow(); - for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) { - mDiscoveredPrefixes[i].mExpireTime = past; + mDiscoveredPrefixes[i].mValidLifetime = 0; } InvalidateDiscoveredPrefixes(); @@ -1111,6 +1163,21 @@ void RoutingManager::InvalidateAllDiscoveredPrefixes(void) OT_ASSERT(mDiscoveredPrefixNum == 0); } +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. @@ -1122,7 +1189,11 @@ bool RoutingManager::AddDiscoveredPrefix(const Ip6::Prefix &aPrefix, OT_ASSERT(aIsOnLinkPrefix ? IsValidOmrPrefix(aPrefix) : IsValidOnLinkPrefix(aPrefix)); OT_ASSERT(aLifetime > 0); - bool added = false; + bool addedNewPrefix = false; + ExternalPrefix *discoveredPrefix = nullptr; + TimeMilli now = TimerMilli::GetNow(); + TimeMilli onLinkPrefixStaleTime = now; + TimeMilli omrPrefixStaleTime = now.GetDistantFuture(); for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) { @@ -1130,51 +1201,39 @@ bool RoutingManager::AddDiscoveredPrefix(const Ip6::Prefix &aPrefix, if (aPrefix == prefix.mPrefix && aIsOnLinkPrefix == prefix.mIsOnLinkPrefix) { - prefix.mExpireTime = TimerMilli::GetNow() + GetPrefixExpireDelay(aLifetime); - mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(prefix.mExpireTime); + discoveredPrefix = &prefix; + } - otLogInfoBr("discovered prefix %s refreshed lifetime: %u seconds", aPrefix.ToString().AsCString(), - aLifetime); + UpdateStaleTime(onLinkPrefixStaleTime, omrPrefixStaleTime, prefix.mIsOnLinkPrefix, prefix.GetStaleTime()); + } + + if (discoveredPrefix == nullptr) + { + if (mDiscoveredPrefixNum < kMaxDiscoveredPrefixNum) + { + SuccessOrExit(AddExternalRoute(aPrefix, aRoutePreference)); + discoveredPrefix = &mDiscoveredPrefixes[mDiscoveredPrefixNum++]; + addedNewPrefix = true; + } + else + { + otLogWarnBr("discovered too many prefixes, ignore new prefix %s", aPrefix.ToString().AsCString()); ExitNow(); } } - if (mDiscoveredPrefixNum < kMaxDiscoveredPrefixNum) - { - ExternalPrefix &newPrefix = mDiscoveredPrefixes[mDiscoveredPrefixNum]; + discoveredPrefix->mPrefix = aPrefix; + discoveredPrefix->mIsOnLinkPrefix = aIsOnLinkPrefix; + discoveredPrefix->mValidLifetime = aLifetime; + discoveredPrefix->mTimeLastUpdate = now; + mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(discoveredPrefix->GetExpireTime()); + UpdateStaleTime(onLinkPrefixStaleTime, omrPrefixStaleTime, discoveredPrefix->mIsOnLinkPrefix, + discoveredPrefix->GetStaleTime()); - SuccessOrExit(AddExternalRoute(aPrefix, aRoutePreference)); - - if (aIsOnLinkPrefix && mRouterSolicitCount > 0) - { - // Stop Router Solicitation if we discovered a valid on-link prefix. - // Otherwise, we wait till the Router Solicitation process times out. - // So the maximum delay before the Border Router starts advertising - // its own on-link prefix is 10 seconds = RS_DELAY (1) + RS_INTERNAL (4) - // + RS_INTERVAL (4) + RS_DELAY (1). - // - // Always send at least one RS message so that all BRs will respond. - // Consider that there are multiple BRs on the infra link, we may not learn - // RIOs of other BRs if the router discovery process is stopped immediately - // before sending any RS messages because of receiving an unsolicited RA. - mRouterSolicitTimer.Stop(); - } - - newPrefix.mPrefix = aPrefix; - newPrefix.mIsOnLinkPrefix = aIsOnLinkPrefix; - newPrefix.mExpireTime = TimerMilli::GetNow() + GetPrefixExpireDelay(aLifetime); - mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(newPrefix.mExpireTime); - - ++mDiscoveredPrefixNum; - added = true; - } - else - { - otLogWarnBr("discovered too many prefixes, ignore new prefix %s", aPrefix.ToString().AsCString()); - } + mDiscoveredPrefixStaleTimer.FireAtIfEarlier(OT_MIN(onLinkPrefixStaleTime, omrPrefixStaleTime)); exit: - return added; + return addedNewPrefix; } bool RoutingManager::NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const @@ -1197,20 +1256,22 @@ bool RoutingManager::NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) co // Update the `mRouterAdvMessage` with given Router Advertisement message. // Returns a boolean which indicates whether there are changes of `mRouterAdvMessage`. -bool RoutingManager::UpdateRouterAdvMessage(const RouterAdv::RouterAdvMessage &aRouterAdvMessage) +bool RoutingManager::UpdateRouterAdvMessage(const RouterAdv::RouterAdvMessage *aRouterAdvMessage) { RouterAdv::RouterAdvMessage oldRouterAdvMessage; oldRouterAdvMessage = mRouterAdvMessage; - if (aRouterAdvMessage.GetRouterLifetime() == 0) + + mTimeRouterAdvMessageLastUpdate = TimerMilli::GetNow(); + if (aRouterAdvMessage == nullptr || aRouterAdvMessage->GetRouterLifetime() == 0) { mRouterAdvMessage.SetToDefault(); } else { - mRouterAdvMessage = aRouterAdvMessage; - // TODO: add a timer for invalidating the learned RA parameters - // for cases that the other RA daemon crashed or is force killed. + mRouterAdvMessage = *aRouterAdvMessage; + mDiscoveredPrefixStaleTimer.FireAtIfEarlier(mTimeRouterAdvMessageLastUpdate + + Time::SecToMsec(kRtrAdvStaleTime)); } return (mRouterAdvMessage != oldRouterAdvMessage); diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 33307520e..800f9c2e7 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -65,8 +65,8 @@ namespace BorderRouter { /** * This class implements bi-directional routing between Thread and Infrastructure networks. * - * The Border Routing manager works on both Thread interface and infrastructure interface. All ICMPv6 messages are - * sent/received on the infrastructure interface. + * The Border Routing manager works on both Thread interface and infrastructure interface. + * All ICMPv6 messages are sent/received on the infrastructure interface. * */ class RoutingManager : public InstanceLocator @@ -156,8 +156,14 @@ private: enum : uint32_t { - kDefaultOmrPrefixLifetime = 1800u, // The default OMR prefix valid lifetime. In seconds. - kDefaultOnLinkPrefixLifetime = 1800u, // The default on-link prefix valid lifetime. In seconds. + kMaxInitRtrAdvertisements = 3, // The maximum number of initial Router Advertisements. + kMaxRtrSolicitations = 3, // The Maximum number of Router Solicitations before sending Router Advertisements. + }; + + enum : uint32_t + { + kDefaultOmrPrefixLifetime = 1800, // The default OMR prefix valid lifetime. In seconds. + kDefaultOnLinkPrefixLifetime = 1800, // The default on-link prefix valid lifetime. In seconds. kMaxRtrAdvInterval = 600, // Maximum Router Advertisement Interval. In seconds. kMinRtrAdvInterval = kMaxRtrAdvInterval / 3, // Minimum Router Advertisement Interval. In seconds. kMaxInitRtrAdvInterval = 16, // Maximum Initial Router Advertisement Interval. In seconds. @@ -165,25 +171,40 @@ private: kRtrSolicitationInterval = 4, // The interval between Router Solicitations. In seconds. kMaxRtrSolicitationDelay = 1, // The maximum delay for initial solicitation. In seconds. kMaxRoutingPolicyDelay = 1, // The maximum delay for routing policy evaluation. In seconds. + + // The STALE_RA_TIME in seconds. The Routing Manager will consider the prefixes + // and learned RA parameters STALE when they are not refreshed in STALE_RA_TIME + // seconds. The Routing Manager will then start Router Solicitation to verify + // that the STALE prefix is not being advertised anymore and remove the STALE + // prefix. + // The value is chosen in range of [`kMaxRtrAdvInterval` upper bound (1800s), `kDefaultOnLinkPrefixLifetime`]. + kRtrAdvStaleTime = 1800, + + // The VICARIOUS_SOLICIT_TIME in seconds. The Routing Manager will consider + // the discovered prefixes invalid if they are not refreshed after receiving + // a Router Solicitation message. + // The value is equal to Router Solicitation timeout. + kVicariousSolicitationTime = kRtrSolicitationInterval * (kMaxRtrSolicitations - 1) + kMaxRtrSolicitationDelay, }; static_assert(kMinRtrAdvInterval <= 3 * kMaxRtrAdvInterval / 4, "invalid RA intervals"); static_assert(kDefaultOmrPrefixLifetime >= kMaxRtrAdvInterval, "invalid default OMR prefix lifetime"); static_assert(kDefaultOnLinkPrefixLifetime >= kMaxRtrAdvInterval, "invalid default on-link prefix lifetime"); - - enum : uint32_t - { - kMaxInitRtrAdvertisements = 3, // The maximum number of initial Router Advertisements. - kMaxRtrSolicitations = 3, // The Maximum number of Router Solicitations before sending Router Advertisements. - }; + static_assert(kRtrAdvStaleTime >= 1800 && kRtrAdvStaleTime <= kDefaultOnLinkPrefixLifetime, + "invalid RA STALE time"); // This struct represents an external prefix which is // discovered on the infrastructure interface. struct ExternalPrefix : public Clearable { Ip6::Prefix mPrefix; - TimeMilli mExpireTime; + uint32_t mValidLifetime; + TimeMilli mTimeLastUpdate; bool mIsOnLinkPrefix; + + TimeMilli GetExpireTime(void) const { return mTimeLastUpdate + GetPrefixExpireDelay(mValidLifetime); } + TimeMilli GetStaleTime(void) const { return mTimeLastUpdate + TimeMilli::SecToMsec(kRtrAdvStaleTime); } + static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime); }; void EvaluateState(void); @@ -212,13 +233,14 @@ private: static void HandleRouterAdvertisementTimer(Timer &aTimer); void HandleRouterAdvertisementTimer(void); - + static void HandleVicariousRouterSolicitTimer(Timer &aTimer); + void HandleVicariousRouterSolicitTimer(void); static void HandleRouterSolicitTimer(Timer &aTimer); void HandleRouterSolicitTimer(void); - static void HandleDiscoveredPrefixInvalidTimer(Timer &aTimer); void HandleDiscoveredPrefixInvalidTimer(void); - + static void HandleDiscoveredPrefixStaleTimer(Timer &aTimer); + void HandleDiscoveredPrefixStaleTimer(void); static void HandleRoutingPolicyTimer(Timer &aTimer); void HandleRouterSolicit(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); @@ -232,14 +254,13 @@ private: uint32_t aLifetime, otRoutePreference aRoutePreference = OT_ROUTE_PREFERENCE_MED); bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const; - bool UpdateRouterAdvMessage(const RouterAdv::RouterAdvMessage &aRouterAdvMessage); + bool UpdateRouterAdvMessage(const RouterAdv::RouterAdvMessage *aRouterAdvMessage); - static bool IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig); - static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix); - static bool IsValidOnLinkPrefix(const RouterAdv::PrefixInfoOption &aPio, bool aManagedAddrConfig); - static bool IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix); - static bool ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum); - static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime); + static bool IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig); + static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix); + static bool IsValidOnLinkPrefix(const RouterAdv::PrefixInfoOption &aPio, bool aManagedAddrConfig); + static bool IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix); + static bool ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum); // Indicates whether the Routing Manager is running (started). bool mIsRunning; @@ -287,13 +308,18 @@ private: // This value is initialized with `RouterAdvMessage::SetToDefault` // and updated with RA messages initiated from infra interface. RouterAdv::RouterAdvMessage mRouterAdvMessage; + TimeMilli mTimeRouterAdvMessageLastUpdate; TimerMilli mDiscoveredPrefixInvalidTimer; + TimerMilli mDiscoveredPrefixStaleTimer; TimerMilli mRouterAdvertisementTimer; uint32_t mRouterAdvertisementCount; + TimerMilli mVicariousRouterSolicitTimer; + TimeMilli mTimeVicariousRouterSolicitStart; TimerMilli mRouterSolicitTimer; + TimeMilli mTimeRouterSolicitStart; uint8_t mRouterSolicitCount; TimerMilli mRoutingPolicyTimer; 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 new file mode 100755 index 000000000..35f41f626 --- /dev/null +++ b/tests/scripts/thread-cert/border_router/test_vicarious_router_solicit.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, 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. +# +import ipaddress +import logging +import unittest + +import config +import thread_cert + +# Test description: +# This test verifies Vicarious Router Solicitation. +# +# Topology: +# -------------(eth)---------------------------- +# | | | +# BR1 BR2 HOST +# | | +# ROUTER1 ROUTER2 +# +# Thread Net1 Thread Net2 +# + +BR1 = 1 +ROUTER1 = 2 +BR2 = 3 +ROUTER2 = 4 +HOST = 5 + +CHANNEL1 = 18 +CHANNEL2 = 19 + + +class MultiThreadNetworks(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR1: { + 'name': 'BR_1', + 'allowlist': [ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 1, + }, + ROUTER1: { + 'name': 'Router_1', + 'allowlist': [BR1], + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 1, + }, + BR2: { + 'name': 'BR_2', + 'allowlist': [ROUTER2], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL2, + 'router_selection_jitter': 1, + }, + ROUTER2: { + 'name': 'Router_2', + 'allowlist': [BR2], + 'version': '1.2', + 'channel': CHANNEL2, + 'router_selection_jitter': 1, + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def test(self): + ON_LINK_PREFIX = 'fd00::/64' + br1 = self.nodes[BR1] + router1 = self.nodes[ROUTER1] + br2 = self.nodes[BR2] + router2 = self.nodes[ROUTER2] + host = self.nodes[HOST] + + host.start(start_radvd=True, prefix=ON_LINK_PREFIX, slaac=True) + self.simulator.go(5) + + br1.start() + self.simulator.go(5) + self.assertEqual('leader', br1.get_state()) + + router1.start() + self.simulator.go(5) + self.assertEqual('router', router1.get_state()) + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", br1.get_addrs()) + logging.info("ROUTER1 addrs: %r", router1.get_addrs()) + logging.info("HOST addrs: %r", host.get_addrs()) + + 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)) + + host_on_link_addr = host.get_matched_ula_addresses(br1_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)) + + # Force kill the radvd host so that BR2 will start advertising its own on-link prefix. + host.kill_radvd_service() + + br2.start() + self.simulator.go(5) + self.assertEqual('leader', br2.get_state()) + + router2.start() + self.simulator.go(5) + self.assertEqual('router', router2.get_state()) + + self.simulator.go(25) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", br1.get_addrs()) + logging.info("ROUTER1 addrs: %r", router1.get_addrs()) + logging.info("BR2 addrs: %r", br2.get_addrs()) + 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) + + br1_omr_prefix = br1.get_prefixes()[0].split(' ')[0] + br2_omr_prefix = br2.get_prefixes()[0].split(' ')[0] + 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) + self.assertEqual(len(br2.get_routes()), 2) + self.assertEqual(len(router2.get_routes()), 2) + + br1_external_routes = [route.split(' ')[0] for route in br1.get_routes()] + br2_external_routes = [route.split(' ')[0] for route in br2.get_routes()] + + 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] + + # 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)) + + 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)) + 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])) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index fd3e59c46..828a96b8b 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -2823,8 +2823,8 @@ interface eth0 AdvReachableTime 200; AdvRetransTimer 200; AdvDefaultLifetime 1800; - MinRtrAdvInterval 3; - MaxRtrAdvInterval 30; + MinRtrAdvInterval 1200; + MaxRtrAdvInterval 1800; AdvDefaultPreference low; prefix %s @@ -2842,6 +2842,9 @@ EOF def stop_radvd_service(self): self.bash('service radvd stop') + def kill_radvd_service(self): + self.bash('pkill radvd') + class OtbrNode(LinuxHost, NodeImpl, OtbrDocker): is_otbr = True @@ -2889,7 +2892,7 @@ class HostNode(LinuxHost, OtbrDocker): addrs = [] for addr in self.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA): - if addr.startswith(prefix.split('::')[0]): + if ipaddress.IPv6Address(addr) in ipaddress.IPv6Network(prefix): addrs.append(addr) return addrs