From 4e2a570d0e3c9470bcf09c5c163976a7f7764f0a Mon Sep 17 00:00:00 2001 From: Yang Song Date: Fri, 31 Oct 2025 22:35:36 +0800 Subject: [PATCH] [nat64] add support for RFC8781 NAT64 prefix (#12012) This commit introduces support for discovering NAT64 prefixes as specified in RFC 8781. The key changes include: - New `Nat64PrefixInfoOption`: A new `Nat64PrefixInfoOption` class is added to represent the PREF64 option in ND messages. This handles parsing the prefix and its lifetime from incoming RAs. - `RxRaTracker` Enhancement: The `RxRaTracker` is updated to process `Nat64PrefixInfoOption` from RAs. It now maintains a list of discovered NAT64 prefixes from routers on the infrastructure link and determines a "favored" prefix among them. - `RoutingManager` Update: The `Nat64PrefixManager` is enhanced to utilize the RA-discovered prefix. - `test_routing_manager` Update: The `TestNat64PrefixSelection` is updated to include cases with RA-discovered prefixes. --- include/openthread/border_routing.h | 14 ++ include/openthread/instance.h | 2 +- src/core/border_router/br_log.cpp | 7 + src/core/border_router/br_log.hpp | 13 ++ src/core/border_router/br_types.cpp | 32 ++++ src/core/border_router/br_types.hpp | 43 ++++++ src/core/border_router/routing_manager.cpp | 34 ++++- src/core/border_router/routing_manager.hpp | 4 +- src/core/border_router/rx_ra_tracker.cpp | 166 +++++++++++++++++++-- src/core/border_router/rx_ra_tracker.hpp | 69 ++++++++- src/core/net/nd6.cpp | 85 +++++++++++ src/core/net/nd6.hpp | 132 ++++++++++++++++ tests/unit/test_routing_manager.cpp | 134 ++++++++++++++--- 13 files changed, 694 insertions(+), 41 deletions(-) diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index 2cf31d24c..9a1f0a245 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -131,6 +131,20 @@ typedef struct otBorderRoutingPrefixTableEntry uint32_t mPreferredLifetime; ///< Preferred lifetime of the on-link prefix when `mIsOnLink`. } otBorderRoutingPrefixTableEntry; +/** + * Represents an entry from the discovered NAT64 prefix table. + * + * The entries in the discovered table track the NAT64 Prefix Options in the received Router Advertisement messages from + * other routers on the infrastructure link. + */ +typedef struct otBorderRoutingNat64PrefixEntry +{ + otBorderRoutingRouterEntry mRouter; ///< Information about the router advertising this NAT64 prefix. + otIp6Prefix mPrefix; ///< The discovered IPv6 prefix. + uint32_t mMsecSinceLastUpdate; ///< Milliseconds since last update of this prefix. + uint32_t mLifetime; ///< Lifetime of the prefix (in seconds). +} otBorderRoutingNat64PrefixEntry; + /** * Represents a discovered Recursive DNS Server (RDNSS) address entry. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index dfdac71a3..b5f1205c8 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 (547) +#define OPENTHREAD_API_VERSION (548) /** * @addtogroup api-instance diff --git a/src/core/border_router/br_log.cpp b/src/core/border_router/br_log.cpp index 0f1495eef..91db136e2 100644 --- a/src/core/border_router/br_log.cpp +++ b/src/core/border_router/br_log.cpp @@ -68,6 +68,13 @@ void LogRouteInfoOption(const Ip6::Prefix &aPrefix, uint32_t aLifetime, RoutePre RoutePreferenceToString(aPreference)); } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +void LogNat64PrefixOption(const Ip6::Prefix &aPrefix, uint32_t aLifetime) +{ + LogInfo("- NAT64 Prefix %s (lifetime:%lu)", aPrefix.ToString().AsCString(), ToUlong(aLifetime)); +} +#endif + void LogRecursiveDnsServerOption(const Ip6::Address &aAddress, uint32_t aLifetime) { LogInfo("- RDNSS %s (lifetime:%lu)", aAddress.ToString().AsCString(), ToUlong(aLifetime)); diff --git a/src/core/border_router/br_log.hpp b/src/core/border_router/br_log.hpp index f518d18b4..a9f5572af 100644 --- a/src/core/border_router/br_log.hpp +++ b/src/core/border_router/br_log.hpp @@ -76,6 +76,16 @@ void LogPrefixInfoOption(const Ip6::Prefix &aPrefix, */ void LogRouteInfoOption(const Ip6::Prefix &aPrefix, uint32_t aLifetime, RoutePreference aPreference); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +/** + * Logs a NAT64 Prefix Option at info log level. + * + * @param[in] aPrefix The IPv6 prefix. + * @param[in] aLifetime The NAT64 prefix lifetime in seconds. + */ +void LogNat64PrefixOption(const Ip6::Prefix &aPrefix, uint32_t aLifetime); +#endif + /** * Logs a Recursive DNS Server (RDNSS) option at info log level. * @@ -89,6 +99,9 @@ void LogRecursiveDnsServerOption(const Ip6::Address &aAddress, uint32_t aLifetim inline void LogRaHeader(const RouterAdvert::Header &) {} inline void LogPrefixInfoOption(const Ip6::Prefix &, uint32_t, uint32_t, PrefixInfoOption::Flags) {} inline void LogRouteInfoOption(const Ip6::Prefix &, uint32_t, RoutePreference) {} +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +inline void LogNat64PrefixOption(const Ip6::Prefix &, uint32_t) {} +#endif inline void LogRecursiveDnsServerOption(const Ip6::Address &, uint32_t) {} #endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) diff --git a/src/core/border_router/br_types.cpp b/src/core/border_router/br_types.cpp index 55f52be39..ab7defa36 100644 --- a/src/core/border_router/br_types.cpp +++ b/src/core/border_router/br_types.cpp @@ -177,6 +177,38 @@ void RoutePrefix::CopyInfoTo(PrefixTableEntry &aEntry, TimeMilli aNow) const aEntry.mRoutePreference = static_cast(GetRoutePreference()); } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +//--------------------------------------------------------------------------------------------------------------------- +// Nat64Prefix + +void Nat64Prefix::SetFrom(const Nat64PrefixOption &aNat64Prefix) +{ + IgnoreError(aNat64Prefix.GetPrefix(mPrefix)); + mValidLifetime = aNat64Prefix.GetLifetime(); + mLastUpdateTime = TimerMilli::GetNow(); +} + +void Nat64Prefix::CopyInfoTo(Nat64PrefixEntry &aEntry, TimeMilli aNow) const +{ + aEntry.mPrefix = GetPrefix(); + aEntry.mMsecSinceLastUpdate = aNow - GetLastUpdateTime(); + aEntry.mLifetime = GetValidLifetime(); +} + +bool Nat64Prefix::IsFavoredOver(const Ip6::Prefix &aPrefix) const +{ + bool isFavored = false; + + VerifyOrExit(mPrefix.GetLength() != 0); + VerifyOrExit(aPrefix.GetLength() != 0, isFavored = true); + + isFavored = GetPrefix() < aPrefix; + +exit: + return isFavored; +} +#endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // RdnssAddress diff --git a/src/core/border_router/br_types.hpp b/src/core/border_router/br_types.hpp index 608c85c98..4b709825c 100644 --- a/src/core/border_router/br_types.hpp +++ b/src/core/border_router/br_types.hpp @@ -58,6 +58,7 @@ typedef NetworkData::RoutePreference RoutePreference; ///< Route pr typedef otBorderRoutingPrefixTableIterator PrefixTableIterator; ///< Prefix Table Iterator. typedef otBorderRoutingPrefixTableEntry PrefixTableEntry; ///< Prefix Table Entry. typedef otBorderRoutingRouterEntry RouterEntry; ///< Router Entry. +typedef otBorderRoutingNat64PrefixEntry Nat64PrefixEntry; ///< NAT64 Prefix Entry. typedef otBorderRoutingRdnssAddrEntry RdnssAddrEntry; ///< RDNSS Address Entry. typedef otBorderRoutingRdnssAddrCallback RdnssAddrCallback; ///< RDNS Address changed callback. typedef otBorderRoutingIfAddrEntry IfAddrEntry; ///< Infra-if IPv6 Address Entry. @@ -71,6 +72,7 @@ typedef otBorderRoutingMultiAilCallback MultiAilCallback; ///< Multi AI typedef Ip6::Nd::PrefixInfoOption PrefixInfoOption; ///< Prefix Info Option (PIO). typedef Ip6::Nd::RouteInfoOption RouteInfoOption; ///< Route Info Option (RIO). typedef Ip6::Nd::RaFlagsExtOption RaFlagsExtOption; ///< RA Flags Extension Option. +typedef Ip6::Nd::Nat64PrefixOption Nat64PrefixOption; ///< NAT64 Prefix Option. typedef Ip6::Nd::RecursiveDnsServerOption RecursiveDnsServerOption; ///< Recursive DNS Server (RDNSS) Option. typedef Ip6::Nd::RouterAdvert RouterAdvert; ///< Router Advertisement (RA). typedef Ip6::Nd::NeighborAdvertMessage NeighborAdvertMessage; ///< Neighbor Advertisement message. @@ -338,6 +340,47 @@ private: RoutePreference mRoutePreference; }; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +/** + * Represents a NAT64 Prefix. + */ +class Nat64Prefix : public LifetimedPrefix, public Clearable +{ +public: + /** + * Sets the NAT64 prefix information from a `Nat64PrefixOption`. + * + * @param[in] aNat64Prefix The `Nat64PrefixOption` to set from. + */ + void SetFrom(const Nat64PrefixOption &aNat64Prefix); + + /** + * Clears (sets to zero) the valid lifetime of the NAT64 prefix. + */ + void ClearValidLifetime(void) { mValidLifetime = 0; } + + /** + * Copies the NAT64 prefix information to a `Nat64PrefixEntry`. + * + * @param[out] aEntry The `Nat64PrefixEntry` to copy information to. + * @param[in] aNow The current time. + */ + void CopyInfoTo(Nat64PrefixEntry &aEntry, TimeMilli aNow) const; + + /** + * Indicates whether this NAT64 prefix is favored over another NAT64 prefix. + * + * @param[in] aPrefix The IPv6 prefix to compare against. + * + * @retval TRUE If this prefix is favored over @p aPrefix. + * @retval FALSE If this prefix is not favored over @p aPrefix. + */ + bool IsFavoredOver(const Ip6::Prefix &aPrefix) const; +}; +#endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /** diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index c094e92e7..b3fbfbd08 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -608,6 +608,9 @@ void RoutingManager::HandleRxRaTrackerDecisionFactorChanged(void) VerifyOrExit(mIsRunning); mOnLinkPrefixManager.HandleRxRaTrackerChanged(); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + mNat64PrefixManager.HandleRaDiscoverChanged(); +#endif mRoutePublisher.Evaluate(); exit: @@ -2187,6 +2190,7 @@ RoutingManager::Nat64PrefixManager::Nat64PrefixManager(Instance &aInstance) , mEnabled(false) , mTimer(aInstance) { + mRaTrackerPrefix.Clear(); mInfraIfPrefix.Clear(); mLocalPrefix.Clear(); mPublishedPrefix.Clear(); @@ -2248,16 +2252,29 @@ void RoutingManager::Nat64PrefixManager::GenerateLocalPrefix(const Ip6::Prefix & const Ip6::Prefix &RoutingManager::Nat64PrefixManager::GetFavoredPrefix(RoutePreference &aPreference) const { - const Ip6::Prefix *favoredPrefix = &mLocalPrefix; + const Ip6::Prefix *favoredPrefix; aPreference = NetworkData::kRoutePreferenceLow; - if (mInfraIfPrefix.IsValidNat64() && + if (mRaTrackerPrefix.IsValidNat64() && Get().mOmrPrefixManager.GetFavoredPrefix().IsInfrastructureDerived()) { + // Per RFC 8781 section 5, discovered NAT64 prefixes from RAs should be favored. + favoredPrefix = &mRaTrackerPrefix; + aPreference = NetworkData::kRoutePreferenceMedium; + } + else if (mInfraIfPrefix.IsValidNat64() && + Get().mOmrPrefixManager.GetFavoredPrefix().IsInfrastructureDerived()) + { + // We have a valid NAT64 prefix from infrastructure (e.g. using DNS - RFC 7050) and the network is aligned with + // an infrastructure-provided OMR prefix. favoredPrefix = &mInfraIfPrefix; aPreference = NetworkData::kRoutePreferenceMedium; } + else + { + favoredPrefix = &mLocalPrefix; + } return *favoredPrefix; } @@ -2405,6 +2422,19 @@ void RoutingManager::Nat64PrefixManager::HandleInfraIfDiscoverDone(const Ip6::Pr Get().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); } +void RoutingManager::Nat64PrefixManager::HandleRaDiscoverChanged(void) +{ + const Ip6::Prefix &favoredPrefix = Get().GetFavoredNat64Prefix(); + + if (favoredPrefix != mRaTrackerPrefix) + { + mRaTrackerPrefix = favoredPrefix; + LogInfo("RA discovered NAT64 prefix: %s", + mRaTrackerPrefix.IsValidNat64() ? mRaTrackerPrefix.ToString().AsCString() : "none"); + Get().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); + } +} + Nat64::State RoutingManager::Nat64PrefixManager::GetState(void) const { Nat64::State state = Nat64::kStateDisabled; diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index e70fee240..d0b04dbc7 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -827,6 +827,7 @@ private: const Ip6::Prefix &GetFavoredPrefix(RoutePreference &aPreference) const; void Evaluate(void); void HandleInfraIfDiscoverDone(const Ip6::Prefix &aPrefix); + void HandleRaDiscoverChanged(void); void HandleTimer(void); private: @@ -837,7 +838,8 @@ private: using Nat64Timer = TimerMilliIn; bool mEnabled; - Ip6::Prefix mInfraIfPrefix; // The latest NAT64 prefix discovered on the infrastructure interface. + Ip6::Prefix mRaTrackerPrefix; // The best NAT64 prefix discovered from RAs (RFC 8781). + Ip6::Prefix mInfraIfPrefix; // The platform-provided NAT64 prefix (e.g., using DNS - RFC 7050). Ip6::Prefix mLocalPrefix; // The local prefix (from BR ULA prefix). Ip6::Prefix mPublishedPrefix; // The prefix to publish in Net Data (empty or local or from infra-if). RoutePreference mPublishedPreference; // The published prefix preference. diff --git a/src/core/border_router/rx_ra_tracker.cpp b/src/core/border_router/rx_ra_tracker.cpp index 9d46ff919..140893320 100644 --- a/src/core/border_router/rx_ra_tracker.cpp +++ b/src/core/border_router/rx_ra_tracker.cpp @@ -218,6 +218,12 @@ void RxRaTracker::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPacket, ProcessRouteInfoOption(static_cast(option), *router); break; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + case Option::kTypeNat64Prefix: + ProcessNat64PrefixOption(static_cast(option), *router); + break; +#endif + case Option::kTypeRecursiveDnsServer: ProcessRecursiveDnsServerOption(static_cast(option), *router); break; @@ -433,6 +439,51 @@ exit: return; } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +void RxRaTracker::ProcessNat64PrefixOption(const Nat64PrefixOption &aNat64Prefix, Router &aRouter) +{ + Ip6::Prefix prefix; + uint32_t lifetime; + Entry *entry; + + VerifyOrExit(aNat64Prefix.IsValid()); + SuccessOrExit(aNat64Prefix.GetPrefix(prefix)); + + lifetime = aNat64Prefix.GetLifetime(); + + LogNat64PrefixOption(prefix, lifetime); + + if (lifetime == 0) + { + aRouter.mNat64Prefixes.RemoveAndFreeAllMatching(prefix); + } + else + { + entry = aRouter.mNat64Prefixes.FindMatching(prefix); + + if (entry != nullptr) + { + entry->SetFrom(aNat64Prefix); + } + else + { + entry = AllocateEntry(); + if (entry == nullptr) + { + LogWarn("Discovered too many entries, ignore NAT64 prefix %s", prefix.ToString().AsCString()); + ExitNow(); + } + + entry->SetFrom(aNat64Prefix); + aRouter.mNat64Prefixes.Push(*entry); + } + } + +exit: + return; +} +#endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + void RxRaTracker::ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, Router &aRouter) { Entry *entry; @@ -521,8 +572,11 @@ exit: template RxRaTracker::Entry *RxRaTracker::AllocateEntry(void) { static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + TypeTraits::IsSame::kValue || +#endif TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, - "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); + "Type MUST be RoutePrefix, OnLinkPrefix, Nat64Prefix, RdnssAddress, or IfAddress"); Entry *entry = nullptr; SharedEntry *sharedEntry = mEntryPool.Allocate(); @@ -539,6 +593,9 @@ template <> void RxRaTracker::Entry::Free(void) { mOnLinkPrefixes.Free(); mRoutePrefixes.Free(); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + mNat64Prefixes.Free(); +#endif mRdnssAddresses.Free(); Get().mRouterPool.Free(*this); } @@ -546,8 +603,11 @@ template <> void RxRaTracker::Entry::Free(void) template void RxRaTracker::Entry::Free(void) { static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + TypeTraits::IsSame::kValue || +#endif TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, - "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); + "Type MUST be RoutePrefix, OnLinkPrefix, Nat64Prefix, RdnssAddress, or IfAddress"); Get().mEntryPool.Free(*reinterpret_cast(this)); } @@ -653,6 +713,16 @@ void RxRaTracker::RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold) } } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + for (Nat64Prefix &entry : router.mNat64Prefixes) + { + if (entry.GetLastUpdateTime() <= aTimeThreshold) + { + entry.ClearValidLifetime(); + } + } +#endif + for (RdnssAddress &entry : router.mRdnssAddresses) { if (entry.GetLastUpdateTime() <= aTimeThreshold) @@ -689,6 +759,9 @@ void RxRaTracker::Evaluate(void) router.mOnLinkPrefixes.RemoveAndFreeAllMatching(expirationChecker); router.mRoutePrefixes.RemoveAndFreeAllMatching(expirationChecker); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + router.mNat64Prefixes.RemoveAndFreeAllMatching(expirationChecker); +#endif if (router.mRdnssAddresses.RemoveAndFreeAllMatching(expirationChecker)) { @@ -698,7 +771,7 @@ void RxRaTracker::Evaluate(void) //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Remove any router entry that no longer has any valid on-link - // or route prefixes, RDNSS addresses, or other relevant flags set. + // or route prefixes, NAT64 Prefix, RDNSS addresses, or other relevant flags set. mRouters.RemoveAllMatching(removedRouters, Router::EmptyChecker()); @@ -738,6 +811,13 @@ void RxRaTracker::Evaluate(void) router.mAllEntriesDisregarded &= entry.ShouldDisregard(); } + +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + for (Nat64Prefix &entry : router.mNat64Prefixes) + { + mDecisionFactors.UpdateFrom(entry); + } +#endif } #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE @@ -790,6 +870,13 @@ void RxRaTracker::Evaluate(void) } } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + for (const Nat64Prefix &entry : router.mNat64Prefixes) + { + entryExpireTime.UpdateIfEarlier(entry.GetExpireTime()); + } +#endif + for (const RdnssAddress &entry : router.mRdnssAddresses) { rdnsssAddrExpireTime.UpdateIfEarlier(entry.GetExpireTime()); @@ -973,6 +1060,13 @@ void RxRaTracker::HandleRouterTimer(void) entry.ClearValidLifetime(); } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + for (Nat64Prefix &entry : router.mNat64Prefixes) + { + entry.ClearValidLifetime(); + } +#endif + for (RdnssAddress &entry : router.mRdnssAddresses) { entry.ClearLifetime(); @@ -1114,6 +1208,24 @@ exit: return error; } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +Error RxRaTracker::GetNextNat64PrefixEntry(PrefixTableIterator &aIterator, Nat64PrefixEntry &aEntry) const +{ + Error error = kErrorNone; + Iterator &iterator = static_cast(aIterator); + + ClearAllBytes(aEntry); + + SuccessOrExit(error = iterator.AdvanceToNextNat64PrefixEntry()); + + iterator.GetRouter()->CopyInfoTo(aEntry.mRouter, iterator.GetInitTime(), iterator.GetInitUptime()); + iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); + +exit: + return error; +} +#endif + Error RxRaTracker::GetNextRdnssAddrEntry(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const { Error error = kErrorNone; @@ -1335,6 +1447,30 @@ exit: return error; } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +Error RxRaTracker::Iterator::AdvanceToNextNat64PrefixEntry(void) +{ + Error error = kErrorNone; + + VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); + + if (HasEntry()) + { + VerifyOrExit(GetType() == kNat64PrefixIterator, error = kErrorInvalidArgs); + SetEntry(GetEntry()->GetNext()); + } + + while (!HasEntry()) + { + SuccessOrExit(error = AdvanceToNextRouter(kNat64PrefixIterator)); + SetEntry(GetRouter()->mNat64Prefixes.GetHead()); + } + +exit: + return error; +} +#endif + Error RxRaTracker::Iterator::AdvanceToNextRdnssAddrEntry(void) { Error error = kErrorNone; @@ -1427,18 +1563,20 @@ bool RxRaTracker::Router::Matches(const EmptyChecker &aChecker) bool hasFlags = false; - // Router can be removed if it does not advertise M or O flags and - // 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. + // Router can be removed if it does not advertise M or O flags and also does not have any advertised prefix entries + // (RIO/PIO/NAT64) 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() && mRdnssAddresses.IsEmpty(); + return !hasFlags && mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty() && mRdnssAddresses.IsEmpty() +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + && mNat64Prefixes.IsEmpty() +#endif + ; } bool RxRaTracker::Router::IsPeerBr(void) const @@ -1569,6 +1707,16 @@ exit: return; } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +void RxRaTracker::DecisionFactors::UpdateFrom(const Nat64Prefix &aNat64Prefix) +{ + if (aNat64Prefix.IsFavoredOver(mFavoredNat64Prefix)) + { + mFavoredNat64Prefix = aNat64Prefix.GetPrefix(); + } +} +#endif + //--------------------------------------------------------------------------------------------------------------------- // RxRaTracker::RsSender diff --git a/src/core/border_router/rx_ra_tracker.hpp b/src/core/border_router/rx_ra_tracker.hpp index 030cef1da..69289de59 100644 --- a/src/core/border_router/rx_ra_tracker.hpp +++ b/src/core/border_router/rx_ra_tracker.hpp @@ -150,6 +150,20 @@ public: */ Error GetNextRouterEntry(PrefixTableIterator &aIterator, RouterEntry &aEntry) const; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + /** + * Iterates over the discovered NAT64 prefix 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 GetNextNat64PrefixEntry(PrefixTableIterator &aIterator, Nat64PrefixEntry &aEntry) const; +#endif + /** * Iterates over the discovered Recursive DNS Server (RDNSS) address entries. * @@ -228,6 +242,15 @@ public: */ const Ip6::Prefix &GetFavoredOnLinkPrefix(void) const { return mDecisionFactors.mFavoredOnLinkPrefix; } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + /** + * Gets the favored NAT64 prefix among all discovered NAT64 prefixes. + * + * @returns The favored NAT64 prefix. + */ + const Ip6::Prefix &GetFavoredNat64Prefix(void) const { return mDecisionFactors.mFavoredNat64Prefix; } +#endif + /** * Sets the Managed Address Configuration (M) and Other Configuration (O) flags on a Router Advertisement header. * @@ -362,6 +385,9 @@ private: using OnLinkPrefixList = OwningList>; using RoutePrefixList = OwningList>; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + using Nat64PrefixList = OwningList>; +#endif using RdnssAddressList = OwningList>; // `mDiscoverTime` tracks the initial discovery time of @@ -377,6 +403,9 @@ private: Ip6::Address mAddress; OnLinkPrefixList mOnLinkPrefixes; RoutePrefixList mRoutePrefixes; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + Nat64PrefixList mNat64Prefixes; +#endif RdnssAddressList mRdnssAddresses; uint32_t mDiscoverTime; TimeMilli mLastUpdateTime; @@ -401,6 +430,7 @@ private: kUnspecified, kRouterIterator, kPrefixIterator, + kNat64PrefixIterator, kRdnssAddrIterator, kIfAddrIterator, kNetDataBrIterator, // Used by `NetDataPeerBrTracker` @@ -412,9 +442,12 @@ private: kRoutePrefix, }; - void Init(const Entry *aRoutersHead, uint32_t aUptime); - Error AdvanceToNextRouter(Type aType); - Error AdvanceToNextPrefixEntry(void); + void Init(const Entry *aRoutersHead, uint32_t aUptime); + Error AdvanceToNextRouter(Type aType); + Error AdvanceToNextPrefixEntry(void); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + Error AdvanceToNextNat64PrefixEntry(void); +#endif Error AdvanceToNextRdnssAddrEntry(void); Error AdvanceToNextIfAddrEntry(const Entry *aListHead); uint32_t GetInitUptime(void) const { return mData0; } @@ -455,6 +488,9 @@ private: SharedEntry *mNext; Entry mOnLinkEntry; Entry mRouteEntry; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + Entry mNat64PrefixEntry; +#endif Entry mRdnssAddrEntry; Entry mIfAddrEntry; }; @@ -470,13 +506,20 @@ private: void UpdateFlagsFrom(const Router &aRouter); void UpdateFrom(const OnLinkPrefix &aOnLinkPrefix); void UpdateFrom(const RoutePrefix &aRoutePrefix); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + void UpdateFrom(const Nat64Prefix &aNat64Prefix); +#endif Ip6::Prefix mFavoredOnLinkPrefix; - bool mHasNonUlaRoute : 1; - bool mHasNonUlaOnLink : 1; - bool mHasUlaOnLink : 1; - bool mHeaderManagedAddressConfigFlag : 1; - bool mHeaderOtherConfigFlag : 1; +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + Ip6::Prefix mFavoredNat64Prefix; +#endif + bool mHasNonUlaRoute : 1; + bool mHasNonUlaOnLink : 1; + bool mHasUlaOnLink : 1; + bool mHeaderManagedAddressConfigFlag : 1; + bool mHeaderOtherConfigFlag : 1; + #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE uint16_t mReachablePeerBrCount; #endif @@ -529,6 +572,9 @@ 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); +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + void ProcessNat64PrefixOption(const Nat64PrefixOption &aNat64Prefix, Router &aRouter); +#endif void ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, Router &aRouter); void UpdateIfAddresses(const Ip6::Address &aAddress); void RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold); @@ -609,6 +655,13 @@ template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::G template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) { return mRouteEntry; } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) +{ + return mNat64PrefixEntry; +} +#endif + template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) { return mRdnssAddrEntry; diff --git a/src/core/net/nd6.cpp b/src/core/net/nd6.cpp index 420f4bf00..689138e9e 100644 --- a/src/core/net/nd6.cpp +++ b/src/core/net/nd6.cpp @@ -182,6 +182,70 @@ void RaFlagsExtOption::Init(void) OT_UNUSED_VARIABLE(mFlags); } +//---------------------------------------------------------------------------------------------------------------------- +// Nat64PrefixOption + +void Nat64PrefixOption::Init(void) +{ + Clear(); + SetType(kTypeNat64Prefix); + SetSize(sizeof(Nat64PrefixOption)); +} + +const uint8_t Nat64PrefixOption::kPrefixLengths[] = {96, 64, 56, 48, 40, 32}; + +void Nat64PrefixOption::SetLifetime(uint32_t aLifetime) +{ + uint32_t scaledLifetime = DivideAndRoundUp(aLifetime, kLifetimeScalingUnit); + + mPrefixAttr = BigEndian::HostSwap16((BigEndian::HostSwap16(mPrefixAttr) & kPrefixLengthCodeMask) | + ClampToUint16(scaledLifetime << kScaledLifetimeOffset)); +} + +void Nat64PrefixOption::SetPrefixLengthCode(const uint8_t aPrefixLengthCode) +{ + mPrefixAttr = BigEndian::HostSwap16((BigEndian::HostSwap16(mPrefixAttr) & ~kPrefixLengthCodeMask) | + (aPrefixLengthCode & kPrefixLengthCodeMask)); +} + +Error Nat64PrefixOption::SetPrefix(const Prefix &aPrefix) +{ + Error error = kErrorInvalidArgs; + + memcpy(mPrefixMsb, aPrefix.GetBytes(), sizeof(mPrefixMsb)); + + for (uint8_t code = 0; code < ClampToUint8(GetArrayLength(kPrefixLengths)); code++) + { + if (kPrefixLengths[code] == aPrefix.mLength) + { + SetPrefixLengthCode(code); + error = kErrorNone; + break; + } + } + + return error; +} + +Error Nat64PrefixOption::GetPrefix(Prefix &aPrefix) const +{ + Error error = kErrorNone; + uint8_t prefixLengthCode = GetPrefixLengthCode(); + + VerifyOrExit(prefixLengthCode < GetArrayLength(kPrefixLengths), error = kErrorParse); + + aPrefix.Set(mPrefixMsb, kPrefixLengths[prefixLengthCode]); + +exit: + return error; +} + +bool Nat64PrefixOption::IsValid(void) const +{ + // Per RFC 8781, the length of the NAT64 Prefix Option MUST be 2 (in units of 8 octets). + return (GetLength() == 2) && (GetPrefixLengthCode() < GetArrayLength(kPrefixLengths)); +} + //---------------------------------------------------------------------------------------------------------------------- // RecursiveDnsServerOption @@ -332,6 +396,27 @@ exit: return error; } +Error RouterAdvert::TxMessage::AppendNat64PrefixOption(const Prefix &aPrefix, uint32_t aLifetime) +{ + Error error = kErrorNone; + Nat64PrefixOption *pref64; + + VerifyOrExit(aPrefix.IsValidNat64(), error = kErrorInvalidArgs); + + pref64 = static_cast(AppendOption(sizeof(Nat64PrefixOption))); + VerifyOrExit(pref64 != nullptr, error = kErrorNoBufs); + + pref64->Init(); + pref64->SetLifetime(aLifetime); + + // `SetPrefix()` can fail if `aPrefix` has an unsupported length. + // `IsValidNat64()` check above ensures this will not happen. + IgnoreError(pref64->SetPrefix(aPrefix)); + +exit: + return error; +} + Error RouterAdvert::TxMessage::AppendRecursiveDnsServerOption(const Address *aAddresses, uint8_t aNumAddresses, uint32_t aLifetime) diff --git a/src/core/net/nd6.hpp b/src/core/net/nd6.hpp index a67c24a12..a0192f046 100644 --- a/src/core/net/nd6.hpp +++ b/src/core/net/nd6.hpp @@ -84,6 +84,7 @@ public: kTypeRouteInfo = 24, ///< Route Information Option. kTypeRecursiveDnsServer = 25, ///< Recursive DNS Server (RDNSS) Option. kTypeRaFlagsExtension = 26, ///< RA Flags Extension Option. + kTypeNat64Prefix = 38, ///< NAT64 Prefix Option (aka PREF64 Option). }; static constexpr uint16_t kLengthUnit = 8; ///< The unit of length in octets. @@ -505,6 +506,119 @@ private: static_assert(sizeof(RaFlagsExtOption) == 8, "invalid RaFlagsExtOption structure"); +/** + * Represents the NAT64 Prefix Option (aka PREF64 Option). + * + * See section 4 of RFC 8781 for definition of this option [https://tools.ietf.org/html/rfc8781#section-4] + */ +OT_TOOL_PACKED_BEGIN +class Nat64PrefixOption : public Option, private Clearable +{ + friend class Clearable; + +public: + static constexpr Type kType = kTypeNat64Prefix; ///< NAT64 Prefix Option Type. + + /** + * Initializes the NAT64 Prefix option with proper type and length and sets all other fields to zero. + */ + void Init(void); + + /** + * Sets the NAT64 prefix lifetime. + * + * @param[in] aLifetime The prefix lifetime in seconds. + */ + void SetLifetime(uint32_t aLifetime); + + /** + * Returns the NAT64 prefix lifetime in seconds. + * + * The NAT64 prefix lifetime is encoded by scaled lifetime in units of 8 seconds. + * + * @returns The prefix lifetime in seconds. + */ + uint32_t GetLifetime(void) const + { + return (BigEndian::HostSwap16(mPrefixAttr) >> kScaledLifetimeOffset) * kLifetimeScalingUnit; + } + + /** + * Sets the prefix. + * + * @param[in] aPrefix The prefix contained in this option. + * + * @retval kErrorNone Successfully set the prefix. + * @retval kErrorInvalidArgs The prefix length in @p aPrefix is not a valid NAT64 prefix length (must be one of + * 32, 40, 48, 56, 64, or 96). + */ + Error SetPrefix(const Prefix &aPrefix); + + /** + * Gets the prefix in this option. + * + * @param[out] aPrefix Reference to a `Prefix` to return the prefix. + * + * @retval kErrorNone Successfully retrieved the prefix. + * @retval kErrorParse The Prefix Length Code is not valid. + */ + Error GetPrefix(Prefix &aPrefix) const; + + /** + * Indicates whether or not the option is valid. + * + * @retval TRUE The option is valid + * @retval FALSE The option is not valid. + */ + bool IsValid(void) const; + + Nat64PrefixOption(void) = delete; + +private: + // NAT64 Prefix 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 | Scaled Lifetime | PLC | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | Highest 96 bits of the Prefix | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + static constexpr uint32_t kLifetimeScalingUnit = 8; // Lifetime is scaled in units of 8 seconds. + static constexpr uint16_t kScaledLifetimeOffset = 3; // Scaled Lifetime offset in `mPrefixAttr`. + static constexpr uint16_t kPrefixLengthCodeMask = 0x07; // Prefix Length Code mask in `mPrefixAttr`. + + static const uint8_t kPrefixLengths[]; // Map from prefix length code to prefix length in bits. + + /** + * Returns the NAT64 prefix length code. + * + * The prefix length code values 0, 1, 2, 3, 4, and 5 indicate the NAT64 prefix length of 96, 64, 56, 48, 40, and 32 + * bits, respectively. + * + * @returns The prefix length code. + */ + uint8_t GetPrefixLengthCode(void) const { return BigEndian::HostSwap16(mPrefixAttr) & kPrefixLengthCodeMask; } + + /** + * Sets the NAT64 prefix length code. + * + * @param[in] aPrefixLengthCode The prefix length code. + */ + void SetPrefixLengthCode(const uint8_t aPrefixLengthCode); + + uint16_t mPrefixAttr; // The prefix attributes (Scaled Lifetime and Prefix Length Code). + uint8_t mPrefixMsb[12]; + +} OT_TOOL_PACKED_END; + +static_assert(sizeof(Nat64PrefixOption) == 16, "invalid Nat64PrefixOption structure"); + /** * Represents the Recursive DNS Server (RDNSS) Option. * @@ -939,9 +1053,27 @@ public: */ Error AppendRouteInfoOption(const Prefix &aPrefix, uint32_t aRouteLifetime, RoutePreference aPreference); + /** + * Appends a NAT64 Prefix Option to the RA message. + * + * @note This is intended for testing only. An OTBR should not advertise a NAT64 prefix option; it should only + * process received ones. + * + * @param[in] aPrefix The prefix. + * @param[in] aLifetime The lifetime in seconds. + * + * @retval kErrorNone Option is appended successfully. + * @retval kErrorInvalidArgs Unsupported length for NAT64 prefix. + * @retval kErrorNoBufs Insufficient available buffers to grow the message. + */ + Error AppendNat64PrefixOption(const Prefix &aPrefix, uint32_t aLifetime); + /** * Append a Recursive DNS Server Option to the RA message. * + * @note This is intended for testing only. An OTBR should not advertise an RDNSS option; it should only + * process received ones. + * * @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. diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index a17ac3d98..ff3469107 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -613,6 +613,18 @@ void LogRouterAdvert(const Icmp6Packet &aPacket) break; } + case Ip6::Nd::Option::kTypeNat64Prefix: + { + const Ip6::Nd::Nat64PrefixOption &nat64Prefix = static_cast(option); + Ip6::Prefix prefix; + + VerifyOrQuit(nat64Prefix.IsValid()); + SuccessOrQuit(nat64Prefix.GetPrefix(prefix)); + + Log(" NAT64 Prefix - %s, lifetime:%u", prefix.ToString().AsCString(), nat64Prefix.GetLifetime()); + break; + } + case Ip6::Nd::Option::kTypeRecursiveDnsServer: { const Ip6::Nd::RecursiveDnsServerOption &rdnss = @@ -880,6 +892,18 @@ struct RaFlags : public Clearable bool mSnacRouterFlag; }; +struct Nat64Prefix +{ + Nat64Prefix(const Ip6::Prefix &aPrefix, uint32_t aLifetime) + : mPrefix(aPrefix) + , mLifetime(aLifetime) + { + } + + const Ip6::Prefix &mPrefix; + uint32_t mLifetime; +}; + struct Rdnss { template static Rdnss Create(uint32_t aLifetime, const Ip6::Address (&aAddresses)[kNumAddrs]) @@ -906,6 +930,8 @@ void BuildRouterAdvert(Ip6::Nd::RouterAdvert::TxMessage &aRaMsg, uint16_t aNumRios, const Rdnss *aRdnsses, uint16_t aNumRdnsses, + const Nat64Prefix *aNat64Prefixes, + uint16_t aNumNat64Prefixes, const DefaultRoute &aDefaultRoute, const RaFlags &aRaFlags) { @@ -942,6 +968,13 @@ void BuildRouterAdvert(Ip6::Nd::RouterAdvert::TxMessage &aRaMsg, SuccessOrQuit(aRaMsg.AppendRouteInfoOption(aRios->mPrefix, aRios->mValidLifetime, aRios->mPreference)); } +#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + for (; aNumNat64Prefixes > 0; aNat64Prefixes++, aNumNat64Prefixes--) + { + SuccessOrQuit(aRaMsg.AppendNat64PrefixOption(aNat64Prefixes->mPrefix, aNat64Prefixes->mLifetime)); + } +#endif + for (; aNumRdnsses > 0; aRdnsses++, aNumRdnsses--) { SuccessOrQuit( @@ -956,13 +989,16 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, uint16_t aNumRios, const Rdnss *aRdnsses, uint16_t aNumRdnsses, + const Nat64Prefix *aNat64Prefixes, + uint16_t aNumNat64Prefixes, const DefaultRoute &aDefaultRoute, const RaFlags &aRaFlags) { Ip6::Nd::RouterAdvert::TxMessage raMsg; Icmp6Packet packet; - BuildRouterAdvert(raMsg, aPios, aNumPios, aRios, aNumRios, aRdnsses, aNumRdnsses, aDefaultRoute, aRaFlags); + BuildRouterAdvert(raMsg, aPios, aNumPios, aRios, aNumRios, aRdnsses, aNumRdnsses, aNat64Prefixes, aNumNat64Prefixes, + aDefaultRoute, aRaFlags); raMsg.GetAsPacket(packet); SendRouterAdvert(aRouterAddress, packet); @@ -977,7 +1013,7 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, aPios, kNumPios, aRios, kNumRios, nullptr, 0, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, aPios, kNumPios, aRios, kNumRios, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); } template @@ -986,7 +1022,7 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, aPios, kNumPios, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, aPios, kNumPios, nullptr, 0, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); } template @@ -995,19 +1031,19 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, nullptr, 0, aRios, kNumRios, nullptr, 0, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, nullptr, 0, aRios, kNumRios, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); } void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Rdnss &aRdnss) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, &aRdnss, 1, + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, &aRdnss, 1, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); } template void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Rdnss (&aRdnsses)[kNumRdnsses]) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aRdnsses, kNumRdnsses, + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aRdnsses, kNumRdnsses, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); } @@ -1015,15 +1051,21 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute, const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); } void SendRouterAdvert(const Ip6::Address &aRouterAddress, const RaFlags &aRaFlags) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), aRaFlags); } +void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Nat64Prefix &aNat64Pio) +{ + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, nullptr, 0, &aNat64Pio, 1, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); +} + struct OnLinkPrefix : public Pio { OnLinkPrefix(const Ip6::Prefix &aPrefix, @@ -4507,10 +4549,16 @@ void TestAutoEnableOfSrpServer(void) void TestNat64PrefixSelection(void) { Ip6::Prefix localNat64; - Ip6::Prefix ailNat64 = PrefixFromString("2000:0:0:1:0:0::", 96); Ip6::Prefix localOmr; - Ip6::Prefix omrPrefix = PrefixFromString("2000:0000:1111:4444::", 64); NetworkData::OnMeshPrefixConfig prefixConfig; + Ip6::Prefix omrPrefix = PrefixFromString("2000:0000:1111:4444::", 64); + Ip6::Prefix infraIfNat64Prefix = PrefixFromString("2000:0:0:1:0:0::", 96); + Ip6::Prefix raTrackerNat64PrefixA = PrefixFromString("2000:0:0:2:0:f::", 96); + Ip6::Address routerAddressA = AddressFromString("fd00::aaaa"); + Ip6::Prefix raTrackerNat64PrefixB = PrefixFromString("2000:0:0:2:0:0::", 96); + Ip6::Address routerAddressB = AddressFromString("fd00::bbbb"); + Ip6::Prefix raTrackerNat64PrefixC = PrefixFromString("2000:0:0:2:0:8::", 96); + Ip6::Address routerAddressC = AddressFromString("fd00::cccc"); uint16_t heapAllocations; Log("--------------------------------------------------------------------------------------------"); @@ -4530,7 +4578,7 @@ void TestNat64PrefixSelection(void) Log("Local OMR prefix is %s", localOmr.ToString().AsCString()); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Enable Nat64 Prefix Manager. Check local NAT64 prefix in Network Data. + // Enable NAT64 Prefix Manager. Check local NAT64 prefix in Network Data. sInstance->Get().SetNat64PrefixManagerEnabled(true); @@ -4540,10 +4588,10 @@ void TestNat64PrefixSelection(void) VerifyNat64PrefixInNetData(localNat64); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // AIL NAT64 prefix discovered. No infra-derived OMR prefix in Network Data. - // Check local NAT64 prefix in Network Data. + // InfraIf NAT64 prefix (e.g. using DNS - RFC 7050) discovered. No infra-derived OMR prefix in Network Data. Check + // local NAT64 prefix in Network Data. - DiscoverNat64Prefix(ailNat64); + DiscoverNat64Prefix(infraIfNat64Prefix); AdvanceTime(20000); @@ -4551,7 +4599,7 @@ void TestNat64PrefixSelection(void) //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Add a medium preference OMR prefix into Network Data. - // Check AIL NAT64 prefix published in Network Data. + // Check InfraIf NAT64 prefix published in Network Data. prefixConfig.Clear(); prefixConfig.mPrefix = omrPrefix; @@ -4568,14 +4616,60 @@ void TestNat64PrefixSelection(void) AdvanceTime(20000); VerifyOmrPrefixInNetData(omrPrefix, /* aDefaultRoute */ false); - VerifyNat64PrefixInNetData(ailNat64); + VerifyNat64PrefixInNetData(infraIfNat64Prefix); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // AIL NAT64 prefix removed. + // Send an RA from a router advertising a NAT64 prefix. + // Check that the RA-discovered NAT64 prefix is now favored and published. + + SendRouterAdvert(routerAddressA, Nat64Prefix(raTrackerNat64PrefixA, kValidLitime)); + + AdvanceTime(20000); + + VerifyOmrPrefixInNetData(omrPrefix, /* aDefaultRoute */ false); + VerifyNat64PrefixInNetData(raTrackerNat64PrefixA); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send two RAs from routers B and C advertising two different NAT64 prefixes. + // Check that the numerically smallest prefix is now favored. + + SendRouterAdvert(routerAddressB, Nat64Prefix(raTrackerNat64PrefixB, kValidLitime)); + SendRouterAdvert(routerAddressC, Nat64Prefix(raTrackerNat64PrefixC, kValidLitime)); + + AdvanceTime(20000); + + VerifyOmrPrefixInNetData(omrPrefix, /* aDefaultRoute */ false); + VerifyNat64PrefixInNetData(raTrackerNat64PrefixB); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove RA-discovered NAT64 prefix A and B from router A and B. + // Check that the remained RA-discovered prefix C is now favored. + + SendRouterAdvert(routerAddressA, Nat64Prefix(raTrackerNat64PrefixA, 0)); + SendRouterAdvert(routerAddressB, Nat64Prefix(raTrackerNat64PrefixB, 0)); + + AdvanceTime(20000); + + VerifyOmrPrefixInNetData(omrPrefix, /* aDefaultRoute */ false); + VerifyNat64PrefixInNetData(raTrackerNat64PrefixC); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove RA-discovered NAT64 prefix C. + // Check that the infra-if NAT64 prefix is published again. + + SendRouterAdvert(routerAddressC, Nat64Prefix(raTrackerNat64PrefixC, 0)); + + AdvanceTime(20000); + + VerifyOmrPrefixInNetData(omrPrefix, /* aDefaultRoute */ false); + VerifyNat64PrefixInNetData(infraIfNat64Prefix); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // InfraIf NAT64 prefix removed. // Check local NAT64 prefix in Network Data. - ailNat64.Clear(); - DiscoverNat64Prefix(ailNat64); + infraIfNat64Prefix.Clear(); + DiscoverNat64Prefix(infraIfNat64Prefix); AdvanceTime(20000); @@ -4614,7 +4708,7 @@ template void ReportPdPrefixesAsRa(const Pio (&aPios)[kNumPi Ip6::Nd::RouterAdvert::TxMessage raMsg; Icmp6Packet packet; - BuildRouterAdvert(raMsg, aPios, kNumPios, nullptr, 0, nullptr, 0, + BuildRouterAdvert(raMsg, aPios, kNumPios, nullptr, 0, nullptr, 0, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); raMsg.GetAsPacket(packet);