diff --git a/src/core/common/linked_list.hpp b/src/core/common/linked_list.hpp index c51d8a394..25e57b5b2 100644 --- a/src/core/common/linked_list.hpp +++ b/src/core/common/linked_list.hpp @@ -546,6 +546,23 @@ public: */ Type *GetTail(void) { return AsNonConst(AsConst(this)->GetTail()); } + /** + * Counts and returns the number of entries in the linked list. + * + * @returns The number of entries in the linked list. + */ + uint32_t CountAllEntries(void) const + { + uint32_t count = 0; + + for (const Type *entry = mHead; entry != nullptr; entry = entry->GetNext()) + { + count++; + } + + return count; + } + // The following methods are intended to support range-based `for` // loop iteration over the linked-list entries and should not be // used directly. diff --git a/src/core/net/ip4_types.hpp b/src/core/net/ip4_types.hpp index 30a35296f..0b9c77b97 100644 --- a/src/core/net/ip4_types.hpp +++ b/src/core/net/ip4_types.hpp @@ -238,6 +238,13 @@ public: */ InfoString ToString(void) const; + /** + * Gets the CIDR length (in bits). + * + * @returns The CIDR length. + */ + uint8_t GetLength(void) const { return mLength; } + /** * Gets the prefix as a pointer to a byte array. * diff --git a/src/core/net/nat64_translator.cpp b/src/core/net/nat64_translator.cpp index f356a47cc..092159f6f 100644 --- a/src/core/net/nat64_translator.cpp +++ b/src/core/net/nat64_translator.cpp @@ -67,6 +67,9 @@ Translator::Translator(Instance &aInstance) : InstanceLocator(aInstance) , mState(kStateDisabled) , mMappingPool(aInstance) + , mMinHostId(0) + , mMaxHostId(0) + , mNextHostId(0) , mTimer(aInstance) { Random::NonCrypto::Fill(mNextMappingId); @@ -355,20 +358,6 @@ void Translator::Mapping::Free(void) { LogInfo("Mapping removed: %s", ToString().AsCString()); -#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE - if (Get().mIp4Cidr.mLength > kAddressMappingCidrLimit) - { - // If `CONFIG_NAT64_PORT_TRANSLATION_ENABLE` is enabled - // IPv4 addresses are allocated from the pool only when the - // pool size is above a minimum value. Otherwise use just the - // first address from the pool. - } - else -#endif - { - IgnoreError(Get().mIp4AddressPool.PushBack(mIp4Address)); - } - Get().mMappingPool.Free(*this); } @@ -401,45 +390,70 @@ uint16_t Translator::AllocateSourcePort(uint16_t aSrcPort) } #endif +void Translator::GetNextIp4Address(Ip4::Address &aIp4Address) +{ + aIp4Address.SynthesizeFromCidrAndHost(mIp4Cidr, mNextHostId); + + mNextHostId++; + + if (mNextHostId > mMaxHostId) + { + mNextHostId = mMinHostId; + } +} + +#if !OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE + +Error Translator::AllocateIp4Address(Ip4::Address &aIp4Address) +{ + // Allocate a currently unused IPv4 address from the CIDR range. + // If all addresses are allocated, try to free expired mappings. + + Error error = kErrorNone; + uint32_t numberOfHosts; + + numberOfHosts = mMaxHostId - mMinHostId + 1; + + if (mActiveMappings.CountAllEntries() >= numberOfHosts) + { + mActiveMappings.RemoveAndFreeAllMatching(TimerMilli::GetNow()); + + VerifyOrExit(mActiveMappings.CountAllEntries() < numberOfHosts, error = kErrorFailed); + } + + do + { + GetNextIp4Address(aIp4Address); + } while (mActiveMappings.ContainsMatching(aIp4Address)); + +exit: + return error; +} + +#endif + Translator::Mapping *Translator::AllocateMapping(const Ip6::Headers &aIp6Headers) { Mapping *mapping = nullptr; Ip4::Address ip4Addr; + // When `PORT_TRANSLATION_ENABLE` is enabled, the translator uses + // IPv4 addresses from the provided CIDR range sequentially. In + // this case, mappings can share an IPv4 address because they are + // distinguished by their translated port numbers. + // + // Otherwise, a 1-to-1 address mapping is used, where each IPv6 + // address is mapped to a unique IPv4 address from the available + // range. + #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE - // When port translation (`NAT64_PORT_TRANSLATION`) is enabled - // the NAT64 translator can work in 2 ways, either with a single - // IPv4 address or a larger pool of addresses. There is also the - // corner case where the address pool is generated from a big - // CIDR length and the number of available IPv4 addresses is not - // big enough to apply a 1 to 1 translation from IPv6 to IPv4 - // address. When operating in the first case, there is no need to - // manage the address pool and all active mappings will use 1 - // single address (or the limited number alternatively). If a - // larger pool is available each active mapping will use a - // separate IPv4 address. - - if (mIp4Cidr.mLength > kAddressMappingCidrLimit) - { - // TODO: add logic to cycle between available IPv4 addresses - ip4Addr = *mIp4AddressPool.At(0); - } - else + GetNextIp4Address(ip4Addr); +#else + SuccessOrExit(AllocateIp4Address(ip4Addr)); #endif - { - if (mIp4AddressPool.IsEmpty()) - { - mActiveMappings.RemoveAndFreeAllMatching(TimerMilli::GetNow()); - } - - VerifyOrExit(!mIp4AddressPool.IsEmpty()); - ip4Addr = *mIp4AddressPool.PopBack(); - } mapping = mMappingPool.Allocate(); - // We should get a valid item, there is enough space in the - // mapping pool. Otherwise return null and fail the translation. VerifyOrExit(mapping != nullptr); mActiveMappings.Push(*mapping); @@ -572,50 +586,33 @@ exit: Error Translator::SetIp4Cidr(const Ip4::Cidr &aCidr) { - Error error = kErrorNone; + Error error = kErrorNone; + uint8_t len = aCidr.GetLength(); - uint32_t numberOfHosts; - uint32_t hostIdBegin; - - VerifyOrExit(aCidr.mLength > 0 && aCidr.mLength <= 32, error = kErrorInvalidArgs); + VerifyOrExit(IsValueInRange(len, 1, BitSizeOf(Ip4::Address)), error = 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? + // Determine the usable host ID range based on the CIDR prefix + // length. For prefixes /1 through /30, the all-zeros and + // all-ones host IDs are excluded. For /31 and /32 prefixes, + // all host IDs are used. - if (aCidr.mLength == 32) + mMinHostId = 0; + mMaxHostId = (1 << (BitSizeOf(Ip4::Address) - len)) - 1; + + if (len < BitSizeOf(Ip4::Address) - 1) { - 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); + mMinHostId++; + mMaxHostId--; } - numberOfHosts = OT_MIN(numberOfHosts, kPoolSize); + mNextHostId = mMinHostId; mActiveMappings.Free(); - 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, %lu addresses)", aCidr.ToString().AsCString(), - mIp4AddressPool.Front()->ToString().AsCString(), mIp4AddressPool.Back()->ToString().AsCString(), - ToUlong(numberOfHosts)); + LogInfo("IPv4 CIDR for NAT64: %s (%lu addresses)", aCidr.ToString().AsCString(), + ToUlong(mMaxHostId - mMinHostId + 1)); mIp4Cidr = aCidr; @@ -635,7 +632,6 @@ void Translator::ClearIp4Cidr(void) mIp4Cidr.Clear(); mActiveMappings.Free(); - mIp4AddressPool.Clear(); UpdateState(); diff --git a/src/core/net/nat64_translator.hpp b/src/core/net/nat64_translator.hpp index 256b117c7..226dd5933 100644 --- a/src/core/net/nat64_translator.hpp +++ b/src/core/net/nat64_translator.hpp @@ -36,7 +36,6 @@ #include "openthread-core-config.h" -#include "common/array.hpp" #include "common/locator.hpp" #include "common/owning_list.hpp" #include "common/pool.hpp" @@ -284,20 +283,6 @@ private: static constexpr uint32_t kPoolSize = OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS; #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE - // Under `PORT_TRANSLATION_ENABLE`, the translator can operate in - // two modes: 1-to-1 address mapping where each IPv6 address gets - // a unique IPv4 address from a pool, or having the mappings - // share one (or a few) IPv4 addresses and are distinguished by - // the translated port numbers. - // - // This constant defines the maximum allowed CIDR prefix length - // for the IPv4 address pool to be considered large enough to use - // 1-to-1 address mapping. If the configured prefix length is - // greater than this value (e.g., /29, /30), the address pool is - // too small, and the translator will fall back to using port - // translation. - static constexpr uint8_t kAddressMappingCidrLimit = 28; - static constexpr uint16_t kMinTranslationPort = 49152; static constexpr uint16_t kMaxTranslationPort = 65535; #endif @@ -323,6 +308,8 @@ private: bool Matches(const TimeMilli aNow) const { return mExpiry < aNow; } #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE bool Matches(const uint16_t aPort) const { return mTranslatedPortOrId == aPort; } +#else + bool Matches(const Ip4::Address &aIp4Address) const { return mIp4Address == aIp4Address; } #endif Mapping *mNext; @@ -343,6 +330,8 @@ private: void UpdateState(void); Error TranslateIcmp4(Message &aMessage, uint16_t aOriginalId); Error TranslateIcmp6(Message &aMessage, uint16_t aTranslatedId); + void GetNextIp4Address(Ip4::Address &aIp4Address); + Error AllocateIp4Address(Ip4::Address &aIp4Address); Mapping *AllocateMapping(const Ip6::Headers &aIp6Headers); void HandleTimer(void); #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE @@ -354,16 +343,18 @@ private: using TranslatorTimer = TimerMilliIn; - State mState; - uint64_t mNextMappingId; - Array mIp4AddressPool; - Pool mMappingPool; - OwningList mActiveMappings; - Ip6::Prefix mNat64Prefix; - Ip4::Cidr mIp4Cidr; - TranslatorTimer mTimer; - ProtocolCounters mCounters; - ErrorCounters mErrorCounters; + State mState; + uint64_t mNextMappingId; + Pool mMappingPool; + OwningList mActiveMappings; + Ip6::Prefix mNat64Prefix; + Ip4::Cidr mIp4Cidr; + uint32_t mMinHostId; + uint32_t mMaxHostId; + uint32_t mNextHostId; + TranslatorTimer mTimer; + ProtocolCounters mCounters; + ErrorCounters mErrorCounters; }; #endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE diff --git a/tests/nexus/test_nat64_translator.cpp b/tests/nexus/test_nat64_translator.cpp index 092af4c8f..35fb1c372 100644 --- a/tests/nexus/test_nat64_translator.cpp +++ b/tests/nexus/test_nat64_translator.cpp @@ -591,6 +591,175 @@ void TestNat64Mapping(void) VerifyOrQuit(iterator.GetNext(mapping) == kErrorNotFound); } +void TestNat64CidrAddressReuse(const char *aCidr) +{ + static constexpr uint32_t kExpireTimeout = 120 * Time::kOneSecondInMsec; + + static constexpr uint16_t kSrcPort = 55387; + static constexpr uint16_t kDstPort = 55388; + static constexpr uint16_t kPayloadLength = 32; + + Core nexus; + Node &node = nexus.CreateNode(); + Ip6::Prefix prefix; + Ip4::Cidr cidr; + uint16_t numIp4Addrs; + Nat64::Translator::AddressMappingIterator iterator; + Nat64::Translator::AddressMapping mapping; + OwnedPtr message; + Ip6::Address ip6Addr; + Ip4::Address ip4Addr; + Ip4::Headers ip4Headers; + uint16_t count; + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestNat64CidrAddressReuse(%s)", aCidr); + + nexus.AdvanceTime(0); + + node.Form(); + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + VerifyOrQuit(node.Get().IsLeader()); + + node.Get().SetLogLevel(kLogLevelInfo); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Enable NAT64 translator"); + + SuccessOrQuit(prefix.FromString("fd01::/96")); + SuccessOrQuit(cidr.FromString(aCidr)); + + node.Get().SetNat64Prefix(prefix); + SuccessOrQuit(node.Get().SetIp4Cidr(cidr)); + + node.Get().SetEnabled(true); + VerifyOrQuit(node.Get().GetState() == Nat64::kStateActive); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Determine number of available IPv4 addresses"); + + switch (cidr.GetLength()) + { + case 32: + numIp4Addrs = 1; + break; + case 31: + numIp4Addrs = 2; + break; + default: + numIp4Addrs = (1 << (32 - cidr.GetLength())) - 2; + break; + } + + Log("Number of available IPv4 addresses: %u", numIp4Addrs); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Translate IPv6 messages to use all IPv4 addresses"); + + for (uint16_t i = 0; i < numIp4Addrs; i++) + { + SuccessOrQuit(ip6Addr.FromString("fd02::0")); + SuccessOrQuit(ip4Addr.FromString("200.100.1.1")); + + ip6Addr.mFields.m8[15] = i; + + message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + + SuccessOrQuit(ip4Headers.ParseFrom(*message)); + VerifyOrQuit(ip4Headers.GetDestinationAddress() == ip4Addr); + VerifyOrQuit(ip4Headers.IsUdp()); + VerifyOrQuit(ip4Headers.GetSourcePort() == kSrcPort); + VerifyOrQuit(ip4Headers.GetDestinationPort() == kDstPort); + VerifyOrQuit(ip4Headers.GetUdpHeader().GetLength() == kPayloadLength); + + nexus.AdvanceTime(1000); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the Address Mappings"); + + iterator.Init(node.GetInstance()); + + for (count = 0; iterator.GetNext(mapping) == kErrorNone; count++) + { + LogAddressMapping(mapping); + VerifyOrQuit(mapping.mRemainingTimeMs > 0); + } + + VerifyOrQuit(count == numIp4Addrs); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Validate that next translation fails since all IPv4 addresses are in use"); + + SuccessOrQuit(ip6Addr.FromString("fd02::100")); + + message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Wait for mapping entries to expire"); + + nexus.AdvanceTime(kExpireTimeout); + + iterator.Init(node.GetInstance()); + + for (count = 0; iterator.GetNext(mapping) == kErrorNone; count++) + { + LogAddressMapping(mapping); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Translate IPv6 messages again and check that IPv4 addresses are now reused"); + + for (uint16_t i = 0; i < numIp4Addrs; i++) + { + SuccessOrQuit(ip6Addr.FromString("fd02::200:0")); + SuccessOrQuit(ip4Addr.FromString("200.100.3.3")); + + ip6Addr.mFields.m8[15] = i + 100; + + message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + + SuccessOrQuit(ip4Headers.ParseFrom(*message)); + VerifyOrQuit(ip4Headers.GetDestinationAddress() == ip4Addr); + VerifyOrQuit(ip4Headers.IsUdp()); + VerifyOrQuit(ip4Headers.GetSourcePort() == kSrcPort); + VerifyOrQuit(ip4Headers.GetDestinationPort() == kDstPort); + VerifyOrQuit(ip4Headers.GetUdpHeader().GetLength() == kPayloadLength); + + nexus.AdvanceTime(1000); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the Address Mappings"); + + iterator.Init(node.GetInstance()); + + for (count = 0; iterator.GetNext(mapping) == kErrorNone; count++) + { + LogAddressMapping(mapping); + VerifyOrQuit(mapping.mRemainingTimeMs > 0); + } + + VerifyOrQuit(count == numIp4Addrs); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Validate that next translation fails since all IPv4 addresses are in use"); + + SuccessOrQuit(ip6Addr.FromString("fd02::200:100")); + + message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); + + Log("End of TestNat64CidrAddressReuse(%s)", aCidr); +} + } // namespace Nexus } // namespace ot @@ -598,6 +767,10 @@ int main(void) { ot::Nexus::TestNat64StateChanges(); ot::Nexus::TestNat64Mapping(); + ot::Nexus::TestNat64CidrAddressReuse("192.168.101.133/32"); + ot::Nexus::TestNat64CidrAddressReuse("192.168.102.178/31"); + ot::Nexus::TestNat64CidrAddressReuse("192.168.103.0/30"); + ot::Nexus::TestNat64CidrAddressReuse("192.168.104.0/27"); printf("All tests passed\n"); return 0; diff --git a/tests/unit/test_linked_list.cpp b/tests/unit/test_linked_list.cpp index 26426b307..859fe7293 100644 --- a/tests/unit/test_linked_list.cpp +++ b/tests/unit/test_linked_list.cpp @@ -93,6 +93,7 @@ void VerifyLinkedListContent(const LinkedList *aList, ...) Entry *argPrev = nullptr; const Entry *prev; uint16_t unusedId = 100; + uint16_t count = 0; va_start(args, aList); @@ -117,6 +118,7 @@ void VerifyLinkedListContent(const LinkedList *aList, ...) VerifyOrQuit(!argEntry->WasFreed()); argPrev = argEntry; + count++; } argEntry = va_arg(args, Entry *); @@ -129,6 +131,8 @@ void VerifyLinkedListContent(const LinkedList *aList, ...) VerifyOrQuit(aList->FindMatching("none") == nullptr, "succeeded for a missing entry"); VerifyOrQuit(aList->FindMatching(unusedId) == nullptr, "succeeded for a missing entry"); + + VerifyOrQuit(aList->CountAllEntries() == count); } void TestLinkedList(void)