| /* |
| * Copyright (c) 2020, 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. |
| */ |
| |
| /** |
| * @file |
| * This file includes implementation for the RA-based routing management. |
| * |
| */ |
| |
| #include "border_router/routing_manager.hpp" |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE |
| |
| #include <string.h> |
| |
| #include <openthread/border_router.h> |
| #include <openthread/platform/border_routing.h> |
| #include <openthread/platform/infra_if.h> |
| |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| #include "common/num_utils.hpp" |
| #include "common/numeric_limits.hpp" |
| #include "common/random.hpp" |
| #include "common/settings.hpp" |
| #include "instance/instance.hpp" |
| #include "meshcop/extended_panid.hpp" |
| #include "net/ip6.hpp" |
| #include "net/nat64_translator.hpp" |
| #include "net/nd6.hpp" |
| #include "thread/mle_router.hpp" |
| #include "thread/network_data_leader.hpp" |
| #include "thread/network_data_local.hpp" |
| #include "thread/network_data_notifier.hpp" |
| |
| namespace ot { |
| |
| namespace BorderRouter { |
| |
| RegisterLogModule("RoutingManager"); |
| |
| RoutingManager::RoutingManager(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mIsRunning(false) |
| , mIsEnabled(false) |
| , mInfraIf(aInstance) |
| , mOmrPrefixManager(aInstance) |
| , mRioAdvertiser(aInstance) |
| , mOnLinkPrefixManager(aInstance) |
| , mDiscoveredPrefixTable(aInstance) |
| , mRoutePublisher(aInstance) |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| , mNat64PrefixManager(aInstance) |
| #endif |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| , mPdPrefixManager(aInstance) |
| #endif |
| , mRsSender(aInstance) |
| , mDiscoveredPrefixStaleTimer(aInstance) |
| , mRoutingPolicyTimer(aInstance) |
| { |
| mBrUlaPrefix.Clear(); |
| } |
| |
| Error RoutingManager::Init(uint32_t aInfraIfIndex, bool aInfraIfIsRunning) |
| { |
| Error error; |
| |
| VerifyOrExit(GetState() == kStateUninitialized || GetState() == kStateDisabled, error = kErrorInvalidState); |
| |
| if (!mInfraIf.IsInitialized()) |
| { |
| LogInfo("Initializing - InfraIfIndex:%lu", ToUlong(aInfraIfIndex)); |
| SuccessOrExit(error = mInfraIf.Init(aInfraIfIndex)); |
| SuccessOrExit(error = LoadOrGenerateRandomBrUlaPrefix()); |
| mOmrPrefixManager.Init(mBrUlaPrefix); |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| mNat64PrefixManager.GenerateLocalPrefix(mBrUlaPrefix); |
| #endif |
| mOnLinkPrefixManager.Init(); |
| } |
| else if (aInfraIfIndex != mInfraIf.GetIfIndex()) |
| { |
| LogInfo("Reinitializing - InfraIfIndex:%lu -> %lu", ToUlong(mInfraIf.GetIfIndex()), ToUlong(aInfraIfIndex)); |
| |
| #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE && OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF |
| IgnoreError(Get<Dns::Multicast::Core>().SetEnabled(false, mInfraIf.GetIfIndex())); |
| #endif |
| |
| mInfraIf.SetIfIndex(aInfraIfIndex); |
| } |
| |
| error = mInfraIf.HandleStateChanged(mInfraIf.GetIfIndex(), aInfraIfIsRunning); |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| mInfraIf.Deinit(); |
| } |
| |
| return error; |
| } |
| |
| Error RoutingManager::SetEnabled(bool aEnabled) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| |
| VerifyOrExit(aEnabled != mIsEnabled); |
| |
| mIsEnabled = aEnabled; |
| LogInfo("%s", mIsEnabled ? "Enabling" : "Disabling"); |
| EvaluateState(); |
| |
| exit: |
| return error; |
| } |
| |
| RoutingManager::State RoutingManager::GetState(void) const |
| { |
| State state = kStateUninitialized; |
| |
| VerifyOrExit(IsInitialized()); |
| VerifyOrExit(IsEnabled(), state = kStateDisabled); |
| |
| state = IsRunning() ? kStateRunning : kStateStopped; |
| |
| exit: |
| return state; |
| } |
| |
| Error RoutingManager::GetOmrPrefix(Ip6::Prefix &aPrefix) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| aPrefix = mOmrPrefixManager.GetGeneratedPrefix(); |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| Error RoutingManager::GetPdOmrPrefix(PrefixTableEntry &aPrefixInfo) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| error = mPdPrefixManager.GetPrefixInfo(aPrefixInfo); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::GetPdProcessedRaInfo(PdProcessedRaInfo &aPdProcessedRaInfo) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| error = mPdPrefixManager.GetProcessedRaInfo(aPdProcessedRaInfo); |
| |
| exit: |
| return error; |
| } |
| #endif |
| |
| Error RoutingManager::GetFavoredOmrPrefix(Ip6::Prefix &aPrefix, RoutePreference &aPreference) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsRunning(), error = kErrorInvalidState); |
| aPrefix = mOmrPrefixManager.GetFavoredPrefix().GetPrefix(); |
| aPreference = mOmrPrefixManager.GetFavoredPrefix().GetPreference(); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::GetOnLinkPrefix(Ip6::Prefix &aPrefix) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| aPrefix = mOnLinkPrefixManager.GetLocalPrefix(); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::GetFavoredOnLinkPrefix(Ip6::Prefix &aPrefix) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| aPrefix = mOnLinkPrefixManager.GetFavoredDiscoveredPrefix(); |
| |
| if (aPrefix.GetLength() == 0) |
| { |
| aPrefix = mOnLinkPrefixManager.GetLocalPrefix(); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| void RoutingManager::SetNat64PrefixManagerEnabled(bool aEnabled) |
| { |
| // PrefixManager will start itself if routing manager is running. |
| mNat64PrefixManager.SetEnabled(aEnabled); |
| } |
| |
| Error RoutingManager::GetNat64Prefix(Ip6::Prefix &aPrefix) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| aPrefix = mNat64PrefixManager.GetLocalPrefix(); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::GetFavoredNat64Prefix(Ip6::Prefix &aPrefix, RoutePreference &aRoutePreference) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsInitialized(), error = kErrorInvalidState); |
| aPrefix = mNat64PrefixManager.GetFavoredPrefix(aRoutePreference); |
| |
| exit: |
| return error; |
| } |
| #endif |
| |
| Error RoutingManager::LoadOrGenerateRandomBrUlaPrefix(void) |
| { |
| Error error = kErrorNone; |
| bool generated = false; |
| |
| if (Get<Settings>().Read<Settings::BrUlaPrefix>(mBrUlaPrefix) != kErrorNone || !IsValidBrUlaPrefix(mBrUlaPrefix)) |
| { |
| Ip6::NetworkPrefix randomUlaPrefix; |
| |
| LogNote("No valid /48 BR ULA prefix found in settings, generating new one"); |
| |
| SuccessOrExit(error = randomUlaPrefix.GenerateRandomUla()); |
| |
| mBrUlaPrefix.Set(randomUlaPrefix); |
| mBrUlaPrefix.SetSubnetId(0); |
| mBrUlaPrefix.SetLength(kBrUlaPrefixLength); |
| |
| IgnoreError(Get<Settings>().Save<Settings::BrUlaPrefix>(mBrUlaPrefix)); |
| generated = true; |
| } |
| |
| OT_UNUSED_VARIABLE(generated); |
| |
| LogNote("BR ULA prefix: %s (%s)", mBrUlaPrefix.ToString().AsCString(), generated ? "generated" : "loaded"); |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| LogCrit("Failed to generate random /48 BR ULA prefix"); |
| } |
| return error; |
| } |
| |
| void RoutingManager::EvaluateState(void) |
| { |
| if (mIsEnabled && Get<Mle::MleRouter>().IsAttached() && mInfraIf.IsRunning()) |
| { |
| Start(); |
| } |
| else |
| { |
| Stop(); |
| } |
| } |
| |
| void RoutingManager::Start(void) |
| { |
| if (!mIsRunning) |
| { |
| LogInfo("Starting"); |
| |
| mIsRunning = true; |
| UpdateDiscoveredPrefixTableOnNetDataChange(); |
| mOnLinkPrefixManager.Start(); |
| mOmrPrefixManager.Start(); |
| mRoutePublisher.Start(); |
| mRsSender.Start(); |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| mPdPrefixManager.Start(); |
| #endif |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| mNat64PrefixManager.Start(); |
| #endif |
| } |
| } |
| |
| void RoutingManager::Stop(void) |
| { |
| VerifyOrExit(mIsRunning); |
| |
| mOmrPrefixManager.Stop(); |
| mOnLinkPrefixManager.Stop(); |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| mPdPrefixManager.Stop(); |
| #endif |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| mNat64PrefixManager.Stop(); |
| #endif |
| |
| SendRouterAdvertisement(kInvalidateAllPrevPrefixes); |
| |
| mDiscoveredPrefixTable.RemoveAllEntries(); |
| mDiscoveredPrefixStaleTimer.Stop(); |
| |
| mRaInfo.mTxCount = 0; |
| |
| mRsSender.Stop(); |
| |
| mRoutingPolicyTimer.Stop(); |
| |
| mRoutePublisher.Stop(); |
| |
| LogInfo("Stopped"); |
| |
| mIsRunning = false; |
| |
| #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE |
| if (Get<Srp::Server>().IsAutoEnableMode()) |
| { |
| Get<Srp::Server>().Disable(); |
| } |
| #endif |
| |
| exit: |
| return; |
| } |
| |
| Error RoutingManager::SetExtraRouterAdvertOptions(const uint8_t *aOptions, uint16_t aLength) |
| { |
| Error error = kErrorNone; |
| |
| if (aOptions == nullptr) |
| { |
| mExtraRaOptions.Free(); |
| } |
| else |
| { |
| error = mExtraRaOptions.SetFrom(aOptions, aLength); |
| } |
| |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE |
| void RoutingManager::HandleSrpServerAutoEnableMode(void) |
| { |
| VerifyOrExit(Get<Srp::Server>().IsAutoEnableMode()); |
| |
| if (IsInitalPolicyEvaluationDone()) |
| { |
| Get<Srp::Server>().Enable(); |
| } |
| else |
| { |
| Get<Srp::Server>().Disable(); |
| } |
| |
| exit: |
| return; |
| } |
| #endif |
| |
| void RoutingManager::HandleReceived(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) |
| { |
| const Ip6::Icmp::Header *icmp6Header; |
| |
| VerifyOrExit(mIsRunning); |
| |
| icmp6Header = reinterpret_cast<const Ip6::Icmp::Header *>(aPacket.GetBytes()); |
| |
| switch (icmp6Header->GetType()) |
| { |
| case Ip6::Icmp::Header::kTypeRouterAdvert: |
| HandleRouterAdvertisement(aPacket, aSrcAddress); |
| break; |
| case Ip6::Icmp::Header::kTypeRouterSolicit: |
| HandleRouterSolicit(aPacket, aSrcAddress); |
| break; |
| case Ip6::Icmp::Header::kTypeNeighborAdvert: |
| HandleNeighborAdvertisement(aPacket); |
| break; |
| default: |
| break; |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::HandleNotifierEvents(Events aEvents) |
| { |
| if (aEvents.Contains(kEventThreadRoleChanged)) |
| { |
| mRioAdvertiser.HandleRoleChanged(); |
| } |
| |
| mRoutePublisher.HandleNotifierEvents(aEvents); |
| |
| VerifyOrExit(IsInitialized() && IsEnabled()); |
| |
| if (aEvents.Contains(kEventThreadRoleChanged)) |
| { |
| EvaluateState(); |
| } |
| |
| if (mIsRunning && aEvents.Contains(kEventThreadNetdataChanged)) |
| { |
| UpdateDiscoveredPrefixTableOnNetDataChange(); |
| mOnLinkPrefixManager.HandleNetDataChange(); |
| ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| } |
| |
| if (aEvents.Contains(kEventThreadExtPanIdChanged)) |
| { |
| mOnLinkPrefixManager.HandleExtPanIdChange(); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::UpdateDiscoveredPrefixTableOnNetDataChange(void) |
| { |
| NetworkData::Iterator iterator = NetworkData::kIteratorInit; |
| NetworkData::OnMeshPrefixConfig prefixConfig; |
| |
| // Remove all OMR prefixes in Network Data from the |
| // discovered prefix table. Also check if we have |
| // an OMR prefix with default route flag. |
| |
| while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone) |
| { |
| if (!IsValidOmrPrefix(prefixConfig)) |
| { |
| continue; |
| } |
| |
| mDiscoveredPrefixTable.RemoveRoutePrefix(prefixConfig.GetPrefix()); |
| } |
| } |
| |
| void RoutingManager::EvaluateRoutingPolicy(void) |
| { |
| OT_ASSERT(mIsRunning); |
| |
| LogInfo("Evaluating routing policy"); |
| |
| mOnLinkPrefixManager.Evaluate(); |
| mOmrPrefixManager.Evaluate(); |
| mRoutePublisher.Evaluate(); |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| mNat64PrefixManager.Evaluate(); |
| #endif |
| |
| if (IsInitalPolicyEvaluationDone()) |
| { |
| SendRouterAdvertisement(kAdvPrefixesFromNetData); |
| } |
| |
| #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE |
| if (Get<Srp::Server>().IsAutoEnableMode() && IsInitalPolicyEvaluationDone()) |
| { |
| // If SRP server uses the auto-enable mode, we enable the SRP |
| // server on the first RA transmission after we are done with |
| // initial prefix/route configurations. Note that if SRP server |
| // is already enabled, calling `Enable()` again does nothing. |
| |
| Get<Srp::Server>().Enable(); |
| } |
| #endif |
| |
| ScheduleRoutingPolicyEvaluation(kForNextRa); |
| } |
| |
| bool RoutingManager::IsInitalPolicyEvaluationDone(void) const |
| { |
| // This method indicates whether or not we are done with the |
| // initial policy evaluation and prefix and route setup, i.e., |
| // the OMR and on-link prefixes are determined, advertised in |
| // the emitted Router Advert message on infrastructure side |
| // and published in the Thread Network Data. |
| |
| return mIsRunning && !mOmrPrefixManager.GetFavoredPrefix().IsEmpty() && |
| mOnLinkPrefixManager.IsInitalEvaluationDone(); |
| } |
| |
| void RoutingManager::ScheduleRoutingPolicyEvaluation(ScheduleMode aMode) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| uint32_t delay = 0; |
| TimeMilli evaluateTime; |
| |
| VerifyOrExit(mIsRunning); |
| |
| switch (aMode) |
| { |
| case kImmediately: |
| break; |
| |
| case kForNextRa: |
| delay = Random::NonCrypto::GetUint32InRange(Time::SecToMsec(kMinRtrAdvInterval), |
| Time::SecToMsec(kMaxRtrAdvInterval)); |
| |
| if (mRaInfo.mTxCount <= kMaxInitRtrAdvertisements && delay > Time::SecToMsec(kMaxInitRtrAdvInterval)) |
| { |
| delay = Time::SecToMsec(kMaxInitRtrAdvInterval); |
| } |
| break; |
| |
| case kAfterRandomDelay: |
| delay = Random::NonCrypto::GetUint32InRange(kPolicyEvaluationMinDelay, kPolicyEvaluationMaxDelay); |
| break; |
| |
| case kToReplyToRs: |
| delay = Random::NonCrypto::GetUint32InRange(0, kRaReplyJitter); |
| break; |
| } |
| |
| // Ensure we wait a min delay after last RA tx |
| evaluateTime = Max(now + delay, mRaInfo.mLastTxTime + kMinDelayBetweenRtrAdvs); |
| |
| mRoutingPolicyTimer.FireAtIfEarlier(evaluateTime); |
| |
| #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) |
| { |
| uint32_t duration = evaluateTime - now; |
| |
| if (duration == 0) |
| { |
| LogInfo("Will evaluate routing policy immediately"); |
| } |
| else |
| { |
| String<Uptime::kStringSize> string; |
| |
| Uptime::UptimeToString(duration, string, /* aIncludeMsec */ true); |
| LogInfo("Will evaluate routing policy in %s (%lu msec)", string.AsCString() + 3, ToUlong(duration)); |
| } |
| } |
| #endif |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::SendRouterAdvertisement(RouterAdvTxMode aRaTxMode) |
| { |
| Error error = kErrorNone; |
| RouterAdvert::TxMessage raMsg; |
| RouterAdvert::Header header; |
| Ip6::Address destAddress; |
| InfraIf::Icmp6Packet packet; |
| |
| LogInfo("Preparing RA"); |
| |
| header = mRaInfo.mHeader; |
| mDiscoveredPrefixTable.DetermineAndSetFlags(header); |
| |
| SuccessOrExit(error = raMsg.AppendHeader(header)); |
| |
| LogInfo("- RA Header - flags - M:%u O:%u", header.IsManagedAddressConfigFlagSet(), header.IsOtherConfigFlagSet()); |
| LogInfo("- RA Header - default route - lifetime:%u", header.GetRouterLifetime()); |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_STUB_ROUTER_FLAG_IN_EMITTED_RA_ENABLE |
| SuccessOrExit(error = raMsg.AppendFlagsExtensionOption(/* aStubRouterFlag */ true)); |
| LogInfo("- FlagsExt - StubRouter:1"); |
| #endif |
| |
| // Append PIO for local on-link prefix if is either being |
| // advertised or deprecated and for old prefix if is being |
| // deprecated. |
| |
| SuccessOrExit(error = mOnLinkPrefixManager.AppendAsPiosTo(raMsg)); |
| |
| if (aRaTxMode == kInvalidateAllPrevPrefixes) |
| { |
| SuccessOrExit(error = mRioAdvertiser.InvalidatPrevRios(raMsg)); |
| } |
| else |
| { |
| SuccessOrExit(error = mRioAdvertiser.AppendRios(raMsg)); |
| } |
| |
| if (mExtraRaOptions.GetLength() > 0) |
| { |
| SuccessOrExit(error = raMsg.AppendBytes(mExtraRaOptions.GetBytes(), mExtraRaOptions.GetLength())); |
| } |
| |
| VerifyOrExit(raMsg.ContainsAnyOptions()); |
| |
| destAddress.SetToLinkLocalAllNodesMulticast(); |
| raMsg.GetAsPacket(packet); |
| |
| mRaInfo.IncrementTxCountAndSaveHash(packet); |
| |
| SuccessOrExit(error = mInfraIf.Send(packet, destAddress)); |
| |
| mRaInfo.mLastTxTime = TimerMilli::GetNow(); |
| Get<Ip6::Ip6>().GetBorderRoutingCounters().mRaTxSuccess++; |
| LogInfo("Sent RA on %s", mInfraIf.ToString().AsCString()); |
| DumpDebg("[BR-CERT] direction=send | type=RA |", packet.GetBytes(), packet.GetLength()); |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| Get<Ip6::Ip6>().GetBorderRoutingCounters().mRaTxFailure++; |
| LogWarn("Failed to send RA on %s: %s", mInfraIf.ToString().AsCString(), ErrorToString(error)); |
| } |
| } |
| |
| bool RoutingManager::IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix) |
| { |
| return aBrUlaPrefix.mLength == kBrUlaPrefixLength && aBrUlaPrefix.mPrefix.mFields.m8[0] == 0xfd; |
| } |
| |
| bool RoutingManager::IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig) |
| { |
| return IsValidOmrPrefix(aOnMeshPrefixConfig.GetPrefix()) && aOnMeshPrefixConfig.mOnMesh && |
| aOnMeshPrefixConfig.mSlaac && aOnMeshPrefixConfig.mStable; |
| } |
| |
| bool RoutingManager::IsValidOmrPrefix(const Ip6::Prefix &aPrefix) |
| { |
| // Accept ULA/GUA prefixes with 64-bit length. |
| return (aPrefix.GetLength() == kOmrPrefixLength) && !aPrefix.IsLinkLocal() && !aPrefix.IsMulticast(); |
| } |
| |
| bool RoutingManager::IsValidOnLinkPrefix(const PrefixInfoOption &aPio) |
| { |
| Ip6::Prefix prefix; |
| |
| aPio.GetPrefix(prefix); |
| |
| return IsValidOnLinkPrefix(prefix) && aPio.IsOnLinkFlagSet() && aPio.IsAutoAddrConfigFlagSet(); |
| } |
| |
| bool RoutingManager::IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix) |
| { |
| return (aOnLinkPrefix.GetLength() == kOnLinkPrefixLength) && !aOnLinkPrefix.IsLinkLocal() && |
| !aOnLinkPrefix.IsMulticast(); |
| } |
| |
| void RoutingManager::HandleRsSenderFinished(TimeMilli aStartTime) |
| { |
| // This is a callback from `RsSender` and is invoked when it |
| // finishes a cycle of sending Router Solicitations. `aStartTime` |
| // specifies the start time of the RS transmission cycle. |
| // |
| // We remove or deprecate old entries in discovered table that are |
| // not refreshed during Router Solicitation. We also invalidate |
| // the learned RA header if it is not refreshed during Router |
| // Solicitation. |
| |
| mDiscoveredPrefixTable.RemoveOrDeprecateOldEntries(aStartTime); |
| |
| if (mRaInfo.mHeaderUpdateTime <= aStartTime) |
| { |
| UpdateRouterAdvertHeader(/* aRouterAdvertMessage */ nullptr); |
| } |
| |
| ScheduleRoutingPolicyEvaluation(kImmediately); |
| } |
| |
| void RoutingManager::HandleDiscoveredPrefixStaleTimer(void) |
| { |
| LogInfo("Stale On-Link or OMR Prefixes or RA messages are detected"); |
| mRsSender.Start(); |
| } |
| |
| void RoutingManager::HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) |
| { |
| OT_UNUSED_VARIABLE(aPacket); |
| OT_UNUSED_VARIABLE(aSrcAddress); |
| |
| Get<Ip6::Ip6>().GetBorderRoutingCounters().mRsRx++; |
| LogInfo("Received RS from %s on %s", aSrcAddress.ToString().AsCString(), mInfraIf.ToString().AsCString()); |
| |
| ScheduleRoutingPolicyEvaluation(kToReplyToRs); |
| } |
| |
| void RoutingManager::HandleNeighborAdvertisement(const InfraIf::Icmp6Packet &aPacket) |
| { |
| const NeighborAdvertMessage *naMsg; |
| |
| VerifyOrExit(aPacket.GetLength() >= sizeof(naMsg)); |
| naMsg = reinterpret_cast<const NeighborAdvertMessage *>(aPacket.GetBytes()); |
| |
| mDiscoveredPrefixTable.ProcessNeighborAdvertMessage(*naMsg); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) |
| { |
| RouterAdvert::RxMessage routerAdvMessage(aPacket); |
| |
| OT_ASSERT(mIsRunning); |
| |
| VerifyOrExit(routerAdvMessage.IsValid()); |
| |
| Get<Ip6::Ip6>().GetBorderRoutingCounters().mRaRx++; |
| |
| LogInfo("Received RA from %s on %s", aSrcAddress.ToString().AsCString(), mInfraIf.ToString().AsCString()); |
| DumpDebg("[BR-CERT] direction=recv | type=RA |", aPacket.GetBytes(), aPacket.GetLength()); |
| |
| mDiscoveredPrefixTable.ProcessRouterAdvertMessage(routerAdvMessage, aSrcAddress); |
| |
| // Remember the header and parameters of RA messages which are |
| // initiated from the infra interface. |
| if (mInfraIf.HasAddress(aSrcAddress)) |
| { |
| UpdateRouterAdvertHeader(&routerAdvMessage); |
| } |
| |
| exit: |
| return; |
| } |
| |
| bool RoutingManager::ShouldProcessPrefixInfoOption(const PrefixInfoOption &aPio, const Ip6::Prefix &aPrefix) |
| { |
| // Indicate whether to process or skip a given prefix |
| // from a PIO (from received RA message). |
| |
| bool shouldProcess = false; |
| |
| VerifyOrExit(mIsRunning); |
| |
| if (!IsValidOnLinkPrefix(aPio)) |
| { |
| LogInfo("- PIO %s - ignore since not a valid on-link prefix", aPrefix.ToString().AsCString()); |
| ExitNow(); |
| } |
| |
| if (mOnLinkPrefixManager.IsPublishingOrAdvertising()) |
| { |
| VerifyOrExit(aPrefix != mOnLinkPrefixManager.GetLocalPrefix()); |
| } |
| |
| shouldProcess = true; |
| |
| exit: |
| return shouldProcess; |
| } |
| |
| bool RoutingManager::ShouldProcessRouteInfoOption(const RouteInfoOption &aRio, const Ip6::Prefix &aPrefix) |
| { |
| // Indicate whether to process or skip a given prefix |
| // from a RIO (from received RA message). |
| |
| OT_UNUSED_VARIABLE(aRio); |
| |
| bool shouldProcess = false; |
| |
| VerifyOrExit(mIsRunning); |
| |
| if (aPrefix.GetLength() == 0) |
| { |
| // Always process default route ::/0 prefix. |
| ExitNow(shouldProcess = true); |
| } |
| |
| if (!IsValidOmrPrefix(aPrefix)) |
| { |
| LogInfo("- RIO %s - ignore since not a valid OMR prefix", aPrefix.ToString().AsCString()); |
| ExitNow(); |
| } |
| |
| VerifyOrExit(mOmrPrefixManager.GetLocalPrefix().GetPrefix() != aPrefix); |
| |
| // Disregard our own advertised OMR prefixes and those currently |
| // present in the Thread Network Data. |
| // |
| // There should be eventual parity between the `RioAdvertiser` |
| // prefixes and the OMR prefixes in Network Data, but temporary |
| // discrepancies can occur due to the tx timing of RAs and time |
| // required to update Network Data (registering with leader). So |
| // both checks are necessary. |
| |
| VerifyOrExit(!mRioAdvertiser.HasAdvertised(aPrefix)); |
| VerifyOrExit(!Get<RoutingManager>().NetworkDataContainsOmrPrefix(aPrefix)); |
| |
| shouldProcess = true; |
| |
| exit: |
| return shouldProcess; |
| } |
| |
| void RoutingManager::HandleDiscoveredPrefixTableChanged(void) |
| { |
| // This is a callback from `mDiscoveredPrefixTable` indicating that |
| // there has been a change in the table. |
| |
| VerifyOrExit(mIsRunning); |
| |
| ResetDiscoveredPrefixStaleTimer(); |
| mOnLinkPrefixManager.HandleDiscoveredPrefixTableChanged(); |
| mRoutePublisher.Evaluate(); |
| |
| exit: |
| return; |
| } |
| |
| bool RoutingManager::NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const |
| { |
| NetworkData::Iterator iterator = NetworkData::kIteratorInit; |
| NetworkData::OnMeshPrefixConfig onMeshPrefixConfig; |
| bool contains = false; |
| |
| while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, onMeshPrefixConfig) == kErrorNone) |
| { |
| if (IsValidOmrPrefix(onMeshPrefixConfig) && onMeshPrefixConfig.GetPrefix() == aPrefix) |
| { |
| contains = true; |
| break; |
| } |
| } |
| |
| return contains; |
| } |
| |
| bool RoutingManager::NetworkDataContainsUlaRoute(void) const |
| { |
| // Determine whether leader Network Data contains a route |
| // prefix which is either the ULA prefix `fc00::/7` or |
| // a sub-prefix of it (e.g., default route). |
| |
| NetworkData::Iterator iterator = NetworkData::kIteratorInit; |
| NetworkData::ExternalRouteConfig routeConfig; |
| bool contains = false; |
| |
| while (Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNone) |
| { |
| if (routeConfig.mStable && RoutePublisher::GetUlaPrefix().ContainsPrefix(routeConfig.GetPrefix())) |
| { |
| contains = true; |
| break; |
| } |
| } |
| |
| return contains; |
| } |
| |
| void RoutingManager::UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRouterAdvertMessage) |
| { |
| // Updates the `mRaInfo` from the given RA message. |
| |
| RouterAdvert::Header oldHeader; |
| |
| if (aRouterAdvertMessage != nullptr) |
| { |
| // We skip and do not update RA header if the received RA message |
| // was not prepared and sent by `RoutingManager` itself. |
| |
| VerifyOrExit(!mRaInfo.IsRaFromManager(*aRouterAdvertMessage)); |
| } |
| |
| oldHeader = mRaInfo.mHeader; |
| mRaInfo.mHeaderUpdateTime = TimerMilli::GetNow(); |
| |
| if (aRouterAdvertMessage == nullptr || aRouterAdvertMessage->GetHeader().GetRouterLifetime() == 0) |
| { |
| mRaInfo.mHeader.SetToDefault(); |
| mRaInfo.mIsHeaderFromHost = false; |
| } |
| else |
| { |
| // The checksum is set to zero in `mRaInfo.mHeader` |
| // which indicates to platform that it needs to do the |
| // calculation and update it. |
| |
| mRaInfo.mHeader = aRouterAdvertMessage->GetHeader(); |
| mRaInfo.mHeader.SetChecksum(0); |
| mRaInfo.mIsHeaderFromHost = true; |
| } |
| |
| ResetDiscoveredPrefixStaleTimer(); |
| |
| if (mRaInfo.mHeader != oldHeader) |
| { |
| // If there was a change to the header, start timer to |
| // reevaluate routing policy and send RA message with new |
| // header. |
| |
| ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::ResetDiscoveredPrefixStaleTimer(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| TimeMilli nextStaleTime; |
| |
| OT_ASSERT(mIsRunning); |
| |
| // The stale timer triggers sending RS to check the state of |
| // discovered prefixes and host RA messages. |
| |
| nextStaleTime = mDiscoveredPrefixTable.CalculateNextStaleTime(now); |
| |
| // Check for stale Router Advertisement Message if learnt from Host. |
| if (mRaInfo.mIsHeaderFromHost) |
| { |
| TimeMilli raStaleTime = Max(now, mRaInfo.mHeaderUpdateTime + Time::SecToMsec(kRtrAdvStaleTime)); |
| |
| nextStaleTime = Min(nextStaleTime, raStaleTime); |
| } |
| |
| if (nextStaleTime == now.GetDistantFuture()) |
| { |
| if (mDiscoveredPrefixStaleTimer.IsRunning()) |
| { |
| LogDebg("Prefix stale timer stopped"); |
| } |
| |
| mDiscoveredPrefixStaleTimer.Stop(); |
| } |
| else |
| { |
| mDiscoveredPrefixStaleTimer.FireAt(nextStaleTime); |
| LogDebg("Prefix stale timer scheduled in %lu ms", ToUlong(nextStaleTime - now)); |
| } |
| } |
| |
| #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) |
| void RoutingManager::LogPrefixInfoOption(const Ip6::Prefix &aPrefix, |
| uint32_t aValidLifetime, |
| uint32_t aPreferredLifetime) |
| { |
| LogInfo("- PIO %s (valid:%lu, preferred:%lu)", aPrefix.ToString().AsCString(), ToUlong(aValidLifetime), |
| ToUlong(aPreferredLifetime)); |
| } |
| |
| void RoutingManager::LogRouteInfoOption(const Ip6::Prefix &aPrefix, uint32_t aLifetime, RoutePreference aPreference) |
| { |
| LogInfo("- RIO %s (lifetime:%lu, prf:%s)", aPrefix.ToString().AsCString(), ToUlong(aLifetime), |
| RoutePreferenceToString(aPreference)); |
| } |
| #else |
| void RoutingManager::LogPrefixInfoOption(const Ip6::Prefix &, uint32_t, uint32_t) {} |
| void RoutingManager::LogRouteInfoOption(const Ip6::Prefix &, uint32_t, RoutePreference) {} |
| #endif |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // DiscoveredPrefixTable |
| |
| RoutingManager::DiscoveredPrefixTable::DiscoveredPrefixTable(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mEntryTimer(aInstance) |
| , mRouterTimer(aInstance) |
| , mSignalTask(aInstance) |
| { |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, |
| const Ip6::Address &aSrcAddress) |
| { |
| // Process a received RA message and update the prefix table. |
| |
| Router *router = mRouters.FindMatching(aSrcAddress); |
| |
| if (router == nullptr) |
| { |
| router = AllocateRouter(); |
| |
| if (router == nullptr) |
| { |
| LogWarn("Received RA from too many routers, ignore RA from %s", aSrcAddress.ToString().AsCString()); |
| ExitNow(); |
| } |
| |
| router->Clear(); |
| router->mAddress = aSrcAddress; |
| |
| mRouters.Push(*router); |
| } |
| |
| // RA message can indicate router provides default route in the RA |
| // message header and can also include an RIO for `::/0`. When |
| // processing an RA message, the preference and lifetime values |
| // in a `::/0` RIO override the preference and lifetime values in |
| // the RA header (per RFC 4191 section 3.1). |
| |
| ProcessRaHeader(aRaMessage.GetHeader(), *router); |
| |
| for (const Option &option : aRaMessage) |
| { |
| switch (option.GetType()) |
| { |
| case Option::kTypePrefixInfo: |
| ProcessPrefixInfoOption(static_cast<const PrefixInfoOption &>(option), *router); |
| break; |
| |
| case Option::kTypeRouteInfo: |
| ProcessRouteInfoOption(static_cast<const RouteInfoOption &>(option), *router); |
| break; |
| |
| case Option::kTypeRaFlagsExtension: |
| ProcessRaFlagsExtOption(static_cast<const RaFlagsExtOption &>(option), *router); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| UpdateRouterOnRx(*router); |
| |
| RemoveRoutersWithNoEntriesOrFlags(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::ProcessRaHeader(const RouterAdvert::Header &aRaHeader, Router &aRouter) |
| { |
| Entry *entry; |
| Ip6::Prefix prefix; |
| |
| aRouter.mManagedAddressConfigFlag = aRaHeader.IsManagedAddressConfigFlagSet(); |
| aRouter.mOtherConfigFlag = aRaHeader.IsOtherConfigFlagSet(); |
| LogInfo("- RA Header - flags - M:%u O:%u", aRouter.mManagedAddressConfigFlag, aRouter.mOtherConfigFlag); |
| |
| prefix.Clear(); |
| entry = aRouter.mEntries.FindMatching(Entry::Matcher(prefix, Entry::kTypeRoute)); |
| |
| LogInfo("- RA Header - default route - lifetime:%u", aRaHeader.GetRouterLifetime()); |
| |
| if (entry == nullptr) |
| { |
| VerifyOrExit(aRaHeader.GetRouterLifetime() != 0); |
| |
| entry = AllocateEntry(); |
| |
| if (entry == nullptr) |
| { |
| LogWarn("Discovered too many prefixes, ignore default route from RA header"); |
| ExitNow(); |
| } |
| |
| entry->SetFrom(aRaHeader); |
| aRouter.mEntries.Push(*entry); |
| } |
| else |
| { |
| entry->SetFrom(aRaHeader); |
| } |
| |
| mEntryTimer.FireAtIfEarlier(entry->GetExpireTime()); |
| |
| SignalTableChanged(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::ProcessPrefixInfoOption(const PrefixInfoOption &aPio, Router &aRouter) |
| { |
| Ip6::Prefix prefix; |
| Entry *entry; |
| |
| VerifyOrExit(aPio.IsValid()); |
| aPio.GetPrefix(prefix); |
| |
| VerifyOrExit(Get<RoutingManager>().ShouldProcessPrefixInfoOption(aPio, prefix)); |
| |
| LogPrefixInfoOption(prefix, aPio.GetValidLifetime(), aPio.GetPreferredLifetime()); |
| |
| entry = aRouter.mEntries.FindMatching(Entry::Matcher(prefix, Entry::kTypeOnLink)); |
| |
| if (entry == nullptr) |
| { |
| VerifyOrExit(aPio.GetValidLifetime() != 0); |
| |
| entry = AllocateEntry(); |
| |
| if (entry == nullptr) |
| { |
| LogWarn("Discovered too many prefixes, ignore on-link prefix %s", prefix.ToString().AsCString()); |
| ExitNow(); |
| } |
| |
| entry->SetFrom(aPio); |
| aRouter.mEntries.Push(*entry); |
| } |
| else |
| { |
| Entry newEntry; |
| |
| newEntry.SetFrom(aPio); |
| entry->AdoptValidAndPreferredLifetimesFrom(newEntry); |
| } |
| |
| mEntryTimer.FireAtIfEarlier(entry->GetExpireTime()); |
| |
| SignalTableChanged(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::ProcessRouteInfoOption(const RouteInfoOption &aRio, Router &aRouter) |
| { |
| Ip6::Prefix prefix; |
| Entry *entry; |
| |
| VerifyOrExit(aRio.IsValid()); |
| aRio.GetPrefix(prefix); |
| |
| VerifyOrExit(Get<RoutingManager>().ShouldProcessRouteInfoOption(aRio, prefix)); |
| |
| LogRouteInfoOption(prefix, aRio.GetRouteLifetime(), aRio.GetPreference()); |
| |
| entry = aRouter.mEntries.FindMatching(Entry::Matcher(prefix, Entry::kTypeRoute)); |
| |
| if (entry == nullptr) |
| { |
| VerifyOrExit(aRio.GetRouteLifetime() != 0); |
| |
| entry = AllocateEntry(); |
| |
| if (entry == nullptr) |
| { |
| LogWarn("Discovered too many prefixes, ignore route prefix %s", prefix.ToString().AsCString()); |
| ExitNow(); |
| } |
| |
| entry->SetFrom(aRio); |
| aRouter.mEntries.Push(*entry); |
| } |
| else |
| { |
| entry->SetFrom(aRio); |
| } |
| |
| mEntryTimer.FireAtIfEarlier(entry->GetExpireTime()); |
| |
| SignalTableChanged(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::ProcessRaFlagsExtOption(const RaFlagsExtOption &aRaFlagsOption, |
| Router &aRouter) |
| { |
| VerifyOrExit(aRaFlagsOption.IsValid()); |
| aRouter.mStubRouterFlag = aRaFlagsOption.IsStubRouterFlagSet(); |
| |
| LogInfo("- FlagsExt - StubRouter:%u", aRouter.mStubRouterFlag); |
| |
| exit: |
| return; |
| } |
| |
| #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE |
| |
| RoutingManager::DiscoveredPrefixTable::Router *RoutingManager::DiscoveredPrefixTable::AllocateRouter(void) |
| { |
| Router *router = mRouterPool.Allocate(); |
| |
| VerifyOrExit(router != nullptr); |
| router->Init(GetInstance()); |
| |
| exit: |
| return router; |
| } |
| |
| RoutingManager::DiscoveredPrefixTable::Entry *RoutingManager::DiscoveredPrefixTable::AllocateEntry(void) |
| { |
| Entry *entry = mEntryPool.Allocate(); |
| |
| VerifyOrExit(entry != nullptr); |
| entry->Init(GetInstance()); |
| |
| exit: |
| return entry; |
| } |
| |
| #endif // !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE |
| |
| bool RoutingManager::DiscoveredPrefixTable::Contains(const Entry::Checker &aChecker) const |
| { |
| bool contains = false; |
| |
| for (const Router &router : mRouters) |
| { |
| if (router.mEntries.ContainsMatching(aChecker)) |
| { |
| contains = true; |
| break; |
| } |
| } |
| |
| return contains; |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::ContainsDefaultOrNonUlaRoutePrefix(void) const |
| { |
| return Contains(Entry::Checker(Entry::Checker::kIsNotUla, Entry::kTypeRoute)); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::ContainsNonUlaOnLinkPrefix(void) const |
| { |
| return Contains(Entry::Checker(Entry::Checker::kIsNotUla, Entry::kTypeOnLink)); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::ContainsUlaOnLinkPrefix(void) const |
| { |
| return Contains(Entry::Checker(Entry::Checker::kIsUla, Entry::kTypeOnLink)); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::FindFavoredOnLinkPrefix(Ip6::Prefix &aPrefix) const |
| { |
| // Find the smallest preferred on-link prefix entry in the table |
| // and return it in `aPrefix`. If there is none, `aPrefix` is |
| // cleared (prefix length is set to zero). |
| |
| aPrefix.Clear(); |
| |
| for (const Router &router : mRouters) |
| { |
| for (const Entry &entry : router.mEntries) |
| { |
| if (!entry.IsOnLinkPrefix() || entry.IsDeprecated() || |
| (entry.GetPreferredLifetime() < kFavoredOnLinkPrefixMinPreferredLifetime)) |
| { |
| continue; |
| } |
| |
| if ((aPrefix.GetLength() == 0) || (entry.GetPrefix() < aPrefix)) |
| { |
| aPrefix = entry.GetPrefix(); |
| } |
| } |
| } |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveOnLinkPrefix(const Ip6::Prefix &aPrefix) |
| { |
| RemovePrefix(Entry::Matcher(aPrefix, Entry::kTypeOnLink)); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveRoutePrefix(const Ip6::Prefix &aPrefix) |
| { |
| RemovePrefix(Entry::Matcher(aPrefix, Entry::kTypeRoute)); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemovePrefix(const Entry::Matcher &aMatcher) |
| { |
| // Removes all entries matching a given prefix from the table. |
| |
| bool didRemove = false; |
| |
| for (Router &router : mRouters) |
| { |
| didRemove |= router.mEntries.RemoveAndFreeAllMatching(aMatcher); |
| } |
| |
| VerifyOrExit(didRemove); |
| |
| RemoveRoutersWithNoEntriesOrFlags(); |
| |
| SignalTableChanged(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveAllEntries(void) |
| { |
| // Remove all entries from the table. |
| |
| mRouters.Free(); |
| mEntryTimer.Stop(); |
| |
| SignalTableChanged(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold) |
| { |
| // Remove route prefix entries and deprecate on-link entries in |
| // the table that are old (not updated since `aTimeThreshold`). |
| |
| for (Router &router : mRouters) |
| { |
| for (Entry &entry : router.mEntries) |
| { |
| if (entry.GetLastUpdateTime() <= aTimeThreshold) |
| { |
| if (entry.IsOnLinkPrefix()) |
| { |
| entry.ClearPreferredLifetime(); |
| } |
| else |
| { |
| entry.ClearValidLifetime(); |
| } |
| |
| SignalTableChanged(); |
| } |
| } |
| } |
| |
| RemoveExpiredEntries(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveOrDeprecateEntriesFromInactiveRouters(void) |
| { |
| // Remove route prefix entries and deprecate on-link prefix entries |
| // in the table for routers that have reached the max NS probe |
| // attempts and considered as inactive. |
| |
| for (Router &router : mRouters) |
| { |
| if (router.mNsProbeCount <= Router::kMaxNsProbes) |
| { |
| continue; |
| } |
| |
| for (Entry &entry : router.mEntries) |
| { |
| if (entry.IsOnLinkPrefix()) |
| { |
| if (!entry.IsDeprecated()) |
| { |
| entry.ClearPreferredLifetime(); |
| SignalTableChanged(); |
| } |
| } |
| else |
| { |
| entry.ClearValidLifetime(); |
| } |
| } |
| } |
| |
| RemoveExpiredEntries(); |
| } |
| |
| TimeMilli RoutingManager::DiscoveredPrefixTable::CalculateNextStaleTime(TimeMilli aNow) const |
| { |
| TimeMilli onLinkStaleTime = aNow; |
| TimeMilli routeStaleTime = aNow.GetDistantFuture(); |
| bool foundOnLink = false; |
| |
| // For on-link prefixes, we consider stale time as when all on-link |
| // prefixes become stale (the latest stale time) but for route |
| // prefixes we consider the earliest stale time. |
| |
| for (const Router &router : mRouters) |
| { |
| for (const Entry &entry : router.mEntries) |
| { |
| TimeMilli entryStaleTime = Max(aNow, entry.GetStaleTime()); |
| |
| if (entry.IsOnLinkPrefix() && !entry.IsDeprecated()) |
| { |
| onLinkStaleTime = Max(onLinkStaleTime, entryStaleTime); |
| foundOnLink = true; |
| } |
| |
| if (!entry.IsOnLinkPrefix()) |
| { |
| routeStaleTime = Min(routeStaleTime, entryStaleTime); |
| } |
| } |
| } |
| |
| return foundOnLink ? Min(onLinkStaleTime, routeStaleTime) : routeStaleTime; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveRoutersWithNoEntriesOrFlags(void) |
| { |
| mRouters.RemoveAndFreeAllMatching(Router::kContainsNoEntriesOrFlags); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::HandleEntryTimer(void) { RemoveExpiredEntries(); } |
| |
| void RoutingManager::DiscoveredPrefixTable::RemoveExpiredEntries(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| TimeMilli nextExpireTime = now.GetDistantFuture(); |
| bool didRemove = false; |
| |
| for (Router &router : mRouters) |
| { |
| didRemove |= router.mEntries.RemoveAndFreeAllMatching(Entry::ExpirationChecker(now)); |
| } |
| |
| RemoveRoutersWithNoEntriesOrFlags(); |
| |
| if (didRemove) |
| { |
| SignalTableChanged(); |
| } |
| |
| // Determine the next expire time and schedule timer. |
| |
| for (const Router &router : mRouters) |
| { |
| for (const Entry &entry : router.mEntries) |
| { |
| nextExpireTime = Min(nextExpireTime, entry.GetExpireTime()); |
| } |
| } |
| |
| if (nextExpireTime != now.GetDistantFuture()) |
| { |
| mEntryTimer.FireAt(nextExpireTime); |
| } |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::SignalTableChanged(void) { mSignalTask.Post(); } |
| |
| void RoutingManager::DiscoveredPrefixTable::ProcessNeighborAdvertMessage(const NeighborAdvertMessage &aNaMessage) |
| { |
| Router *router; |
| |
| VerifyOrExit(aNaMessage.IsValid()); |
| |
| router = mRouters.FindMatching(aNaMessage.GetTargetAddress()); |
| VerifyOrExit(router != nullptr); |
| |
| LogInfo("Received NA from router %s", router->mAddress.ToString().AsCString()); |
| |
| UpdateRouterOnRx(*router); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::UpdateRouterOnRx(Router &aRouter) |
| { |
| aRouter.mNsProbeCount = 0; |
| aRouter.mTimeout = TimerMilli::GetNow() + Random::NonCrypto::AddJitter(Router::kActiveTimeout, Router::kJitter); |
| |
| mRouterTimer.FireAtIfEarlier(aRouter.mTimeout); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::HandleRouterTimer(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| TimeMilli nextTime = now.GetDistantFuture(); |
| |
| for (Router &router : mRouters) |
| { |
| if (router.mNsProbeCount > Router::kMaxNsProbes) |
| { |
| continue; |
| } |
| |
| // If the `router` emitting RA has an address belonging to |
| // infra interface, it indicates that the RAs are from |
| // same device. In this case we skip performing NS probes. |
| // This addresses situation where platform may not be |
| // be able to receive and pass the NA message response |
| // from device itself. |
| |
| if (Get<RoutingManager>().mInfraIf.HasAddress(router.mAddress)) |
| { |
| continue; |
| } |
| |
| if (router.mTimeout <= now) |
| { |
| router.mNsProbeCount++; |
| |
| if (router.mNsProbeCount > Router::kMaxNsProbes) |
| { |
| LogInfo("No response to all Neighbor Solicitations attempts from router %s", |
| router.mAddress.ToString().AsCString()); |
| continue; |
| } |
| |
| router.mTimeout = now + ((router.mNsProbeCount < Router::kMaxNsProbes) ? Router::kNsProbeRetryInterval |
| : Router::kNsProbeTimeout); |
| |
| SendNeighborSolicitToRouter(router); |
| } |
| |
| nextTime = Min(nextTime, router.mTimeout); |
| } |
| |
| RemoveOrDeprecateEntriesFromInactiveRouters(); |
| |
| if (nextTime != now.GetDistantFuture()) |
| { |
| mRouterTimer.FireAtIfEarlier(nextTime); |
| } |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::SendNeighborSolicitToRouter(const Router &aRouter) |
| { |
| InfraIf::Icmp6Packet packet; |
| NeighborSolicitMessage neighborSolicitMsg; |
| |
| VerifyOrExit(!Get<RoutingManager>().mRsSender.IsInProgress()); |
| |
| neighborSolicitMsg.SetTargetAddress(aRouter.mAddress); |
| packet.InitFrom(neighborSolicitMsg); |
| |
| IgnoreError(Get<RoutingManager>().mInfraIf.Send(packet, aRouter.mAddress)); |
| |
| LogInfo("Sent Neighbor Solicitation to %s - attempt:%u/%u", aRouter.mAddress.ToString().AsCString(), |
| aRouter.mNsProbeCount, Router::kMaxNsProbes); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::DetermineAndSetFlags(RouterAdvert::Header &aHeader) const |
| { |
| // Determine the `M` and `O` flags to include in the RA message |
| // header to be emitted. |
| // |
| // If any discovered router on infrastructure which is not itself a |
| // stub router (e.g., another Thread BR) includes the `M` or `O` |
| // flag, we also include the same flag. |
| // |
| // If a router has failed to respond to max number of NS probe |
| // attempts, we consider it as offline and ignore its flags. |
| |
| for (const Router &router : mRouters) |
| { |
| if (router.mStubRouterFlag) |
| { |
| continue; |
| } |
| |
| if (router.mNsProbeCount > Router::kMaxNsProbes) |
| { |
| continue; |
| } |
| |
| if (router.mManagedAddressConfigFlag) |
| { |
| aHeader.SetManagedAddressConfigFlag(); |
| } |
| |
| if (router.mOtherConfigFlag) |
| { |
| aHeader.SetOtherConfigFlag(); |
| } |
| } |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::InitIterator(PrefixTableIterator &aIterator) const |
| { |
| static_cast<Iterator &>(aIterator).Init(mRouters); |
| } |
| |
| Error RoutingManager::DiscoveredPrefixTable::GetNextEntry(PrefixTableIterator &aIterator, |
| PrefixTableEntry &aEntry) const |
| { |
| Error error = kErrorNone; |
| Iterator &iterator = static_cast<Iterator &>(aIterator); |
| |
| VerifyOrExit(iterator.GetRouter() != nullptr, error = kErrorNotFound); |
| OT_ASSERT(iterator.GetEntry() != nullptr); |
| |
| iterator.GetRouter()->CopyInfoTo(aEntry.mRouter); |
| aEntry.mPrefix = iterator.GetEntry()->GetPrefix(); |
| aEntry.mIsOnLink = iterator.GetEntry()->IsOnLinkPrefix(); |
| aEntry.mMsecSinceLastUpdate = iterator.GetInitTime() - iterator.GetEntry()->GetLastUpdateTime(); |
| aEntry.mValidLifetime = iterator.GetEntry()->GetValidLifetime(); |
| aEntry.mPreferredLifetime = aEntry.mIsOnLink ? iterator.GetEntry()->GetPreferredLifetime() : 0; |
| aEntry.mRoutePreference = |
| static_cast<otRoutePreference>(aEntry.mIsOnLink ? 0 : iterator.GetEntry()->GetRoutePreference()); |
| |
| iterator.Advance(Iterator::kToNextEntry); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::DiscoveredPrefixTable::GetNextRouter(PrefixTableIterator &aIterator, RouterEntry &aEntry) const |
| { |
| Error error = kErrorNone; |
| Iterator &iterator = static_cast<Iterator &>(aIterator); |
| |
| VerifyOrExit(iterator.GetRouter() != nullptr, error = kErrorNotFound); |
| |
| iterator.GetRouter()->CopyInfoTo(aEntry); |
| iterator.Advance(Iterator::kToNextRouter); |
| |
| exit: |
| return error; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // DiscoveredPrefixTable::Iterator |
| |
| void RoutingManager::DiscoveredPrefixTable::Iterator::Init(const LinkedList<Router> &aRouters) |
| { |
| SetInitTime(); |
| SetRouter(aRouters.GetHead()); |
| SetEntry(aRouters.IsEmpty() ? nullptr : aRouters.GetHead()->mEntries.GetHead()); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::Iterator::Advance(AdvanceMode aMode) |
| { |
| switch (aMode) |
| { |
| case kToNextEntry: |
| SetEntry(GetEntry()->GetNext()); |
| |
| if (GetEntry() != nullptr) |
| { |
| break; |
| } |
| |
| OT_FALL_THROUGH; |
| |
| case kToNextRouter: |
| SetRouter(GetRouter()->GetNext()); |
| |
| if (GetRouter() != nullptr) |
| { |
| SetEntry(GetRouter()->mEntries.GetHead()); |
| } |
| |
| break; |
| } |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // DiscoveredPrefixTable::Entry |
| |
| void RoutingManager::DiscoveredPrefixTable::Entry::SetFrom(const RouterAdvert::Header &aRaHeader) |
| { |
| mPrefix.Clear(); |
| mType = kTypeRoute; |
| mValidLifetime = aRaHeader.GetRouterLifetime(); |
| mShared.mRoutePreference = aRaHeader.GetDefaultRouterPreference(); |
| mLastUpdateTime = TimerMilli::GetNow(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::Entry::SetFrom(const PrefixInfoOption &aPio) |
| { |
| aPio.GetPrefix(mPrefix); |
| mType = kTypeOnLink; |
| mValidLifetime = aPio.GetValidLifetime(); |
| mShared.mPreferredLifetime = aPio.GetPreferredLifetime(); |
| mLastUpdateTime = TimerMilli::GetNow(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::Entry::SetFrom(const RouteInfoOption &aRio) |
| { |
| aRio.GetPrefix(mPrefix); |
| mType = kTypeRoute; |
| mValidLifetime = aRio.GetRouteLifetime(); |
| mShared.mRoutePreference = aRio.GetPreference(); |
| mLastUpdateTime = TimerMilli::GetNow(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::Entry::SetFrom(const PrefixTableEntry &aPrefixTableEntry) |
| { |
| mPrefix = AsCoreType(&aPrefixTableEntry.mPrefix); |
| mType = aPrefixTableEntry.mIsOnLink ? kTypeOnLink : kTypeRoute; |
| mValidLifetime = aPrefixTableEntry.mValidLifetime; |
| mShared.mPreferredLifetime = aPrefixTableEntry.mPreferredLifetime; |
| mLastUpdateTime = TimerMilli::GetNow(); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::Entry::operator==(const Entry &aOther) const |
| { |
| return (mType == aOther.mType) && (mPrefix == aOther.mPrefix); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::Entry::Matches(const Matcher &aMatcher) const |
| { |
| return (mType == aMatcher.mType) && (mPrefix == aMatcher.mPrefix); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::Entry::Matches(const Checker &aChecker) const |
| { |
| return (mType == aChecker.mType) && (mPrefix.IsUniqueLocal() == (aChecker.mMode == Checker::kIsUla)); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::Entry::Matches(const ExpirationChecker &aChecker) const |
| { |
| return GetExpireTime() <= aChecker.mNow; |
| } |
| |
| TimeMilli RoutingManager::DiscoveredPrefixTable::Entry::GetExpireTime(void) const |
| { |
| return CalculateExpirationTime(mValidLifetime); |
| } |
| |
| TimeMilli RoutingManager::DiscoveredPrefixTable::Entry::GetStaleTime(void) const |
| { |
| uint32_t delay = Min(kRtrAdvStaleTime, IsOnLinkPrefix() ? GetPreferredLifetime() : mValidLifetime); |
| |
| return mLastUpdateTime + TimeMilli::SecToMsec(delay); |
| } |
| |
| TimeMilli RoutingManager::DiscoveredPrefixTable::Entry::GetStaleTimeFromPreferredLifetime(void) const |
| { |
| return CalculateExpirationTime(GetPreferredLifetime()); |
| } |
| |
| bool RoutingManager::DiscoveredPrefixTable::Entry::IsDeprecated(void) const |
| { |
| OT_ASSERT(IsOnLinkPrefix()); |
| |
| return CalculateExpirationTime(GetPreferredLifetime()) <= TimerMilli::GetNow(); |
| } |
| |
| RoutingManager::RoutePreference RoutingManager::DiscoveredPrefixTable::Entry::GetPreference(void) const |
| { |
| // Returns the preference level to use when we publish |
| // the prefix entry in Network Data. |
| |
| return IsOnLinkPrefix() ? NetworkData::kRoutePreferenceMedium : GetRoutePreference(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::Entry::AdoptValidAndPreferredLifetimesFrom(const Entry &aEntry) |
| { |
| 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 (aEntry.mValidLifetime > kTwoHoursInSeconds || aEntry.GetExpireTime() > GetExpireTime()) |
| { |
| mValidLifetime = aEntry.mValidLifetime; |
| } |
| else if (GetExpireTime() > TimerMilli::GetNow() + TimeMilli::SecToMsec(kTwoHoursInSeconds)) |
| { |
| mValidLifetime = kTwoHoursInSeconds; |
| } |
| |
| mShared.mPreferredLifetime = aEntry.GetPreferredLifetime(); |
| mLastUpdateTime = aEntry.GetLastUpdateTime(); |
| } |
| |
| TimeMilli RoutingManager::DiscoveredPrefixTable::Entry::CalculateExpirationTime(uint32_t aLifetime) const |
| { |
| // `aLifetime` is in unit of seconds. We clamp the lifetime to max |
| // interval supported by `Timer` (`2^31` msec or ~24.8 days). |
| |
| static constexpr uint32_t kMaxLifetime = Time::MsecToSec(Timer::kMaxDelay); |
| |
| return mLastUpdateTime + Time::SecToMsec(Min(aLifetime, kMaxLifetime)); |
| } |
| |
| #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE |
| void RoutingManager::DiscoveredPrefixTable::Entry::Free(void) |
| { |
| Get<RoutingManager>().mDiscoveredPrefixTable.mEntryPool.Free(*this); |
| } |
| #endif |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // DiscoveredPrefixTable::Router |
| |
| bool RoutingManager::DiscoveredPrefixTable::Router::Matches(EmptyChecker aChecker) const |
| { |
| // Checks whether or not a `Router` instance has any useful info. An |
| // entry can be removed if it does not advertise M or O flags and |
| // also does not have any advertised prefix entries (RIO/PIO). If |
| // the router already failed to respond to max NS probe attempts, |
| // we consider it as offline and therefore do not consider its |
| // flags anymore. |
| |
| OT_UNUSED_VARIABLE(aChecker); |
| |
| bool hasFlags = false; |
| |
| if (mNsProbeCount <= kMaxNsProbes) |
| { |
| hasFlags = (mManagedAddressConfigFlag || mOtherConfigFlag); |
| } |
| |
| return !hasFlags && mEntries.IsEmpty(); |
| } |
| |
| void RoutingManager::DiscoveredPrefixTable::Router::CopyInfoTo(RouterEntry &aEntry) const |
| { |
| aEntry.mAddress = mAddress; |
| aEntry.mManagedAddressConfigFlag = mManagedAddressConfigFlag; |
| aEntry.mOtherConfigFlag = mOtherConfigFlag; |
| aEntry.mStubRouterFlag = mStubRouterFlag; |
| } |
| |
| #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE |
| void RoutingManager::DiscoveredPrefixTable::Router::Free(void) |
| { |
| mEntries.Free(); |
| Get<RoutingManager>().mDiscoveredPrefixTable.mRouterPool.Free(*this); |
| } |
| #endif |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // FavoredOmrPrefix |
| |
| bool RoutingManager::FavoredOmrPrefix::IsInfrastructureDerived(void) const |
| { |
| // Indicate whether the OMR prefix is infrastructure-derived which |
| // can be identified as a valid OMR prefix with preference of |
| // medium or higher. |
| |
| return !IsEmpty() && (mPreference >= NetworkData::kRoutePreferenceMedium); |
| } |
| |
| void RoutingManager::FavoredOmrPrefix::SetFrom(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig) |
| { |
| mPrefix = aOnMeshPrefixConfig.GetPrefix(); |
| mPreference = aOnMeshPrefixConfig.GetPreference(); |
| mIsDomainPrefix = aOnMeshPrefixConfig.mDp; |
| } |
| |
| void RoutingManager::FavoredOmrPrefix::SetFrom(const OmrPrefix &aOmrPrefix) |
| { |
| mPrefix = aOmrPrefix.GetPrefix(); |
| mPreference = aOmrPrefix.GetPreference(); |
| mIsDomainPrefix = aOmrPrefix.IsDomainPrefix(); |
| } |
| |
| bool RoutingManager::FavoredOmrPrefix::IsFavoredOver(const NetworkData::OnMeshPrefixConfig &aOmrPrefixConfig) const |
| { |
| // This method determines whether this OMR prefix is favored |
| // over another prefix. A prefix with higher preference is |
| // favored. If the preference is the same, then the smaller |
| // prefix (in the sense defined by `Ip6::Prefix`) is favored. |
| |
| bool isFavored = (mPreference > aOmrPrefixConfig.GetPreference()); |
| |
| OT_ASSERT(IsValidOmrPrefix(aOmrPrefixConfig)); |
| |
| if (mPreference == aOmrPrefixConfig.GetPreference()) |
| { |
| isFavored = (mPrefix < aOmrPrefixConfig.GetPrefix()); |
| } |
| |
| return isFavored; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // OmrPrefixManager |
| |
| RoutingManager::OmrPrefixManager::OmrPrefixManager(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mIsLocalAddedInNetData(false) |
| , mDefaultRoute(false) |
| { |
| } |
| |
| void RoutingManager::OmrPrefixManager::Init(const Ip6::Prefix &aBrUlaPrefix) |
| { |
| mGeneratedPrefix = aBrUlaPrefix; |
| mGeneratedPrefix.SetSubnetId(kOmrPrefixSubnetId); |
| mGeneratedPrefix.SetLength(kOmrPrefixLength); |
| |
| LogInfo("Generated local OMR prefix: %s", mGeneratedPrefix.ToString().AsCString()); |
| } |
| |
| void RoutingManager::OmrPrefixManager::Start(void) { DetermineFavoredPrefix(); } |
| |
| void RoutingManager::OmrPrefixManager::Stop(void) |
| { |
| RemoveLocalFromNetData(); |
| mFavoredPrefix.Clear(); |
| } |
| |
| void RoutingManager::OmrPrefixManager::DetermineFavoredPrefix(void) |
| { |
| // Determine the favored OMR prefix present in Network Data. |
| |
| NetworkData::Iterator iterator = NetworkData::kIteratorInit; |
| NetworkData::OnMeshPrefixConfig prefixConfig; |
| |
| mFavoredPrefix.Clear(); |
| |
| while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone) |
| { |
| if (!IsValidOmrPrefix(prefixConfig) || !prefixConfig.mPreferred) |
| { |
| continue; |
| } |
| |
| if (mFavoredPrefix.IsEmpty() || !mFavoredPrefix.IsFavoredOver(prefixConfig)) |
| { |
| mFavoredPrefix.SetFrom(prefixConfig); |
| } |
| } |
| } |
| |
| void RoutingManager::OmrPrefixManager::Evaluate(void) |
| { |
| OT_ASSERT(Get<RoutingManager>().IsRunning()); |
| |
| DetermineFavoredPrefix(); |
| |
| // Determine the local prefix and remove outdated prefix published by us. |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| if (Get<RoutingManager>().mPdPrefixManager.HasPrefix()) |
| { |
| if (mLocalPrefix.GetPrefix() != Get<RoutingManager>().mPdPrefixManager.GetPrefix()) |
| { |
| RemoveLocalFromNetData(); |
| mLocalPrefix.mPrefix = Get<RoutingManager>().mPdPrefixManager.GetPrefix(); |
| mLocalPrefix.mPreference = RoutePreference::kRoutePreferenceMedium; |
| mLocalPrefix.mIsDomainPrefix = false; |
| LogInfo("Setting local OMR prefix to PD prefix: %s", mLocalPrefix.GetPrefix().ToString().AsCString()); |
| } |
| } |
| else |
| #endif |
| if (mLocalPrefix.GetPrefix() != mGeneratedPrefix) |
| { |
| RemoveLocalFromNetData(); |
| mLocalPrefix.mPrefix = mGeneratedPrefix; |
| mLocalPrefix.mPreference = RoutePreference::kRoutePreferenceLow; |
| mLocalPrefix.mIsDomainPrefix = false; |
| LogInfo("Setting local OMR prefix to generated prefix: %s", mLocalPrefix.GetPrefix().ToString().AsCString()); |
| } |
| |
| // Decide if we need to add or remove our local OMR prefix. |
| if (mFavoredPrefix.IsEmpty() || mFavoredPrefix.GetPreference() < mLocalPrefix.GetPreference()) |
| { |
| if (mFavoredPrefix.IsEmpty()) |
| { |
| LogInfo("No favored OMR prefix found in Thread network."); |
| } |
| else |
| { |
| LogInfo("Replacing favored OMR prefix %s with higher preference local prefix %s.", |
| mFavoredPrefix.GetPrefix().ToString().AsCString(), mLocalPrefix.GetPrefix().ToString().AsCString()); |
| } |
| |
| // The `mFavoredPrefix` remains empty if we fail to publish |
| // the local OMR prefix. |
| SuccessOrExit(AddLocalToNetData()); |
| |
| mFavoredPrefix.SetFrom(mLocalPrefix); |
| } |
| else if (mFavoredPrefix.GetPrefix() == mLocalPrefix.GetPrefix()) |
| { |
| IgnoreError(AddLocalToNetData()); |
| } |
| else if (mIsLocalAddedInNetData) |
| { |
| LogInfo("There is already a favored OMR prefix %s in the Thread network", |
| mFavoredPrefix.GetPrefix().ToString().AsCString()); |
| |
| RemoveLocalFromNetData(); |
| } |
| |
| exit: |
| return; |
| } |
| |
| bool RoutingManager::OmrPrefixManager::ShouldAdvertiseLocalAsRio(void) const |
| { |
| // Determines whether the local OMR prefix should be advertised as |
| // RIO in emitted RAs. To advertise, we must have decided to |
| // publish it, and it must already be added and present in the |
| // Network Data. This ensures that we only advertise the local |
| // OMR prefix in emitted RAs when, as a Border Router, we can |
| // accept and route messages using an OMR-based address |
| // destination, which requires the prefix to be present in |
| // Network Data. Similarly, we stop advertising (and start |
| // deprecating) the OMR prefix in RAs as soon as we decide to |
| // remove it. After requesting its removal from Network Data, it |
| // may still be present in Network Data for a short interval due |
| // to delays in registering changes with the leader. |
| |
| bool shouldAdvertise = false; |
| NetworkData::Iterator iterator = NetworkData::kIteratorInit; |
| NetworkData::OnMeshPrefixConfig prefixConfig; |
| |
| VerifyOrExit(mIsLocalAddedInNetData); |
| |
| while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone) |
| { |
| if (!IsValidOmrPrefix(prefixConfig)) |
| { |
| continue; |
| } |
| |
| if (prefixConfig.GetPrefix() == mLocalPrefix.GetPrefix()) |
| { |
| shouldAdvertise = true; |
| break; |
| } |
| } |
| |
| exit: |
| return shouldAdvertise; |
| } |
| |
| Error RoutingManager::OmrPrefixManager::AddLocalToNetData(void) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(!mIsLocalAddedInNetData); |
| SuccessOrExit(error = AddOrUpdateLocalInNetData()); |
| mIsLocalAddedInNetData = true; |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::OmrPrefixManager::AddOrUpdateLocalInNetData(void) |
| { |
| // Add the local OMR prefix in Thread Network Data or update it |
| // (e.g., change default route flag) if it is already added. |
| |
| Error error; |
| NetworkData::OnMeshPrefixConfig config; |
| |
| config.Clear(); |
| config.mPrefix = mLocalPrefix.GetPrefix(); |
| config.mStable = true; |
| config.mSlaac = true; |
| config.mPreferred = true; |
| config.mOnMesh = true; |
| config.mDefaultRoute = mDefaultRoute; |
| config.mPreference = mLocalPrefix.GetPreference(); |
| |
| error = Get<NetworkData::Local>().AddOnMeshPrefix(config); |
| |
| if (error != kErrorNone) |
| { |
| LogWarn("Failed to %s %s in Thread Network Data: %s", !mIsLocalAddedInNetData ? "add" : "update", |
| LocalToString().AsCString(), ErrorToString(error)); |
| ExitNow(); |
| } |
| |
| Get<NetworkData::Notifier>().HandleServerDataUpdated(); |
| |
| LogInfo("%s %s in Thread Network Data", !mIsLocalAddedInNetData ? "Added" : "Updated", LocalToString().AsCString()); |
| |
| exit: |
| return error; |
| } |
| |
| void RoutingManager::OmrPrefixManager::RemoveLocalFromNetData(void) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(mIsLocalAddedInNetData); |
| |
| error = Get<NetworkData::Local>().RemoveOnMeshPrefix(mLocalPrefix.GetPrefix()); |
| |
| if (error != kErrorNone) |
| { |
| LogWarn("Failed to remove %s from Thread Network Data: %s", LocalToString().AsCString(), ErrorToString(error)); |
| ExitNow(); |
| } |
| |
| mIsLocalAddedInNetData = false; |
| Get<NetworkData::Notifier>().HandleServerDataUpdated(); |
| LogInfo("Removed %s from Thread Network Data", LocalToString().AsCString()); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OmrPrefixManager::UpdateDefaultRouteFlag(bool aDefaultRoute) |
| { |
| VerifyOrExit(aDefaultRoute != mDefaultRoute); |
| |
| mDefaultRoute = aDefaultRoute; |
| |
| VerifyOrExit(mIsLocalAddedInNetData); |
| IgnoreError(AddOrUpdateLocalInNetData()); |
| |
| exit: |
| return; |
| } |
| |
| RoutingManager::OmrPrefixManager::InfoString RoutingManager::OmrPrefixManager::LocalToString(void) const |
| { |
| InfoString string; |
| |
| string.Append("local OMR prefix %s (def-route:%s)", mLocalPrefix.GetPrefix().ToString().AsCString(), |
| ToYesNo(mDefaultRoute)); |
| return string; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // OnLinkPrefixManager |
| |
| RoutingManager::OnLinkPrefixManager::OnLinkPrefixManager(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mState(kIdle) |
| , mTimer(aInstance) |
| { |
| mLocalPrefix.Clear(); |
| mFavoredDiscoveredPrefix.Clear(); |
| mOldLocalPrefixes.Clear(); |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::SetState(State aState) |
| { |
| VerifyOrExit(mState != aState); |
| |
| LogInfo("Local on-link prefix state: %s -> %s (%s)", StateToString(mState), StateToString(aState), |
| mLocalPrefix.ToString().AsCString()); |
| mState = aState; |
| |
| // Mark the Advertising PIO (AP) flag in the published route, when |
| // the local on-link prefix is being published, advertised, or |
| // deprecated. |
| |
| Get<RoutingManager>().mRoutePublisher.UpdateAdvPioFlags(aState != kIdle); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::Init(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| Settings::BrOnLinkPrefix savedPrefix; |
| bool refreshStoredPrefixes = false; |
| |
| // Restore old prefixes from `Settings` |
| |
| for (int index = 0; Get<Settings>().ReadBrOnLinkPrefix(index, savedPrefix) == kErrorNone; index++) |
| { |
| uint32_t lifetime; |
| OldPrefix *entry; |
| |
| if (mOldLocalPrefixes.ContainsMatching(savedPrefix.GetPrefix())) |
| { |
| // We should not see duplicate entries in `Settings` |
| // but if we do we refresh the stored prefixes to make |
| // it consistent. |
| refreshStoredPrefixes = true; |
| continue; |
| } |
| |
| entry = mOldLocalPrefixes.PushBack(); |
| |
| if (entry == nullptr) |
| { |
| // If there are more stored prefixes, we refresh the |
| // prefixes in `Settings` to remove the ones we cannot |
| // handle. |
| |
| refreshStoredPrefixes = true; |
| break; |
| } |
| |
| lifetime = Min(savedPrefix.GetLifetime(), Time::MsecToSec(TimerMilli::kMaxDelay)); |
| |
| entry->mPrefix = savedPrefix.GetPrefix(); |
| entry->mExpireTime = now + Time::SecToMsec(lifetime); |
| |
| LogInfo("Restored old prefix %s, lifetime:%lu", entry->mPrefix.ToString().AsCString(), ToUlong(lifetime)); |
| |
| mTimer.FireAtIfEarlier(entry->mExpireTime); |
| } |
| |
| if (refreshStoredPrefixes) |
| { |
| // We clear the entries in `Settings` and re-write the entries |
| // from `mOldLocalPrefixes` array. |
| |
| IgnoreError(Get<Settings>().DeleteAllBrOnLinkPrefixes()); |
| |
| for (OldPrefix &oldPrefix : mOldLocalPrefixes) |
| { |
| SavePrefix(oldPrefix.mPrefix, oldPrefix.mExpireTime); |
| } |
| } |
| |
| GenerateLocalPrefix(); |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::GenerateLocalPrefix(void) |
| { |
| const MeshCoP::ExtendedPanId &extPanId = Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId(); |
| OldPrefix *entry; |
| Ip6::Prefix oldLocalPrefix = mLocalPrefix; |
| |
| // Global ID: 40 most significant bits of Extended PAN ID |
| // Subnet ID: 16 least significant bits of Extended PAN ID |
| |
| mLocalPrefix.mPrefix.mFields.m8[0] = 0xfd; |
| memcpy(mLocalPrefix.mPrefix.mFields.m8 + 1, extPanId.m8, 5); |
| memcpy(mLocalPrefix.mPrefix.mFields.m8 + 6, extPanId.m8 + 6, 2); |
| |
| mLocalPrefix.SetLength(kOnLinkPrefixLength); |
| |
| // We ensure that the local prefix did change, since not all the |
| // bytes in Extended PAN ID are used in derivation of the local prefix. |
| |
| VerifyOrExit(mLocalPrefix != oldLocalPrefix); |
| |
| LogNote("Local on-link prefix: %s", mLocalPrefix.ToString().AsCString()); |
| |
| // Check if the new local prefix happens to be in `mOldLocalPrefixes` array. |
| // If so, we remove it from the array and update the state accordingly. |
| |
| entry = mOldLocalPrefixes.FindMatching(mLocalPrefix); |
| |
| if (entry != nullptr) |
| { |
| SetState(kDeprecating); |
| mExpireTime = entry->mExpireTime; |
| mOldLocalPrefixes.Remove(*entry); |
| } |
| else |
| { |
| SetState(kIdle); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::Start(void) {} |
| |
| void RoutingManager::OnLinkPrefixManager::Stop(void) |
| { |
| mFavoredDiscoveredPrefix.Clear(); |
| |
| switch (GetState()) |
| { |
| case kIdle: |
| break; |
| |
| case kPublishing: |
| case kAdvertising: |
| case kDeprecating: |
| SetState(kDeprecating); |
| break; |
| } |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::Evaluate(void) |
| { |
| VerifyOrExit(!Get<RoutingManager>().mRsSender.IsInProgress()); |
| |
| Get<RoutingManager>().mDiscoveredPrefixTable.FindFavoredOnLinkPrefix(mFavoredDiscoveredPrefix); |
| |
| if ((mFavoredDiscoveredPrefix.GetLength() == 0) || (mFavoredDiscoveredPrefix == mLocalPrefix)) |
| { |
| // We advertise the local on-link prefix if no other prefix is |
| // discovered, or if the favored discovered prefix is the |
| // same as the local prefix (for redundancy). Note that the |
| // local on-link prefix, derived from the extended PAN ID, is |
| // identical for all BRs on the same Thread mesh. |
| |
| PublishAndAdvertise(); |
| |
| // We remove the local on-link prefix from the discovered prefix |
| // table, in case it was previously discovered and is now |
| // deprecating. `ShouldProcessPrefixInfoOption()` also prevents |
| // adding the local prefix to the table while we're advertising it. |
| |
| Get<RoutingManager>().mDiscoveredPrefixTable.RemoveOnLinkPrefix(mLocalPrefix); |
| |
| mFavoredDiscoveredPrefix.Clear(); |
| } |
| else if (IsPublishingOrAdvertising()) |
| { |
| // When an application-specific on-link prefix is received and |
| // it is larger than the local prefix, we will not remove the |
| // advertised local prefix. In this case, there will be two |
| // on-link prefixes on the infra link. But all BRs will still |
| // converge to the same smallest/favored on-link prefix and the |
| // application-specific prefix is not used. |
| |
| if (!(mLocalPrefix < mFavoredDiscoveredPrefix)) |
| { |
| LogInfo("Found a favored on-link prefix %s", mFavoredDiscoveredPrefix.ToString().AsCString()); |
| Deprecate(); |
| } |
| } |
| |
| exit: |
| return; |
| } |
| |
| bool RoutingManager::OnLinkPrefixManager::IsInitalEvaluationDone(void) const |
| { |
| // This method indicates whether or not we are done with the |
| // initial policy evaluation of the on-link prefixes, i.e., either |
| // we have discovered a favored on-link prefix (being advertised by |
| // another router on infra link) or we are advertising our local |
| // on-link prefix. |
| |
| return (mFavoredDiscoveredPrefix.GetLength() != 0 || IsPublishingOrAdvertising()); |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::HandleDiscoveredPrefixTableChanged(void) |
| { |
| // This is a callback from `mDiscoveredPrefixTable` indicating that |
| // there has been a change in the table. If the favored on-link |
| // prefix has changed, we trigger a re-evaluation of the routing |
| // policy. |
| |
| Ip6::Prefix newFavoredPrefix; |
| |
| Get<RoutingManager>().mDiscoveredPrefixTable.FindFavoredOnLinkPrefix(newFavoredPrefix); |
| |
| if (newFavoredPrefix != mFavoredDiscoveredPrefix) |
| { |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| } |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::PublishAndAdvertise(void) |
| { |
| // Start publishing and advertising the local on-link prefix if |
| // not already. |
| |
| switch (GetState()) |
| { |
| case kIdle: |
| case kDeprecating: |
| break; |
| |
| case kPublishing: |
| case kAdvertising: |
| ExitNow(); |
| } |
| |
| SetState(kPublishing); |
| ResetExpireTime(TimerMilli::GetNow()); |
| |
| // We wait for the ULA `fc00::/7` route or a sub-prefix of it (e.g., |
| // default route) to be added in Network Data before |
| // starting to advertise the local on-link prefix in RAs. |
| // However, if it is already present in Network Data (e.g., |
| // added by another BR on the same Thread mesh), we can |
| // immediately start advertising it. |
| |
| if (Get<RoutingManager>().NetworkDataContainsUlaRoute()) |
| { |
| SetState(kAdvertising); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::Deprecate(void) |
| { |
| // Deprecate the local on-link prefix if it was being advertised |
| // before. While depreciating the prefix, we wait for the lifetime |
| // timer to expire before unpublishing the prefix from the Network |
| // Data. We also continue to include it as a PIO in the RA message |
| // with zero preferred lifetime and the remaining valid lifetime |
| // until the timer expires. |
| |
| switch (GetState()) |
| { |
| case kPublishing: |
| case kAdvertising: |
| SetState(kDeprecating); |
| break; |
| |
| case kIdle: |
| case kDeprecating: |
| break; |
| } |
| } |
| |
| bool RoutingManager::OnLinkPrefixManager::ShouldPublishUlaRoute(void) const |
| { |
| // Determine whether or not we should publish ULA prefix. We need |
| // to publish if we are in any of `kPublishing`, `kAdvertising`, |
| // or `kDeprecating` states, or if there is at least one old local |
| // prefix being deprecated. |
| |
| return (GetState() != kIdle) || !mOldLocalPrefixes.IsEmpty(); |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::ResetExpireTime(TimeMilli aNow) |
| { |
| mExpireTime = aNow + TimeMilli::SecToMsec(kDefaultOnLinkPrefixLifetime); |
| mTimer.FireAtIfEarlier(mExpireTime); |
| SavePrefix(mLocalPrefix, mExpireTime); |
| } |
| |
| bool RoutingManager::OnLinkPrefixManager::IsPublishingOrAdvertising(void) const |
| { |
| return (GetState() == kPublishing) || (GetState() == kAdvertising); |
| } |
| |
| Error RoutingManager::OnLinkPrefixManager::AppendAsPiosTo(RouterAdvert::TxMessage &aRaMessage) |
| { |
| Error error; |
| |
| SuccessOrExit(error = AppendCurPrefix(aRaMessage)); |
| error = AppendOldPrefixes(aRaMessage); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::OnLinkPrefixManager::AppendCurPrefix(RouterAdvert::TxMessage &aRaMessage) |
| { |
| // Append the local on-link prefix to the `aRaMessage` as a PIO |
| // only if it is being advertised or deprecated. |
| // |
| // If in `kAdvertising` state, we reset the expire time. |
| // If in `kDeprecating` state, we include it as PIO with zero |
| // preferred lifetime and the remaining valid lifetime. |
| |
| Error error = kErrorNone; |
| uint32_t validLifetime = kDefaultOnLinkPrefixLifetime; |
| uint32_t preferredLifetime = kDefaultOnLinkPrefixLifetime; |
| TimeMilli now = TimerMilli::GetNow(); |
| |
| switch (GetState()) |
| { |
| case kAdvertising: |
| ResetExpireTime(now); |
| break; |
| |
| case kDeprecating: |
| VerifyOrExit(mExpireTime > now); |
| validLifetime = TimeMilli::MsecToSec(mExpireTime - now); |
| preferredLifetime = 0; |
| break; |
| |
| case kIdle: |
| case kPublishing: |
| ExitNow(); |
| } |
| |
| SuccessOrExit(error = aRaMessage.AppendPrefixInfoOption(mLocalPrefix, validLifetime, preferredLifetime)); |
| |
| LogPrefixInfoOption(mLocalPrefix, validLifetime, preferredLifetime); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::OnLinkPrefixManager::AppendOldPrefixes(RouterAdvert::TxMessage &aRaMessage) |
| { |
| Error error = kErrorNone; |
| TimeMilli now = TimerMilli::GetNow(); |
| uint32_t validLifetime; |
| |
| for (const OldPrefix &oldPrefix : mOldLocalPrefixes) |
| { |
| if (oldPrefix.mExpireTime < now) |
| { |
| continue; |
| } |
| |
| validLifetime = TimeMilli::MsecToSec(oldPrefix.mExpireTime - now); |
| SuccessOrExit(error = aRaMessage.AppendPrefixInfoOption(oldPrefix.mPrefix, validLifetime, 0)); |
| |
| LogPrefixInfoOption(oldPrefix.mPrefix, validLifetime, 0); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::HandleNetDataChange(void) |
| { |
| VerifyOrExit(GetState() == kPublishing); |
| |
| if (Get<RoutingManager>().NetworkDataContainsUlaRoute()) |
| { |
| SetState(kAdvertising); |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::HandleExtPanIdChange(void) |
| { |
| // If the current local prefix is being advertised or deprecated, |
| // we save it in `mOldLocalPrefixes` and keep deprecating it. It will |
| // be included in emitted RAs as PIO with zero preferred lifetime. |
| // It will still be present in Network Data until its expire time |
| // so to allow Thread nodes to continue to communicate with `InfraIf` |
| // device using addresses based on this prefix. |
| |
| uint16_t oldState = GetState(); |
| Ip6::Prefix oldPrefix = mLocalPrefix; |
| |
| GenerateLocalPrefix(); |
| |
| VerifyOrExit(oldPrefix != mLocalPrefix); |
| |
| switch (oldState) |
| { |
| case kIdle: |
| case kPublishing: |
| break; |
| |
| case kAdvertising: |
| case kDeprecating: |
| DeprecateOldPrefix(oldPrefix, mExpireTime); |
| break; |
| } |
| |
| if (Get<RoutingManager>().mIsRunning) |
| { |
| Get<RoutingManager>().mRoutePublisher.Evaluate(); |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::DeprecateOldPrefix(const Ip6::Prefix &aPrefix, TimeMilli aExpireTime) |
| { |
| OldPrefix *entry = nullptr; |
| Ip6::Prefix removedPrefix; |
| |
| removedPrefix.Clear(); |
| |
| VerifyOrExit(!mOldLocalPrefixes.ContainsMatching(aPrefix)); |
| |
| LogInfo("Deprecating old on-link prefix %s", aPrefix.ToString().AsCString()); |
| |
| if (!mOldLocalPrefixes.IsFull()) |
| { |
| entry = mOldLocalPrefixes.PushBack(); |
| } |
| else |
| { |
| // If there is no more room in `mOldLocalPrefixes` array |
| // we evict the entry with the earliest expiration time. |
| |
| entry = &mOldLocalPrefixes[0]; |
| |
| for (OldPrefix &oldPrefix : mOldLocalPrefixes) |
| { |
| if ((oldPrefix.mExpireTime < entry->mExpireTime)) |
| { |
| entry = &oldPrefix; |
| } |
| } |
| |
| removedPrefix = entry->mPrefix; |
| |
| IgnoreError(Get<Settings>().RemoveBrOnLinkPrefix(removedPrefix)); |
| } |
| |
| entry->mPrefix = aPrefix; |
| entry->mExpireTime = aExpireTime; |
| mTimer.FireAtIfEarlier(aExpireTime); |
| |
| SavePrefix(aPrefix, aExpireTime); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::SavePrefix(const Ip6::Prefix &aPrefix, TimeMilli aExpireTime) |
| { |
| Settings::BrOnLinkPrefix savedPrefix; |
| |
| savedPrefix.SetPrefix(aPrefix); |
| savedPrefix.SetLifetime(TimeMilli::MsecToSec(aExpireTime - TimerMilli::GetNow())); |
| IgnoreError(Get<Settings>().AddOrUpdateBrOnLinkPrefix(savedPrefix)); |
| } |
| |
| void RoutingManager::OnLinkPrefixManager::HandleTimer(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| TimeMilli nextExpireTime = now.GetDistantFuture(); |
| Array<Ip6::Prefix, kMaxOldPrefixes> expiredPrefixes; |
| |
| switch (GetState()) |
| { |
| case kIdle: |
| break; |
| case kPublishing: |
| case kAdvertising: |
| case kDeprecating: |
| if (now >= mExpireTime) |
| { |
| IgnoreError(Get<Settings>().RemoveBrOnLinkPrefix(mLocalPrefix)); |
| SetState(kIdle); |
| } |
| else |
| { |
| nextExpireTime = mExpireTime; |
| } |
| break; |
| } |
| |
| for (OldPrefix &entry : mOldLocalPrefixes) |
| { |
| if (now >= entry.mExpireTime) |
| { |
| SuccessOrAssert(expiredPrefixes.PushBack(entry.mPrefix)); |
| } |
| else |
| { |
| nextExpireTime = Min(nextExpireTime, entry.mExpireTime); |
| } |
| } |
| |
| for (const Ip6::Prefix &prefix : expiredPrefixes) |
| { |
| LogInfo("Old local on-link prefix %s expired", prefix.ToString().AsCString()); |
| IgnoreError(Get<Settings>().RemoveBrOnLinkPrefix(prefix)); |
| mOldLocalPrefixes.RemoveMatching(prefix); |
| } |
| |
| if (nextExpireTime != now.GetDistantFuture()) |
| { |
| mTimer.FireAtIfEarlier(nextExpireTime); |
| } |
| |
| Get<RoutingManager>().mRoutePublisher.Evaluate(); |
| } |
| |
| const char *RoutingManager::OnLinkPrefixManager::StateToString(State aState) |
| { |
| static const char *const kStateStrings[] = { |
| "Removed", // (0) kIdle |
| "Publishing", // (1) kPublishing |
| "Advertising", // (2) kAdvertising |
| "Deprecating", // (3) kDeprecating |
| }; |
| |
| static_assert(0 == kIdle, "kIdle value is incorrect"); |
| static_assert(1 == kPublishing, "kPublishing value is incorrect"); |
| static_assert(2 == kAdvertising, "kAdvertising value is incorrect"); |
| static_assert(3 == kDeprecating, "kDeprecating value is incorrect"); |
| |
| return kStateStrings[aState]; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // RioAdvertiser |
| |
| RoutingManager::RioAdvertiser::RioAdvertiser(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mTimer(aInstance) |
| , mPreference(NetworkData::kRoutePreferenceLow) |
| , mUserSetPreference(false) |
| { |
| } |
| |
| void RoutingManager::RioAdvertiser::SetPreference(RoutePreference aPreference) |
| { |
| LogInfo("User explicitly set RIO Preference to %s", RoutePreferenceToString(aPreference)); |
| mUserSetPreference = true; |
| UpdatePreference(aPreference); |
| } |
| |
| void RoutingManager::RioAdvertiser::ClearPreference(void) |
| { |
| VerifyOrExit(mUserSetPreference); |
| |
| LogInfo("User cleared explicitly set RIO Preference"); |
| mUserSetPreference = false; |
| SetPreferenceBasedOnRole(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::RioAdvertiser::HandleRoleChanged(void) |
| { |
| if (!mUserSetPreference) |
| { |
| SetPreferenceBasedOnRole(); |
| } |
| } |
| |
| void RoutingManager::RioAdvertiser::SetPreferenceBasedOnRole(void) |
| { |
| UpdatePreference(Get<Mle::Mle>().IsRouterOrLeader() ? NetworkData::kRoutePreferenceMedium |
| : NetworkData::kRoutePreferenceLow); |
| } |
| |
| void RoutingManager::RioAdvertiser::UpdatePreference(RoutePreference aPreference) |
| { |
| VerifyOrExit(mPreference != aPreference); |
| |
| LogInfo("RIO Preference changed: %s -> %s", RoutePreferenceToString(mPreference), |
| RoutePreferenceToString(aPreference)); |
| mPreference = aPreference; |
| |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| |
| exit: |
| return; |
| } |
| |
| Error RoutingManager::RioAdvertiser::InvalidatPrevRios(RouterAdvert::TxMessage &aRaMessage) |
| { |
| Error error = kErrorNone; |
| |
| for (const RioPrefix &prefix : mPrefixes) |
| { |
| SuccessOrExit(error = AppendRio(prefix.mPrefix, /* aRouteLifetime */ 0, aRaMessage)); |
| } |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE |
| mPrefixes.Free(); |
| #endif |
| |
| mPrefixes.Clear(); |
| mTimer.Stop(); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::RioAdvertiser::AppendRios(RouterAdvert::TxMessage &aRaMessage) |
| { |
| Error error = kErrorNone; |
| TimeMilli now = TimerMilli::GetNow(); |
| TimeMilli nextTime = now.GetDistantFuture(); |
| RioPrefixArray oldPrefixes; |
| NetworkData::Iterator iterator = NetworkData::kIteratorInit; |
| NetworkData::OnMeshPrefixConfig prefixConfig; |
| const OmrPrefixManager &omrPrefixManager = Get<RoutingManager>().mOmrPrefixManager; |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE |
| oldPrefixes.TakeFrom(static_cast<RioPrefixArray &&>(mPrefixes)); |
| #else |
| oldPrefixes = mPrefixes; |
| #endif |
| |
| mPrefixes.Clear(); |
| |
| // `mPrefixes` array can have a limited size. We add more |
| // important prefixes first in the array to ensure they are |
| // advertised in the RA message. Note that `Add()` method |
| // will ensure to add a prefix only once (will check if |
| // prefix is already present in the array). |
| |
| // (1) Local OMR prefix. |
| |
| if (omrPrefixManager.ShouldAdvertiseLocalAsRio()) |
| { |
| mPrefixes.Add(omrPrefixManager.GetLocalPrefix().GetPrefix()); |
| } |
| |
| // (2) Favored OMR prefix. |
| |
| if (!omrPrefixManager.GetFavoredPrefix().IsEmpty() && !omrPrefixManager.GetFavoredPrefix().IsDomainPrefix()) |
| { |
| mPrefixes.Add(omrPrefixManager.GetFavoredPrefix().GetPrefix()); |
| } |
| |
| // (3) All other OMR prefixes. |
| |
| iterator = NetworkData::kIteratorInit; |
| |
| while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone) |
| { |
| // The decision to include the local OMR prefix as a RIO is |
| // delegated to `OmrPrefixManager.ShouldAdvertiseLocalAsRio()` |
| // at step (1). Here, as we iterate over Network Data prefixes, |
| // we exclude entries matching the local OMR prefix. This is |
| // because `OmrPrefixManager` may have decided to stop advertising |
| // it, while it might still be present in the Network Data due to |
| // delays in registering changes with the leader. |
| |
| if (prefixConfig.mDp) |
| { |
| continue; |
| } |
| |
| if (IsValidOmrPrefix(prefixConfig) && |
| (prefixConfig.GetPrefix() != omrPrefixManager.GetLocalPrefix().GetPrefix())) |
| { |
| mPrefixes.Add(prefixConfig.GetPrefix()); |
| } |
| } |
| |
| // (4) All other on-mesh prefixes (excluding Domain Prefix). |
| |
| iterator = NetworkData::kIteratorInit; |
| |
| while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone) |
| { |
| if (prefixConfig.mOnMesh && !prefixConfig.mDp && !IsValidOmrPrefix(prefixConfig)) |
| { |
| mPrefixes.Add(prefixConfig.GetPrefix()); |
| } |
| } |
| |
| // Determine deprecating prefixes |
| |
| for (RioPrefix &prefix : oldPrefixes) |
| { |
| if (mPrefixes.ContainsMatching(prefix.mPrefix)) |
| { |
| continue; |
| } |
| |
| if (prefix.mIsDeprecating) |
| { |
| if (now >= prefix.mExpirationTime) |
| { |
| SuccessOrExit(error = AppendRio(prefix.mPrefix, /* aRouteLifetime */ 0, aRaMessage)); |
| continue; |
| } |
| } |
| else |
| { |
| prefix.mIsDeprecating = true; |
| prefix.mExpirationTime = now + kDeprecationTime; |
| } |
| |
| if (mPrefixes.PushBack(prefix) != kErrorNone) |
| { |
| LogWarn("Too many deprecating on-mesh prefixes, removing %s", prefix.mPrefix.ToString().AsCString()); |
| SuccessOrExit(error = AppendRio(prefix.mPrefix, /* aRouteLifetime */ 0, aRaMessage)); |
| } |
| |
| nextTime = Min(nextTime, prefix.mExpirationTime); |
| } |
| |
| // Advertise all prefixes in `mPrefixes` |
| |
| for (const RioPrefix &prefix : mPrefixes) |
| { |
| uint32_t lifetime = kDefaultOmrPrefixLifetime; |
| |
| if (prefix.mIsDeprecating) |
| { |
| lifetime = TimeMilli::MsecToSec(prefix.mExpirationTime - now); |
| } |
| |
| SuccessOrExit(error = AppendRio(prefix.mPrefix, lifetime, aRaMessage)); |
| } |
| |
| if (nextTime != now.GetDistantFuture()) |
| { |
| mTimer.FireAtIfEarlier(nextTime); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::RioAdvertiser::AppendRio(const Ip6::Prefix &aPrefix, |
| uint32_t aRouteLifetime, |
| RouterAdvert::TxMessage &aRaMessage) |
| { |
| Error error; |
| |
| SuccessOrExit(error = aRaMessage.AppendRouteInfoOption(aPrefix, aRouteLifetime, mPreference)); |
| LogRouteInfoOption(aPrefix, aRouteLifetime, mPreference); |
| |
| exit: |
| return error; |
| } |
| |
| void RoutingManager::RioAdvertiser::HandleTimer(void) |
| { |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately); |
| } |
| |
| void RoutingManager::RioAdvertiser::RioPrefixArray::Add(const Ip6::Prefix &aPrefix) |
| { |
| // Checks if `aPrefix` is already present in the array and if not |
| // adds it as a new entry. |
| |
| Error error; |
| RioPrefix newEntry; |
| |
| VerifyOrExit(!ContainsMatching(aPrefix)); |
| |
| newEntry.Clear(); |
| newEntry.mPrefix = aPrefix; |
| |
| error = PushBack(newEntry); |
| |
| if (error != kErrorNone) |
| { |
| LogWarn("Too many on-mesh prefixes in net data, ignoring prefix %s", aPrefix.ToString().AsCString()); |
| } |
| |
| exit: |
| return; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // RoutePublisher |
| |
| const otIp6Prefix RoutingManager::RoutePublisher::kUlaPrefix = { |
| {{{0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, |
| 7, |
| }; |
| |
| RoutingManager::RoutePublisher::RoutePublisher(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mState(kDoNotPublish) |
| , mPreference(NetworkData::kRoutePreferenceMedium) |
| , mUserSetPreference(false) |
| , mAdvPioFlag(false) |
| , mTimer(aInstance) |
| { |
| } |
| |
| void RoutingManager::RoutePublisher::Evaluate(void) |
| { |
| State newState = kDoNotPublish; |
| |
| VerifyOrExit(Get<RoutingManager>().IsRunning()); |
| |
| if (Get<RoutingManager>().mOmrPrefixManager.GetFavoredPrefix().IsInfrastructureDerived() && |
| Get<RoutingManager>().mDiscoveredPrefixTable.ContainsDefaultOrNonUlaRoutePrefix()) |
| { |
| newState = kPublishDefault; |
| } |
| else if (Get<RoutingManager>().mDiscoveredPrefixTable.ContainsNonUlaOnLinkPrefix()) |
| { |
| newState = kPublishDefault; |
| } |
| else if (Get<RoutingManager>().mDiscoveredPrefixTable.ContainsUlaOnLinkPrefix() || |
| Get<RoutingManager>().mOnLinkPrefixManager.ShouldPublishUlaRoute()) |
| { |
| newState = kPublishUla; |
| } |
| |
| exit: |
| if (newState != mState) |
| { |
| LogInfo("RoutePublisher state: %s -> %s", StateToString(mState), StateToString(newState)); |
| UpdatePublishedRoute(newState); |
| Get<RoutingManager>().mOmrPrefixManager.UpdateDefaultRouteFlag(newState == kPublishDefault); |
| } |
| } |
| |
| void RoutingManager::RoutePublisher::DeterminePrefixFor(State aState, Ip6::Prefix &aPrefix) const |
| { |
| aPrefix.Clear(); |
| |
| switch (aState) |
| { |
| case kDoNotPublish: |
| case kPublishDefault: |
| // `Clear()` will set the prefix to `::/0`. |
| break; |
| case kPublishUla: |
| aPrefix = GetUlaPrefix(); |
| break; |
| } |
| } |
| |
| void RoutingManager::RoutePublisher::UpdatePublishedRoute(State aNewState) |
| { |
| // Updates the published route entry in Network Data, transitioning |
| // from current `mState` to new `aNewState`. This method can be used |
| // when there is no change to `mState` but a change to `mPreference` |
| // or `mAdvPioFlag`. |
| |
| Ip6::Prefix oldPrefix; |
| NetworkData::ExternalRouteConfig routeConfig; |
| |
| DeterminePrefixFor(mState, oldPrefix); |
| |
| if (aNewState == kDoNotPublish) |
| { |
| VerifyOrExit(mState != kDoNotPublish); |
| IgnoreError(Get<NetworkData::Publisher>().UnpublishPrefix(oldPrefix)); |
| ExitNow(); |
| } |
| |
| routeConfig.Clear(); |
| routeConfig.mPreference = mPreference; |
| routeConfig.mAdvPio = mAdvPioFlag; |
| routeConfig.mStable = true; |
| DeterminePrefixFor(aNewState, routeConfig.GetPrefix()); |
| |
| // If we were not publishing a route prefix before, publish the new |
| // `routeConfig`. Otherwise, use `ReplacePublishedExternalRoute()` to |
| // replace the previously published prefix entry. This ensures that we do |
| // not have a situation where the previous route is removed while the new |
| // one is not yet added in the Network Data. |
| |
| if (mState == kDoNotPublish) |
| { |
| SuccessOrAssert(Get<NetworkData::Publisher>().PublishExternalRoute( |
| routeConfig, NetworkData::Publisher::kFromRoutingManager)); |
| } |
| else |
| { |
| SuccessOrAssert(Get<NetworkData::Publisher>().ReplacePublishedExternalRoute( |
| oldPrefix, routeConfig, NetworkData::Publisher::kFromRoutingManager)); |
| } |
| |
| exit: |
| mState = aNewState; |
| } |
| |
| void RoutingManager::RoutePublisher::Unpublish(void) |
| { |
| // Unpublish the previously published route based on `mState` |
| // and update `mState`. |
| |
| Ip6::Prefix prefix; |
| |
| VerifyOrExit(mState != kDoNotPublish); |
| DeterminePrefixFor(mState, prefix); |
| IgnoreError(Get<NetworkData::Publisher>().UnpublishPrefix(prefix)); |
| mState = kDoNotPublish; |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::RoutePublisher::UpdateAdvPioFlags(bool aAdvPioFlag) |
| { |
| VerifyOrExit(mAdvPioFlag != aAdvPioFlag); |
| mAdvPioFlag = aAdvPioFlag; |
| UpdatePublishedRoute(mState); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::RoutePublisher::SetPreference(RoutePreference aPreference) |
| { |
| LogInfo("User explicitly set published route preference to %s", RoutePreferenceToString(aPreference)); |
| mUserSetPreference = true; |
| mTimer.Stop(); |
| UpdatePreference(aPreference); |
| } |
| |
| void RoutingManager::RoutePublisher::ClearPreference(void) |
| { |
| VerifyOrExit(mUserSetPreference); |
| |
| LogInfo("User cleared explicitly set published route preference - set based on role"); |
| mUserSetPreference = false; |
| SetPreferenceBasedOnRole(); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::RoutePublisher::SetPreferenceBasedOnRole(void) |
| { |
| RoutePreference preference = NetworkData::kRoutePreferenceMedium; |
| |
| if (Get<Mle::Mle>().IsChild() && (Get<Mle::Mle>().GetParent().GetTwoWayLinkQuality() != kLinkQuality3)) |
| { |
| preference = NetworkData::kRoutePreferenceLow; |
| } |
| |
| UpdatePreference(preference); |
| mTimer.Stop(); |
| } |
| |
| void RoutingManager::RoutePublisher::HandleNotifierEvents(Events aEvents) |
| { |
| VerifyOrExit(!mUserSetPreference); |
| |
| if (aEvents.Contains(kEventThreadRoleChanged)) |
| { |
| SetPreferenceBasedOnRole(); |
| } |
| |
| if (aEvents.Contains(kEventParentLinkQualityChanged)) |
| { |
| VerifyOrExit(Get<Mle::Mle>().IsChild()); |
| |
| if (Get<Mle::Mle>().GetParent().GetTwoWayLinkQuality() == kLinkQuality3) |
| { |
| VerifyOrExit(!mTimer.IsRunning()); |
| mTimer.Start(kDelayBeforePrfUpdateOnLinkQuality3); |
| } |
| else |
| { |
| UpdatePreference(NetworkData::kRoutePreferenceLow); |
| mTimer.Stop(); |
| } |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::RoutePublisher::HandleTimer(void) { SetPreferenceBasedOnRole(); } |
| |
| void RoutingManager::RoutePublisher::UpdatePreference(RoutePreference aPreference) |
| { |
| VerifyOrExit(mPreference != aPreference); |
| |
| LogInfo("Published route preference changed: %s -> %s", RoutePreferenceToString(mPreference), |
| RoutePreferenceToString(aPreference)); |
| mPreference = aPreference; |
| UpdatePublishedRoute(mState); |
| |
| exit: |
| return; |
| } |
| |
| const char *RoutingManager::RoutePublisher::StateToString(State aState) |
| { |
| static const char *const kStateStrings[] = { |
| "none", // (0) kDoNotPublish |
| "def-route", // (1) kPublishDefault |
| "ula", // (2) kPublishUla |
| }; |
| |
| static_assert(0 == kDoNotPublish, "kDoNotPublish value is incorrect"); |
| static_assert(1 == kPublishDefault, "kPublishDefault value is incorrect"); |
| static_assert(2 == kPublishUla, "kPublishUla value is incorrect"); |
| |
| return kStateStrings[aState]; |
| } |
| |
| #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // Nat64PrefixManager |
| |
| RoutingManager::Nat64PrefixManager::Nat64PrefixManager(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mEnabled(false) |
| , mTimer(aInstance) |
| { |
| mInfraIfPrefix.Clear(); |
| mLocalPrefix.Clear(); |
| mPublishedPrefix.Clear(); |
| } |
| |
| void RoutingManager::Nat64PrefixManager::SetEnabled(bool aEnabled) |
| { |
| VerifyOrExit(mEnabled != aEnabled); |
| mEnabled = aEnabled; |
| |
| if (aEnabled) |
| { |
| if (Get<RoutingManager>().IsRunning()) |
| { |
| Start(); |
| } |
| } |
| else |
| { |
| Stop(); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::Nat64PrefixManager::Start(void) |
| { |
| VerifyOrExit(mEnabled); |
| LogInfo("Starting Nat64PrefixManager"); |
| mTimer.Start(0); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::Nat64PrefixManager::Stop(void) |
| { |
| LogInfo("Stopping Nat64PrefixManager"); |
| |
| if (mPublishedPrefix.IsValidNat64()) |
| { |
| IgnoreError(Get<NetworkData::Publisher>().UnpublishPrefix(mPublishedPrefix)); |
| } |
| |
| mPublishedPrefix.Clear(); |
| mInfraIfPrefix.Clear(); |
| mTimer.Stop(); |
| |
| #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE |
| Get<Nat64::Translator>().ClearNat64Prefix(); |
| #endif |
| } |
| |
| void RoutingManager::Nat64PrefixManager::GenerateLocalPrefix(const Ip6::Prefix &aBrUlaPrefix) |
| { |
| mLocalPrefix = aBrUlaPrefix; |
| mLocalPrefix.SetSubnetId(kNat64PrefixSubnetId); |
| mLocalPrefix.mPrefix.mFields.m32[2] = 0; |
| mLocalPrefix.SetLength(kNat64PrefixLength); |
| |
| LogInfo("Generated local NAT64 prefix: %s", mLocalPrefix.ToString().AsCString()); |
| } |
| |
| const Ip6::Prefix &RoutingManager::Nat64PrefixManager::GetFavoredPrefix(RoutePreference &aPreference) const |
| { |
| const Ip6::Prefix *favoredPrefix = &mLocalPrefix; |
| |
| aPreference = NetworkData::kRoutePreferenceLow; |
| |
| if (mInfraIfPrefix.IsValidNat64() && |
| Get<RoutingManager>().mOmrPrefixManager.GetFavoredPrefix().IsInfrastructureDerived()) |
| { |
| favoredPrefix = &mInfraIfPrefix; |
| aPreference = NetworkData::kRoutePreferenceMedium; |
| } |
| |
| return *favoredPrefix; |
| } |
| |
| void RoutingManager::Nat64PrefixManager::Evaluate(void) |
| { |
| Error error; |
| Ip6::Prefix prefix; |
| RoutePreference preference; |
| NetworkData::ExternalRouteConfig netdataPrefixConfig; |
| bool shouldPublish; |
| |
| VerifyOrExit(mEnabled); |
| |
| LogInfo("Evaluating NAT64 prefix"); |
| |
| prefix = GetFavoredPrefix(preference); |
| |
| error = Get<NetworkData::Leader>().GetPreferredNat64Prefix(netdataPrefixConfig); |
| |
| // NAT64 prefix is expected to be published from this BR |
| // when one of the following is true: |
| // |
| // - No NAT64 prefix in Network Data. |
| // - The preferred NAT64 prefix in Network Data has lower |
| // preference than this BR's prefix. |
| // - The preferred NAT64 prefix in Network Data was published |
| // by this BR. |
| // - The preferred NAT64 prefix in Network Data is same as the |
| // discovered infrastructure prefix. |
| // |
| // TODO: change to check RLOC16 to determine if the NAT64 prefix |
| // was published by this BR. |
| |
| shouldPublish = |
| ((error == kErrorNotFound) || (netdataPrefixConfig.mPreference < preference) || |
| (netdataPrefixConfig.GetPrefix() == mPublishedPrefix) || (netdataPrefixConfig.GetPrefix() == mInfraIfPrefix)); |
| |
| if (mPublishedPrefix.IsValidNat64() && (!shouldPublish || (prefix != mPublishedPrefix))) |
| { |
| IgnoreError(Get<NetworkData::Publisher>().UnpublishPrefix(mPublishedPrefix)); |
| mPublishedPrefix.Clear(); |
| } |
| |
| if (shouldPublish && ((prefix != mPublishedPrefix) || (preference != mPublishedPreference))) |
| { |
| mPublishedPrefix = prefix; |
| mPublishedPreference = preference; |
| Publish(); |
| } |
| |
| #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE |
| |
| // If a prefix other than `mLocalPrefix` is present, an external |
| // translator is available. To bypass the NAT64 translator, we |
| // clear its NAT64 prefix. |
| |
| if (mPublishedPrefix == mLocalPrefix) |
| { |
| Get<Nat64::Translator>().SetNat64Prefix(mLocalPrefix); |
| } |
| else |
| { |
| Get<Nat64::Translator>().ClearNat64Prefix(); |
| } |
| #endif |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::Nat64PrefixManager::Publish(void) |
| { |
| NetworkData::ExternalRouteConfig routeConfig; |
| |
| routeConfig.Clear(); |
| routeConfig.SetPrefix(mPublishedPrefix); |
| routeConfig.mPreference = mPublishedPreference; |
| routeConfig.mStable = true; |
| routeConfig.mNat64 = true; |
| |
| SuccessOrAssert( |
| Get<NetworkData::Publisher>().PublishExternalRoute(routeConfig, NetworkData::Publisher::kFromRoutingManager)); |
| } |
| |
| void RoutingManager::Nat64PrefixManager::HandleTimer(void) |
| { |
| OT_ASSERT(mEnabled); |
| |
| Discover(); |
| |
| mTimer.Start(TimeMilli::SecToMsec(kDefaultNat64PrefixLifetime)); |
| LogInfo("NAT64 prefix timer scheduled in %lu seconds", ToUlong(kDefaultNat64PrefixLifetime)); |
| } |
| |
| void RoutingManager::Nat64PrefixManager::Discover(void) |
| { |
| Error error = Get<RoutingManager>().mInfraIf.DiscoverNat64Prefix(); |
| |
| if (error == kErrorNone) |
| { |
| LogInfo("Discovering infraif NAT64 prefix"); |
| } |
| else |
| { |
| LogWarn("Failed to discover infraif NAT64 prefix: %s", ErrorToString(error)); |
| } |
| } |
| |
| void RoutingManager::Nat64PrefixManager::HandleDiscoverDone(const Ip6::Prefix &aPrefix) |
| { |
| mInfraIfPrefix = aPrefix; |
| |
| LogInfo("Infraif NAT64 prefix: %s", mInfraIfPrefix.IsValidNat64() ? mInfraIfPrefix.ToString().AsCString() : "none"); |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); |
| } |
| |
| Nat64::State RoutingManager::Nat64PrefixManager::GetState(void) const |
| { |
| Nat64::State state = Nat64::kStateDisabled; |
| |
| VerifyOrExit(mEnabled); |
| VerifyOrExit(Get<RoutingManager>().IsRunning(), state = Nat64::kStateNotRunning); |
| VerifyOrExit(mPublishedPrefix.IsValidNat64(), state = Nat64::kStateIdle); |
| state = Nat64::kStateActive; |
| |
| exit: |
| return state; |
| } |
| |
| #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // RaInfo |
| |
| void RoutingManager::RaInfo::IncrementTxCountAndSaveHash(const InfraIf::Icmp6Packet &aRaMessage) |
| { |
| mTxCount++; |
| mLastHashIndex++; |
| |
| if (mLastHashIndex == kNumHashEntries) |
| { |
| mLastHashIndex = 0; |
| } |
| |
| CalculateHash(aRaMessage, mHashes[mLastHashIndex]); |
| } |
| |
| bool RoutingManager::RaInfo::IsRaFromManager(const Ip6::Nd::RouterAdvert::RxMessage &aRaMessage) const |
| { |
| // Determines whether or not a received RA message was prepared by |
| // by `RoutingManager` itself (is present in the saved `mHashes`). |
| |
| bool isFromManager = false; |
| uint16_t hashIndex = mLastHashIndex; |
| uint32_t count = Min<uint32_t>(mTxCount, kNumHashEntries); |
| Hash hash; |
| |
| CalculateHash(aRaMessage.GetAsPacket(), hash); |
| |
| for (; count > 0; count--) |
| { |
| if (mHashes[hashIndex] == hash) |
| { |
| isFromManager = true; |
| break; |
| } |
| |
| // Go to the previous index (ring buffer) |
| |
| if (hashIndex == 0) |
| { |
| hashIndex = kNumHashEntries - 1; |
| } |
| else |
| { |
| hashIndex--; |
| } |
| } |
| |
| return isFromManager; |
| } |
| |
| void RoutingManager::RaInfo::CalculateHash(const InfraIf::Icmp6Packet &aRaMessage, Hash &aHash) |
| { |
| Crypto::Sha256 sha256; |
| |
| sha256.Start(); |
| sha256.Update(aRaMessage.GetBytes(), aRaMessage.GetLength()); |
| sha256.Finish(aHash); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // RsSender |
| |
| RoutingManager::RsSender::RsSender(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mTxCount(0) |
| , mTimer(aInstance) |
| { |
| } |
| |
| void RoutingManager::RsSender::Start(void) |
| { |
| uint32_t delay; |
| |
| VerifyOrExit(!IsInProgress()); |
| |
| delay = Random::NonCrypto::GetUint32InRange(0, kMaxStartDelay); |
| |
| LogInfo("RsSender: Starting - will send first RS in %lu msec", ToUlong(delay)); |
| |
| mTxCount = 0; |
| mStartTime = TimerMilli::GetNow(); |
| mTimer.Start(delay); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::RsSender::Stop(void) { mTimer.Stop(); } |
| |
| Error RoutingManager::RsSender::SendRs(void) |
| { |
| Ip6::Address destAddress; |
| RouterSolicitMessage routerSolicit; |
| InfraIf::Icmp6Packet packet; |
| Error error; |
| |
| packet.InitFrom(routerSolicit); |
| destAddress.SetToLinkLocalAllRoutersMulticast(); |
| |
| error = Get<RoutingManager>().mInfraIf.Send(packet, destAddress); |
| |
| if (error == kErrorNone) |
| { |
| Get<Ip6::Ip6>().GetBorderRoutingCounters().mRsTxSuccess++; |
| } |
| else |
| { |
| Get<Ip6::Ip6>().GetBorderRoutingCounters().mRsTxFailure++; |
| } |
| return error; |
| } |
| |
| void RoutingManager::RsSender::HandleTimer(void) |
| { |
| Error error; |
| uint32_t delay; |
| |
| if (mTxCount >= kMaxTxCount) |
| { |
| LogInfo("RsSender: Finished sending RS msgs and waiting for RAs"); |
| Get<RoutingManager>().HandleRsSenderFinished(mStartTime); |
| ExitNow(); |
| } |
| |
| error = SendRs(); |
| |
| if (error == kErrorNone) |
| { |
| mTxCount++; |
| delay = (mTxCount == kMaxTxCount) ? kWaitOnLastAttempt : kTxInterval; |
| LogInfo("RsSender: Sent RS %u/%u", mTxCount, kMaxTxCount); |
| } |
| else |
| { |
| LogCrit("RsSender: Failed to send RS %u/%u: %s", mTxCount + 1, kMaxTxCount, ErrorToString(error)); |
| |
| // Note that `mTxCount` is intentionally not incremented |
| // if the tx fails. |
| delay = kRetryDelay; |
| } |
| |
| mTimer.Start(delay); |
| |
| exit: |
| return; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // PdPrefixManager |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| |
| RoutingManager::PdPrefixManager::PdPrefixManager(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mEnabled(false) |
| , mIsRunning(false) |
| , mNumPlatformPioProcessed(0) |
| , mNumPlatformRaReceived(0) |
| , mLastPlatformRaTime(0) |
| , mTimer(aInstance) |
| { |
| } |
| |
| void RoutingManager::PdPrefixManager::SetEnabled(bool aEnabled) |
| { |
| State oldState = GetState(); |
| |
| VerifyOrExit(mEnabled != aEnabled); |
| mEnabled = aEnabled; |
| EvaluateStateChange(oldState); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::PdPrefixManager::StartStop(bool aStart) |
| { |
| State oldState = GetState(); |
| |
| VerifyOrExit(aStart != mIsRunning); |
| mIsRunning = aStart; |
| EvaluateStateChange(oldState); |
| |
| exit: |
| return; |
| } |
| |
| RoutingManager::PdPrefixManager::State RoutingManager::PdPrefixManager::GetState(void) const |
| { |
| State state = kDhcp6PdStateDisabled; |
| |
| if (mEnabled) |
| { |
| state = mIsRunning ? kDhcp6PdStateRunning : kDhcp6PdStateStopped; |
| } |
| |
| return state; |
| } |
| |
| void RoutingManager::PdPrefixManager::EvaluateStateChange(Dhcp6PdState aOldState) |
| { |
| State newState = GetState(); |
| |
| VerifyOrExit(aOldState != newState); |
| LogInfo("PdPrefixManager: %s -> %s", StateToString(aOldState), StateToString(newState)); |
| |
| switch (newState) |
| { |
| case kDhcp6PdStateDisabled: |
| case kDhcp6PdStateStopped: |
| WithdrawPrefix(); |
| break; |
| case kDhcp6PdStateRunning: |
| break; |
| } |
| |
| mStateCallback.InvokeIfSet(static_cast<otBorderRoutingDhcp6PdState>(newState)); |
| |
| exit: |
| return; |
| } |
| |
| Error RoutingManager::PdPrefixManager::GetPrefixInfo(PrefixTableEntry &aInfo) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsRunning() && HasPrefix(), error = kErrorNotFound); |
| |
| aInfo.mPrefix = mPrefix.GetPrefix(); |
| aInfo.mValidLifetime = mPrefix.GetValidLifetime(); |
| aInfo.mPreferredLifetime = mPrefix.GetPreferredLifetime(); |
| aInfo.mMsecSinceLastUpdate = TimerMilli::GetNow() - mPrefix.GetLastUpdateTime(); |
| |
| exit: |
| return error; |
| } |
| |
| Error RoutingManager::PdPrefixManager::GetProcessedRaInfo(PdProcessedRaInfo &aPdProcessedRaInfo) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(IsRunning() && HasPrefix(), error = kErrorNotFound); |
| |
| aPdProcessedRaInfo.mNumPlatformRaReceived = mNumPlatformRaReceived; |
| aPdProcessedRaInfo.mNumPlatformPioProcessed = mNumPlatformPioProcessed; |
| aPdProcessedRaInfo.mLastPlatformRaMsec = TimerMilli::GetNow() - mLastPlatformRaTime; |
| |
| exit: |
| return error; |
| } |
| |
| void RoutingManager::PdPrefixManager::WithdrawPrefix(void) |
| { |
| VerifyOrExit(HasPrefix()); |
| |
| LogInfo("Withdrew DHCPv6 PD prefix %s", mPrefix.GetPrefix().ToString().AsCString()); |
| |
| mPrefix.Clear(); |
| mTimer.Stop(); |
| |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately); |
| |
| exit: |
| return; |
| } |
| |
| void RoutingManager::PdPrefixManager::ProcessRa(const uint8_t *aRouterAdvert, const uint16_t aLength) |
| { |
| // Processes a Router Advertisement (RA) message received on the |
| // platform's Thread interface. This RA message, generated by |
| // software entities like dnsmasq, radvd, or systemd-networkd, is |
| // part of the DHCPv6 prefix delegation process for distributing |
| // prefixes to interfaces. |
| |
| RouterAdvert::Icmp6Packet packet; |
| |
| packet.Init(aRouterAdvert, aLength); |
| Process(&packet, nullptr); |
| } |
| |
| void RoutingManager::PdPrefixManager::ProcessPrefix(const PrefixTableEntry &aPrefixTableEntry) |
| { |
| // Processes a prefix delegated by a DHCPv6 Prefix Delegation |
| // (PD) server. Similar to `ProcessRa()`, but sets the prefix |
| // directly instead of parsing an RA message. Calling this method |
| // again with new values can update the prefix's lifetime. |
| |
| Process(nullptr, &aPrefixTableEntry); |
| } |
| |
| void RoutingManager::PdPrefixManager::Process(const RouterAdvert::Icmp6Packet *aRaPacket, |
| const PrefixTableEntry *aPrefixTableEntry) |
| { |
| // Processes DHCPv6 Prefix Delegation (PD) prefixes, either from |
| // an RA message or directly set. Requires either `aRaPacket` or |
| // `aPrefixTableEntry` to be non-null. |
| |
| bool currentPrefixUpdated = false; |
| Error error = kErrorNone; |
| PrefixEntry favoredEntry; |
| PrefixEntry entry; |
| |
| VerifyOrExit(mEnabled, error = kErrorInvalidState); |
| |
| if (aRaPacket != nullptr) |
| { |
| RouterAdvert::RxMessage raMsg = RouterAdvert::RxMessage(*aRaPacket); |
| |
| VerifyOrExit(raMsg.IsValid(), error = kErrorParse); |
| |
| for (const Option &option : raMsg) |
| { |
| if (option.GetType() != Option::kTypePrefixInfo || !static_cast<const PrefixInfoOption &>(option).IsValid()) |
| { |
| continue; |
| } |
| |
| mNumPlatformPioProcessed++; |
| entry.SetFrom(static_cast<const PrefixInfoOption &>(option)); |
| currentPrefixUpdated |= ProcessPrefixEntry(entry, favoredEntry); |
| } |
| |
| mNumPlatformRaReceived++; |
| mLastPlatformRaTime = TimerMilli::GetNow(); |
| } |
| else // aPrefixTableEntry != nullptr |
| { |
| entry.SetFrom(*aPrefixTableEntry); |
| currentPrefixUpdated = ProcessPrefixEntry(entry, favoredEntry); |
| } |
| |
| if (currentPrefixUpdated && mPrefix.IsDeprecated()) |
| { |
| LogInfo("DHCPv6 PD prefix %s is deprecated", mPrefix.GetPrefix().ToString().AsCString()); |
| mPrefix.Clear(); |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately); |
| } |
| |
| if (favoredEntry.IsFavoredOver(mPrefix)) |
| { |
| mPrefix = favoredEntry; |
| currentPrefixUpdated = true; |
| LogInfo("DHCPv6 PD prefix set to %s", mPrefix.GetPrefix().ToString().AsCString()); |
| Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately); |
| } |
| |
| if (HasPrefix() && currentPrefixUpdated) |
| { |
| // If the prefix is obtained from an RA message, use |
| // `GetStaleTime()` to apply the minimum `RA_STABLE_TIME`. |
| // Otherwise, calculate it directly from the prefix's |
| // preferred lifetime. |
| |
| mTimer.FireAt((aPrefixTableEntry != nullptr) ? mPrefix.GetStaleTimeFromPreferredLifetime() |
| : mPrefix.GetStaleTime()); |
| } |
| else |
| { |
| mTimer.Stop(); |
| } |
| |
| exit: |
| LogWarnOnError(error, "process DHCPv6 delegated prefix"); |
| OT_UNUSED_VARIABLE(error); |
| } |
| |
| bool RoutingManager::PdPrefixManager::ProcessPrefixEntry(PrefixEntry &aEntry, PrefixEntry &aFavoredEntry) |
| { |
| bool currentPrefixUpdated = false; |
| |
| if (!aEntry.IsValidPdPrefix()) |
| { |
| LogWarn("Ignore invalid DHCPv6 PD prefix %s", aEntry.GetPrefix().ToString().AsCString()); |
| ExitNow(); |
| } |
| |
| aEntry.mPrefix.Tidy(); |
| aEntry.mPrefix.SetLength(kOmrPrefixLength); |
| |
| // Check if there is an update to the current prefix. The valid or |
| // preferred lifetime may have changed. |
| |
| if (HasPrefix() && (mPrefix.GetPrefix() == aEntry.GetPrefix())) |
| { |
| currentPrefixUpdated = true; |
| mPrefix = aEntry; |
| } |
| |
| VerifyOrExit(!aEntry.IsDeprecated()); |
| |
| // Some platforms may delegate multiple prefixes. We'll select the |
| // smallest one, as GUA prefixes (`2000::/3`) are inherently |
| // smaller than ULA prefixes (`fc00::/7`). This rule prefers GUA |
| // prefixes over ULA. |
| |
| if (aEntry.IsFavoredOver(aFavoredEntry)) |
| { |
| aFavoredEntry = aEntry; |
| } |
| |
| exit: |
| return currentPrefixUpdated; |
| } |
| |
| bool RoutingManager::PdPrefixManager::PrefixEntry::IsValidPdPrefix(void) const |
| { |
| // We should accept ULA prefix since it could be used by the internet infrastructure like NAT64. |
| |
| return !IsEmpty() && (GetPrefix().GetLength() <= kOmrPrefixLength) && !GetPrefix().IsLinkLocal() && |
| !GetPrefix().IsMulticast(); |
| } |
| |
| bool RoutingManager::PdPrefixManager::PrefixEntry::IsFavoredOver(const PrefixEntry &aOther) const |
| { |
| bool isFavored; |
| |
| if (IsEmpty()) |
| { |
| // Empty prefix is not favored over any (including another |
| // empty prefix). |
| isFavored = false; |
| ExitNow(); |
| } |
| |
| if (aOther.IsEmpty()) |
| { |
| // A non-empty prefix is favored over an empty one. |
| isFavored = true; |
| ExitNow(); |
| } |
| |
| // Numerically smaller prefix is favored. |
| |
| isFavored = GetPrefix() < aOther.GetPrefix(); |
| |
| exit: |
| return isFavored; |
| } |
| |
| const char *RoutingManager::PdPrefixManager::StateToString(State aState) |
| { |
| static const char *const kStateStrings[] = { |
| "Disabled", // (0) kDisabled |
| "Stopped", // (1) kStopped |
| "Running", // (2) kRunning |
| }; |
| |
| static_assert(0 == kDhcp6PdStateDisabled, "kDhcp6PdStateDisabled value is incorrect"); |
| static_assert(1 == kDhcp6PdStateStopped, "kDhcp6PdStateStopped value is incorrect"); |
| static_assert(2 == kDhcp6PdStateRunning, "kDhcp6PdStateRunning value is incorrect"); |
| |
| return kStateStrings[aState]; |
| } |
| |
| extern "C" void otPlatBorderRoutingProcessIcmp6Ra(otInstance *aInstance, const uint8_t *aMessage, uint16_t aLength) |
| { |
| AsCoreType(aInstance).Get<BorderRouter::RoutingManager>().mPdPrefixManager.ProcessRa(aMessage, aLength); |
| } |
| |
| extern "C" void otPlatBorderRoutingProcessDhcp6PdPrefix(otInstance *aInstance, |
| const otBorderRoutingPrefixTableEntry *aPrefixInfo) |
| { |
| AssertPointerIsNotNull(aPrefixInfo); |
| |
| AsCoreType(aInstance).Get<BorderRouter::RoutingManager>().mPdPrefixManager.ProcessPrefix(*aPrefixInfo); |
| } |
| #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE |
| |
| } // namespace BorderRouter |
| |
| } // namespace ot |
| |
| #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE |