From dacdc7d36f0ad5b8b04b69c1e06efe22cd1993b8 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 25 Jan 2024 15:14:58 -0800 Subject: [PATCH] [ip6] add method for creating/parsing IPv4-mapped IPv6 address (#9799) The IPv4-mapped IPv6 address is useful in certain platform/public APIs where we need to support both IPv6 or IPv4 address. We can use IPv4-mapped IPv6 address to represent an IPv4 address. This commit adds simple helper functions to validate, create and parse such addresses. --- include/openthread/instance.h | 2 +- include/openthread/nat64.h | 24 +++++++++++++++++++++ src/core/api/nat64_api.cpp | 10 +++++++++ src/core/net/ip4_types.cpp | 11 ++++++++++ src/core/net/ip4_types.hpp | 11 ++++++++++ src/core/net/ip6_address.cpp | 12 +++++++++++ src/core/net/ip6_address.hpp | 21 ++++++++++++++++++ tests/unit/test_ip_address.cpp | 39 ++++++++++++++++++++++++++++++++++ 8 files changed, 129 insertions(+), 1 deletion(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 58f33e64d..47de8daef 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 (390) +#define OPENTHREAD_API_VERSION (391) /** * @addtogroup api-instance diff --git a/include/openthread/nat64.h b/include/openthread/nat64.h index 930c4c342..61fe1b697 100644 --- a/include/openthread/nat64.h +++ b/include/openthread/nat64.h @@ -416,6 +416,30 @@ bool otIp4IsAddressEqual(const otIp4Address *aFirst, const otIp4Address *aSecond */ void otIp4ExtractFromIp6Address(uint8_t aPrefixLength, const otIp6Address *aIp6Address, otIp4Address *aIp4Address); +/** + * Extracts the IPv4 address from a given IPv4-mapped IPv6 address. + * + * An IPv4-mapped IPv6 address consists of an 80-bit prefix of zeros, the next 16 bits set to ones, and the remaining, + * least-significant 32 bits contain the IPv4 address, e.g., `::ffff:192.0.2.128` representing `192.0.2.128`. + * + * @param[in] aIp6Address An IPv6 address to extract IPv4 from. + * @param[out] aIp4Address An IPv4 address to output the extracted address. + * + * @retval OT_ERROR_NONE Extracted the IPv4 address successfully. @p aIp4Address is updated. + * @retval OT_ERROR_PARSE The @p aIp6Address does not follow the IPv4-mapped IPv6 address format. + * + */ +otError otIp4FromIp4MappedIp6Address(const otIp6Address *aIp6Address, otIp4Address *aIp4Address); + +/** + * Converts a given IP4 address to an IPv6 address following the IPv4-mapped IPv6 address format. + * + * @param[in] aIp4Address An IPv4 address to convert. + * @param[out] aIp6Address An IPv6 address to set. + * + */ +void otIp4ToIp4MappedIp6Address(const otIp4Address *aIp4Address, otIp6Address *aIp6Address); + #define OT_IP4_ADDRESS_STRING_SIZE 17 ///< Length of 000.000.000.000 plus a suffix NUL /** diff --git a/src/core/api/nat64_api.cpp b/src/core/api/nat64_api.cpp index 02aa785b4..f2130e6bc 100644 --- a/src/core/api/nat64_api.cpp +++ b/src/core/api/nat64_api.cpp @@ -140,6 +140,16 @@ void otIp4ExtractFromIp6Address(uint8_t aPrefixLength, const otIp6Address *aIp6A AsCoreType(aIp4Address).ExtractFromIp6Address(aPrefixLength, AsCoreType(aIp6Address)); } +otError otIp4FromIp4MappedIp6Address(const otIp6Address *aIp6Address, otIp4Address *aIp4Address) +{ + return AsCoreType(aIp4Address).ExtractFromIp4MappedIp6Address(AsCoreType(aIp6Address)); +} + +void otIp4ToIp4MappedIp6Address(const otIp4Address *aIp4Address, otIp6Address *aIp6Address) +{ + AsCoreType(aIp6Address).SetToIp4Mapped(AsCoreType(aIp4Address)); +} + otError otIp4AddressFromString(const char *aString, otIp4Address *aAddress) { AssertPointerIsNotNull(aString); diff --git a/src/core/net/ip4_types.cpp b/src/core/net/ip4_types.cpp index 7f03a3e0e..472dee003 100644 --- a/src/core/net/ip4_types.cpp +++ b/src/core/net/ip4_types.cpp @@ -66,6 +66,17 @@ exit: return error; } +Error Address::ExtractFromIp4MappedIp6Address(const Ip6::Address &aIp6Address) +{ + Error error = kErrorNone; + + VerifyOrExit(aIp6Address.IsIp4Mapped(), error = kErrorParse); + SetBytes(&aIp6Address.GetBytes()[12]); + +exit: + return error; +} + void Address::ExtractFromIp6Address(uint8_t aPrefixLength, const Ip6::Address &aIp6Address) { // The prefix length must be 32, 40, 48, 56, 64, 96. IPv4 bytes are added diff --git a/src/core/net/ip4_types.hpp b/src/core/net/ip4_types.hpp index 8095a818a..169fe3dcf 100644 --- a/src/core/net/ip4_types.hpp +++ b/src/core/net/ip4_types.hpp @@ -122,6 +122,17 @@ public: */ void SetBytes(const uint8_t *aBuffer) { memcpy(mFields.m8, aBuffer, kSize); } + /** + * Sets the IPv4 address from a given IPv4-mapped IPv6 address. + * + * @param[in] aIp6Address An IPv6 address. + * + * @retval kErrorNone Set the IPv4 address successfully. + * @retval kErrorPase The @p aIp6Address does not follow the IPv4-mapped IPv6 address format. + * + */ + Error ExtractFromIp4MappedIp6Address(const Ip6::Address &aIp6Address); + /** * Sets the IPv4 address by performing NAT64 address translation from a given IPv6 address as specified * in RFC 6052. diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp index 4c092267e..8e5cdc869 100644 --- a/src/core/net/ip6_address.cpp +++ b/src/core/net/ip6_address.cpp @@ -388,6 +388,18 @@ bool Address::IsRealmLocalAllMplForwarders(void) const { return (*this == GetRea void Address::SetToRealmLocalAllMplForwarders(void) { *this = GetRealmLocalAllMplForwarders(); } +bool Address::IsIp4Mapped(void) const +{ + return (mFields.m32[0] == 0) && (mFields.m32[1] == 0) && (mFields.m32[2] == BigEndian::HostSwap32(0xffff)); +} + +void Address::SetToIp4Mapped(const Ip4::Address &aIp4Address) +{ + Clear(); + mFields.m16[5] = 0xffff; + memcpy(&mFields.m8[12], aIp4Address.GetBytes(), sizeof(Ip4::Address)); +} + bool Address::MatchesPrefix(const Prefix &aPrefix) const { return Prefix::MatchLength(mFields.m8, aPrefix.GetBytes(), aPrefix.GetBytesSize()) >= aPrefix.GetLength(); diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index b066557de..d7dfe4969 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -808,6 +808,27 @@ public: SetToLocator(aNetworkPrefix, aAloc16); } + /** + * Indicates whether or not the IPv6 address follows the IPv4-mapped format. + * + * An IPv4-mapped IPv6 address consists of an 80-bit prefix of zeros, the next 16 bits set to ones, and the + * remaining, least-significant 32 bits contain the IPv4 address, e.g., `::ffff:192.0.2.128` representing + * `192.0.2.128` IPv4 address. + * + * @retval TRUE If the IPv6 address follows the IPv4-mapped format. + * @retval FALSE If the IPv6 address does not follow the IPv4-mapped format. + * + */ + bool IsIp4Mapped(void) const; + + /** + * Sets the IPv6 address to follow the IPv4-mapped IPv6 address for a given IPv4 address. + * + * @param[in] aIp4Address An IPv4 address. + * + */ + void SetToIp4Mapped(const Ip4::Address &aIp4Address); + /** * Returns the Network Prefix of the IPv6 address (most significant 64 bits of the address). * diff --git a/tests/unit/test_ip_address.cpp b/tests/unit/test_ip_address.cpp index 746ac02c0..221002652 100644 --- a/tests/unit/test_ip_address.cpp +++ b/tests/unit/test_ip_address.cpp @@ -758,6 +758,44 @@ void TestIp6PrefixTidy(void) } } +void TestIp4MappedIp6Address(void) +{ + const uint8_t kIp4Address[] = {192, 0, 2, 33}; + + const char *kInvalidIp4MappedFormats[] = { + "8000::ffff:192.0.2.23", "0:400::ffff:192.0.2.23", "0:0:1::ffff:192.0.2.23", "0:0:0:4:0:ffff:192.0.2.23", + "0:0:0:0:1:ffff:192.0.2.23", "::fffe:192.0.2.23", "::efff:192.0.2.23", + }; + + Ip4::Address expectedIp4Address; + Ip4::Address ip4Address; + Ip6::Address expectedIp6Address; + Ip6::Address ip6Address; + + printf("\nTestIp4MappedIp6Address()\n"); + + expectedIp4Address.SetBytes(kIp4Address); + + SuccessOrQuit(expectedIp6Address.FromString("::ffff:192.0.2.33")); + ip6Address.SetToIp4Mapped(expectedIp4Address); + + printf("IPv4-mapped IPv6 address: %s\n", ip6Address.ToString().AsCString()); + + VerifyOrQuit(ip6Address.IsIp4Mapped()); + VerifyOrQuit(ip6Address == expectedIp6Address); + + SuccessOrQuit(ip4Address.ExtractFromIp4MappedIp6Address(ip6Address)); + VerifyOrQuit(ip4Address == expectedIp4Address); + + for (const char *invalidIp4MappedAddr : kInvalidIp4MappedFormats) + { + SuccessOrQuit(ip6Address.FromString(invalidIp4MappedAddr)); + printf("Invalid IPv4-mapped IPv6 address: %s -> %s\n", invalidIp4MappedAddr, ip6Address.ToString().AsCString()); + VerifyOrQuit(!ip6Address.IsIp4Mapped()); + VerifyOrQuit(ip4Address.ExtractFromIp4MappedIp6Address(ip6Address) != kErrorNone); + } +} + void TestIp4Ip6Translation(void) { struct TestCase @@ -884,6 +922,7 @@ int main(void) ot::TestIp6PrefixFromString(); ot::TestIp6Prefix(); ot::TestIp6PrefixTidy(); + ot::TestIp4MappedIp6Address(); ot::TestIp4Ip6Translation(); ot::TestIp4Cidr(); ot::TestIp4CidrFromString();