1682 lines
51 KiB
C++
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
|