From 80565c58a95bf74e69c8237e8b7e4ee9b0f739f2 Mon Sep 17 00:00:00 2001 From: Yi Date: Fri, 12 Aug 2022 22:37:05 +0800 Subject: [PATCH] [nat64] fetch NAT64 prefix from infrastructure interface and advertise it to netdata (#7619) This commit fetches the NAT64 prefix on infrastructure interface and advertise it to Network Data at medium preference. - Use `getaddrinfo_a()` function to asynchronously lookup the ipv6 address of the special domain `ipv4only.arpa`. The infrastructure NAT64 prefix is extracted from the domain answer. - `mInfraIfNat64PrefixStaleTimer` is scheduled to monitor the presence and change of infrastructure NAT64 prefix. - `EvaluateNat64Prefix` evaluates whether to advertise the infrastructure prefix or the local ULA prefix or neither. When there is a new infrastructure prefix, it will withdraw the legacy one and add the new one. When the infrastructure prefix no longer exists, it will withdraw the legacy one and add the local ULA prefix. When the infrastructure prefix presents again, it will add the infrastructure prefix and withdraw the local ULA prefix. New tests are added to test the scenarios when infrastructure NAT64 prefix exists. `DNS64` on OTBR is turned on to enable `bind9` with NAT64 prefix on infrastructure interface for these tests. `bind9` is explicitly turned off when testing local ULA prefix. Since bind9 is conflict with other components like dnssd, all nat64 tests are moved under /nat64 directory and configured separately. The case that two or more BRs have same infrastructure NAT64 prefix is not covered by this commit and will be followed up later. --- Android.mk | 2 + examples/platforms/simulation/infra_if.c | 7 + include/openthread/border_routing.h | 17 ++ include/openthread/instance.h | 2 +- include/openthread/ip6.h | 10 ++ include/openthread/nat64.h | 27 ++++ include/openthread/platform/infra_if.h | 29 ++++ script/test | 1 + src/cli/cli.cpp | 19 +++ src/core/BUILD.gn | 1 + src/core/CMakeLists.txt | 1 + src/core/Makefile.am | 1 + src/core/api/border_routing_api.cpp | 15 ++ src/core/api/ip6_api.cpp | 5 + src/core/api/nat64_api.cpp | 48 ++++++ src/core/border_router/infra_if.cpp | 34 ++++ src/core/border_router/infra_if.hpp | 20 +++ src/core/border_router/routing_manager.cpp | 139 +++++++++++----- src/core/border_router/routing_manager.hpp | 50 +++++- src/core/net/ip6_address.hpp | 9 ++ src/core/thread/network_data_leader.cpp | 3 +- src/posix/Makefile.am | 1 + src/posix/platform/CMakeLists.txt | 1 + src/posix/platform/infra_if.cpp | 135 ++++++++++++++++ src/posix/platform/infra_if.hpp | 33 +++- .../nat64/test_multi_border_routers.py | 69 +++++--- .../nat64/test_single_border_router.py | 41 ++++- .../nat64/test_with_infrastructure_prefix.py | 149 ++++++++++++++++++ tests/scripts/thread-cert/node.py | 20 ++- tests/unit/test_platform.cpp | 5 + 30 files changed, 801 insertions(+), 93 deletions(-) create mode 100644 src/core/api/nat64_api.cpp create mode 100644 tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py 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