blob: 84db52d54f6756da566d8c60ba01a32ca520187b [file] [log] [blame]
/*
* 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