From 6973a1b067c14902e957302371b7aa4559408a11 Mon Sep 17 00:00:00 2001 From: Song GUO Date: Thu, 15 Sep 2022 23:43:36 +0800 Subject: [PATCH] [nat64] implement functions for NAT64 on the client side (#8126) This commit adds the ability for accepting an IPv4 address for {ping, tcp, udp} CLI commands. --- include/openthread/instance.h | 2 +- include/openthread/nat64.h | 26 +++++++++++++++++++++++++ src/cli/README.md | 12 ++++++++++++ src/cli/README_TCP.md | 12 +++++++++++- src/cli/README_UDP.md | 24 +++++++++++++++++++++-- src/cli/cli.cpp | 33 +++++++++++++++++++++++++++++++- src/cli/cli.hpp | 21 ++++++++++++++++++++ src/cli/cli_tcp.cpp | 11 ++++++++++- src/cli/cli_udp.cpp | 22 +++++++++++++++++++-- src/core/api/ip6_api.cpp | 1 + src/core/api/nat64_api.cpp | 19 ++++++++++++++++++ src/core/utils/parse_cmdline.cpp | 5 +++++ src/core/utils/parse_cmdline.hpp | 28 +++++++++++++++++++++++++++ 13 files changed, 208 insertions(+), 8 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 36c567037..2846d8cb0 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 (243) +#define OPENTHREAD_API_VERSION (244) /** * @addtogroup api-instance diff --git a/include/openthread/nat64.h b/include/openthread/nat64.h index b8e6096ae..09a26b87c 100644 --- a/include/openthread/nat64.h +++ b/include/openthread/nat64.h @@ -384,6 +384,32 @@ void otIp4AddressToString(const otIp4Address *aAddress, char *aBuffer, uint16_t */ void otIp4CidrToString(const otIp4Cidr *aCidr, char *aBuffer, uint16_t aSize); +/** + * Converts a human-readable IPv4 address string into a binary representation. + * + * @param[in] aString A pointer to a NULL-terminated string. + * @param[out] aAddress A pointer to an IPv4 address. + * + * @retval OT_ERROR_NONE Successfully parsed the string. + * @retval OT_ERROR_INVALID_ARGS Failed to parse the string. + * + */ +otError otIp4AddressFromString(const char *aString, otIp4Address *aAddress); + +/** + * Sets the IPv6 address by performing NAT64 address translation from the preferred NAT64 prefix and the given IPv4 + * address as specified in RFC 6052. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aIp4Address A pointer to the IPv4 address to translate to IPv6. + * @param[out] aIp6Address A pointer to the synthesized IPv6 address. + * + * @returns OT_ERROR_NONE Successfully synthesized the IPv6 address from NAT64 prefix and IPv4 address. + * @returns OT_ERROR_INVALID_STATE No valid NAT64 prefix in the network data. + * + */ +otError otNat64SynthersizeIp6Address(otInstance *aInstance, const otIp4Address *aIp4Address, otIp6Address *aIp6Address); + /** * @} * diff --git a/src/cli/README.md b/src/cli/README.md index 9b7f5f845..164088742 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -2147,6 +2147,18 @@ Done Done ``` +The address can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix from the network data. + +> Note: The command will return `InvalidState` when the preferred NAT64 prefix is unavailable. + +```bash +> ping 172.17.0.1 +Pinging synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1 +> 16 bytes from fdde:ad00:beef:2:0:0:ac11:1: icmp_seq=5 hlim=64 time=0ms +1 packets transmitted, 1 packets received. Packet loss = 0.0%. Round-trip min/avg/max = 0/0.0/0 ms. +Done +``` + ### ping stop Stop sending ICMPv6 Echo Requests. diff --git a/src/cli/README_TCP.md b/src/cli/README_TCP.md index c1aba71d0..eb42fbaff 100644 --- a/src/cli/README_TCP.md +++ b/src/cli/README_TCP.md @@ -109,7 +109,7 @@ Establishes a connection with the specified peer. If the connection establishment is successful, the resulting TCP connection is associated with the example TCP endpoint. -- ip: the peer's IPv6 address. +- ip: the peer's IP address. - port: the peer's TCP port. ```bash @@ -118,6 +118,16 @@ Done TCP: Connection established ``` +The address can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix from the network data. + +> Note: The command will return `InvalidState` when the preferred NAT64 prefix is unavailable. + +```bash +> tcp connect 172.17.0.1 1234 +Connecting to synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1 +Done +``` + ### deinit Deinitializes the example TCP listener and the example TCP endpoint. diff --git a/src/cli/README_UDP.md b/src/cli/README_UDP.md index 51cd8cc5b..14444cdaf 100644 --- a/src/cli/README_UDP.md +++ b/src/cli/README_UDP.md @@ -96,7 +96,7 @@ Done Specifies the peer with which the socket is to be associated. -- ip: the peer's IPv6 address. +- ip: the peer's IP address. - port: the peer's UDP port. ```bash @@ -104,6 +104,16 @@ Specifies the peer with which the socket is to be associated. Done ``` +The address can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix from the network data. + +> Note: The command will return `InvalidState` when the preferred NAT64 prefix is unavailable. + +```bash +> udp connect 172.17.0.1 1234 +Connecting to synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1 +Done +``` + ### linksecurity Indicates whether the link security is enabled or disabled. @@ -145,7 +155,7 @@ Done Send a UDP message. -- ip: the IPv6 destination address. +- ip: the destination address. - port: the UDP destination port. - message: the message to send. @@ -154,6 +164,16 @@ Send a UDP message. Done ``` +The address can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix from the network data. + +> Note: The command will return `InvalidState` when the preferred NAT64 prefix is unavailable. + +```bash +> udp send 172.17.0.1 1234 +Sending to synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1 +Done +``` + ### send \ \ \ \ Send a few bytes over UDP. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 25acbef6a..a6302be6a 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -455,6 +455,31 @@ const char *Interpreter::PreferenceToString(signed int aPreference) return str; } +otError Interpreter::ParseToIp6Address(otInstance * aInstance, + const Arg & aArg, + otIp6Address &aAddress, + bool & aSynthesized) +{ + Error error = kErrorNone; + + VerifyOrExit(!aArg.IsEmpty(), error = OT_ERROR_INVALID_ARGS); + error = aArg.ParseAsIp6Address(aAddress); + aSynthesized = false; + if (error != kErrorNone) + { + // It might be an IPv4 address, let's have a try. + otIp4Address ip4Address; + + // Do not touch the error value if we failed to parse it as an IPv4 address. + SuccessOrExit(aArg.ParseAsIp4Address(ip4Address)); + SuccessOrExit(error = otNat64SynthersizeIp6Address(aInstance, &ip4Address, &aAddress)); + aSynthesized = true; + } + +exit: + return error; +} + #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { @@ -4760,6 +4785,7 @@ template <> otError Interpreter::Process(Arg aArgs[]) otError error = OT_ERROR_NONE; otPingSenderConfig config; bool async = false; + bool nat64SynthesizedAddress; if (aArgs[0] == "stop") { @@ -4799,7 +4825,12 @@ template <> otError Interpreter::Process(Arg aArgs[]) aArgs += 2; } - SuccessOrExit(error = aArgs[0].ParseAsIp6Address(config.mDestination)); + SuccessOrExit(error = ParseToIp6Address(GetInstancePtr(), aArgs[0], config.mDestination, nat64SynthesizedAddress)); + if (nat64SynthesizedAddress) + { + OutputFormat("Pinging synthesized IPv6 address: "); + OutputIp6AddressLine(config.mDestination); + } if (!aArgs[1].IsEmpty()) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 5b25f4f22..afc40a85e 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -235,6 +235,27 @@ public: */ static const char *PreferenceToString(signed int aPreference); + /** + * This method parses the argument as an IP address. + * + * If the argument string is an IPv4 address, this method will try to synthersize an IPv6 address using preferred + * NAT64 prefix in the network data. + * + * @param[in] aInstance A pointer to openthread instance. + * @param[in] aArg The argument string to parse. + * @param[out] aAddress A reference to an `otIp6Address` to output the parsed IPv6 address. + * @param[out] aSynthesized Whether @p aAddress is synthesized from an IPv4 address. + * + * @retval OT_ERROR_NONE The argument was parsed successfully. + * @retval OT_ERROR_INVALID_ARGS The argument is empty or does not contain valid IP address. + * @retval OT_ERROR_INVALID_STATE No valid NAT64 prefix in the network data. + * + */ + static otError ParseToIp6Address(otInstance * aInstance, + const Arg & aArg, + otIp6Address &aAddress, + bool & aSynthesized); + protected: static Interpreter *sInterpreter; diff --git a/src/cli/cli_tcp.cpp b/src/cli/cli_tcp.cpp index 97076e9df..7c242d047 100644 --- a/src/cli/cli_tcp.cpp +++ b/src/cli/cli_tcp.cpp @@ -39,6 +39,7 @@ #include "cli_tcp.hpp" +#include #include #include "cli/cli.hpp" @@ -175,10 +176,18 @@ otError TcpExample::ProcessConnect(Arg aArgs[]) { otError error; otSockAddr sockaddr; + bool nat64SynthesizedAddress; VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); - SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress)); + SuccessOrExit( + error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], sockaddr.mAddress, nat64SynthesizedAddress)); + if (nat64SynthesizedAddress) + { + OutputFormat("Connecting to synthesized IPv6 address: "); + OutputIp6AddressLine(sockaddr.mAddress); + } + SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); diff --git a/src/cli/cli_udp.cpp b/src/cli/cli_udp.cpp index 62ad05790..22b39cc17 100644 --- a/src/cli/cli_udp.cpp +++ b/src/cli/cli_udp.cpp @@ -34,6 +34,7 @@ #include "cli_udp.hpp" #include +#include #include #include "cli/cli.hpp" @@ -94,8 +95,16 @@ otError UdpExample::ProcessConnect(Arg aArgs[]) { otError error; otSockAddr sockaddr; + bool nat64SynthesizedAddress; + + SuccessOrExit( + error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], sockaddr.mAddress, nat64SynthesizedAddress)); + if (nat64SynthesizedAddress) + { + OutputFormat("Connecting to synthesized IPv6 address: "); + OutputIp6AddressLine(sockaddr.mAddress); + } - SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress)); SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); @@ -143,7 +152,16 @@ otError UdpExample::ProcessSend(Arg aArgs[]) if (!aArgs[2].IsEmpty()) { - SuccessOrExit(error = aArgs[0].ParseAsIp6Address(messageInfo.mPeerAddr)); + bool nat64SynthesizedAddress; + + SuccessOrExit(error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], messageInfo.mPeerAddr, + nat64SynthesizedAddress)); + if (nat64SynthesizedAddress) + { + OutputFormat("Sending to synthesized IPv6 address: "); + OutputIp6AddressLine(messageInfo.mPeerAddr); + } + SuccessOrExit(error = aArgs[1].ParseAsUint16(messageInfo.mPeerPort)); aArgs += 2; } diff --git a/src/core/api/ip6_api.cpp b/src/core/api/ip6_api.cpp index 2da28f004..454132b4d 100644 --- a/src/core/api/ip6_api.cpp +++ b/src/core/api/ip6_api.cpp @@ -39,6 +39,7 @@ #include "common/locator_getters.hpp" #include "net/ip4_types.hpp" #include "net/ip6_headers.hpp" +#include "thread/network_data_leader.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 726872c79..5e16c284d 100644 --- a/src/core/api/nat64_api.cpp +++ b/src/core/api/nat64_api.cpp @@ -110,6 +110,25 @@ void otIp4ExtractFromIp6Address(uint8_t aPrefixLength, const otIp6Address *aIp6A AsCoreType(aIp4Address).ExtractFromIp6Address(aPrefixLength, AsCoreType(aIp6Address)); } +otError otIp4AddressFromString(const char *aString, otIp4Address *aAddress) +{ + AssertPointerIsNotNull(aString); + return AsCoreType(aAddress).FromString(aString); +} + +otError otNat64SynthersizeIp6Address(otInstance *aInstance, const otIp4Address *aIp4Address, otIp6Address *aIp6Address) +{ + otError err = OT_ERROR_NONE; + NetworkData::ExternalRouteConfig nat64Prefix; + + VerifyOrExit(AsCoreType(aInstance).Get().GetPreferredNat64Prefix(nat64Prefix) == OT_ERROR_NONE, + err = OT_ERROR_INVALID_STATE); + AsCoreType(aIp6Address).SynthesizeFromIp4Address(nat64Prefix.GetPrefix(), AsCoreType(aIp4Address)); + +exit: + return err; +} + void otIp4AddressToString(const otIp4Address *aAddress, char *aBuffer, uint16_t aSize) { AssertPointerIsNotNull(aBuffer); diff --git a/src/core/utils/parse_cmdline.cpp b/src/core/utils/parse_cmdline.cpp index 52f77dded..2829e6d01 100644 --- a/src/core/utils/parse_cmdline.cpp +++ b/src/core/utils/parse_cmdline.cpp @@ -264,6 +264,11 @@ Error ParseAsIp6Address(const char *aString, otIp6Address &aAddress) return (aString != nullptr) ? otIp6AddressFromString(aString, &aAddress) : kErrorInvalidArgs; } +Error ParseAsIp4Address(const char *aString, otIp4Address &aAddress) +{ + return (aString != nullptr) ? otIp4AddressFromString(aString, &aAddress) : kErrorInvalidArgs; +} + Error ParseAsIp6Prefix(const char *aString, otIp6Prefix &aPrefix) { enum : uint8_t diff --git a/src/core/utils/parse_cmdline.hpp b/src/core/utils/parse_cmdline.hpp index 6b9025f6f..7703ac214 100644 --- a/src/core/utils/parse_cmdline.hpp +++ b/src/core/utils/parse_cmdline.hpp @@ -38,7 +38,9 @@ #include #include +#include #include +#include namespace ot { namespace Utils { @@ -182,6 +184,18 @@ otError ParseAsBool(const char *aString, bool &aBool); */ otError ParseAsIp6Address(const char *aString, otIp6Address &aAddress); +/** + * This function parses a string as an IPv4 address. + * + * @param[in] aString The string to parse. + * @param[out] aAddress A reference to an `otIp6Address` to output the parsed IPv6 address. + * + * @retval kErrorNone The string was parsed successfully. + * @retval kErrorInvalidArgs The string does not contain valid IPv4 address. + * + */ +otError ParseAsIp4Address(const char *aString, otIp4Address &aAddress); + /** * This function parses a string as an IPv6 prefix. * @@ -485,6 +499,20 @@ public: return CmdLineParser::ParseAsIp6Address(mString, aAddress); } + /** + * This method parses the argument as an IPv4 address. + * + * @param[out] aAddress A reference to an `otIp4Address` to output the parsed IPv4 address. + * + * @retval kErrorNone The argument was parsed successfully. + * @retval kErrorInvalidArgs The argument is empty or does not contain valid IPv4 address. + * + */ + otError ParseAsIp4Address(otIp4Address &aAddress) const + { + return CmdLineParser::ParseAsIp4Address(mString, aAddress); + } + /** * This method parses the argument as an IPv6 prefix. *