| /* |
| * 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 implementation for the NAT64 translator. |
| * |
| */ |
| |
| #include "nat64_translator.hpp" |
| |
| #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE |
| |
| #include <openthread/platform/toolchain.h> |
| |
| #include "common/code_utils.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| #include "net/checksum.hpp" |
| #include "net/ip4_types.hpp" |
| #include "net/ip6.hpp" |
| |
| namespace ot { |
| namespace Nat64 { |
| |
| RegisterLogModule("Nat64"); |
| |
| const char *StateToString(State aState) |
| { |
| static const char *const kStateString[] = { |
| "Disabled", |
| "NotRunning", |
| "Idle", |
| "Active", |
| }; |
| |
| static_assert(0 == kStateDisabled, "kStateDisabled value is incorrect"); |
| static_assert(1 == kStateNotRunning, "kStateNotRunning value is incorrect"); |
| static_assert(2 == kStateIdle, "kStateIdle value is incorrect"); |
| static_assert(3 == kStateActive, "kStateActive value is incorrect"); |
| |
| return kStateString[aState]; |
| } |
| |
| Translator::Translator(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mState(State::kStateDisabled) |
| , mMappingExpirerTimer(aInstance) |
| { |
| Random::NonCrypto::Fill(mNextMappingId); |
| |
| mNat64Prefix.Clear(); |
| mIp4Cidr.Clear(); |
| mMappingExpirerTimer.Start(kAddressMappingIdleTimeoutMsec); |
| } |
| |
| Message *Translator::NewIp4Message(const Message::Settings &aSettings) |
| { |
| Message *message = Get<Ip6::Ip6>().NewMessage(sizeof(Ip6::Header) - sizeof(Ip4::Header), aSettings); |
| |
| if (message != nullptr) |
| { |
| message->SetType(Message::kTypeIp4); |
| } |
| |
| return message; |
| } |
| |
| Error Translator::SendMessage(Message &aMessage) |
| { |
| bool freed = false; |
| Error error = kErrorDrop; |
| Result result = TranslateToIp6(aMessage); |
| |
| VerifyOrExit(result == kForward); |
| |
| error = Get<Ip6::Ip6>().SendRaw(aMessage); |
| freed = true; |
| |
| exit: |
| if (!freed) |
| { |
| aMessage.Free(); |
| } |
| |
| return error; |
| } |
| |
| Translator::Result Translator::TranslateFromIp6(Message &aMessage) |
| { |
| Result res = kDrop; |
| ErrorCounters::Reason dropReason = ErrorCounters::kUnknown; |
| Ip6::Header ip6Header; |
| Ip4::Header ip4Header; |
| AddressMapping *mapping = nullptr; |
| |
| if (mIp4Cidr.mLength == 0 || !mNat64Prefix.IsValidNat64()) |
| { |
| ExitNow(res = kNotTranslated); |
| } |
| |
| // ParseFrom will do basic checks for the message, including the message length and IP protocol version. |
| if (ip6Header.ParseFrom(aMessage) != kErrorNone) |
| { |
| LogWarn("outgoing datagram is not a valid IPv6 datagram, drop"); |
| dropReason = ErrorCounters::Reason::kIllegalPacket; |
| ExitNow(res = kDrop); |
| } |
| |
| if (!ip6Header.GetDestination().MatchesPrefix(mNat64Prefix)) |
| { |
| ExitNow(res = kNotTranslated); |
| } |
| |
| mapping = FindOrAllocateMapping(ip6Header.GetSource()); |
| if (mapping == nullptr) |
| { |
| LogWarn("failed to get a mapping for %s (mapping pool full?)", ip6Header.GetSource().ToString().AsCString()); |
| dropReason = ErrorCounters::Reason::kNoMapping; |
| ExitNow(res = kDrop); |
| } |
| |
| aMessage.RemoveHeader(sizeof(Ip6::Header)); |
| |
| ip4Header.Clear(); |
| ip4Header.InitVersionIhl(); |
| ip4Header.SetSource(mapping->mIp4); |
| ip4Header.GetDestination().ExtractFromIp6Address(mNat64Prefix.mLength, ip6Header.GetDestination()); |
| ip4Header.SetTtl(ip6Header.GetHopLimit()); |
| ip4Header.SetIdentification(0); |
| |
| switch (ip6Header.GetNextHeader()) |
| { |
| case Ip6::kProtoUdp: |
| ip4Header.SetProtocol(Ip4::kProtoUdp); |
| res = kForward; |
| break; |
| case Ip6::kProtoTcp: |
| ip4Header.SetProtocol(Ip4::kProtoTcp); |
| res = kForward; |
| break; |
| case Ip6::kProtoIcmp6: |
| ip4Header.SetProtocol(Ip4::kProtoIcmp); |
| SuccessOrExit(TranslateIcmp6(aMessage)); |
| res = kForward; |
| break; |
| default: |
| dropReason = ErrorCounters::Reason::kUnsupportedProto; |
| ExitNow(res = kDrop); |
| } |
| |
| // res 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. |
| LogCrit("failed to prepend IPv4 head to translated message"); |
| ExitNow(res = kDrop); |
| } |
| aMessage.SetType(Message::kTypeIp4); |
| mCounters.Count6To4Packet(ip6Header.GetNextHeader(), ip6Header.GetPayloadLength()); |
| mapping->mCounters.Count6To4Packet(ip6Header.GetNextHeader(), ip6Header.GetPayloadLength()); |
| |
| exit: |
| if (res == Result::kDrop) |
| { |
| mErrorCounters.Count6To4(dropReason); |
| } |
| return res; |
| } |
| |
| Translator::Result Translator::TranslateToIp6(Message &aMessage) |
| { |
| Result res = Result::kDrop; |
| ErrorCounters::Reason dropReason = ErrorCounters::kUnknown; |
| Ip6::Header ip6Header; |
| Ip4::Header ip4Header; |
| AddressMapping *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); |
| |
| 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); |
| } |
| |
| if (!mNat64Prefix.IsValidNat64()) |
| { |
| LogWarn("incoming message is an IPv4 datagram but no NAT64 prefix configured, drop"); |
| ExitNow(res = kDrop); |
| } |
| |
| if (ip4Header.ParseFrom(aMessage) != kErrorNone) |
| { |
| LogWarn("incoming message is neither IPv4 nor an IPv6 datagram, drop"); |
| dropReason = ErrorCounters::Reason::kIllegalPacket; |
| ExitNow(res = kDrop); |
| } |
| |
| mapping = FindMapping(ip4Header.GetDestination()); |
| if (mapping == nullptr) |
| { |
| LogWarn("no mapping found for the IPv4 address"); |
| dropReason = ErrorCounters::Reason::kNoMapping; |
| ExitNow(res = kDrop); |
| } |
| |
| aMessage.RemoveHeader(sizeof(Ip4::Header)); |
| |
| ip6Header.Clear(); |
| ip6Header.InitVersionTrafficClassFlow(); |
| ip6Header.GetSource().SynthesizeFromIp4Address(mNat64Prefix, ip4Header.GetSource()); |
| ip6Header.SetDestination(mapping->mIp6); |
| ip6Header.SetFlow(0); |
| ip6Header.SetHopLimit(ip4Header.GetTtl()); |
| |
| // 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 (ip4Header.GetProtocol()) |
| { |
| case Ip4::kProtoUdp: |
| ip6Header.SetNextHeader(Ip6::kProtoUdp); |
| res = kForward; |
| break; |
| case Ip4::kProtoTcp: |
| ip6Header.SetNextHeader(Ip6::kProtoTcp); |
| res = kForward; |
| break; |
| case Ip4::kProtoIcmp: |
| ip6Header.SetNextHeader(Ip6::kProtoIcmp6); |
| SuccessOrExit(TranslateIcmp4(aMessage)); |
| res = kForward; |
| break; |
| default: |
| dropReason = ErrorCounters::Reason::kUnsupportedProto; |
| ExitNow(res = kDrop); |
| } |
| |
| // res 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); |
| } |
| aMessage.SetType(Message::kTypeIp6); |
| mCounters.Count4To6Packet(ip4Header.GetProtocol(), ip4Header.GetTotalLength() - sizeof(ip4Header)); |
| mapping->mCounters.Count4To6Packet(ip4Header.GetProtocol(), ip4Header.GetTotalLength() - sizeof(ip4Header)); |
| |
| exit: |
| if (res == Result::kDrop) |
| { |
| mErrorCounters.Count4To6(dropReason); |
| } |
| |
| return res; |
| } |
| |
| Translator::AddressMapping::InfoString Translator::AddressMapping::ToString(void) const |
| { |
| InfoString string; |
| |
| string.Append("%s -> %s", mIp6.ToString().AsCString(), mIp4.ToString().AsCString()); |
| |
| return string; |
| } |
| |
| void Translator::AddressMapping::CopyTo(otNat64AddressMapping &aMapping, TimeMilli aNow) const |
| { |
| aMapping.mId = mId; |
| aMapping.mIp4 = mIp4; |
| aMapping.mIp6 = mIp6; |
| 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; |
| } |
| } |
| |
| void Translator::ReleaseMapping(AddressMapping &aMapping) |
| { |
| IgnoreError(mIp4AddressPool.PushBack(aMapping.mIp4)); |
| mAddressMappingPool.Free(aMapping); |
| LogInfo("mapping removed: %s", aMapping.ToString().AsCString()); |
| } |
| |
| uint16_t Translator::ReleaseMappings(LinkedList<AddressMapping> &aMappings) |
| { |
| uint16_t numRemoved = 0; |
| |
| for (AddressMapping *mapping = aMappings.Pop(); mapping != nullptr; mapping = aMappings.Pop()) |
| { |
| numRemoved++; |
| ReleaseMapping(*mapping); |
| } |
| |
| return numRemoved; |
| } |
| |
| uint16_t Translator::ReleaseExpiredMappings(void) |
| { |
| LinkedList<AddressMapping> idleMappings; |
| |
| mActiveAddressMappings.RemoveAllMatching(TimerMilli::GetNow(), idleMappings); |
| |
| return ReleaseMappings(idleMappings); |
| } |
| |
| Translator::AddressMapping *Translator::AllocateMapping(const Ip6::Address &aIp6Addr) |
| { |
| AddressMapping *mapping = nullptr; |
| |
| // The address pool will be no larger than the mapping pool, so checking the address pool is enough. |
| if (mIp4AddressPool.IsEmpty()) |
| { |
| // ReleaseExpiredMappings returns the number of mappings removed. |
| VerifyOrExit(ReleaseExpiredMappings() > 0); |
| } |
| |
| mapping = mAddressMappingPool.Allocate(); |
| // We should get a valid item since address pool is no larger than the mapping pool, and the address pool is not |
| // empty. |
| VerifyOrExit(mapping != nullptr); |
| |
| mActiveAddressMappings.Push(*mapping); |
| mapping->mId = ++mNextMappingId; |
| mapping->mIp6 = aIp6Addr; |
| // PopBack must return a valid address since it is not empty. |
| mapping->mIp4 = *mIp4AddressPool.PopBack(); |
| mapping->Touch(TimerMilli::GetNow()); |
| LogInfo("mapping created: %s", mapping->ToString().AsCString()); |
| |
| exit: |
| return mapping; |
| } |
| |
| Translator::AddressMapping *Translator::FindOrAllocateMapping(const Ip6::Address &aIp6Addr) |
| { |
| AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Addr); |
| |
| // Exit if we found a valid mapping. |
| VerifyOrExit(mapping == nullptr); |
| |
| mapping = AllocateMapping(aIp6Addr); |
| |
| exit: |
| return mapping; |
| } |
| |
| Translator::AddressMapping *Translator::FindMapping(const Ip4::Address &aIp4Addr) |
| { |
| AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Addr); |
| |
| if (mapping != nullptr) |
| { |
| mapping->Touch(TimerMilli::GetNow()); |
| } |
| return mapping; |
| } |
| |
| Error Translator::TranslateIcmp4(Message &aMessage) |
| { |
| Error err = 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)); |
| 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); |
| aMessage.Write(0, icmp6Header); |
| break; |
| } |
| default: |
| err = kErrorInvalidArgs; |
| break; |
| } |
| |
| exit: |
| return err; |
| } |
| |
| Error Translator::TranslateIcmp6(Message &aMessage) |
| { |
| Error err = 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)); |
| 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)); |
| icmp4Header.SetType(Ip4::Icmp::Header::Type::kTypeEchoRequest); |
| aMessage.Write(0, icmp4Header); |
| break; |
| } |
| default: |
| err = kErrorInvalidArgs; |
| break; |
| } |
| |
| exit: |
| return err; |
| } |
| |
| Error Translator::SetIp4Cidr(const Ip4::Cidr &aCidr) |
| { |
| Error err = kErrorNone; |
| |
| uint32_t numberOfHosts; |
| uint32_t hostIdBegin; |
| |
| VerifyOrExit(aCidr.mLength > 0 && aCidr.mLength <= 32, err = 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? |
| if (aCidr.mLength == 32) |
| { |
| hostIdBegin = 0; |
| numberOfHosts = 1; |
| } |
| else if (aCidr.mLength == 31) |
| { |
| hostIdBegin = 0; |
| numberOfHosts = 2; |
| } |
| else |
| { |
| hostIdBegin = 1; |
| numberOfHosts = static_cast<uint32_t>((1 << (Ip4::Address::kSize * 8 - aCidr.mLength)) - 2); |
| } |
| numberOfHosts = OT_MIN(numberOfHosts, kAddressMappingPoolSize); |
| |
| mAddressMappingPool.FreeAll(); |
| mActiveAddressMappings.Clear(); |
| 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)); |
| mIp4Cidr = aCidr; |
| |
| // Always notify the platform when the CIDR is changed. |
| UpdateState(true /* aAlwaysNotify */); |
| |
| exit: |
| return err; |
| } |
| |
| void Translator::SetNat64Prefix(const Ip6::Prefix &aNat64Prefix) |
| { |
| if (aNat64Prefix.GetLength() == 0) |
| { |
| ClearNat64Prefix(); |
| } |
| else if (mNat64Prefix != aNat64Prefix) |
| { |
| LogInfo("IPv6 Prefix for NAT64 updated to %s", aNat64Prefix.ToString().AsCString()); |
| mNat64Prefix = aNat64Prefix; |
| UpdateState(); |
| } |
| } |
| |
| void Translator::ClearNat64Prefix(void) |
| { |
| VerifyOrExit(mNat64Prefix.GetLength() != 0); |
| mNat64Prefix.Clear(); |
| LogInfo("IPv6 Prefix for NAT64 cleared"); |
| UpdateState(); |
| |
| exit: |
| return; |
| } |
| |
| void Translator::HandleMappingExpirerTimer(void) |
| { |
| LogInfo("Released %d expired mappings", ReleaseExpiredMappings()); |
| mMappingExpirerTimer.Start(kAddressMappingIdleTimeoutMsec); |
| } |
| |
| void Translator::InitAddressMappingIterator(AddressMappingIterator &aIterator) |
| { |
| aIterator.mPtr = mActiveAddressMappings.GetHead(); |
| } |
| |
| Error Translator::GetNextAddressMapping(AddressMappingIterator &aIterator, otNat64AddressMapping &aMapping) |
| { |
| Error err = kErrorNotFound; |
| TimeMilli now = TimerMilli::GetNow(); |
| AddressMapping *item = static_cast<AddressMapping *>(aIterator.mPtr); |
| |
| VerifyOrExit(item != nullptr); |
| |
| item->CopyTo(aMapping, now); |
| aIterator.mPtr = item->GetNext(); |
| err = kErrorNone; |
| |
| exit: |
| return err; |
| } |
| |
| Error Translator::GetIp4Cidr(Ip4::Cidr &aCidr) |
| { |
| Error err = kErrorNone; |
| |
| VerifyOrExit(mIp4Cidr.mLength > 0, err = kErrorNotFound); |
| aCidr = mIp4Cidr; |
| |
| exit: |
| return err; |
| } |
| |
| Error Translator::GetIp6Prefix(Ip6::Prefix &aPrefix) |
| { |
| Error err = kErrorNone; |
| |
| VerifyOrExit(mNat64Prefix.mLength > 0, err = kErrorNotFound); |
| aPrefix = mNat64Prefix; |
| |
| exit: |
| return err; |
| } |
| |
| void Translator::ProtocolCounters::Count6To4Packet(uint8_t aProtocol, uint64_t aPacketSize) |
| { |
| switch (aProtocol) |
| { |
| case Ip6::kProtoUdp: |
| mUdp.m6To4Packets++; |
| mUdp.m6To4Bytes += aPacketSize; |
| break; |
| case Ip6::kProtoTcp: |
| mTcp.m6To4Packets++; |
| mTcp.m6To4Bytes += aPacketSize; |
| break; |
| case Ip6::kProtoIcmp6: |
| mIcmp.m6To4Packets++; |
| mIcmp.m6To4Bytes += aPacketSize; |
| break; |
| } |
| |
| mTotal.m6To4Packets++; |
| mTotal.m6To4Bytes += aPacketSize; |
| } |
| |
| void Translator::ProtocolCounters::Count4To6Packet(uint8_t aProtocol, uint64_t aPacketSize) |
| { |
| switch (aProtocol) |
| { |
| case Ip4::kProtoUdp: |
| mUdp.m4To6Packets++; |
| mUdp.m4To6Bytes += aPacketSize; |
| break; |
| case Ip4::kProtoTcp: |
| mTcp.m4To6Packets++; |
| mTcp.m4To6Bytes += aPacketSize; |
| break; |
| case Ip4::kProtoIcmp: |
| mIcmp.m4To6Packets++; |
| mIcmp.m4To6Bytes += aPacketSize; |
| break; |
| } |
| |
| mTotal.m4To6Packets++; |
| mTotal.m4To6Bytes += aPacketSize; |
| } |
| |
| void Translator::UpdateState(bool aAlwaysNotify) |
| { |
| State newState; |
| |
| if (mEnabled) |
| { |
| if (mIp4Cidr.mLength > 0 && mNat64Prefix.IsValidNat64()) |
| { |
| newState = kStateActive; |
| } |
| else |
| { |
| newState = kStateNotRunning; |
| } |
| } |
| else |
| { |
| newState = kStateDisabled; |
| } |
| |
| if (aAlwaysNotify) |
| { |
| Get<Notifier>().Signal(kEventNat64TranslatorStateChanged); |
| } |
| else |
| { |
| SuccessOrExit(Get<Notifier>().Update(mState, newState, kEventNat64TranslatorStateChanged)); |
| } |
| LogInfo("NAT64 translator is now %s", StateToString(mState)); |
| |
| exit: |
| return; |
| } |
| |
| void Translator::SetEnabled(bool aEnabled) |
| { |
| VerifyOrExit(mEnabled != aEnabled); |
| mEnabled = aEnabled; |
| |
| if (!aEnabled) |
| { |
| ReleaseMappings(mActiveAddressMappings); |
| } |
| |
| UpdateState(); |
| |
| exit: |
| return; |
| } |
| |
| } // namespace Nat64 |
| } // namespace ot |
| |
| #endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE |