diff --git a/src/core/net/nat64_translator.cpp b/src/core/net/nat64_translator.cpp index 092159f6f..347fdd655 100644 --- a/src/core/net/nat64_translator.cpp +++ b/src/core/net/nat64_translator.cpp @@ -76,7 +76,7 @@ Translator::Translator(Instance &aInstance) mNat64Prefix.Clear(); mIp4Cidr.Clear(); - mTimer.Start(kIdleTimeout); + mTimer.Start(Min(kIdleTimeout, kIcmpTimeout)); mCounters.Clear(); ClearAllBytes(mErrorCounters); @@ -351,7 +351,7 @@ void Translator::Mapping::CopyTo(AddressMapping &aMapping, TimeMilli aNow) const // might become active again before actually removed. Report the // mapping to be "just expired" to avoid confusion. - aMapping.mRemainingTimeMs = (mExpiry < aNow) ? 0 : mExpiry - aNow; + aMapping.mRemainingTimeMs = (mExpirationTime < aNow) ? 0 : mExpirationTime - aNow; } void Translator::Mapping::Free(void) @@ -416,8 +416,7 @@ Error Translator::AllocateIp4Address(Ip4::Address &aIp4Address) if (mActiveMappings.CountAllEntries() >= numberOfHosts) { - mActiveMappings.RemoveAndFreeAllMatching(TimerMilli::GetNow()); - + EvictStaleMapping(); VerifyOrExit(mActiveMappings.CountAllEntries() < numberOfHosts, error = kErrorFailed); } @@ -454,6 +453,12 @@ Translator::Mapping *Translator::AllocateMapping(const Ip6::Headers &aIp6Headers mapping = mMappingPool.Allocate(); + if (mapping == nullptr) + { + EvictStaleMapping(); + mapping = mMappingPool.Allocate(); + } + VerifyOrExit(mapping != nullptr); mActiveMappings.Push(*mapping); @@ -472,6 +477,110 @@ exit: return mapping; } +Translator::Mapping::ProtoCategory Translator::Mapping::DetermineProtocolCategory(void) const +{ + ProtoCategory category; + + if (!IsCounterZero(mCounters.mTcp)) + { + category = kTcpAndMaybeOthers; + } + else if (!IsCounterZero(mCounters.mUdp)) + { + category = kUdpAndMaybeIcmp; + } + else + { + category = kIcmpOnly; + } + + return category; +} + +bool Translator::Mapping::IsCounterZero(const ProtocolCounters::Counters &aCounters) +{ + return (aCounters.m6To4Packets == 0) && (aCounters.m4To6Packets == 0); +} + +bool Translator::Mapping::IsEligibleForEviction(TimeMilli aNow) const +{ + return DetermineDurationSinceUse(aNow) >= kMinEvictTimeout; +} + +bool Translator::Mapping::IsBetterEvictionCandidateOver(const Mapping &aOther, TimeMilli aNow) const +{ + // Determines whether this mapping is a better candidate for + // eviction compared to `aOther`. This method assumes both + // mappings are already eligible for eviction. + // + // The eviction preference, from most to least preferred to be + // evicted, is: + // + // 1) ICMP-only mappings. + // 2) UDP mappings (maybe used with ICMP as well). + // 3) TCP mappings (maybe used with UDP or ICMP). + // + // If both mappings are in the same protocol category, the one + // that hasn't been used for the longest time is preferred. + + bool isBetter = false; + int compare; + + // Smaller value for `DetermineProtocolCategory` is preferred. + // (kIcmpOnly -> kUdpAndMaybeIcmp, -> kTcpAndMaybeOthers). + + static_assert(kIcmpOnly < kUdpAndMaybeIcmp, "ProtoCategory enum constants are invalid"); + static_assert(kUdpAndMaybeIcmp < kTcpAndMaybeOthers, "ProtoCategory enum constants are invalid"); + + compare = ThreeWayCompare(DetermineProtocolCategory(), aOther.DetermineProtocolCategory()); + + if (compare < 0) + { + isBetter = true; + } + else if (compare == 0) + { + isBetter = (DetermineDurationSinceUse(aNow) > aOther.DetermineDurationSinceUse(aNow)); + } + + return isBetter; +} + +void Translator::EvictStaleMapping(void) +{ + // First tries to remove expired mappings, if there is no expired + // mapping, it will then try to evict a stale mapping. + + TimeMilli now = TimerMilli::GetNow(); + const Mapping *evictCandidate = nullptr; + bool didRemoveAny; + + didRemoveAny = mActiveMappings.RemoveAndFreeAllMatching(ExpirationChecker(now)); + + VerifyOrExit(!didRemoveAny); + + for (const Mapping &mapping : mActiveMappings) + { + if (!mapping.IsEligibleForEviction(now)) + { + continue; + } + + if ((evictCandidate == nullptr) || mapping.IsBetterEvictionCandidateOver(*evictCandidate, now)) + { + evictCandidate = &mapping; + } + } + + if (evictCandidate != nullptr) + { + mActiveMappings.RemoveMatching(*evictCandidate); + } + +exit: + return; +} + void Translator::Mapping::Touch(uint8_t aProtocol) { uint32_t timeout; @@ -485,7 +594,8 @@ void Translator::Mapping::Touch(uint8_t aProtocol) timeout = kIdleTimeout; } - mExpiry = TimerMilli::GetNow() + timeout; + mLastUseTime = TimerMilli::GetNow(); + mExpirationTime = mLastUseTime + timeout; } bool Translator::Mapping::Matches(const Ip6::Headers &aIp6Headers) const @@ -683,7 +793,7 @@ exit: void Translator::HandleTimer(void) { - mActiveMappings.RemoveAndFreeAllMatching(TimerMilli::GetNow()); + mActiveMappings.RemoveAndFreeAllMatching(ExpirationChecker(TimerMilli::GetNow())); mTimer.Start(Min(kIcmpTimeout, kIdleTimeout)); } diff --git a/src/core/net/nat64_translator.hpp b/src/core/net/nat64_translator.hpp index 226dd5933..c831b8022 100644 --- a/src/core/net/nat64_translator.hpp +++ b/src/core/net/nat64_translator.hpp @@ -280,6 +280,11 @@ private: static constexpr uint32_t kIdleTimeout = OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS * Time::kOneSecondInMsec; static constexpr uint32_t kIcmpTimeout = OPENTHREAD_CONFIG_NAT64_ICMP_IDLE_TIMEOUT_SECONDS * Time::kOneSecondInMsec; + static constexpr uint32_t kMinUdpTimeout = 2 * Time::kOneMinuteInMsec; // UDP_MIN in RFC 6146 (2 min) + static constexpr uint32_t kMinEvictTimeout = kMinUdpTimeout; + + static_assert(kIdleTimeout >= kMinUdpTimeout, "OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS is too short"); + static constexpr uint32_t kPoolSize = OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS; #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE @@ -298,25 +303,40 @@ private: typedef String InfoString; - void Init(Instance &aInstance) { InstanceLocatorInit::Init(aInstance); } - void Free(void); - void Touch(uint8_t aProtocol); - InfoString ToString(void) const; - void CopyTo(AddressMapping &aMapping, TimeMilli aNow) const; - bool Matches(const Ip6::Headers &aIp6Headers) const; - bool Matches(const Ip4::Headers &aIp4Headers) const; - bool Matches(const TimeMilli aNow) const { return mExpiry < aNow; } + enum ProtoCategory : uint8_t + { + kIcmpOnly, + kUdpAndMaybeIcmp, + kTcpAndMaybeOthers, + }; + + void Init(Instance &aInstance) { InstanceLocatorInit::Init(aInstance); } + void Free(void); + void Touch(uint8_t aProtocol); + InfoString ToString(void) const; + void CopyTo(AddressMapping &aMapping, TimeMilli aNow) const; + uint32_t DetermineDurationSinceUse(TimeMilli aNow) const { return aNow - mLastUseTime; } + ProtoCategory DetermineProtocolCategory(void) const; + bool IsEligibleForEviction(TimeMilli aNow) const; + bool IsBetterEvictionCandidateOver(const Mapping &aOther, TimeMilli aNow) const; + bool Matches(const Ip6::Headers &aIp6Headers) const; + bool Matches(const Ip4::Headers &aIp4Headers) const; + bool Matches(const ExpirationChecker &aChecker) const { return aChecker.IsExpired(mExpirationTime); } + bool Matches(const Mapping &aMapping) const { return this == &aMapping; } #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 + static bool IsCounterZero(const ProtocolCounters::Counters &aCounters); + Mapping *mNext; uint64_t mId; + TimeMilli mLastUseTime; + TimeMilli mExpirationTime; Ip4::Address mIp4Address; Ip6::Address mIp6Address; - TimeMilli mExpiry; ProtocolCounters mCounters; #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE uint16_t mSrcPortOrId; @@ -333,6 +353,7 @@ private: void GetNextIp4Address(Ip4::Address &aIp4Address); Error AllocateIp4Address(Ip4::Address &aIp4Address); Mapping *AllocateMapping(const Ip6::Headers &aIp6Headers); + void EvictStaleMapping(void); void HandleTimer(void); #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE uint16_t AllocateSourcePort(uint16_t aSrcPort); diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index f9902952a..0ef01b35b 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -100,7 +100,7 @@ #define OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE 1 #define OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE 1 #define OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE 1 -#define OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS 120 +#define OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS 600 #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE OPENTHREAD_FTD #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "Nexus Simulation" #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "OpenThread by Google Nest" diff --git a/tests/nexus/test_nat64_translator.cpp b/tests/nexus/test_nat64_translator.cpp index 35fb1c372..bc9b61976 100644 --- a/tests/nexus/test_nat64_translator.cpp +++ b/tests/nexus/test_nat64_translator.cpp @@ -285,12 +285,12 @@ void TestNat64StateChanges(void) Log("End of TestNat64StateChanges"); } -Message *PrepareMessage(Node &aNode, - const Ip6::Address &aSrcIp6Address, - const Ip4::Address &aDstIp4Address, - uint16_t aSrcPort = 1234, - uint16_t aDstPort = 1235, - uint16_t aPayloadLen = 10) +Message *PrepareUdpMessage(Node &aNode, + const Ip6::Address &aSrcIp6Address, + const Ip4::Address &aDstIp4Address, + uint16_t aSrcPort = 1234, + uint16_t aDstPort = 1235, + uint16_t aPayloadLen = 10) { Message *message = nullptr; Ip6::Prefix nat64Prefix; @@ -328,6 +328,87 @@ Message *PrepareMessage(Node &aNode, return message; } +Message *PrepareTcpMessage(Node &aNode, + const Ip6::Address &aSrcIp6Address, + const Ip4::Address &aDstIp4Address, + uint16_t aSrcPort = 1234, + uint16_t aDstPort = 1235, + uint16_t aPayloadLen = 10) +{ + Message *message = nullptr; + Ip6::Prefix nat64Prefix; + Ip6::Header ip6Header; + Ip6::Tcp::Header tcpHeader; + + message = aNode.Get().Allocate(Message::kTypeIp6); + VerifyOrQuit(message != nullptr); + + ip6Header.Clear(); + ip6Header.InitVersionTrafficClassFlow(); + + ip6Header.SetSource(aSrcIp6Address); + + SuccessOrQuit(aNode.Get().GetNat64Prefix(nat64Prefix)); + ip6Header.GetDestination().SynthesizeFromIp4Address(nat64Prefix, aDstIp4Address); + + ip6Header.SetNextHeader(Ip6::kProtoTcp); + ip6Header.SetPayloadLength(sizeof(Ip6::Tcp::Header) + aPayloadLen); + + SuccessOrQuit(message->Append(ip6Header)); + + tcpHeader.Clear(); + tcpHeader.SetSourcePort(aSrcPort); + tcpHeader.SetDestinationPort(aDstPort); + + SuccessOrQuit(message->Append(tcpHeader)); + + for (uint16_t i = 0; i < aPayloadLen; i++) + { + SuccessOrQuit(message->Append(static_cast(i & 0xff))); + } + + return message; +} + +Message *PrepareIcmp6Message(Node &aNode, + const Ip6::Address &aSrcIp6Address, + const Ip4::Address &aDstIp4Address, + uint16_t aPayloadLen = 10) +{ + Message *message = nullptr; + Ip6::Prefix nat64Prefix; + Ip6::Header ip6Header; + Ip6::Icmp::Header icmpHeader; + + message = aNode.Get().Allocate(Message::kTypeIp6); + VerifyOrQuit(message != nullptr); + + ip6Header.Clear(); + ip6Header.InitVersionTrafficClassFlow(); + + ip6Header.SetSource(aSrcIp6Address); + + SuccessOrQuit(aNode.Get().GetNat64Prefix(nat64Prefix)); + ip6Header.GetDestination().SynthesizeFromIp4Address(nat64Prefix, aDstIp4Address); + + ip6Header.SetNextHeader(Ip6::kProtoIcmp6); + ip6Header.SetPayloadLength(sizeof(Ip6::Icmp::Header) + aPayloadLen); + + SuccessOrQuit(message->Append(ip6Header)); + + icmpHeader.Clear(); + icmpHeader.SetType(Ip6::Icmp::Header::kTypeEchoRequest); + + SuccessOrQuit(message->Append(icmpHeader)); + + for (uint16_t i = 0; i < aPayloadLen; i++) + { + SuccessOrQuit(message->Append(static_cast(i & 0xff))); + } + + return message; +} + void LogAddressMapping(const Nat64::Translator::AddressMapping &aMapping) { Log("Mapping: %s -> %s, remaining-time:%lu", AsCoreType(&aMapping.mIp6).ToString().AsCString(), @@ -336,7 +417,7 @@ void LogAddressMapping(const Nat64::Translator::AddressMapping &aMapping) void TestNat64Mapping(void) { - static constexpr uint32_t kExpireTimeout = 250 * Time::kOneSecondInMsec; + static constexpr uint32_t kExpireTimeout = 2 * 610 * Time::kOneSecondInMsec; static constexpr uint16_t kSrcPort = 55387; static constexpr uint16_t kDstPort = 55388; @@ -387,7 +468,7 @@ void TestNat64Mapping(void) SuccessOrQuit(ip6Addr.FromString("fd02::1")); SuccessOrQuit(ip4Addr.FromString("200.100.1.1")); - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -416,7 +497,7 @@ void TestNat64Mapping(void) SuccessOrQuit(ip4Addr.FromString("200.100.1.2")); - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength * 2)); + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength * 2)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -447,7 +528,7 @@ void TestNat64Mapping(void) SuccessOrQuit(ip6Addr2.FromString("fd02::2")); - message.Reset(PrepareMessage(node, ip6Addr2, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr2, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -484,7 +565,7 @@ void TestNat64Mapping(void) SuccessOrQuit(ip4Addr.FromString("200.100.1.5")); - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -518,7 +599,7 @@ void TestNat64Mapping(void) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - message.Reset(PrepareMessage(node, ip6Addr2, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr2, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -558,7 +639,7 @@ void TestNat64Mapping(void) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Translate a new IPv6 message and check that a new Address Mapping is created"); - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -593,7 +674,7 @@ void TestNat64Mapping(void) void TestNat64CidrAddressReuse(const char *aCidr) { - static constexpr uint32_t kExpireTimeout = 120 * Time::kOneSecondInMsec; + static constexpr uint32_t kExpireTimeout = 600 * Time::kOneSecondInMsec; static constexpr uint16_t kSrcPort = 55387; static constexpr uint16_t kDstPort = 55388; @@ -663,7 +744,7 @@ void TestNat64CidrAddressReuse(const char *aCidr) ip6Addr.mFields.m8[15] = i; - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); @@ -695,7 +776,7 @@ void TestNat64CidrAddressReuse(const char *aCidr) SuccessOrQuit(ip6Addr.FromString("fd02::100")); - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); @@ -721,16 +802,15 @@ void TestNat64CidrAddressReuse(const char *aCidr) ip6Addr.mFields.m8[15] = i + 100; - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareTcpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); SuccessOrQuit(ip4Headers.ParseFrom(*message)); VerifyOrQuit(ip4Headers.GetDestinationAddress() == ip4Addr); - VerifyOrQuit(ip4Headers.IsUdp()); + VerifyOrQuit(ip4Headers.IsTcp()); VerifyOrQuit(ip4Headers.GetSourcePort() == kSrcPort); VerifyOrQuit(ip4Headers.GetDestinationPort() == kDstPort); - VerifyOrQuit(ip4Headers.GetUdpHeader().GetLength() == kPayloadLength); nexus.AdvanceTime(1000); } @@ -753,13 +833,271 @@ void TestNat64CidrAddressReuse(const char *aCidr) SuccessOrQuit(ip6Addr.FromString("fd02::200:100")); - message.Reset(PrepareMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + message.Reset(PrepareTcpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); Log("End of TestNat64CidrAddressReuse(%s)", aCidr); } +void TestNat64Evict(void) +{ + static constexpr uint32_t kEvictTimeout = (120 + 10) * 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; + uint16_t count; + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestNat64Evict()"); + + 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("fd00:abba::/96")); + SuccessOrQuit(cidr.FromString("192.168.107.0/29")); + numIp4Addrs = 6; + + node.Get().SetNat64Prefix(prefix); + SuccessOrQuit(node.Get().SetIp4Cidr(cidr)); + + node.Get().SetEnabled(true); + VerifyOrQuit(node.Get().GetState() == Nat64::kStateActive); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Translate IPv6 messages to use all IPv4 addresses"); + + SuccessOrQuit(ip6Addr.FromString("fd00:cdef::0")); + SuccessOrQuit(ip4Addr.FromString("200.100.2.1")); + + for (uint16_t i = 0; i < numIp4Addrs; i++) + { + ip6Addr.mFields.m8[15] = i; + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + 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 and none can be evicted"); + + SuccessOrQuit(ip6Addr.FromString("fd00:cdef::ff")); + + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Wait for longer than min evict time"); + + nexus.AdvanceTime(kEvictTimeout); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Now a new transaction should evict the oldest mapping"); + + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the Address Mappings and that oldest entry is evicted"); + + iterator.Init(node.GetInstance()); + + for (count = 0; iterator.GetNext(mapping) == kErrorNone; count++) + { + LogAddressMapping(mapping); + VerifyOrQuit(mapping.mRemainingTimeMs > 0); + VerifyOrQuit(mapping.mIp6.mFields.m8[15] != 0); + } + + VerifyOrQuit(count == numIp4Addrs); + + Log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="); + + node.Get().SetEnabled(false); + node.Get().SetEnabled(true); + VerifyOrQuit(node.Get().GetState() == Nat64::kStateActive); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Translate IPv6 TCP messages to use all IPv4 addresses"); + + SuccessOrQuit(ip6Addr.FromString("fd00:cdef::0")); + SuccessOrQuit(ip4Addr.FromString("200.100.2.1")); + + for (uint16_t i = 1; i < numIp4Addrs; i++) + { + ip6Addr.mFields.m8[15] = i; + message.Reset(PrepareTcpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + nexus.AdvanceTime(1000); + } + + Log("Translate one final IPv6 UDP message"); + + ip6Addr.mFields.m8[15] = 0; + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + 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 and none can be evicted"); + + SuccessOrQuit(ip6Addr.FromString("fd00:cdef::ff")); + + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Wait for longer than min evict time"); + + nexus.AdvanceTime(kEvictTimeout); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Now a new transaction should evict the UDP entry"); + + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the Address Mappings and that correct entry is evicted"); + + iterator.Init(node.GetInstance()); + + for (count = 0; iterator.GetNext(mapping) == kErrorNone; count++) + { + LogAddressMapping(mapping); + VerifyOrQuit(mapping.mRemainingTimeMs > 0); + VerifyOrQuit(mapping.mIp6.mFields.m8[15] != 0); + } + + VerifyOrQuit(count == numIp4Addrs); + + Log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="); + + node.Get().SetEnabled(false); + node.Get().SetEnabled(true); + VerifyOrQuit(node.Get().GetState() == Nat64::kStateActive); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Translate various TCP/UDP messages to use all IPv4 addresses"); + + SuccessOrQuit(ip6Addr.FromString("fd00:cdef::0")); + SuccessOrQuit(ip4Addr.FromString("200.100.2.1")); + + for (uint16_t i = 1; i < numIp4Addrs - 1; i++) + { + ip6Addr.mFields.m8[15] = i; + message.Reset(PrepareTcpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + nexus.AdvanceTime(1000); + } + + ip6Addr.mFields.m8[15] = numIp4Addrs - 1; + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + nexus.AdvanceTime(1000); + + Log("Translate one final ICMPv6 message"); + + ip6Addr.mFields.m8[15] = 0; + message.Reset(PrepareIcmp6Message(node, ip6Addr, ip4Addr)); + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + 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 and none can be evicted"); + + SuccessOrQuit(ip6Addr.FromString("fd00:cdef::ff")); + + message.Reset(PrepareUdpMessage(node, ip6Addr, ip4Addr, kSrcPort, kDstPort, kPayloadLength)); + + VerifyOrQuit(node.Get().TranslateIp6ToIp4(*message) == kErrorDrop); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Wait for longer than min evict time"); + + nexus.AdvanceTime(kEvictTimeout); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Now a new transaction should evict the ICMP entry"); + + SuccessOrQuit(node.Get().TranslateIp6ToIp4(*message)); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the Address Mappings and that correct entry is evicted"); + + iterator.Init(node.GetInstance()); + + for (count = 0; iterator.GetNext(mapping) == kErrorNone; count++) + { + LogAddressMapping(mapping); + VerifyOrQuit(mapping.mRemainingTimeMs > 0); + VerifyOrQuit(mapping.mIp6.mFields.m8[15] != 0); + } + + VerifyOrQuit(count == numIp4Addrs); + + Log("End of TestNat64Evict()"); +} + } // namespace Nexus } // namespace ot @@ -771,6 +1109,7 @@ int main(void) ot::Nexus::TestNat64CidrAddressReuse("192.168.102.178/31"); ot::Nexus::TestNat64CidrAddressReuse("192.168.103.0/30"); ot::Nexus::TestNat64CidrAddressReuse("192.168.104.0/27"); + ot::Nexus::TestNat64Evict(); printf("All tests passed\n"); return 0;