From 68543b0faf690f6fe059f20d8b3184f0549fb30a Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 12 Mar 2025 11:06:16 -0700 Subject: [PATCH] [routing-manager] process RDNSS options and track addresses (#11328) This commit introduces a mechanism in `RoutingManager` to parse and process Recursive DNS Server (RDNSS) options within received Router Advertisement (RA) messages. The list of discovered RDNSS addresses is tracked (per router) by `RoutingManager`, applying the advertised lifetime to properly age the discovered entries. This commit adds a new public API to retrieve the list of tracked RDNSS addresses, along with a callback mechanism where OpenThread users can register to be notified of changes to the RDNSS list. This callback is invoked when any of the following changes occur: - A new RDNSS address is advertised by a router. - A previously discovered address is removed due to the router advertising it with a zero lifetime. - A previously discovered address has aged out (its lifetime expired without being re-advertised). - `RoutingManager` determines that the router advertising the address is now unreachable, resulting in the removal of all its associated entries. This commit also adds corresponding CLI commands for the new API. Furthermore, `test_routing_manager` is updated to include a test case covering all the newly added behaviors in detail. --- include/openthread/border_routing.h | 71 +++- include/openthread/instance.h | 2 +- src/cli/README_BR.md | 25 ++ src/cli/cli_br.cpp | 46 +++ src/core/api/border_routing_api.cpp | 17 + src/core/border_router/routing_manager.cpp | 211 ++++++++++-- src/core/border_router/routing_manager.hpp | 123 +++++-- src/core/net/nd6.cpp | 38 +++ src/core/net/nd6.hpp | 133 +++++++- tests/unit/test_routing_manager.cpp | 377 ++++++++++++++++++++- 10 files changed, 982 insertions(+), 61 deletions(-) diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index 3eb12c147..92534c087 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -127,6 +127,20 @@ typedef struct otBorderRoutingPrefixTableEntry uint32_t mPreferredLifetime; ///< Preferred lifetime of the on-link prefix when `mIsOnLink`. } otBorderRoutingPrefixTableEntry; +/** + * Represents a discovered Recursive DNS Server (RDNSS) address entry. + * + * Address entries are discovered by processing the RDNSS options within received Router Advertisement messages from + * routers on infrastructure link. + */ +typedef struct otBorderRoutingRdnssAddrEntry +{ + otBorderRoutingRouterEntry mRouter; ///< Information about the router advertising this address. + otIp6Address mAddress; ///< The DNS Server IPv6 address. + uint32_t mMsecSinceLastUpdate; ///< Milliseconds since last update of this address. + uint32_t mLifetime; ///< Lifetime of the address (in seconds). +} otBorderRoutingRdnssAddrEntry; + /** * Represents information about a peer Border Router found in the Network Data. */ @@ -455,7 +469,6 @@ void otBorderRoutingPrefixTableInitIterator(otInstance *aInstance, otBorderRouti otError otBorderRoutingGetNextPrefixTableEntry(otInstance *aInstance, otBorderRoutingPrefixTableIterator *aIterator, otBorderRoutingPrefixTableEntry *aEntry); - /** * Iterates over the discovered router entries on the infrastructure link. * @@ -520,6 +533,62 @@ otError otBorderRoutingGetNextPeerBrEntry(otInstance * */ uint16_t otBorderRoutingCountPeerBrs(otInstance *aInstance, uint32_t *aMinAge); +/** + * Iterates over the Recursive DNS Server (RDNSS) address entries. + * + * Address entries are discovered by processing the RDNSS options within received Router Advertisement messages from + * routers on infrastructure link. + * + * Address entries associated with the same discovered router on an infrastructure link are guaranteed to be grouped + * together (retrieved back-to-back). + * + * @param[in] aInstance The OpenThread instance. + * @param[in,out] aIterator A pointer to the iterator. + * @param[out] aEntry A pointer to the entry to populate. + * + * @retval OT_ERROR_NONE Iterated to the next address entry, @p aEntry and @p aIterator are updated. + * @retval OT_ERROR_NOT_FOUND No more entries in the table. + * @retval OT_ERROR_INVALID_ARSG The iterator is invalid (used to iterate over other entry types, e.g. prefix). + */ +otError otBorderRoutingGetNextRdnssAddrEntry(otInstance *aInstance, + otBorderRoutingPrefixTableIterator *aIterator, + otBorderRoutingRdnssAddrEntry *aEntry); + +/** + * Callback function pointer to notify of changes to discovered Recursive DNS Server (RDNSS) address entries. + * + * Address entries are discovered by processing the RDNSS options within received Router Advertisement messages from + * routers on infrastructure link. + * + * The `otBorderRoutingGetNextRdnssAddrEntry()` function can be used to iterate over the discovered RDNSS address + * entries. + * + * This callback is invoked when any of the following changes occur to the address entries associated with a discovered + * router: + * - A new RDNSS address is advertised by the router. + * - A previously discovered address is removed due to the router advertising it with a zero lifetime. + * - A previously discovered address has aged out (its lifetime expired without being re-advertised). + * - We determine that the router that advertised the address is now unreachable, and therefore all its associated + * entries are removed. + * + * @param[in] aContext A pointer to arbitrary context information. + */ +typedef void (*otBorderRoutingRdnssAddrCallback)(void *aContext); + +/** + * Sets the callback to be notified of changes to discovered Recursive DNS Server (RDNSS) address entries. + * + * A subsequent call to this function, replaces a previously set callback. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aCallback The callback function pointer. Can be `NULL` if no callback is required. + * @param[in] aConext An arbitrary context information (used when invoking the callback). + * + */ +void otBorderRoutingSetRdnssAddrCallback(otInstance *aInstance, + otBorderRoutingRdnssAddrCallback aCallback, + void *aContext); + /** * Enables / Disables DHCPv6 Prefix Delegation. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index e53305d93..62b578dac 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (483) +#define OPENTHREAD_API_VERSION (484) /** * @addtogroup api-instance diff --git a/src/cli/README_BR.md b/src/cli/README_BR.md index dddb123a5..2b4e1385e 100644 --- a/src/cli/README_BR.md +++ b/src/cli/README_BR.md @@ -39,6 +39,7 @@ pd peers prefixtable raoptions +rdnsstable rioprf routeprf routers @@ -314,6 +315,30 @@ Clear any previously set additional options to append at the end of emitted Rout Done ``` +### rdnsstable + +Usage: `br rdnsstable` + +Get the discovered Recursive DNS Server (RDNSS) address table by Border Routing Manager on the infrastructure link. + +Info per entry: + +- IPv6 address +- Lifetime in seconds +- Milliseconds since last received Router Advertisement containing this address +- The router IPv6 address which advertised this prefix +- Flags in received Router Advertisement header: + - M: Managed Address Config flag + - O: Other Config flag + - S: SNAC Router flag + +```bash +> br rdnsstable +fd00:1234:5678::1, lifetime:500, ms-since-rx:29526, router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1) +fd00:aaaa::2, lifetime:500, ms-since-rx:107, router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1) +Done +``` + ### rioprf Usage: `br rioprf` diff --git a/src/cli/cli_br.cpp b/src/cli/cli_br.cpp index 98d203c86..68108693b 100644 --- a/src/cli/cli_br.cpp +++ b/src/cli/cli_br.cpp @@ -526,6 +526,51 @@ exit: return error; } +/** + * @cli br rdnsstable + * @code + * br rdnsstable + * fd00:1234:5678::1, lifetime:500, ms-since-rx:29526, router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1) + * fd00:aaaa::2, lifetime:500, ms-since-rx:107, router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1) + * Done + * @endcode + * @par + * Get the discovered Recursive DNS Server (RDNSS) address table by Border Routing Manager on the infrastructure link. + * Info per entry: + * - IPv6 address + * - Lifetime in seconds + * - Milliseconds since last received Router Advertisement containing this address + * - The router IPv6 address which advertised this prefix + * - Flags in received Router Advertisement header: + * - M: Managed Address Config flag + * - O: Other Config flag + * - S: SNAC Router flag + * @sa otBorderRoutingGetNextRdnssAddrEntry + */ +template <> otError Br::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otBorderRoutingPrefixTableIterator iterator; + otBorderRoutingRdnssAddrEntry entry; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator); + + while (otBorderRoutingGetNextRdnssAddrEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE) + { + char string[OT_IP6_ADDRESS_STRING_SIZE]; + + otIp6AddressToString(&entry.mAddress, string, sizeof(string)); + OutputFormat("%s, lifetime:%lu, ms-since-rx:%lu, router:", string, ToUlong(entry.mLifetime), + ToUlong(entry.mMsecSinceLastUpdate)); + OutputRouterInfo(entry.mRouter, kShortVersion); + } + +exit: + return error; +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE template <> otError Br::Process(Arg aArgs[]) { @@ -890,6 +935,7 @@ otError Br::Process(Arg aArgs[]) #endif CmdEntry("prefixtable"), CmdEntry("raoptions"), + CmdEntry("rdnsstable"), CmdEntry("rioprf"), CmdEntry("routeprf"), CmdEntry("routers"), diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index 306c9c282..ce5bde85e 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -190,6 +190,23 @@ otError otBorderRoutingGetNextRouterEntry(otInstance *aI return AsCoreType(aInstance).Get().GetNextRouterEntry(*aIterator, *aEntry); } +otError otBorderRoutingGetNextRdnssAddrEntry(otInstance *aInstance, + otBorderRoutingPrefixTableIterator *aIterator, + otBorderRoutingRdnssAddrEntry *aEntry) +{ + AssertPointerIsNotNull(aIterator); + AssertPointerIsNotNull(aEntry); + + return AsCoreType(aInstance).Get().GetNextRdnssAddrEntry(*aIterator, *aEntry); +} + +void otBorderRoutingSetRdnssAddrCallback(otInstance *aInstance, + otBorderRoutingRdnssAddrCallback aCallback, + void *aContext) +{ + AsCoreType(aInstance).Get().SetRdnssAddrCallback(aCallback, aContext); +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE otError otBorderRoutingGetNextPeerBrEntry(otInstance *aInstance, diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 5b80aec20..faae82ec3 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -885,6 +885,11 @@ void RoutingManager::LogRouteInfoOption(const Ip6::Prefix &aPrefix, uint32_t aLi RoutePreferenceToString(aPreference)); } +void RoutingManager::LogRecursiveDnsServerOption(const Ip6::Address &aAddress, uint32_t aLifetime) +{ + LogInfo("- RDNSS %s (lifetime:%lu)", aAddress.ToString().AsCString(), ToUlong(aLifetime)); +} + const char *RoutingManager::RouterAdvOriginToString(RouterAdvOrigin aRaOrigin) { static const char *const kOriginStrings[] = { @@ -909,6 +914,7 @@ const char *RoutingManager::RouterAdvOriginToString(RouterAdvOrigin aRaOrigin) void RoutingManager::LogRaHeader(const RouterAdvert::Header &) {} void RoutingManager::LogPrefixInfoOption(const Ip6::Prefix &, uint32_t, uint32_t) {} void RoutingManager::LogRouteInfoOption(const Ip6::Prefix &, uint32_t, RoutePreference) {} +void RoutingManager::LogRecursiveDnsServerOption(const Ip6::Address &, uint32_t) {} #endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) @@ -1046,6 +1052,28 @@ void RoutingManager::RoutePrefix::CopyInfoTo(PrefixTableEntry &aEntry, TimeMilli aEntry.mRoutePreference = static_cast(GetRoutePreference()); } +//--------------------------------------------------------------------------------------------------------------------- +// RdnssAddress + +void RoutingManager::RdnssAddress::SetFrom(const RecursiveDnsServerOption &aRdnss, uint8_t aAddressIndex) +{ + mAddress = aRdnss.GetAddressAt(aAddressIndex); + mLifetime = aRdnss.GetLifetime(); + mLastUpdateTime = TimerMilli::GetNow(); +} + +TimeMilli RoutingManager::RdnssAddress::GetExpireTime(void) const +{ + return RoutingManager::CalculateExpirationTime(mLastUpdateTime, mLifetime); +} + +void RoutingManager::RdnssAddress::CopyInfoTo(RdnssAddrEntry &aEntry, TimeMilli aNow) const +{ + aEntry.mAddress = GetAddress(); + aEntry.mMsecSinceLastUpdate = aNow - GetLastUpdateTime(); + aEntry.mLifetime = GetLifetime(); +} + //--------------------------------------------------------------------------------------------------------------------- // NetDataPeerBrTracker @@ -1139,7 +1167,9 @@ RoutingManager::RxRaTracker::RxRaTracker(Instance &aInstance) , mExpirationTimer(aInstance) , mStaleTimer(aInstance) , mRouterTimer(aInstance) + , mRdnssAddrTimer(aInstance) , mSignalTask(aInstance) + , mRdnssAddrTask(aInstance) { mLocalRaHeader.Clear(); } @@ -1155,6 +1185,7 @@ void RoutingManager::RxRaTracker::Stop(void) mExpirationTimer.Stop(); mStaleTimer.Stop(); mRouterTimer.Stop(); + mRdnssAddrTimer.Stop(); } void RoutingManager::RxRaTracker::ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, @@ -1207,6 +1238,10 @@ void RoutingManager::RxRaTracker::ProcessRouterAdvertMessage(const RouterAdvert: ProcessRouteInfoOption(static_cast(option), *router); break; + case Option::kTypeRecursiveDnsServer: + ProcessRecursiveDnsServerOption(static_cast(option), *router); + break; + default: break; } @@ -1410,6 +1445,58 @@ exit: return; } +void RoutingManager::RxRaTracker::ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, + Router &aRouter) +{ + Entry *entry; + bool didChange = false; + uint32_t lifetime; + + VerifyOrExit(aRdnss.IsValid()); + + lifetime = aRdnss.GetLifetime(); + + for (uint8_t index = 0; index < aRdnss.GetNumAddresses(); index++) + { + const Ip6::Address &address = aRdnss.GetAddressAt(index); + + LogRecursiveDnsServerOption(address, lifetime); + + if (lifetime == 0) + { + didChange |= (aRouter.mRdnssAddresses.RemoveAndFreeAllMatching(address)); + continue; + } + + entry = aRouter.mRdnssAddresses.FindMatching(address); + + if (entry != nullptr) + { + entry->SetFrom(aRdnss, index); + } + else + { + entry = AllocateEntry(); + + if (entry == nullptr) + { + LogWarn("Discovered too many entries, ignore RDNSS address %s", address.ToString().AsCString()); + ExitNow(); + } + + entry->SetFrom(aRdnss, index); + aRouter.mRdnssAddresses.Push(*entry); + didChange = true; + } + } + +exit: + if (didChange) + { + mRdnssAddrTask.Post(); + } +} + #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE template <> @@ -1425,18 +1512,17 @@ exit: return router; } -template -RoutingManager::RxRaTracker::Entry *RoutingManager::RxRaTracker::AllocateEntry(void) +template RoutingManager::RxRaTracker::Entry *RoutingManager::RxRaTracker::AllocateEntry(void) { - static_assert(TypeTraits::IsSame::kValue || - TypeTraits::IsSame::kValue, - "PrefixType MSUT be either RoutePrefix or OnLinkPrefix"); + static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || + TypeTraits::IsSame::kValue, + "Type MSUT be either RoutePrefix, OnLinkPrefix, or RdnssAddress"); - Entry *entry = nullptr; - SharedEntry *sharedEntry = mEntryPool.Allocate(); + Entry *entry = nullptr; + SharedEntry *sharedEntry = mEntryPool.Allocate(); VerifyOrExit(sharedEntry != nullptr); - entry = &sharedEntry->GetEntry(); + entry = &sharedEntry->GetEntry(); entry->Init(GetInstance()); exit: @@ -1447,14 +1533,15 @@ template <> void RoutingManager::RxRaTracker::Entry().mRxRaTracker.mRouterPool.Free(*this); } -template void RoutingManager::RxRaTracker::Entry::Free(void) +template void RoutingManager::RxRaTracker::Entry::Free(void) { - static_assert(TypeTraits::IsSame::kValue || - TypeTraits::IsSame::kValue, - "PrefixType MSUT be either RoutePrefix or OnLinkPrefix"); + static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || + TypeTraits::IsSame::kValue, + "Type MSUT be either RoutePrefix, OnLinkPrefix, or RdnssAddress"); Get().mRxRaTracker.mEntryPool.Free(*reinterpret_cast(this)); } @@ -1551,6 +1638,14 @@ void RoutingManager::RxRaTracker::RemoveOrDeprecateOldEntries(TimeMilli aTimeThr entry.ClearValidLifetime(); } } + + for (RdnssAddress &entry : router.mRdnssAddresses) + { + if (entry.GetLastUpdateTime() <= aTimeThreshold) + { + entry.ClearLifetime(); + } + } } if (mLocalRaHeader.IsValid() && (mLocalRaHeaderUpdateTime <= aTimeThreshold)) @@ -1568,12 +1663,29 @@ void RoutingManager::RxRaTracker::Evaluate(void) NextFireTime routerTimeoutTime(now); NextFireTime entryExpireTime(now); NextFireTime staleTime(now); + NextFireTime rdnsssAddrExpireTime(now); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Remove expired prefix entries in routers and then remove any - // router that has no prefix entries or flags. + // Remove expired entries associated with each router - mRouters.RemoveAndFreeAllMatching(Router::EmptyChecker(now)); + for (Router &router : mRouters) + { + ExpirationChecker expirationChecker(now); + + router.mOnLinkPrefixes.RemoveAndFreeAllMatching(expirationChecker); + router.mRoutePrefixes.RemoveAndFreeAllMatching(expirationChecker); + + if (router.mRdnssAddresses.RemoveAndFreeAllMatching(expirationChecker)) + { + mRdnssAddrTask.Post(); + } + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove any router entry that no longer has any valid on-link + // or route prefixes, RDNSS addresses, or other relevant flags set. + + mRouters.RemoveAndFreeAllMatching(Router::EmptyChecker()); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Determine decision factors (favored on-link prefix, has any @@ -1649,6 +1761,11 @@ void RoutingManager::RxRaTracker::Evaluate(void) DetermineStaleTimeFor(entry, staleTime); } } + + for (const RdnssAddress &entry : router.mRdnssAddresses) + { + rdnsssAddrExpireTime.UpdateIfEarlier(entry.GetExpireTime()); + } } if (mLocalRaHeader.IsValid()) @@ -1666,6 +1783,7 @@ void RoutingManager::RxRaTracker::Evaluate(void) mRouterTimer.FireAt(routerTimeoutTime); mExpirationTimer.FireAt(entryExpireTime); mStaleTimer.FireAt(staleTime); + mRdnssAddrTimer.FireAt(rdnsssAddrExpireTime); } void RoutingManager::RxRaTracker::DetermineStaleTimeFor(const OnLinkPrefix &aPrefix, NextFireTime &aStaleTime) @@ -1742,6 +1860,8 @@ void RoutingManager::RxRaTracker::HandleExpirationTimer(void) { Evaluate(); } void RoutingManager::RxRaTracker::HandleSignalTask(void) { Get().HandleRaPrefixTableChanged(); } +void RoutingManager::RxRaTracker::HandleRdnssAddrTask(void) { mRdnssCallback.InvokeIfSet(); } + void RoutingManager::RxRaTracker::ProcessNeighborAdvertMessage(const NeighborAdvertMessage &aNaMessage) { Router *router; @@ -1800,12 +1920,19 @@ void RoutingManager::RxRaTracker::HandleRouterTimer(void) { entry.ClearValidLifetime(); } + + for (RdnssAddress &entry : router.mRdnssAddresses) + { + entry.ClearLifetime(); + } } } Evaluate(); } +void RoutingManager::RxRaTracker::HandleRdnssAddrTimer(void) { Evaluate(); } + void RoutingManager::RxRaTracker::SendNeighborSolicitToRouter(const Router &aRouter) { InfraIf::Icmp6Packet packet; @@ -1935,6 +2062,22 @@ exit: return error; } +Error RoutingManager::RxRaTracker::GetNextRdnssAddr(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const +{ + Error error = kErrorNone; + Iterator &iterator = static_cast(aIterator); + + ClearAllBytes(aEntry); + + SuccessOrExit(error = iterator.AdvanceToNextRdnssAddrEntry()); + + iterator.GetRouter()->CopyInfoTo(aEntry.mRouter, iterator.GetInitTime(), iterator.GetInitUptime()); + iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); + +exit: + return error; +} + //--------------------------------------------------------------------------------------------------------------------- // RxRaTracker::Iterator @@ -2029,6 +2172,28 @@ exit: return error; } +Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextRdnssAddrEntry(void) +{ + Error error = kErrorNone; + + VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); + + if (HasEntry()) + { + VerifyOrExit(GetType() == kRdnssAddrIterator, error = kErrorInvalidArgs); + SetEntry(GetEntry()->GetNext()); + } + + while (!HasEntry()) + { + SuccessOrExit(error = AdvanceToNextRouter(kRdnssAddrIterator)); + SetEntry(GetRouter()->mRdnssAddresses.GetHead()); + } + +exit: + return error; +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextPeerBr(const PeerBr *aPeerBrsHead) @@ -2099,26 +2264,22 @@ exit: bool RoutingManager::RxRaTracker::Router::Matches(const EmptyChecker &aChecker) { - // First removes all expired on-link or router prefixes. Then - // checks whether or not the router has any useful info. + OT_UNUSED_VARIABLE(aChecker); bool hasFlags = false; - mOnLinkPrefixes.RemoveAndFreeAllMatching(aChecker); - mRoutePrefixes.RemoveAndFreeAllMatching(aChecker); - // Router can be removed if it does not advertise M or O flags and - // also does not have any advertised prefix entries (RIO/PIO). If - // the router already failed to respond to max NS probe attempts, - // we consider it as offline and therefore do not consider its - // flags anymore. + // also does not have any advertised prefix entries (RIO/PIO) or + // RDNSS address entries. If the router already failed to respond + // to max NS probe attempts, we consider it as offline and + // therefore do not consider its flags anymore. if (IsReachable()) { hasFlags = (mManagedAddressConfigFlag || mOtherConfigFlag); } - return !hasFlags && mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty(); + return !hasFlags && mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty() && mRdnssAddresses.IsEmpty(); } bool RoutingManager::RxRaTracker::Router::IsPeerBr(void) const diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index ea09cdd12..086e1e12f 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -109,6 +109,8 @@ public: typedef otBorderRoutingPrefixTableIterator PrefixTableIterator; ///< Prefix Table Iterator. typedef otBorderRoutingPrefixTableEntry PrefixTableEntry; ///< Prefix Table Entry. typedef otBorderRoutingRouterEntry RouterEntry; ///< Router Entry. + typedef otBorderRoutingRdnssAddrEntry RdnssAddrEntry; ///< RDNSS Address Entry. + typedef otBorderRoutingRdnssAddrCallback RdnssAddrCallback; ///< RDNS Address changed callback. typedef otBorderRoutingPeerBorderRouterEntry PeerBrEntry; ///< Peer Border Router Entry. typedef otPdProcessedRaInfo PdProcessedRaInfo; ///< Data of PdProcessedRaInfo. typedef otBorderRoutingRequestDhcp6PdCallback PdCallback; ///< DHCPv6 PD callback. @@ -472,6 +474,35 @@ public: return mRxRaTracker.GetNextRouter(aIterator, aEntry); } + /** + * Iterates over the discovered Recursive DNS Server (RDNSS) address entries. + * + * @param[in,out] aIterator An iterator. + * @param[out] aEntry A reference to the entry to populate. + * + * @retval kErrorNone Iterated to the next address entry, @p aEntry and @p aIterator are updated. + * @retval kErrorNotFound No more entries in the table. + * @retval kErrorInvalidArgs The @p aIterator is not valid (e.g. used to iterate over other entry types). + */ + Error GetNextRdnssAddrEntry(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) + { + return mRxRaTracker.GetNextRdnssAddr(aIterator, aEntry); + } + + /** + * Sets the callback to be notified of changes to discovered Recursive DNS Server (RDNSS) address entries. + * + * A subsequent call to this method, replaces a previously set callback. + * + * @param[in] aCallback The callback function pointer. Can be `nullptr` if no callback is required. + * @param[in] aConext An arbitrary context information (used when invoking the callback). + * + */ + void SetRdnssAddrCallback(RdnssAddrCallback aCallback, void *aContext) + { + mRxRaTracker.SetRdnssCallback(aCallback, aContext); + } + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE /** @@ -642,16 +673,17 @@ private: //------------------------------------------------------------------------------------------------------------------ // Typedefs - using Option = Ip6::Nd::Option; - using PrefixInfoOption = Ip6::Nd::PrefixInfoOption; - using RouteInfoOption = Ip6::Nd::RouteInfoOption; - using RaFlagsExtOption = Ip6::Nd::RaFlagsExtOption; - using RouterAdvert = Ip6::Nd::RouterAdvert; - using NeighborAdvertMessage = Ip6::Nd::NeighborAdvertMessage; - using TxMessage = Ip6::Nd::TxMessage; - using NeighborSolicitHeader = Ip6::Nd::NeighborSolicitHeader; - using RouterSolicitHeader = Ip6::Nd::RouterSolicitHeader; - using LinkLayerAddress = InfraIf::LinkLayerAddress; + using Option = Ip6::Nd::Option; + using PrefixInfoOption = Ip6::Nd::PrefixInfoOption; + using RouteInfoOption = Ip6::Nd::RouteInfoOption; + using RaFlagsExtOption = Ip6::Nd::RaFlagsExtOption; + using RecursiveDnsServerOption = Ip6::Nd::RecursiveDnsServerOption; + using RouterAdvert = Ip6::Nd::RouterAdvert; + using NeighborAdvertMessage = Ip6::Nd::NeighborAdvertMessage; + using TxMessage = Ip6::Nd::TxMessage; + using NeighborSolicitHeader = Ip6::Nd::NeighborSolicitHeader; + using RouterSolicitHeader = Ip6::Nd::RouterSolicitHeader; + using LinkLayerAddress = InfraIf::LinkLayerAddress; //------------------------------------------------------------------------------------------------------------------ // Enumerations @@ -680,18 +712,18 @@ private: //------------------------------------------------------------------------------------------------------------------ // Nested types + struct ExpirationChecker + { + explicit ExpirationChecker(TimeMilli aNow) { mNow = aNow; } + TimeMilli mNow; + }; + class LifetimedPrefix { // Represents an IPv6 prefix with its valid lifetime. Used as // base class for `OnLinkPrefix` or `RoutePrefix`. public: - struct ExpirationChecker - { - explicit ExpirationChecker(TimeMilli aNow) { mNow = aNow; } - TimeMilli mNow; - }; - const Ip6::Prefix &GetPrefix(void) const { return mPrefix; } Ip6::Prefix &GetPrefix(void) { return mPrefix; } const TimeMilli &GetLastUpdateTime(void) const { return mLastUpdateTime; } @@ -759,6 +791,28 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + class RdnssAddress + { + public: + void SetFrom(const RecursiveDnsServerOption &aRdnss, uint8_t aAddressIndex); + const Ip6::Address &GetAddress(void) const { return mAddress; } + const TimeMilli &GetLastUpdateTime(void) const { return mLastUpdateTime; } + uint32_t GetLifetime(void) const { return mLifetime; } + TimeMilli GetExpireTime(void) const; + void ClearLifetime(void) { mLifetime = 0; } + void CopyInfoTo(RdnssAddrEntry &aEntry, TimeMilli aNow) const; + + bool Matches(const Ip6::Address &aAddress) const { return (mAddress == aAddress); } + bool Matches(const ExpirationChecker &aChecker) const { return (GetExpireTime() <= aChecker.mNow); } + + private: + Ip6::Address mAddress; + uint32_t mLifetime; + TimeMilli mLastUpdateTime; + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE class RxRaTracker; @@ -805,9 +859,11 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void HandleRxRaTrackerSignalTask(void) { mRxRaTracker.HandleSignalTask(); } + void HandleRxRaTrackerRdnssAddrTask(void) { mRxRaTracker.HandleRdnssAddrTask(); } void HandleRxRaTrackerExpirationTimer(void) { mRxRaTracker.HandleExpirationTimer(); } void HandleRxRaTrackerStaleTimer(void) { mRxRaTracker.HandleStaleTimer(); } void HandleRxRaTrackerRouterTimer(void) { mRxRaTracker.HandleRouterTimer(); } + void HandleRxRaTrackerRdnssAddrTimer(void) { mRxRaTracker.HandleRdnssAddrTimer(); } class RxRaTracker : public InstanceLocator { @@ -831,6 +887,8 @@ private: void Start(void); void Stop(void); + void SetRdnssCallback(RdnssAddrCallback aCallback, void *aContext) { mRdnssCallback.Set(aCallback, aContext); } + void ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, const Ip6::Address &aSrcAddress, RouterAdvOrigin aRaOrigin); @@ -853,6 +911,7 @@ private: void InitIterator(PrefixTableIterator &aIterator) const; Error GetNextEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const; Error GetNextRouter(PrefixTableIterator &aIterator, RouterEntry &aEntry) const; + Error GetNextRdnssAddr(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const; // Callbacks notifying of changes void RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold); @@ -861,9 +920,11 @@ private: // Tasklet or timer callbacks void HandleSignalTask(void); + void HandleRdnssAddrTask(void); void HandleExpirationTimer(void); void HandleStaleTimer(void); void HandleRouterTimer(void); + void HandleRdnssAddrTimer(void); private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -906,7 +967,9 @@ private: static_assert(kMaxNsProbes < 255, "kMaxNsProbes MUST not be 255"); - typedef LifetimedPrefix::ExpirationChecker EmptyChecker; + struct EmptyChecker + { + }; bool IsReachable(void) const { return mNsProbeCount <= kMaxNsProbes; } bool ShouldCheckReachability(void) const; @@ -919,6 +982,7 @@ private: using OnLinkPrefixList = OwningList>; using RoutePrefixList = OwningList>; + using RdnssAddressList = OwningList>; // `mDiscoverTime` tracks the initial discovery time of // this router. To accommodate longer durations, the @@ -933,6 +997,7 @@ private: Ip6::Address mAddress; OnLinkPrefixList mOnLinkPrefixes; RoutePrefixList mRoutePrefixes; + RdnssAddressList mRdnssAddresses; uint32_t mDiscoverTime; TimeMilli mLastUpdateTime; TimeMilli mTimeoutTime; @@ -954,6 +1019,7 @@ private: kUnspecified, kRouterIterator, kPrefixIterator, + kRdnssAddrIterator, kPeerBrIterator, }; @@ -966,15 +1032,16 @@ private: void Init(const Entry *aRoutersHead, uint32_t aUptime); Error AdvanceToNextRouter(Type aType); Error AdvanceToNextEntry(void); + Error AdvanceToNextRdnssAddrEntry(void); uint32_t GetInitUptime(void) const { return mData0; } TimeMilli GetInitTime(void) const { return TimeMilli(mData1); } Type GetType(void) const { return static_cast(mData2); } const Entry *GetRouter(void) const { return static_cast *>(mPtr1); } EntryType GetEntryType(void) const { return static_cast(mData3); } - template const Entry *GetEntry(void) const + template const Entry *GetEntry(void) const { - return static_cast *>(mPtr2); + return static_cast *>(mPtr2); } #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE @@ -1007,11 +1074,12 @@ private: SharedEntry *GetNext(void) { return mNext; } const SharedEntry *GetNext(void) const { return mNext; } - template Entry &GetEntry(void); + template Entry &GetEntry(void); SharedEntry *mNext; Entry mOnLinkEntry; Entry mRouteEntry; + Entry mRdnssAddrEntry; }; #endif @@ -1039,6 +1107,7 @@ private: void ProcessRaHeader(const RouterAdvert::Header &aRaHeader, Router &aRouter, RouterAdvOrigin aRaOrigin); void ProcessPrefixInfoOption(const PrefixInfoOption &aPio, Router &aRouter); void ProcessRouteInfoOption(const RouteInfoOption &aRio, Router &aRouter); + void ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, Router &aRouter); void Evaluate(void); void DetermineStaleTimeFor(const OnLinkPrefix &aPrefix, NextFireTime &aStaleTime); void DetermineStaleTimeFor(const RoutePrefix &aPrefix, NextFireTime &aStaleTime); @@ -1050,17 +1119,23 @@ private: #endif using SignalTask = TaskletIn; + using RdnssAddrTask = TaskletIn; using ExpirationTimer = TimerMilliIn; using StaleTimer = TimerMilliIn; using RouterTimer = TimerMilliIn; + using RdnssAddrTimer = TimerMilliIn; using RouterList = OwningList>; + using RdnssCallback = Callback; DecisionFactors mDecisionFactors; RouterList mRouters; ExpirationTimer mExpirationTimer; StaleTimer mStaleTimer; RouterTimer mRouterTimer; + RdnssAddrTimer mRdnssAddrTimer; SignalTask mSignalTask; + RdnssAddrTask mRdnssAddrTask; + RdnssCallback mRdnssCallback; RouterAdvert::Header mLocalRaHeader; TimeMilli mLocalRaHeaderUpdateTime; @@ -1551,6 +1626,7 @@ private: static void LogRaHeader(const RouterAdvert::Header &aRaHeader); static void LogPrefixInfoOption(const Ip6::Prefix &aPrefix, uint32_t aValidLifetime, uint32_t aPreferredLifetime); static void LogRouteInfoOption(const Ip6::Prefix &aPrefix, uint32_t aLifetime, RoutePreference aPreference); + static void LogRecursiveDnsServerOption(const Ip6::Address &aAddress, uint32_t aLifetime); static const char *RouterAdvOriginToString(RouterAdvOrigin aRaOrigin); @@ -1622,6 +1698,13 @@ inline RoutingManager::RxRaTracker::Entry return mRouteEntry; } +template <> +inline RoutingManager::RxRaTracker::Entry + &RoutingManager::RxRaTracker::SharedEntry::GetEntry(void) +{ + return mRdnssAddrEntry; +} + // Declare template (full) specializations for `Router` type. template <> diff --git a/src/core/net/nd6.cpp b/src/core/net/nd6.cpp index 3e55126fd..febd9f39d 100644 --- a/src/core/net/nd6.cpp +++ b/src/core/net/nd6.cpp @@ -182,6 +182,24 @@ void RaFlagsExtOption::Init(void) OT_UNUSED_VARIABLE(mFlags); } +//---------------------------------------------------------------------------------------------------------------------- +// RouteInfoOption + +void RecursiveDnsServerOption::Init(void) +{ + OT_UNUSED_VARIABLE(mReserved); + + Clear(); + SetType(kTypeRecursiveDnsServer); +} + +uint8_t RecursiveDnsServerOption::OptionLengthFor(uint8_t aNumAddresses) +{ + uint16_t size = sizeof(RecursiveDnsServerOption) + aNumAddresses * sizeof(Address); + + return ClampToUint8(DivideAndRoundUp(size, kLengthUnit)); +} + //---------------------------------------------------------------------------------------------------------------------- // RouterAdver::Header @@ -314,6 +332,26 @@ exit: return error; } +Error RouterAdvert::TxMessage::AppendRecursiveDnsServerOption(const Address *aAddresses, + uint8_t aNumAddresses, + uint32_t aLifetime) +{ + Error error = kErrorNone; + RecursiveDnsServerOption *rdnss; + uint8_t optionLength = RecursiveDnsServerOption::OptionLengthFor(aNumAddresses); + + rdnss = static_cast(AppendOption(Option::kLengthUnit * optionLength)); + VerifyOrExit(rdnss != nullptr, error = kErrorNoBufs); + + rdnss->Init(); + rdnss->SetLength(optionLength); + rdnss->SetLifetime(aLifetime); + memcpy(rdnss->GetAddresses(), aAddresses, aNumAddresses * sizeof(Address)); + +exit: + return error; +} + //---------------------------------------------------------------------------------------------------------------------- // RouterSolicitHeader diff --git a/src/core/net/nd6.hpp b/src/core/net/nd6.hpp index c16421ce4..8ff0899a3 100644 --- a/src/core/net/nd6.hpp +++ b/src/core/net/nd6.hpp @@ -78,11 +78,12 @@ class Option public: enum Type : uint8_t { - kSourceLinkLayerAddr = 1, ///< Source Link Layer Address Option. - kTargetLinkLayerAddr = 2, ///< Target Link Layer Address Option. - kTypePrefixInfo = 3, ///< Prefix Information Option. - kTypeRouteInfo = 24, ///< Route Information Option. - kTypeRaFlagsExtension = 26, ///< RA Flags Extension Option. + kSourceLinkLayerAddr = 1, ///< Source Link Layer Address Option. + kTargetLinkLayerAddr = 2, ///< Target Link Layer Address Option. + kTypePrefixInfo = 3, ///< Prefix Information Option. + kTypeRouteInfo = 24, ///< Route Information Option. + kTypeRecursiveDnsServer = 25, ///< Recursive DNS Server (RDNSS) Option. + kTypeRaFlagsExtension = 26, ///< RA Flags Extension Option. }; static constexpr uint16_t kLengthUnit = 8; ///< The unit of length in octets. @@ -488,6 +489,116 @@ private: static_assert(sizeof(RaFlagsExtOption) == 8, "invalid RaFlagsExtOption structure"); +/** + * Represents the Recursive DNS Server (RDNSS) Option. + * + * See section 5.1 of RFC 8106 [https://datatracker.ietf.org/doc/html/rfc8106#section-5.1]. + */ +OT_TOOL_PACKED_BEGIN +class RecursiveDnsServerOption : public Option, private Clearable +{ + friend class Clearable; + +public: + static constexpr uint16_t kMinSize = kLengthUnit; ///< Minimum size (in bytes) of a RDNSS Option. + static constexpr Type kType = kTypeRecursiveDnsServer; ///< Route Information Option Type. + + /** + * Initializes the option setting the type and clearing (setting to zero) all other fields. + */ + void Init(void); + + /** + * Tells whether this option is valid. + * + * @returns A boolean indicates whether this option is valid. + */ + bool IsValid(void) const { return (GetLength() > 0); } + + /** + * Sets the Lifetime field. + * + * @param[in] aLifetime The lifetime in seconds. + */ + void SetLifetime(uint32_t aLifetime) { mLifetime = BigEndian::HostSwap32(aLifetime); } + + /** + * Gets the Lifetime fields + * + * @returns The Lifetime in seconds. + */ + uint32_t GetLifetime(void) const { return BigEndian::HostSwap32(mLifetime); } + + /** + * Gets the numbers of IPv6 addresses. + * + * @returns Number of IPv6 addresses. + */ + uint8_t GetNumAddresses(void) const { return IsValid() ? (GetLength() - 1) / 2 : 0; } + + /** + * Returns a pointer to array of IPv6 addresses of DNS server. + * + * @returns A pointer to the array of IPv6 addresses. + */ + const Address *GetAddresses(void) const + { + return reinterpret_cast(reinterpret_cast(this) + sizeof(*this)); + } + + /** + * Returns a pointer to array of IPv6 addresses of DNS server. + * + * @returns A pointer to the array of IPv6 addresses. + */ + Address *GetAddresses(void) { return AsNonConst(AsConst(this)->GetAddresses()); } + + /** + * Returns the IPv6 address at a given index. + * + * Caller MUST ensure that @p aIndex is valid and smaller than `GetNumberOfAddresses()`. Otherwise the behavior + * of this method is undefined. + * + * @param[in] aIndex The index. + * + * @returns The IPv6 address at @p aIndex. + */ + const Address &GetAddressAt(uint8_t aIndex) const { return GetAddresses()[aIndex]; } + + /** + * Calculates the option length for a given number of IPv6 addresses. + * + * @param[in] aNumAddresses Number of IPv6 addresses + * + * @returns The option length (in unit of 8 octets) for @p aNumAddresses. + */ + static uint8_t OptionLengthFor(uint8_t aNumAddresses); + + RecursiveDnsServerOption(void) = delete; + +private: + // RDNSS Option + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Lifetime | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // : Addresses of IPv6 Recursive DNS Servers : + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + uint16_t mReserved; + uint32_t mLifetime; + // Followed by IPv6 Addresses of DNS servers (variable length). + +} OT_TOOL_PACKED_END; + +static_assert(sizeof(RecursiveDnsServerOption) == 8, "invalid RecursiveDnsServerOption structure"); + /** * Defines the ND6 Tx Message. */ @@ -811,6 +922,18 @@ public: */ Error AppendRouteInfoOption(const Prefix &aPrefix, uint32_t aRouteLifetime, RoutePreference aPreference); + /** + * Append a Recursive DNS Server Option to the RA message. + * + * @param[in] aAddresses A pointer to an array of IPv6 addresses. + * @param[in] aNumAddresses Number of addresses in @p aAddresses array. + * @param[in] aLifetime The lifetime in seconds. + * + * @retval kErrorNone Option is appended successfully. + * @retval kErrorNoBufs Insufficient available buffers to grow the message. + */ + Error AppendRecursiveDnsServerOption(const Address *aAddresses, uint8_t aNumAddresses, uint32_t aLifetime); + /** * Indicates whether or not the received RA message contains any options. * diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index 50b3b3dc2..5a5245d51 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -612,6 +612,22 @@ void LogRouterAdvert(const Icmp6Packet &aPacket) break; } + case Ip6::Nd::Option::kTypeRecursiveDnsServer: + { + const Ip6::Nd::RecursiveDnsServerOption &rdnss = + static_cast(option); + + VerifyOrQuit(rdnss.IsValid()); + + for (uint8_t index = 0; index < rdnss.GetNumAddresses(); index++) + { + Log(" RDNSS - %s, lifetime:%u", rdnss.GetAddressAt(index).ToString().AsCString(), + rdnss.GetLifetime()); + } + + break; + } + default: VerifyOrQuit(false, "Bad option type in RA msg"); } @@ -847,11 +863,32 @@ struct RaFlags : public Clearable bool mSnacRouterFlag; }; +struct Rdnss +{ + template static Rdnss Create(uint32_t aLifetime, const Ip6::Address (&aAddresses)[kNumAddrs]) + { + return Rdnss(aLifetime, aAddresses, kNumAddrs); + } + + Rdnss(uint32_t aLifetime, const Ip6::Address *aAddresses, uint8_t aNumAddresses) + : mLifetime(aLifetime) + , mAddresses(aAddresses) + , mNumAddresses(aNumAddresses) + { + } + + uint32_t mLifetime; + const Ip6::Address *mAddresses; + uint8_t mNumAddresses; +}; + void BuildRouterAdvert(Ip6::Nd::RouterAdvert::TxMessage &aRaMsg, const Pio *aPios, uint16_t aNumPios, const Rio *aRios, uint16_t aNumRios, + const Rdnss *aRdnsses, + uint16_t aNumRdnsses, const DefaultRoute &aDefaultRoute, const RaFlags &aRaFlags) { @@ -886,6 +923,12 @@ void BuildRouterAdvert(Ip6::Nd::RouterAdvert::TxMessage &aRaMsg, { SuccessOrQuit(aRaMsg.AppendRouteInfoOption(aRios->mPrefix, aRios->mValidLifetime, aRios->mPreference)); } + + for (; aNumRdnsses > 0; aRdnsses++, aNumRdnsses--) + { + SuccessOrQuit( + aRaMsg.AppendRecursiveDnsServerOption(aRdnsses->mAddresses, aRdnsses->mNumAddresses, aRdnsses->mLifetime)); + } } void SendRouterAdvert(const Ip6::Address &aRouterAddress, @@ -893,13 +936,15 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, uint16_t aNumPios, const Rio *aRios, uint16_t aNumRios, + const Rdnss *aRdnsses, + uint16_t aNumRdnsses, const DefaultRoute &aDefaultRoute, const RaFlags &aRaFlags) { Ip6::Nd::RouterAdvert::TxMessage raMsg; Icmp6Packet packet; - BuildRouterAdvert(raMsg, aPios, aNumPios, aRios, aNumRios, aDefaultRoute, aRaFlags); + BuildRouterAdvert(raMsg, aPios, aNumPios, aRios, aNumRios, aRdnsses, aNumRdnsses, aDefaultRoute, aRaFlags); raMsg.GetAsPacket(packet); SendRouterAdvert(aRouterAddress, packet); @@ -914,7 +959,7 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, aPios, kNumPios, aRios, kNumRios, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, aPios, kNumPios, aRios, kNumRios, nullptr, 0, aDefaultRoute, aRaFlags); } template @@ -923,7 +968,7 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, aPios, kNumPios, nullptr, 0, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, aPios, kNumPios, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); } template @@ -932,20 +977,33 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, nullptr, 0, aRios, kNumRios, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, nullptr, 0, aRios, kNumRios, nullptr, 0, aDefaultRoute, aRaFlags); +} + +void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Rdnss &aRdnss) +{ + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, &aRdnss, 1, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); +} + +template +void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Rdnss (&aRdnsses)[kNumRdnsses]) +{ + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aRdnsses, kNumRdnsses, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); } void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute, const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); } void SendRouterAdvert(const Ip6::Address &aRouterAddress, const RaFlags &aRaFlags) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), - aRaFlags); + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), aRaFlags); } template void SendRouterAdvertToBorderRoutingProcessIcmp6Ra(const Pio (&aPios)[kNumPios]) @@ -953,8 +1011,8 @@ template void SendRouterAdvertToBorderRoutingProcessIcmp6Ra( Ip6::Nd::RouterAdvert::TxMessage raMsg; Icmp6Packet packet; - BuildRouterAdvert(raMsg, aPios, kNumPios, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), - RaFlags()); + BuildRouterAdvert(raMsg, aPios, kNumPios, nullptr, 0, nullptr, 0, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); raMsg.GetAsPacket(packet); otPlatBorderRoutingProcessIcmp6Ra(sInstance, packet.GetBytes(), packet.GetLength()); @@ -1080,6 +1138,66 @@ void VerifyPrefixTable(const OnLinkPrefix *aOnLinkPrefixes, void VerifyPrefixTableIsEmpty(void) { VerifyPrefixTable(nullptr, 0, nullptr, 0); } +struct RdnssAddress +{ + RdnssAddress(const Ip6::Address &aAddress, uint32_t aLifetime, const Ip6::Address &aRouterAddress) + : mAddress(aAddress) + , mLifetime(aLifetime) + , mRouterAddress(aRouterAddress) + { + } + + const Ip6::Address &mAddress; + uint32_t mLifetime; + const Ip6::Address &mRouterAddress; +}; + +template void VerifyRdnssAddressTable(const RdnssAddress (&aRdnssAddresses)[kNumAddrs]) +{ + VerifyRdnssAddressTable(aRdnssAddresses, kNumAddrs); +} + +void VerifyRdnssAddressTable(const RdnssAddress *aRdnssAddresses, uint16_t aNumAddrs) +{ + BorderRouter::RoutingManager::PrefixTableIterator iter; + BorderRouter::RoutingManager::RdnssAddrEntry entry; + uint16_t count = 0; + + Log("VerifyRdnssAddressTable()"); + + sInstance->Get().InitPrefixTableIterator(iter); + + while (sInstance->Get().GetNextRdnssAddrEntry(iter, entry) == kErrorNone) + { + bool didFind = false; + + Log(" address:%s, lifetime:%u, router:%s, age:%u", AsCoreType(&entry.mAddress).ToString().AsCString(), + entry.mLifetime, AsCoreType(&entry.mRouter.mAddress).ToString().AsCString(), + entry.mMsecSinceLastUpdate / 1000); + + count++; + + for (uint16_t index = 0; index < aNumAddrs; index++) + { + const RdnssAddress &rndssAddress = aRdnssAddresses[index]; + + if ((rndssAddress.mAddress == AsCoreType(&entry.mAddress)) && + (AsCoreType(&entry.mRouter.mAddress) == rndssAddress.mRouterAddress)) + { + VerifyOrQuit(entry.mLifetime == rndssAddress.mLifetime); + didFind = true; + break; + } + } + + VerifyOrQuit(didFind); + } + + VerifyOrQuit(count == aNumAddrs); +} + +void VerifyRdnssAddressTableIsEmpty(void) { VerifyRdnssAddressTable(nullptr, 0); } + struct InfraRouter { InfraRouter(const Ip6::Address &aAddress, @@ -4347,6 +4465,246 @@ void TestBorderRoutingProcessPlatfromGeneratedNd(void) } #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +static void HandleRdnssChanged(void *aContext) +{ + VerifyOrQuit(aContext != nullptr); + *static_cast(aContext) = true; +} + +void TestRdnss(void) +{ + Ip6::Address rdnssAddr1 = AddressFromString("fd77::1"); + Ip6::Address rdnssAddr2 = AddressFromString("fd77::2"); + Ip6::Address rdnssAddr3 = AddressFromString("fd77::3"); + Ip6::Address rdnssAddr4 = AddressFromString("fd77::4"); + Ip6::Address routerAddressA = AddressFromString("fd00::aaaa"); + Ip6::Address routerAddressB = AddressFromString("fd00::bbbb"); + Ip6::Prefix localOnLink; + Ip6::Prefix localOmr; + bool rdnssCallbackCalled = false; + + uint16_t heapAllocations; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestRdnss"); + + InitTest(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start Routing Manager. Check emitted RS and RA messages. + + sRsEmitted = false; + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + sExpectedRios.Clear(); + + heapAllocations = sHeapAllocatedPtrs.GetLength(); + + SuccessOrQuit(sInstance->Get().SetEnabled(true)); + + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + SuccessOrQuit(sInstance->Get().GetOmrPrefix(localOmr)); + + Log("Local on-link prefix is %s", localOnLink.ToString().AsCString()); + Log("Local OMR prefix is %s", localOmr.ToString().AsCString()); + + sExpectedRios.Add(localOmr); + + AdvanceTime(30000); + + VerifyOrQuit(sRsEmitted); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sExpectedRios.SawAll()); + Log("Received RA was validated"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Set the RDNSS callback on Routing Manager + + rdnssCallbackCalled = false; + sInstance->Get().SetRdnssAddrCallback(HandleRdnssChanged, &rdnssCallbackCalled); + + VerifyOrQuit(!rdnssCallbackCalled); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check Network Data to include the local OMR and ULA prefix. + + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false); + VerifyExternalRouteInNetData(kUlaRoute, kWithAdvPioFlagSet); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send RA from router A advertising an RDNS address. Ensure + // that the RDNSS callback is called and new advertised address + // is present in the RDNSS table. + + VerifyRdnssAddressTableIsEmpty(); + + rdnssCallbackCalled = false; + SendRouterAdvert(routerAddressA, {Rdnss::Create(300, {rdnssAddr1})}); + + AdvanceTime(1); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr1, 300, routerAddressA)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send same RA again from router A, ensure there is no callback + // as there is no change in table. + + rdnssCallbackCalled = false; + + AdvanceTime(15 * 1000); + VerifyOrQuit(!rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr1, 300, routerAddressA)}); + + SendRouterAdvert(routerAddressA, {Rdnss::Create(300, {rdnssAddr1})}); + AdvanceTime(1); + + VerifyOrQuit(!rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr1, 300, routerAddressA)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A, now adding a different RDNSS address. + // Ensure callback is invoked and we see the new address in the + // table. + + rdnssCallbackCalled = false; + + SendRouterAdvert(routerAddressA, {Rdnss::Create(600, {rdnssAddr2})}); + AdvanceTime(1); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTable( + {RdnssAddress(rdnssAddr1, 300, routerAddressA), RdnssAddress(rdnssAddr2, 600, routerAddressA)}); + + AdvanceTime(20 * 1000); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send RA from router B, adding a new different RDNSS address. + // Ensure callback is invoked and validate RDNSS address table. + + rdnssCallbackCalled = false; + + SendRouterAdvert(routerAddressB, {Rdnss::Create(0xffffffff, {rdnssAddr3})}); + AdvanceTime(1); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr1, 300, routerAddressA), + RdnssAddress(rdnssAddr2, 600, routerAddressA), + RdnssAddress(rdnssAddr3, 0xffffffff, routerAddressB)}); + + AdvanceTime(10 * 1000); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A with two RDNSS options changing the + // lifetimes of the two addresses. Ensure the callback is not invoked + // (since there are no changes to the address table), but we should + // see the new lifetimes reflected in the RDNSS address table. + + rdnssCallbackCalled = false; + + SendRouterAdvert(routerAddressA, {Rdnss::Create(400, {rdnssAddr2}), Rdnss::Create(800, {rdnssAddr1})}); + AdvanceTime(1); + + VerifyOrQuit(!rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr1, 800, routerAddressA), + RdnssAddress(rdnssAddr2, 400, routerAddressA), + RdnssAddress(rdnssAddr3, 0xffffffff, routerAddressB)}); + + AdvanceTime(30 * 1000); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send RA from router B, removing a previous RDNSS address + // and one that it never advertised. Ensure callback is + // invoked and validate RDNSS address table is updated + // correctly. + + rdnssCallbackCalled = false; + + SendRouterAdvert(routerAddressB, {Rdnss::Create(0, {rdnssAddr3, rdnssAddr4, rdnssAddr3})}); + AdvanceTime(1); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTable( + {RdnssAddress(rdnssAddr1, 800, routerAddressA), RdnssAddress(rdnssAddr2, 400, routerAddressA)}); + + AdvanceTime(30 * 1000); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send RA from router A, advertising a short lifetime. + + rdnssCallbackCalled = false; + + SendRouterAdvert(routerAddressA, {Rdnss::Create(32, {rdnssAddr1, rdnssAddr2})}); + AdvanceTime(1); + + VerifyOrQuit(!rdnssCallbackCalled); + VerifyRdnssAddressTable( + {RdnssAddress(rdnssAddr1, 32, routerAddressA), RdnssAddress(rdnssAddr2, 32, routerAddressA)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for the expiration time (based on lifetime) and ensure + // callback is invoked and the addresses are removed from + // the table. + + AdvanceTime(32 * 1000 - 10); + + VerifyOrQuit(!rdnssCallbackCalled); + + AdvanceTime(15); + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTableIsEmpty(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router B, adding an RDNSS address with infinite + // lifetime. + + rdnssCallbackCalled = false; + + SendRouterAdvert(routerAddressB, {Rdnss::Create(0xffffffff, {rdnssAddr3})}); + AdvanceTime(1); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr3, 0xffffffff, routerAddressB)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Disallow responding to NS message. + + // Wait for longer than "reachable" timeout to ensure the router B + // is fully removed (deemed unreachable). Validate that its + // RNDSS address is also removed and RDNSS callback is invoked. + + rdnssCallbackCalled = false; + sRespondToNs = false; + + AdvanceTime(250 * 1000); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTableIsEmpty(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A with one RDNNS address. Validate + // that it is removed when `RoutingManager` is stopped. + + rdnssCallbackCalled = false; + SendRouterAdvert(routerAddressA, {Rdnss::Create(300, {rdnssAddr1})}); + AdvanceTime(1); + + VerifyOrQuit(rdnssCallbackCalled); + VerifyRdnssAddressTable({RdnssAddress(rdnssAddr1, 300, routerAddressA)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + SuccessOrQuit(sInstance->Get().SetEnabled(false)); + + VerifyRdnssAddressTableIsEmpty(); + + VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength()); + + Log("End of TestRdnss"); + + FinalizeTest(); +} + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE } // namespace ot @@ -4381,6 +4739,7 @@ int main(void) #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE ot::TestBorderRoutingProcessPlatfromGeneratedNd(); #endif + ot::TestRdnss(); printf("All tests passed\n"); #else