diff --git a/Android.mk b/Android.mk index 863059fb4..c5681a7e0 100644 --- a/Android.mk +++ b/Android.mk @@ -298,7 +298,7 @@ LOCAL_SRC_FILES := \ src/core/net/dns_types.cpp \ src/core/net/dnssd_server.cpp \ src/core/net/icmp6.cpp \ - src/core/net/ip4_address.cpp \ + src/core/net/ip4_types.cpp \ src/core/net/ip6.cpp \ src/core/net/ip6_address.cpp \ src/core/net/ip6_filter.cpp \ diff --git a/include/Makefile.am b/include/Makefile.am index a4a377b8d..722775d8e 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -70,6 +70,7 @@ openthread_headers = \ openthread/logging.h \ openthread/message.h \ openthread/multi_radio.h \ + openthread/nat64.h \ openthread/ncp.h \ openthread/netdata.h \ openthread/netdata_publisher.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index d07d311ac..931c7ac12 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -74,6 +74,7 @@ source_set("openthread") { "logging.h", "message.h", "multi_radio.h", + "nat64.h", "ncp.h", "netdata.h", "netdata_publisher.h", diff --git a/include/openthread/instance.h b/include/openthread/instance.h index d6ae34650..2c3deb6d5 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 (226) +#define OPENTHREAD_API_VERSION (227) /** * @addtogroup api-instance diff --git a/src/core/net/ip4_address.cpp b/include/openthread/nat64.h similarity index 52% rename from src/core/net/ip4_address.cpp rename to include/openthread/nat64.h index 6c9f3222f..55bef4df9 100644 --- a/src/core/net/ip4_address.cpp +++ b/include/openthread/nat64.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, The OpenThread Authors. + * Copyright (c) 2022, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,69 +28,72 @@ /** * @file - * This file implements IPv4 address related functionality. + * @brief + * This file defines the OpenThread API for NAT64 on a border router. */ -#include "ip4_address.hpp" +#ifndef OPENTHREAD_NAT64_H_ +#define OPENTHREAD_NAT64_H_ -#include "common/code_utils.hpp" -#include "common/numeric_limits.hpp" +#include -namespace ot { -namespace Ip4 { +#ifdef __cplusplus +extern "C" { +#endif -Error Address::FromString(const char *aString) +/** + * @addtogroup api-nat64 + * + * @brief This module includes functions and structs for the NAT64 function on the border router. These functions are + * only available when `OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE` is enabled. + * + * @{ + * + */ + +#define OT_IP4_ADDRESS_SIZE 4 ///< Size of an IPv4 address (bytes) + +/** + * @struct otIp4Address + * + * This structure represents an IPv4 address. + * + */ +OT_TOOL_PACKED_BEGIN +struct otIp4Address { - constexpr char kSeperatorChar = '.'; - constexpr char kNullChar = '\0'; - - Error error = kErrorParse; - - for (uint8_t index = 0;; index++) + union OT_TOOL_PACKED_FIELD { - uint16_t value = 0; - uint8_t hasFirstDigit = false; + uint8_t m8[OT_IP4_ADDRESS_SIZE]; ///< 8-bit fields + uint32_t m32; ///< 32-bit representation + } mFields; +} OT_TOOL_PACKED_END; - for (char digitChar = *aString;; ++aString, digitChar = *aString) - { - if ((digitChar < '0') || (digitChar > '9')) - { - break; - } +/** + * This structure represents an IPv4 address. + * + */ +typedef struct otIp4Address otIp4Address; - value = static_cast((value * 10) + static_cast(digitChar - '0')); - VerifyOrExit(value <= NumericLimits::kMax); - hasFirstDigit = true; - } - - VerifyOrExit(hasFirstDigit); - - mBytes[index] = static_cast(value); - - if (index == sizeof(Address) - 1) - { - break; - } - - VerifyOrExit(*aString == kSeperatorChar); - aString++; - } - - VerifyOrExit(*aString == kNullChar); - error = kErrorNone; - -exit: - return error; -} - -Address::InfoString Address::ToString(void) const +/** + * @struct otIp4Cidr + * + * This structure represents an IPv4 CIDR block. + * + */ +typedef struct otIp4Cidr { - InfoString string; + otIp4Address mAddress; + uint8_t mLength; +} otIp4Cidr; - string.Append("%d.%d.%d.%d", mBytes[0], mBytes[1], mBytes[2], mBytes[3]); +/** + * @} + * + */ - return string; +#ifdef __cplusplus } +#endif -} // namespace Ip4 -} // namespace ot +#endif // OPENTHREAD_NAT64_H_ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index e0b58d431..d885a6844 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -548,8 +548,8 @@ openthread_core_files = [ "net/dnssd_server.hpp", "net/icmp6.cpp", "net/icmp6.hpp", - "net/ip4_address.cpp", - "net/ip4_address.hpp", + "net/ip4_types.cpp", + "net/ip4_types.hpp", "net/ip6.cpp", "net/ip6.hpp", "net/ip6_address.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e2d090872..a2d342997 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -164,7 +164,7 @@ set(COMMON_SOURCES net/dns_types.cpp net/dnssd_server.cpp net/icmp6.cpp - net/ip4_address.cpp + net/ip4_types.cpp net/ip6.cpp net/ip6_address.cpp net/ip6_filter.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 1440810aa..22797c039 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -254,7 +254,7 @@ SOURCES_COMMON = \ net/dns_types.cpp \ net/dnssd_server.cpp \ net/icmp6.cpp \ - net/ip4_address.cpp \ + net/ip4_types.cpp \ net/ip6.cpp \ net/ip6_address.cpp \ net/ip6_filter.cpp \ @@ -559,7 +559,7 @@ HEADERS_COMMON = \ net/dns_types.hpp \ net/dnssd_server.hpp \ net/icmp6.hpp \ - net/ip4_address.hpp \ + net/ip4_types.hpp \ net/ip6.hpp \ net/ip6_address.hpp \ net/ip6_filter.hpp \ diff --git a/src/core/net/checksum.cpp b/src/core/net/checksum.cpp index 7c532ff4c..16860cbdf 100644 --- a/src/core/net/checksum.cpp +++ b/src/core/net/checksum.cpp @@ -36,6 +36,7 @@ #include "common/code_utils.hpp" #include "common/message.hpp" #include "net/icmp6.hpp" +#include "net/ip4_types.hpp" #include "net/tcp6.hpp" #include "net/udp6.hpp" @@ -115,6 +116,35 @@ void Checksum::Calculate(const Ip6::Address &aSource, } } +void Checksum::Calculate(const Ip4::Address &aSource, + const Ip4::Address &aDestination, + uint8_t aIpProto, + const Message & aMessage) +{ + Message::Chunk chunk; + uint16_t length = aMessage.GetLength() - aMessage.GetOffset(); + + // Pseudo-header for checksum calculation (RFC-768/792/793). + // Note: ICMP checksum won't count the presudo header like TCP and UDP. + if (aIpProto != Ip4::kProtoIcmp) + { + AddData(aSource.GetBytes(), sizeof(Ip4::Address)); + AddData(aDestination.GetBytes(), sizeof(Ip4::Address)); + AddUint16(static_cast(aIpProto)); + AddUint16(length); + } + + // Add message content (from offset to the end) to checksum. + + aMessage.GetFirstChunk(aMessage.GetOffset(), length, chunk); + + while (chunk.GetLength() > 0) + { + AddData(chunk.GetBytes(), chunk.GetLength()); + aMessage.GetNextChunk(length, chunk); + } +} + Error Checksum::VerifyMessageChecksum(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint8_t aIpProto) { Checksum checksum; @@ -150,6 +180,8 @@ void Checksum::UpdateMessageChecksum(Message & aMessage, ExitNow(); } + // Clear the checksum before calculating it. + aMessage.Write(aMessage.GetOffset() + headerOffset, 0); checksum.Calculate(aSource, aDestination, aIpProto, aMessage); checksum.WriteToMessage(aMessage.GetOffset() + headerOffset, aMessage); @@ -157,4 +189,48 @@ exit: return; } +void Checksum::UpdateMessageChecksum(Message & aMessage, + const Ip4::Address &aSource, + const Ip4::Address &aDestination, + uint8_t aIpProto) +{ + uint16_t headerOffset; + Checksum checksum; + + switch (aIpProto) + { + case Ip4::kProtoTcp: + headerOffset = Ip4::Tcp::Header::kChecksumFieldOffset; + break; + + case Ip4::kProtoUdp: + headerOffset = Ip4::Udp::Header::kChecksumFieldOffset; + break; + + case Ip4::kProtoIcmp: + headerOffset = Ip4::Icmp::Header::kChecksumFieldOffset; + break; + + default: + ExitNow(); + } + + // Clear the checksum before calculating it. + aMessage.Write(aMessage.GetOffset() + headerOffset, 0); + checksum.Calculate(aSource, aDestination, aIpProto, aMessage); + checksum.WriteToMessage(aMessage.GetOffset() + headerOffset, aMessage); + +exit: + return; +} + +void Checksum::UpdateIp4HeaderChecksum(Ip4::Header &aHeader) +{ + Checksum checksum; + + aHeader.SetChecksum(0); + checksum.AddData(reinterpret_cast(&aHeader), sizeof(aHeader)); + aHeader.SetChecksum(~checksum.GetValue()); +} + } // namespace ot diff --git a/src/core/net/checksum.hpp b/src/core/net/checksum.hpp index 311ba6da8..f335785ce 100644 --- a/src/core/net/checksum.hpp +++ b/src/core/net/checksum.hpp @@ -39,6 +39,7 @@ #include #include "common/message.hpp" +#include "net/ip4_types.hpp" #include "net/ip6_address.hpp" #include "net/ip6_headers.hpp" #include "net/socket.hpp" @@ -69,11 +70,11 @@ public: static Error VerifyMessageChecksum(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint8_t aIpProto); /** - * This static method calculates and then updates the checksum in a given message (if UDP/ICMP6). + * This static method calculates and then updates the checksum in a given message (if TCP/UDP/ICMPv6). * * @param[in,out] aMessage The message to update the checksum in. The `aMessage.GetOffset()` should point to start - * of the UDP/ICMP6 header. On exit the checksum field in UDP/ICMP6 header in the message - * is updated. + * of the TCP/UDP/ICMPv6 header. On exit the checksum field in TCP/UDP/ICMPv6 header in the + * message is updated. * @param[in] aSource The source address. * @param[in] aDestination The destination address. * @param[in] aIpProto The Internet Protocol value. @@ -84,6 +85,30 @@ public: const Ip6::Address &aDestination, uint8_t aIpProto); + /** + * This static method calculates and then updates the checksum in a given IPv4 message (if TCP/UDP/ICMP(v4)). + * + * @param[in,out] aMessage The message to update the checksum in. The `aMessage.GetOffset()` should point to start + * of the TCP/UDP/ICMP(v4) header. On exit the checksum field in TCP/UDP/ICMP(v4) header in + * the message is updated. + * @param[in] aSource The source address. + * @param[in] aDestination The destination address. + * @param[in] aIpProto The Internet Protocol value. + * + */ + static void UpdateMessageChecksum(Message & aMessage, + const Ip4::Address &aSource, + const Ip4::Address &aDestination, + uint8_t aIpProto); + + /** + * This static method calculates and then updates the checksum field in the IPv4 header. + * + * @param[in,out] aHeader The IPv4 header to update the checksum in. + * + */ + static void UpdateIp4HeaderChecksum(Ip4::Header &aHeader); + private: Checksum(void) : mValue(0) @@ -100,6 +125,10 @@ private: const Ip6::Address &aDestination, uint8_t aIpProto, const Message & aMessage); + void Calculate(const Ip4::Address &aSource, + const Ip4::Address &aDestination, + uint8_t aIpProto, + const Message & aMessage); static constexpr uint16_t kValidRxChecksum = 0xffff; diff --git a/src/core/net/dns_types.hpp b/src/core/net/dns_types.hpp index db447aa83..2fa3f1a7b 100644 --- a/src/core/net/dns_types.hpp +++ b/src/core/net/dns_types.hpp @@ -46,7 +46,7 @@ #include "common/equatable.hpp" #include "common/message.hpp" #include "crypto/ecdsa.hpp" -#include "net/ip4_address.hpp" +#include "net/ip4_types.hpp" #include "net/ip6_address.hpp" namespace ot { diff --git a/src/core/net/ip4_address.hpp b/src/core/net/ip4_address.hpp deleted file mode 100644 index fa1a013ce..000000000 --- a/src/core/net/ip4_address.hpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2021, 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 IPv4 addresses. - */ - -#ifndef IP4_ADDRESS_HPP_ -#define IP4_ADDRESS_HPP_ - -#include "openthread-core-config.h" - -#include "common/clearable.hpp" -#include "common/equatable.hpp" -#include "common/error.hpp" -#include "common/string.hpp" - -namespace ot { -namespace Ip4 { - -/** - * This class represents an IPv4 address. - * - */ -OT_TOOL_PACKED_BEGIN -class Address : public Equatable
, public Clearable
-{ -public: - static constexpr uint16_t kSize = 4; ///< Size of an IPv4 Address (in bytes). - static constexpr uint16_t kAddressStringSize = 17; ///< String size used by `ToString()`. - - /** - * This type defines the fixed-length `String` object returned from `ToString()`. - * - */ - typedef String InfoString; - - /** - * This method gets the IPv4 address as a pointer to a byte array. - * - * @returns A pointer to a byte array containing the IPv4 address. - * - */ - const uint8_t *GetBytes(void) const { return mBytes; } - - /** - * This method sets the IPv4 address from a given byte array. - * - * @param[in] aBuffer Pointer to an array containing the IPv4 address. `kSize` bytes from the buffer - * are copied to form the IPv4 address. - * - */ - void SetBytes(const uint8_t *aBuffer) { memcpy(mBytes, aBuffer, kSize); } - - /** - * This method parses an IPv4 address string. - * - * The string MUST follow the quad-dotted notation of four decimal values (ranging from 0 to 255 each). For - * example, "127.0.0.1" - * - * @param[in] aString A pointer to the null-terminated string. - * - * @retval kErrorNone Successfully parsed the IPv4 address string. - * @retval kErrorParse Failed to parse the IPv4 address string. - * - */ - Error FromString(const char *aString); - - /** - * This method converts the IPv4 address to a string. - * - * The string format uses quad-dotted notation of four bytes in the address (e.g., "127.0.0.1"). - * - * @returns An `InfoString` representing the IPv4 address. - * - */ - InfoString ToString(void) const; - -private: - uint8_t mBytes[kSize]; -} OT_TOOL_PACKED_END; - -} // namespace Ip4 -} // namespace ot - -#endif // IP4_ADDRESS_HPP_ diff --git a/src/core/net/ip4_types.cpp b/src/core/net/ip4_types.cpp new file mode 100644 index 000000000..a23f51e33 --- /dev/null +++ b/src/core/net/ip4_types.cpp @@ -0,0 +1,175 @@ +/* + * 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 IP4 headers processing. + */ + +#include "ip4_types.hpp" +#include "ip6_address.hpp" + +namespace ot { +namespace Ip4 { + +Error Address::FromString(const char *aString) +{ + constexpr char kSeperatorChar = '.'; + constexpr char kNullChar = '\0'; + + Error error = kErrorParse; + + for (uint8_t index = 0;; index++) + { + uint16_t value = 0; + uint8_t hasFirstDigit = false; + + for (char digitChar = *aString;; ++aString, digitChar = *aString) + { + if ((digitChar < '0') || (digitChar > '9')) + { + break; + } + + value = static_cast((value * 10) + static_cast(digitChar - '0')); + VerifyOrExit(value <= NumericLimits::kMax); + hasFirstDigit = true; + } + + VerifyOrExit(hasFirstDigit); + + mFields.m8[index] = static_cast(value); + + if (index == sizeof(Address) - 1) + { + break; + } + + VerifyOrExit(*aString == kSeperatorChar); + aString++; + } + + VerifyOrExit(*aString == kNullChar); + error = kErrorNone; + +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 + // after the prefix, skipping over the bits 64 to 71 (byte at `kSkipIndex`) + // which must be set to zero. The suffix is set to zero (per RFC 6502). + // + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------| + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |32| prefix |v4(32) | u | suffix | + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |40| prefix |v4(24) | u |(8)| suffix | + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |48| prefix |v4(16) | u | (16) | suffix | + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |56| prefix |(8)| u | v4(24) | suffix | + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |64| prefix | u | v4(32) | suffix | + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // |96| prefix | v4(32) | + // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + constexpr uint8_t kSkipIndex = 8; + + uint8_t ip6Index; + + OT_ASSERT(Ip6::Prefix::IsValidNat64PrefixLength(aPrefixLength)); + + ip6Index = aPrefixLength / CHAR_BIT; + + for (uint8_t i = 0; i < Ip4::Address::kSize; i++) + { + if (ip6Index == kSkipIndex) + { + ip6Index++; + } + + mFields.m8[i] = aIp6Address.GetBytes()[ip6Index++]; + } +} + +void Address::SynthesizeFromCidrAndHost(const Cidr &aCidr, const uint32_t aHost) +{ + mFields.m32 = (aCidr.mAddress.mFields.m32 & aCidr.SubnetMask()) | (HostSwap32(aHost) & aCidr.HostMask()); +} + +Address::InfoString Address::ToString(void) const +{ + InfoString string; + + string.Append("%d.%d.%d.%d", mFields.m8[0], mFields.m8[1], mFields.m8[2], mFields.m8[3]); + + return string; +} + +Cidr::InfoString Cidr::ToString(void) const +{ + InfoString string; + + string.Append("%s/%d", AsCoreType(&mAddress).ToString().AsCString(), mLength); + + return string; +} + +bool Cidr::operator==(const Cidr &aOther) const +{ + return (mLength == aOther.mLength) && + (Ip6::Prefix::MatchLength(GetBytes(), aOther.GetBytes(), Ip4::Address::kSize) >= mLength); +} + +void Cidr::Set(const uint8_t *aAddress, uint8_t aLength) +{ + memcpy(mAddress.mFields.m8, aAddress, Ip4::Address::kSize); + mLength = aLength; +} + +Error Header::ParseFrom(const Message &aMessage) +{ + Error error = kErrorParse; + + SuccessOrExit(aMessage.Read(0, *this)); + VerifyOrExit(IsValid()); + VerifyOrExit(GetTotalLength() == aMessage.GetLength()); + + error = kErrorNone; + +exit: + return error; +} + +} // namespace Ip4 +} // namespace ot diff --git a/src/core/net/ip4_types.hpp b/src/core/net/ip4_types.hpp new file mode 100644 index 000000000..042e4ca49 --- /dev/null +++ b/src/core/net/ip4_types.hpp @@ -0,0 +1,672 @@ +/* + * 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 IPv4 packet processing. + */ + +#ifndef IP4_TYPES_HPP_ +#define IP4_TYPES_HPP_ + +#include "openthread-core-config.h" + +#include + +#include + +#include "common/clearable.hpp" +#include "common/encoding.hpp" +#include "common/message.hpp" +#include "net/ip6_types.hpp" +#include "net/netif.hpp" +#include "net/socket.hpp" +#include "net/tcp6.hpp" +#include "net/udp6.hpp" + +namespace ot { + +namespace Ip6 { +// Forward declaration for ExtractFromIp4Address +class Address; +} // namespace Ip6 + +/** + * @namespace ot::Ip4 + * + * @brief + * This namespace includes definitions for IPv4 networking used by NAT64. + * + */ +namespace Ip4 { + +using Encoding::BigEndian::HostSwap16; +using Encoding::BigEndian::HostSwap32; + +using Ecn = Ip6::Ecn; + +/** + * @addtogroup core-ipv4 + * + * @brief + * This module includes definitions for the IPv4 network layer. + * + */ + +/** + * @addtogroup core-ip4-ip4 + * + * @brief + * This module includes definitions for IPv4 networking used by NAT64. + * + * @{ + * + */ + +// Forward declaration for Address::SynthesizeFromCidrAndHost +class Cidr; + +/** + * This class represents an IPv4 address. + * + */ +OT_TOOL_PACKED_BEGIN +class Address : public otIp4Address, public Equatable
, public Clearable
+{ +public: + static constexpr uint16_t kSize = 4; ///< Size of an IPv4 Address (in bytes). + static constexpr uint16_t kAddressStringSize = 17; ///< String size used by `ToString()`. + + /** + * This type defines the fixed-length `String` object returned from `ToString()`. + * + */ + typedef String InfoString; + + /** + * This method gets the IPv4 address as a pointer to a byte array. + * + * @returns A pointer to a byte array containing the IPv4 address. + * + */ + const uint8_t *GetBytes(void) const { return mFields.m8; } + + /** + * This method sets the IPv4 address from a given byte array. + * + * @param[in] aBuffer Pointer to an array containing the IPv4 address. `kSize` bytes from the buffer + * are copied to form the IPv4 address. + * + */ + void SetBytes(const uint8_t *aBuffer) { memcpy(mFields.m8, aBuffer, kSize); } + + /** + * This method sets the IPv4 address by performing NAT64 address translation from a given IPv6 address 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 The IPv6 address to translate to IPv4. + * + */ + void ExtractFromIp6Address(uint8_t aPrefixLength, const Ip6::Address &aIp6Address); + + /** + * This method sets the IPv4 address from the given CIDR and the host field. + * + * @param[in] aCidr The CIDR for the IPv4 address. + * @param[in] aHost The host bits of the IPv4 address in host byte order. The aHost will be masked by host mask. + * + */ + void SynthesizeFromCidrAndHost(const Cidr &aCidr, uint32_t aHost); + + /** + * This method parses an IPv4 address string. + * + * The string MUST follow the quad-dotted notation of four decimal values (ranging from 0 to 255 each). For + * example, "127.0.0.1" + * + * @param[in] aString A pointer to the null-terminated string. + * + * @retval kErrorNone Successfully parsed the IPv4 address string. + * @retval kErrorParse Failed to parse the IPv4 address string. + * + */ + Error FromString(const char *aString); + + /** + * This method converts the IPv4 address to a string. + * + * The string format uses quad-dotted notation of four bytes in the address (e.g., "127.0.0.1"). + * + * @returns An `InfoString` representing the IPv4 address. + * + */ + InfoString ToString(void) const; +} OT_TOOL_PACKED_END; + +/** + * This class represents an IPv4 CIDR block. + * + */ +class Cidr : public otIp4Cidr, public Unequatable, public Clearable
+{ + friend class Address; + +public: + static constexpr uint16_t kCidrSuffixSize = 3; ///< Suffix to represent CIDR (/dd). + + /** + * This type defines the fixed-length `String` object returned from `ToString()`. + * + */ + typedef String InfoString; + + /** + * This method converts the IPv4 CIDR to a string. + * + * The string format uses quad-dotted notation of four bytes in the address with the length of prefix (e.g., + * "127.0.0.1/32"). + * + * @returns An `InfoString` representing the IPv4 cidr. + * + */ + InfoString ToString(void) const; + + /** + * This method gets the prefix as a pointer to a byte array. + * + * @returns A pointer to a byte array containing the Prefix. + * + */ + const uint8_t *GetBytes(void) const { return mAddress.mFields.m8; } + + /** + * This method overloads operator `==` to evaluate whether or not two prefixes are equal. + * + * @param[in] aOther The other prefix to compare with. + * + * @retval TRUE If the two prefixes are equal. + * @retval FALSE If the two prefixes are not equal. + * + */ + bool operator==(const Cidr &aOther) const; + + /** + * This method sets the CIDR. + * + * @param[in] aAddress A pointer to buffer containing the CIDR bytes. The length of aAddress should be 4 bytes. + * @param[in] aLength The length of CIDR in bits. + * + */ + void Set(const uint8_t *aAddress, uint8_t aLength); + +private: + uint32_t HostMask(void) const + { + // Note: Using LL suffix to make it a uint64 since /32 is a valid CIDR, and right shifting 32 bits is undefined + // for uint32. + return HostSwap32(0xffffffffLL >> mLength); + } + + uint32_t SubnetMask(void) const { return ~HostMask(); } +}; + +/** + * This class implements IPv4 header generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class Header : public Clearable
+{ +public: + static constexpr uint8_t kVersionIhlOffset = 0; + static constexpr uint8_t kTrafficClassOffset = 1; + static constexpr uint8_t kTotalLengthOffset = 2; + static constexpr uint8_t kIdenficationOffset = 4; + static constexpr uint8_t kFlagsFragmentOffset = 6; + static constexpr uint8_t kTtlOffset = 8; + static constexpr uint8_t kProtocolOffset = 9; + static constexpr uint8_t kHeaderChecksumOffset = 10; + static constexpr uint8_t kSourceAddressOffset = 12; + static constexpr uint8_t kDestinationAddressOffset = 16; + + /** + * This method indicates whether or not the header appears to be well-formed. + * + * @retval TRUE If the header appears to be well-formed. + * @retval FALSE If the header does not appear to be well-formed. + * + */ + bool IsValid(void) const { return IsVersion4(); } + + /** + * This method initializes the Version to 4 and sets Traffic Class and Flow fields to zero. + * + * The other fields in the IPv4 header remain unchanged. + * + */ + void InitVersionIhl(void) { SetVersionIhl(kVersIhlInit); } + + /** + * This method sets the version and Ihl of the IPv4 header. + * + * @param[in] aVersionIhl The octet for the version and Ihl field. + * + */ + void SetVersionIhl(uint8_t aVersionIhl) { mVersIhl = aVersionIhl; } + + /** + * This method indicates whether or not the IPv4 Version is set to 6. + * + * @retval TRUE If the IPv4 Version is set to 4. + * @retval FALSE If the IPv4 Version is not set to 4. + * + */ + bool IsVersion4(void) const { return (mVersIhl & kVersionMask) == kVersion4; } + + /** + * This method returns the octet for DSCP + ECN. + * + * @retval The octet for DSCP and ECN. + * + */ + uint8_t GetDscpEcn(void) const { return mDscpEcn; } + + /** + * This method gets the 6-bit Differentiated Services Code Point (DSCP) from Traffic Class field. + * + * @returns The DSCP value. + * + */ + uint8_t GetDscp(void) const { return (mDscpEcn & kDscpMask) >> kDscpOffset; } + + /** + * This method sets 6-bit Differentiated Services Code Point (DSCP) in IPv4 header. + * + * @param[in] aDscp The DSCP value. + * + */ + void SetDscp(uint8_t aDscp) { mDscpEcn = static_cast((mDscpEcn & ~kDscpMask) | (aDscp << kDscpOffset)); } + + /** + * This method gets the 2-bit Explicit Congestion Notification (ECN) from Traffic Class field. + * + * @returns The ECN value. + * + */ + Ecn GetEcn(void) const { return static_cast(mDscpEcn & kEcnMask); } + + /** + * This method sets the 2-bit Explicit Congestion Notification (ECN) in IPv4 header.. + * + * @param[in] aEcn The ECN value. + * + */ + void SetEcn(Ecn aEcn) { mDscpEcn = ((mDscpEcn & ~kEcnMask) | aEcn); } + + /** + * This method returns the IPv4 Payload Length value. + * + * @returns The IPv4 Payload Length value. + * + */ + uint16_t GetTotalLength(void) const { return HostSwap16(mTotalLength); } + + /** + * This method sets the IPv4 Payload Length value. + * + * @param[in] aLength The IPv4 Payload Length value. + * + */ + void SetTotalLength(uint16_t aLength) { mTotalLength = HostSwap16(aLength); } + + /** + * This method returns the IPv4 payload protocol. + * + * @returns The IPv4 payload protocol value. + * + */ + uint8_t GetProtocol(void) const { return mProtocol; } + + /** + * This method sets the IPv4 payload protocol. + * + * @param[in] aProtocol The IPv4 payload protocol. + * + */ + void SetProtocol(uint8_t aProtocol) { mProtocol = aProtocol; } + + /** + * This method returns the IPv4 header checksum, the checksum is in host endian. + * + * @returns The checksum field in the IPv4 header. + * + */ + uint16_t GetChecksum(void) const { return HostSwap16(mHeaderChecksum); } + + /** + * This method sets the IPv4 header checksum, the checksum is in host endian. + * + * @param[in] aChecksum The checksum for the IPv4 header. + * + */ + void SetChecksum(uint16_t aChecksum) { mHeaderChecksum = HostSwap16(aChecksum); } + + /** + * This method returns the IPv4 Identification value. + * + * @returns The IPv4 Identification value. + * + */ + uint16_t GetIdentification(void) const { return HostSwap16(mIdentification); } + + /** + * This method sets the IPv4 Identification value. + * + * @param[in] aIdentification The IPv4 Identification value. + * + */ + void SetIdentification(uint16_t aIdentification) { mIdentification = HostSwap16(aIdentification); } + + /** + * This method returns the IPv4 Time-to-Live value. + * + * @returns The IPv4 Time-to-Live value. + * + */ + uint8_t GetTtl(void) const { return mTtl; } + + /** + * This method sets the IPv4 Time-to-Live value. + * + * @param[in] aTtl The IPv4 Time-to-Live value. + * + */ + void SetTtl(uint8_t aTtl) { mTtl = aTtl; } + + /** + * This method returns the IPv4 Source address. + * + * @returns A reference to the IPv4 Source address. + * + */ + Address &GetSource(void) { return mSource; } + + /** + * This method returns the IPv4 Source address. + * + * @returns A reference to the IPv4 Source address. + * + */ + const Address &GetSource(void) const { return mSource; } + + /** + * This method sets the IPv4 Source address. + * + * @param[in] aSource A reference to the IPv4 Source address. + * + */ + void SetSource(const Address &aSource) { mSource = aSource; } + + /** + * This method returns the IPv4 Destination address. + * + * @returns A reference to the IPv4 Destination address. + * + */ + Address &GetDestination(void) { return mDestination; } + + /** + * This method returns the IPv4 Destination address. + * + * @returns A reference to the IPv4 Destination address. + * + */ + const Address &GetDestination(void) const { return mDestination; } + + /** + * This method sets the IPv4 Destination address. + * + * @param[in] aDestination A reference to the IPv4 Destination address. + * + */ + void SetDestination(const Address &aDestination) { mDestination = aDestination; } + + /** + * This method parses and validates the IPv4 header from a given message. + * + * The header is read from @p aMessage at offset zero. + * + * @param[in] aMessage The IPv4 message. + * + * @retval kErrorNone Successfully parsed the IPv4 header from @p aMessage. + * @retval kErrorParse Malformed IPv4 header or message (e.g., message does not contained expected payload length). + * + */ + Error ParseFrom(const Message &aMessage); + + /** + * This method returns the Df flag in the IPv4 header. + * + * @returns Whether don't fragment flag is set. + * + */ + bool GetDf(void) const { return HostSwap16(mFlagsFargmentOffset) & kFlagsDf; } + + /** + * This method returns the Mf flag in the IPv4 header. + * + * @returns Whether more fragments flag is set. + * + */ + bool GetMf(void) const { return HostSwap16(mFlagsFargmentOffset) & kFlagsMf; } + + /** + * This method returns the fragment offset in the IPv4 header. + * + * @returns The fragment offset of the IPv4 packet. + * + */ + uint16_t GetFragmentOffset(void) const { return HostSwap16(mFlagsFargmentOffset) & kFragmentOffsetMask; } + +private: + // IPv4 header + // + // +---------------+---------------+---------------+---------------+ + // |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |Version| IHL | DSCP |ECN| Total Length | + // | Identification |Flags| Fragment Offset | + // | TTL | Protocol | Header Checksum | + // | Source IP Address | + // | Dest IP Address | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + static constexpr uint8_t kVersion4 = 0x40; // Use with `mVersIhl` + static constexpr uint8_t kVersionMask = 0xf0; // Use with `mVersIhl` + static constexpr uint8_t kIhlMask = 0x0f; // Use with `mVersIhl` + static constexpr uint8_t kDscpOffset = 2; // Use with `mDscpEcn` + static constexpr uint16_t kDscpMask = 0xfc; // Use with `mDscpEcn` + static constexpr uint8_t kEcnOffset = 0; // Use with `mDscpEcn` + static constexpr uint8_t kEcnMask = 0x03; // Use with `mDscpEcn` + static constexpr uint16_t kFlagsMask = 0xe000; // Use with `mFlagsFragmentOffset` + static constexpr uint16_t kFlagsDf = 0x4000; // Use with `mFlagsFragmentOffset` + static constexpr uint16_t kFlagsMf = 0x2000; // Use with `mFlagsFragmentOffset` + static constexpr uint16_t kFragmentOffsetMask = 0x1fff; // Use with `mFlagsFragmentOffset` + static constexpr uint32_t kVersIhlInit = 0x45; // Version 4, Header length = 5x8 bytes. + + uint8_t mVersIhl; + uint8_t mDscpEcn; + uint16_t mTotalLength; + uint16_t mIdentification; + uint16_t mFlagsFargmentOffset; + uint8_t mTtl; + uint8_t mProtocol; + uint16_t mHeaderChecksum; + Address mSource; + Address mDestination; +} OT_TOOL_PACKED_END; + +/** + * This class implements ICMP(v4). + * Note: ICMP(v4) messages will only be generated / handled by NAT64. So only header defination is required. + * + */ +class Icmp +{ +public: + /** + * This class represents an IPv4 ICMP header. + * + */ + OT_TOOL_PACKED_BEGIN + class Header : public Clearable
+ { + public: + static constexpr uint16_t kChecksumFieldOffset = 2; + // A few ICMP types, only the ICMP types work with NAT64 are listed here. + enum Type : uint8_t + { + kTypeEchoReply = 0, + kTypeDestinationUnreachable = 3, + kTypeEchoRequest = 8, + kTypeTimeExceeded = 11, + }; + + enum Code : uint8_t + { + kCodeNone = 0, + // Destination Unreachable codes + kCodeNetworkUnreachable = 0, + kCodeHostUnreachable = 1, + kCodeProtocolUnreachable = 2, + kCodePortUnreachable = 3, + kCodeSourceRouteFailed = 5, + kCodeNetworkUnknown = 6, + kCodeHostUnknown = 7, + }; + + /** + * This method returns the type of the ICMP message. + * + * @returns The type field of the ICMP message. + * + */ + uint8_t GetType(void) const { return mType; } + + /** + * This method sets the type of the ICMP message. + * + * @param[in] aType The type of the ICMP message. + * + */ + void SetType(uint8_t aType) { mType = aType; } + + /** + * This method returns the code of the ICMP message. + * + * @returns The code field of the ICMP message. + * + */ + uint8_t GetCode(void) const { return mCode; } + + /** + * This method sets the code of the ICMP message. + * + * @param[in] aCode The code of the ICMP message. + * + */ + void SetCode(uint8_t aCode) { mCode = aCode; } + + /** + * This method sets the checksum field in the ICMP message. + * + * @returns The checksum of the ICMP message. + * + */ + uint16_t GetChecksum(void) const { return HostSwap16(mChecksum); } + + /** + * This method sets the checksum field in the ICMP message. + * + * @param[in] aChecksum The checksum of the ICMP message. + * + */ + void SetChecksum(uint16_t aChecksum) { mChecksum = HostSwap16(aChecksum); } + + /** + * This method returns the rest of header field in the ICMP message. + * + * @returns The rest of header field in the ICMP message. The returned buffer has 4 octets. + * + */ + const uint8_t *GetRestOfHeader(void) const { return mRestOfHeader; } + + /** + * This method sets the rest of header field in the ICMP message. + * + * @param[in] aRestOfHeader The rest of header field in the ICMP message. The buffer should have 4 octets. + * + */ + void SetRestOfHeader(const uint8_t *aRestOfheader) + { + memcpy(mRestOfHeader, aRestOfheader, sizeof(mRestOfHeader)); + } + + private: + uint8_t mType; + uint8_t mCode; + uint16_t mChecksum; + uint8_t mRestOfHeader[4]; + } OT_TOOL_PACKED_END; +}; + +// Internet Protocol Numbers +static constexpr uint8_t kProtoTcp = Ip6::kProtoTcp; ///< Transmission Control Protocol +static constexpr uint8_t kProtoUdp = Ip6::kProtoUdp; ///< User Datagram +static constexpr uint8_t kProtoIcmp = 1; ///< ICMP for IPv4 + +using Tcp = Ip6::Tcp; // TCP in IPv4 is the same as TCP in IPv6 +using Udp = Ip6::Udp; // UDP in IPv4 is the same as UDP in IPv6 + +/** + * @} + * + */ + +} // namespace Ip4 + +DefineCoreType(otIp4Address, Ip4::Address); +DefineCoreType(otIp4Cidr, Ip4::Cidr); + +} // namespace ot + +#endif // IP4_TYPES_HPP_ diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp index 5708146a0..7f50c941c 100644 --- a/src/core/net/ip6_address.cpp +++ b/src/core/net/ip6_address.cpp @@ -42,7 +42,7 @@ #include "common/instance.hpp" #include "common/numeric_limits.hpp" #include "common/random.hpp" -#include "net/ip4_address.hpp" +#include "net/ip4_types.hpp" #include "net/netif.hpp" using ot::Encoding::BigEndian::HostSwap32; @@ -124,10 +124,10 @@ uint8_t Prefix::MatchLength(const uint8_t *aPrefixA, const uint8_t *aPrefixB, ui return matchedLength; } -bool Prefix::IsValidNat64(void) const +bool Prefix::IsValidNat64PrefixLength(uint8_t aLength) { - return (mLength == 32) || (mLength == 40) || (mLength == 48) || (mLength == 56) || (mLength == 64) || - (mLength == 96); + return (aLength == 32) || (aLength == 40) || (aLength == 48) || (aLength == 56) || (aLength == 64) || + (aLength == 96); } Prefix::InfoString Prefix::ToString(void) const diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 3c55f2190..064e282b6 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -46,11 +46,16 @@ #include "common/equatable.hpp" #include "common/string.hpp" #include "mac/mac_types.hpp" -#include "net/ip4_address.hpp" using ot::Encoding::BigEndian::HostSwap16; namespace ot { + +namespace Ip4 { +// Forward declaration for SynthesizeFromIp4Address +class Address; +} // namespace Ip4 + namespace Ip6 { /** @@ -301,6 +306,19 @@ public: */ static uint8_t MatchLength(const uint8_t *aPrefixA, const uint8_t *aPrefixB, uint8_t aMaxSize); + /** + * This method indicates whether or not a given prefix length is valid for use as a NAT64 prefix. + * + * A NAT64 prefix must have one of the following lengths: 32, 40, 48, 56, 64, or 96 (per RFC 6502). + * + * @param[in] aLength The length of the prefix. + * + * @retval TRUE If the prefix has a valid length for use as a NAT64 prefix. + * @retval FALSE If the prefix does not have a valid length for use as a NAT64 prefix. + * + */ + static bool IsValidNat64PrefixLength(uint8_t aLength); + /** * This method indicates whether or not the prefix has a valid length for use as a NAT64 prefix. * @@ -310,7 +328,7 @@ public: * @retval FALSE If the prefix does not have a valid length for use as a NAT64 prefix. * */ - bool IsValidNat64(void) const; + bool IsValidNat64(void) const { return IsValidNat64PrefixLength(mLength); } /** * This method converts the prefix to a string. diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a7d58ed79..6d7b62dea 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -431,6 +431,27 @@ target_link_libraries(ot-test-hmac-sha256 add_test(NAME ot-test-hmac-sha256 COMMAND ot-test-hmac-sha256) +add_executable(ot-test-ip4-header + test_ip4_header.cpp +) + +target_include_directories(ot-test-ip4-header + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-ip4-header + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-ip4-header + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-ip4-header COMMAND ot-test-ip4-header) + add_executable(ot-test-ip6-header test_ip6_header.cpp ) diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index df54384bd..2063dedd4 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -128,6 +128,7 @@ check_PROGRAMS += \ ot-test-heap-string \ ot-test-hkdf-sha256 \ ot-test-hmac-sha256 \ + ot-test-ip4-header \ ot-test-ip6-header \ ot-test-ip-address \ ot-test-link-quality \ @@ -262,6 +263,10 @@ ot_test_hmac_sha256_LDADD = $(COMMON_LDADD) ot_test_hmac_sha256_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) ot_test_hmac_sha256_SOURCES = $(COMMON_SOURCES) test_hmac_sha256.cpp +ot_test_ip4_header_LDADD = $(COMMON_LDADD) +ot_test_ip4_header_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) +ot_test_ip4_header_SOURCES = $(COMMON_SOURCES) test_ip4_header.cpp + ot_test_ip6_header_LDADD = $(COMMON_LDADD) ot_test_ip6_header_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) ot_test_ip6_header_SOURCES = $(COMMON_SOURCES) test_ip6_header.cpp diff --git a/tests/unit/test_checksum.cpp b/tests/unit/test_checksum.cpp index 28b834e47..ebf3209d3 100644 --- a/tests/unit/test_checksum.cpp +++ b/tests/unit/test_checksum.cpp @@ -32,6 +32,7 @@ #include "common/random.hpp" #include "net/checksum.hpp" #include "net/icmp6.hpp" +#include "net/ip4_types.hpp" #include "net/udp6.hpp" #include "test_platform.h" @@ -75,11 +76,7 @@ uint16_t CalculateChecksum(const Ip6::Address &aSource, const Message & aMessage) { // This method calculates the checksum over an IPv6 message. - - enum : uint16_t - { - kMaxPayload = 1024, - }; + constexpr uint16_t kMaxPayload = 1024; OT_TOOL_PACKED_BEGIN struct PseudoHeader @@ -112,6 +109,45 @@ uint16_t CalculateChecksum(const Ip6::Address &aSource, return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength); } +uint16_t CalculateChecksum(const Ip4::Address &aSource, + const Ip4::Address &aDestination, + uint8_t aIpProto, + const Message & aMessage) +{ + // This method calculates the checksum over an IPv4 message. + constexpr uint16_t kMaxPayload = 1024; + + OT_TOOL_PACKED_BEGIN + struct PseudoHeader + { + Ip4::Address mSource; + Ip4::Address mDestination; + uint16_t mPayloadLength; + uint16_t mProtocol; + } OT_TOOL_PACKED_END; + + OT_TOOL_PACKED_BEGIN + struct ChecksumData + { + PseudoHeader mPseudoHeader; + uint8_t mPayload[kMaxPayload]; + } OT_TOOL_PACKED_END; + + ChecksumData data; + uint16_t payloadLength; + + payloadLength = aMessage.GetLength() - aMessage.GetOffset(); + + data.mPseudoHeader.mSource = aSource; + data.mPseudoHeader.mDestination = aDestination; + data.mPseudoHeader.mProtocol = Encoding::BigEndian::HostSwap16(aIpProto); + data.mPseudoHeader.mPayloadLength = Encoding::BigEndian::HostSwap16(payloadLength); + + SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), data.mPayload, payloadLength)); + + return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength); +} + void CorruptMessage(Message &aMessage) { // Change a random bit in the message. @@ -133,11 +169,8 @@ void CorruptMessage(Message &aMessage) void TestUdpMessageChecksum(void) { - enum : uint16_t - { - kMinSize = sizeof(Ip6::Udp::Header), - kMaxSize = kBufferSize * 3 + 24, - }; + constexpr uint16_t kMinSize = sizeof(Ip6::Udp::Header); + constexpr uint16_t kMaxSize = kBufferSize * 3 + 24; const char *kSourceAddress = "fd00:1122:3344:5566:7788:99aa:bbcc:ddee"; const char *kDestAddress = "fd01:2345:6789:abcd:ef01:2345:6789:abcd"; @@ -204,11 +237,8 @@ void TestUdpMessageChecksum(void) void TestIcmp6MessageChecksum(void) { - enum : uint16_t - { - kMinSize = sizeof(Ip6::Icmp::Header), - kMaxSize = kBufferSize * 3 + 24, - }; + constexpr uint16_t kMinSize = sizeof(Ip6::Icmp::Header); + constexpr uint16_t kMaxSize = kBufferSize * 3 + 24; const char *kSourceAddress = "fd00:feef:dccd:baab:9889:7667:5444:3223"; const char *kDestAddress = "fd01:abab:beef:cafe:1234:5678:9abc:0"; @@ -274,6 +304,149 @@ void TestIcmp6MessageChecksum(void) } } +void TestTcp4MessageChecksum(void) +{ + constexpr size_t kMinSize = sizeof(Ip4::Tcp::Header); + constexpr size_t kMaxSize = kBufferSize * 3 + 24; + + const char *kSourceAddress = "12.34.56.78"; + const char *kDestAddress = "87.65.43.21"; + + Ip4::Address sourceAddress; + Ip4::Address destAddress; + + Instance *instance = static_cast(testInitInstance()); + + VerifyOrQuit(instance != nullptr); + + SuccessOrQuit(sourceAddress.FromString(kSourceAddress)); + SuccessOrQuit(destAddress.FromString(kDestAddress)); + + for (uint16_t size = kMinSize; size <= kMaxSize; size++) + { + Message * message = instance->Get().NewMessage(sizeof(Ip4::Tcp::Header)); + Ip4::Tcp::Header tcpHeader; + + VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed"); + SuccessOrQuit(message->SetLength(size)); + + // Write TCP header with a random payload. + + Random::NonCrypto::FillBuffer(reinterpret_cast(&tcpHeader), sizeof(tcpHeader)); + message->Write(0, tcpHeader); + + if (size > sizeof(tcpHeader)) + { + uint8_t buffer[kMaxSize]; + uint16_t payloadSize = size - sizeof(tcpHeader); + + Random::NonCrypto::FillBuffer(buffer, payloadSize); + message->WriteBytes(sizeof(tcpHeader), &buffer[0], payloadSize); + } + + // Verify that the `Checksum::UpdateMessageChecksum` correctly + // updates the checksum field in the UDP header on the message. + + Checksum::UpdateMessageChecksum(*message, sourceAddress, destAddress, Ip4::kProtoTcp); + + SuccessOrQuit(message->Read(message->GetOffset(), tcpHeader)); + VerifyOrQuit(tcpHeader.GetChecksum() != 0); + + // Verify that the calculated UDP checksum is valid. + + VerifyOrQuit(CalculateChecksum(sourceAddress, destAddress, Ip4::kProtoTcp, *message) == 0xffff); + message->Free(); + } +} + +void TestUdp4MessageChecksum(void) +{ + constexpr uint16_t kMinSize = sizeof(Ip4::Udp::Header); + constexpr uint16_t kMaxSize = kBufferSize * 3 + 24; + + const char *kSourceAddress = "12.34.56.78"; + const char *kDestAddress = "87.65.43.21"; + + Ip4::Address sourceAddress; + Ip4::Address destAddress; + + Instance *instance = static_cast(testInitInstance()); + + SuccessOrQuit(sourceAddress.FromString(kSourceAddress)); + SuccessOrQuit(destAddress.FromString(kDestAddress)); + + VerifyOrQuit(instance != nullptr); + + for (uint16_t size = kMinSize; size <= kMaxSize; size++) + { + Message * message = instance->Get().NewMessage(sizeof(Ip4::Udp::Header)); + Ip4::Udp::Header udpHeader; + + VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed"); + SuccessOrQuit(message->SetLength(size)); + + // Write UDP header with a random payload. + + Random::NonCrypto::FillBuffer(reinterpret_cast(&udpHeader), sizeof(udpHeader)); + udpHeader.SetChecksum(0); + message->Write(0, udpHeader); + + if (size > sizeof(udpHeader)) + { + uint8_t buffer[kMaxSize]; + uint16_t payloadSize = size - sizeof(udpHeader); + + Random::NonCrypto::FillBuffer(buffer, payloadSize); + message->WriteBytes(sizeof(udpHeader), &buffer[0], payloadSize); + } + + // Verify that the `Checksum::UpdateMessageChecksum` correctly + // updates the checksum field in the UDP header on the message. + + Checksum::UpdateMessageChecksum(*message, sourceAddress, destAddress, Ip4::kProtoUdp); + + SuccessOrQuit(message->Read(message->GetOffset(), udpHeader)); + VerifyOrQuit(udpHeader.GetChecksum() != 0); + + // Verify that the calculated UDP checksum is valid. + + VerifyOrQuit(CalculateChecksum(sourceAddress, destAddress, Ip4::kProtoUdp, *message) == 0xffff); + message->Free(); + } +} + +void TestIcmp4MessageChecksum(void) +{ + // A captured ICMP echo request (ping) message. Checksum field is set to zero. + const uint8_t kExampleIcmpMessage[] = "\x08\x00\x00\x00\x67\x2e\x00\x00\x62\xaf\xf1\x61\x00\x04\xfc\x24" + "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17" + "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27" + "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37"; + uint16_t kChecksumForExampleMessage = 0x5594; + Instance *instance = static_cast(testInitInstance()); + Message * message = instance->Get().NewMessage(sizeof(kExampleIcmpMessage)); + + Ip4::Address source; + Ip4::Address dest; + + uint8_t mPayload[sizeof(kExampleIcmpMessage)]; + Ip4::Icmp::Header icmpHeader; + + message->AppendBytes(kExampleIcmpMessage, sizeof(kExampleIcmpMessage)); + + // Random IPv4 address, ICMP message checksum does not include a presudo header like TCP and UDP. + source.mFields.m32 = 0x12345678; + dest.mFields.m32 = 0x87654321; + + Checksum::UpdateMessageChecksum(*message, source, dest, Ip4::kProtoIcmp); + + message->Read(0, icmpHeader); + VerifyOrQuit(icmpHeader.GetChecksum() == kChecksumForExampleMessage); + + SuccessOrQuit(message->Read(message->GetOffset(), mPayload, sizeof(mPayload))); + VerifyOrQuit(CalculateChecksum(mPayload, sizeof(mPayload)) == 0xffff); +} + class ChecksumTester { public: @@ -300,6 +473,9 @@ int main(void) ot::ChecksumTester::TestExampleVector(); ot::TestUdpMessageChecksum(); ot::TestIcmp6MessageChecksum(); + ot::TestTcp4MessageChecksum(); + ot::TestUdp4MessageChecksum(); + ot::TestIcmp4MessageChecksum(); printf("All tests passed\n"); return 0; } diff --git a/tests/unit/test_ip4_header.cpp b/tests/unit/test_ip4_header.cpp new file mode 100644 index 000000000..e9021a820 --- /dev/null +++ b/tests/unit/test_ip4_header.cpp @@ -0,0 +1,132 @@ +/* + * 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 "common/encoding.hpp" +#include "net/ip4_types.hpp" + +#include "test_util.hpp" + +using ot::Encoding::BigEndian::ReadUint16; + +namespace ot { +namespace Ip4 { + +void VerifyEcnDscp(const Header &aHeader, uint8_t aDscp, Ecn aEcn) +{ + uint8_t expectedDscpEcn = static_cast((aDscp << 2) + aEcn); + + printf("%02x {dscp:%d, ecn:%d}\n", aHeader.GetDscpEcn(), aHeader.GetDscp(), aHeader.GetEcn()); + + VerifyOrQuit(aHeader.GetDscp() == aDscp); + VerifyOrQuit(aHeader.GetEcn() == aEcn); + VerifyOrQuit(aHeader.GetDscpEcn() == expectedDscpEcn); +} + +void TestIp4Header(void) +{ + static constexpr uint16_t kTotalLength = 84; + static constexpr uint8_t kTtl = 64; + + const uint8_t kDscps[] = {0x0, 0x1, 0x3, 0xf, 0x30, 0x2f, 0x3f}; + const Ecn kEcns[] = {Ecn::kEcnNotCapable, Ecn::kEcnCapable0, Ecn::kEcnCapable1, Ecn::kEcnMarked}; + const uint8_t kExampleIp4Header[] = "\x45\x00\x00\x54\x23\xed\x00\x00\x40\x01\x41\xd1\x0a\x00\x00\xeb" + "\x0a\x00\x00\x01"; + + Header header; + Address source; + Address destination; + const uint8_t *headerBytes = reinterpret_cast(&header); + + SuccessOrQuit(source.FromString("10.0.0.235"), "Address::FromString() failed"); + SuccessOrQuit(destination.FromString("10.0.0.1"), "Address::FromString() failed"); + + header.InitVersionIhl(); + + header.Clear(); + header.InitVersionIhl(); + VerifyOrQuit(header.IsValid()); + VerifyOrQuit(header.GetTotalLength() == 0); + VerifyOrQuit(header.GetProtocol() == 0); + VerifyOrQuit(header.GetTtl() == 0); + VerifyOrQuit(header.GetSource().mFields.m32 == 0); + VerifyOrQuit(header.GetDestination().mFields.m32 == 0); + VerifyOrQuit(header.GetFragmentOffset() == 0); + + header.SetTotalLength(kTotalLength); + header.SetProtocol(Ip4::kProtoIcmp); + header.SetTtl(kTtl); + header.SetSource(source); + header.SetDestination(destination); + + VerifyOrQuit(header.IsValid()); + VerifyOrQuit(header.GetProtocol() == kProtoIcmp); + VerifyOrQuit(header.GetTtl() == kTtl); + VerifyOrQuit(header.GetSource() == source); + VerifyOrQuit(header.GetDestination() == destination); + + // Verify the offsets to different fields. + + VerifyOrQuit(ReadUint16(headerBytes + Header::kTotalLengthOffset) == kTotalLength, "kTotalLength is incorrect"); + VerifyOrQuit(headerBytes[Header::kProtocolOffset] == kProtoIcmp, "kProtocol is incorrect"); + VerifyOrQuit(headerBytes[Header::kTtlOffset] == kTtl, "kTtl is incorrect"); + VerifyOrQuit(memcmp(&headerBytes[Header::kSourceAddressOffset], &source, sizeof(source)) == 0, + "Source address is incorrect"); + VerifyOrQuit(memcmp(&headerBytes[Header::kDestinationAddressOffset], &destination, sizeof(destination)) == 0, + "Destination address is incorrect"); + + for (uint8_t dscp : kDscps) + { + for (Ecn ecn : kEcns) + { + printf("Expecting {dscp:%-2d, ecn:%d} => ", dscp, ecn); + header.SetEcn(ecn); + header.SetDscp(dscp); + VerifyEcnDscp(header, dscp, ecn); + } + } + + memcpy(&header, kExampleIp4Header, sizeof(header)); + VerifyOrQuit(header.IsValid()); + VerifyOrQuit(memcmp(&headerBytes[Header::kSourceAddressOffset], &source, sizeof(source)) == 0, + "Source address is incorrect"); + VerifyOrQuit(memcmp(&headerBytes[Header::kDestinationAddressOffset], &destination, sizeof(destination)) == 0, + "Destination address is incorrect"); + VerifyOrQuit(ReadUint16(headerBytes + Header::kTotalLengthOffset) == kTotalLength, "kTotalLength is incorrect"); + VerifyOrQuit(headerBytes[Header::kProtocolOffset] == kProtoIcmp, "kProtocol is incorrect"); + VerifyOrQuit(headerBytes[Header::kTtlOffset] == kTtl, "kTtl is incorrect"); +} + +} // namespace Ip4 +} // namespace ot + +int main(void) +{ + ot::Ip4::TestIp4Header(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/unit/test_ip_address.cpp b/tests/unit/test_ip_address.cpp index 4ad860d99..cea514742 100644 --- a/tests/unit/test_ip_address.cpp +++ b/tests/unit/test_ip_address.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, The OpenThread Authors. + * Copyright (c) 2019-2022, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,7 +28,8 @@ #include -#include "net/ip4_address.hpp" +#include "common/encoding.hpp" +#include "net/ip4_types.hpp" #include "net/ip6_address.hpp" #include "test_util.h" @@ -444,6 +445,73 @@ void TestIp4Ip6Translation(void) VerifyOrQuit(address == expectedAddress, "Ip6::SynthesizeFromIp4Address() failed"); } + + for (const TestCase &testCase : kTestCases) + { + const ot::Ip4::Address expectedAddress = ip4Address; + ot::Ip4::Address address; + ot::Ip6::Address ip6Address; + + SuccessOrQuit(ip6Address.FromString(testCase.mIp6Address)); + + address.ExtractFromIp6Address(testCase.mLength, ip6Address); + + printf("Ipv6Address: %-36s IPv4Addr: %-12s Expected: %s\n", testCase.mIp6Address, + address.ToString().AsCString(), expectedAddress.ToString().AsCString()); + + VerifyOrQuit(address == expectedAddress, "Ip4::ExtractFromIp6Address() failed"); + } +} + +void TestIp4Cidr(void) +{ + using ot::Encoding::BigEndian::HostSwap32; + struct TestCase + { + const char * mNetwork; + const uint8_t mLength; + const uint32_t mHost; + const char * mOutcome; + }; + + const TestCase kTestCases[] = { + {"172.16.12.34", 32, 0x12345678, "172.16.12.34"}, {"172.16.12.34", 31, 0x12345678, "172.16.12.34"}, + {"172.16.12.34", 30, 0x12345678, "172.16.12.32"}, {"172.16.12.34", 29, 0x12345678, "172.16.12.32"}, + {"172.16.12.34", 28, 0x12345678, "172.16.12.40"}, {"172.16.12.34", 27, 0x12345678, "172.16.12.56"}, + {"172.16.12.34", 26, 0x12345678, "172.16.12.56"}, {"172.16.12.34", 25, 0x12345678, "172.16.12.120"}, + {"172.16.12.34", 24, 0x12345678, "172.16.12.120"}, {"172.16.12.34", 23, 0x12345678, "172.16.12.120"}, + {"172.16.12.34", 22, 0x12345678, "172.16.14.120"}, {"172.16.12.34", 21, 0x12345678, "172.16.14.120"}, + {"172.16.12.34", 20, 0x12345678, "172.16.6.120"}, {"172.16.12.34", 19, 0x12345678, "172.16.22.120"}, + {"172.16.12.34", 18, 0x12345678, "172.16.22.120"}, {"172.16.12.34", 17, 0x12345678, "172.16.86.120"}, + {"172.16.12.34", 16, 0x12345678, "172.16.86.120"}, {"172.16.12.34", 15, 0x12345678, "172.16.86.120"}, + {"172.16.12.34", 14, 0x12345678, "172.16.86.120"}, {"172.16.12.34", 13, 0x12345678, "172.20.86.120"}, + {"172.16.12.34", 12, 0x12345678, "172.20.86.120"}, {"172.16.12.34", 11, 0x12345678, "172.20.86.120"}, + {"172.16.12.34", 10, 0x12345678, "172.52.86.120"}, {"172.16.12.34", 9, 0x12345678, "172.52.86.120"}, + {"172.16.12.34", 8, 0x12345678, "172.52.86.120"}, {"172.16.12.34", 7, 0x12345678, "172.52.86.120"}, + {"172.16.12.34", 6, 0x12345678, "174.52.86.120"}, {"172.16.12.34", 5, 0x12345678, "170.52.86.120"}, + {"172.16.12.34", 4, 0x12345678, "162.52.86.120"}, {"172.16.12.34", 3, 0x12345678, "178.52.86.120"}, + {"172.16.12.34", 2, 0x12345678, "146.52.86.120"}, {"172.16.12.34", 1, 0x12345678, "146.52.86.120"}, + {"172.16.12.34", 0, 0x12345678, "18.52.86.120"}, + }; + + for (const TestCase &testCase : kTestCases) + { + ot::Ip4::Address network; + ot::Ip4::Cidr cidr; + ot::Ip4::Address generated; + + network.FromString(testCase.mNetwork); + cidr.mAddress = network; + cidr.mLength = testCase.mLength; + + generated.SynthesizeFromCidrAndHost(cidr, testCase.mHost); + + printf("CIDR: %-18s HostID: %-8x Host: %-14s Expected: %s\n", cidr.ToString().AsCString(), testCase.mHost, + generated.ToString().AsCString(), testCase.mOutcome); + + VerifyOrQuit(strcmp(generated.ToString().AsCString(), testCase.mOutcome) == 0, + "Ip4::Address::SynthesizeFromCidrAndHost() failed"); + } } int main(void) @@ -453,6 +521,7 @@ int main(void) TestIp6AddressFromString(); TestIp6Prefix(); TestIp4Ip6Translation(); + TestIp4Cidr(); printf("All tests passed\n"); return 0; }