diff --git a/Android.mk b/Android.mk index ca6599afa..02ec53819 100644 --- a/Android.mk +++ b/Android.mk @@ -197,6 +197,7 @@ LOCAL_SRC_FILES := \ src/core/api/logging_api.cpp \ src/core/api/message_api.cpp \ src/core/api/multi_radio_api.cpp \ + src/core/api/nat64_api.cpp \ src/core/api/netdata_api.cpp \ src/core/api/netdata_publisher_api.cpp \ src/core/api/netdiag_api.cpp \ @@ -583,6 +584,7 @@ LOCAL_CPPFLAGS := \ $(NULL) LOCAL_LDLIBS := \ + -lanl \ -lrt \ -lutil diff --git a/examples/platforms/simulation/infra_if.c b/examples/platforms/simulation/infra_if.c index b62041b8d..67cb9f752 100644 --- a/examples/platforms/simulation/infra_if.c +++ b/examples/platforms/simulation/infra_if.c @@ -51,4 +51,11 @@ otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, return OT_ERROR_FAILED; } + +otError otPlatInfraIfDiscoverNat64Prefix(uint32_t aInfraIfIndex) +{ + OT_UNUSED_VARIABLE(aInfraIfIndex); + + return OT_ERROR_FAILED; +} #endif diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index 8cfec1bdb..fcd295dd1 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -227,6 +227,23 @@ otError otBorderRoutingGetOnLinkPrefix(otInstance *aInstance, otIp6Prefix *aPref */ otError otBorderRoutingGetNat64Prefix(otInstance *aInstance, otIp6Prefix *aPrefix); +/** + * Gets the currently favored NAT64 prefix. + * + * The favored NAT64 prefix can be discovered from infrastructure link or can be this device's local NAT64 prefix. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[out] aPrefix A pointer to output the favored NAT64 prefix. + * @param[out] aPreference A pointer to output the preference associated the favored prefix. + * + * @retval OT_ERROR_INVALID_STATE The Border Routing Manager is not initialized yet. + * @retval OT_ERROR_NONE Successfully retrieved the favored NAT64 prefix. + * + */ +otError otBorderRoutingGetFavoredNat64Prefix(otInstance * aInstance, + otIp6Prefix * aPrefix, + otRoutePreference *aPreference); + /** * This function initializes an `otBorderRoutingPrefixTableIterator`. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 9666074e4..a7d71ab0c 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (233) +#define OPENTHREAD_API_VERSION (234) /** * @addtogroup api-instance diff --git a/include/openthread/ip6.h b/include/openthread/ip6.h index f624b426e..d289536e2 100644 --- a/include/openthread/ip6.h +++ b/include/openthread/ip6.h @@ -679,6 +679,16 @@ void otIp6PrefixToString(const otIp6Prefix *aPrefix, char *aBuffer, uint16_t aSi */ uint8_t otIp6PrefixMatch(const otIp6Address *aFirst, const otIp6Address *aSecond); +/** + * This method gets a prefix with @p aLength from @p aAddress. + * + * @param[in] aAddress A pointer to an IPv6 address. + * @param[in] aLength The length of prefix in bits. + * @param[out] aPrefix A pointer to output the IPv6 prefix. + * + */ +void otIp6GetPrefix(const otIp6Address *aAddress, uint8_t aLength, otIp6Prefix *aPrefix); + /** * This function indicates whether or not a given IPv6 address is the Unspecified Address. * diff --git a/include/openthread/nat64.h b/include/openthread/nat64.h index 55bef4df9..2d22a5cf2 100644 --- a/include/openthread/nat64.h +++ b/include/openthread/nat64.h @@ -35,6 +35,7 @@ #ifndef OPENTHREAD_NAT64_H_ #define OPENTHREAD_NAT64_H_ +#include #include #ifdef __cplusplus @@ -87,6 +88,32 @@ typedef struct otIp4Cidr uint8_t mLength; } otIp4Cidr; +/** + * Test if two IPv4 addresses are the same. + * + * @param[in] aFirst A pointer to the first IPv4 address to compare. + * @param[in] aSecond A pointer to the second IPv4 address to compare. + * + * @retval TRUE The two IPv4 addresses are the same. + * @retval FALSE The two IPv4 addresses are not the same. + * + */ +bool otIp4IsAddressEqual(const otIp4Address *aFirst, const otIp4Address *aSecond); + +/** + * Set @p aIp4Address by performing NAT64 address translation from @p aIp6Address as specified + * in RFC 6052. + * + * The NAT64 @p aPrefixLength MUST be one of the following values: 32, 40, 48, 56, 64, or 96, otherwise the behavior + * of this method is undefined. + * + * @param[in] aPrefixLength The prefix length to use for IPv4/IPv6 translation. + * @param[in] aIp6Address A pointer to an IPv6 address. + * @param[out] aIp4Address A pointer to output the IPv4 address. + * + */ +void otIp4ExtractFromIp6Address(uint8_t aPrefixLength, const otIp6Address *aIp6Address, otIp4Address *aIp4Address); + /** * @} * diff --git a/include/openthread/platform/infra_if.h b/include/openthread/platform/infra_if.h index 9242213ef..2e40434e8 100644 --- a/include/openthread/platform/infra_if.h +++ b/include/openthread/platform/infra_if.h @@ -133,6 +133,35 @@ extern void otPlatInfraIfRecvIcmp6Nd(otInstance * aInstance, */ extern otError otPlatInfraIfStateChanged(otInstance *aInstance, uint32_t aInfraIfIndex, bool aIsRunning); +/** + * Send a request to discover the NAT64 prefix on the infrastructure interface with @p aInfraIfIndex. + * + * OpenThread will call this method periodically to monitor the presence or change of NAT64 prefix. + * + * @param[in] aInfraIfIndex The index of the infrastructure interface to discover the NAT64 prefix. + * + * @retval OT_ERROR_NONE Successfully request NAT64 prefix discovery. + * @retval OT_ERROR_FAILED Failed to request NAT64 prefix discovery. + * + */ +otError otPlatInfraIfDiscoverNat64Prefix(uint32_t aInfraIfIndex); + +/** + * The infra interface driver calls this method to notify OpenThread that + * the discovery of NAT64 prefix is done. + * + * This method is expected to be invoked after calling otPlatInfraIfDiscoverNat64Prefix. + * If no NAT64 prefix is discovered, @p aIp6Prefix shall point to an empty prefix with zero length. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aInfraIfIndex The index of the infrastructure interface on which the NAT64 prefix is discovered. + * @param[in] aIp6Prefix A pointer to NAT64 prefix. + * + */ +extern void otPlatInfraIfDiscoverNat64PrefixDone(otInstance * aInstance, + uint32_t aInfraIfIndex, + const otIp6Prefix *aIp6Prefix); + /** * @} * diff --git a/script/test b/script/test index be16f85bd..38612d46c 100755 --- a/script/test +++ b/script/test @@ -325,6 +325,7 @@ do_build_otbr_docker() --build-arg REFERENCE_DEVICE=1 \ --build-arg OT_BACKBONE_CI=1 \ --build-arg NAT64="${NAT64}" \ + --build-arg DNS64="${NAT64}" \ --build-arg REST_API=0 \ --build-arg WEB_GUI=0 \ --build-arg MDNS="${OTBR_MDNS:-mDNSResponder}" \ diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 4d0b6e9d0..c3e21c3a7 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -603,6 +603,25 @@ template <> otError Interpreter::Process(Arg aArgs[]) SuccessOrExit(error = otBorderRoutingGetNat64Prefix(GetInstancePtr(), &nat64Prefix)); OutputIp6PrefixLine(nat64Prefix); } + /** + * @cli br favorednat64prefix + * @code + * br favorednat64prefix + * fd14:1078:b3d5:b0b0:0:0::/96 prf:low + * Done + * @endcode + * @par api_copy + * #otBorderRoutingGetFavoredNat64Prefix + */ + else if (aArgs[0] == "favorednat64prefix") + { + otIp6Prefix prefix; + otRoutePreference preference; + + SuccessOrExit(error = otBorderRoutingGetFavoredNat64Prefix(GetInstancePtr(), &prefix, &preference)); + OutputIp6Prefix(prefix); + OutputLine(" prf:%s", PreferenceToString(preference)); + } #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE /** * @cli br rioprf (high,med,low) diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 0ad020356..4f4886709 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -336,6 +336,7 @@ openthread_core_files = [ "api/logging_api.cpp", "api/message_api.cpp", "api/multi_radio_api.cpp", + "api/nat64_api.cpp", "api/netdata_api.cpp", "api/netdata_publisher_api.cpp", "api/netdiag_api.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ce3668964..23caa6389 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -63,6 +63,7 @@ set(COMMON_SOURCES api/logging_api.cpp api/message_api.cpp api/multi_radio_api.cpp + api/nat64_api.cpp api/netdata_api.cpp api/netdata_publisher_api.cpp api/netdiag_api.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 289858c64..b2dd5281f 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -153,6 +153,7 @@ SOURCES_COMMON = \ api/logging_api.cpp \ api/message_api.cpp \ api/multi_radio_api.cpp \ + api/nat64_api.cpp \ api/netdata_api.cpp \ api/netdata_publisher_api.cpp \ api/netdiag_api.cpp \ diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index 1d942c5b8..bfc92515d 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -92,6 +92,21 @@ otError otBorderRoutingGetNat64Prefix(otInstance *aInstance, otIp6Prefix *aPrefi { return AsCoreType(aInstance).Get().GetNat64Prefix(AsCoreType(aPrefix)); } + +otError otBorderRoutingGetFavoredNat64Prefix(otInstance * aInstance, + otIp6Prefix * aPrefix, + otRoutePreference *aPreference) +{ + otError error; + BorderRouter::RoutingManager::RoutePreference preference; + + SuccessOrExit(error = AsCoreType(aInstance).Get().GetFavoredNat64Prefix( + AsCoreType(aPrefix), preference)); + *aPreference = static_cast(preference); + +exit: + return error; +} #endif void otBorderRoutingPrefixTableInitIterator(otInstance *aInstance, otBorderRoutingPrefixTableIterator *aIterator) diff --git a/src/core/api/ip6_api.cpp b/src/core/api/ip6_api.cpp index b12b35ec5..2b54e8ddf 100644 --- a/src/core/api/ip6_api.cpp +++ b/src/core/api/ip6_api.cpp @@ -208,6 +208,11 @@ uint8_t otIp6PrefixMatch(const otIp6Address *aFirst, const otIp6Address *aSecond return AsCoreType(aFirst).PrefixMatch(AsCoreType(aSecond)); } +void otIp6GetPrefix(const otIp6Address *aAddress, uint8_t aLength, otIp6Prefix *aPrefix) +{ + AsCoreType(aAddress).GetPrefix(aLength, AsCoreType(aPrefix)); +} + bool otIp6IsAddressUnspecified(const otIp6Address *aAddress) { return AsCoreType(aAddress).IsUnspecified(); diff --git a/src/core/api/nat64_api.cpp b/src/core/api/nat64_api.cpp new file mode 100644 index 000000000..23e90e991 --- /dev/null +++ b/src/core/api/nat64_api.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the OpenThread APIs for handling IPv4 (NAT64) messages + */ + +#include + +#include "net/ip4_types.hpp" + +using namespace ot; + +bool otIp4IsAddressEqual(const otIp4Address *aFirst, const otIp4Address *aSecond) +{ + return AsCoreType(aFirst) == AsCoreType(aSecond); +} + +void otIp4ExtractFromIp6Address(uint8_t aPrefixLength, const otIp6Address *aIp6Address, otIp4Address *aIp4Address) +{ + AsCoreType(aIp4Address).ExtractFromIp6Address(aPrefixLength, AsCoreType(aIp6Address)); +} diff --git a/src/core/border_router/infra_if.cpp b/src/core/border_router/infra_if.cpp index 7fe20c942..93b77d1c4 100644 --- a/src/core/border_router/infra_if.cpp +++ b/src/core/border_router/infra_if.cpp @@ -111,6 +111,33 @@ exit: } } +Error InfraIf::DiscoverNat64Prefix(void) +{ + OT_ASSERT(mInitialized); + + return otPlatInfraIfDiscoverNat64Prefix(mIfIndex); +} + +void InfraIf::DiscoverNat64PrefixDone(uint32_t aIfIndex, const Ip6::Prefix &aPrefix) +{ + Error error = kErrorNone; + + OT_UNUSED_VARIABLE(aPrefix); + + VerifyOrExit(mInitialized && mIsRunning, error = kErrorInvalidState); + VerifyOrExit(aIfIndex == mIfIndex, error = kErrorInvalidArgs); + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + Get().UpdateInfraIfNat64Prefix(aPrefix); +#endif + +exit: + if (error != kErrorNone) + { + LogDebg("Failed to handle discovered NAT64 synthetic addresses: %s", ErrorToString(error)); + } +} + Error InfraIf::HandleStateChanged(uint32_t aIfIndex, bool aIsRunning) { Error error = kErrorNone; @@ -156,6 +183,13 @@ extern "C" otError otPlatInfraIfStateChanged(otInstance *aInstance, uint32_t aIn return AsCoreType(aInstance).Get().HandleStateChanged(aInfraIfIndex, aIsRunning); } +extern "C" void otPlatInfraIfDiscoverNat64PrefixDone(otInstance * aInstance, + uint32_t aInfraIfIndex, + const otIp6Prefix *aIp6Prefix) +{ + AsCoreType(aInstance).Get().DiscoverNat64PrefixDone(aInfraIfIndex, AsCoreType(aIp6Prefix)); +} + } // namespace BorderRouter } // namespace ot diff --git a/src/core/border_router/infra_if.hpp b/src/core/border_router/infra_if.hpp index b67009e81..6f059928a 100644 --- a/src/core/border_router/infra_if.hpp +++ b/src/core/border_router/infra_if.hpp @@ -151,6 +151,26 @@ public: */ void HandledReceived(uint32_t aIfIndex, const Ip6::Address &aSource, const Icmp6Packet &aPacket); + /** + * This method sends a request to discover the NAT64 prefix on the infrastructure interface. + * + * @note This method MUST be used when interface is initialized. + * + * @retval kErrorNone Successfully request NAT64 prefix discovery. + * @retval kErrorFailed Failed to request NAT64 prefix discovery. + * + */ + Error DiscoverNat64Prefix(void); + + /** + * This method processes the discovered NAT64 prefix. + * + * @param[in] aIfIndex The infrastructure interface index on which the host address is received. + * @param[in] aPrefix The NAT64 prefix on the infrastructure link. + * + */ + void DiscoverNat64PrefixDone(uint32_t aIfIndex, const Ip6::Prefix &aPrefix); + /** * This method handles infrastructure interface state changes. * diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 6026e1d89..48a7808c3 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -67,8 +67,10 @@ RoutingManager::RoutingManager(Instance &aInstance) , mLocalOmrPrefix(aInstance) , mRouteInfoOptionPreference(NetworkData::kRoutePreferenceMedium) , mLocalOnLinkPrefix(aInstance) - , mIsAdvertisingLocalNat64Prefix(false) , mDiscoveredPrefixTable(aInstance) +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + , mInfraIfNat64PrefixStaleTimer(aInstance, HandleInfraIfNat64PrefixStaleTimer) +#endif , mTimeRouterAdvMessageLastUpdate(TimerMilli::GetNow()) , mLearntRouterAdvMessageFromHost(false) , mDiscoveredPrefixStaleTimer(aInstance, HandleDiscoveredPrefixStaleTimer) @@ -81,7 +83,11 @@ RoutingManager::RoutingManager(Instance &aInstance) mFavoredDiscoveredOnLinkPrefix.Clear(); mBrUlaPrefix.Clear(); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + mInfraIfNat64Prefix.Clear(); mLocalNat64Prefix.Clear(); + mAdvertisedNat64Prefix.Clear(); +#endif } Error RoutingManager::Init(uint32_t aInfraIfIndex, bool aInfraIfIsRunning) @@ -178,6 +184,19 @@ Error RoutingManager::GetNat64Prefix(Ip6::Prefix &aPrefix) VerifyOrExit(IsInitialized(), error = kErrorInvalidState); aPrefix = mLocalNat64Prefix; +exit: + return error; +} + +Error RoutingManager::GetFavoredNat64Prefix(Ip6::Prefix &aPrefix, RoutePreference &aRoutePreference) +{ + Error error = kErrorNone; + + VerifyOrExit(IsInitialized(), error = kErrorInvalidState); + aPrefix = mInfraIfNat64Prefix.IsValidNat64() ? mInfraIfNat64Prefix : mLocalNat64Prefix; + aRoutePreference = + mInfraIfNat64Prefix.IsValidNat64() ? NetworkData::kRoutePreferenceMedium : NetworkData::kRoutePreferenceLow; + exit: return error; } @@ -217,6 +236,35 @@ exit: } #if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE +void RoutingManager::DiscoverInfraIfNat64Prefix(void) +{ + Error error = kErrorNone; + + VerifyOrExit(IsInitialized() && mInfraIf.IsRunning(), error = kErrorInvalidState); + + LogInfo("Discovering infrastructure NAT64 prefix on %s", mInfraIf.ToString().AsCString()); + error = mInfraIf.DiscoverNat64Prefix(); + +exit: + if (error != kErrorNone) + { + LogWarn("Failed to request infrastructure NAT64 prefix on %s: %s", mInfraIf.ToString().AsCString(), + ErrorToString(error)); + } +} + +void RoutingManager::UpdateInfraIfNat64Prefix(const Ip6::Prefix &aPrefix) +{ + mInfraIfNat64Prefix = aPrefix; + LogInfo("Get infrastructure NAT64 prefix: %s", + mInfraIfNat64Prefix.IsValidNat64() ? mInfraIfNat64Prefix.ToString().AsCString() : "none"); + + if (mIsRunning) + { + StartRoutingPolicyEvaluationJitter(kRoutingPolicyEvaluationJitter); + } +} + void RoutingManager::GenerateNat64Prefix(void) { mLocalNat64Prefix = mBrUlaPrefix; @@ -224,7 +272,7 @@ void RoutingManager::GenerateNat64Prefix(void) mLocalNat64Prefix.mPrefix.mFields.m32[2] = 0; mLocalNat64Prefix.SetLength(kNat64PrefixLength); - LogInfo("Generated NAT64 prefix: %s", mLocalNat64Prefix.ToString().AsCString()); + LogInfo("Generated local NAT64 prefix: %s", mLocalNat64Prefix.ToString().AsCString()); } #endif @@ -250,6 +298,9 @@ void RoutingManager::Start(void) UpdateDiscoveredPrefixTableOnNetDataChange(); mLocalOnLinkPrefix.Start(); StartRouterSolicitationDelay(); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + mInfraIfNat64PrefixStaleTimer.Start(0); +#endif } } @@ -265,11 +316,13 @@ void RoutingManager::Stop(void) mLocalOnLinkPrefix.Stop(); #if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE - if (mIsAdvertisingLocalNat64Prefix) + if (mAdvertisedNat64Prefix.IsValidNat64()) { - UnpublishExternalRoute(mLocalNat64Prefix); - mIsAdvertisingLocalNat64Prefix = false; + UnpublishExternalRoute(mAdvertisedNat64Prefix); } + mAdvertisedNat64Prefix.Clear(); + mInfraIfNat64Prefix.Clear(); + mInfraIfNat64PrefixStaleTimer.Stop(); #endif SendRouterAdvertisement(kInvalidateAllPrevPrefixes); @@ -517,49 +570,38 @@ exit: #if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE void RoutingManager::EvaluateNat64Prefix(void) { - OT_ASSERT(mIsRunning); + Ip6::Prefix nat64Prefix; + RoutePreference routePreference; + Error error; + NetworkData::ExternalRouteConfig preferredNat64PrefixConfig; + bool shouldAdvertise; - NetworkData::Iterator iterator = NetworkData::kIteratorInit; - NetworkData::ExternalRouteConfig config; - Ip6::Prefix smallestNat64Prefix; + OT_ASSERT(mIsRunning); LogInfo("Evaluating NAT64 prefix"); - smallestNat64Prefix.Clear(); - while (Get().GetNextExternalRoute(iterator, config) == kErrorNone) - { - const Ip6::Prefix &prefix = config.GetPrefix(); + SuccessOrAssert(GetFavoredNat64Prefix(nat64Prefix, routePreference)); + error = Get().GetPreferredNat64Prefix(preferredNat64PrefixConfig); - if (config.mNat64 && prefix.IsValidNat64()) - { - if (smallestNat64Prefix.GetLength() == 0 || prefix < smallestNat64Prefix) - { - smallestNat64Prefix = prefix; - } - } + // NAT64 prefix is expected to be advertised from this BR when one of the following is true: + // - no NAT64 prefix exits in Network Data yet + // - the preferred NAT64 prefix in Network Data has lower preference than this BR's prefix + // - the preferred NAT64 prefix in Network Data was advertised by this BR + // - the preferred NAT64 prefix in Network Data is same as the infrastructure prefix + // TODO: change to check RLOC16 to determine if the NAT64 prefix was advertised by this BR + shouldAdvertise = (error == kErrorNotFound || preferredNat64PrefixConfig.mPreference < routePreference || + preferredNat64PrefixConfig.GetPrefix() == mAdvertisedNat64Prefix || + preferredNat64PrefixConfig.GetPrefix() == mInfraIfNat64Prefix); + + if (mAdvertisedNat64Prefix.IsValidNat64() && (!shouldAdvertise || nat64Prefix != mAdvertisedNat64Prefix)) + { + UnpublishExternalRoute(mAdvertisedNat64Prefix); + mAdvertisedNat64Prefix.Clear(); } - - if (smallestNat64Prefix.GetLength() == 0 || smallestNat64Prefix == mLocalNat64Prefix) + if (shouldAdvertise && nat64Prefix != mAdvertisedNat64Prefix && + PublishExternalRoute(nat64Prefix, routePreference, /* aNat64= */ true) == kErrorNone) { - LogInfo("No NAT64 prefix in Network Data is smaller than the local NAT64 prefix %s", - mLocalNat64Prefix.ToString().AsCString()); - - // Advertise local NAT64 prefix. - if (!mIsAdvertisingLocalNat64Prefix && - PublishExternalRoute(mLocalNat64Prefix, NetworkData::kRoutePreferenceLow, /* aNat64= */ true) == kErrorNone) - { - mIsAdvertisingLocalNat64Prefix = true; - } - } - else if (mIsAdvertisingLocalNat64Prefix && smallestNat64Prefix < mLocalNat64Prefix) - { - // Withdraw local NAT64 prefix if it's not the smallest one in Network Data. - // TODO: remove the prefix with lower preference after discovering upstream NAT64 prefix is supported - LogNote("Withdrawing local NAT64 prefix since a smaller one %s exists.", - smallestNat64Prefix.ToString().AsCString()); - - UnpublishExternalRoute(mLocalNat64Prefix); - mIsAdvertisingLocalNat64Prefix = false; + mAdvertisedNat64Prefix = nat64Prefix; } } #endif @@ -985,6 +1027,21 @@ void RoutingManager::HandleRoutingPolicyTimer(Timer &aTimer) aTimer.Get().EvaluateRoutingPolicy(); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE +void RoutingManager::HandleInfraIfNat64PrefixStaleTimer(Timer &aTimer) +{ + aTimer.Get().HandleInfraIfNat64PrefixStaleTimer(); +} + +void RoutingManager::HandleInfraIfNat64PrefixStaleTimer(void) +{ + DiscoverInfraIfNat64Prefix(); + + mInfraIfNat64PrefixStaleTimer.Start(TimeMilli::SecToMsec(kDefaultNat64PrefixLifetime)); + LogInfo("NAT64 prefix timer scheduled in %u seconds", kDefaultNat64PrefixLifetime); +} +#endif + void RoutingManager::HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) { OT_UNUSED_VARIABLE(aPacket); diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index d31cbf5a5..83b382ab7 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -47,6 +47,7 @@ #error "OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE is required for OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE." #endif +#include #include #include "border_router/infra_if.hpp" @@ -172,7 +173,7 @@ public: Error GetFavoredOmrPrefix(Ip6::Prefix &aPrefix, RoutePreference &aPreference); /** - * This method returns the on-link prefix for the adjacent infrastructure link. + * This method returns the on-link prefix for the adjacent infrastructure link. * * The randomly generated 64-bit prefix will be advertised * on the infrastructure link if there isn't already a usable @@ -190,9 +191,6 @@ public: /** * This method returns the local NAT64 prefix. * - * The local NAT64 prefix will be published in the Thread network - * if none exists. - * * @param[out] aPrefix A reference to where the prefix will be output to. * * @retval kErrorInvalidState The Border Routing Manager is not initialized yet. @@ -200,6 +198,28 @@ public: * */ Error GetNat64Prefix(Ip6::Prefix &aPrefix); + + /** + * This method returns the currently favored NAT64 prefix. + * + * The favored NAT64 prefix can be discovered from infrastructure link or can be the local NAT64 prefix. + * + * @param[out] aPrefix A reference to output the favored prefix. + * @param[out] aPreference A reference to output the preference associated with the favored prefix. + * + * @retval kErrorInvalidState The Border Routing Manager is not initialized yet. + * @retval kErrorNone Successfully retrieved the NAT64 prefix. + * + */ + Error GetFavoredNat64Prefix(Ip6::Prefix &aPrefix, RoutePreference &aRoutePreference); + + /** + * This method updates mInfraIfNat64Prefix to @p aPrefix. + * + * @param[in] aPrefix A NAT64 prefix on infrastructure link. + * + */ + void UpdateInfraIfNat64Prefix(const Ip6::Prefix &aPrefix); #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE /** @@ -291,6 +311,7 @@ private: static constexpr uint32_t kDefaultOmrPrefixLifetime = 1800; // The default OMR prefix valid lifetime. In sec. static constexpr uint32_t kDefaultOnLinkPrefixLifetime = 1800; // The default on-link prefix valid lifetime. In sec. + static constexpr uint32_t kDefaultNat64PrefixLifetime = 300; // The default NAT64 prefix valid lifetime. In sec. static constexpr uint32_t kMaxRtrAdvInterval = 600; // Max Router Advertisement Interval. In sec. static constexpr uint32_t kMinRtrAdvInterval = kMaxRtrAdvInterval / 3; // Min RA Interval. In sec. static constexpr uint32_t kMaxInitRtrAdvInterval = 16; // Max Initial RA Interval. In sec. @@ -585,6 +606,7 @@ private: void EvaluateOnLinkPrefix(void); #if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + void DiscoverInfraIfNat64Prefix(void); void GenerateNat64Prefix(void); void EvaluateNat64Prefix(void); #endif @@ -607,6 +629,10 @@ private: static void HandleDiscoveredPrefixStaleTimer(Timer &aTimer); void HandleDiscoveredPrefixStaleTimer(void); static void HandleRoutingPolicyTimer(Timer &aTimer); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + static void HandleInfraIfNat64PrefixStaleTimer(Timer &aTimer); + void HandleInfraIfNat64PrefixStaleTimer(void); +#endif void DeprecateOnLinkPrefix(void); void HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress); @@ -652,13 +678,21 @@ private: LocalOnLinkPrefix mLocalOnLinkPrefix; + DiscoveredPrefixTable mDiscoveredPrefixTable; + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE + // The latest NAT64 prefix discovered on the infrastructure interface. + Ip6::Prefix mInfraIfNat64Prefix; // The NAT64 prefix allocated from the /48 BR ULA prefix. Ip6::Prefix mLocalNat64Prefix; + // The NAT64 prefix advertised in Network Data. It can have the following value: + // - empty: no NAT64 prefix is advertised from this BR + // - the local NAT64 prefix + // - the latest advertised infrastructure NAT64 prefix, which might differs from mInfraIfNat64Prefix + Ip6::Prefix mAdvertisedNat64Prefix; - // True if the local NAT64 prefix is advertised in Thread network. - bool mIsAdvertisingLocalNat64Prefix; - - DiscoveredPrefixTable mDiscoveredPrefixTable; + TimerMilli mInfraIfNat64PrefixStaleTimer; +#endif // The RA header and parameters for the infra interface. // This value is initialized with `RouterAdvMessage::SetToDefault` diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 0c7b262ee..46a5325c1 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -803,6 +803,15 @@ public: return static_cast(mFields.mComponents.mNetworkPrefix); } + /** + * This method gets a prefix of the IPv6 address with a given length. + * + * @param[in] aLength The length of prefix in bits. + * @param[out] aPrefix A reference to a prefix to output the fetched prefix. + * + */ + void GetPrefix(uint8_t aLength, Prefix &aPrefix) const { aPrefix.Set(mFields.m8, aLength); } + /** * This method indicates whether the IPv6 address matches a given prefix. * diff --git a/src/core/thread/network_data_leader.cpp b/src/core/thread/network_data_leader.cpp index 19e57450c..4c1dee72b 100644 --- a/src/core/thread/network_data_leader.cpp +++ b/src/core/thread/network_data_leader.cpp @@ -102,7 +102,8 @@ Error LeaderBase::GetPreferredNat64Prefix(ExternalRouteConfig &aConfig) const continue; } - if ((error == kErrorNotFound) || (config.mPreference > aConfig.mPreference)) + if ((error == kErrorNotFound) || (config.mPreference > aConfig.mPreference) || + (config.mPreference == aConfig.mPreference && config.GetPrefix() < aConfig.GetPrefix())) { aConfig = config; error = kErrorNone; diff --git a/src/posix/Makefile.am b/src/posix/Makefile.am index 3844ea1f3..c0a076660 100644 --- a/src/posix/Makefile.am +++ b/src/posix/Makefile.am @@ -62,6 +62,7 @@ LDADD_COMMON = \ if OPENTHREAD_TARGET_LINUX LDADD_COMMON += \ + -lanl \ -lrt \ $(NULL) endif diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 701eb2183..5b1a6fc84 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -118,6 +118,7 @@ target_link_libraries(openthread-posix ot-posix-config util $<$:rt> + $<$:anl> ) target_compile_definitions(openthread-posix diff --git a/src/posix/platform/infra_if.cpp b/src/posix/platform/infra_if.cpp index 11f1562c6..1898bf024 100644 --- a/src/posix/platform/infra_if.cpp +++ b/src/posix/platform/infra_if.cpp @@ -41,10 +41,12 @@ #include #include +#include // clang-format off #include #include // clang-format on +#include #include #include #include @@ -60,6 +62,8 @@ #include "lib/platform/exit_code.h" #include "posix/platform/infra_if.hpp" +uint32_t ot::Posix::InfraNetif::mInfraIfIndex = 0; + bool otPlatInfraIfHasAddress(uint32_t aInfraIfIndex, const otIp6Address *aAddress) { bool ret = false; @@ -96,6 +100,11 @@ otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, return ot::Posix::InfraNetif::Get().SendIcmp6Nd(aInfraIfIndex, *aDestAddress, aBuffer, aBufferLength); } +otError otPlatInfraIfDiscoverNat64Prefix(uint32_t aInfraIfIndex) +{ + return ot::Posix::InfraNetif::Get().DiscoverNat64Prefix(aInfraIfIndex); +} + bool platformInfraIfIsRunning(void) { return ot::Posix::InfraNetif::Get().IsRunning(); @@ -515,6 +524,132 @@ exit: } } +const char InfraNetif::kWellKnownIpv4OnlyName[] = "ipv4only.arpa"; +const otIp4Address InfraNetif::kWellKnownIpv4OnlyAddress1 = {{{192, 0, 0, 170}}}; +const otIp4Address InfraNetif::kWellKnownIpv4OnlyAddress2 = {{{192, 0, 0, 171}}}; +const uint8_t InfraNetif::kValidNat64PrefixLength[] = {96, 64, 56, 48, 40, 32}; + +void InfraNetif::DiscoverNat64PrefixDone(union sigval sv) +{ + struct gaicb * req = (struct gaicb *)sv.sival_ptr; + struct addrinfo *res = (struct addrinfo *)req->ar_result; + + otIp6Prefix prefix = {}; + + VerifyOrExit((char *)req->ar_name == kWellKnownIpv4OnlyName); + + otLogInfoPlat("Handling host address response for %s", kWellKnownIpv4OnlyName); + + // We extract the first valid NAT64 prefix from the address look-up response. + for (struct addrinfo *rp = res; rp != NULL && prefix.mLength == 0; rp = rp->ai_next) + { + struct sockaddr_in6 *ip6Addr; + otIp6Address ip6Address; + + if (rp->ai_family != AF_INET6) + { + continue; + } + + ip6Addr = reinterpret_cast(rp->ai_addr); + memcpy(&ip6Address.mFields.m8, &ip6Addr->sin6_addr.s6_addr, OT_IP6_ADDRESS_SIZE); + for (uint8_t length : kValidNat64PrefixLength) + { + otIp4Address ip4Address; + + otIp4ExtractFromIp6Address(length, &ip6Address, &ip4Address); + if (otIp4IsAddressEqual(&ip4Address, &kWellKnownIpv4OnlyAddress1) || + otIp4IsAddressEqual(&ip4Address, &kWellKnownIpv4OnlyAddress2)) + { + // We check that the well-known IPv4 address is present only once in the IPv6 address. + // In case another instance of the value is found for another prefix length, we ignore this address + // and search for the other well-known IPv4 address (per RFC 7050 section 3). + bool foundDuplicate = false; + + for (uint8_t dupLength : kValidNat64PrefixLength) + { + otIp4Address dupIp4Address; + + if (dupLength == length) + { + continue; + } + + otIp4ExtractFromIp6Address(dupLength, &ip6Address, &dupIp4Address); + if (otIp4IsAddressEqual(&dupIp4Address, &ip4Address)) + { + foundDuplicate = true; + break; + } + } + + if (!foundDuplicate) + { + otIp6GetPrefix(&ip6Address, length, &prefix); + break; + } + } + + if (prefix.mLength != 0) + { + break; + } + } + } + + otPlatInfraIfDiscoverNat64PrefixDone(gInstance, mInfraIfIndex, &prefix); + +exit: + freeaddrinfo(res); + freeaddrinfo((struct addrinfo *)req->ar_request); + free(req); +} + +otError InfraNetif::DiscoverNat64Prefix(uint32_t aInfraIfIndex) +{ + otError error = OT_ERROR_NONE; + struct addrinfo *hints; + struct gaicb * reqs[1]; + struct sigevent sig; + int status; + + VerifyOrExit(aInfraIfIndex == mInfraIfIndex, error = OT_ERROR_DROP); + + hints = (struct addrinfo *)malloc(sizeof(struct addrinfo)); + VerifyOrExit(hints != nullptr, error = OT_ERROR_NO_BUFS); + memset(hints, 0, sizeof(struct addrinfo)); + hints->ai_family = AF_INET6; + hints->ai_socktype = SOCK_STREAM; + + reqs[0] = (struct gaicb *)malloc(sizeof(struct gaicb)); + VerifyOrExit(reqs[0] != nullptr, error = OT_ERROR_NO_BUFS); + memset(reqs[0], 0, sizeof(struct gaicb)); + reqs[0]->ar_name = kWellKnownIpv4OnlyName; + reqs[0]->ar_request = hints; + + memset(&sig, 0, sizeof(struct sigevent)); + sig.sigev_notify = SIGEV_THREAD; + sig.sigev_value.sival_ptr = reqs[0]; + sig.sigev_notify_function = &InfraNetif::DiscoverNat64PrefixDone; + + status = getaddrinfo_a(GAI_NOWAIT, reqs, 1, &sig); + + if (status != 0) + { + otLogNotePlat("getaddrinfo_a failed: %s", gai_strerror(status)); + ExitNow(error = OT_ERROR_FAILED); + } + otLogInfoPlat("getaddrinfo_a requested for %s", kWellKnownIpv4OnlyName); + +exit: + if (error != OT_ERROR_NONE) + { + freeaddrinfo(hints); + free(reqs[0]); + } + return error; +} + void InfraNetif::Process(const otSysMainloopContext &aContext) { VerifyOrExit(mInfraIfIcmp6Socket != -1); diff --git a/src/posix/platform/infra_if.hpp b/src/posix/platform/infra_if.hpp index ccd2b1206..2bd727a64 100644 --- a/src/posix/platform/infra_if.hpp +++ b/src/posix/platform/infra_if.hpp @@ -34,6 +34,7 @@ #include "openthread-posix-config.h" #include +#include #include "core/common/non_copyable.hpp" #include "posix/platform/mainloop.hpp" @@ -129,6 +130,18 @@ public: const uint8_t * aBuffer, uint16_t aBufferLength); + /** + * This method sends an asynchronous address lookup for the well-known host name "ipv4only.arpa" + * to discover the NAT64 prefix. + * + * @param[in] aInfraIfIndex The index of the infrastructure interface the address look-up is sent to. + * + * @retval OT_ERROR_NONE Successfully request address look-up. + * @retval OT_ERROR_FAILED Failed to request address look-up. + * + */ + otError DiscoverNat64Prefix(uint32_t aInfraIfIndex); + /** * This method gets the infrastructure network interface name. * @@ -146,14 +159,20 @@ public: static InfraNetif &Get(void); private: - char mInfraIfName[IFNAMSIZ]; - uint32_t mInfraIfIndex = 0; - int mInfraIfIcmp6Socket = -1; - int mNetLinkSocket = -1; + static const char kWellKnownIpv4OnlyName[]; // "ipv4only.arpa" + static const otIp4Address kWellKnownIpv4OnlyAddress1; // 192.0.0.170 + static const otIp4Address kWellKnownIpv4OnlyAddress2; // 192.0.0.171 + static const uint8_t kValidNat64PrefixLength[]; - void ReceiveNetLinkMessage(void); - void ReceiveIcmp6Message(void); - bool HasLinkLocalAddress(void) const; + char mInfraIfName[IFNAMSIZ]; + static uint32_t mInfraIfIndex; + int mInfraIfIcmp6Socket = -1; + int mNetLinkSocket = -1; + + void ReceiveNetLinkMessage(void); + void ReceiveIcmp6Message(void); + bool HasLinkLocalAddress(void) const; + static void DiscoverNat64PrefixDone(union sigval sv); }; } // namespace Posix diff --git a/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py b/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py index 8ce9e0de2..6a4d5c846 100644 --- a/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py +++ b/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py @@ -51,6 +51,8 @@ ROUTER = 2 BR2 = 3 HOST = 4 +NAT64_PREFIX_REFRESH_DELAY = 305 + class Nat64MultiBorderRouter(thread_cert.TestCase): USE_MESSAGE_FACTORY = False @@ -90,6 +92,8 @@ class Nat64MultiBorderRouter(thread_cert.TestCase): br1.start() self.simulator.go(config.LEADER_STARTUP_DELAY) + br1.bash("service bind9 stop") + self.simulator.go(NAT64_PREFIX_REFRESH_DELAY) self.assertEqual('leader', br1.get_state()) router.start() @@ -97,44 +101,65 @@ class Nat64MultiBorderRouter(thread_cert.TestCase): self.assertEqual('router', router.get_state()) # - # Case 1. BR2 joins the network later and it will not add - # its local nat64 prefix to Network Data. + # Case 1. BR2 with an infrastructure prefix joins the network later and + # it will add the infrastructure nat64 prefix to Network Data. # br2.start() self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) self.assertEqual('router', br2.get_state()) - # Only 1 NAT64 prefix in Network Data. - self.simulator.go(30) - self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1) - self.assertEqual(len(br2.get_netdata_nat64_prefix()), 1) - self.assertEqual(br1.get_netdata_nat64_prefix()[0], br2.get_netdata_nat64_prefix()[0]) - nat64_prefix = br1.get_netdata_nat64_prefix()[0] + self.simulator.go(10) + self.assertNotEqual(br1.get_br_favored_nat64_prefix(), br2.get_br_favored_nat64_prefix()) + br1_local_nat64_prefix = br1.get_br_nat64_prefix() + br2_infra_nat64_prefix = br2.get_br_favored_nat64_prefix() - # The NAT64 prefix in Network Data is same as BR1's local NAT64 prefix. - br1_nat64_prefix = br1.get_br_nat64_prefix() - br2_nat64_prefix = br2.get_br_nat64_prefix() - self.assertEqual(nat64_prefix, br1_nat64_prefix) - self.assertNotEqual(nat64_prefix, br2_nat64_prefix) + self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1) + nat64_prefix = br1.get_netdata_nat64_prefix()[0] + self.assertEqual(nat64_prefix, br2_infra_nat64_prefix) + self.assertNotEqual(nat64_prefix, br1_local_nat64_prefix) + + br2.disable_br() # - # Case 2. Disable and re-enable border routing on BR1. + # Case 2. Re-enables BR2 with a local prefix and it will not add + # its local nat64 prefix to Network Data. + # + br2.bash("service bind9 stop") + self.simulator.go(5) + br2.enable_br() + + self.simulator.go(10) + self.assertNotEqual(br2_infra_nat64_prefix, br2.get_br_favored_nat64_prefix()) + br2_local_nat64_prefix = br2.get_br_nat64_prefix() + + self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1) + nat64_prefix = br1.get_netdata_nat64_prefix()[0] + self.assertEqual(nat64_prefix, br1_local_nat64_prefix) + self.assertNotEqual(nat64_prefix, br2_local_nat64_prefix) + + # + # Case 3. Disable border routing on BR1. + # BR1 withdraws its prefix and BR2 advertises its prefix. # br1.disable_br() - self.simulator.go(30) - # BR1 withdraws its prefix and BR2 advertises its prefix. + self.simulator.go(10) self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1) - self.assertEqual(br2_nat64_prefix, br1.get_netdata_nat64_prefix()[0]) - self.assertNotEqual(br1_nat64_prefix, br1.get_netdata_nat64_prefix()[0]) + nat64_prefix = br1.get_netdata_nat64_prefix()[0] + self.assertEqual(br2_local_nat64_prefix, nat64_prefix) + self.assertNotEqual(br1_local_nat64_prefix, nat64_prefix) + # + # Case 4. Re-enable border routing on BR1. + # NAT64 prefix in Network Data is still advertised by BR2. + # br1.enable_br() - self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) - # NAT64 prefix in Network Data is still advertised by BR2. + self.simulator.go(10) self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1) - self.assertEqual(br2_nat64_prefix, br1.get_netdata_nat64_prefix()[0]) - self.assertNotEqual(br1_nat64_prefix, br1.get_netdata_nat64_prefix()[0]) + nat64_prefix = br1.get_netdata_nat64_prefix()[0] + self.assertEqual(br2_local_nat64_prefix, nat64_prefix) + self.assertNotEqual(br1_local_nat64_prefix, nat64_prefix) if __name__ == '__main__': diff --git a/tests/scripts/thread-cert/border_router/nat64/test_single_border_router.py b/tests/scripts/thread-cert/border_router/nat64/test_single_border_router.py index a301cdaea..c485b14e9 100644 --- a/tests/scripts/thread-cert/border_router/nat64/test_single_border_router.py +++ b/tests/scripts/thread-cert/border_router/nat64/test_single_border_router.py @@ -32,7 +32,8 @@ import config import thread_cert # Test description: -# This test verifies the advertisement of NAT64 prefix in Thread network. +# This test verifies the advertisement of local NAT64 prefix in Thread network +# when no NAT64 prefix found on infrastructure interface. # # TODO: add checks for outbound connectivity from Thread device to IPv4 host # after OTBR change is ready. @@ -49,9 +50,12 @@ BR = 1 ROUTER = 2 HOST = 3 -# The prefix is set small enough that a random-generated NAT64 prefix is very -# likely greater than it. So that the BR will remove the random-generated one. +# The prefix is set small enough that a random-generated ULA NAT64 prefix is very +# likely greater than it. So the BR will remove the random-generated one. SMALL_NAT64_PREFIX = "fd00:00:00:01:00:00::/96" +# The prefix is set larger than a random-generated ULA NAT64 prefix. +# So the BR will remove the random-generated one. +LARGE_NAT64_PREFIX = "ff00:00:00:01:00:00::/96" class Nat64SingleBorderRouter(thread_cert.TestCase): @@ -85,6 +89,8 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): br.start() self.simulator.go(config.LEADER_STARTUP_DELAY) + br.bash("service bind9 stop") + self.simulator.go(330) self.assertEqual('leader', br.get_state()) router.start() @@ -103,10 +109,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): # # Case 2. - # User adds a smaller NAT64 prefix and the local prefix is withdrawn. + # User adds a smaller NAT64 prefix (same preference) and the local prefix is withdrawn. # User removes the smaller NAT64 prefix and the local prefix is re-added. # - br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True) + br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True, prf='low') br.register_netdata() self.simulator.go(5) @@ -115,13 +121,32 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): br.remove_route(SMALL_NAT64_PREFIX) br.register_netdata() - self.simulator.go(5) + self.simulator.go(10) self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) self.assertEqual(local_nat64_prefix, br.get_netdata_nat64_prefix()[0]) # - # Case 3. Disable and re-enable border routing on the border router. + # Case 3. + # User adds a larger NAT64 prefix (higher preference) and the local prefix is withdrawn. + # User removes the larger NAT64 prefix and the local prefix is re-added. + # + br.add_route(LARGE_NAT64_PREFIX, stable=False, nat64=True, prf='med') + br.register_netdata() + self.simulator.go(5) + + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertNotEqual(local_nat64_prefix, br.get_netdata_nat64_prefix()[0]) + + br.remove_route(LARGE_NAT64_PREFIX) + br.register_netdata() + self.simulator.go(10) + + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertEqual(local_nat64_prefix, br.get_netdata_nat64_prefix()[0]) + + # + # Case 4. Disable and re-enable border routing on the border router. # br.disable_br() self.simulator.go(5) @@ -137,7 +162,7 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertEqual(nat64_prefix, br.get_netdata_nat64_prefix()[0]) # - # Case 4. Disable and re-enable ethernet on the border router. + # Case 5. Disable and re-enable ethernet on the border router. # br.disable_ether() self.simulator.go(5) diff --git a/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py b/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py new file mode 100644 index 000000000..1eee2954f --- /dev/null +++ b/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +import unittest + +import config +import thread_cert + +# Test description: +# This test verifies the advertisement of infrastructure NAT64 prefix in Thread network. +# +# +# Topology: +# +# ----------------(eth)-------------------- +# | +# BR (with DNS64 on infrastructure interface) +# | +# ROUTER +# + +BR = 1 +ROUTER = 2 + +# The prefix is set smaller than the default infrastructure NAT64 prefix. +SMALL_NAT64_PREFIX = "2000:0:0:1:0:0::/96" + +NAT64_PREFIX_REFRESH_DELAY = 305 + + +class Nat64SingleBorderRouter(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR: { + 'name': 'BR', + 'allowlist': [ROUTER], + 'is_otbr': True, + 'version': '1.2', + }, + ROUTER: { + 'name': 'Router', + 'allowlist': [BR], + 'version': '1.2', + }, + } + + def test(self): + br = self.nodes[BR] + router = self.nodes[ROUTER] + + br.start() + self.simulator.go(config.LEADER_STARTUP_DELAY) + self.assertEqual('leader', br.get_state()) + + router.start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual('router', router.get_state()) + + # Case 1 BR advertise the infrastructure prefix + infra_nat64_prefix = br.get_br_favored_nat64_prefix() + + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + nat64_prefix = br.get_netdata_nat64_prefix()[0] + self.assertEqual(nat64_prefix, infra_nat64_prefix) + + # Case 2 Withdraw infrastructure prefix when a smaller prefix in medium + # preference is present + br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True, prf='med') + br.register_netdata() + self.simulator.go(5) + + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertNotEqual(infra_nat64_prefix, br.get_netdata_nat64_prefix()[0]) + + br.remove_route(SMALL_NAT64_PREFIX) + br.register_netdata() + self.simulator.go(10) + + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertEqual(nat64_prefix, infra_nat64_prefix) + + # Case 3 No change when a smaller prefix in low preference is present + br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True, prf='low') + br.register_netdata() + self.simulator.go(5) + + self.assertEqual(len(br.get_netdata_nat64_prefix()), 2) + self.assertEqual(br.get_netdata_nat64_prefix(), [infra_nat64_prefix, SMALL_NAT64_PREFIX]) + + br.remove_route(SMALL_NAT64_PREFIX) + br.register_netdata() + self.simulator.go(5) + + # Case 4 Infrastructure nat64 prefix no longer presents + br.bash("service bind9 stop") + self.simulator.go(NAT64_PREFIX_REFRESH_DELAY) + + local_nat64_prefix = br.get_br_nat64_prefix() + self.assertNotEqual(local_nat64_prefix, infra_nat64_prefix) + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertEqual(br.get_netdata_nat64_prefix()[0], local_nat64_prefix) + + # Case 5 Infrastructure nat64 prefix is recovered + br.bash("service bind9 start") + self.simulator.go(NAT64_PREFIX_REFRESH_DELAY) + + self.assertEqual(br.get_br_favored_nat64_prefix(), infra_nat64_prefix) + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertEqual(br.get_netdata_nat64_prefix()[0], infra_nat64_prefix) + + # Case 6 Change infrastructure nat64 prefix + br.bash("sed -i 's/dns64 /\/\/dns64 /' /etc/bind/named.conf.options") + br.bash("sed -i '/\/\/dns64 /a dns64 " + SMALL_NAT64_PREFIX + " {};' /etc/bind/named.conf.options") + br.bash("service bind9 restart") + self.simulator.go(NAT64_PREFIX_REFRESH_DELAY) + + self.assertEqual(br.get_br_favored_nat64_prefix(), SMALL_NAT64_PREFIX) + self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) + self.assertEqual(br.get_netdata_nat64_prefix()[0], SMALL_NAT64_PREFIX) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 496713fa1..ddc960304 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -51,6 +51,8 @@ import thread_cert PORT_OFFSET = int(os.getenv('PORT_OFFSET', "0")) +INFRA_DNS64 = int(os.getenv('NAT64', 0)) + class OtbrDocker: RESET_DELAY = 3 @@ -107,13 +109,17 @@ class OtbrDocker: logging.info(f'Docker image: {config.OTBR_DOCKER_IMAGE}') subprocess.check_call(f"docker rm -f {self._docker_name} || true", shell=True) CI_ENV = os.getenv('CI_ENV', '').split() + dns = ['--dns=127.0.0.1'] if INFRA_DNS64 == 1 else [] + nat64_prefix = ['--nat64-prefix', '2001:db8:1:ffff::/96'] if INFRA_DNS64 == 1 else [] os.makedirs('/tmp/coverage/', exist_ok=True) - self._docker_proc = subprocess.Popen(['docker', 'run'] + CI_ENV + [ + + cmd = ['docker', 'run'] + CI_ENV + [ '--rm', '--name', self._docker_name, '--network', config.BACKBONE_DOCKER_NETWORK_NAME, + ] + dns + [ '-i', '--sysctl', 'net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1', @@ -128,10 +134,9 @@ class OtbrDocker: config.BACKBONE_IFNAME, '--trel-url', f'trel://{config.BACKBONE_IFNAME}', - ], - stdin=subprocess.DEVNULL, - stdout=sys.stdout, - stderr=sys.stderr) + ] + nat64_prefix + logging.info(' '.join(cmd)) + self._docker_proc = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=sys.stdout, stderr=sys.stderr) launch_docker_deadline = time.time() + 300 launch_ok = False @@ -1989,6 +1994,11 @@ class NodeImpl: self.send_command(cmd) return self._expect_command_output()[0] + def get_br_favored_nat64_prefix(self): + cmd = 'br favorednat64prefix' + self.send_command(cmd) + return self._expect_command_output()[0].split(' ')[0] + def get_netdata_nat64_prefix(self): prefixes = [] routes = self.get_routes() diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 9d01cd3cf..5f26338dd 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -492,6 +492,11 @@ OT_TOOL_WEAK otError otPlatInfraIfSendIcmp6Nd(uint32_t, const otIp6Address *, co { return OT_ERROR_FAILED; } + +OT_TOOL_WEAK otError otPlatInfraIfDiscoverNat64Prefix(uint32_t) +{ + return OT_ERROR_FAILED; +} #endif #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE