From e84f05c641d9fe2b460c62613c452271ffb50f31 Mon Sep 17 00:00:00 2001 From: Song GUO Date: Fri, 19 Aug 2022 13:28:26 +0800 Subject: [PATCH] [nat64] implement nat64 translator (ot::Nat64::Translator) (#7836) This commit: - implements the core logic for translating packets for NAT64, including the public APIs exposed to platform daemons. - includes changes for POSIX platform, use `OT_POSIX_NAT64_CIDR`, `OPENTHREAD_POSIX_CONFIG_NAT64_CIDR` for setting the CIDR for NAT64 during build time. - exposes `otNat64Send(otInstance *aInstance, otMessage *aMessage)` and `void otNat64SetReceiveIp4Callback(otInstance *aInstance, otNat64ReceiveIp4Callback aCallback, void *aContext)`. --- Android.mk | 1 + etc/cmake/options.cmake | 1 + examples/README.md | 1 + examples/common-switches.mk | 5 + include/openthread/instance.h | 2 +- include/openthread/nat64.h | 86 ++++ script/check-scan-build | 1 + script/check-simulation-build-autotools | 2 + script/check-simulation-build-cmake | 2 + script/make-pretty | 1 + src/core/BUILD.gn | 2 + src/core/CMakeLists.txt | 1 + src/core/Makefile.am | 2 + src/core/api/ip6_api.cpp | 2 + src/core/api/nat64_api.cpp | 31 ++ src/core/border_router/routing_manager.cpp | 5 + src/core/border_router/routing_manager.hpp | 1 + src/core/common/instance.cpp | 3 + src/core/common/instance.hpp | 12 + src/core/common/message.hpp | 4 +- src/core/config/border_router.h | 30 ++ src/core/net/ip6.cpp | 28 ++ src/core/net/ip6.hpp | 20 + src/core/net/nat64_translator.cpp | 466 +++++++++++++++++++++ src/core/net/nat64_translator.hpp | 216 ++++++++++ src/posix/platform/CMakeLists.txt | 7 + src/posix/platform/netif.cpp | 77 +++- src/posix/platform/platform-posix.h | 6 + src/posix/platform/system.cpp | 10 + tests/unit/CMakeLists.txt | 21 + tests/unit/Makefile.am | 5 + tests/unit/test_nat64.cpp | 314 ++++++++++++++ 32 files changed, 1343 insertions(+), 22 deletions(-) create mode 100644 src/core/net/nat64_translator.cpp create mode 100644 src/core/net/nat64_translator.hpp create mode 100644 tests/unit/test_nat64.cpp diff --git a/Android.mk b/Android.mk index 02ec53819..c5b10491a 100644 --- a/Android.mk +++ b/Android.mk @@ -306,6 +306,7 @@ LOCAL_SRC_FILES := \ src/core/net/ip6_filter.cpp \ src/core/net/ip6_headers.cpp \ src/core/net/ip6_mpl.cpp \ + src/core/net/nat64_translator.cpp \ src/core/net/nd6.cpp \ src/core/net/nd_agent.cpp \ src/core/net/netif.cpp \ diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 4c51cc32f..4ee054e85 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -115,6 +115,7 @@ ot_option(OT_MLE_LONG_ROUTES OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE "MLE long ot_option(OT_MLR OPENTHREAD_CONFIG_MLR_ENABLE "Multicast Listener Registration (MLR)") ot_option(OT_MTD_NETDIAG OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE "TMF network diagnostics on MTDs") ot_option(OT_MULTIPLE_INSTANCE OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE "multiple instances") +ot_option(OT_NAT64_TRANSLATOR OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE "NAT64 translator support") ot_option(OT_NEIGHBOR_DISCOVERY_AGENT OPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT_ENABLE "neighbor discovery agent") ot_option(OT_NETDATA_PUBLISHER OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE "Network Data publisher") ot_option(OT_OTNS OPENTHREAD_CONFIG_OTNS_ENABLE "OTNS") diff --git a/examples/README.md b/examples/README.md index 2d0f5d5ef..17464989c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -13,6 +13,7 @@ This page lists the available common switches with description. Unless stated ot | BORDER_ROUTER | OT_BORDER_ROUTER | Enables support for Border Router. This switch is usually combined with the BORDER_AGENT and UDP_FORWARD (or PLATFORM_UDP in case of RCP design) switches to build Border Router device. | | BORDER_ROUTING | OT_BORDER_ROUTING | Enables bi-directional border routing between Thread and Infrastructure networks for Border Router. | | BORDER_ROUTING_NAT64 | OT_BORDER_ROUTING_NAT64 | Enables NAT64 border routing support for Border Router. | +| NAT64_TRANSLATOR | OT_NAT64_TRANSLATOR | Enables NAT64 translator for Border Router. | | BUILTIN_MBEDTLS_MANAGEMENT | OT_BUILTIN_MBEDTLS_MANAGEMENT | Enables the built-in mbedTLS management. Enable this switch if the external mbedTLS is used, but mbedTLS memory allocation and debug config should be managed internally by OpenThread. | | CHANNEL_MANAGER | OT_CHANNEL_MANAGER | Enables support for channel manager. Enable this switch on devices that are supposed to request a Thread network channel change. This switch should be used only with an FTD build. | | CHANNEL_MONITOR | OT_CHANNEL_MONITOR | Enables support for channel monitor. Enable this switch on devices that are supposed to determine the cleaner channels. | diff --git a/examples/common-switches.mk b/examples/common-switches.mk index ac05ccd6c..4fd85e8e4 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -73,6 +73,7 @@ MLE_LONG_ROUTES ?= 0 MLR ?= 0 MTD_NETDIAG ?= 0 MULTIPLE_INSTANCE ?= 0 +NAT64_TRANSLATOR ?= 0 NEIGHBOR_DISCOVERY_AGENT ?= 0 NETDATA_PUBLISHER ?= 0 OTNS ?= 0 @@ -121,6 +122,10 @@ ifeq ($(BORDER_ROUTING_NAT64),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE=1 endif +ifeq ($(NAT64_TRANSLATOR),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE=1 +endif + ifeq ($(COAP),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_COAP_API_ENABLE=1 endif diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 10f890b84..bdd09f312 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 (235) +#define OPENTHREAD_API_VERSION (236) /** * @addtogroup api-instance diff --git a/include/openthread/nat64.h b/include/openthread/nat64.h index 2d22a5cf2..967287862 100644 --- a/include/openthread/nat64.h +++ b/include/openthread/nat64.h @@ -88,6 +88,92 @@ typedef struct otIp4Cidr uint8_t mLength; } otIp4Cidr; +/** + * Allocate a new message buffer for sending an IPv4 message to the NAT64 translator. + * + * Message buffers allocated by this function will have 20 bytes (difference between the size of IPv6 headers + * and IPv4 header sizes) reserved. + * + * This function is available only when `OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE` is enabled. + * + * @note If @p aSettings is `NULL`, the link layer security is enabled and the message priority is set to + * OT_MESSAGE_PRIORITY_NORMAL by default. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aSettings A pointer to the message settings or NULL to set default settings. + * + * @returns A pointer to the message buffer or NULL if no message buffers are available or parameters are invalid. + * + * @sa otNat64Send + * + */ +otMessage *otIp4NewMessage(otInstance *aInstance, const otMessageSettings *aSettings); + +/** + * Sets the CIDR used when setting the source address of the outgoing translated IPv4 packets. + * + * This function is available only when OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE is enabled. + * + * @note A valid CIDR must have a non-zero prefix length. The actual addresses pool is limited by the size of the + * mapping pool and the number of addresses available in the CIDR block. + * + * @note This function can be called at any time, but the NAT64 translator will be reset and all existing sessions will + * be expired when updating the configured CIDR. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCidr A pointer to an otIp4Cidr for the IPv4 CIDR block for NAT64. + * + * @retval OT_ERROR_INVALID_ARGS The given CIDR is not a valid IPv4 CIDR for NAT64. + * @retval OT_ERROR_NONE Successfully set the CIDR for NAT64. + * + * @sa otBorderRouterSend + * @sa otBorderRouterSetReceiveCallback + * + */ +otError otNat64SetIp4Cidr(otInstance *aInstance, const otIp4Cidr *aCidr); + +/** + * Translates an IPv4 datagram to an IPv6 datagram and sends via the Thread interface. + * + * The caller transfers ownership of @p aMessage when making this call. OpenThread will free @p aMessage when + * processing is complete, including when a value other than `OT_ERROR_NONE` is returned. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the message buffer containing the IPv4 datagram. + * + * @retval OT_ERROR_NONE Successfully processed the message. + * @retval OT_ERROR_DROP Message was well-formed but not fully processed due to packet processing + * rules. + * @retval OT_ERROR_NO_BUFS Could not allocate necessary message buffers when processing the datagram. + * @retval OT_ERROR_NO_ROUTE No route to host. + * @retval OT_ERROR_INVALID_SOURCE_ADDRESS Source address is invalid, e.g. an anycast address or a multicast address. + * @retval OT_ERROR_PARSE Encountered a malformed header when processing the message. + * + */ +otError otNat64Send(otInstance *aInstance, otMessage *aMessage); + +/** + * This function pointer is called when an IPv4 datagram (translated by NAT64 translator) is received. + * + * @param[in] aMessage A pointer to the message buffer containing the received IPv6 datagram. This function transfers + * the ownership of the @p aMessage to the receiver of the callback. The message should be + * freed by the receiver of the callback after it is processed. + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otNat64ReceiveIp4Callback)(otMessage *aMessage, void *aContext); + +/** + * Registers a callback to provide received IPv4 datagrams. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback A pointer to a function that is called when an IPv4 datagram is received or + * NULL to disable the callback. + * @param[in] aCallbackContext A pointer to application-specific context. + * + */ +void otNat64SetReceiveIp4Callback(otInstance *aInstance, otNat64ReceiveIp4Callback aCallback, void *aContext); + /** * Test if two IPv4 addresses are the same. * diff --git a/script/check-scan-build b/script/check-scan-build index 06329d9ca..98db59294 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -38,6 +38,7 @@ readonly OT_BUILD_OPTIONS=( "-DOT_BORDER_ROUTER=ON" "-DOT_BORDER_ROUTING=ON" "-DOT_BORDER_ROUTING_NAT64=ON" + "-DOT_NAT64_TRANSLATOR=ON" "-DOT_COAP=ON" "-DOT_COAP_BLOCK=ON" "-DOT_COAP_OBSERVE=ON" diff --git a/script/check-simulation-build-autotools b/script/check-simulation-build-autotools index aa6f6316b..cfa19ad7e 100755 --- a/script/check-simulation-build-autotools +++ b/script/check-simulation-build-autotools @@ -95,6 +95,8 @@ build_all_features() local options_1_3=( "-DOPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE=1" + "-DOPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE=1" + "-DOPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE=1" "-DOPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE=1" "-DOPENTHREAD_CONFIG_DUA_ENABLE=1" "-DOPENTHREAD_CONFIG_MLR_ENABLE=1" diff --git a/script/check-simulation-build-cmake b/script/check-simulation-build-cmake index f296ab1ec..4dff1b378 100755 --- a/script/check-simulation-build-cmake +++ b/script/check-simulation-build-cmake @@ -57,6 +57,8 @@ build_all_features() local options=( "-DOT_BACKBONE_ROUTER=ON" "-DOT_BORDER_ROUTING=ON" + "-DOT_BORDER_ROUTING_NAT64=ON" + "-DOT_NAT64_TRANSLATOR=ON" "-DOT_CSL_RECEIVER=ON" "-DOT_MLR=ON" "-DOT_OTNS=ON" diff --git a/script/make-pretty b/script/make-pretty index 22201c3f1..48ddf75a6 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -84,6 +84,7 @@ readonly OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_BORDER_ROUTER=ON' '-DOT_BORDER_ROUTING=ON' '-DOT_BORDER_ROUTING_NAT64=ON' + '-DOT_NAT64_TRANSLATOR=ON' '-DOT_CHANNEL_MANAGER=ON' '-DOT_CHANNEL_MONITOR=ON' '-DOT_CHILD_SUPERVISION=ON' diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index b1711be43..e2fb12a42 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -565,6 +565,8 @@ openthread_core_files = [ "net/ip6_mpl.cpp", "net/ip6_mpl.hpp", "net/ip6_types.hpp", + "net/nat64_translator.cpp", + "net/nat64_translator.hpp", "net/nd6.cpp", "net/nd6.hpp", "net/nd_agent.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 23caa6389..e602de41e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -172,6 +172,7 @@ set(COMMON_SOURCES net/ip6_filter.cpp net/ip6_headers.cpp net/ip6_mpl.cpp + net/nat64_translator.cpp net/nd6.cpp net/nd_agent.cpp net/netif.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 44a729c1b..72de6d75a 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -262,6 +262,7 @@ SOURCES_COMMON = \ net/ip6_filter.cpp \ net/ip6_headers.cpp \ net/ip6_mpl.cpp \ + net/nat64_translator.cpp \ net/nd6.cpp \ net/nd_agent.cpp \ net/netif.cpp \ @@ -572,6 +573,7 @@ HEADERS_COMMON = \ net/ip6_headers.hpp \ net/ip6_mpl.hpp \ net/ip6_types.hpp \ + net/nat64_translator.hpp \ net/nd6.hpp \ net/nd_agent.hpp \ net/netif.hpp \ diff --git a/src/core/api/ip6_api.cpp b/src/core/api/ip6_api.cpp index ebdc85098..ad9a68bcb 100644 --- a/src/core/api/ip6_api.cpp +++ b/src/core/api/ip6_api.cpp @@ -37,6 +37,8 @@ #include "common/as_core_type.hpp" #include "common/locator_getters.hpp" +#include "net/ip4_types.hpp" +#include "net/ip6_headers.hpp" #include "utils/slaac_address.hpp" using namespace ot; diff --git a/src/core/api/nat64_api.cpp b/src/core/api/nat64_api.cpp index 23e90e991..61d66f2cb 100644 --- a/src/core/api/nat64_api.cpp +++ b/src/core/api/nat64_api.cpp @@ -31,12 +31,43 @@ * This file implements the OpenThread APIs for handling IPv4 (NAT64) messages */ +#include "openthread-core-config.h" + +#include +#include #include +#include "border_router/routing_manager.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" #include "net/ip4_types.hpp" +#include "net/ip6_headers.hpp" +#include "net/nat64_translator.hpp" using namespace ot; +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE +otError otNat64SetIp4Cidr(otInstance *aInstance, const otIp4Cidr *aCidr) +{ + return AsCoreType(aInstance).Get().SetIp4Cidr(AsCoreType(aCidr)); +} + +otMessage *otIp4NewMessage(otInstance *aInstance, const otMessageSettings *aSettings) +{ + return AsCoreType(aInstance).Get().NewIp4Message(Message::Settings::From(aSettings)); +} + +otError otNat64Send(otInstance *aInstance, otMessage *aMessage) +{ + return AsCoreType(aInstance).Get().SendMessage(AsCoreType(aMessage)); +} + +void otNat64SetReceiveIp4Callback(otInstance *aInstance, otNat64ReceiveIp4Callback aCallback, void *aContext) +{ + AsCoreType(aInstance).Get().SetNat64ReceiveIp4DatagramCallback(aCallback, aContext); +} +#endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + bool otIp4IsAddressEqual(const otIp4Address *aFirst, const otIp4Address *aSecond) { return AsCoreType(aFirst) == AsCoreType(aSecond); diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index f2165942e..4fb1e3f02 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -38,6 +38,7 @@ #include +#include #include #include "common/code_utils.hpp" @@ -50,6 +51,7 @@ #include "common/settings.hpp" #include "meshcop/extended_panid.hpp" #include "net/ip6.hpp" +#include "net/nat64_translator.hpp" #include "thread/network_data_leader.hpp" #include "thread/network_data_local.hpp" #include "thread/network_data_notifier.hpp" @@ -600,6 +602,9 @@ void RoutingManager::EvaluateNat64Prefix(void) { mAdvertisedNat64Prefix = nat64Prefix; } +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + Get().SetNat64Prefix(mAdvertisedNat64Prefix); +#endif } #endif diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 31afc47fc..61bb0550e 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -60,6 +60,7 @@ #include "common/string.hpp" #include "common/timer.hpp" #include "net/ip6.hpp" +#include "net/nat64_translator.hpp" #include "net/nd6.hpp" #include "thread/network_data.hpp" diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index 6aa107590..3f2c615eb 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -223,6 +223,9 @@ Instance::Instance(void) #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE , mRoutingManager(*this) #endif +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + , mNat64Translator(*this) +#endif #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE , mLinkRaw(*this) diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index dd3e4e6d4..8dabb6054 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -90,6 +90,7 @@ #include "net/dnssd_server.hpp" #include "net/ip6.hpp" #include "net/ip6_filter.hpp" +#include "net/nat64_translator.hpp" #include "net/nd_agent.hpp" #include "net/netif.hpp" #include "net/sntp_client.hpp" @@ -596,6 +597,10 @@ private: BorderRouter::RoutingManager mRoutingManager; #endif +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + Nat64::Translator mNat64Translator; +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE @@ -1173,6 +1178,13 @@ template <> inline BorderRouter::InfraIf &Instance::Get(void) { return mRoutingManager.mInfraIf; } +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE +template <> inline Nat64::Translator &Instance::Get(void) +{ + return mNat64Translator; +} #endif #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 060171a8f..902852b1a 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -39,6 +39,7 @@ #include #include +#include #include #include "common/as_core_type.hpp" @@ -280,7 +281,8 @@ public: kType6lowpan = 1, ///< A 6lowpan frame kTypeSupervision = 2, ///< A child supervision frame. kTypeMacEmptyData = 3, ///< An empty MAC data frame. - kTypeOther = 4, ///< Other (data) message. + kTypeIp4 = 4, ///< A full uncompressed IPv4 packet, for NAT64. + kTypeOther = 5, ///< Other (data) message. }; /** diff --git a/src/core/config/border_router.h b/src/core/config/border_router.h index 01ddfe9aa..feda2595d 100644 --- a/src/core/config/border_router.h +++ b/src/core/config/border_router.h @@ -71,4 +71,34 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + * + * Define to 1 to enable the internal NAT64 translator. + * + */ +#ifndef OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE +#define OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS + * + * Specifies maximum number of active mappings for NAT64. + * + */ +#ifndef OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS +#define OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS 254 +#endif + +/** + * @def OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS + * + * Specifies timeout in seconds before removing an inactive address mapping. + * + */ +#ifndef OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS +#define OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS 7200 +#endif + #endif // CONFIG_BORDER_ROUTER_H_ diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index e5d80d75c..bc7535571 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -47,6 +47,7 @@ #include "net/icmp6.hpp" #include "net/ip6_address.hpp" #include "net/ip6_filter.hpp" +#include "net/nat64_translator.hpp" #include "net/netif.hpp" #include "net/udp6.hpp" #include "openthread/ip6.h" @@ -70,6 +71,10 @@ Ip6::Ip6(Instance &aInstance) , mIsReceiveIp6FilterEnabled(false) , mReceiveIp6DatagramCallback(nullptr) , mReceiveIp6DatagramCallbackContext(nullptr) +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + , mReceiveIp4DatagramCallback(nullptr) + , mReceiveIp4DatagramCallbackContext(nullptr) +#endif , mSendQueueTask(aInstance, Ip6::HandleSendQueue) , mIcmp(aInstance) , mUdp(aInstance) @@ -192,6 +197,14 @@ void Ip6::SetReceiveDatagramCallback(otIp6ReceiveCallback aCallback, void *aCall mReceiveIp6DatagramCallbackContext = aCallbackContext; } +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE +void Ip6::SetNat64ReceiveIp4DatagramCallback(otNat64ReceiveIp4Callback aCallback, void *aCallbackContext) +{ + mReceiveIp4DatagramCallback = aCallback; + mReceiveIp4DatagramCallbackContext = aCallbackContext; +} +#endif + Error Ip6::AddMplOption(Message &aMessage, Header &aHeader) { Error error = kErrorNone; @@ -1081,6 +1094,21 @@ Error Ip6::ProcessReceiveCallback(Message & aMessage, } IgnoreError(RemoveMplOption(*message)); + +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + switch (Get().TranslateFromIp6(aMessage)) + { + case Nat64::Translator::kNotTranslated: + break; + case Nat64::Translator::kDrop: + ExitNow(error = kErrorDrop); + case Nat64::Translator::kForward: + VerifyOrExit(mReceiveIp4DatagramCallback != nullptr, error = kErrorNoRoute); + mReceiveIp4DatagramCallback(message, mReceiveIp4DatagramCallbackContext); + ExitNow(); + } +#endif + mReceiveIp6DatagramCallback(message, mReceiveIp6DatagramCallbackContext); exit: diff --git a/src/core/net/ip6.hpp b/src/core/net/ip6.hpp index 13fdcc86a..60c631201 100644 --- a/src/core/net/ip6.hpp +++ b/src/core/net/ip6.hpp @@ -39,6 +39,7 @@ #include #include +#include #include #include "common/encoding.hpp" @@ -231,6 +232,20 @@ public: */ void SetReceiveDatagramCallback(otIp6ReceiveCallback aCallback, void *aCallbackContext); +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + /** + * This method registers a callback to provide received translated IPv4 datagrams. + * + * @param[in] aCallback A pointer to a function that is called when a translated IPv4 datagram is received + * or `nullptr` to disable the callback. + * @param[in] aCallbackContext A pointer to application-specific context. + * + * @sa SetReceiveDatagramCallback + * + */ + void SetNat64ReceiveIp4DatagramCallback(otNat64ReceiveIp4Callback aCallback, void *aCallbackContext); +#endif + /** * This method indicates whether or not Thread control traffic is filtered out when delivering IPv6 datagrams * via the callback specified in SetReceiveIp6DatagramCallback(). @@ -361,6 +376,11 @@ private: otIp6ReceiveCallback mReceiveIp6DatagramCallback; void * mReceiveIp6DatagramCallbackContext; +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + otNat64ReceiveIp4Callback mReceiveIp4DatagramCallback; + void * mReceiveIp4DatagramCallbackContext; +#endif + PriorityQueue mSendQueue; Tasklet mSendQueueTask; diff --git a/src/core/net/nat64_translator.cpp b/src/core/net/nat64_translator.cpp new file mode 100644 index 000000000..c7c86248d --- /dev/null +++ b/src/core/net/nat64_translator.cpp @@ -0,0 +1,466 @@ +/* + * 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 includes implementation for the NAT64 translator. + * + */ + +#include "nat64_translator.hpp" + +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + +#include "common/code_utils.hpp" +#include "common/locator_getters.hpp" +#include "common/log.hpp" +#include "net/checksum.hpp" +#include "net/ip4_types.hpp" +#include "net/ip6.hpp" + +namespace ot { +namespace Nat64 { + +RegisterLogModule("Nat64"); + +Translator::Translator(Instance &aInstance) + : InstanceLocator(aInstance) + , mMappingExpirer(aInstance, MappingExpirerHandler) +{ + mNat64Prefix.Clear(); + mIp4Cidr.Clear(); + mMappingExpirer.Start(kAddressMappingIdleTimeoutMsec); +} + +Message *Translator::NewIp4Message(const Message::Settings &aSettings) +{ + Message *message = Get().NewMessage(sizeof(Ip6::Header) - sizeof(Ip4::Header), aSettings); + + if (message != nullptr) + { + message->SetType(Message::kTypeIp4); + } + + return message; +} + +Error Translator::SendMessage(Message &aMessage) +{ + bool freed = false; + Error error = kErrorDrop; + Result result = TranslateToIp6(aMessage); + + VerifyOrExit(result == kForward); + + error = Get().SendRaw(aMessage, !OPENTHREAD_CONFIG_IP6_ALLOW_LOOP_BACK_HOST_DATAGRAMS); + freed = true; + +exit: + if (!freed) + { + aMessage.Free(); + } + + return error; +} + +Translator::Result Translator::TranslateFromIp6(Message &aMessage) +{ + Result res = kDrop; + Ip6::Header ip6Header; + Ip4::Header ip4Header; + AddressMapping *mapping = nullptr; + + if (mIp4Cidr.mLength == 0 || !mNat64Prefix.IsValidNat64()) + { + ExitNow(res = kNotTranslated); + } + + // ParseFrom will do basic checks for the message, including the message length and IP protocol version. + if (ip6Header.ParseFrom(aMessage) != kErrorNone) + { + LogWarn("outgoing datagram is not a valid IPv6 datagram, drop"); + ExitNow(res = kDrop); + } + + if (!ip6Header.GetDestination().MatchesPrefix(mNat64Prefix)) + { + ExitNow(res = kNotTranslated); + } + + mapping = FindOrAllocateMapping(ip6Header.GetSource()); + if (mapping == nullptr) + { + LogWarn("failed to get a mapping for %s (mapping pool full?)", ip6Header.GetSource().ToString().AsCString()); + ExitNow(res = kDrop); + } + + aMessage.RemoveHeader(sizeof(Ip6::Header)); + + ip4Header.Clear(); + ip4Header.InitVersionIhl(); + ip4Header.SetSource(mapping->mIp4); + ip4Header.GetDestination().ExtractFromIp6Address(mNat64Prefix.mLength, ip6Header.GetDestination()); + ip4Header.SetTtl(ip6Header.GetHopLimit()); + ip4Header.SetIdentification(0); + + switch (ip6Header.GetNextHeader()) + { + case Ip6::kProtoUdp: + ip4Header.SetProtocol(Ip4::kProtoUdp); + res = kForward; + break; + case Ip6::kProtoTcp: + ip4Header.SetProtocol(Ip4::kProtoTcp); + res = kForward; + break; + case Ip6::kProtoIcmp6: + ip4Header.SetProtocol(Ip4::kProtoIcmp); + SuccessOrExit(TranslateIcmp6(aMessage)); + res = kForward; + break; + default: + ExitNow(res = kDrop); + } + + // res here must be kForward based on the switch above. + // TODO: Implement the logic for replying ICMP messages. + ip4Header.SetTotalLength(sizeof(Ip4::Header) + aMessage.GetLength() - aMessage.GetOffset()); + Checksum::UpdateMessageChecksum(aMessage, ip4Header.GetSource(), ip4Header.GetDestination(), + ip4Header.GetProtocol()); + Checksum::UpdateIp4HeaderChecksum(ip4Header); + if (aMessage.Prepend(ip4Header) != kErrorNone) + { + // This should never happen since the IPv4 header is shorter than the IPv6 header. + LogCrit("failed to prepend IPv4 head to translated message"); + ExitNow(res = kDrop); + } + aMessage.SetType(Message::kTypeIp4); + +exit: + return res; +} + +Translator::Result Translator::TranslateToIp6(Message &aMessage) +{ + Result res = Result::kDrop; + Ip6::Header ip6Header; + Ip4::Header ip4Header; + AddressMapping *mapping = nullptr; + + // Ip6::Header::ParseFrom may return an error value when the incoming message is an IPv4 datagram. + // If the message is already an IPv6 datagram, forward it directly. + VerifyOrExit(ip6Header.ParseFrom(aMessage) != kErrorNone, res = kNotTranslated); + + if (mIp4Cidr.mLength == 0) + { + // The NAT64 translation is bypassed (will be handled externally) + LogWarn("incoming message is an IPv4 datagram but no IPv4 CIDR for NAT64 configured, drop"); + ExitNow(res = kForward); + } + + if (!mNat64Prefix.IsValidNat64()) + { + LogWarn("incoming message is an IPv4 datagram but no NAT64 prefix configured, drop"); + ExitNow(res = kDrop); + } + + if (ip4Header.ParseFrom(aMessage) != kErrorNone) + { + LogWarn("incoming message is neither IPv4 nor an IPv6 datagram, drop"); + ExitNow(res = kDrop); + } + + mapping = FindMapping(ip4Header.GetDestination()); + if (mapping == nullptr) + { + LogWarn("no mapping found for the IPv4 address"); + ExitNow(res = kDrop); + } + + aMessage.RemoveHeader(sizeof(Ip4::Header)); + + ip6Header.Clear(); + ip6Header.InitVersionTrafficClassFlow(); + ip6Header.GetSource().SynthesizeFromIp4Address(mNat64Prefix, ip4Header.GetSource()); + ip6Header.SetDestination(mapping->mIp6); + ip6Header.SetFlow(0); + ip6Header.SetHopLimit(ip4Header.GetTtl()); + + // Note: TCP and UDP are the same for both IPv4 and IPv6 except for the checksum calculation, we will update the + // checksum in the payload later. However, we need to translate ICMPv6 messages to ICMP messages in IPv4. + switch (ip4Header.GetProtocol()) + { + case Ip4::kProtoUdp: + ip6Header.SetNextHeader(Ip6::kProtoUdp); + res = kForward; + break; + case Ip4::kProtoTcp: + ip6Header.SetNextHeader(Ip6::kProtoTcp); + res = kForward; + break; + case Ip4::kProtoIcmp: + ip6Header.SetNextHeader(Ip6::kProtoIcmp6); + SuccessOrExit(TranslateIcmp4(aMessage)); + res = kForward; + break; + default: + ExitNow(res = kDrop); + } + + // res here must be kForward based on the switch above. + // TODO: Implement the logic for replying ICMP datagrams. + ip6Header.SetPayloadLength(aMessage.GetLength() - aMessage.GetOffset()); + Checksum::UpdateMessageChecksum(aMessage, ip6Header.GetSource(), ip6Header.GetDestination(), + ip6Header.GetNextHeader()); + if (aMessage.Prepend(ip6Header) != kErrorNone) + { + // This might happen when the platform failed to reserve enough space before the original IPv4 datagram. + LogWarn("failed to prepend IPv6 head to translated message"); + ExitNow(res = kDrop); + } + aMessage.SetType(Message::kTypeIp6); + +exit: + return res; +} + +Translator::AddressMapping::InfoString Translator::AddressMapping::ToString(void) +{ + InfoString string; + + string.Append("%s -> %s", mIp6.ToString().AsCString(), mIp4.ToString().AsCString()); + + return string; +} + +void Translator::ReleaseMapping(AddressMapping &aMapping) +{ + IgnoreError(mIp4AddressPool.PushBack(aMapping.mIp4)); + mAddressMappingPool.Free(aMapping); + LogInfo("mapping removed: %s", aMapping.ToString().AsCString()); +} + +uint16_t Translator::ReleaseExpiredMappings(void) +{ + uint16_t numRemoved = 0; + TimeMilli now = TimerMilli::GetNow(); + LinkedList idleMappings; + + mActiveAddressMappings.RemoveAllMatching(now, idleMappings); + + for (AddressMapping *idleMapping = idleMappings.Pop(); idleMapping != nullptr; idleMapping = idleMappings.Pop()) + { + numRemoved++; + ReleaseMapping(*idleMapping); + } + + return numRemoved; +} + +Translator::AddressMapping *Translator::AllocateMapping(const Ip6::Address &aIp6Addr) +{ + AddressMapping *mapping = nullptr; + + // The address pool will be no larger than the mapping pool, so checking the address pool is enough. + if (mIp4AddressPool.IsEmpty()) + { + // ReleaseExpiredMappings returns the number of mappings removed. + VerifyOrExit(ReleaseExpiredMappings() > 0); + } + + mapping = mAddressMappingPool.Allocate(); + // We should get a valid item since address pool is no larger than the mapping pool, and the address pool is not + // empty. + VerifyOrExit(mapping != nullptr); + + mActiveAddressMappings.Push(*mapping); + mapping->mIp6 = aIp6Addr; + // PopBack must return a valid address since it is not empty. + mapping->mIp4 = *mIp4AddressPool.PopBack(); + mapping->Touch(TimerMilli::GetNow()); + LogInfo("mapping created: %s", mapping->ToString().AsCString()); + +exit: + return mapping; +} + +Translator::AddressMapping *Translator::FindOrAllocateMapping(const Ip6::Address &aIp6Addr) +{ + AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Addr); + + // Exit if we found a valid mapping. + VerifyOrExit(mapping == nullptr); + + mapping = AllocateMapping(aIp6Addr); + +exit: + return mapping; +} + +Translator::AddressMapping *Translator::FindMapping(const Ip4::Address &aIp4Addr) +{ + AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Addr); + + if (mapping != nullptr) + { + mapping->Touch(TimerMilli::GetNow()); + } + return mapping; +} + +Error Translator::TranslateIcmp4(Message &aMessage) +{ + Error err = kErrorNone; + Ip4::Icmp::Header icmp4Header; + Ip6::Icmp::Header icmp6Header; + + // TODO: Implement the translation of other ICMP messages. + + // Note: The caller consumed the IP header, so the ICMP header is at offset 0. + SuccessOrExit(err = aMessage.Read(0, icmp4Header)); + switch (icmp4Header.GetType()) + { + case Ip4::Icmp::Header::Type::kTypeEchoReply: + { + // The only difference between ICMPv6 echo and ICMP4 echo is the message type field, so we can reinteprete it as + // ICMP6 header and set the message type. + SuccessOrExit(err = aMessage.Read(0, icmp6Header)); + icmp6Header.SetType(Ip6::Icmp::Header::Type::kTypeEchoReply); + aMessage.Write(0, icmp6Header); + break; + } + default: + err = kErrorInvalidArgs; + break; + } + +exit: + return err; +} + +Error Translator::TranslateIcmp6(Message &aMessage) +{ + Error err = kErrorNone; + Ip4::Icmp::Header icmp4Header; + Ip6::Icmp::Header icmp6Header; + + // TODO: Implement the translation of other ICMP messages. + + // Note: The caller have consumed the IP header, so the ICMP header is at offset 0. + SuccessOrExit(err = aMessage.Read(0, icmp6Header)); + switch (icmp6Header.GetType()) + { + case Ip6::Icmp::Header::Type::kTypeEchoRequest: + { + // The only difference between ICMPv6 echo and ICMP4 echo is the message type field, so we can reinteprete it as + // ICMP6 header and set the message type. + SuccessOrExit(err = aMessage.Read(0, icmp4Header)); + icmp4Header.SetType(Ip4::Icmp::Header::Type::kTypeEchoRequest); + aMessage.Write(0, icmp4Header); + break; + } + default: + err = kErrorInvalidArgs; + break; + } + +exit: + return err; +} + +Error Translator::SetIp4Cidr(const Ip4::Cidr &aCidr) +{ + Error err = kErrorNone; + + uint32_t numberOfHosts; + uint32_t hostIdBegin; + + VerifyOrExit(aCidr.mLength > 0 && aCidr.mLength <= 32, err = kErrorInvalidArgs); + + VerifyOrExit(mIp4Cidr != aCidr); + + // Avoid using the 0s and 1s in the host id of an address, but what if the user provides us with /32 or /31 + // addresses? + if (aCidr.mLength == 32) + { + hostIdBegin = 0; + numberOfHosts = 1; + } + else if (aCidr.mLength == 31) + { + hostIdBegin = 0; + numberOfHosts = 2; + } + else + { + hostIdBegin = 1; + numberOfHosts = static_cast((1 << (Ip4::Address::kSize * 8 - aCidr.mLength)) - 2); + } + numberOfHosts = OT_MIN(numberOfHosts, kAddressMappingPoolSize); + + mAddressMappingPool.FreeAll(); + mActiveAddressMappings.Clear(); + mIp4AddressPool.Clear(); + + for (uint32_t i = 0; i < numberOfHosts; i++) + { + Ip4::Address addr; + + addr.SynthesizeFromCidrAndHost(aCidr, i + hostIdBegin); + IgnoreError(mIp4AddressPool.PushBack(addr)); + } + + LogInfo("IPv4 CIDR for NAT64: %s (actual address pool: %s - %s, %u addresses)", aCidr.ToString().AsCString(), + mIp4AddressPool.Front()->ToString().AsCString(), mIp4AddressPool.Back()->ToString().AsCString(), + numberOfHosts); + mIp4Cidr = aCidr; + +exit: + return err; +} + +void Translator::SetNat64Prefix(const Ip6::Prefix &aNat64Prefix) +{ + if (mNat64Prefix != aNat64Prefix) + { + LogInfo("IPv6 Prefix for NAT64 updated to %s", aNat64Prefix.ToString().AsCString()); + mNat64Prefix = aNat64Prefix; + } +} + +void Translator::MappingExpirerHandler(Timer &aTimer) +{ + LogInfo("Released %d expired mappings", aTimer.Get().ReleaseExpiredMappings()); + aTimer.Get().mMappingExpirer.Start(kAddressMappingIdleTimeoutMsec); +} + +} // namespace Nat64 +} // namespace ot + +#endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE diff --git a/src/core/net/nat64_translator.hpp b/src/core/net/nat64_translator.hpp new file mode 100644 index 000000000..e10548361 --- /dev/null +++ b/src/core/net/nat64_translator.hpp @@ -0,0 +1,216 @@ +/* + * 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 includes definitions for the NAT64 translator. + * + */ + +#ifndef NAT64_TRANSLATOR_HPP_ +#define NAT64_TRANSLATOR_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + +#include "common/array.hpp" +#include "common/linked_list.hpp" +#include "common/locator.hpp" +#include "common/pool.hpp" +#include "common/timer.hpp" +#include "net/ip4_types.hpp" +#include "net/ip6.hpp" + +namespace ot { +namespace Nat64 { + +/** + * This class implements the NAT64 translator. + * + */ +class Translator : public InstanceLocator, private NonCopyable +{ +public: + static constexpr uint32_t kAddressMappingIdleTimeoutMsec = + OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS * Time::kOneSecondInMsec; + static constexpr uint32_t kAddressMappingPoolSize = OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS; + + /** + * The possible results of NAT64 translation. + * + */ + enum Result : uint8_t + { + kNotTranslated, ///< The message is not translated, it might be sending to an non-nat64 prefix (for outgoing + ///< datagrams), or it is already an IPv6 message (for incoming datagrams). + kForward, ///< Message is successfully translated, the caller should continue forwarding the translated + ///< datagram. + kDrop, ///< The caller should drop the datagram silently. + }; + + /** + * This constructor initializes the NAT64 translator. + * + */ + explicit Translator(Instance &aInstance); + + /** + * This method translates an IPv4 datagram to an IPv6 datagram and sends it via Thread interface. + * + * The caller transfers ownership of @p aMessage when making this call. OpenThread will free @p aMessage when + * processing is complete, including when a value other than `kErrorNone` is returned. + * + * @param[in] aMessage A reference to the message. + * + * @retval kErrorNone Successfully processed the message. + * @retval kErrorDrop Message was well-formed but not fully processed due to datagram processing rules. + * @retval kErrorNoBufs Could not allocate necessary message buffers when processing the datagram. + * @retval kErrorNoRoute No route to host. + * @retval kErrorParse Encountered a malformed header when processing the message. + * + */ + Error SendMessage(Message &aMessage); + + /** + * Allocate a new message buffer for sending an IPv4 message (which will be translated into an IPv6 datagram by + * NAT64 later). Message buffers allocated by this function will have 20 bytes (The differences between the size of + * IPv6 headers and the size of IPv4 headers) reserved. + * + * @param[in] aSettings The message settings. + * + * @returns A pointer to the message buffer or NULL if no message buffers are available or parameters are invalid. + * + */ + Message *NewIp4Message(const Message::Settings &aSettings); + + /** + * Translates an IPv4 datagram to IPv6 datagram. Note the datagram and datagramLength might be adjusted. + * Note the message can have 20 bytes reserved before the message to avoid potential copy operations. If the message + * is already an IPv6 datagram, `Result::kNotTranslated` will be returned and @p aMessage won't be modified. + * + * @param[in,out] aMessage the message to be processed. + * + * @retval kNotTranslated The message is already an IPv6 datagram. @p aMessage is not updated. + * @retval kForward The caller should contiue forwarding the datagram. + * @retval kDrop The caller should drop the datagram silently. + * + */ + Result TranslateToIp6(Message &message); + + /** + * Translates an IPv6 datagram to IPv4 datagram. Note the datagram and datagramLength might be adjusted. + * If the message is not targeted to NAT64-mapped address, `Result::kNotTranslated` will be returned and @p aMessage + * won't be modified. + * + * @param[in,out] aMessage the message to be processed. + * + * @retval kNotTranslated The datagram is not sending to the configured NAT64 prefix. + * @retval kForward The caller should contiue forwarding the datagram. + * @retval kDrop The caller should drop the datagram silently. + * + */ + Result TranslateFromIp6(Message &aMessage); + + /** + * Sets the CIDR used when setting the source address of the outgoing translated IPv4 datagrams. A valid CIDR must + * have a non-zero prefix length. + * + * @note The actual addresses pool is limited by the size of the mapping pool and the number of addresses available + * in the CIDR block. If the provided is a valid IPv4 CIDR for NAT64, and it is different from the one already + * configured, the NAT64 translator will be reset and all existing sessions will be expired. + * + * @param[in] aCidr the CIDR for the sources of the translated datagrams. + * + * @retval kErrorInvalidArgs The the given CIDR a valid CIDR for NAT64. + * @retval kErrorNone Successfully enabled/disabled the NAT64 translator. + * + */ + Error SetIp4Cidr(const Ip4::Cidr &aCidr); + + /** + * Sets the prefix of NAT64-mapped addresses in the thread network. The address mapping table will not be cleared. + * If an empty NAT64 prefix is set, the translator will return kNotTranslated for all IPv6 datagrams and kDrop for + * all IPv4 datagrams. + * + * @param[in] aNat64Prefix The prefix of the NAT64-mapped addresses. + * + */ + void SetNat64Prefix(const Ip6::Prefix &aNat64Prefix); + +private: + class AddressMapping : public LinkedListEntry + { + public: + friend class LinkedListEntry; + friend class LinkedList; + + typedef String InfoString; + + void Touch(TimeMilli aNow) { mExpiry = aNow + kAddressMappingIdleTimeoutMsec; } + InfoString ToString(void); + + Ip4::Address mIp4; + Ip6::Address mIp6; + TimeMilli mExpiry; // The timestamp when this mapping expires, in milliseconds. + + private: + bool Matches(const Ip4::Address &aIp4) const { return mIp4 == aIp4; } + bool Matches(const Ip6::Address &aIp6) const { return mIp6 == aIp6; } + bool Matches(const TimeMilli aNow) const { return mExpiry < aNow; } + + AddressMapping *mNext; + }; + + Error TranslateIcmp4(Message &aMessage); + Error TranslateIcmp6(Message &aMessage); + + void ReleaseMapping(AddressMapping &aMapping); + uint16_t ReleaseExpiredMappings(void); + AddressMapping *AllocateMapping(const Ip6::Address &aIp6Addr); + AddressMapping *FindOrAllocateMapping(const Ip6::Address &aIp6Addr); + AddressMapping *FindMapping(const Ip4::Address &aIp4Addr); + + static void MappingExpirerHandler(Timer &aTimer); + + Array mIp4AddressPool; + Pool mAddressMappingPool; + LinkedList mActiveAddressMappings; + + Ip6::Prefix mNat64Prefix; + Ip4::Cidr mIp4Cidr; + + TimerMilli mMappingExpirer; +}; + +} // namespace Nat64 +} // namespace ot + +#endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + +#endif // NAT64_TRANSLATOR_HPP_ diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 5b1a6fc84..9ae2244fb 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -76,6 +76,13 @@ if(OT_POSIX_CONFIG_RCP_BUS) ) endif() +set(OT_POSIX_NAT64_CIDR "192.168.64.0/24" CACHE STRING "NAT64 CIDR for OpenThread NAT64") +if(OT_POSIX_NAT64_CIDR) + target_compile_definitions(ot-posix-config + INTERFACE "OPENTHREAD_POSIX_CONFIG_NAT64_CIDR=\"${OT_POSIX_NAT64_CIDR}\"" + ) +endif() + if(NOT OT_CONFIG) set(OT_CONFIG "openthread-core-posix-config.h" PARENT_SCOPE) endif() diff --git a/src/posix/platform/netif.cpp b/src/posix/platform/netif.cpp index 2d761ce65..36a63133c 100644 --- a/src/posix/platform/netif.cpp +++ b/src/posix/platform/netif.cpp @@ -143,6 +143,7 @@ extern int #include #include #include +#include #include #include @@ -152,6 +153,7 @@ extern int unsigned int gNetifIndex = 0; char gNetifName[IFNAMSIZ]; +otIp4Cidr gNat64Cidr; const char *otSysGetThreadNetifName(void) { @@ -567,7 +569,7 @@ exit: #if __linux__ && \ (OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE || OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE) -static otError AddRoute(const otIp6Prefix &aPrefix, uint32_t aPriority) +template static otError AddRoute(const uint8_t (&aAddress)[N], uint8_t aPrefixLen, uint32_t aPriority) { constexpr unsigned int kBufSize = 128; struct @@ -576,10 +578,10 @@ static otError AddRoute(const otIp6Prefix &aPrefix, uint32_t aPriority) struct rtmsg msg; char buf[kBufSize]; } req{}; - unsigned char data[sizeof(in6_addr)]; - char addrBuf[OT_IP6_ADDRESS_STRING_SIZE]; - unsigned int netifIdx = otSysGetThreadNetifIndex(); - otError error = OT_ERROR_NONE; + unsigned int netifIdx = otSysGetThreadNetifIndex(); + otError error = OT_ERROR_NONE; + + static_assert(N == sizeof(in6_addr) || N == sizeof(in_addr), "aAddress should be 4 octets or 16 octets"); VerifyOrExit(netifIdx > 0, error = OT_ERROR_INVALID_STATE); VerifyOrExit(sNetlinkFd >= 0, error = OT_ERROR_INVALID_STATE); @@ -591,9 +593,9 @@ static otError AddRoute(const otIp6Prefix &aPrefix, uint32_t aPriority) req.header.nlmsg_pid = 0; req.header.nlmsg_seq = ++sNetlinkSequence; - req.msg.rtm_family = AF_INET6; + req.msg.rtm_family = (N == sizeof(in6_addr) ? AF_INET6 : AF_INET); req.msg.rtm_src_len = 0; - req.msg.rtm_dst_len = aPrefix.mLength; + req.msg.rtm_dst_len = aPrefixLen; req.msg.rtm_tos = 0; req.msg.rtm_scope = RT_SCOPE_UNIVERSE; req.msg.rtm_type = RTN_UNICAST; @@ -601,9 +603,7 @@ static otError AddRoute(const otIp6Prefix &aPrefix, uint32_t aPriority) req.msg.rtm_protocol = RTPROT_BOOT; req.msg.rtm_flags = 0; - otIp6AddressToString(&aPrefix.mPrefix, addrBuf, OT_IP6_ADDRESS_STRING_SIZE); - inet_pton(AF_INET6, addrBuf, data); - AddRtAttr(reinterpret_cast(&req), sizeof(req), RTA_DST, data, sizeof(data)); + AddRtAttr(reinterpret_cast(&req), sizeof(req), RTA_DST, aAddress, sizeof(aAddress)); AddRtAttrUint32(&req.header, sizeof(req), RTA_PRIORITY, aPriority); AddRtAttrUint32(&req.header, sizeof(req), RTA_OIF, netifIdx); @@ -665,6 +665,18 @@ exit: return error; } +static otError AddRoute(const otIp6Prefix &aPrefix, uint32_t aPriority) +{ + return AddRoute(aPrefix.mPrefix.mFields.m8, aPrefix.mLength, aPriority); +} + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE +static otError AddIp4Route(const otIp4Cidr &aCidr, uint32_t aPriority) +{ + return AddRoute(aCidr.mAddress.mFields.m8, aCidr.mLength, aPriority); +} +#endif + #endif // __linux__ && (OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE || // OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE) @@ -935,21 +947,15 @@ static void processTransmit(otInstance *aInstance) char packet[kMaxIp6Size]; otError error = OT_ERROR_NONE; size_t offset = 0; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + bool isIp4 = false; +#endif assert(gInstance == aInstance); rval = read(sTunFd, packet, sizeof(packet)); VerifyOrExit(rval > 0, error = OT_ERROR_FAILED); - { - otMessageSettings settings; - - settings.mLinkSecurityEnabled = (otThreadGetDeviceRole(aInstance) != OT_DEVICE_ROLE_DISABLED); - settings.mPriority = OT_MESSAGE_PRIORITY_LOW; - message = otIp6NewMessage(aInstance, &settings); - VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS); - } - #if defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__) // BSD tunnel drivers have (for legacy reasons), may have a 4-byte header on them if ((rval >= 4) && (packet[0] == 0) && (packet[1] == 0)) @@ -959,6 +965,20 @@ static void processTransmit(otInstance *aInstance) } #endif + { + otMessageSettings settings; + + settings.mLinkSecurityEnabled = (otThreadGetDeviceRole(aInstance) != OT_DEVICE_ROLE_DISABLED); + settings.mPriority = OT_MESSAGE_PRIORITY_LOW; +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + isIp4 = (packet[offset] & 0xf0) == 0x40; + message = isIp4 ? otIp4NewMessage(aInstance, &settings) : otIp6NewMessage(aInstance, &settings); +#else + message = otIp6NewMessage(aInstance, &settings); +#endif + VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS); + } + #if OPENTHREAD_POSIX_LOG_TUN_PACKETS otLogInfoPlat("[netif] Packet to NCP (%hu bytes)", static_cast(rval)); otDumpInfoPlat("", &packet[offset], static_cast(rval)); @@ -966,7 +986,11 @@ static void processTransmit(otInstance *aInstance) SuccessOrExit(error = otMessageAppend(message, &packet[offset], static_cast(rval))); - error = otIp6Send(aInstance, message); +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + error = isIp4 ? otNat64Send(aInstance, message) : otIp6Send(aInstance, message); +#else + error = otIp6Send(aInstance, message); +#endif message = nullptr; exit: @@ -1134,6 +1158,15 @@ static void processNetifLinkEvent(otInstance *aInstance, struct nlmsghdr *aNetli otLogInfoPlat("[netif] Succeeded to sync netif state with host"); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + if (isUp && gNat64Cidr.mLength > 0) + { + SuccessOrExit(error = otNat64SetIp4Cidr(gInstance, &gNat64Cidr)); + AddIp4Route(gNat64Cidr, kExternalRoutePriority); + otLogInfoPlat("[netif] Succeeded to enable NAT64"); + } +#endif + exit: if (error != OT_ERROR_NONE) { @@ -1755,6 +1788,10 @@ void platformNetifSetUp(void) otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_DISABLED); #endif otIp6SetReceiveCallback(gInstance, processReceive, gInstance); +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + // We can use the same function for IPv6 and translated IPv4 messages. + otNat64SetReceiveIp4Callback(gInstance, processReceive, gInstance); +#endif otIp6SetAddressCallback(gInstance, processAddressChange, gInstance); #if OPENTHREAD_POSIX_MULTICAST_PROMISCUOUS_REQUIRED otIp6SetMulticastPromiscuousEnabled(aInstance, true); diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 919a319e2..b23b77c1f 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -447,6 +448,11 @@ extern char gNetifName[IFNAMSIZ]; */ extern unsigned int gNetifIndex; +/** + * The CIDR for NAT64 + */ +extern otIp4Cidr gNat64Cidr; + /** * This function initializes platform Backbone network. * diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index c3e176abe..7aa6c9b90 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -36,6 +36,7 @@ #include "platform-posix.h" #include +#include #include #include @@ -143,6 +144,15 @@ void platformInit(otPlatformConfig *aPlatformConfig) gNetifName[0] = '\0'; +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + if ((sscanf(OPENTHREAD_POSIX_CONFIG_NAT64_CIDR, "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8 "/%" SCNu8, + &gNat64Cidr.mAddress.mFields.m8[0], &gNat64Cidr.mAddress.mFields.m8[1], + &gNat64Cidr.mAddress.mFields.m8[2], &gNat64Cidr.mAddress.mFields.m8[3], &gNat64Cidr.mLength)) != 5) + { + gNat64Cidr.mLength = 0; + } +#endif + #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE platformNetifInit(aPlatformConfig->mInterfaceName); #endif diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 9a68ecfcf..6d606ec8a 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -683,6 +683,27 @@ target_link_libraries(ot-test-multicast-listeners-table add_test(NAME ot-test-multicast-listeners-table COMMAND ot-test-multicast-listeners-table) +add_test(NAME ot-test-nat64 COMMAND ot-test-nat64) + +add_executable(ot-test-nat64 + test_nat64.cpp +) + +target_include_directories(ot-test-nat64 + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-nat64 + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-nat64 + PRIVATE + ${COMMON_LIBS} +) + add_executable(ot-test-ndproxy-table test_ndproxy_table.cpp ) diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 21316aab2..3bd12609a 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -141,6 +141,7 @@ check_PROGRAMS += \ ot-test-message \ ot-test-message-queue \ ot-test-multicast-listeners-table \ + ot-test-nat64 \ ot-test-ndproxy-table \ ot-test-netif \ ot-test-network-data \ @@ -313,6 +314,10 @@ ot_test_multicast_listeners_table_LDADD = $(COMMON_LDADD) ot_test_multicast_listeners_table_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) ot_test_multicast_listeners_table_SOURCES = $(COMMON_SOURCES) test_multicast_listeners_table.cpp +ot_test_nat64_LDADD = $(COMMON_LDADD) +ot_test_nat64_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) +ot_test_nat64_SOURCES = $(COMMON_SOURCES) test_nat64.cpp + ot_test_network_name_LDADD = $(COMMON_LDADD) ot_test_network_name_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) ot_test_network_name_SOURCES = $(COMMON_SOURCES) test_network_name.cpp diff --git a/tests/unit/test_nat64.cpp b/tests/unit/test_nat64.cpp new file mode 100644 index 000000000..c060b3770 --- /dev/null +++ b/tests/unit/test_nat64.cpp @@ -0,0 +1,314 @@ +/* + * 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. + */ + +#include "net/nat64_translator.hpp" + +#include "test_platform.h" +#include "test_util.hpp" + +#include "string.h" + +#include "common/code_utils.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" +#include "common/message.hpp" +#include "net/ip6.hpp" + +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + +namespace ot { +namespace BorderRouter { + +static ot::Instance *sInstance; + +void DumpMessageInHex(const char *prefix, const uint8_t *aBuf, size_t aBufLen) +{ + // This function dumps all packets the output of this function can be imported to packet analyser for debugging. + printf("%s", prefix); + for (uint16_t i = 0; i < aBufLen; i++) + { + printf("%02x", aBuf[i]); + } + printf("\n"); +} + +bool CheckMessage(const Message &aMessage, const uint8_t *aExpectedMessage, size_t aExpectedMessageLen) +{ + uint8_t readMessage[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; + uint16_t messageLength; + bool success = true; + + success = success && (aMessage.GetLength() == aExpectedMessageLen); + messageLength = aMessage.ReadBytes(0, readMessage, aMessage.GetLength()); + success = success && (aExpectedMessageLen == messageLength); + success = success && (memcmp(readMessage, aExpectedMessage, aExpectedMessageLen) == 0); + + if (!success) + { + printf("Expected Message\n"); + for (uint16_t i = 0; i < aExpectedMessageLen; i++) + { + printf("%02x%c", aExpectedMessage[i], " \n"[(i & 0xf) == 0xf]); + } + printf("\n"); + printf("Actual Message\n"); + for (uint16_t i = 0; i < messageLength; i++) + { + printf("%02x%c", readMessage[i], " \n"[(i & 0xf) == 0xf]); + } + printf("\n"); + } + + return success; +} + +template +void TestCase6To4(const char *aTestName, + const uint8_t (&aIp6Message)[N], + Nat64::Translator::Result aResult, + const uint8_t * aOutMessage, + size_t aOutMessageLen) +{ + Message *msg = sInstance->Get().NewMessage(0); + + printf("Testing NAT64 6 to 4: %s\n", aTestName); + + VerifyOrQuit(msg != nullptr); + SuccessOrQuit(msg->AppendBytes(aIp6Message, N)); + + DumpMessageInHex("I ", aIp6Message, N); + + VerifyOrQuit(sInstance->Get().TranslateFromIp6(*msg) == aResult); + + if (aOutMessage != nullptr) + { + DumpMessageInHex("O ", aOutMessage, aOutMessageLen); + VerifyOrQuit(CheckMessage(*msg, aOutMessage, aOutMessageLen)); + } + + printf(" ... PASS\n"); +} + +template +void TestCase4To6(const char *aTestName, + const uint8_t (&aIp4Message)[N], + Nat64::Translator::Result aResult, + const uint8_t * aOutMessage, + size_t aOutMessageLen) +{ + Message *msg = sInstance->Get().NewMessage(0); + + printf("Testing NAT64 4 to 6: %s\n", aTestName); + + VerifyOrQuit(msg != nullptr); + SuccessOrQuit(msg->AppendBytes(aIp4Message, N)); + + DumpMessageInHex("I ", aIp4Message, N); + + VerifyOrQuit(sInstance->Get().TranslateToIp6(*msg) == aResult); + + if (aOutMessage != nullptr) + { + DumpMessageInHex("O ", aOutMessage, aOutMessageLen); + VerifyOrQuit(CheckMessage(*msg, aOutMessage, aOutMessageLen)); + } + + printf(" ... PASS\n"); +} + +void TestNat64(void) +{ + Ip6::Prefix nat64prefix; + Ip4::Cidr nat64cidr; + Ip6::Address ip6Source; + Ip6::Address ip6Dest; + + sInstance = testInitInstance(); + + { + const uint8_t ip6Address[] = {0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + const uint8_t ip4Address[] = {192, 168, 123, 1}; + + nat64cidr.Set(ip4Address, 32); + nat64prefix.Set(ip6Address, 96); + SuccessOrQuit(sInstance->Get().SetIp4Cidr(nat64cidr)); + sInstance->Get().SetNat64Prefix(nat64prefix); + } + + { + // fd02::1 fd01::ac10:f3c5 UDP 52 43981 → 4660 Len=4 + const uint8_t kIp6Packet[] = { + 0x60, 0x08, 0x6e, 0x38, 0x00, 0x0c, 0x11, 0x40, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 172, 16, 243, 197, 0xab, 0xcd, 0x12, 0x34, 0x00, 0x0c, 0xe3, 0x31, 0x61, 0x62, 0x63, 0x64, + }; + // 192.168.123.1 172.16.243.197 UDP 32 43981 → 4660 Len=4 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x40, 0x11, 0x9f, + 0x4d, 192, 168, 123, 1, 172, 16, 243, 197, 0xab, 0xcd, + 0x12, 0x34, 0x00, 0x0c, 0xa1, 0x8d, 0x61, 0x62, 0x63, 0x64}; + + TestCase6To4("good v6 udp datagram", kIp6Packet, Nat64::Translator::kForward, kIp4Packet, sizeof(kIp4Packet)); + } + + { + // 172.16.243.197 192.168.123.1 UDP 32 43981 → 4660 Len=4 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x11, 0xa0, + 0x4d, 172, 16, 243, 197, 192, 168, 123, 1, 0xab, 0xcd, + 0x12, 0x34, 0x00, 0x0c, 0xa1, 0x8d, 0x61, 0x62, 0x63, 0x64}; + // fd01::ac10:f3c5 fd02::1 UDP 52 43981 → 4660 Len=4 + const uint8_t kIp6Packet[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x3f, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 172, 16, 243, 197, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xab, 0xcd, 0x12, 0x34, 0x00, 0x0c, 0xe3, 0x31, 0x61, 0x62, 0x63, 0x64, + }; + + TestCase4To6("good v4 udp datagram", kIp4Packet, Nat64::Translator::kForward, kIp6Packet, sizeof(kIp6Packet)); + } + + { + // fd02::1 fd01::ac10:f3c5 TCP 64 43981 → 4660 [ACK] Seq=1 Ack=1 Win=1 Len=4 + const uint8_t kIp6Packet[] = { + 0x60, 0x08, 0x6e, 0x38, 0x00, 0x18, 0x06, 0x40, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 172, 16, 243, 197, 0xab, 0xcd, 0x12, 0x34, 0x87, 0x65, 0x43, 0x21, + 0x12, 0x34, 0x56, 0x78, 0x50, 0x10, 0x00, 0x01, 0x5f, 0xf8, 0x00, 0x00, 0x61, 0x62, 0x63, 0x64, + }; + // 192.168.123.1 172.16.243.197 TCP 44 43981 → 4660 [ACK] Seq=1 Ack=1 Win=1 Len=4 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, 0x9f, + 0x4c, 192, 168, 123, 1, 172, 16, 243, 197, 0xab, 0xcd, + 0x12, 0x34, 0x87, 0x65, 0x43, 0x21, 0x12, 0x34, 0x56, 0x78, 0x50, + 0x10, 0x00, 0x01, 0x1e, 0x54, 0x00, 0x00, 0x61, 0x62, 0x63, 0x64}; + + TestCase6To4("good v6 tcp datagram", kIp6Packet, Nat64::Translator::kForward, kIp4Packet, sizeof(kIp4Packet)); + } + + { + // 172.16.243.197 192.168.123.1 TCP 44 43981 → 4660 [ACK] Seq=1 Ack=1 Win=1 Len=4 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, 0x9f, + 0x4c, 172, 16, 243, 197, 192, 168, 123, 1, 0xab, 0xcd, + 0x12, 0x34, 0x87, 0x65, 0x43, 0x21, 0x12, 0x34, 0x56, 0x78, 0x50, + 0x10, 0x00, 0x01, 0x1e, 0x54, 0x00, 0x00, 0x61, 0x62, 0x63, 0x64}; + // fd01::ac10:f3c5 fd02::1 TCP 64 43981 → 4660 [ACK] Seq=1 Ack=1 Win=1 Len=4 + const uint8_t kIp6Packet[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x40, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 172, 16, 243, 197, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xab, 0xcd, 0x12, 0x34, 0x87, 0x65, 0x43, 0x21, + 0x12, 0x34, 0x56, 0x78, 0x50, 0x10, 0x00, 0x01, 0x5f, 0xf8, 0x00, 0x00, 0x61, 0x62, 0x63, 0x64, + }; + + TestCase4To6("good v4 tcp datagram", kIp4Packet, Nat64::Translator::kForward, kIp6Packet, sizeof(kIp6Packet)); + } + + { + // fd02::1 fd01::ac10:f3c5 ICMPv6 52 Echo (ping) request id=0xaabb, seq=1, hop limit=64 + const uint8_t kIp6Packet[] = { + 0x60, 0x08, 0x6e, 0x38, 0x00, 0x0c, 0x3a, 0x40, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 172, 16, 243, 197, 0x80, 0x00, 0x76, 0x59, 0xaa, 0xbb, 0x00, 0x01, 0x61, 0x62, 0x63, 0x64, + }; + // 192.168.123.1 172.16.243.197 ICMP 32 Echo (ping) request id=0xaabb, seq=1/256, ttl=63 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x9f, + 0x5d, 192, 168, 123, 1, 172, 16, 243, 197, 0x08, 0x00, + 0x88, 0x7c, 0xaa, 0xbb, 0x00, 0x01, 0x61, 0x62, 0x63, 0x64}; + + TestCase6To4("good v6 icmp ping request datagram", kIp6Packet, Nat64::Translator::kForward, kIp4Packet, + sizeof(kIp4Packet)); + } + + { + // 172.16.243.197 192.168.123.1 ICMP 32 Echo (ping) reply id=0xaabb, seq=1/256, ttl=63 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x01, 0xa0, + 0x5d, 172, 16, 243, 197, 192, 168, 123, 1, 0x00, 0x00, + 0x90, 0x7c, 0xaa, 0xbb, 0x00, 0x01, 0x61, 0x62, 0x63, 0x64}; + // fd01::ac10:f3c5 fd02::1 ICMPv6 52 Echo (ping) reply id=0xaabb, seq=1, hop limit=62 + const uint8_t kIp6Packet[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x3f, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 172, 16, 243, 197, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x81, 0x00, 0x75, 0x59, 0xaa, 0xbb, 0x00, 0x01, 0x61, 0x62, 0x63, 0x64, + }; + + TestCase4To6("good v4 icmp ping response datagram", kIp4Packet, Nat64::Translator::kForward, kIp6Packet, + sizeof(kIp6Packet)); + } + + { + // fd02::1 N/A IPv6 39 Invalid IPv6 header + const uint8_t kIp6Packet[] = {0x60, 0x08, 0x6e, 0x38, 0x00, 0x0c, 0x11, 0x40, 0xfd, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfd, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172, 16, 243}; + + TestCase6To4("bad v6 datagram", kIp6Packet, Nat64::Translator::kDrop, nullptr, 0); + } + + { + // 172.16.243.197 N/A IPv4 19 [Malformed Packet] + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x11, + 0xa0, 0x4c, 172, 16, 243, 197, 192, 168, 123}; + + TestCase4To6("bad v4 datagram", kIp4Packet, Nat64::Translator::kDrop, nullptr, 0); + } + + { + // 172.16.243.197 192.168.123.2 UDP 32 43981 → 4660 Len=4 + const uint8_t kIp4Packet[] = {0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x11, 0xa0, + 0x4c, 172, 16, 243, 197, 192, 168, 123, 2, 0xab, 0xcd, + 0x12, 0x34, 0x00, 0x0c, 0xa1, 0x8c, 0x61, 0x62, 0x63, 0x64}; + + TestCase4To6("no v4 mapping", kIp4Packet, Nat64::Translator::kDrop, nullptr, 0); + } + + { + // fd02::2 fd01::ac10:f3c5 UDP 52 43981 → 4660 Len=4 + const uint8_t kIp6Packet[] = { + 0x60, 0x08, 0x6e, 0x38, 0x00, 0x0c, 0x11, 0x40, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 172, 16, 243, 197, 0xab, 0xcd, 0x12, 0x34, 0x00, 0x0c, 0xe3, 0x30, 0x61, 0x62, 0x63, 0x64, + }; + + TestCase6To4("mapping pool exhausted", kIp6Packet, Nat64::Translator::kDrop, nullptr, 0); + } + + testFreeInstance(sInstance); +} + +} // namespace BorderRouter +} // namespace ot + +#endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + +int main(void) +{ +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + ot::BorderRouter::TestNat64(); + printf("All tests passed\n"); +#else // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + printf("NAT64 is not enabled\n"); +#endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + return 0; +}