diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index 536d5632d..c8b5fa6ce 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -254,7 +254,7 @@ jobs: MULTIPLY: 1 PYTHONUNBUFFERED: 1 VERBOSE: 1 - # The border routing and DUA feature can coexist, but current wireshark + # The Border Routing and DUA feature can coexist, but current wireshark # packet verification can't handle it because of the order of context ID # of OMR prefix and Domain prefix is not deterministic. BORDER_ROUTING: 0 diff --git a/include/openthread/border_router.h b/include/openthread/border_router.h index 841d8cd88..dcc0889bc 100644 --- a/include/openthread/border_router.h +++ b/include/openthread/border_router.h @@ -55,16 +55,32 @@ extern "C" { /** * This method initializes the Border Routing Manager on given infrastructure interface. * + * @note This method MUST be called before any other otBorderRouting* APIs. + * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aInfraIfIndex The infrastructure interface index. * - * @retval OT_ERROR_NONE Successfully started the border routing manager on given infrastructure. + * @retval OT_ERROR_NONE Successfully started the Border Routing manager on given infrastructure. * @retval OT_ERROR_INVALID_ARGS The index of the infra interface is not valid. * @retval OT_ERROR_FAILED Internal failure. This is usually failed to generate random prefixes. * */ otError otBorderRoutingInit(otInstance *aInstance, uint32_t aInfraIfIndex); +/** + * This method enables/disables the Border Routing Manager. + * + * @note The Border Routing Manager is enabled by default. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aEnabled A boolean to enable/disable the routing manager. + * + * @retval OT_ERROR_INVALID_STATE The Border Routing Manager is not initialized yet. + * @retval OT_ERROR_NONE Successfully enabled/disabled the Border Routing Manager. + * + */ +otError otBorderRoutingSetEnabled(otInstance *aInstance, bool aEnabled); + /** * This method provides a full or stable copy of the local Thread Network Data. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index d90d4c8f4..c2e4dd173 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (63) +#define OPENTHREAD_API_VERSION (64) /** * @addtogroup api-instance diff --git a/include/openthread/platform/infra_if.h b/include/openthread/platform/infra_if.h index ebca43b18..959dfdd01 100644 --- a/include/openthread/platform/infra_if.h +++ b/include/openthread/platform/infra_if.h @@ -46,6 +46,16 @@ extern "C" { #endif +/** + * This method returns the IPv6 link-local address of given infrastructure interface. + * + * @param[in] aInfraIfIndex The index of the infrastructure interface. + * + * @returns A pointer to the IPv6 link-local address. NULL if no valid IPv6 link-local address found. + * + */ +const otIp6Address *otPlatInfraIfGetLinkLocalAddress(uint32_t aInfraIfIndex); + /** * This method sends an ICMPv6 Neighbor Discovery message on given infrastructure interface. * @@ -83,8 +93,6 @@ otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, * @note Per RFC 4861, the caller should enforce that the source address MUST be a IPv6 link-local * address and the IP Hop Limit MUST be 255. * - * @note ICMPv6 message received from @p aInfraIfIndex via multicast loopback should not be passed in. - * */ extern void otPlatInfraIfRecvIcmp6Nd(otInstance * aInstance, uint32_t aInfraIfIndex, diff --git a/src/cli/README.md b/src/cli/README.md index 7b8d9c27c..65b08a52e 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -22,6 +22,7 @@ Done ## OpenThread Command List - [bbr](#bbr) +- [br](#br) - [bufferinfo](#bufferinfo) - [ccathreshold](#ccathreshold) - [channel](#channel) @@ -312,6 +313,20 @@ Set jitter (in seconds) for Backbone Router registration for Thread 1.2 FTD. Done ``` +### br + +Enbale/disable the Border Routing functionality. + +```bash +> br enable +Done +``` + +```bash +> br disable +Done +``` + ### bufferinfo Show the current message buffer information. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 7f860f4dd..88a5287d9 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -271,6 +271,34 @@ otError Interpreter::ProcessHelp(uint8_t aArgsLength, char *aArgs[]) return OT_ERROR_NONE; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +otError Interpreter::ProcessBorderRouting(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_NONE; + bool enable = false; + + VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS); + + if (strcmp(aArgs[0], "enable") == 0) + { + enable = true; + } + else if (strcmp(aArgs[0], "disable") == 0) + { + enable = false; + } + else + { + ExitNow(error = OT_ERROR_INVALID_COMMAND); + } + + SuccessOrExit(error = otBorderRoutingSetEnabled(mInstance, enable)); + +exit: + return error; +} +#endif + #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) otError Interpreter::ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 91e208052..1c9948a9a 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -294,6 +294,9 @@ private: otError ProcessCcaThreshold(uint8_t aArgsLength, char *aArgs[]); otError ProcessBufferInfo(uint8_t aArgsLength, char *aArgs[]); otError ProcessChannel(uint8_t aArgsLength, char *aArgs[]); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + otError ProcessBorderRouting(uint8_t aArgsLength, char *aArgs[]); +#endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) otError ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]); @@ -597,6 +600,9 @@ private: static constexpr Command sCommands[] = { #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) {"bbr", &Interpreter::ProcessBackboneRouter}, +#endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + {"br", &Interpreter::ProcessBorderRouting}, #endif {"bufferinfo", &Interpreter::ProcessBufferInfo}, {"ccathreshold", &Interpreter::ProcessCcaThreshold}, diff --git a/src/core/api/border_router_api.cpp b/src/core/api/border_router_api.cpp index 3cb5e4be4..720498713 100644 --- a/src/core/api/border_router_api.cpp +++ b/src/core/api/border_router_api.cpp @@ -50,6 +50,13 @@ otError otBorderRoutingInit(otInstance *aInstance, uint32_t aInfraIfIndex) return instance.Get().Init(aInfraIfIndex); } + +otError otBorderRoutingSetEnabled(otInstance *aInstance, bool aEnabled) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetEnabled(aEnabled); +} #endif otError otBorderRouterGetNetData(otInstance *aInstance, bool aStable, uint8_t *aData, uint8_t *aDataLength) diff --git a/src/core/border_router/router_advertisement.cpp b/src/core/border_router/router_advertisement.cpp index c9cc1c24b..ea3d11a66 100644 --- a/src/core/border_router/router_advertisement.cpp +++ b/src/core/border_router/router_advertisement.cpp @@ -55,12 +55,12 @@ const Option *Option::GetNextOption(const Option *aCurOption, const uint8_t *aBu } else { - nextOption = reinterpret_cast(aCurOption) + aCurOption->GetLength(); + nextOption = reinterpret_cast(aCurOption) + aCurOption->GetSize(); } VerifyOrExit(nextOption + sizeof(Option) <= bufferEnd, nextOption = nullptr); - VerifyOrExit(reinterpret_cast(nextOption)->GetLength() > 0, nextOption = nullptr); - VerifyOrExit(nextOption + reinterpret_cast(nextOption)->GetLength() <= bufferEnd, + VerifyOrExit(reinterpret_cast(nextOption)->GetSize() > 0, nextOption = nullptr); + VerifyOrExit(nextOption + reinterpret_cast(nextOption)->GetSize() <= bufferEnd, nextOption = nullptr); exit: @@ -110,9 +110,12 @@ void PrefixInfoOption::SetPrefix(const Ip6::Prefix &aPrefix) mPrefix = static_cast(aPrefix.mPrefix); } -void PrefixInfoOption::GetPrefix(Ip6::Prefix &aPrefix) const +Ip6::Prefix PrefixInfoOption::GetPrefix(void) const { - aPrefix.Set(mPrefix.GetBytes(), mPrefixLength); + Ip6::Prefix prefix; + + prefix.Set(mPrefix.GetBytes(), mPrefixLength); + return prefix; } RouteInfoOption::RouteInfoOption(void) @@ -126,18 +129,64 @@ RouteInfoOption::RouteInfoOption(void) mPrefix.Clear(); } +void RouteInfoOption::SetPreference(otRoutePreference aPreference) +{ + mReserved &= ~kPreferenceMask; + mReserved |= (static_cast(aPreference) << kPreferenceOffset) & kPreferenceMask; +} + +otRoutePreference RouteInfoOption::GetPreference(void) const +{ + otRoutePreference preference; + + switch ((mReserved & kPreferenceMask) >> kPreferenceOffset) + { + case kPreferenceLow: + preference = OT_ROUTE_PREFERENCE_LOW; + break; + case kPreferenceMed: + preference = OT_ROUTE_PREFERENCE_MED; + break; + case kPreferenceHigh: + preference = OT_ROUTE_PREFERENCE_HIGH; + break; + default: + preference = OT_ROUTE_PREFERENCE_LOW; + break; + } + + return preference; +} + void RouteInfoOption::SetPrefix(const Ip6::Prefix &aPrefix) { // The total length (in bytes) of a Router Information Option // is: (8 bytes fixed option header) + (0, 8, or 16 bytes prefix). // Because the length of the option must be padded with 8 bytes, // the length of the prefix (in bits) must be padded with 64 bits. - SetLength(((aPrefix.mLength + kLengthUnit * CHAR_BIT - 1) / (kLengthUnit * CHAR_BIT) + 1) * kLengthUnit); + SetLength((aPrefix.mLength + kLengthUnit * CHAR_BIT - 1) / (kLengthUnit * CHAR_BIT) + 1); mPrefixLength = aPrefix.mLength; mPrefix = static_cast(aPrefix.mPrefix); } +Ip6::Prefix RouteInfoOption::GetPrefix(void) const +{ + Ip6::Prefix prefix; + + prefix.Set(mPrefix.GetBytes(), mPrefixLength); + return prefix; +} + +bool RouteInfoOption::IsValid(void) const +{ + otRoutePreference pref = GetPreference(); + + return (GetLength() == 1 || GetLength() == 2 || GetLength() == 3) && + (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT) && + (pref == OT_ROUTE_PREFERENCE_LOW || pref == OT_ROUTE_PREFERENCE_MED || pref == OT_ROUTE_PREFERENCE_HIGH); +} + RouterAdvMessage::RouterAdvMessage(void) : mReachableTime(0) , mRetransTimer(0) diff --git a/src/core/border_router/router_advertisement.hpp b/src/core/border_router/router_advertisement.hpp index 7411e5bc2..e34641271 100644 --- a/src/core/border_router/router_advertisement.hpp +++ b/src/core/border_router/router_advertisement.hpp @@ -43,6 +43,7 @@ #include +#include #include #include "common/encoding.hpp" @@ -103,23 +104,39 @@ public: Type GetType(void) const { return mType; } /** - * This method sets the length of the option (in bytes). + * This method sets the size of the option (in bytes). * * Since the option must end on their natural 64-bits boundaries, - * the actual length set to the option is padded to (aLength + 7) / 8 * 8. + * the actual length set to the option is padded to (aSize + 7) / 8 * 8. * - * @param[in] aLength The length of the option in unit of 1 byte. + * @param[in] aSize The size of the option in unit of 1 byte. * */ - void SetLength(uint16_t aLength) { mLength = (aLength + kLengthUnit - 1) / kLengthUnit; } + void SetSize(uint16_t aSize) { mLength = (aSize + kLengthUnit - 1) / kLengthUnit; } /** - * This method returns the length of the option (in bytes). + * This method returns the size of the option (in bytes). * - * @returns The length of the option. + * @returns The size of the option in unit of 1 byte. * */ - uint16_t GetLength(void) const { return mLength * 8; } + uint16_t GetSize(void) const { return mLength * kLengthUnit; } + + /** + * This method sets the length of the option (in unit of 8 bytes). + * + * @param[in] aLength The length of the option in unit of 8 bytes. + * + */ + void SetLength(uint8_t aLength) { mLength = aLength; } + + /** + * This method returns the length of the option (in unit of 8 bytes). + * + * @returns The length of the option in unit of 8 bytes. + * + */ + uint16_t GetLength(void) const { return mLength; } /** * This helper method returns a pointer to the next valid option in the buffer. @@ -215,12 +232,12 @@ public: void SetPrefix(const Ip6::Prefix &aPrefix); /** - * THis method returns the prefix in this option. + * This method returns the prefix in this option. * - * @param[out] aPrefix The prefix to output to. + * @returns The IPv6 prefix in this option. * */ - void GetPrefix(Ip6::Prefix &aPrefix) const; + Ip6::Prefix GetPrefix(void) const; /** * This method tells whether this option is valid. @@ -230,7 +247,7 @@ public: */ bool IsValid(void) const { - return (GetLength() == sizeof(*this)) && (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT); + return (GetSize() == sizeof(*this)) && (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT); } private: @@ -267,6 +284,22 @@ public: */ RouteInfoOption(void); + /** + * This method sets the route preference. + * + * @param[in] aPreference The route preference. + * + */ + void SetPreference(otRoutePreference aPreference); + + /** + * This method returns the route preference. + * + * @returns The route preference. + * + */ + otRoutePreference GetPreference(void) const; + /** * This method sets the lifetime of the route in seconds. * @@ -275,6 +308,14 @@ public: */ void SetRouteLifetime(uint32_t aLifetime) { mRouteLifetime = HostSwap32(aLifetime); } + /** + * This method returns Route Lifetime in seconds. + * + * @returns The Route Lifetime in seconds. + * + */ + uint32_t GetRouteLifetime(void) const { return HostSwap32(mRouteLifetime); } + /** * This method sets the prefix. * @@ -283,19 +324,36 @@ public: */ void SetPrefix(const Ip6::Prefix &aPrefix); + /** + * This method returns the prefix in this option. + * + * @returns The IPv6 prefix in this option. + * + */ + Ip6::Prefix GetPrefix(void) const; + /** * This method tells whether this option is valid. * * @returns A boolean indicates whether this option is valid. * */ - bool IsValid(void) const - { - return (GetLength() == kLengthUnit || GetLength() == 2 * kLengthUnit || GetLength() == 3 * kLengthUnit) && - (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT); - } + bool IsValid(void) const; private: + enum : uint8_t + { + kPreferenceMask = 0x18u, + kPreferenceOffset = 3u, + }; + + enum : uint8_t + { + kPreferenceLow = 0x03, + kPreferenceMed = 0x00, + kPreferenceHigh = 0x01, + }; + uint8_t mPrefixLength; // The prefix length in bits. uint8_t mReserved; // The reserved field. uint32_t mRouteLifetime; // The lifetime in seconds. diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index e6b501347..491545dc1 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -61,26 +61,29 @@ RoutingManager::RoutingManager(Instance &aInstance) : InstanceLocator(aInstance) , mIsRunning(false) , mInfraIfIndex(0) + , mEnabled(true) // The routing manager is by default enabled. , mAdvertisedOmrPrefixNum(0) , mAdvertisedOnLinkPrefix(nullptr) + , mDiscoveredPrefixNum(0) + , mDiscoveredPrefixInvalidTimer(aInstance, HandleDiscoveredPrefixInvalidTimer, this) , mRouterAdvertisementTimer(aInstance, HandleRouterAdvertisementTimer, this) , mRouterAdvertisementCount(0) , mRouterSolicitTimer(aInstance, HandleRouterSolicitTimer, this) , mRouterSolicitCount(0) - , mDiscoveredOnLinkPrefixInvalidTimer(aInstance, HandleDiscoveredOnLinkPrefixInvalidTimer, this) { mLocalOmrPrefix.Clear(); memset(mAdvertisedOmrPrefixes, 0, sizeof(mAdvertisedOmrPrefixes)); mLocalOnLinkPrefix.Clear(); - mDiscoveredOnLinkPrefix.Clear(); + + memset(mDiscoveredPrefixes, 0, sizeof(mDiscoveredPrefixes)); } otError RoutingManager::Init(uint32_t aInfraIfIndex) { otError error; - OT_ASSERT(!IsInitialized() && !Get().IsAttached()); + OT_ASSERT(!IsInitialized()); VerifyOrExit(aInfraIfIndex > 0, error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = LoadOrGenerateRandomOmrPrefix()); @@ -92,6 +95,29 @@ exit: return error; } +otError RoutingManager::SetEnabled(bool aEnabled) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(IsInitialized(), error = OT_ERROR_INVALID_STATE); + + VerifyOrExit(aEnabled != mEnabled); + + mEnabled = aEnabled; + + if (!mEnabled) + { + Stop(); + } + else if (Get().IsAttached()) + { + Start(); + } + +exit: + return error; +} + otError RoutingManager::LoadOrGenerateRandomOmrPrefix(void) { otError error = OT_ERROR_NONE; @@ -150,6 +176,8 @@ void RoutingManager::Start(void) { if (!mIsRunning) { + otLogInfoBr("Border Routing manager started"); + mIsRunning = true; StartRouterSolicitation(); } @@ -160,6 +188,11 @@ void RoutingManager::Stop(void) VerifyOrExit(mIsRunning); UnpublishLocalOmrPrefix(); + if (mAdvertisedOnLinkPrefix != nullptr) + { + RemoveExternalRoute(*mAdvertisedOnLinkPrefix); + } + InvalidateAllDiscoveredPrefixes(); // Use empty OMR & on-link prefixes to invalidate possible advertised prefixes. SendRouterAdvertisement(nullptr, 0, nullptr); @@ -169,8 +202,9 @@ void RoutingManager::Stop(void) mAdvertisedOnLinkPrefix = nullptr; - mDiscoveredOnLinkPrefix.Clear(); - mDiscoveredOnLinkPrefixInvalidTimer.Stop(); + memset(mDiscoveredPrefixes, 0, sizeof(mDiscoveredPrefixes)); + mDiscoveredPrefixNum = 0; + mDiscoveredPrefixInvalidTimer.Stop(); mRouterAdvertisementTimer.Stop(); mRouterAdvertisementCount = 0; @@ -178,6 +212,8 @@ void RoutingManager::Stop(void) mRouterSolicitTimer.Stop(); mRouterSolicitCount = 0; + otLogInfoBr("Border Routing manager stopped"); + mIsRunning = false; exit: @@ -189,12 +225,19 @@ void RoutingManager::RecvIcmp6Message(uint32_t aInfraIfIndex, const uint8_t * aBuffer, uint16_t aBufferLength) { + otError error = OT_ERROR_NONE; const Ip6::Icmp::Header *icmp6Header; + const Ip6::Address * infraLinkLocalAddr; - VerifyOrExit(mIsRunning); + VerifyOrExit(IsInitialized() && mIsRunning, error = OT_ERROR_DROP); - VerifyOrExit(aInfraIfIndex == mInfraIfIndex); - VerifyOrExit(aBuffer != nullptr && aBufferLength >= sizeof(*icmp6Header)); + VerifyOrExit(aInfraIfIndex == mInfraIfIndex, error = OT_ERROR_DROP); + infraLinkLocalAddr = static_cast(otPlatInfraIfGetLinkLocalAddress(mInfraIfIndex)); + + // Drop any ICMPv6 messages sent from myself. + VerifyOrExit(infraLinkLocalAddr != nullptr && aSrcAddress != *infraLinkLocalAddr, error = OT_ERROR_DROP); + + VerifyOrExit(aBuffer != nullptr && aBufferLength >= sizeof(*icmp6Header), error = OT_ERROR_PARSE); icmp6Header = reinterpret_cast(aBuffer); @@ -211,12 +254,15 @@ void RoutingManager::RecvIcmp6Message(uint32_t aInfraIfIndex, } exit: - return; + if (error != OT_ERROR_NONE) + { + otLogDebgBr("drop ICMPv6 message: %s", otThreadErrorToString(error)); + } } void RoutingManager::HandleNotifierEvents(Events aEvents) { - VerifyOrExit(IsInitialized()); + VerifyOrExit(IsInitialized() && IsEnabled()); if (aEvents.Contains(kEventThreadRoleChanged)) { @@ -253,8 +299,7 @@ uint8_t RoutingManager::EvaluateOmrPrefix(Ip6::Prefix *aNewOmrPrefixes, uint8_t { uint8_t newPrefixIndex; - if (!IsValidOmrPrefix(onMeshPrefixConfig.GetPrefix()) || !onMeshPrefixConfig.mDefaultRoute || - !onMeshPrefixConfig.mSlaac || onMeshPrefixConfig.mDp) + if (!IsValidOmrPrefix(onMeshPrefixConfig.GetPrefix()) || !onMeshPrefixConfig.mSlaac || onMeshPrefixConfig.mDp) { continue; } @@ -327,12 +372,13 @@ otError RoutingManager::PublishLocalOmrPrefix(void) omrPrefixConfig.mSlaac = true; omrPrefixConfig.mPreferred = true; omrPrefixConfig.mOnMesh = true; - omrPrefixConfig.mDefaultRoute = true; + omrPrefixConfig.mDefaultRoute = false; + omrPrefixConfig.mPreference = OT_ROUTE_PREFERENCE_MED; error = Get().AddOnMeshPrefix(omrPrefixConfig); if (error != OT_ERROR_NONE) { - otLogInfoBr("failed to publish local OMR prefix %s in Thread network: %s", + otLogWarnBr("failed to publish local OMR prefix %s in Thread network: %s", mLocalOmrPrefix.ToString().AsCString(), otThreadErrorToString(error)); } else @@ -346,15 +392,67 @@ otError RoutingManager::PublishLocalOmrPrefix(void) void RoutingManager::UnpublishLocalOmrPrefix(void) { + otError error = OT_ERROR_NONE; + VerifyOrExit(mIsRunning); - IgnoreError(Get().RemoveOnMeshPrefix(mLocalOmrPrefix)); - Get().HandleServerDataUpdated(); + SuccessOrExit(error = Get().RemoveOnMeshPrefix(mLocalOmrPrefix)); - otLogInfoBr("unpubished local OMR prefix %s from Thread network", mLocalOmrPrefix.ToString().AsCString()); + Get().HandleServerDataUpdated(); + otLogInfoBr("unpublished local OMR prefix %s from Thread network", mLocalOmrPrefix.ToString().AsCString()); exit: - return; + if (error != OT_ERROR_NONE) + { + otLogWarnBr("failed to unpublish local OMR prefix %s from Thread network: %s", + mLocalOmrPrefix.ToString().AsCString(), otThreadErrorToString(error)); + } +} + +otError RoutingManager::AddExternalRoute(const Ip6::Prefix &aPrefix, otRoutePreference aRoutePreference) +{ + otError error; + NetworkData::ExternalRouteConfig routeConfig; + + OT_ASSERT(mIsRunning); + + routeConfig.Clear(); + routeConfig.SetPrefix(aPrefix); + routeConfig.mStable = true; + routeConfig.mPreference = aRoutePreference; + + error = Get().AddHasRoutePrefix(routeConfig); + if (error != OT_ERROR_NONE) + { + otLogWarnBr("failed to add external route %s: %s", aPrefix.ToString().AsCString(), + otThreadErrorToString(error)); + } + else + { + Get().HandleServerDataUpdated(); + otLogInfoBr("added external route %s", aPrefix.ToString().AsCString()); + } + + return error; +} + +void RoutingManager::RemoveExternalRoute(const Ip6::Prefix &aPrefix) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(mIsRunning); + + SuccessOrExit(error = Get().RemoveHasRoutePrefix(aPrefix)); + + Get().HandleServerDataUpdated(); + otLogInfoBr("removed external route %s", aPrefix.ToString().AsCString()); + +exit: + if (error != OT_ERROR_NONE) + { + otLogWarnBr("failed to remove external route %s: %s", aPrefix.ToString().AsCString(), + otThreadErrorToString(error)); + } } bool RoutingManager::ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum) @@ -373,27 +471,74 @@ bool RoutingManager::ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefi return ret; } -const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void) const +const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void) { - const Ip6::Prefix *newOnLinkPrefix = nullptr; + const Ip6::Prefix *newOnLinkPrefix = nullptr; + const Ip6::Prefix *smallestOnLinkPrefix = nullptr; - // We don't evaluate on-link prefix if we are doing - // Router Discovery or we have already discovered some - // on-link prefixes. + // We don't evaluate on-link prefix if we are doing Router Solicitation. VerifyOrExit(!mRouterSolicitTimer.IsRunning()); - if (IsValidOnLinkPrefix(mDiscoveredOnLinkPrefix)) + + for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) { - otLogInfoBr("EvaluateOnLinkPrefix: there is already on-link prefix %s on interface %u", - mDiscoveredOnLinkPrefix.ToString().AsCString(), mInfraIfIndex); - ExitNow(); + ExternalPrefix &prefix = mDiscoveredPrefixes[i]; + + if (!prefix.mIsOnLinkPrefix) + { + continue; + } + + if (smallestOnLinkPrefix == nullptr || IsPrefixSmallerThan(prefix.mPrefix, *smallestOnLinkPrefix)) + { + smallestOnLinkPrefix = &prefix.mPrefix; + } } - newOnLinkPrefix = &mLocalOnLinkPrefix; + // We start advertising our local on-link prefix if there is no existing one. + if (smallestOnLinkPrefix == nullptr) + { + if (mAdvertisedOnLinkPrefix != nullptr) + { + newOnLinkPrefix = mAdvertisedOnLinkPrefix; + } + else if (AddExternalRoute(mLocalOnLinkPrefix, OT_ROUTE_PREFERENCE_MED) == OT_ERROR_NONE) + { + newOnLinkPrefix = &mLocalOnLinkPrefix; + } + } + // When an application-specific on-link prefix is received and it is bigger than the + // advertised prefix, we will not remove the advertised 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 on-link prefix and the application-specific prefix is not used. + else if (mAdvertisedOnLinkPrefix != nullptr) + { + if (IsPrefixSmallerThan(*mAdvertisedOnLinkPrefix, *smallestOnLinkPrefix)) + { + newOnLinkPrefix = mAdvertisedOnLinkPrefix; + } + else + { + otLogInfoBr("EvaluateOnLinkPrefix: there is already smaller on-link prefix %s on interface %u", + smallestOnLinkPrefix->ToString().AsCString(), mInfraIfIndex); + + // TODO: we removes the advertised on-link prefix by setting valid lifetime for PIO. + // But SLAAC addresses configured by PIO prefix will not be removed immediately ( + // https://tools.ietf.org/html/rfc4862#section-5.5.3). This leads to a situation that + // a WiFi device keeps using the old SLAAC address but a Thread device cannot reach to + // it. One solution is to delay removing external route until the SLAAC addresses are + // actually expired/deprecated. + RemoveExternalRoute(*mAdvertisedOnLinkPrefix); + } + } exit: return newOnLinkPrefix; } +// 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 prefix in the Thread network. void RoutingManager::EvaluateRoutingPolicy(void) { const Ip6::Prefix *newOnLinkPrefix = nullptr; @@ -430,7 +575,7 @@ void RoutingManager::EvaluateRoutingPolicy(void) } otLogInfoBr("router advertisement scheduled in %u seconds", nextSendTime); - mRouterAdvertisementTimer.Start(nextSendTime * 1000); + mRouterAdvertisementTimer.Start(Time::SecToMsec(nextSendTime)); } // 3. Update advertised on-link & OMR prefixes information. @@ -443,10 +588,19 @@ exit: return; } +// starts sending Router Solicitations in random delay +// between 0 and kMaxRtrSolicitationDelay. void RoutingManager::StartRouterSolicitation(void) { + uint32_t randomDelay; + mRouterSolicitCount = 0; - mRouterSolicitTimer.Start(Random::NonCrypto::GetUint32InRange(0, kMaxRtrSolicitationDelay * 1000)); + + static_assert(kMaxRtrSolicitationDelay > 0, "invalid maximum Router Solicitation delay"); + randomDelay = Random::NonCrypto::GetUint32InRange(0, Time::SecToMsec(kMaxRtrSolicitationDelay)); + + otLogInfoBr("start Router Solicitation, scheduled in %u milliseconds", randomDelay); + mRouterSolicitTimer.Start(randomDelay); } otError RoutingManager::SendRouterSolicitation(void) @@ -461,6 +615,13 @@ otError RoutingManager::SendRouterSolicitation(void) sizeof(routerSolicit)); } +// This method sends Router Advertisement messages to advertise on-link prefix and route for OMR prefix. +// @param[in] aNewOmrPrefixes A pointer to an array of the new OMR prefixes to be advertised. +// @p aNewOmrPrefixNum must be zero if this argument is nullptr. +// @param[in] aNewOmrPrefixNum The number of the new OMR prefixes to be advertised. +// Zero means we should stop advertising OMR prefixes. +// @param[in] aOnLinkPrefix A pointer to the new on-link prefix to be advertised. +// nullptr means we should stop advertising on-link prefix. void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, uint8_t aNewOmrPrefixNum, const Ip6::Prefix *aNewOnLinkPrefix) @@ -488,11 +649,11 @@ void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, pio.SetPreferredLifetime(kDefaultOnLinkPrefixLifetime); pio.SetPrefix(*aNewOnLinkPrefix); - OT_ASSERT(bufferLength + pio.GetLength() <= sizeof(buffer)); - memcpy(buffer + bufferLength, &pio, pio.GetLength()); - bufferLength += pio.GetLength(); + OT_ASSERT(bufferLength + pio.GetSize() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &pio, pio.GetSize()); + bufferLength += pio.GetSize(); - if (mAdvertisedOnLinkPrefix != nullptr) + if (mAdvertisedOnLinkPrefix == nullptr) { otLogInfoBr("start advertising new on-link prefix %s on interface %u", aNewOnLinkPrefix->ToString().AsCString(), mInfraIfIndex); @@ -505,14 +666,17 @@ void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, { RouterAdv::PrefixInfoOption pio; + pio.SetOnLink(true); + pio.SetAutoAddrConfig(true); + // Set zero valid lifetime to immediately invalidate the advertised on-link prefix. pio.SetValidLifetime(0); pio.SetPreferredLifetime(0); pio.SetPrefix(*mAdvertisedOnLinkPrefix); - OT_ASSERT(bufferLength + pio.GetLength() <= sizeof(buffer)); - memcpy(buffer + bufferLength, &pio, pio.GetLength()); - bufferLength += pio.GetLength(); + OT_ASSERT(bufferLength + pio.GetSize() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &pio, pio.GetSize()); + bufferLength += pio.GetSize(); otLogInfoBr("stop advertising on-link prefix %s on interface %u", mAdvertisedOnLinkPrefix->ToString().AsCString(), mInfraIfIndex); @@ -531,9 +695,9 @@ void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, rio.SetRouteLifetime(0); rio.SetPrefix(advertisedOmrPrefix); - OT_ASSERT(bufferLength + rio.GetLength() <= sizeof(buffer)); - memcpy(buffer + bufferLength, &rio, rio.GetLength()); - bufferLength += rio.GetLength(); + OT_ASSERT(bufferLength + rio.GetSize() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &rio, rio.GetSize()); + bufferLength += rio.GetSize(); otLogInfoBr("stop advertising OMR prefix %s on interface %u", advertisedOmrPrefix.ToString().AsCString(), mInfraIfIndex); @@ -548,9 +712,9 @@ void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, rio.SetRouteLifetime(kDefaultOmrPrefixLifetime); rio.SetPrefix(newOmrPrefix); - OT_ASSERT(bufferLength + rio.GetLength() <= sizeof(buffer)); - memcpy(buffer + bufferLength, &rio, rio.GetLength()); - bufferLength += rio.GetLength(); + OT_ASSERT(bufferLength + rio.GetSize() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &rio, rio.GetSize()); + bufferLength += rio.GetSize(); otLogInfoBr("send OMR prefix %s in RIO (valid lifetime = %u seconds)", newOmrPrefix.ToString().AsCString(), kDefaultOmrPrefixLifetime); @@ -649,7 +813,7 @@ void RoutingManager::HandleRouterSolicitTimer(void) (mRouterSolicitCount == kMaxRtrSolicitations) ? kMaxRtrSolicitationDelay : kRtrSolicitationInterval; otLogDebgBr("router solicitation timer scheduled in %u seconds", nextSolicitationDelay); - mRouterSolicitTimer.Start(nextSolicitationDelay * 1000); + mRouterSolicitTimer.Start(Time::SecToMsec(nextSolicitationDelay)); } else { @@ -658,33 +822,38 @@ void RoutingManager::HandleRouterSolicitTimer(void) } } -void RoutingManager::HandleDiscoveredOnLinkPrefixInvalidTimer(Timer &aTimer) +void RoutingManager::HandleDiscoveredPrefixInvalidTimer(Timer &aTimer) { - aTimer.GetOwner().HandleDiscoveredOnLinkPrefixInvalidTimer(); + aTimer.GetOwner().HandleDiscoveredPrefixInvalidTimer(); } -void RoutingManager::HandleDiscoveredOnLinkPrefixInvalidTimer(void) +void RoutingManager::HandleDiscoveredPrefixInvalidTimer(void) { - otLogInfoBr("invalidate discovered on-link prefix: %s", mDiscoveredOnLinkPrefix.ToString().AsCString()); - mDiscoveredOnLinkPrefix.Clear(); - - // The discovered on-link prefix becomes invalid, start Router Solicitation - // to discover new one. - StartRouterSolicitation(); + InvalidateDiscoveredPrefixes(); } void RoutingManager::HandleRouterSolicit(const Ip6::Address &aSrcAddress, const uint8_t * aBuffer, uint16_t aBufferLength) { + uint32_t randomDelay; + OT_UNUSED_VARIABLE(aSrcAddress); OT_UNUSED_VARIABLE(aBuffer); OT_UNUSED_VARIABLE(aBufferLength); + VerifyOrExit(!mRouterSolicitTimer.IsRunning()); + otLogInfoBr("received Router Solicitation from %s on interface %u", aSrcAddress.ToString().AsCString(), mInfraIfIndex); - mRouterAdvertisementTimer.Start(Random::NonCrypto::GetUint32InRange(0, kMaxRaDelayTime)); + randomDelay = Random::NonCrypto::GetUint32InRange(0, kMaxRaDelayTime); + + otLogInfoBr("Router Advertisement scheduled in %u milliseconds", randomDelay); + mRouterAdvertisementTimer.Start(randomDelay); + +exit: + return; } uint32_t RoutingManager::GetPrefixExpireDelay(uint32_t aValidLifetime) @@ -711,6 +880,7 @@ void RoutingManager::HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, using RouterAdv::Option; using RouterAdv::PrefixInfoOption; + using RouterAdv::RouteInfoOption; using RouterAdv::RouterAdvMessage; bool needReevaluate = false; @@ -729,53 +899,32 @@ void RoutingManager::HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, option = nullptr; while ((option = Option::GetNextOption(option, optionsBegin, optionsLength)) != nullptr) { - const PrefixInfoOption *pio; - Ip6::Prefix prefix; - - if (option->GetType() != Option::Type::kPrefixInfo) + switch (option->GetType()) { - continue; - } - - pio = static_cast(option); - - if (!pio->IsValid()) + case Option::Type::kPrefixInfo: { - continue; - } + const PrefixInfoOption *pio = static_cast(option); - pio->GetPrefix(prefix); - if (!IsValidOnLinkPrefix(prefix)) - { - otLogInfoBr("ignore invalid prefix in PIO: %s", prefix.ToString().AsCString()); - continue; - } - - if (pio->GetValidLifetime() == 0) - { - if (mDiscoveredOnLinkPrefix == prefix) + if (pio->IsValid()) { - otLogInfoBr("invalidate discovered on-link prefix %s", prefix.ToString().AsCString()); - mDiscoveredOnLinkPrefixInvalidTimer.Stop(); - mDiscoveredOnLinkPrefix.Clear(); - needReevaluate = true; + needReevaluate |= UpdateDiscoveredPrefixes(*pio); } } - else + break; + + case Option::Type::kRouteInfo: { - otLogInfoBr("set discovered on-link prefix to %s, valid lifetime: %u seconds", - prefix.ToString().AsCString(), pio->GetValidLifetime()); + const RouteInfoOption *rio = static_cast(option); - // We keep tracking the latest on-link prefix. - mDiscoveredOnLinkPrefixInvalidTimer.Start(GetPrefixExpireDelay(pio->GetValidLifetime())); - mDiscoveredOnLinkPrefix = prefix; + if (rio->IsValid()) + { + needReevaluate |= UpdateDiscoveredPrefixes(*rio); + } + } + break; - // Stop Router Solicitation if we found a valid on-link prefix. - // Otherwise, we wait till the Router Solicitation process times out. - // So the maximum delay before the Border Router starts advertising - // its own on-link prefix is 9 (4 + 4 + 1) seconds. - mRouterSolicitTimer.Stop(); - needReevaluate = true; + default: + break; } } @@ -788,6 +937,186 @@ exit: return; } +bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption &aPio) +{ + Ip6::Prefix prefix = aPio.GetPrefix(); + bool needReevaluate = false; + + if (!IsValidOnLinkPrefix(prefix)) + { + otLogInfoBr("ignore invalid prefix in PIO: %s", prefix.ToString().AsCString()); + ExitNow(); + } + + otLogInfoBr("discovered on-link prefix (%s, %u seconds) from interface %u", prefix.ToString().AsCString(), + aPio.GetValidLifetime(), mInfraIfIndex); + + if (aPio.GetValidLifetime() == 0) + { + needReevaluate = InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ true) > 0; + } + else + { + needReevaluate = AddDiscoveredPrefix(prefix, /* aIsOnLinkPrefix */ true, aPio.GetValidLifetime()); + } + +exit: + return needReevaluate; +} + +bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &aRio) +{ + Ip6::Prefix prefix = aRio.GetPrefix(); + bool needReevaluate = false; + + if (!IsValidOmrPrefix(prefix)) + { + otLogInfoBr("ignore invalid prefix in RIO: %s", prefix.ToString().AsCString()); + ExitNow(); + } + + // Ignore the OMR prefix that matches what we have advertised. + VerifyOrExit(!ContainsPrefix(prefix, mAdvertisedOmrPrefixes, mAdvertisedOmrPrefixNum)); + + otLogInfoBr("discovered OMR prefix (%s, %u seconds) from interface %u", prefix.ToString().AsCString(), + aRio.GetRouteLifetime(), mInfraIfIndex); + + if (aRio.GetRouteLifetime() == 0) + { + needReevaluate = (InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ false) > 0); + } + else + { + needReevaluate = + AddDiscoveredPrefix(prefix, /* aIsOnLinkPrefix */ false, aRio.GetRouteLifetime(), aRio.GetPreference()); + } + +exit: + return needReevaluate; +} + +bool RoutingManager::InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix, bool aIsOnLinkPrefix) +{ + uint8_t removedNum = 0; + ExternalPrefix *keptPrefix = mDiscoveredPrefixes; + TimeMilli now = TimerMilli::GetNow(); + TimeMilli earliestExpireTime = now.GetDistantFuture(); + uint8_t keptOnLinkPrefixNum = 0; + + for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) + { + ExternalPrefix &prefix = mDiscoveredPrefixes[i]; + + if ((aPrefix != nullptr && prefix.mPrefix == *aPrefix && prefix.mIsOnLinkPrefix == aIsOnLinkPrefix) || + (prefix.mExpireTime <= now)) + { + RemoveExternalRoute(prefix.mPrefix); + ++removedNum; + } + else + { + earliestExpireTime = OT_MIN(earliestExpireTime, prefix.mExpireTime); + *keptPrefix = prefix; + ++keptPrefix; + if (prefix.mIsOnLinkPrefix) + { + ++keptOnLinkPrefixNum; + } + } + } + + mDiscoveredPrefixNum -= removedNum; + + if (keptOnLinkPrefixNum == 0) + { + mDiscoveredPrefixInvalidTimer.Stop(); + + // There are no valid on-link prefixes on infra link now, start Router Solicitation + // To find out more on-link prefixes or timeout to advertise my local on-link prefix. + StartRouterSolicitation(); + } + else + { + mDiscoveredPrefixInvalidTimer.FireAt(earliestExpireTime); + } + + return (removedNum != 0); // If anything was removed we need to reevaluate. +} + +void RoutingManager::InvalidateAllDiscoveredPrefixes(void) +{ + TimeMilli past = TimerMilli::GetNow(); + + for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) + { + mDiscoveredPrefixes[i].mExpireTime = past; + } + + InvalidateDiscoveredPrefixes(); + + OT_ASSERT(mDiscoveredPrefixNum == 0); +} + +// Adds a discovered prefix on infra link. If the same prefix already exists, +// only the lifetime will be updated. Returns a boolean which indicates whether +// a new prefix is added. +bool RoutingManager::AddDiscoveredPrefix(const Ip6::Prefix &aPrefix, + bool aIsOnLinkPrefix, + uint32_t aLifetime, + otRoutePreference aRoutePreference) +{ + OT_ASSERT(aIsOnLinkPrefix ? IsValidOmrPrefix(aPrefix) : IsValidOnLinkPrefix(aPrefix)); + OT_ASSERT(aLifetime > 0); + + bool added = false; + + for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i) + { + ExternalPrefix &prefix = mDiscoveredPrefixes[i]; + + if (aPrefix == prefix.mPrefix && aIsOnLinkPrefix == prefix.mIsOnLinkPrefix) + { + prefix.mExpireTime = TimerMilli::GetNow() + GetPrefixExpireDelay(aLifetime); + mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(prefix.mExpireTime); + + otLogInfoBr("discovered prefix %s refreshed lifetime: %u seconds", aPrefix.ToString().AsCString(), + aLifetime); + ExitNow(); + } + } + + if (mDiscoveredPrefixNum < kMaxDiscoveredPrefixNum) + { + ExternalPrefix &newPrefix = mDiscoveredPrefixes[mDiscoveredPrefixNum]; + + SuccessOrExit(AddExternalRoute(aPrefix, aRoutePreference)); + + if (aIsOnLinkPrefix) + { + // Stop Router Solicitation if we discovered a valid on-link prefix. + // Otherwise, we wait till the Router Solicitation process times out. + // So the maximum delay before the Border Router starts advertising + // its own on-link prefix is 9 (4 + 4 + 1) seconds. + mRouterSolicitTimer.Stop(); + } + + newPrefix.mPrefix = aPrefix; + newPrefix.mIsOnLinkPrefix = aIsOnLinkPrefix; + newPrefix.mExpireTime = TimerMilli::GetNow() + GetPrefixExpireDelay(aLifetime); + mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(newPrefix.mExpireTime); + + ++mDiscoveredPrefixNum; + added = true; + } + else + { + otLogWarnBr("discovered too many prefixes, ignore new prefix %s", aPrefix.ToString().AsCString()); + } + +exit: + return added; +} + } // namespace BorderRouter } // namespace ot diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 45eb4d2e4..55bfb242b 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -40,6 +40,7 @@ #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE #include +#include #include #include "border_router/router_advertisement.hpp" @@ -56,9 +57,9 @@ namespace BorderRouter { * This class implements bi-directional routing between Thread and * Infrastructure networks. * - * The routing manager works on both Thread interface and infrastructure - * interface. All ICMPv6 messages are sent/recv on the infrastructure - * interface. + * The Border Routing manager works on both Thread interface and + * infrastructure interface. All ICMPv6 messages are sent/recv + * on the infrastructure interface. * */ class RoutingManager : public InstanceLocator @@ -85,6 +86,19 @@ public: */ otError Init(uint32_t aInfraIfIndex); + /** + * This method enables/disables the Border Routing Manager. + * + * @note The Border Routing Manager is enabled by default. + * + * @param[in] aEnabled A boolean to enable/disable the Border Routing Manager. + * + * @retval OT_ERROR_INVALID_STATE The Border Routing Manager is not initialized yet. + * @retval OT_ERROR_NONE Successfully enabled/disabled the Border Routing Manager. + * + */ + otError SetEnabled(bool aEnabled); + /** * This method receives an ICMPv6 message on the infrastructure interface. * @@ -111,8 +125,9 @@ private: { kMaxOmrPrefixNum = OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES, // The maximum number of the OMR prefixes to advertise. - kOmrPrefixLength = OT_IP6_PREFIX_BITSIZE, // The length of an OMR prefix. In bits. - kOnLinkPrefixLength = OT_IP6_PREFIX_BITSIZE, // The length of an On-link prefix. In bits. + kMaxDiscoveredPrefixNum = 8u, // The maximum number of prefixes to discover on the infra link. + kOmrPrefixLength = OT_IP6_PREFIX_BITSIZE, // The length of an OMR prefix. In bits. + kOnLinkPrefixLength = OT_IP6_PREFIX_BITSIZE, // The length of an On-link prefix. In bits. }; enum : uint32_t @@ -137,105 +152,36 @@ private: kMaxRtrSolicitations = 3, // The Maximum number of Router Solicitations before sending Router Advertisements. }; + // This struct represents an external prefix which is + // discovered on the infrastructure interface. + struct ExternalPrefix : public Clearable + { + Ip6::Prefix mPrefix; + TimeMilli mExpireTime; + bool mIsOnLinkPrefix; + }; + void Start(void); void Stop(void); void HandleNotifierEvents(Events aEvents); bool IsInitialized(void) const { return mInfraIfIndex != 0; } + bool IsEnabled(void) const { return mEnabled; } otError LoadOrGenerateRandomOmrPrefix(void); otError LoadOrGenerateRandomOnLinkPrefix(void); - /** - * This method tells whether the first prefix is numerically smaller than the second one. - * - * @note The caller must guarantee that the two prefix has the same length. - * - */ - static bool IsPrefixSmallerThan(const Ip6::Prefix &aFirstPrefix, const Ip6::Prefix &aSecondPrefix); + const Ip6::Prefix *EvaluateOnLinkPrefix(void); - static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix); - static bool IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix); - - /** - * 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 prefix in the Thread network. - * - * @sa EvaluateOmrPrefix - * @sa EvaluateOnLinkPrefix - * @sa PublishLocalOmrPrefix - * @sa UnpublishLocalOmrPrefix - * - */ - void EvaluateRoutingPolicy(void); - - /** - * This method evaluates the OMR prefix for the Thread Network. - * - * @param[out] aNewOmrPrefixes An array of the new OMR prefixes should be advertised. - * MUST not be nullptr. - * @param[in] aMaxOmrPrefixNum The maximum number of OMR prefixes that @p aNewOmrPrefixes can hold. - * - * @returns The number of the new OMR prefixes that should be advertised after this evaluation. - * - */ + void EvaluateRoutingPolicy(void); uint8_t EvaluateOmrPrefix(Ip6::Prefix *aNewOmrPrefixes, uint8_t aMaxOmrPrefixNum); - - /** - * This method evaluates the on-link prefix for the infra link. - * - * @returns A pointer to the new on-link prefix should be advertised. - * nullptr if we should no longer advertise an on-link prefix. - * - */ - const Ip6::Prefix *EvaluateOnLinkPrefix(void) const; - - /** - * This method publishes the local OMR prefix in Thread network. - * - */ otError PublishLocalOmrPrefix(void); - - /** - * This method unpublishes the local OMR prefix. - * - */ - void UnpublishLocalOmrPrefix(void); - - /** - * This method starts sending Router Solicitations in random delay - * between 0 and kMaxRtrSolicitationDelay. - * - */ - void StartRouterSolicitation(void); - - /** - * This method sends Router Solicitation messages to discover on-link - * prefix on infra links. - * - * @sa HandleRouterAdvertisement - * - * @retval OT_ERROR_NONE Successfully sent the message. - * @retval OT_ERROR_FAILED Failed to send the message. - * - */ + void UnpublishLocalOmrPrefix(void); + otError AddExternalRoute(const Ip6::Prefix &aPrefix, otRoutePreference aRoutePreference); + void RemoveExternalRoute(const Ip6::Prefix &aPrefix); + void StartRouterSolicitation(void); otError SendRouterSolicitation(void); - - /** - * This method sends Router Advertisement messages to advertise - * on-link prefix and route for OMR prefix. - * - * @param[in] aNewOmrPrefixes A pointer to an array of the new OMR prefixes to be advertised. - * @p aNewOmrPrefixNum must be zero if this argument is nullptr. - * @param[in] aNewOmrPrefixNum The number of the new OMR prefixes to be advertised. - * Zero means we should stop advertising OMR prefixes. - * @param[in] aOnLinkPrefix A pointer to the new on-link prefix to be advertised. - * nullptr means we should stop advertising on-link prefix. - * - */ - void SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, - uint8_t aNewOmrPrefixNum, - const Ip6::Prefix *aNewOnLinkPrefix); + void SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, + uint8_t aNewOmrPrefixNum, + const Ip6::Prefix *aNewOnLinkPrefix); static void HandleRouterAdvertisementTimer(Timer &aTimer); void HandleRouterAdvertisementTimer(void); @@ -243,61 +189,63 @@ private: static void HandleRouterSolicitTimer(Timer &aTimer); void HandleRouterSolicitTimer(void); - static void HandleDiscoveredOnLinkPrefixInvalidTimer(Timer &aTimer); - void HandleDiscoveredOnLinkPrefixInvalidTimer(void); + static void HandleDiscoveredPrefixInvalidTimer(Timer &aTimer); + void HandleDiscoveredPrefixInvalidTimer(void); void HandleRouterSolicit(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); void HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); + bool UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption &aPio); + bool UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &aRio); + bool InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix = nullptr, bool aIsOnLinkPrefix = true); + void InvalidateAllDiscoveredPrefixes(void); + bool AddDiscoveredPrefix(const Ip6::Prefix &aPrefix, + bool aIsOnLinkPrefix, + uint32_t aLifetime, + otRoutePreference aRoutePreference = OT_ROUTE_PREFERENCE_MED); - static bool ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum); - + // Decides the first prefix is numerically smaller than the second one. + static bool IsPrefixSmallerThan(const Ip6::Prefix &aFirstPrefix, const Ip6::Prefix &aSecondPrefix); + static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix); + static bool IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix); + static bool ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum); static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime); bool mIsRunning; uint32_t mInfraIfIndex; + bool mEnabled; - /** - * The OMR prefix loaded from local persistent storage or randomly generated - * if non is found in persistent storage. - * - */ + // The OMR prefix loaded from local persistent storage or randomly generated + // if non is found in persistent storage. Ip6::Prefix mLocalOmrPrefix; - /** - * The advertised OMR prefixes. - * - */ + // The advertised OMR prefixes. For a stable Thread network without + // manually configured OMR prefixes, there should be a single OMR prefix + // that is being advertised because each BRs will converge to the smallest + // OMR prefix sorted by method IsPrefixSmallerThan. If manually configured + // OMR prefixes exist, they will also be advertised on infra link. Ip6::Prefix mAdvertisedOmrPrefixes[kMaxOmrPrefixNum]; uint8_t mAdvertisedOmrPrefixNum; - /** - * The on-link prefix loaded from local persistent storage or randomly generated - * if non is found in persistent storage. - * - */ + // The on-link prefix loaded from local persistent storage or randomly generated + // if non is found in persistent storage. Ip6::Prefix mLocalOnLinkPrefix; - /** - * The advertised on-link prefix. - * - * Could only be nullptr or a pointer to mLocalOnLinkPrefix. - * - */ + // Could only be nullptr or a pointer to mLocalOnLinkPrefix. const Ip6::Prefix *mAdvertisedOnLinkPrefix; - /** - * The on-link prefix we discovered on the infra link. - * - */ - Ip6::Prefix mDiscoveredOnLinkPrefix; + // The array of prefixes discovered on the infra link. Those prefixes consist of + // on-link prefix(es) and OMR prefixes advertised by BRs in another Thread Network + // which is connected to the same infra link. + ExternalPrefix mDiscoveredPrefixes[kMaxDiscoveredPrefixNum]; + uint8_t mDiscoveredPrefixNum; + + TimerMilli mDiscoveredPrefixInvalidTimer; TimerMilli mRouterAdvertisementTimer; uint32_t mRouterAdvertisementCount; TimerMilli mRouterSolicitTimer; uint8_t mRouterSolicitCount; - - TimerMilli mDiscoveredOnLinkPrefixInvalidTimer; }; } // namespace BorderRouter diff --git a/src/core/thread/network_data.hpp b/src/core/thread/network_data.hpp index 38ad1735c..bc5f98ef0 100644 --- a/src/core/thread/network_data.hpp +++ b/src/core/thread/network_data.hpp @@ -157,6 +157,14 @@ public: */ Ip6::Prefix &GetPrefix(void) { return static_cast(mPrefix); } + /** + * This method sets the prefix. + * + * @param[in] aPrefix The prefix to set to. + * + */ + void SetPrefix(const Ip6::Prefix &aPrefix) { mPrefix = aPrefix; } + private: void SetFrom(Instance & aInstance, const PrefixTlv & aPrefixTlv, diff --git a/src/posix/platform/infra_if.cpp b/src/posix/platform/infra_if.cpp index 8184b522a..efba5e485 100644 --- a/src/posix/platform/infra_if.cpp +++ b/src/posix/platform/infra_if.cpp @@ -51,12 +51,19 @@ #include #include "common/code_utils.hpp" +#include "lib/platform/exit_code.h" static char sInfraIfName[IFNAMSIZ]; static uint32_t sInfraIfIndex = 0; static int sInfraIfIcmp6Socket = -1; static otIp6Address sInfraIfLinkLocalAddr; +const otIp6Address *otPlatInfraIfGetLinkLocalAddress(uint32_t aInfraIfIndex) +{ + VerifyOrDie(aInfraIfIndex == sInfraIfIndex, OT_EXIT_FAILURE); + return &sInfraIfLinkLocalAddr; +} + otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, const otIp6Address *aDestAddress, const uint8_t * aBuffer, @@ -131,8 +138,9 @@ exit: return error; } -static void InitLinkLocalAddress(void) +static otError InitLinkLocalAddress(void) { + otError error; struct ifaddrs *ifAddrs = nullptr; if (getifaddrs(&ifAddrs) < 0) @@ -154,11 +162,16 @@ static void InitLinkLocalAddress(void) if (IN6_IS_ADDR_LINKLOCAL(&ip6Addr->sin6_addr)) { memcpy(&sInfraIfLinkLocalAddr, &ip6Addr->sin6_addr, sizeof(sInfraIfLinkLocalAddr)); - break; + ExitNow(error = OT_ERROR_NONE); } } + otLogCritPlat("cannot find IPv6 link-local address for interface %s", sInfraIfName); + error = OT_ERROR_NOT_FOUND; + +exit: freeifaddrs(ifAddrs); + return error; } void platformInfraIfInit(otInstance *aInstance, const char *aIfName) @@ -251,7 +264,7 @@ void platformInfraIfInit(otInstance *aInstance, const char *aIfName) } sInfraIfIcmp6Socket = sock; - InitLinkLocalAddress(); + SuccessOrDie(InitLinkLocalAddress()); } void platformInfraIfDeinit(void) @@ -341,8 +354,6 @@ void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet) // the hoplimit must be 255 and the source address must be a link-local address. VerifyOrExit(hopLimit == 255 && IN6_IS_ADDR_LINKLOCAL(&srcAddr.sin6_addr)); - // Drop multicast messages sent by ourselves. - VerifyOrExit(!otIp6IsAddressEqual(&sInfraIfLinkLocalAddr, reinterpret_cast(&srcAddr.sin6_addr))); otPlatInfraIfRecvIcmp6Nd(aInstance, ifIndex, reinterpret_cast(&srcAddr.sin6_addr), buffer, bufferLength); diff --git a/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py b/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py index 2e96ba9aa..f67d49bbe 100755 --- a/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py +++ b/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py @@ -41,14 +41,15 @@ import thread_cert # ----------------(eth)------------------ # | | | # BR1 (Leader) ----- BR2 HOST -# | -# ED1 +# | | +# ROUTER1 ROUTER2 # BR1 = 1 ROUTER1 = 2 BR2 = 3 -HOST = 4 +ROUTER2 = 4 +HOST = 5 CHANNEL1 = 18 @@ -74,12 +75,19 @@ class MultiBorderRouters(thread_cert.TestCase): }, BR2: { 'name': 'BR_2', - 'allowlist': [BR1], + 'allowlist': [BR1, ROUTER2], 'is_otbr': True, 'version': '1.2', 'channel': CHANNEL1, 'router_selection_jitter': 2, }, + ROUTER2: { + 'name': 'Router_2', + 'allowlist': [BR2], + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 2, + }, HOST: { 'name': 'Host', 'is_host': True @@ -87,7 +95,7 @@ class MultiBorderRouters(thread_cert.TestCase): } def test(self): - self.nodes[HOST].start(start_radvd=True, prefix=config.ONLINK_PREFIX, slaac=True) + self.nodes[HOST].start(start_radvd=False) self.simulator.go(5) self.nodes[BR1].start() @@ -102,12 +110,70 @@ class MultiBorderRouters(thread_cert.TestCase): self.simulator.go(5) self.assertEqual('router', self.nodes[BR2].get_state()) + self.nodes[ROUTER2].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER2].get_state()) + + # + # Case 1. bi-directional connectivity when there are two BRs. + # + self.simulator.go(10) self.collect_ipaddrs() logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) logging.info("BR2 addrs: %r", self.nodes[BR2].get_addrs()) + logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1) + + br1_omr_prefix = self.nodes[BR1].get_prefixes()[0] + + # Each BR should independently register an external route for the on-link prefix. + self.assertTrue(len(self.nodes[BR1].get_routes()) == 2) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 2) + self.assertTrue(len(self.nodes[BR2].get_routes()) == 2) + self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 2) + + external_route = self.nodes[BR1].get_routes()[0] + br1_on_link_prefix = external_route.split(' ')[0] + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)) == 1) + + # Router1 and Router2 can ping each other inside the Thread network. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + + # Both Router1 and Router2 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + + # + # Case 2. Another BR continues providing Border Routing when current one is disabled. + # + + self.nodes[BR1].disable_br() + + self.simulator.go(15) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("BR2 addrs: %r", self.nodes[BR2].get_addrs()) + logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs()) logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2) @@ -115,23 +181,36 @@ class MultiBorderRouters(thread_cert.TestCase): self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1) + + br2_omr_prefix = self.nodes[BR1].get_prefixes()[0] + self.assertNotEqual(br1_omr_prefix, br2_omr_prefix) + + # Only BR2 will register external route for the on-link prefix. + self.assertTrue(len(self.nodes[BR1].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1) + self.assertTrue(len(self.nodes[BR2].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 1) + + br2_external_route = self.nodes[BR2].get_routes()[0] + br2_on_link_prefix = br2_external_route.split(' ')[0] self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) - self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) - # Router1 and BR2 can ping each other inside the Thread network. - self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) - self.assertTrue(self.nodes[BR2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + self.assertTrue(len(self.nodes[HOST].get_matched_ula_addresses(br2_on_link_prefix)) == 1) - # Both Router1 and BR2 can ping to/from the Host on infra link. - self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) - self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], - backbone=True)) - self.assertTrue(self.nodes[BR2].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) - self.assertTrue(self.nodes[HOST].ping(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], - backbone=True)) + # Router1 and Router2 can ping each other inside the Thread network. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + + # Both Router1 and Router2 can ping to/from the Host on infra link. + for router in [ROUTER1, ROUTER2]: + self.assertTrue(self.nodes[router].ping(self.nodes[HOST].get_matched_ula_addresses(br2_on_link_prefix)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[router].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) if __name__ == '__main__': diff --git a/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py b/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py index 3e462a6ac..a05432da8 100755 --- a/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py +++ b/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py @@ -40,7 +40,7 @@ import thread_cert # | | # BR1 BR2 # | | -# ED1 ED2 +# ROUTER1 ROUTER2 # # Thread Net1 Thread Net2 # @@ -119,6 +119,25 @@ class MultiThreadNetworks(thread_cert.TestCase): self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1) self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1) + br1_omr_prefix = self.nodes[BR1].get_prefixes()[0] + br2_omr_prefix = self.nodes[BR2].get_prefixes()[0] + + self.assertNotEqual(br1_omr_prefix, br2_omr_prefix) + + # Each BR should independently register an external route for the on-link prefix + # and OMR prefix in another Thread Network. + self.assertTrue(len(self.nodes[BR1].get_routes()) == 2) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 2) + self.assertTrue(len(self.nodes[BR2].get_routes()) == 2) + self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 2) + + br1_external_routes = self.nodes[BR1].get_routes() + br2_external_routes = self.nodes[BR2].get_routes() + + br1_external_routes.sort() + br2_external_routes.sort() + self.assertNotEqual(br1_external_routes, br2_external_routes) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) diff --git a/tests/scripts/thread-cert/border_routing/test_single_border_router.py b/tests/scripts/thread-cert/border_routing/test_single_border_router.py index ee598479b..ea7dda767 100755 --- a/tests/scripts/thread-cert/border_routing/test_single_border_router.py +++ b/tests/scripts/thread-cert/border_routing/test_single_border_router.py @@ -93,6 +93,10 @@ class SingleBorderRouter(thread_cert.TestCase): self.simulator.go(5) self.assertEqual('router', self.nodes[ROUTER1].get_state()) + # + # Case 1. There is no OMR prefix or on-link prefix. + # + self.simulator.go(10) self.collect_ipaddrs() @@ -100,22 +104,34 @@ class SingleBorderRouter(thread_cert.TestCase): logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) - self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2) - self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1) + + omr_prefix = self.nodes[BR1].get_prefixes()[0] + external_route = self.nodes[BR1].get_routes()[0] self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + br1_omr_address = self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0] + router1_omr_address = self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0] + host_ula_address = self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0] + # Router1 can ping to/from the Host on infra link. self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True)) - # Add two on-mesh prefix on BR1, so that - # it will deregister its random-generated OMR prefix. + # + # Case 2. User adds smaller on-mesh prefix. + # 1. Should deregister our local OMR prefix. + # 2. Should re-register our local OMR prefix when user prefix + # is removed. + # + self.nodes[BR1].add_prefix(ON_MESH_PREFIX1) self.nodes[BR1].add_prefix(ON_MESH_PREFIX2) self.nodes[BR1].register_netdata() @@ -131,6 +147,8 @@ class SingleBorderRouter(thread_cert.TestCase): self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 2) self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 2) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1) self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2) self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2) @@ -143,6 +161,100 @@ class SingleBorderRouter(thread_cert.TestCase): self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[1], backbone=True)) + # Remove user prefixes, should re-register local OMR prefix. + self.nodes[BR1].remove_prefix(ON_MESH_PREFIX1) + self.nodes[BR1].remove_prefix(ON_MESH_PREFIX2) + self.nodes[BR1].register_netdata() + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1) + + # The same local OMR and on-link prefix should be re-register. + self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0]) + self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0]) + self.assertEqual(external_route, self.nodes[BR1].get_routes()[0]) + self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0]) + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]) + self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]) + self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]) + + # Router1 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + + # + # Case 3. OMR and on-link prefixes should be removed when Border Routing is + # explicitly disabled and added when Border Routing is enabled again. + # + + self.nodes[BR1].disable_br() + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 0) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 0) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 0) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 0) + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0) + + # Per RFC 4862, the host will not immediately remove the ULA address, but deprecate it. + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + self.nodes[BR1].enable_br() + + # It takes around 10 seconds to start sending RA messages. + self.simulator.go(15) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1) + + # The same local OMR and on-link prefix should be re-registered. + self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0]) + self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0]) + self.assertEqual(external_route, self.nodes[BR1].get_routes()[0]) + self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0]) + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]) + self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]) + self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]) + + # Router1 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 09e3603f9..840b1f747 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1281,21 +1281,39 @@ class NodeImpl: self.send_command(cmd) self._expect_done() + def enable_br(self): + self.send_command('br enable') + self._expect('Done') + + def disable_br(self): + self.send_command('br disable') + self._expect('Done') + def get_prefixes(self): - netdata = self.netdata_show() - prefixes = [] + return self.get_netdata()['Prefixes'] - for i in range(1, len(netdata)): - if netdata[i].startswith("Routes:"): - break - prefixes.append(netdata[i]) - - return prefixes + def get_routes(self): + return self.get_netdata()['Routes'] def netdata_show(self): self.send_command('netdata show') return self._expect_command_output('netdata show') + def get_netdata(self): + raw_netdata = self.netdata_show() + netdata = {'Prefixes': [], 'Routes': [], 'Services': []} + key_list = ['Prefixes', 'Routes', 'Services'] + key = None + + for i in range(0, len(raw_netdata)): + keys = list(filter(raw_netdata[i].startswith, key_list)) + if keys != []: + key = keys[0] + elif key is not None: + netdata[key].append(raw_netdata[i]) + + return netdata + def add_route(self, prefix, stable=False, prf='med'): cmd = 'route add %s ' % prefix if stable: @@ -2157,6 +2175,17 @@ class HostNode(LinuxHost, OtbrDocker): def __repr__(self): return f'Host<{self.nodeid}>' + def get_matched_ula_addresses(self, prefix): + """Get the IPv6 addresses that matches given prefix. + """ + + addrs = [] + for addr in self.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA): + if addr.startswith(prefix.split('::')[0]): + addrs.append(addr) + + return addrs + def get_ip6_address(self, address_type: config.ADDRESS_TYPE): """Get specific type of IPv6 address configured on thread device.