openthread/src/core/border_router/routing_manager.cpp

1682 lines
51 KiB
C++

/*
* 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/platform/infra_if.h>
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "common/random.hpp"
#include "common/settings.hpp"
#include "meshcop/extended_panid.hpp"
#include "net/ip6.hpp"
#include "thread/network_data_leader.hpp"
#include "thread/network_data_local.hpp"
#include "thread/network_data_notifier.hpp"
namespace ot {
namespace BorderRouter {
RegisterLogModule("BorderRouter");
RoutingManager::RoutingManager(Instance &aInstance)
: InstanceLocator(aInstance)
, mIsRunning(false)
, mIsEnabled(false)
, mInfraIf(aInstance)
, mIsAdvertisingLocalOnLinkPrefix(false)
, mOnLinkPrefixDeprecateTimer(aInstance, HandleOnLinkPrefixDeprecateTimer)
, mIsAdvertisingLocalNat64Prefix(false)
, mDiscoveredPrefixTable(aInstance)
, mTimeRouterAdvMessageLastUpdate(TimerMilli::GetNow())
, mLearntRouterAdvMessageFromHost(false)
, mDiscoveredPrefixStaleTimer(aInstance, HandleDiscoveredPrefixStaleTimer)
, mRouterAdvertisementCount(0)
, mLastRouterAdvertisementSendTime(TimerMilli::GetNow() - kMinDelayBetweenRtrAdvs)
, mRouterSolicitTimer(aInstance, HandleRouterSolicitTimer)
, mRouterSolicitCount(0)
, mRoutingPolicyTimer(aInstance, HandleRoutingPolicyTimer)
{
mFavoredDiscoveredOnLinkPrefix.Clear();
mBrUlaPrefix.Clear();
mLocalOmrPrefix.Clear();
mLocalOnLinkPrefix.Clear();
mLocalNat64Prefix.Clear();
}
Error RoutingManager::Init(uint32_t aInfraIfIndex, bool aInfraIfIsRunning)
{
Error error;
SuccessOrExit(error = mInfraIf.Init(aInfraIfIndex));
SuccessOrExit(error = LoadOrGenerateRandomBrUlaPrefix());
GenerateOmrPrefix();
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
GenerateNat64Prefix();
#endif
GenerateOnLinkPrefix();
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;
EvaluateState();
exit:
return error;
}
Error RoutingManager::GetOmrPrefix(Ip6::Prefix &aPrefix)
{
Error error = kErrorNone;
VerifyOrExit(IsInitialized(), error = kErrorInvalidState);
aPrefix = mLocalOmrPrefix;
exit:
return error;
}
Error RoutingManager::GetOnLinkPrefix(Ip6::Prefix &aPrefix)
{
Error error = kErrorNone;
VerifyOrExit(IsInitialized(), error = kErrorInvalidState);
aPrefix = mLocalOnLinkPrefix;
exit:
return error;
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
Error RoutingManager::GetNat64Prefix(Ip6::Prefix &aPrefix)
{
Error error = kErrorNone;
VerifyOrExit(IsInitialized(), error = kErrorInvalidState);
aPrefix = mLocalNat64Prefix;
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::GenerateOmrPrefix(void)
{
mLocalOmrPrefix = mBrUlaPrefix;
mLocalOmrPrefix.SetSubnetId(kOmrPrefixSubnetId);
mLocalOmrPrefix.SetLength(kOmrPrefixLength);
LogInfo("Generated OMR prefix: %s", mLocalOmrPrefix.ToString().AsCString());
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
void RoutingManager::GenerateNat64Prefix(void)
{
mLocalNat64Prefix = mBrUlaPrefix;
mLocalNat64Prefix.SetSubnetId(kNat64PrefixSubnetId);
mLocalNat64Prefix.mPrefix.mFields.m32[2] = 0;
mLocalNat64Prefix.SetLength(kNat64PrefixLength);
LogInfo("Generated NAT64 prefix: %s", mLocalNat64Prefix.ToString().AsCString());
}
#endif
void RoutingManager::GenerateOnLinkPrefix(void)
{
MeshCoP::ExtendedPanId extPanId = Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId();
mLocalOnLinkPrefix.mPrefix.mFields.m8[0] = 0xfd;
// Global ID: 40 most significant bits of Extended PAN ID
memcpy(mLocalOnLinkPrefix.mPrefix.mFields.m8 + 1, extPanId.m8, 5);
// Subnet ID: 16 least significant bits of Extended PAN ID
memcpy(mLocalOnLinkPrefix.mPrefix.mFields.m8 + 6, extPanId.m8 + 6, 2);
mLocalOnLinkPrefix.SetLength(kOnLinkPrefixLength);
LogNote("Local on-link prefix: %s", mLocalOnLinkPrefix.ToString().AsCString());
}
void RoutingManager::EvaluateState(void)
{
if (mIsEnabled && Get<Mle::MleRouter>().IsAttached() && mInfraIf.IsRunning())
{
Start();
}
else
{
Stop();
}
}
void RoutingManager::Start(void)
{
if (!mIsRunning)
{
LogInfo("Border Routing manager started");
mIsRunning = true;
StartRouterSolicitationDelay();
}
}
void RoutingManager::Stop(void)
{
VerifyOrExit(mIsRunning);
UnpublishLocalOmrPrefix();
mFavoredDiscoveredOnLinkPrefix.Clear();
if (mIsAdvertisingLocalOnLinkPrefix)
{
UnpublishExternalRoute(mLocalOnLinkPrefix);
// Start deprecating the local on-link prefix to send a PIO
// with zero preferred lifetime in `SendRouterAdvertisement`.
DeprecateOnLinkPrefix();
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
if (mIsAdvertisingLocalNat64Prefix)
{
UnpublishExternalRoute(mLocalNat64Prefix);
mIsAdvertisingLocalNat64Prefix = false;
}
#endif
// Use empty OMR & on-link prefixes to invalidate possible advertised prefixes.
SendRouterAdvertisement(OmrPrefixArray());
mAdvertisedOmrPrefixes.Clear();
mOnLinkPrefixDeprecateTimer.Stop();
mDiscoveredPrefixTable.RemoveAllEntries();
mDiscoveredPrefixStaleTimer.Stop();
mRouterAdvertisementCount = 0;
mRouterSolicitTimer.Stop();
mRouterSolicitCount = 0;
mRoutingPolicyTimer.Stop();
LogInfo("Border Routing manager stopped");
mIsRunning = false;
exit:
return;
}
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;
default:
break;
}
exit:
return;
}
void RoutingManager::HandleNotifierEvents(Events aEvents)
{
VerifyOrExit(IsInitialized() && IsEnabled());
if (aEvents.Contains(kEventThreadRoleChanged))
{
EvaluateState();
}
if (mIsRunning && aEvents.Contains(kEventThreadNetdataChanged))
{
// Remove all OMR prefixes in Network Data from the
// discovered prefix table.
NetworkData::Iterator iterator = NetworkData::kIteratorInit;
NetworkData::OnMeshPrefixConfig prefixConfig;
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone)
{
if (!IsValidOmrPrefix(prefixConfig))
{
continue;
}
mDiscoveredPrefixTable.RemoveRoutePrefix(prefixConfig.GetPrefix(),
DiscoveredPrefixTable::kUnpublishFromNetData);
}
StartRoutingPolicyEvaluationJitter(kRoutingPolicyEvaluationJitter);
}
if (aEvents.Contains(kEventThreadExtPanIdChanged))
{
if (mIsAdvertisingLocalOnLinkPrefix)
{
UnpublishExternalRoute(mLocalOnLinkPrefix);
// TODO: consider deprecating/invalidating existing
// on-link prefix
mIsAdvertisingLocalOnLinkPrefix = false;
}
GenerateOnLinkPrefix();
if (mIsRunning)
{
StartRoutingPolicyEvaluationJitter(kRoutingPolicyEvaluationJitter);
}
}
exit:
return;
}
void RoutingManager::EvaluateOmrPrefix(OmrPrefixArray &aNewOmrPrefixes)
{
NetworkData::Iterator iterator = NetworkData::kIteratorInit;
NetworkData::OnMeshPrefixConfig onMeshPrefixConfig;
OmrPrefix * favoredOmrEntry = nullptr;
OmrPrefix * localOmrEntry = nullptr;
OT_ASSERT(mIsRunning);
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, onMeshPrefixConfig) == kErrorNone)
{
OmrPrefix *entry;
if (!IsValidOmrPrefix(onMeshPrefixConfig))
{
continue;
}
entry = aNewOmrPrefixes.FindMatching(onMeshPrefixConfig.GetPrefix());
if (entry != nullptr)
{
// Update the entry if we find the same prefix with higher
// preference in network data
if (onMeshPrefixConfig.GetPreference() <= entry->GetPreference())
{
continue;
}
entry->SetPreference(onMeshPrefixConfig.GetPreference());
}
else
{
entry = aNewOmrPrefixes.PushBack();
if (entry == nullptr)
{
LogWarn("EvaluateOmrPrefix: Too many OMR prefixes, ignoring prefix %s",
onMeshPrefixConfig.GetPrefix().ToString().AsCString());
continue;
}
entry->InitFrom(onMeshPrefixConfig);
}
if (onMeshPrefixConfig.mPreferred)
{
if ((favoredOmrEntry == nullptr) || (entry->IsFavoredOver(*favoredOmrEntry)))
{
favoredOmrEntry = entry;
}
}
if (entry->GetPrefix() == mLocalOmrPrefix)
{
localOmrEntry = entry;
}
}
// Decide if we need to add or remove our local OMR prefix.
if (favoredOmrEntry == nullptr)
{
LogInfo("EvaluateOmrPrefix: No preferred OMR prefixes found in Thread network");
// The `aNewOmrPrefixes` remains empty if we fail to publish
// the local OMR prefix.
SuccessOrExit(PublishLocalOmrPrefix());
localOmrEntry = aNewOmrPrefixes.PushBack();
VerifyOrExit(localOmrEntry != nullptr);
localOmrEntry->Init(mLocalOmrPrefix, NetworkData::kRoutePreferenceLow);
}
else if (favoredOmrEntry == localOmrEntry)
{
IgnoreError(PublishLocalOmrPrefix());
}
else if (IsOmrPrefixAddedToLocalNetworkData())
{
LogInfo("EvaluateOmrPrefix: There is already a preferred OMR prefix %s in the Thread network",
favoredOmrEntry->ToString().AsCString());
UnpublishLocalOmrPrefix();
if (localOmrEntry != nullptr)
{
// Remove the local OMR prefix from the list by overwriting it
// with popped last entry in the list.
*localOmrEntry = *aNewOmrPrefixes.PopBack();
}
}
exit:
return;
}
Error RoutingManager::PublishLocalOmrPrefix(void)
{
Error error = kErrorNone;
NetworkData::OnMeshPrefixConfig omrPrefixConfig;
OT_ASSERT(mIsRunning);
VerifyOrExit(!IsOmrPrefixAddedToLocalNetworkData());
omrPrefixConfig.Clear();
omrPrefixConfig.mPrefix = mLocalOmrPrefix;
omrPrefixConfig.mStable = true;
omrPrefixConfig.mSlaac = true;
omrPrefixConfig.mPreferred = true;
omrPrefixConfig.mOnMesh = true;
omrPrefixConfig.mDefaultRoute = false;
omrPrefixConfig.mPreference = NetworkData::kRoutePreferenceLow;
error = Get<NetworkData::Local>().AddOnMeshPrefix(omrPrefixConfig);
if (error != kErrorNone)
{
LogWarn("Failed to publish local OMR prefix %s in Thread network: %s", mLocalOmrPrefix.ToString().AsCString(),
ErrorToString(error));
}
else
{
Get<NetworkData::Notifier>().HandleServerDataUpdated();
LogInfo("Publishing local OMR prefix %s in Thread network", mLocalOmrPrefix.ToString().AsCString());
}
exit:
return error;
}
void RoutingManager::UnpublishLocalOmrPrefix(void)
{
Error error = kErrorNone;
VerifyOrExit(mIsRunning);
VerifyOrExit(IsOmrPrefixAddedToLocalNetworkData());
SuccessOrExit(error = Get<NetworkData::Local>().RemoveOnMeshPrefix(mLocalOmrPrefix));
Get<NetworkData::Notifier>().HandleServerDataUpdated();
LogInfo("Unpublishing local OMR prefix %s from Thread network", mLocalOmrPrefix.ToString().AsCString());
exit:
if (error != kErrorNone && error != kErrorNotFound)
{
LogWarn("Failed to unpublish local OMR prefix %s from Thread network: %s",
mLocalOmrPrefix.ToString().AsCString(), ErrorToString(error));
}
}
bool RoutingManager::IsOmrPrefixAddedToLocalNetworkData(void) const
{
return Get<NetworkData::Local>().ContainsOnMeshPrefix(mLocalOmrPrefix);
}
Error RoutingManager::PublishExternalRoute(const Ip6::Prefix &aPrefix, RoutePreference aRoutePreference, bool aNat64)
{
Error error;
NetworkData::ExternalRouteConfig routeConfig;
OT_ASSERT(mIsRunning);
routeConfig.Clear();
routeConfig.SetPrefix(aPrefix);
routeConfig.mStable = true;
routeConfig.mNat64 = aNat64;
routeConfig.mPreference = aRoutePreference;
error = Get<NetworkData::Publisher>().PublishExternalRoute(routeConfig);
if (error != kErrorNone)
{
LogWarn("Failed to publish external route %s: %s", aPrefix.ToString().AsCString(), ErrorToString(error));
}
return (error == kErrorAlready) ? kErrorNone : error;
}
void RoutingManager::UnpublishExternalRoute(const Ip6::Prefix &aPrefix)
{
Error error = kErrorNone;
VerifyOrExit(mIsRunning);
error = Get<NetworkData::Publisher>().UnpublishPrefix(aPrefix);
if (error != kErrorNone)
{
LogWarn("Failed to unpublish route %s: %s", aPrefix.ToString().AsCString(), ErrorToString(error));
}
exit:
return;
}
void RoutingManager::EvaluateOnLinkPrefix(void)
{
VerifyOrExit(!IsRouterSolicitationInProgress());
mDiscoveredPrefixTable.FindFavoredOnLinkPrefix(mFavoredDiscoveredOnLinkPrefix);
if (mFavoredDiscoveredOnLinkPrefix.GetLength() == 0)
{
// We need to advertise our local on-link prefix since there is
// no discovered on-link prefix.
mOnLinkPrefixDeprecateTimer.Stop();
VerifyOrExit(!mIsAdvertisingLocalOnLinkPrefix);
SuccessOrExit(PublishExternalRoute(mLocalOnLinkPrefix, NetworkData::kRoutePreferenceMedium));
mIsAdvertisingLocalOnLinkPrefix = true;
LogInfo("Start advertising on-link prefix %s on %s", mLocalOnLinkPrefix.ToString().AsCString(),
mInfraIf.ToString().AsCString());
// We remove the local on-link prefix from discovered prefix
// table, in case it was previously discovered and included in
// the table (now as a deprecating entry). We remove it with
// `kKeepInNetData` flag to ensure that the prefix is not
// unpublished from network data.
//
// Note that `ShouldProcessPrefixInfoOption()` will also check
// not allow the local on-link prefix to be added in the prefix
// table while we are advertising it.
mDiscoveredPrefixTable.RemoveOnLinkPrefix(mLocalOnLinkPrefix, DiscoveredPrefixTable::kKeepInNetData);
}
else
{
VerifyOrExit(mIsAdvertisingLocalOnLinkPrefix);
// 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 (!(mLocalOnLinkPrefix < mFavoredDiscoveredOnLinkPrefix))
{
LogInfo("EvaluateOnLinkPrefix: There is already favored on-link prefix %s on %s",
mFavoredDiscoveredOnLinkPrefix.ToString().AsCString(), mInfraIf.ToString().AsCString());
DeprecateOnLinkPrefix();
}
}
exit:
return;
}
void RoutingManager::HandleOnLinkPrefixDeprecateTimer(Timer &aTimer)
{
aTimer.Get<RoutingManager>().HandleOnLinkPrefixDeprecateTimer();
}
void RoutingManager::HandleOnLinkPrefixDeprecateTimer(void)
{
OT_ASSERT(!mIsAdvertisingLocalOnLinkPrefix);
LogInfo("Local on-link prefix %s expired", mLocalOnLinkPrefix.ToString().AsCString());
if (!mDiscoveredPrefixTable.ContainsOnLinkPrefix(mLocalOnLinkPrefix))
{
UnpublishExternalRoute(mLocalOnLinkPrefix);
}
}
void RoutingManager::DeprecateOnLinkPrefix(void)
{
OT_ASSERT(mIsAdvertisingLocalOnLinkPrefix);
mIsAdvertisingLocalOnLinkPrefix = false;
LogInfo("Deprecate local on-link prefix %s", mLocalOnLinkPrefix.ToString().AsCString());
mOnLinkPrefixDeprecateTimer.StartAt(mTimeAdvertisedOnLinkPrefix,
TimeMilli::SecToMsec(kDefaultOnLinkPrefixLifetime));
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
void RoutingManager::EvaluateNat64Prefix(void)
{
OT_ASSERT(mIsRunning);
NetworkData::Iterator iterator = NetworkData::kIteratorInit;
NetworkData::ExternalRouteConfig config;
Ip6::Prefix smallestNat64Prefix;
LogInfo("Evaluating NAT64 prefix");
smallestNat64Prefix.Clear();
while (Get<NetworkData::Leader>().GetNextExternalRoute(iterator, config) == kErrorNone)
{
const Ip6::Prefix &prefix = config.GetPrefix();
if (config.mNat64 && prefix.IsValidNat64())
{
if (smallestNat64Prefix.GetLength() == 0 || prefix < smallestNat64Prefix)
{
smallestNat64Prefix = prefix;
}
}
}
if (smallestNat64Prefix.GetLength() == 0 || smallestNat64Prefix == mLocalNat64Prefix)
{
LogInfo("No NAT64 prefix in Network Data is smaller than the local NAT64 prefix %s",
mLocalNat64Prefix.ToString().AsCString());
// Advertise local NAT64 prefix.
if (!mIsAdvertisingLocalNat64Prefix &&
PublishExternalRoute(mLocalNat64Prefix, NetworkData::kRoutePreferenceLow, /* aNat64= */ true) == kErrorNone)
{
mIsAdvertisingLocalNat64Prefix = true;
}
}
else if (mIsAdvertisingLocalNat64Prefix && smallestNat64Prefix < mLocalNat64Prefix)
{
// Withdraw local NAT64 prefix if it's not the smallest one in Network Data.
// TODO: remove the prefix with lower preference after discovering upstream NAT64 prefix is supported
LogNote("Withdrawing local NAT64 prefix since a smaller one %s exists.",
smallestNat64Prefix.ToString().AsCString());
UnpublishExternalRoute(mLocalNat64Prefix);
mIsAdvertisingLocalNat64Prefix = false;
}
}
#endif
// This method evaluate the routing policy depends on prefix and route
// information on Thread Network and infra link. As a result, this
// method May send RA messages on infra link and publish/unpublish
// OMR and NAT64 prefix in the Thread network.
void RoutingManager::EvaluateRoutingPolicy(void)
{
OT_ASSERT(mIsRunning);
OmrPrefixArray newOmrPrefixes;
LogInfo("Evaluating routing policy");
// 0. Evaluate on-link, OMR and NAT64 prefixes.
EvaluateOnLinkPrefix();
EvaluateOmrPrefix(newOmrPrefixes);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
EvaluateNat64Prefix();
#endif
// 1. Send Router Advertisement message if necessary.
SendRouterAdvertisement(newOmrPrefixes);
if (newOmrPrefixes.IsEmpty())
{
// This is the very exceptional case and happens only when we failed to publish
// our local OMR prefix to the Thread network. We schedule the routing policy
// timer to re-evaluate our routing policy in the future.
LogWarn("No OMR prefix advertised! Start Routing Policy timer for future evaluation");
}
// 2. Schedule routing policy timer with random interval for the next Router Advertisement.
{
uint32_t nextSendDelay;
nextSendDelay = Random::NonCrypto::GetUint32InRange(kMinRtrAdvInterval, kMaxRtrAdvInterval);
if (mRouterAdvertisementCount <= kMaxInitRtrAdvertisements && nextSendDelay > kMaxInitRtrAdvInterval)
{
nextSendDelay = kMaxInitRtrAdvInterval;
}
StartRoutingPolicyEvaluationDelay(Time::SecToMsec(nextSendDelay));
}
// 3. Update OMR prefixes information.
mAdvertisedOmrPrefixes = newOmrPrefixes;
}
void RoutingManager::StartRoutingPolicyEvaluationJitter(uint32_t aJitterMilli)
{
OT_ASSERT(mIsRunning);
StartRoutingPolicyEvaluationDelay(Random::NonCrypto::GetUint32InRange(0, aJitterMilli));
}
void RoutingManager::StartRoutingPolicyEvaluationDelay(uint32_t aDelayMilli)
{
TimeMilli now = TimerMilli::GetNow();
TimeMilli evaluateTime = now + aDelayMilli;
TimeMilli earlestTime = mLastRouterAdvertisementSendTime + kMinDelayBetweenRtrAdvs;
evaluateTime = OT_MAX(evaluateTime, earlestTime);
LogInfo("Start evaluating routing policy, scheduled in %u milliseconds", evaluateTime - now);
mRoutingPolicyTimer.FireAtIfEarlier(evaluateTime);
}
// starts sending Router Solicitations in random delay
// between 0 and kMaxRtrSolicitationDelay.
void RoutingManager::StartRouterSolicitationDelay(void)
{
uint32_t randomDelay;
VerifyOrExit(!IsRouterSolicitationInProgress());
OT_ASSERT(mRouterSolicitCount == 0);
static_assert(kMaxRtrSolicitationDelay > 0, "invalid maximum Router Solicitation delay");
randomDelay = Random::NonCrypto::GetUint32InRange(0, Time::SecToMsec(kMaxRtrSolicitationDelay));
LogInfo("Start Router Solicitation, scheduled in %u milliseconds", randomDelay);
mTimeRouterSolicitStart = TimerMilli::GetNow();
mRouterSolicitTimer.Start(randomDelay);
exit:
return;
}
bool RoutingManager::IsRouterSolicitationInProgress(void) const
{
return mRouterSolicitTimer.IsRunning() || mRouterSolicitCount > 0;
}
Error RoutingManager::SendRouterSolicitation(void)
{
Ip6::Address destAddress;
Ip6::Nd::RouterSolicitMessage routerSolicit;
InfraIf::Icmp6Packet packet;
OT_ASSERT(IsInitialized());
packet.InitFrom(routerSolicit);
destAddress.SetToLinkLocalAllRoutersMulticast();
return mInfraIf.Send(packet, destAddress);
}
void RoutingManager::SendRouterAdvertisement(const OmrPrefixArray &aNewOmrPrefixes)
{
uint8_t buffer[kMaxRouterAdvMessageLength];
Ip6::Nd::RouterAdvertMessage raMsg(mRouterAdvertHeader, buffer);
// Append PIO for local on-link prefix. Ensure it is either being
// advertised or deprecated.
if (mIsAdvertisingLocalOnLinkPrefix || mOnLinkPrefixDeprecateTimer.IsRunning())
{
uint32_t validLifetime = kDefaultOnLinkPrefixLifetime;
uint32_t preferredLifetime = kDefaultOnLinkPrefixLifetime;
if (mOnLinkPrefixDeprecateTimer.IsRunning())
{
validLifetime = TimeMilli::MsecToSec(mOnLinkPrefixDeprecateTimer.GetFireTime() - TimerMilli::GetNow());
preferredLifetime = 0;
}
SuccessOrAssert(raMsg.AppendPrefixInfoOption(mLocalOnLinkPrefix, validLifetime, preferredLifetime));
if (mIsAdvertisingLocalOnLinkPrefix)
{
mTimeAdvertisedOnLinkPrefix = TimerMilli::GetNow();
}
LogInfo("RouterAdvert: Added PIO for %s (valid=%u, preferred=%u)", mLocalOnLinkPrefix.ToString().AsCString(),
validLifetime, preferredLifetime);
}
// Invalidate previously advertised OMR prefixes if they are no
// longer in the new OMR prefix array.
for (const OmrPrefix &omrPrefix : mAdvertisedOmrPrefixes)
{
if (!aNewOmrPrefixes.ContainsMatching(omrPrefix.GetPrefix()))
{
SuccessOrAssert(
raMsg.AppendRouteInfoOption(omrPrefix.GetPrefix(), /* aRouteLifetime */ 0, omrPrefix.GetPreference()));
LogInfo("RouterAdvert: Added RIO for %s (lifetime=0)", omrPrefix.ToString().AsCString());
}
}
for (const OmrPrefix &omrPrefix : aNewOmrPrefixes)
{
SuccessOrAssert(
raMsg.AppendRouteInfoOption(omrPrefix.GetPrefix(), kDefaultOmrPrefixLifetime, omrPrefix.GetPreference()));
LogInfo("RouterAdvert: Added RIO for %s (lifetime=%u)", omrPrefix.ToString().AsCString(),
kDefaultOmrPrefixLifetime);
}
if (raMsg.ContainsAnyOptions())
{
Error error;
Ip6::Address destAddress;
++mRouterAdvertisementCount;
destAddress.SetToLinkLocalAllNodesMulticast();
error = mInfraIf.Send(raMsg.GetAsPacket(), destAddress);
if (error == kErrorNone)
{
mLastRouterAdvertisementSendTime = TimerMilli::GetNow();
LogInfo("Sent Router Advertisement on %s", mInfraIf.ToString().AsCString());
DumpDebg("[BR-CERT] direction=send | type=RA |", raMsg.GetAsPacket().GetBytes(),
raMsg.GetAsPacket().GetLength());
}
else
{
LogWarn("Failed to send Router Advertisement 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 && !aOnMeshPrefixConfig.mDp;
}
bool RoutingManager::IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix)
{
// Accept ULA prefix with length of 64 bits and GUA prefix.
return (aOmrPrefix.mLength == kOmrPrefixLength && aOmrPrefix.mPrefix.mFields.m8[0] == 0xfd) ||
(aOmrPrefix.mLength >= 3 && (aOmrPrefix.GetBytes()[0] & 0xE0) == 0x20);
}
bool RoutingManager::IsValidOnLinkPrefix(const Ip6::Nd::PrefixInfoOption &aPio)
{
Ip6::Prefix prefix;
aPio.GetPrefix(prefix);
return IsValidOnLinkPrefix(prefix) && aPio.IsOnLinkFlagSet() && aPio.IsAutoAddrConfigFlagSet();
}
bool RoutingManager::IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix)
{
return aOnLinkPrefix.IsValid() && (aOnLinkPrefix.GetLength() > 0) && !aOnLinkPrefix.IsLinkLocal() &&
!aOnLinkPrefix.IsMulticast();
}
void RoutingManager::HandleRouterSolicitTimer(Timer &aTimer)
{
aTimer.Get<RoutingManager>().HandleRouterSolicitTimer();
}
void RoutingManager::HandleRouterSolicitTimer(void)
{
LogInfo("Router solicitation times out");
if (mRouterSolicitCount < kMaxRtrSolicitations)
{
uint32_t nextSolicitationDelay;
Error error;
error = SendRouterSolicitation();
if (error == kErrorNone)
{
LogDebg("Successfully sent %uth Router Solicitation", mRouterSolicitCount);
++mRouterSolicitCount;
nextSolicitationDelay =
(mRouterSolicitCount == kMaxRtrSolicitations) ? kMaxRtrSolicitationDelay : kRtrSolicitationInterval;
}
else
{
LogCrit("Failed to send %uth Router Solicitation: %s", mRouterSolicitCount, ErrorToString(error));
// It's unexpected that RS will fail and we will retry sending RS messages in 60 seconds.
// Notice that `mRouterSolicitCount` is not incremented for failed RS and thus we will
// not start configuring on-link prefixes before `kMaxRtrSolicitations` successful RS
// messages have been sent.
nextSolicitationDelay = kRtrSolicitationRetryDelay;
mRouterSolicitCount = 0;
}
LogDebg("Router solicitation timer scheduled in %u seconds", nextSolicitationDelay);
mRouterSolicitTimer.Start(Time::SecToMsec(nextSolicitationDelay));
}
else
{
// Remove route prefixes and deprecate on-link prefixes that
// are not refreshed during Router Solicitation.
mDiscoveredPrefixTable.RemoveOrDeprecateOldEntries(mTimeRouterSolicitStart);
// Invalidate the learned RA message if it is not refreshed during Router Solicitation.
if (mTimeRouterAdvMessageLastUpdate <= mTimeRouterSolicitStart)
{
UpdateRouterAdvertHeader(/* aRouterAdvertMessage */ nullptr);
}
mRouterSolicitCount = 0;
// Re-evaluate our routing policy and send Router Advertisement if necessary.
StartRoutingPolicyEvaluationDelay(/* aDelayJitter */ 0);
}
}
void RoutingManager::HandleDiscoveredPrefixStaleTimer(Timer &aTimer)
{
aTimer.Get<RoutingManager>().HandleDiscoveredPrefixStaleTimer();
}
void RoutingManager::HandleDiscoveredPrefixStaleTimer(void)
{
LogInfo("Stale On-Link or OMR Prefixes or RA messages are detected");
StartRouterSolicitationDelay();
}
void RoutingManager::HandleRoutingPolicyTimer(Timer &aTimer)
{
aTimer.Get<RoutingManager>().EvaluateRoutingPolicy();
}
void RoutingManager::HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress)
{
OT_UNUSED_VARIABLE(aPacket);
OT_UNUSED_VARIABLE(aSrcAddress);
LogInfo("Received Router Solicitation from %s on %s", aSrcAddress.ToString().AsCString(),
mInfraIf.ToString().AsCString());
// Schedule routing policy evaluation with random jitter to respond with Router Advertisement.
StartRoutingPolicyEvaluationJitter(kRaReplyJitter);
}
void RoutingManager::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress)
{
Ip6::Nd::RouterAdvertMessage routerAdvMessage(aPacket);
OT_ASSERT(mIsRunning);
VerifyOrExit(routerAdvMessage.IsValid());
LogInfo("Received Router Advertisement 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 Ip6::Nd::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("Ignore invalid on-link prefix in PIO: %s", aPrefix.ToString().AsCString());
ExitNow();
}
if (mIsAdvertisingLocalOnLinkPrefix)
{
VerifyOrExit(aPrefix != mLocalOnLinkPrefix);
}
shouldProcess = true;
exit:
return shouldProcess;
}
bool RoutingManager::ShouldProcessRouteInfoOption(const Ip6::Nd::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 (!IsValidOmrPrefix(aPrefix))
{
LogInfo("Ignore RIO prefix %s since not a valid OMR prefix", aPrefix.ToString().AsCString());
ExitNow();
}
VerifyOrExit(mLocalOmrPrefix != aPrefix);
// Ignore OMR prefixes advertised by ourselves or in current Thread Network Data.
// The `mAdvertisedOmrPrefixes` and the OMR prefix set in Network Data should eventually
// be equal, but there is time that they are not synchronized immediately:
// 1. Network Data could contain more OMR prefixes than `mAdvertisedOmrPrefixes` because
// we added random delay before Evaluating routing policy when Network Data is changed.
// 2. `mAdvertisedOmrPrefixes` could contain more OMR prefixes than Network Data because
// it takes time to sync a new OMR prefix into Network Data (multicast loopback RA
// messages are usually faster than Thread Network Data propagation).
// They are the reasons why we need both the checks.
VerifyOrExit(!mAdvertisedOmrPrefixes.ContainsMatching(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. If the favored on-link
// prefix has changed, we trigger a re-evaluation of the routing
// policy.
Ip6::Prefix newFavoredPrefix;
VerifyOrExit(mIsRunning);
ResetDiscoveredPrefixStaleTimer();
mDiscoveredPrefixTable.FindFavoredOnLinkPrefix(newFavoredPrefix);
if (newFavoredPrefix != mFavoredDiscoveredOnLinkPrefix)
{
StartRoutingPolicyEvaluationJitter(kRoutingPolicyEvaluationJitter);
}
exit:
return;
}
bool RoutingManager::NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const
{
NetworkData::Iterator iterator = NetworkData::kIteratorInit;
NetworkData::OnMeshPrefixConfig onMeshPrefixConfig;
bool contain = false;
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, onMeshPrefixConfig) == OT_ERROR_NONE)
{
if (IsValidOmrPrefix(onMeshPrefixConfig) && onMeshPrefixConfig.GetPrefix() == aPrefix)
{
contain = true;
break;
}
}
return contain;
}
void RoutingManager::UpdateRouterAdvertHeader(const Ip6::Nd::RouterAdvertMessage *aRouterAdvertMessage)
{
// Updates the `mRouterAdvertHeader` from the given RA message.
Ip6::Nd::RouterAdvertMessage::Header oldHeader;
oldHeader = mRouterAdvertHeader;
mTimeRouterAdvMessageLastUpdate = TimerMilli::GetNow();
if (aRouterAdvertMessage == nullptr || aRouterAdvertMessage->GetHeader().GetRouterLifetime() == 0)
{
mRouterAdvertHeader.SetToDefault();
mLearntRouterAdvMessageFromHost = false;
}
else
{
// The checksum is set to zero in `mRouterAdvertHeader`
// which indicates to platform that it needs to do the
// calculation and update it.
mRouterAdvertHeader = aRouterAdvertMessage->GetHeader();
mRouterAdvertHeader.SetChecksum(0);
mLearntRouterAdvMessageFromHost = true;
}
ResetDiscoveredPrefixStaleTimer();
if (mRouterAdvertHeader != oldHeader)
{
// If there was a change to the header, start timer to
// reevaluate routing policy and send RA message with new
// header.
StartRoutingPolicyEvaluationJitter(kRoutingPolicyEvaluationJitter);
}
}
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 (mLearntRouterAdvMessageFromHost)
{
TimeMilli raStaleTime = OT_MAX(now, mTimeRouterAdvMessageLastUpdate + Time::SecToMsec(kRtrAdvStaleTime));
nextStaleTime = OT_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", nextStaleTime - now);
}
}
//---------------------------------------------------------------------------------------------------------------------
// DiscoveredPrefixTable
RoutingManager::DiscoveredPrefixTable::DiscoveredPrefixTable(Instance &aInstance)
: InstanceLocator(aInstance)
, mTimer(aInstance, HandleTimer)
, mSignalTask(aInstance, HandleSignalTask)
{
}
void RoutingManager::DiscoveredPrefixTable::ProcessRouterAdvertMessage(const Ip6::Nd::RouterAdvertMessage &aRaMessage,
const Ip6::Address & aSrcAddress)
{
// Process a received RA message and update the prefix table.
OT_UNUSED_VARIABLE(aSrcAddress);
for (const Ip6::Nd::Option &option : aRaMessage)
{
switch (option.GetType())
{
case Ip6::Nd::Option::kTypePrefixInfo:
ProcessPrefixInfoOption(static_cast<const Ip6::Nd::PrefixInfoOption &>(option));
break;
case Ip6::Nd::Option::kTypeRouteInfo:
ProcessRouteInfoOption(static_cast<const Ip6::Nd::RouteInfoOption &>(option));
break;
default:
break;
}
}
}
void RoutingManager::DiscoveredPrefixTable::ProcessPrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio)
{
Ip6::Prefix prefix;
Entry * entry;
VerifyOrExit(aPio.IsValid());
aPio.GetPrefix(prefix);
VerifyOrExit(Get<RoutingManager>().ShouldProcessPrefixInfoOption(aPio, prefix));
LogInfo("Processing PIO (%s, %u seconds)", prefix.ToString().AsCString(), aPio.GetValidLifetime());
entry = mEntries.FindMatching(Entry::Matcher(prefix, Entry::kTypeOnLink));
if (entry == nullptr)
{
VerifyOrExit(aPio.GetValidLifetime() != 0);
if (mEntries.IsFull())
{
LogWarn("Discovered too many prefixes, ignore on-link prefix %s", prefix.ToString().AsCString());
ExitNow();
}
SuccessOrExit(Get<RoutingManager>().PublishExternalRoute(prefix, NetworkData::kRoutePreferenceMedium));
entry = mEntries.PushBack();
entry->InitFrom(aPio);
}
else
{
Entry newEntry;
newEntry.InitFrom(aPio);
entry->AdoptValidAndPreferredLiftimesFrom(newEntry);
}
mTimer.FireAtIfEarlier(entry->GetExpireTime());
SignalTableChanged();
exit:
return;
}
void RoutingManager::DiscoveredPrefixTable::ProcessRouteInfoOption(const Ip6::Nd::RouteInfoOption &aRio)
{
Ip6::Prefix prefix;
Entry * entry;
VerifyOrExit(aRio.IsValid());
aRio.GetPrefix(prefix);
VerifyOrExit(Get<RoutingManager>().ShouldProcessRouteInfoOption(aRio, prefix));
LogInfo("Processing RIO (%s, %u seconds)", prefix.ToString().AsCString(), aRio.GetRouteLifetime());
entry = mEntries.FindMatching(Entry::Matcher(prefix, Entry::kTypeRoute));
if (aRio.GetRouteLifetime() == 0)
{
VerifyOrExit(entry != nullptr);
Get<RoutingManager>().UnpublishExternalRoute(entry->GetPrefix());
mEntries.Remove(*entry);
ExitNow();
}
if (entry == nullptr)
{
if (mEntries.IsFull())
{
LogWarn("Discovered too many prefixes, ignore new prefix %s", prefix.ToString().AsCString());
ExitNow();
}
SuccessOrExit(Get<RoutingManager>().PublishExternalRoute(prefix, aRio.GetPreference()));
entry = mEntries.PushBack();
}
entry->InitFrom(aRio);
mTimer.FireAtIfEarlier(entry->GetExpireTime());
SignalTableChanged();
exit:
return;
}
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 Entry &entry : mEntries)
{
if (!entry.IsOnLinkPrefix() || entry.IsDeprecated())
{
continue;
}
if ((aPrefix.GetLength() == 0) || (entry.GetPrefix() < aPrefix))
{
aPrefix = entry.GetPrefix();
}
}
}
bool RoutingManager::DiscoveredPrefixTable::ContainsOnLinkPrefix(const Ip6::Prefix &aPrefix) const
{
return mEntries.ContainsMatching(Entry::Matcher(aPrefix, Entry::kTypeOnLink));
}
bool RoutingManager::DiscoveredPrefixTable::ContainsRoutePrefix(const Ip6::Prefix &aPrefix) const
{
return mEntries.ContainsMatching(Entry::Matcher(aPrefix, Entry::kTypeRoute));
}
void RoutingManager::DiscoveredPrefixTable::RemoveOnLinkPrefix(const Ip6::Prefix &aPrefix, NetDataMode aNetDataMode)
{
RemovePrefix(aPrefix, Entry::kTypeOnLink, aNetDataMode);
}
void RoutingManager::DiscoveredPrefixTable::RemoveRoutePrefix(const Ip6::Prefix &aPrefix, NetDataMode aNetDataMode)
{
RemovePrefix(aPrefix, Entry::kTypeRoute, aNetDataMode);
}
void RoutingManager::DiscoveredPrefixTable::RemovePrefix(const Ip6::Prefix &aPrefix,
Entry::Type aType,
NetDataMode aNetDataMode)
{
// Remove a prefix of given type from the table if there is any.
// `aNetDataMode` specifies behavior when a match is found and
// removed. It indicates whether or not to unpublish it from
// Network Data.
Entry *entry = mEntries.FindMatching(Entry::Matcher(aPrefix, aType));
VerifyOrExit(entry != nullptr);
if (aNetDataMode == kUnpublishFromNetData)
{
Get<RoutingManager>().UnpublishExternalRoute(aPrefix);
}
mEntries.Remove(*entry);
SignalTableChanged();
exit:
return;
}
void RoutingManager::DiscoveredPrefixTable::RemoveAllEntries(void)
{
// Remove all entries from the table and unpublish them
// from Network Data.
Entry *entry;
while ((entry = mEntries.PopBack()) != nullptr)
{
Get<RoutingManager>().UnpublishExternalRoute(entry->GetPrefix());
SignalTableChanged();
}
mTimer.Stop();
}
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 (Entry &entry : mEntries)
{
if (entry.GetLastUpdateTime() <= aTimeThreshold)
{
if (entry.IsOnLinkPrefix())
{
entry.ClearPreferredLifetime();
}
else
{
entry.ClearValidLifetime();
}
SignalTableChanged();
}
}
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 Entry &entry : mEntries)
{
TimeMilli entryStaleTime = OT_MAX(aNow, entry.GetStaleTime());
if (entry.IsOnLinkPrefix() && !entry.IsDeprecated())
{
onLinkStaleTime = OT_MAX(onLinkStaleTime, entryStaleTime);
foundOnLink = true;
}
if (!entry.IsOnLinkPrefix())
{
routeStaleTime = OT_MIN(routeStaleTime, entryStaleTime);
}
}
return foundOnLink ? OT_MIN(onLinkStaleTime, routeStaleTime) : routeStaleTime;
}
void RoutingManager::DiscoveredPrefixTable::HandleTimer(Timer &aTimer)
{
aTimer.Get<RoutingManager>().mDiscoveredPrefixTable.HandleTimer();
}
void RoutingManager::DiscoveredPrefixTable::HandleTimer(void)
{
RemoveExpiredEntries();
}
void RoutingManager::DiscoveredPrefixTable::RemoveExpiredEntries(void)
{
TimeMilli now = TimerMilli::GetNow();
TimeMilli nextExpireTime = now.GetDistantFuture();
for (EntryArray::IndexType index = 0; index < mEntries.GetLength();)
{
Entry &entry = mEntries[index];
if (entry.GetExpireTime() <= now)
{
Get<RoutingManager>().UnpublishExternalRoute(entry.GetPrefix());
// Remove the prefix from the array (which replaces it with
// last entry in the array). So in this case, we do not
// increment the `index`.
mEntries.Remove(entry);
SignalTableChanged();
}
else
{
nextExpireTime = OT_MIN(nextExpireTime, entry.GetExpireTime());
index++;
}
}
if (nextExpireTime != now.GetDistantFuture())
{
mTimer.FireAt(nextExpireTime);
}
}
void RoutingManager::DiscoveredPrefixTable::SignalTableChanged(void)
{
mSignalTask.Post();
}
void RoutingManager::DiscoveredPrefixTable::HandleSignalTask(Tasklet &aTasklet)
{
aTasklet.Get<RoutingManager>().HandleDiscoveredPrefixTableChanged();
}
//---------------------------------------------------------------------------------------------------------------------
// DiscoveredPrefixTable::Entry
void RoutingManager::DiscoveredPrefixTable::Entry::InitFrom(const Ip6::Nd::PrefixInfoOption &aPio)
{
Clear();
aPio.GetPrefix(mPrefix);
mType = kTypeOnLink;
mValidLifetime = aPio.GetValidLifetime();
mPreferredLifetime = aPio.GetPreferredLifetime();
mLastUpdateTime = TimerMilli::GetNow();
}
void RoutingManager::DiscoveredPrefixTable::Entry::InitFrom(const Ip6::Nd::RouteInfoOption &aRio)
{
Clear();
aRio.GetPrefix(mPrefix);
mType = kTypeRoute;
mValidLifetime = aRio.GetRouteLifetime();
mRoutePreference = aRio.GetPreference();
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);
}
TimeMilli RoutingManager::DiscoveredPrefixTable::Entry::GetExpireTime(void) const
{
return mLastUpdateTime + CalculateExpireDelay(mValidLifetime);
}
TimeMilli RoutingManager::DiscoveredPrefixTable::Entry::GetStaleTime(void) const
{
uint32_t delay = OT_MIN(kRtrAdvStaleTime, IsOnLinkPrefix() ? mPreferredLifetime : mValidLifetime);
return mLastUpdateTime + TimeMilli::SecToMsec(delay);
}
bool RoutingManager::DiscoveredPrefixTable::Entry::IsDeprecated(void) const
{
OT_ASSERT(IsOnLinkPrefix());
return mLastUpdateTime + TimeMilli::SecToMsec(mPreferredLifetime) <= TimerMilli::GetNow();
}
void RoutingManager::DiscoveredPrefixTable::Entry::AdoptValidAndPreferredLiftimesFrom(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;
}
mPreferredLifetime = aEntry.GetPreferredLifetime();
mLastUpdateTime = aEntry.GetLastUpdateTime();
}
uint32_t RoutingManager::DiscoveredPrefixTable::Entry::CalculateExpireDelay(uint32_t aValidLifetime)
{
uint32_t delay;
if (aValidLifetime * static_cast<uint64_t>(1000) > Timer::kMaxDelay)
{
delay = Timer::kMaxDelay;
}
else
{
delay = aValidLifetime * 1000;
}
return delay;
}
//---------------------------------------------------------------------------------------------------------------------
// OmrPrefix
void RoutingManager::OmrPrefix::Init(const Ip6::Prefix &aPrefix, RoutePreference aPreference)
{
mPrefix = aPrefix;
mPreference = aPreference;
}
void RoutingManager::OmrPrefix::InitFrom(NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig)
{
Init(aOnMeshPrefixConfig.GetPrefix(), aOnMeshPrefixConfig.GetPreference());
}
bool RoutingManager::OmrPrefix::IsFavoredOver(const OmrPrefix &aOther) const
{
// This method determines whether this OMR prefix is favored
// over `aOther` 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.
return (mPreference > aOther.mPreference) || ((mPreference == aOther.mPreference) && (mPrefix < aOther.mPrefix));
}
RoutingManager::OmrPrefix::InfoString RoutingManager::OmrPrefix::ToString(void) const
{
InfoString string;
string.Append("%s (prf:", mPrefix.ToString().AsCString());
switch (mPreference)
{
case NetworkData::kRoutePreferenceHigh:
string.Append("high)");
break;
case NetworkData::kRoutePreferenceMedium:
string.Append("med)");
break;
case NetworkData::kRoutePreferenceLow:
string.Append("low)");
break;
}
return string;
}
} // namespace BorderRouter
} // namespace ot
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE