diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 6afef68f9..a429b7a59 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -89,6 +89,7 @@ ot_option(OT_BORDER_AGENT OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE "border agent") ot_option(OT_BORDER_AGENT_ID OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE "create and save border agent ID") ot_option(OT_BORDER_ROUTER OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE "border router") ot_option(OT_BORDER_ROUTING OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE "border routing") +ot_option(OT_BORDER_ROUTING_DHCP6_PD OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE "dhcpv6 pd support in border routing") ot_option(OT_BORDER_ROUTING_COUNTERS OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE "border routing counters") ot_option(OT_CHANNEL_MANAGER OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE "channel manager") ot_option(OT_CHANNEL_MONITOR OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE "channel monitor") diff --git a/include/Makefile.am b/include/Makefile.am index 6ce6a7dff..f87e5b159 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -100,6 +100,7 @@ dist_openthread_HEADERS = $(openthread_headers) ot_platform_headers = \ openthread/platform/alarm-micro.h \ openthread/platform/alarm-milli.h \ + openthread/platform/border_routing.h \ openthread/platform/crypto.h \ openthread/platform/debug_uart.h \ openthread/platform/diag.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index fba1dfe9c..1c376ad87 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -84,6 +84,7 @@ source_set("openthread") { "ping_sender.h", "platform/alarm-micro.h", "platform/alarm-milli.h", + "platform/border_routing.h", "platform/crypto.h", "platform/debug_uart.h", "platform/diag.h", diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index f6384a8f8..2de6dde3a 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -118,6 +118,17 @@ typedef enum OT_BORDER_ROUTING_STATE_RUNNING, ///< Routing Manager is initialized, enabled, and running. } otBorderRoutingState; +/** + * This enumeration represents the state of DHCPv6 Prefix Delegation State. + * + */ +typedef enum +{ + OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED, ///< DHCPv6 PD is disabled on the border router. + OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED, ///< DHCPv6 PD in enabled but won't try to request and publish a prefix. + OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING, ///< DHCPv6 PD is enabled and will try to request and publish a prefix. +} otBorderRoutingDhcp6PdState; + /** * Initializes the Border Routing Manager on given infrastructure interface. * @@ -209,15 +220,40 @@ void otBorderRoutingClearRouteInfoOptionPreference(otInstance *aInstance); * Thread network if there isn't already an OMR prefix. This prefix can be reached * from the local Wi-Fi or Ethernet network. * + * Note: When DHCPv6 PD is enabled, the border router may publish the prefix from + * DHCPv6 PD. + * * @param[in] aInstance A pointer to an OpenThread instance. * @param[out] aPrefix A pointer to where the prefix will be output to. * * @retval OT_ERROR_INVALID_STATE The Border Routing Manager is not initialized yet. * @retval OT_ERROR_NONE Successfully retrieved the OMR prefix. * + * @sa otBorderRoutingGetPdOmrPrefix + * */ otError otBorderRoutingGetOmrPrefix(otInstance *aInstance, otIp6Prefix *aPrefix); +/** + * Gets the DHCPv6 Prefix Delegation (PD) provided off-mesh-routable (OMR) prefix. + * + * Only mPrefix, mValidLifetime and mPreferredLifetime fields are used in the returned prefix info. + * + * `OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE` must be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[out] aPrefixInfo A pointer to where the prefix info will be output to. + * + * @retval OT_ERROR_NONE Successfully retrieved the OMR prefix. + * @retval OT_ERROR_INVALID_STATE The Border Routing Manager is not initialized yet. + * @retval OT_ERROR_NOT_FOUND There are no valid PD prefix on this BR. + * + * @sa otBorderRoutingGetOmrPrefix + * @sa otPlatBorderRoutingProcessIcmp6Ra + * + */ +otError otBorderRoutingGetPdOmrPrefix(otInstance *aInstance, otBorderRoutingPrefixTableEntry *aPrefixInfo); + /** * Gets the currently favored Off-Mesh-Routable (OMR) Prefix. * @@ -326,6 +362,17 @@ otError otBorderRoutingGetNextPrefixTableEntry(otInstance otBorderRoutingPrefixTableIterator *aIterator, otBorderRoutingPrefixTableEntry *aEntry); +/** + * Enables / Disables DHCPv6 Prefix Delegation. + * + * `OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE` must be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aEnabled Whether to accept platform generated RA messages. + * + */ +void otBorderRoutingDhcp6PdSetEnabled(otInstance *aInstance, bool aEnabled); + /** * @} * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 64ff53588..d7a1e80bc 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 (328) +#define OPENTHREAD_API_VERSION (329) /** * @addtogroup api-instance diff --git a/include/openthread/platform/border_routing.h b/include/openthread/platform/border_routing.h new file mode 100644 index 000000000..eeb825c0d --- /dev/null +++ b/include/openthread/platform/border_routing.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, 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 + * @brief + * This file includes the platform abstraction for border routing manager. + */ + +#ifndef OPENTHREAD_PLATFORM_BORDER_ROUTER_H_ +#define OPENTHREAD_PLATFORM_BORDER_ROUTER_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Handles ICMP6 RA messages received on the Thread interface on the platform. + * + * The `aMessage` should point to a buffer of a valid ICMPv6 message (without IP headers) with router advertisement as + * the value of type field of the message. + * + * When DHCPv6 PD is disabled, the message will be dropped silently. + * + * Note: RA messages will not be forwarded into Thread networks, while for many platforms, RA messages is the way of + * distributing a prefix and other infomations to the downstream network. The typical usecase of this function is to + * handle the router advertisement messages sent by the platform as a result of DHCPv6 Prefix Delegation. + * + * Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to an ICMPv6 RouterAdvertisement message. + * @param[in] aLength The length of ICMPv6 RouterAdvertisement message. + * + */ +extern void otPlatBorderRoutingProcessIcmp6Ra(otInstance *aInstance, const uint8_t *aMessage, uint16_t aLength); + +#ifdef __cplusplus +} +#endif + +#endif // OPENTHREAD_PLATFORM_BORDER_ROUTER_H_ diff --git a/script/make-pretty b/script/make-pretty index 28abdf014..af73554ce 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -93,6 +93,7 @@ OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_BORDER_AGENT=ON' '-DOT_BORDER_ROUTER=ON' '-DOT_BORDER_ROUTING=ON' + '-DOT_BORDER_ROUTING_DHCP6_PD=ON' '-DOT_CHANNEL_MANAGER=ON' '-DOT_CHANNEL_MONITOR=ON' '-DOT_COAP=ON' diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index 2731e02c0..1e2c8de28 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -36,6 +36,7 @@ #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE #include +#include #include "border_router/routing_manager.hpp" #include "common/instance.hpp" @@ -79,6 +80,15 @@ otError otBorderRoutingGetOmrPrefix(otInstance *aInstance, otIp6Prefix *aPrefix) return AsCoreType(aInstance).Get().GetOmrPrefix(AsCoreType(aPrefix)); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +otError otBorderRoutingGetPdOmrPrefix(otInstance *aInstance, otBorderRoutingPrefixTableEntry *aPrefixInfo) +{ + AssertPointerIsNotNull(aPrefixInfo); + + return AsCoreType(aInstance).Get().GetPdOmrPrefix(*aPrefixInfo); +} +#endif + otError otBorderRoutingGetFavoredOmrPrefix(otInstance *aInstance, otIp6Prefix *aPrefix, otRoutePreference *aPreference) { otError error; @@ -145,4 +155,17 @@ otError otBorderRoutingGetNextPrefixTableEntry(otInstance return AsCoreType(aInstance).Get().GetNextPrefixTableEntry(*aIterator, *aEntry); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +void otBorderRoutingDhcp6PdSetEnabled(otInstance *aInstance, bool aEnabled) +{ + AsCoreType(aInstance).Get().SetDhcp6PdEnabled(aEnabled); +} + +otBorderRoutingDhcp6PdState otBorderRoutingDhcp6PdGetState(otInstance *aInstance) +{ + return static_cast( + AsCoreType(aInstance).Get().GetDhcp6PdState()); +} +#endif + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 77169287d..528c9aade 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include "common/code_utils.hpp" @@ -47,11 +48,14 @@ #include "common/locator_getters.hpp" #include "common/log.hpp" #include "common/num_utils.hpp" +#include "common/numeric_limits.hpp" #include "common/random.hpp" #include "common/settings.hpp" #include "meshcop/extended_panid.hpp" #include "net/ip6.hpp" #include "net/nat64_translator.hpp" +#include "net/nd6.hpp" +#include "thread/mle_router.hpp" #include "thread/network_data_leader.hpp" #include "thread/network_data_local.hpp" #include "thread/network_data_notifier.hpp" @@ -75,6 +79,9 @@ RoutingManager::RoutingManager(Instance &aInstance) , mRoutePublisher(aInstance) #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE , mNat64PrefixManager(aInstance) +#endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + , mPdPrefixManager(aInstance) #endif , mRsSender(aInstance) , mDiscoveredPrefixStaleTimer(aInstance) @@ -183,12 +190,25 @@ Error RoutingManager::GetOmrPrefix(Ip6::Prefix &aPrefix) const Error error = kErrorNone; VerifyOrExit(IsInitialized(), error = kErrorInvalidState); - aPrefix = mOmrPrefixManager.GetLocalPrefix().GetPrefix(); + aPrefix = mOmrPrefixManager.GetGeneratedPrefix(); exit: return error; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +Error RoutingManager::GetPdOmrPrefix(PrefixTableEntry &aPrefixInfo) const +{ + Error error = kErrorNone; + + VerifyOrExit(IsInitialized(), error = kErrorInvalidState); + error = mPdPrefixManager.GetPrefixInfo(aPrefixInfo); + +exit: + return error; +} +#endif + Error RoutingManager::GetFavoredOmrPrefix(Ip6::Prefix &aPrefix, RoutePreference &aPreference) const { Error error = kErrorNone; @@ -1939,13 +1959,11 @@ RoutingManager::OmrPrefixManager::OmrPrefixManager(Instance &aInstance) void RoutingManager::OmrPrefixManager::Init(const Ip6::Prefix &aBrUlaPrefix) { - mLocalPrefix.mPrefix = aBrUlaPrefix; - mLocalPrefix.mPrefix.SetSubnetId(kOmrPrefixSubnetId); - mLocalPrefix.mPrefix.SetLength(kOmrPrefixLength); - mLocalPrefix.mPreference = NetworkData::kRoutePreferenceLow; - mLocalPrefix.mIsDomainPrefix = false; + mGeneratedPrefix = aBrUlaPrefix; + mGeneratedPrefix.SetSubnetId(kOmrPrefixSubnetId); + mGeneratedPrefix.SetLength(kOmrPrefixLength); - LogInfo("Generated local OMR prefix: %s", mLocalPrefix.mPrefix.ToString().AsCString()); + LogInfo("Generated local OMR prefix: %s", mGeneratedPrefix.ToString().AsCString()); } void RoutingManager::OmrPrefixManager::Start(void) { DetermineFavoredPrefix(); } @@ -1985,11 +2003,42 @@ void RoutingManager::OmrPrefixManager::Evaluate(void) DetermineFavoredPrefix(); - // Decide if we need to add or remove our local OMR prefix. - - if (mFavoredPrefix.IsEmpty()) + // Determine the local prefix and remove outdated prefix published by us. +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + if (Get().mPdPrefixManager.HasPrefix()) { - LogInfo("No favored OMR prefix found in Thread network"); + if (mLocalPrefix.GetPrefix() != Get().mPdPrefixManager.GetPrefix()) + { + RemoveLocalFromNetData(); + mLocalPrefix.mPrefix = Get().mPdPrefixManager.GetPrefix(); + mLocalPrefix.mPreference = RoutePreference::kRoutePreferenceMedium; + mLocalPrefix.mIsDomainPrefix = false; + LogInfo("Setting local OMR prefix to PD prefix: %s", mLocalPrefix.GetPrefix().ToString().AsCString()); + } + } + else +#endif + if (mLocalPrefix.GetPrefix() != mGeneratedPrefix) + { + RemoveLocalFromNetData(); + mLocalPrefix.mPrefix = mGeneratedPrefix; + mLocalPrefix.mPreference = RoutePreference::kRoutePreferenceLow; + mLocalPrefix.mIsDomainPrefix = false; + LogInfo("Setting local OMR prefix to generated prefix: %s", mLocalPrefix.GetPrefix().ToString().AsCString()); + } + + // Decide if we need to add or remove our local OMR prefix. + if (mFavoredPrefix.IsEmpty() || mFavoredPrefix.GetPreference() < mLocalPrefix.GetPreference()) + { + if (mFavoredPrefix.IsEmpty()) + { + LogInfo("No favored OMR prefix found in Thread network."); + } + else + { + LogInfo("Replacing favored OMR prefix %s with higher preference local prefix %s.", + mFavoredPrefix.GetPrefix().ToString().AsCString(), mLocalPrefix.GetPrefix().ToString().AsCString()); + } // The `mFavoredPrefix` remains empty if we fail to publish // the local OMR prefix. @@ -3153,6 +3202,161 @@ exit: return; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +RoutingManager::PdPrefixManager::PdPrefixManager(Instance &aInstance) + : InstanceLocator(aInstance) + , mEnabled(false) + , mTimer(aInstance) +{ + mPrefix.Clear(); +} + +Error RoutingManager::PdPrefixManager::GetPrefixInfo(PrefixTableEntry &aInfo) const +{ + Error error = kErrorNone; + + VerifyOrExit(IsRunning() && HasPrefix(), error = kErrorNotFound); + + aInfo.mPrefix = mPrefix.GetPrefix(); + aInfo.mValidLifetime = mPrefix.GetValidLifetime(); + aInfo.mPreferredLifetime = mPrefix.GetPreferredLifetime(); + aInfo.mMsecSinceLastUpdate = TimerMilli::GetNow() - mPrefix.GetLastUpdateTime(); + +exit: + return error; +} + +void RoutingManager::PdPrefixManager::WithdrawPrefix(void) +{ + VerifyOrExit(HasPrefix()); + + LogInfo("Withdrew platform provided outdated prefix: %s", mPrefix.GetPrefix().ToString().AsCString()); + + mPrefix.Clear(); + mTimer.Stop(); + + Get().ScheduleRoutingPolicyEvaluation(kImmediately); + +exit: + return; +} + +void RoutingManager::PdPrefixManager::ProcessPlatformGeneratedRa(const uint8_t *aRouterAdvert, const uint16_t aLength) +{ + Error error = kErrorNone; + Ip6::Nd::RouterAdvertMessage::Icmp6Packet packet; + + VerifyOrExit(mEnabled, LogWarn("Ignore platform generated RA since PD is disabled.")); + packet.Init(aRouterAdvert, aLength); + error = Process(Ip6::Nd::RouterAdvertMessage(packet)); + +exit: + if (error != kErrorNone) + { + LogCrit("Failed to process platform generated ND OnMeshPrefix: %s", ErrorToString(error)); + } +} + +Error RoutingManager::PdPrefixManager::Process(const Ip6::Nd::RouterAdvertMessage &aMessage) +{ + Error error = kErrorNone; + DiscoveredPrefixTable::Entry favoredEntry; + bool currentPrefixUpdated = false; + + VerifyOrExit(aMessage.IsValid(), error = kErrorParse); + favoredEntry.Clear(); + + for (const Ip6::Nd::Option &option : aMessage) + { + DiscoveredPrefixTable::Entry entry; + + if (option.GetType() != Ip6::Nd::Option::Type::kTypePrefixInfo || + !static_cast(option).IsValid()) + { + continue; + } + + entry.SetFrom(static_cast(option)); + + if (!IsValidPdPrefix(entry.GetPrefix())) + { + LogWarn("PdPrefixManager: Ignore invalid PIO entry %s", entry.GetPrefix().ToString().AsCString()); + continue; + } + + entry.mPrefix.SetLength(kOmrPrefixLength); + entry.mPrefix.Tidy(); + + // The platform may send another RA message to announce that the current prefix we are using is no longer + // preferred or no longer valid. + if (entry.GetPrefix() == GetPrefix()) + { + currentPrefixUpdated = true; + mPrefix = entry; + } + + if (entry.IsDeprecated()) + { + continue; + } + + // Some platforms may delegate us more than one prefixes. We will pick the smallest one. This is a simple rule + // to pick the GUA prefix from the RA messages since GUA prefixes (2000::/3) are always smaller than ULA + // prefixes (fc00::/7). + if (favoredEntry.GetPrefix().GetLength() == 0 || entry.GetPrefix() < favoredEntry.GetPrefix()) + { + favoredEntry = entry; + } + } + + if (currentPrefixUpdated && mPrefix.IsDeprecated()) + { + LogInfo("PdPrefixManager: Prefix %s is deprecated", mPrefix.GetPrefix().ToString().AsCString()); + mPrefix.Clear(); + Get().ScheduleRoutingPolicyEvaluation(kImmediately); + } + + if (!HasPrefix() || (favoredEntry.GetPrefix().GetLength() != 0 && favoredEntry.GetPrefix() < mPrefix.GetPrefix())) + { + mPrefix = favoredEntry; + Get().ScheduleRoutingPolicyEvaluation(kImmediately); + } + +exit: + if (HasPrefix()) + { + mTimer.FireAt(mPrefix.GetStaleTime()); + } + else + { + mTimer.Stop(); + } + + return error; +} + +void RoutingManager::PdPrefixManager::SetEnabled(bool aEnabled) +{ + VerifyOrExit(mEnabled != aEnabled); + + mEnabled = aEnabled; + if (!aEnabled) + { + WithdrawPrefix(); + } + + LogInfo("PdPrefixManager is %s", aEnabled ? "enabled" : "disabled"); + +exit: + return; +} + +extern "C" void otPlatBorderRoutingProcessIcmp6Ra(otInstance *aInstance, const uint8_t *aMessage, uint16_t aLength) +{ + AsCoreType(aInstance).Get().ProcessPlatfromGeneratedRa(aMessage, aLength); +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + } // namespace BorderRouter } // namespace ot diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 17fcd2f7d..b1f554b46 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -55,6 +55,7 @@ #include "common/error.hpp" #include "common/linked_list.hpp" #include "common/locator.hpp" +#include "common/message.hpp" #include "common/notifier.hpp" #include "common/pool.hpp" #include "common/string.hpp" @@ -111,6 +112,17 @@ public: kStateRunning = OT_BORDER_ROUTING_STATE_RUNNING, ///< Initialized, enabled, and running. }; + /** + * This enumeration represents the states of DHCPv6 PD in `RoutingManager`. + * + */ + enum Dhcp6PdState : uint8_t + { + kDhcp6PdStateDisabled = OT_BORDER_ROUTING_STATE_DISABLED, ///< Disabled. + kDhcp6PdStateStopped = OT_BORDER_ROUTING_STATE_STOPPED, ///< Enabled, but currently stopped. + kDhcp6PdStateRunning = OT_BORDER_ROUTING_STATE_RUNNING, ///< Enabled, and running. + }; + /** * Initializes the routing manager. * @@ -217,7 +229,7 @@ public: void ClearRouteInfoOptionPreference(void); /** - * Returns the local off-mesh-routable (OMR) prefix. + * Returns the local generated off-mesh-routable (OMR) prefix. * * The randomly generated 64-bit prefix will be added to the Thread Network Data if there isn't already an OMR * prefix. @@ -230,6 +242,22 @@ public: */ Error GetOmrPrefix(Ip6::Prefix &aPrefix) const; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + /** + * Returns the platform provided off-mesh-routable (OMR) prefix. + * + * The prefix is extracted from the platform generated RA messages handled by `ProcessPlatfromGeneratedNd()`. + * + * @param[out] aPrefixInfo A reference to where the prefix info will be output to. + * + * @retval kErrorNone Successfully retrieved the OMR prefix. + * @retval kErrorNotFound There are no valid PD prefix on this BR. + * @retval kErrorInvalidState The Border Routing Manager is not initialized yet. + * + */ + Error GetPdOmrPrefix(PrefixTableEntry &aPrefixInfo) const; +#endif + /** * Returns the currently favored off-mesh-routable (OMR) prefix. * @@ -413,6 +441,41 @@ public: void HandleSrpServerAutoEnableMode(void); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + /** + * Handles a router advertisement message received on platform's Thread interface. + * + * Note: This method is a part of DHCPv6 PD support on Thread border routers. The message should be generated by the + * software like dnamasq, radvd, systemd-networkd on the platform as a part of the DHCPv6 prefix delegation process + * for distributing the prefix to the interfaces (links). + * + * @param[in] aRouterAdvert A pointer to the buffer of the router advertisement message. + * @param[in] aLength The length of the router advertisement message. + * + */ + void ProcessPlatfromGeneratedRa(const uint8_t *aRouterAdvert, uint16_t aLength) + { + mPdPrefixManager.ProcessPlatformGeneratedRa(aRouterAdvert, aLength); + } + + /** + * Enables / Disables the functions for DHCPv6 PD. + * + * @param[in] aEnabled Whether to accept platform generated RA messages. + * + */ + void SetDhcp6PdEnabled(bool aEnabled) { return mPdPrefixManager.SetEnabled(aEnabled); } + + /** + * Returns the state of accpeting RouterAdvertisement messages on platform interface. + * + * @retval kDhcp6PdStateRunning DHCPv6 PD should be enabled and running on this border router. + * @retval kDhcp6PdStateDisabled DHCPv6 PD should be disabled on this border router.. + * + */ + Dhcp6PdState GetDhcp6PdState(void) const { return mPdPrefixManager.GetState(); } +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + private: static constexpr uint8_t kMaxOnMeshPrefixes = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_ON_MESH_PREFIXES; @@ -472,6 +535,10 @@ private: void HandleDiscoveredPrefixTableEntryTimer(void) { mDiscoveredPrefixTable.HandleEntryTimer(); } void HandleDiscoveredPrefixTableRouterTimer(void) { mDiscoveredPrefixTable.HandleRouterTimer(); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + class PdPrefixManager; // For DiscoveredPrefixTable::Entry +#endif + class DiscoveredPrefixTable : public InstanceLocator { // This class maintains the discovered on-link and route prefixes @@ -492,6 +559,10 @@ private: // changes within the same flow of execution, the callback is // invoked after all the changes are processed. +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + friend class PdPrefixManager; // For DiscoveredPrefixTable::Entry +#endif + public: explicit DiscoveredPrefixTable(Instance &aInstance); @@ -527,6 +598,9 @@ private: { friend class LinkedListEntry; friend class Clearable; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + friend class PdPrefixManager; +#endif public: enum Type : uint8_t @@ -728,6 +802,7 @@ private: void Evaluate(void); void UpdateDefaultRouteFlag(bool aDefaultRoute); bool IsLocalAddedInNetData(void) const { return mIsLocalAddedInNetData; } + const Ip6::Prefix &GetGeneratedPrefix(void) const { return mGeneratedPrefix; } const OmrPrefix &GetLocalPrefix(void) const { return mLocalPrefix; } const FavoredOmrPrefix &GetFavoredPrefix(void) const { return mFavoredPrefix; } @@ -743,6 +818,7 @@ private: InfoString LocalToString(void) const; OmrPrefix mLocalPrefix; + Ip6::Prefix mGeneratedPrefix; FavoredOmrPrefix mFavoredPrefix; bool mIsLocalAddedInNetData; bool mDefaultRoute; @@ -965,6 +1041,53 @@ private: TimeMilli mStartTime; }; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + void HandlePdPrefixManagerTimer(void) { mPdPrefixManager.HandleTimer(); } + + class PdPrefixManager : public InstanceLocator + { + public: + // This class implements handling (including management of the lifetime) of the prefix obtained from platform's + // DHCPv6 PD client. We expect the platform will send ICMP6 RA messages to the platform's interface for the + // information of the prefix. + // This class manages the state of the PD prefix in OmrPrefixManager + + explicit PdPrefixManager(Instance &aInstance); + + void SetEnabled(bool aEnabled); + bool IsRunning(void) const { return GetState() == Dhcp6PdState::kDhcp6PdStateRunning; } + bool HasPrefix(void) const { return IsValidOmrPrefix(mPrefix.GetPrefix()); } + const Ip6::Prefix &GetPrefix(void) const { return mPrefix.GetPrefix(); } + Dhcp6PdState GetState(void) const + { + // TODO: We need to stop and inform the platform when there is already a GUA prefix advertised in the + // network. + return mEnabled ? kDhcp6PdStateRunning : kDhcp6PdStateDisabled; + } + + void ProcessPlatformGeneratedRa(const uint8_t *aRouterAdvert, uint16_t aLength); + Error GetPrefixInfo(PrefixTableEntry &aInfo) const; + void HandleTimer(void) { WithdrawPrefix(); } + + static bool IsValidPdPrefix(const Ip6::Prefix &aPrefix) + { + // We should accept ULA prefix since it could be used by the internet infrastructure like NAT64. + return aPrefix.GetLength() != 0 && aPrefix.GetLength() <= kOmrPrefixLength && !aPrefix.IsLinkLocal() && + !aPrefix.IsMulticast(); + } + + private: + Error Process(const Ip6::Nd::RouterAdvertMessage &aMessage); + void WithdrawPrefix(void); + + using PlatformOmrPrefixTimer = TimerMilliIn; + + bool mEnabled; + PlatformOmrPrefixTimer mTimer; + DiscoveredPrefixTable::Entry mPrefix; + }; +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + void EvaluateState(void); void Start(void); void Stop(void); @@ -1037,6 +1160,10 @@ private: Nat64PrefixManager mNat64PrefixManager; #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + PdPrefixManager mPdPrefixManager; +#endif + RaInfo mRaInfo; RsSender mRsSender; diff --git a/src/core/config/border_routing.h b/src/core/config/border_routing.h index 1327901ea..b016eef6c 100644 --- a/src/core/config/border_routing.h +++ b/src/core/config/border_routing.h @@ -104,4 +104,17 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT (60 * 1000) // (in msec). #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + * + * Specifies whether to support handling platform generated ND messages. + * + * The desired use case is the prefix will be allocated by other software on the interface, and they will advertise the + * assigned prefix to the thread interface via router advertisement messages. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 0 +#endif + #endif // CONFIG_BORDER_ROUTING_H_ diff --git a/src/core/net/nd6.hpp b/src/core/net/nd6.hpp index 1f4b2e645..9cf5c0803 100644 --- a/src/core/net/nd6.hpp +++ b/src/core/net/nd6.hpp @@ -532,6 +532,14 @@ public: */ RoutePreference GetDefaultRouterPreference(void) const; + /** + * This method returns the ICMPv6 message type. + * + * @returns The ICMPv6 message type. + * + */ + Icmp::Header::Type GetType(void) const { return static_cast(mType); } + private: // Router Advertisement Message // @@ -613,7 +621,11 @@ public: * @retval FALSE If the RA message is not valid. * */ - bool IsValid(void) const { return (mData.GetBytes() != nullptr) && (mData.GetLength() >= sizeof(Header)); } + bool IsValid(void) const + { + return (mData.GetBytes() != nullptr) && (mData.GetLength() >= sizeof(Header)) && + (GetHeader().GetType() == Icmp::Header::kTypeRouterAdvert); + } /** * Gets the RA message's header. diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 6f34920d4..a1b20f079 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -83,6 +83,14 @@ */ #define OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE 1 +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + * + * Define to 1 to enable ND handling on Thread interface on host. + * + */ +#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 1 + /** * @def OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE * diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index 93246fac0..7895dcedd 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -32,6 +32,7 @@ #include "test_util.hpp" #include +#include #include "border_router/routing_manager.hpp" #include "common/arg_macros.hpp" @@ -466,6 +467,13 @@ void LogRouterAdvert(const Icmp6Packet &aPacket) } } +void LogRouterAdvert(const uint8_t *aBuffer, size_t aLength) +{ + Icmp6Packet packet; + packet.Init(aBuffer, aLength); + LogRouterAdvert(packet); +} + const char *PreferenceToString(int8_t aPreference) { const char *str = ""; @@ -491,9 +499,14 @@ const char *PreferenceToString(int8_t aPreference) return str; } +void SendRouterAdvert(const Ip6::Address &aAddress, const uint8_t *aBuffer, uint16_t aLength) +{ + otPlatInfraIfRecvIcmp6Nd(sInstance, kInfraIfIndex, &aAddress, aBuffer, aLength); +} + void SendRouterAdvert(const Ip6::Address &aAddress, const Icmp6Packet &aPacket) { - otPlatInfraIfRecvIcmp6Nd(sInstance, kInfraIfIndex, &aAddress, aPacket.GetBytes(), aPacket.GetLength()); + SendRouterAdvert(aAddress, aPacket.GetBytes(), aPacket.GetLength()); } void SendNeighborAdvert(const Ip6::Address &aAddress, const Ip6::Nd::NeighborAdvertMessage &aNaMessage) @@ -662,21 +675,22 @@ struct DefaultRoute RoutePreference mPreference; }; -void SendRouterAdvert(const Ip6::Address &aRouterAddress, - const Pio *aPios, - uint16_t aNumPios, - const Rio *aRios, - uint16_t aNumRios, - const DefaultRoute &aDefaultRoute) +template +uint16_t BuildRouterAdvert(uint8_t (&aBuffer)[N], + const Pio *aPios, + uint16_t aNumPios, + const Rio *aRios, + uint16_t aNumRios, + const DefaultRoute &aDefaultRoute) { Ip6::Nd::RouterAdvertMessage::Header header; - uint8_t buffer[kMaxRaSize]; + uint16_t length; header.SetRouterLifetime(aDefaultRoute.mLifetime); header.SetDefaultRouterPreference(aDefaultRoute.mPreference); { - Ip6::Nd::RouterAdvertMessage raMsg(header, buffer); + Ip6::Nd::RouterAdvertMessage raMsg(header, aBuffer); for (; aNumPios > 0; aPios++, aNumPios--) { @@ -689,10 +703,25 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, SuccessOrQuit(raMsg.AppendRouteInfoOption(aRios->mPrefix, aRios->mValidLifetime, aRios->mPreference)); } - SendRouterAdvert(aRouterAddress, raMsg.GetAsPacket()); - Log("Sending RA from router %s", aRouterAddress.ToString().AsCString()); - LogRouterAdvert(raMsg.GetAsPacket()); + length = raMsg.GetAsPacket().GetLength(); } + + return length; +} + +void SendRouterAdvert(const Ip6::Address &aRouterAddress, + const Pio *aPios, + uint16_t aNumPios, + const Rio *aRios, + uint16_t aNumRios, + const DefaultRoute &aDefaultRoute) +{ + uint8_t buffer[kMaxRaSize]; + uint16_t length = BuildRouterAdvert(buffer, aPios, aNumPios, aRios, aNumRios, aDefaultRoute); + + SendRouterAdvert(aRouterAddress, buffer, length); + Log("Sending RA from router %s", aRouterAddress.ToString().AsCString()); + LogRouterAdvert(buffer, length); } template @@ -725,6 +754,17 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aD SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aDefaultRoute); } +template void SendRouterAdvertToBorderRoutingProcessIcmp6Ra(const Pio (&aPios)[kNumPios]) +{ + uint8_t buffer[kMaxRaSize]; + uint16_t length = + BuildRouterAdvert(buffer, aPios, kNumPios, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium)); + + otPlatBorderRoutingProcessIcmp6Ra(sInstance, buffer, length); + Log("Passing RA to otPlatBorderRoutingProcessIcmp6Ra"); + LogRouterAdvert(buffer, length); +} + struct OnLinkPrefix : public Pio { OnLinkPrefix(const Ip6::Prefix &aPrefix, @@ -3118,6 +3158,216 @@ void TestNat64PrefixSelection(void) } #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +void VerifyPdOmrPrefix(const Ip6::Prefix &aPrefix) +{ + otBorderRoutingPrefixTableEntry platformPrefixInfo; + + VerifyOrQuit(otBorderRoutingGetPdOmrPrefix(sInstance, &platformPrefixInfo) == OT_ERROR_NONE); + VerifyOrQuit(AsCoreType(&platformPrefixInfo.mPrefix) == aPrefix); +} + +void VerifyNoPdOmrPrefix() +{ + otBorderRoutingPrefixTableEntry platformPrefixInfo; + + VerifyOrQuit(otBorderRoutingGetPdOmrPrefix(sInstance, &platformPrefixInfo) == OT_ERROR_NOT_FOUND); +} + +void TestBorderRoutingProcessPlatfromGeneratedNd(void) +{ + Ip6::Prefix localOmr; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestBorderRoutingProcessPlatfromGeneratedNd"); + + InitTest(/* aEnableBorderRouting */ true); + + otBorderRoutingDhcp6PdSetEnabled(sInstance, true); + + { + SuccessOrQuit(sInstance->Get().GetOmrPrefix(localOmr)); + } + + // 0. Reject invalid RA. + Log("0. Invalid RA message."); + { + { + const uint8_t testInvalidRaMessage[] = { + 0x86, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + otPlatBorderRoutingProcessIcmp6Ra(sInstance, testInvalidRaMessage, sizeof(testInvalidRaMessage)); + VerifyNoPdOmrPrefix(); + } + + { + const uint8_t testInvalidRaMessage[] = { + 0x87, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + otPlatBorderRoutingProcessIcmp6Ra(sInstance, testInvalidRaMessage, sizeof(testInvalidRaMessage)); + VerifyNoPdOmrPrefix(); + } + + { + const uint8_t testRaMessageWithInvalidPrefix[] = { + 0x86, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x04, 0x41, 0xc0, 0x00, 0x00, 0x10, 0xe1, 0x00, 0x00, 0x04, 0xd2, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + otPlatBorderRoutingProcessIcmp6Ra(sInstance, testRaMessageWithInvalidPrefix, + sizeof(testRaMessageWithInvalidPrefix)); + VerifyNoPdOmrPrefix(); + } + } + + // 1. Publish a prefix, and wait until it expired. + Log("1. Simple RA message."); + { + Ip6::Prefix raPrefix = PrefixFromString("2001:db8:dead:beef::", 64); + + SendRouterAdvertToBorderRoutingProcessIcmp6Ra({Pio(raPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Add(raPrefix); + AdvanceTime(10000); + + VerifyPdOmrPrefix(raPrefix); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(1500000); + sExpectedRios.Clear(); + VerifyPdOmrPrefix(raPrefix); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(400000); + // Deprecated prefixes will be removed. + VerifyNoPdOmrPrefix(); + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false); + } + + // 1.1. Publish a prefix, and wait until it expired. + // Multiple prefixes are advertised, only the smallest one will be used. + Log("1.1. RA message with multiple prefixes."); + { + Ip6::Prefix raPrefix = PrefixFromString("2001:db8:dead:beef::", 64); + Ip6::Prefix ulaRaPrefix = PrefixFromString("fd01:db8:deaf:beef::", 64); + + SendRouterAdvertToBorderRoutingProcessIcmp6Ra({Pio(ulaRaPrefix, kValidLitime * 2, kPreferredLifetime * 2), + Pio(raPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Add(raPrefix); + AdvanceTime(10000); + + VerifyPdOmrPrefix(raPrefix); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(1500000); + sExpectedRios.Clear(); + VerifyPdOmrPrefix(raPrefix); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(400000); + // Deprecated prefixes will be removed. + VerifyNoPdOmrPrefix(); + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false); + } + + // 2. Publish a prefix, and renew it before it expired. + Log("2. Renew prefix lifetime."); + { + Ip6::Prefix raPrefix = PrefixFromString("2001:db8:1:2::", 64); + + SendRouterAdvertToBorderRoutingProcessIcmp6Ra({Pio(raPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Add(raPrefix); + AdvanceTime(10000); + + VerifyPdOmrPrefix(raPrefix); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(1500000); + sExpectedRios.Clear(); + VerifyPdOmrPrefix(raPrefix); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + SendRouterAdvertToBorderRoutingProcessIcmp6Ra({Pio(raPrefix, kValidLitime, kPreferredLifetime)}); + + AdvanceTime(400000); + VerifyPdOmrPrefix(raPrefix); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(1500000); + VerifyNoPdOmrPrefix(); + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false); + } + + // 3. Publish a prefix, and publish another prefix to replace it (with goodbye ra). + Log("3. Update prefix."); + { + Ip6::Prefix raPrefix = PrefixFromString("2001:db8:1:2::", 64); + Ip6::Prefix newRaPrefix = PrefixFromString("2001:db8:3:4::", 64); + + SendRouterAdvertToBorderRoutingProcessIcmp6Ra({Pio(raPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Add(raPrefix); + sExpectedRios.Clear(); + AdvanceTime(10000); + + VerifyPdOmrPrefix(raPrefix); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(1000000); + VerifyPdOmrPrefix(raPrefix); + + SendRouterAdvertToBorderRoutingProcessIcmp6Ra( + {Pio(raPrefix, 0, 0), Pio(newRaPrefix, kValidLitime, kPreferredLifetime)}); + sExpectedRios.Add(newRaPrefix); + + AdvanceTime(1000000); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyPdOmrPrefix(newRaPrefix); + + AdvanceTime(1000000); + VerifyNoPdOmrPrefix(); + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false); + } + + // 4. Short prefix will be extended to /64. + Log("Short prefix"); + { + // The prefix will be padded to a /64 prefix. + Ip6::Prefix raPrefix = PrefixFromString("2001:db8:cafe:0::", 64); + Ip6::Prefix realRaPrefix; + + realRaPrefix.Set(raPrefix.GetBytes(), 48); + SendRouterAdvertToBorderRoutingProcessIcmp6Ra({Pio(realRaPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Add(raPrefix); + AdvanceTime(10000); + + VerifyPdOmrPrefix(raPrefix); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(1500000); + sExpectedRios.Clear(); + VerifyPdOmrPrefix(raPrefix); + VerifyOmrPrefixInNetData(raPrefix, /* aDefaultRoute */ false); + + AdvanceTime(400000); + // Deprecated prefixes will be removed. + VerifyNoPdOmrPrefix(); + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false); + } + Log("End of TestBorderRoutingProcessPlatfromGeneratedNd"); +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE int main(void) @@ -3143,6 +3393,9 @@ int main(void) #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE TestNat64PrefixSelection(); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + TestBorderRoutingProcessPlatfromGeneratedNd(); +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE printf("All tests passed\n"); #else