diff --git a/src/core/net/nat64_translator.cpp b/src/core/net/nat64_translator.cpp index 4d35b03eb..a910bbf3d 100644 --- a/src/core/net/nat64_translator.cpp +++ b/src/core/net/nat64_translator.cpp @@ -66,13 +66,13 @@ const char *StateToString(State aState) Translator::Translator(Instance &aInstance) : InstanceLocator(aInstance) , mState(State::kStateDisabled) - , mMappingExpirerTimer(aInstance) + , mTimer(aInstance) { Random::NonCrypto::Fill(mNextMappingId); mNat64Prefix.Clear(); mIp4Cidr.Clear(); - mMappingExpirerTimer.Start(kAddressMappingIdleTimeoutMsec); + mTimer.Start(kIdleTimeout); } Message *Translator::NewIp4Message(const Message::Settings &aSettings) @@ -109,38 +109,40 @@ exit: Translator::Result Translator::TranslateFromIp6(Message &aMessage) { - Result res = kDrop; - ErrorCounters::Reason dropReason = ErrorCounters::kUnknown; - Ip6::Headers ip6Headers; - Ip4::Header ip4Header; - uint16_t srcPortOrId = 0; - AddressMapping *mapping = nullptr; + Result result = kDrop; + DropReason dropReason = kReasonUnknown; + Ip6::Headers ip6Headers; + Ip4::Header ip4Header; + uint16_t srcPortOrId = 0; + Mapping *mapping = nullptr; if (mIp4Cidr.mLength == 0 || !mNat64Prefix.IsValidNat64()) { - ExitNow(res = kNotTranslated); + ExitNow(result = kNotTranslated); } - // ParseFrom will do basic checks for the message, including the message length and IP protocol version. + // `ParseFrom()` will do basic checks for the message, including + // the message length and IP protocol version. if (ip6Headers.ParseFrom(aMessage) != kErrorNone) { - LogWarn("outgoing datagram is not a valid IPv6 datagram, drop"); - dropReason = ErrorCounters::Reason::kIllegalPacket; - ExitNow(res = kDrop); + LogWarn("Outgoing datagram is not a valid IPv6 datagram, drop"); + dropReason = kReasonIllegalPacket; + ExitNow(result = kDrop); } if (!ip6Headers.GetDestinationAddress().MatchesPrefix(mNat64Prefix)) { - ExitNow(res = kNotTranslated); + ExitNow(result = kNotTranslated); } mapping = FindOrAllocateMapping(ip6Headers); + if (mapping == nullptr) { - LogWarn("failed to get a mapping for %s (mapping pool full?)", + LogWarn("Failed to get a mapping for %s (mapping pool full?)", ip6Headers.GetSourceAddress().ToString().AsCString()); - dropReason = ErrorCounters::Reason::kNoMapping; - ExitNow(res = kDrop); + dropReason = kReasonNoMapping; + ExitNow(result = kDrop); } #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE @@ -153,100 +155,106 @@ Translator::Result Translator::TranslateFromIp6(Message &aMessage) ip4Header.Clear(); ip4Header.InitVersionIhl(); - ip4Header.SetSource(mapping->mIp4); + ip4Header.SetSource(mapping->mIp4Address); ip4Header.GetDestination().ExtractFromIp6Address(mNat64Prefix.mLength, ip6Headers.GetDestinationAddress()); ip4Header.SetTtl(ip6Headers.GetIpHopLimit()); ip4Header.SetIdentification(0); switch (ip6Headers.GetIpProto()) { - // The IP header is consumed , so the next header is at offset 0. + // The IP header is consumed, so the next header is at offset 0. case Ip6::kProtoUdp: ip4Header.SetProtocol(Ip4::kProtoUdp); ip6Headers.SetSourcePort(srcPortOrId); aMessage.Write(0, ip6Headers.GetUdpHeader()); - res = kForward; + result = kForward; break; case Ip6::kProtoTcp: ip4Header.SetProtocol(Ip4::kProtoTcp); ip6Headers.SetSourcePort(srcPortOrId); aMessage.Write(0, ip6Headers.GetTcpHeader()); - res = kForward; + result = kForward; break; case Ip6::kProtoIcmp6: ip4Header.SetProtocol(Ip4::kProtoIcmp); SuccessOrExit(TranslateIcmp6(aMessage, srcPortOrId)); - res = kForward; + result = kForward; break; default: - dropReason = ErrorCounters::Reason::kUnsupportedProto; - ExitNow(res = kDrop); + dropReason = kReasonUnsupportedProto; + ExitNow(result = kDrop); } - // res here must be kForward based on the switch above. + // `result` here must be kForward based on the switch above. // TODO: Implement the logic for replying ICMP messages. ip4Header.SetTotalLength(sizeof(Ip4::Header) + aMessage.GetLength() - aMessage.GetOffset()); + Checksum::UpdateMessageChecksum(aMessage, ip4Header.GetSource(), ip4Header.GetDestination(), ip4Header.GetProtocol()); Checksum::UpdateIp4HeaderChecksum(ip4Header); + if (aMessage.Prepend(ip4Header) != kErrorNone) { - // This should never happen since the IPv4 header is shorter than the IPv6 header. + // This should never happen since the IPv4 header is shorter + // than the IPv6 header. LogCrit("failed to prepend IPv4 head to translated message"); - ExitNow(res = kDrop); + ExitNow(result = kDrop); } + aMessage.SetType(Message::kTypeIp4); + mCounters.Count6To4Packet(ip6Headers.GetIpProto(), ip6Headers.GetIpLength()); mapping->mCounters.Count6To4Packet(ip6Headers.GetIpProto(), ip6Headers.GetIpLength()); exit: - if (res == Result::kDrop) + if (result == kDrop) { mErrorCounters.Count6To4(dropReason); } - return res; + + return result; } Translator::Result Translator::TranslateToIp6(Message &aMessage) { - Result res = Result::kDrop; - ErrorCounters::Reason dropReason = ErrorCounters::kUnknown; - Ip6::Header ip6Header; - Ip4::Headers ip4Headers; - uint16_t dstPortOrId = 0; - AddressMapping *mapping = nullptr; + Result result = kDrop; + DropReason dropReason = kReasonUnknown; + Ip6::Header ip6Header; + Ip4::Headers ip4Headers; + uint16_t dstPortOrId = 0; + Mapping *mapping = nullptr; - // Ip6::Header::ParseFrom may return an error value when the incoming message is an IPv4 datagram. - // If the message is already an IPv6 datagram, forward it directly. - VerifyOrExit(ip6Header.ParseFrom(aMessage) != kErrorNone, res = kNotTranslated); + // `ParseFrom()` may return an error value when the incoming + // message is an IPv4 datagram. If the message is already an IPv6 + // datagram, forward it directly. + VerifyOrExit(ip6Header.ParseFrom(aMessage) != kErrorNone, result = kNotTranslated); if (mIp4Cidr.mLength == 0) { - // The NAT64 translation is bypassed (will be handled externally) - LogWarn("incoming message is an IPv4 datagram but no IPv4 CIDR for NAT64 configured, drop"); - ExitNow(res = kForward); + LogWarn("Incoming message is an IPv4 datagram but no IPv4 CIDR for NAT64 configured, drop"); + ExitNow(result = kForward); } if (!mNat64Prefix.IsValidNat64()) { - LogWarn("incoming message is an IPv4 datagram but no NAT64 prefix configured, drop"); - ExitNow(res = kDrop); + LogWarn("Incoming message is an IPv4 datagram but no NAT64 prefix configured, drop"); + ExitNow(result = kDrop); } if (ip4Headers.ParseFrom(aMessage) != kErrorNone) { - LogWarn("incoming message is neither IPv4 nor an IPv6 datagram, drop"); - dropReason = ErrorCounters::Reason::kIllegalPacket; - ExitNow(res = kDrop); + LogWarn("Incoming message is neither IPv4 nor an IPv6 datagram, drop"); + dropReason = kReasonIllegalPacket; + ExitNow(result = kDrop); } mapping = FindMapping(ip4Headers); if (mapping == nullptr) { - LogWarn("no mapping found for the IPv4 address"); - dropReason = ErrorCounters::Reason::kNoMapping; - ExitNow(res = kDrop); + LogWarn("No mapping found for the IPv4 address"); + dropReason = kReasonNoMapping; + ExitNow(result = kDrop); } #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE @@ -260,12 +268,14 @@ Translator::Result Translator::TranslateToIp6(Message &aMessage) ip6Header.Clear(); ip6Header.InitVersionTrafficClassFlow(); ip6Header.GetSource().SynthesizeFromIp4Address(mNat64Prefix, ip4Headers.GetSourceAddress()); - ip6Header.SetDestination(mapping->mIp6); + ip6Header.SetDestination(mapping->mIp6Address); ip6Header.SetFlow(0); ip6Header.SetHopLimit(ip4Headers.GetIpTtl()); - // Note: TCP and UDP are the same for both IPv4 and IPv6 except for the checksum calculation, we will update the - // checksum in the payload later. However, we need to translate ICMPv6 messages to ICMP messages in IPv4. + // Note: TCP and UDP are the same for both IPv4 and IPv6 except + // for the checksum calculation, we will update the checksum in + // the payload later. However, we need to translate ICMPv6 + // messages to ICMP messages in IPv4. switch (ip4Headers.GetIpProto()) { // The IP header is consumed , so the next header is at offset 0. @@ -273,95 +283,99 @@ Translator::Result Translator::TranslateToIp6(Message &aMessage) ip6Header.SetNextHeader(Ip6::kProtoUdp); ip4Headers.SetDestinationPort(dstPortOrId); aMessage.Write(0, ip4Headers.GetUdpHeader()); - res = kForward; + result = kForward; break; case Ip4::kProtoTcp: ip6Header.SetNextHeader(Ip6::kProtoTcp); ip4Headers.SetDestinationPort(dstPortOrId); aMessage.Write(0, ip4Headers.GetTcpHeader()); - res = kForward; + result = kForward; break; case Ip4::kProtoIcmp: ip6Header.SetNextHeader(Ip6::kProtoIcmp6); SuccessOrExit(TranslateIcmp4(aMessage, dstPortOrId)); - res = kForward; + result = kForward; break; default: - dropReason = ErrorCounters::Reason::kUnsupportedProto; - ExitNow(res = kDrop); + dropReason = kReasonUnsupportedProto; + ExitNow(result = kDrop); } - // res here must be kForward based on the switch above. + // result here must be kForward based on the switch above. // TODO: Implement the logic for replying ICMP datagrams. ip6Header.SetPayloadLength(aMessage.GetLength() - aMessage.GetOffset()); + Checksum::UpdateMessageChecksum(aMessage, ip6Header.GetSource(), ip6Header.GetDestination(), ip6Header.GetNextHeader()); + if (aMessage.Prepend(ip6Header) != kErrorNone) { - // This might happen when the platform failed to reserve enough space before the original IPv4 datagram. - LogWarn("failed to prepend IPv6 head to translated message"); - ExitNow(res = kDrop); + // This might happen when the platform failed to reserve + // enough space before the original IPv4 datagram. + LogWarn("Failed to prepend IPv6 head to translated message"); + ExitNow(result = kDrop); } + aMessage.SetType(Message::kTypeIp6); + mCounters.Count4To6Packet(ip4Headers.GetIpProto(), ip4Headers.GetIpLength() - sizeof(Ip4::Header)); mapping->mCounters.Count4To6Packet(ip4Headers.GetIpProto(), ip4Headers.GetIpLength() - sizeof(Ip4::Header)); exit: - if (res == Result::kDrop) + if (result == kDrop) { mErrorCounters.Count4To6(dropReason); } - return res; + return result; } -Translator::AddressMapping::InfoString Translator::AddressMapping::ToString(void) const +Translator::Mapping::InfoString Translator::Mapping::ToString(void) const { InfoString string; - string.Append("%s -> %s", mIp6.ToString().AsCString(), mIp4.ToString().AsCString()); + string.Append("%s -> %s", mIp6Address.ToString().AsCString(), mIp4Address.ToString().AsCString()); return string; } -void Translator::AddressMapping::CopyTo(otNat64AddressMapping &aMapping, TimeMilli aNow) const +void Translator::Mapping::CopyTo(AddressMapping &aMapping, TimeMilli aNow) const { aMapping.mId = mId; - aMapping.mIp4 = mIp4; - aMapping.mIp6 = mIp6; + aMapping.mIp4 = mIp4Address; + aMapping.mIp6 = mIp6Address; aMapping.mSrcPortOrId = mSrcPortOrId; aMapping.mTranslatedPortOrId = mTranslatedPortOrId; aMapping.mCounters = mCounters; - // We are removing expired mappings lazily, and an expired mapping might become active again before actually - // removed. Report the mapping to be "just expired" to avoid confusion. - if (mExpiry < aNow) - { - aMapping.mRemainingTimeMs = 0; - } - else - { - aMapping.mRemainingTimeMs = mExpiry - aNow; - } + // We are removing expired mappings lazily, and an expired mapping + // might become active again before actually removed. Report the + // mapping to be "just expired" to avoid confusion. + + aMapping.mRemainingTimeMs = (mExpiry < aNow) ? 0 : mExpiry - aNow; } -void Translator::ReleaseMapping(AddressMapping &aMapping) +void Translator::ReleaseMapping(Mapping &aMapping) { if (mIp4Cidr.mLength <= kMaxCidrLenForValidAddrPool) { - // 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 list and we are not removing it from the array. - IgnoreError(mIp4AddressPool.PushBack(aMapping.mIp4)); + // 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 list and we are not removing it + // from the array. + IgnoreError(mIp4AddressPool.PushBack(aMapping.mIp4Address)); } - mAddressMappingPool.Free(aMapping); - LogInfo("mapping removed: %s", aMapping.ToString().AsCString()); + + mMappingPool.Free(aMapping); + + LogInfo("Mapping removed: %s", aMapping.ToString().AsCString()); } -uint16_t Translator::ReleaseMappings(LinkedList &aMappings) +uint16_t Translator::ReleaseMappings(LinkedList &aMappings) { uint16_t numRemoved = 0; - for (AddressMapping *mapping = aMappings.Pop(); mapping != nullptr; mapping = aMappings.Pop()) + for (Mapping *mapping = aMappings.Pop(); mapping != nullptr; mapping = aMappings.Pop()) { numRemoved++; ReleaseMapping(*mapping); @@ -372,46 +386,57 @@ uint16_t Translator::ReleaseMappings(LinkedList &aMappings) uint16_t Translator::ReleaseExpiredMappings(void) { - LinkedList idleMappings; + LinkedList idleMappings; - mActiveAddressMappings.RemoveAllMatching(idleMappings, TimerMilli::GetNow()); + mActiveMappings.RemoveAllMatching(idleMappings, TimerMilli::GetNow()); return ReleaseMappings(idleMappings); } #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE uint16_t Translator::AllocateSourcePort(uint16_t aSrcPort) { - // The translated port is randomly allocated from the range of dynamic or private ports (RFC 7605 section 4). - // In this way, we will not pick a random port that could be a well-known port preventing an unknown situation on - // the receiver side. - uint16_t retPort; + // The translated port is randomly allocated from the range of + // dynamic or private ports (RFC 7605 section 4). In this way, we + // will not pick a random port that could be a well-known port + // preventing an unknown situation on the receiver side. + + uint16_t port; do { - retPort = Random::NonCrypto::GetUint16InRange(kTranslationPortRangeStart, kTranslationPortRangeEnd); - // The NAT64 SHOULD preserve the port parity (odd/even), as per Section 4.2.2 of [RFC4787]). - // Determine if original and allocated port have different parity - if (((aSrcPort ^ retPort) & 1) == 1) - { - retPort++; - } - } while (mActiveAddressMappings.ContainsMatching(retPort)); + port = Random::NonCrypto::GetUint16InRange(kMinTranslationPort, kMaxTranslationPort); - return retPort; + // The NAT64 SHOULD preserve the port parity (odd/even), as + // per Section 4.2.2 of [RFC4787]). Determine if original and + // allocated port have different parity + + if (((aSrcPort ^ port) & 1) == 1) + { + port++; + } + + } while (mActiveMappings.ContainsMatching(port)); + + return port; } #endif -Translator::AddressMapping *Translator::AllocateMapping(const Ip6::Headers &aIp6Headers) +Translator::Mapping *Translator::AllocateMapping(const Ip6::Headers &aIp6Headers) { - AddressMapping *mapping = nullptr; - Ip4::Address ip4Addr; + Mapping *mapping = nullptr; + Ip4::Address ip4Addr; + + // 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. - // 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 > kMaxCidrLenForValidAddrPool) { // TODO: add logic to cycle between available IPv4 addresses @@ -421,44 +446,47 @@ Translator::AddressMapping *Translator::AllocateMapping(const Ip6::Headers &aIp6 { if (mIp4AddressPool.IsEmpty()) { - // ReleaseExpiredMappings returns the number of mappings removed. + // `ReleaseExpiredMappings()` returns the number of + // mappings removed. + VerifyOrExit(ReleaseExpiredMappings() > 0); } ip4Addr = *mIp4AddressPool.PopBack(); } - mapping = mAddressMappingPool.Allocate(); - // We should get a valid item, there is enough space in the mapping pool. Otherwise return null and fail the - // translation. + 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); - mActiveAddressMappings.Push(*mapping); + mActiveMappings.Push(*mapping); mapping->mCounters.Clear(); - mapping->mId = ++mNextMappingId; - mapping->mIp6 = aIp6Headers.GetSourceAddress(); - mapping->mIp4 = ip4Addr; + mapping->mId = ++mNextMappingId; + mapping->mIp6Address = aIp6Headers.GetSourceAddress(); + mapping->mIp4Address = ip4Addr; #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE mapping->mSrcPortOrId = aIp6Headers.IsIcmp6() ? aIp6Headers.GetIcmpHeader().GetId() : aIp6Headers.GetSourcePort(); - // Allocate a unique source port or ICMP Id mapping->mTranslatedPortOrId = AllocateSourcePort(mapping->mSrcPortOrId); #else mapping->mSrcPortOrId = 0; mapping->mTranslatedPortOrId = 0; #endif mapping->Touch(TimerMilli::GetNow(), aIp6Headers.GetIpProto()); - LogInfo("mapping created: %s", mapping->ToString().AsCString()); + + LogInfo("Mapping created: %s", mapping->ToString().AsCString()); exit: return mapping; } -Translator::AddressMapping *Translator::FindOrAllocateMapping(const Ip6::Headers &aIp6Headers) +Translator::Mapping *Translator::FindOrAllocateMapping(const Ip6::Headers &aIp6Headers) { #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE - uint16_t srcPortOrId = aIp6Headers.IsIcmp6() ? aIp6Headers.GetIcmpHeader().GetId() : aIp6Headers.GetSourcePort(); - AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Headers.GetSourceAddress(), srcPortOrId); + uint16_t srcPortOrId = aIp6Headers.IsIcmp6() ? aIp6Headers.GetIcmpHeader().GetId() : aIp6Headers.GetSourcePort(); + Mapping *mapping = mActiveMappings.FindMatching(aIp6Headers.GetSourceAddress(), srcPortOrId); #else - AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Headers.GetSourceAddress()); + Mapping *mapping = mActiveMappings.FindMatching(aIp6Headers.GetSourceAddress()); #endif // Exit if we found a valid mapping. @@ -470,15 +498,15 @@ exit: return mapping; } -Translator::AddressMapping *Translator::FindMapping(const Ip4::Headers &aIp4Headers) +Translator::Mapping *Translator::FindMapping(const Ip4::Headers &aIp4Headers) { uint16_t dstPortOrId = aIp4Headers.IsIcmp4() ? aIp4Headers.GetIcmpHeader().GetId() : aIp4Headers.GetDestinationPort(); #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE - AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Headers.GetDestinationAddress(), dstPortOrId); + Mapping *mapping = mActiveMappings.FindMatching(aIp4Headers.GetDestinationAddress(), dstPortOrId); #else - AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Headers.GetDestinationAddress()); + Mapping *mapping = mActiveMappings.FindMatching(aIp4Headers.GetDestinationAddress()); OT_UNUSED_VARIABLE(dstPortOrId); #endif @@ -489,93 +517,108 @@ Translator::AddressMapping *Translator::FindMapping(const Ip4::Headers &aIp4Head return mapping; } -void Translator::AddressMapping::Touch(TimeMilli aNow, uint8_t aProtocol) +void Translator::Mapping::Touch(TimeMilli aNow, uint8_t aProtocol) { if ((aProtocol == Ip6::kProtoIcmp6) || (aProtocol == Ip4::kProtoIcmp)) { - mExpiry = aNow + kAddressMappingIcmpIdleTimeoutMsec; + mExpiry = aNow + kIcmpTimeout; } else { - mExpiry = aNow + kAddressMappingIdleTimeoutMsec; + mExpiry = aNow + kIdleTimeout; } } +bool Translator::Mapping::Matches(const Ip6::Address &aIp6Address, const uint16_t aPort) const +{ + return ((mIp6Address == aIp6Address) && (mSrcPortOrId == aPort)); +} + +bool Translator::Mapping::Matches(const Ip4::Address &aIp4Address, const uint16_t aPort) const +{ + return ((mIp4Address == aIp4Address) && (mTranslatedPortOrId == aPort)); +} + Error Translator::TranslateIcmp4(Message &aMessage, uint16_t aOriginalId) { - Error err = kErrorNone; + Error error = kErrorNone; Ip4::Icmp::Header icmp4Header; Ip6::Icmp::Header icmp6Header; // TODO: Implement the translation of other ICMP messages. - // Note: The caller consumed the IP header, so the ICMP header is at offset 0. - SuccessOrExit(err = aMessage.Read(0, icmp4Header)); + // Note: The caller consumed the IP header, so the ICMP header is + // at offset 0. + SuccessOrExit(error = aMessage.Read(0, icmp4Header)); + switch (icmp4Header.GetType()) { case Ip4::Icmp::Header::Type::kTypeEchoReply: - { - // The only difference between ICMPv6 echo and ICMP4 echo is the message type field, so we can reinterpret it as - // ICMP6 header and set the message type. - SuccessOrExit(err = aMessage.Read(0, icmp6Header)); - icmp6Header.SetType(Ip6::Icmp::Header::Type::kTypeEchoReply); + // The only difference between ICMPv6 echo and ICMP4 echo is + // the message type field, so we can reinterpret it as ICMP6 + // header and set the message type. + SuccessOrExit(error = aMessage.Read(0, icmp6Header)); + icmp6Header.SetType(Ip6::Icmp::Header::kTypeEchoReply); icmp6Header.SetId(aOriginalId); aMessage.Write(0, icmp6Header); break; - } + default: - err = kErrorInvalidArgs; + error = kErrorInvalidArgs; break; } exit: - return err; + return error; } Error Translator::TranslateIcmp6(Message &aMessage, uint16_t aTranslatedId) { - Error err = kErrorNone; + Error error = kErrorNone; Ip4::Icmp::Header icmp4Header; Ip6::Icmp::Header icmp6Header; // TODO: Implement the translation of other ICMP messages. - // Note: The caller have consumed the IP header, so the ICMP header is at offset 0. - SuccessOrExit(err = aMessage.Read(0, icmp6Header)); + // Note: The caller have consumed the IP header, so the ICMP + // header is at offset 0. + SuccessOrExit(error = aMessage.Read(0, icmp6Header)); + switch (icmp6Header.GetType()) { - case Ip6::Icmp::Header::Type::kTypeEchoRequest: - { - // The only difference between ICMPv6 echo and ICMP4 echo is the message type field, so we can reinterpret it as - // ICMP6 header and set the message type. - SuccessOrExit(err = aMessage.Read(0, icmp4Header)); + case Ip6::Icmp::Header::kTypeEchoRequest: + // The only difference between ICMPv6 echo and ICMP4 echo is + // the message type field, so we can reinterpret it as ICMP6 + // header and set the message type. + SuccessOrExit(error = aMessage.Read(0, icmp4Header)); icmp4Header.SetType(Ip4::Icmp::Header::Type::kTypeEchoRequest); icmp4Header.SetId(aTranslatedId); aMessage.Write(0, icmp4Header); break; - } + default: - err = kErrorInvalidArgs; + error = kErrorInvalidArgs; break; } exit: - return err; + return error; } Error Translator::SetIp4Cidr(const Ip4::Cidr &aCidr) { - Error err = kErrorNone; + Error error = kErrorNone; uint32_t numberOfHosts; uint32_t hostIdBegin; - VerifyOrExit(aCidr.mLength > 0 && aCidr.mLength <= 32, err = kErrorInvalidArgs); + VerifyOrExit(aCidr.mLength > 0 && aCidr.mLength <= 32, 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? + // 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? + if (aCidr.mLength == 32) { hostIdBegin = 0; @@ -591,10 +634,11 @@ Error Translator::SetIp4Cidr(const Ip4::Cidr &aCidr) hostIdBegin = 1; numberOfHosts = static_cast((1 << (Ip4::Address::kSize * 8 - aCidr.mLength)) - 2); } - numberOfHosts = OT_MIN(numberOfHosts, kAddressMappingPoolSize); - mAddressMappingPool.FreeAll(); - mActiveAddressMappings.Clear(); + numberOfHosts = OT_MIN(numberOfHosts, kPoolSize); + + mMappingPool.FreeAll(); + mActiveMappings.Clear(); mIp4AddressPool.Clear(); for (uint32_t i = 0; i < numberOfHosts; i++) @@ -608,22 +652,22 @@ Error Translator::SetIp4Cidr(const Ip4::Cidr &aCidr) 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)); + mIp4Cidr = aCidr; UpdateState(); - // Notify the platform when the CIDR is changed. Get().Signal(kEventNat64TranslatorStateChanged); exit: - return err; + return error; } void Translator::ClearIp4Cidr(void) { mIp4Cidr.Clear(); - mAddressMappingPool.FreeAll(); - mActiveAddressMappings.Clear(); + mMappingPool.FreeAll(); + mActiveMappings.Clear(); mIp4AddressPool.Clear(); UpdateState(); @@ -654,58 +698,57 @@ exit: return; } -void Translator::HandleMappingExpirerTimer(void) +void Translator::HandleTimer(void) { uint16_t numReleased = ReleaseExpiredMappings(); LogInfo("Released %u expired mappings", numReleased); - mMappingExpirerTimer.Start(Min(kAddressMappingIcmpIdleTimeoutMsec, kAddressMappingIdleTimeoutMsec)); + mTimer.Start(Min(kIcmpTimeout, kIdleTimeout)); OT_UNUSED_VARIABLE(numReleased); } void Translator::InitAddressMappingIterator(AddressMappingIterator &aIterator) { - aIterator.mPtr = mActiveAddressMappings.GetHead(); + aIterator.mPtr = mActiveMappings.GetHead(); } -Error Translator::GetNextAddressMapping(AddressMappingIterator &aIterator, otNat64AddressMapping &aMapping) +Error Translator::GetNextAddressMapping(AddressMappingIterator &aIterator, AddressMapping &aMapping) { - Error err = kErrorNotFound; - TimeMilli now = TimerMilli::GetNow(); - AddressMapping *item = static_cast(aIterator.mPtr); + Error error = kErrorNotFound; + Mapping *mapping = static_cast(aIterator.mPtr); - VerifyOrExit(item != nullptr); + VerifyOrExit(mapping != nullptr); - item->CopyTo(aMapping, now); - aIterator.mPtr = item->GetNext(); - err = kErrorNone; + mapping->CopyTo(aMapping, TimerMilli::GetNow()); + aIterator.mPtr = mapping->GetNext(); + error = kErrorNone; exit: - return err; + return error; } -Error Translator::GetIp4Cidr(Ip4::Cidr &aCidr) +Error Translator::GetIp4Cidr(Ip4::Cidr &aCidr) const { - Error err = kErrorNone; + Error error = kErrorNone; - VerifyOrExit(mIp4Cidr.mLength > 0, err = kErrorNotFound); + VerifyOrExit(mIp4Cidr.mLength > 0, error = kErrorNotFound); aCidr = mIp4Cidr; exit: - return err; + return error; } -Error Translator::GetIp6Prefix(Ip6::Prefix &aPrefix) +Error Translator::GetIp6Prefix(Ip6::Prefix &aPrefix) const { - Error err = kErrorNone; + Error error = kErrorNone; - VerifyOrExit(mNat64Prefix.mLength > 0, err = kErrorNotFound); + VerifyOrExit(mNat64Prefix.mLength > 0, error = kErrorNotFound); aPrefix = mNat64Prefix; exit: - return err; + return error; } void Translator::ProtocolCounters::Count6To4Packet(uint8_t aProtocol, uint64_t aPacketSize) @@ -786,7 +829,7 @@ void Translator::SetEnabled(bool aEnabled) if (!aEnabled) { - ReleaseMappings(mActiveAddressMappings); + ReleaseMappings(mActiveMappings); } UpdateState(); diff --git a/src/core/net/nat64_translator.hpp b/src/core/net/nat64_translator.hpp index 848101093..1b1dd6046 100644 --- a/src/core/net/nat64_translator.hpp +++ b/src/core/net/nat64_translator.hpp @@ -72,32 +72,18 @@ const char *StateToString(State aState); class Translator : public InstanceLocator, private NonCopyable { public: - static constexpr uint32_t kAddressMappingIdleTimeoutMsec = - OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS * Time::kOneSecondInMsec; - // ICMP mappings can expire fast since the identifier field will usually be the same only for - // a ping sessing that can have multiple ping requests. Once a new session is started the - // identifier will change. - static constexpr uint32_t kAddressMappingIcmpIdleTimeoutMsec = - OPENTHREAD_CONFIG_NAT64_ICMP_IDLE_TIMEOUT_SECONDS * Time::kOneSecondInMsec; - static constexpr uint32_t kAddressMappingPoolSize = OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS; - static constexpr uint16_t kTranslationPortRangeStart = 49152; - static constexpr uint16_t kTranslationPortRangeEnd = 65535; - // The maximum value the CIDR len can have in order to have a big enough pool to support a - // minimal number of devices - static constexpr uint8_t kMaxCidrLenForValidAddrPool = 28; - + typedef otNat64AddressMapping AddressMapping; ///< Address mapping. typedef otNat64AddressMappingIterator AddressMappingIterator; ///< Address mapping Iterator. + typedef otNat64DropReason DropReason; ///< Drop reason. /** * The possible results of NAT64 translation. */ enum Result : uint8_t { - kNotTranslated, ///< The message is not translated, it might be sending to an non-nat64 prefix (for outgoing - ///< datagrams), or it is already an IPv6 message (for incoming datagrams). - kForward, ///< Message is successfully translated, the caller should continue forwarding the translated - ///< datagram. - kDrop, ///< The caller should drop the datagram silently. + kNotTranslated, ///< Not translated (e.g., Outgoing msg using a non-NAT64 prefix, or incoming is already IPv6). + kForward, ///< Successfully translated and the translated message should be forwarded. + kDrop, ///< Silently drop the message. }; /** @@ -105,21 +91,10 @@ public: */ class ProtocolCounters : public otNat64ProtocolCounters, public Clearable { - public: - /** - * Adds the packet to the counter for the given IPv6 protocol. - * - * @param[in] aProtocol The protocol of the packet. - * @param[in] aPacketSize The size of the packet. - */ - void Count6To4Packet(uint8_t aProtocol, uint64_t aPacketSize); + friend class Translator; - /** - * Adds the packet to the counter for the given IPv4 protocol. - * - * @param[in] aProtocol The protocol of the packet. - * @param[in] aPacketSize The size of the packet. - */ + private: + void Count6To4Packet(uint8_t aProtocol, uint64_t aPacketSize); void Count4To6Packet(uint8_t aProtocol, uint64_t aPacketSize); }; @@ -128,32 +103,17 @@ public: */ class ErrorCounters : public otNat64ErrorCounters, public Clearable { + friend class Translator; + public: - enum Reason : uint8_t - { - kUnknown = OT_NAT64_DROP_REASON_UNKNOWN, - kIllegalPacket = OT_NAT64_DROP_REASON_ILLEGAL_PACKET, - kUnsupportedProto = OT_NAT64_DROP_REASON_UNSUPPORTED_PROTO, - kNoMapping = OT_NAT64_DROP_REASON_NO_MAPPING, - }; - - /** - * Adds the counter for the given reason when translating an IPv4 datagram. - * - * @param[in] aReason The reason of packet drop. - */ - void Count4To6(Reason aReason) { mCount4To6[aReason]++; } - - /** - * Adds the counter for the given reason when translating an IPv6 datagram. - * - * @param[in] aReason The reason of packet drop. - */ - void Count6To4(Reason aReason) { mCount6To4[aReason]++; } + void Count4To6(DropReason aReason) { mCount4To6[aReason]++; } + void Count6To4(DropReason aReason) { mCount6To4[aReason]++; } }; /** * Initializes the NAT64 translator. + * + * @param[in] aInstance The OpenThread instance. */ explicit Translator(Instance &aInstance); @@ -266,7 +226,7 @@ public: void ClearNat64Prefix(void); /** - * Initializes an `otNat64AddressMappingIterator`. + * Initializes an `AddressMappingIterator`. * * An iterator MUST be initialized before it is used. * @@ -279,17 +239,14 @@ public: /** * Gets the next AddressMapping info (using an iterator). * - * @param[in,out] aIterator The iterator. On success the iterator will be updated to point to next NAT64 - * address mapping record. To get the first entry the iterator should be set to - * OT_NAT64_ADDRESS_MAPPING_ITERATOR_INIT. - * @param[out] aMapping An `otNat64AddressMapping` where information of next NAT64 address mapping record - * is placed (on success). + * @param[in,out] aIterator The iterator. + * @param[out] aMapping An `AddressMapping` to output to next NAT64 address mapping. * - * @retval kErrorNone Successfully found the next NAT64 address mapping info (@p aMapping was successfully - * updated). + * @retval kErrorNone Successfully found the next NAT64 address mapping info (@p aMapping and @p aIterator + * are updated. * @retval kErrorNotFound No subsequent NAT64 address mapping info was found. */ - Error GetNextAddressMapping(AddressMappingIterator &aIterator, otNat64AddressMapping &aMapping); + Error GetNextAddressMapping(AddressMappingIterator &aIterator, AddressMapping &aMapping); /** * Gets the NAT64 translator counters. @@ -317,7 +274,7 @@ public: * @retval kErrorNone @p aCidr is set to the configured CIDR. * @retval kErrorNotFound The translator is not configured with an IPv4 CIDR. */ - Error GetIp4Cidr(Ip4::Cidr &aCidr); + Error GetIp4Cidr(Ip4::Cidr &aCidr) const; /** * Gets the configured IPv6 prefix in the NAT64 translator. @@ -327,83 +284,79 @@ public: * @retval kErrorNone @p aPrefix is set to the configured prefix. * @retval kErrorNotFound The translator is not configured with an IPv6 prefix. */ - Error GetIp6Prefix(Ip6::Prefix &aPrefix); + Error GetIp6Prefix(Ip6::Prefix &aPrefix) const; private: - class AddressMapping : public LinkedListEntry - { - public: - friend class LinkedListEntry; - friend class LinkedList; + // Timeouts are in milliseconds + 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; - typedef String InfoString; + static constexpr uint32_t kPoolSize = OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS; + static constexpr uint16_t kMinTranslationPort = 49152; + static constexpr uint16_t kMaxTranslationPort = 65535; + + // The maximum value the CIDR len can have in order to have a big + // enough pool to support a minimal number of devices + static constexpr uint8_t kMaxCidrLenForValidAddrPool = 28; + + static constexpr DropReason kReasonUnknown = OT_NAT64_DROP_REASON_UNKNOWN; + static constexpr DropReason kReasonIllegalPacket = OT_NAT64_DROP_REASON_ILLEGAL_PACKET; + static constexpr DropReason kReasonUnsupportedProto = OT_NAT64_DROP_REASON_UNSUPPORTED_PROTO; + static constexpr DropReason kReasonNoMapping = OT_NAT64_DROP_REASON_NO_MAPPING; + + struct Mapping : public LinkedListEntry + { + static constexpr uint16_t kInfoStringSize = 70; + + typedef String InfoString; void Touch(TimeMilli aNow, uint8_t aProtocol); InfoString ToString(void) const; - void CopyTo(otNat64AddressMapping &aMapping, TimeMilli aNow) const; - - uint64_t mId; // The unique id for a mapping session. - - Ip4::Address mIp4; - Ip6::Address mIp6; - uint16_t mSrcPortOrId; - uint16_t mTranslatedPortOrId; - TimeMilli mExpiry; // The timestamp when this mapping expires, in milliseconds. + void CopyTo(AddressMapping &aMapping, TimeMilli aNow) const; + bool Matches(const Ip4::Address &aIp4Address) const { return mIp4Address == aIp4Address; } + bool Matches(const Ip6::Address &aIp6Address) const { return mIp6Address == aIp6Address; } + bool Matches(const uint16_t aPort) const { return mTranslatedPortOrId == aPort; } + bool Matches(const TimeMilli aNow) const { return mExpiry < aNow; } + bool Matches(const Ip6::Address &aIp6Address, const uint16_t aPort) const; + bool Matches(const Ip4::Address &aIp4Address, const uint16_t aPort) const; + Mapping *mNext; + uint64_t mId; + Ip4::Address mIp4Address; + Ip6::Address mIp6Address; + uint16_t mSrcPortOrId; + uint16_t mTranslatedPortOrId; + TimeMilli mExpiry; ProtocolCounters mCounters; - - private: - bool Matches(const Ip4::Address &aIp4) const { return mIp4 == aIp4; } - bool Matches(const Ip6::Address &aIp6) const { return mIp6 == aIp6; } - bool Matches(const uint16_t aPort) const { return mTranslatedPortOrId == aPort; } - bool Matches(const TimeMilli aNow) const { return mExpiry < aNow; } - - bool Matches(const Ip6::Address &aIp6, const uint16_t aPort) const - { - return ((mIp6 == aIp6) && (mSrcPortOrId == aPort)); - } - bool Matches(const Ip4::Address &aIp4, const uint16_t aPort) const - { - return ((mIp4 == aIp4) && (mTranslatedPortOrId == aPort)); - } - - AddressMapping *mNext; }; - Error TranslateIcmp4(Message &aMessage, uint16_t aOriginalId); - Error TranslateIcmp6(Message &aMessage, uint16_t aTranslatedId); - + Error TranslateIcmp4(Message &aMessage, uint16_t aOriginalId); + Error TranslateIcmp6(Message &aMessage, uint16_t aTranslatedId); + uint16_t ReleaseMappings(LinkedList &aMappings); + void ReleaseMapping(Mapping &aMapping); + uint16_t ReleaseExpiredMappings(void); + Mapping *AllocateMapping(const Ip6::Headers &aIp6Headers); + Mapping *FindOrAllocateMapping(const Ip6::Headers &aIp6Headers); + Mapping *FindMapping(const Ip4::Headers &aIp4Headers); + void HandleTimer(void); + void UpdateState(void); #if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE uint16_t AllocateSourcePort(uint16_t aSrcPort); #endif - uint16_t ReleaseMappings(LinkedList &aMappings); - void ReleaseMapping(AddressMapping &aMapping); - uint16_t ReleaseExpiredMappings(void); - AddressMapping *AllocateMapping(const Ip6::Headers &aIp6Headers); - AddressMapping *FindOrAllocateMapping(const Ip6::Headers &aIp6Headers); - AddressMapping *FindMapping(const Ip4::Headers &aIp4Headers); - void HandleMappingExpirerTimer(void); - using MappingTimer = TimerMilliIn; + using TranslatorTimer = TimerMilliIn; - void UpdateState(void); - - bool mEnabled; - State mState; - - uint64_t mNextMappingId; - - Array mIp4AddressPool; - Pool mAddressMappingPool; - LinkedList mActiveAddressMappings; - - Ip6::Prefix mNat64Prefix; - Ip4::Cidr mIp4Cidr; - - MappingTimer mMappingExpirerTimer; - - ProtocolCounters mCounters; - ErrorCounters mErrorCounters; + bool mEnabled; + State mState; + uint64_t mNextMappingId; + Array mIp4AddressPool; + Pool mMappingPool; + LinkedList mActiveMappings; + Ip6::Prefix mNat64Prefix; + Ip4::Cidr mIp4Cidr; + TranslatorTimer mTimer; + ProtocolCounters mCounters; + ErrorCounters mErrorCounters; }; #endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE