mirror of
https://github.com/espressif/openthread.git
synced 2026-08-29 21:39:54 +00:00
[nat64] add support for port translation (#11043)
This commit adds support for port translation in NAT64. Currently the translator only supports a direct mapping between an IPv4 and an IPv6 address requiring a large IPv4 pool to work effectively. This implies the need for an additional NAT 4 to 4 translator as the border router will only have one private IPv4 in the LAN. This commit enhances the current functionality according to NAT64 RFC and dynamic PAT mechanism. This is used by any home router to share a public IPv4 address with a range of devices using private addresses. The port translation ensures the source port or the ICMP identifier is always unique in the mapping table allowing a reply packet to be matched with the request using the destination port and address. This functionality can be disabled at compile time. Signed-off-by: Marius Preda <[email protected]>
This commit is contained in:
@@ -52,7 +52,7 @@ extern "C" {
|
||||
*
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (472)
|
||||
#define OPENTHREAD_API_VERSION (473)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -158,11 +158,20 @@ void otNat64GetErrorCounters(otInstance *aInstance, otNat64ErrorCounters *aCount
|
||||
*/
|
||||
typedef struct otNat64AddressMapping
|
||||
{
|
||||
uint64_t mId; ///< The unique id for a mapping session.
|
||||
uint64_t mId; ///< The unique id for a mapping session.
|
||||
otIp4Address mIp4; ///< The IPv4 address of the mapping.
|
||||
otIp6Address mIp6; ///< The IPv6 address of the mapping.
|
||||
|
||||
otIp4Address mIp4; ///< The IPv4 address of the mapping.
|
||||
otIp6Address mIp6; ///< The IPv6 address of the mapping.
|
||||
uint32_t mRemainingTimeMs; ///< Remaining time before expiry in milliseconds.
|
||||
/** The source port or ICMP ID of the mapping. Used when
|
||||
* OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE is true.
|
||||
*/
|
||||
uint16_t mSrcPortOrId;
|
||||
|
||||
/** The translated port or ICMP ID of the mapping. Used when
|
||||
* OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE is true.
|
||||
*/
|
||||
uint16_t mTranslatedPortOrId;
|
||||
uint32_t mRemainingTimeMs; ///< Remaining time before expiry in milliseconds.
|
||||
|
||||
otNat64ProtocolCounters mCounters;
|
||||
} otNat64AddressMapping;
|
||||
|
||||
+23
-15
@@ -867,32 +867,33 @@ template <> otError Interpreter::Process<Cmd("nat64")>(Arg aArgs[])
|
||||
* @cli nat64 mappings
|
||||
* @code
|
||||
* nat64 mappings
|
||||
* | | Address | | 4 to 6 | 6 to 4 |
|
||||
* +----------+---------------------------+--------+--------------+--------------+
|
||||
* | ID | IPv6 | IPv4 | Expiry | Pkts | Bytes | Pkts | Bytes |
|
||||
* +----------+------------+--------------+--------+------+-------+------+-------+
|
||||
* | 00021cb9 | fdc7::df79 | 192.168.64.2 | 7196s | 6 | 456 | 11 | 1928 |
|
||||
* | | TCP | 0 | 0 | 0 | 0 |
|
||||
* | | UDP | 1 | 136 | 16 | 1608 |
|
||||
* | | ICMP | 5 | 320 | 5 | 320 |
|
||||
* | | Address | Ports or ICMP Ids | | 4 to 6 | 6 to 4 |
|
||||
* +----------+---------------------------+-----------------------+--------+--------------+--------------+
|
||||
* | ID | IPv6 | IPv4 | v6 | v4 | Expiry | Pkts | Bytes | Pkts | Bytes |
|
||||
* +----------+------------+--------------+-----------------------+--------+------+-------+------+-------+
|
||||
* | 00021cb9 | fdc7::df79 | 192.168.64.2 | 65100 | 65200 | 7196s | 6 | 456 | 11 | 1928 |
|
||||
* | TCP | 0 | 0 | 0 | 0 |
|
||||
* | UDP | 1 | 136 | 16 | 1608 |
|
||||
* | ICMP | 5 | 320 | 5 | 320 |
|
||||
* @endcode
|
||||
* @par api_copy
|
||||
* #otNat64GetNextAddressMapping
|
||||
*/
|
||||
else if (aArgs[0] == "mappings")
|
||||
{
|
||||
static const char *const kNat64StatusLevel1Title[] = {"", "Address", "", "4 to 6", "6 to 4"};
|
||||
static const char *const kNat64StatusLevel1Title[] = {"", "Address", "Ports or ICMP Ids",
|
||||
"", "4 to 6", "6 to 4"};
|
||||
|
||||
static const uint8_t kNat64StatusLevel1ColumnWidths[] = {
|
||||
18, 61, 8, 25, 25,
|
||||
18, 61, 19, 8, 25, 25,
|
||||
};
|
||||
|
||||
static const char *const kNat64StatusTableHeader[] = {
|
||||
"ID", "IPv6", "IPv4", "Expiry", "Pkts", "Bytes", "Pkts", "Bytes",
|
||||
"ID", "IPv6", "IPv4", "v6", "v4", "Expiry", "Pkts", "Bytes", "Pkts", "Bytes",
|
||||
};
|
||||
|
||||
static const uint8_t kNat64StatusTableColumnWidths[] = {
|
||||
18, 42, 18, 8, 10, 14, 10, 14,
|
||||
18, 42, 18, 9, 9, 8, 10, 14, 10, 14,
|
||||
};
|
||||
|
||||
otNat64AddressMappingIterator iterator;
|
||||
@@ -914,19 +915,26 @@ template <> otError Interpreter::Process<Cmd("nat64")>(Arg aArgs[])
|
||||
ToUlong(static_cast<uint32_t>(mapping.mId & 0xffffffff)));
|
||||
OutputFormat("| %40s ", ip6AddressString);
|
||||
OutputFormat("| %16s ", ip4AddressString);
|
||||
#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
OutputFormat("| %6u ", mapping.mSrcPortOrId);
|
||||
OutputFormat("| %6u ", mapping.mTranslatedPortOrId);
|
||||
#else
|
||||
OutputFormat("| N/A ");
|
||||
OutputFormat("| N/A ");
|
||||
#endif
|
||||
OutputFormat("| %5lus ", ToUlong(mapping.mRemainingTimeMs / 1000));
|
||||
OutputNat64Counters(mapping.mCounters.mTotal);
|
||||
|
||||
OutputFormat("| %16s ", "");
|
||||
OutputFormat("| %68s ", "TCP");
|
||||
OutputFormat("| %88s ", "TCP");
|
||||
OutputNat64Counters(mapping.mCounters.mTcp);
|
||||
|
||||
OutputFormat("| %16s ", "");
|
||||
OutputFormat("| %68s ", "UDP");
|
||||
OutputFormat("| %88s ", "UDP");
|
||||
OutputNat64Counters(mapping.mCounters.mUdp);
|
||||
|
||||
OutputFormat("| %16s ", "");
|
||||
OutputFormat("| %68s ", "ICMP");
|
||||
OutputFormat("| %88s ", "ICMP");
|
||||
OutputNat64Counters(mapping.mCounters.mIcmp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,15 @@
|
||||
#define OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS 7200
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NAT64_ICMP_IDLE_TIMEOUT_SECONDS
|
||||
*
|
||||
* Specifies timeout in seconds before removing an inactive ICMP address mapping.
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_NAT64_ICMP_IDLE_TIMEOUT_SECONDS
|
||||
#define OPENTHREAD_CONFIG_NAT64_ICMP_IDLE_TIMEOUT_SECONDS OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
|
||||
*
|
||||
@@ -79,6 +88,17 @@
|
||||
#define OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
*
|
||||
* Define to 1 to enable NAT64 support for port translation.
|
||||
* This allows the NAT64 translator to operate with just one
|
||||
* or a limited number of IPv4 addresses.
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
#define OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -111,9 +111,10 @@ Translator::Result Translator::TranslateFromIp6(Message &aMessage)
|
||||
{
|
||||
Result res = kDrop;
|
||||
ErrorCounters::Reason dropReason = ErrorCounters::kUnknown;
|
||||
Ip6::Header ip6Header;
|
||||
Ip6::Headers ip6Headers;
|
||||
Ip4::Header ip4Header;
|
||||
AddressMapping *mapping = nullptr;
|
||||
uint16_t srcPortOrId = 0;
|
||||
AddressMapping *mapping = nullptr;
|
||||
|
||||
if (mIp4Cidr.mLength == 0 || !mNat64Prefix.IsValidNat64())
|
||||
{
|
||||
@@ -121,48 +122,60 @@ Translator::Result Translator::TranslateFromIp6(Message &aMessage)
|
||||
}
|
||||
|
||||
// ParseFrom will do basic checks for the message, including the message length and IP protocol version.
|
||||
if (ip6Header.ParseFrom(aMessage) != kErrorNone)
|
||||
if (ip6Headers.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))
|
||||
if (!ip6Headers.GetDestinationAddress().MatchesPrefix(mNat64Prefix))
|
||||
{
|
||||
ExitNow(res = kNotTranslated);
|
||||
}
|
||||
|
||||
mapping = FindOrAllocateMapping(ip6Header.GetSource());
|
||||
mapping = FindOrAllocateMapping(ip6Headers);
|
||||
if (mapping == nullptr)
|
||||
{
|
||||
LogWarn("failed to get a mapping for %s (mapping pool full?)", ip6Header.GetSource().ToString().AsCString());
|
||||
LogWarn("failed to get a mapping for %s (mapping pool full?)",
|
||||
ip6Headers.GetSourceAddress().ToString().AsCString());
|
||||
dropReason = ErrorCounters::Reason::kNoMapping;
|
||||
ExitNow(res = kDrop);
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
srcPortOrId = mapping->mTranslatedPortOrId;
|
||||
#else
|
||||
srcPortOrId = ip6Headers.IsIcmp6() ? ip6Headers.GetIcmpHeader().GetId() : ip6Headers.GetSourcePort();
|
||||
#endif
|
||||
|
||||
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.GetDestination().ExtractFromIp6Address(mNat64Prefix.mLength, ip6Headers.GetDestinationAddress());
|
||||
ip4Header.SetTtl(ip6Headers.GetIpHopLimit());
|
||||
ip4Header.SetIdentification(0);
|
||||
|
||||
switch (ip6Header.GetNextHeader())
|
||||
switch (ip6Headers.GetIpProto())
|
||||
{
|
||||
// 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;
|
||||
break;
|
||||
case Ip6::kProtoTcp:
|
||||
ip4Header.SetProtocol(Ip4::kProtoTcp);
|
||||
ip6Headers.SetSourcePort(srcPortOrId);
|
||||
aMessage.Write(0, ip6Headers.GetTcpHeader());
|
||||
res = kForward;
|
||||
break;
|
||||
case Ip6::kProtoIcmp6:
|
||||
ip4Header.SetProtocol(Ip4::kProtoIcmp);
|
||||
SuccessOrExit(TranslateIcmp6(aMessage));
|
||||
SuccessOrExit(TranslateIcmp6(aMessage, srcPortOrId));
|
||||
res = kForward;
|
||||
break;
|
||||
default:
|
||||
@@ -183,8 +196,8 @@ Translator::Result Translator::TranslateFromIp6(Message &aMessage)
|
||||
ExitNow(res = kDrop);
|
||||
}
|
||||
aMessage.SetType(Message::kTypeIp4);
|
||||
mCounters.Count6To4Packet(ip6Header.GetNextHeader(), ip6Header.GetPayloadLength());
|
||||
mapping->mCounters.Count6To4Packet(ip6Header.GetNextHeader(), ip6Header.GetPayloadLength());
|
||||
mCounters.Count6To4Packet(ip6Headers.GetIpProto(), ip6Headers.GetIpLength());
|
||||
mapping->mCounters.Count6To4Packet(ip6Headers.GetIpProto(), ip6Headers.GetIpLength());
|
||||
|
||||
exit:
|
||||
if (res == Result::kDrop)
|
||||
@@ -199,8 +212,9 @@ Translator::Result Translator::TranslateToIp6(Message &aMessage)
|
||||
Result res = Result::kDrop;
|
||||
ErrorCounters::Reason dropReason = ErrorCounters::kUnknown;
|
||||
Ip6::Header ip6Header;
|
||||
Ip4::Header ip4Header;
|
||||
AddressMapping *mapping = nullptr;
|
||||
Ip4::Headers ip4Headers;
|
||||
uint16_t dstPortOrId = 0;
|
||||
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.
|
||||
@@ -219,14 +233,15 @@ Translator::Result Translator::TranslateToIp6(Message &aMessage)
|
||||
ExitNow(res = kDrop);
|
||||
}
|
||||
|
||||
if (ip4Header.ParseFrom(aMessage) != kErrorNone)
|
||||
if (ip4Headers.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());
|
||||
mapping = FindMapping(ip4Headers);
|
||||
|
||||
if (mapping == nullptr)
|
||||
{
|
||||
LogWarn("no mapping found for the IPv4 address");
|
||||
@@ -234,30 +249,41 @@ Translator::Result Translator::TranslateToIp6(Message &aMessage)
|
||||
ExitNow(res = kDrop);
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
dstPortOrId = mapping->mSrcPortOrId;
|
||||
#else
|
||||
dstPortOrId = ip4Headers.IsIcmp4() ? ip4Headers.GetIcmpHeader().GetId() : ip4Headers.GetDestinationPort();
|
||||
#endif
|
||||
|
||||
aMessage.RemoveHeader(sizeof(Ip4::Header));
|
||||
|
||||
ip6Header.Clear();
|
||||
ip6Header.InitVersionTrafficClassFlow();
|
||||
ip6Header.GetSource().SynthesizeFromIp4Address(mNat64Prefix, ip4Header.GetSource());
|
||||
ip6Header.GetSource().SynthesizeFromIp4Address(mNat64Prefix, ip4Headers.GetSourceAddress());
|
||||
ip6Header.SetDestination(mapping->mIp6);
|
||||
ip6Header.SetFlow(0);
|
||||
ip6Header.SetHopLimit(ip4Header.GetTtl());
|
||||
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.
|
||||
switch (ip4Header.GetProtocol())
|
||||
switch (ip4Headers.GetIpProto())
|
||||
{
|
||||
// The IP header is consumed , so the next header is at offset 0.
|
||||
case Ip4::kProtoUdp:
|
||||
ip6Header.SetNextHeader(Ip6::kProtoUdp);
|
||||
ip4Headers.SetDestinationPort(dstPortOrId);
|
||||
aMessage.Write(0, ip4Headers.GetUdpHeader());
|
||||
res = kForward;
|
||||
break;
|
||||
case Ip4::kProtoTcp:
|
||||
ip6Header.SetNextHeader(Ip6::kProtoTcp);
|
||||
ip4Headers.SetDestinationPort(dstPortOrId);
|
||||
aMessage.Write(0, ip4Headers.GetTcpHeader());
|
||||
res = kForward;
|
||||
break;
|
||||
case Ip4::kProtoIcmp:
|
||||
ip6Header.SetNextHeader(Ip6::kProtoIcmp6);
|
||||
SuccessOrExit(TranslateIcmp4(aMessage));
|
||||
SuccessOrExit(TranslateIcmp4(aMessage, dstPortOrId));
|
||||
res = kForward;
|
||||
break;
|
||||
default:
|
||||
@@ -277,8 +303,8 @@ Translator::Result Translator::TranslateToIp6(Message &aMessage)
|
||||
ExitNow(res = kDrop);
|
||||
}
|
||||
aMessage.SetType(Message::kTypeIp6);
|
||||
mCounters.Count4To6Packet(ip4Header.GetProtocol(), ip4Header.GetTotalLength() - sizeof(ip4Header));
|
||||
mapping->mCounters.Count4To6Packet(ip4Header.GetProtocol(), ip4Header.GetTotalLength() - sizeof(ip4Header));
|
||||
mCounters.Count4To6Packet(ip4Headers.GetIpProto(), ip4Headers.GetIpLength() - sizeof(Ip4::Header));
|
||||
mapping->mCounters.Count4To6Packet(ip4Headers.GetIpProto(), ip4Headers.GetIpLength() - sizeof(Ip4::Header));
|
||||
|
||||
exit:
|
||||
if (res == Result::kDrop)
|
||||
@@ -300,10 +326,12 @@ Translator::AddressMapping::InfoString Translator::AddressMapping::ToString(void
|
||||
|
||||
void Translator::AddressMapping::CopyTo(otNat64AddressMapping &aMapping, TimeMilli aNow) const
|
||||
{
|
||||
aMapping.mId = mId;
|
||||
aMapping.mIp4 = mIp4;
|
||||
aMapping.mIp6 = mIp6;
|
||||
aMapping.mCounters = mCounters;
|
||||
aMapping.mId = mId;
|
||||
aMapping.mIp4 = mIp4;
|
||||
aMapping.mIp6 = mIp6;
|
||||
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.
|
||||
@@ -319,7 +347,12 @@ void Translator::AddressMapping::CopyTo(otNat64AddressMapping &aMapping, TimeMil
|
||||
|
||||
void Translator::ReleaseMapping(AddressMapping &aMapping)
|
||||
{
|
||||
IgnoreError(mIp4AddressPool.PushBack(aMapping.mIp4));
|
||||
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));
|
||||
}
|
||||
mAddressMappingPool.Free(aMapping);
|
||||
LogInfo("mapping removed: %s", aMapping.ToString().AsCString());
|
||||
}
|
||||
@@ -345,61 +378,130 @@ uint16_t Translator::ReleaseExpiredMappings(void)
|
||||
|
||||
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;
|
||||
|
||||
Translator::AddressMapping *Translator::AllocateMapping(const Ip6::Address &aIp6Addr)
|
||||
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));
|
||||
|
||||
return retPort;
|
||||
}
|
||||
#endif
|
||||
|
||||
Translator::AddressMapping *Translator::AllocateMapping(const Ip6::Headers &aIp6Headers)
|
||||
{
|
||||
AddressMapping *mapping = nullptr;
|
||||
Ip4::Address ip4Addr;
|
||||
|
||||
// The address pool will be no larger than the mapping pool, so checking the address pool is enough.
|
||||
if (mIp4AddressPool.IsEmpty())
|
||||
// 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)
|
||||
{
|
||||
// ReleaseExpiredMappings returns the number of mappings removed.
|
||||
VerifyOrExit(ReleaseExpiredMappings() > 0);
|
||||
// TODO: add logic to cycle between available IPv4 addresses
|
||||
ip4Addr = *mIp4AddressPool.At(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mIp4AddressPool.IsEmpty())
|
||||
{
|
||||
// ReleaseExpiredMappings returns the number of mappings removed.
|
||||
VerifyOrExit(ReleaseExpiredMappings() > 0);
|
||||
}
|
||||
ip4Addr = *mIp4AddressPool.PopBack();
|
||||
}
|
||||
|
||||
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.
|
||||
// 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);
|
||||
mapping->mId = ++mNextMappingId;
|
||||
mapping->mCounters.Clear();
|
||||
mapping->mIp6 = aIp6Addr;
|
||||
// PopBack must return a valid address since it is not empty.
|
||||
mapping->mIp4 = *mIp4AddressPool.PopBack();
|
||||
mapping->Touch(TimerMilli::GetNow());
|
||||
mapping->mId = ++mNextMappingId;
|
||||
mapping->mIp6 = aIp6Headers.GetSourceAddress();
|
||||
mapping->mIp4 = 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());
|
||||
|
||||
exit:
|
||||
return mapping;
|
||||
}
|
||||
|
||||
Translator::AddressMapping *Translator::FindOrAllocateMapping(const Ip6::Address &aIp6Addr)
|
||||
Translator::AddressMapping *Translator::FindOrAllocateMapping(const Ip6::Headers &aIp6Headers)
|
||||
{
|
||||
AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Addr);
|
||||
#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
uint16_t srcPortOrId = aIp6Headers.IsIcmp6() ? aIp6Headers.GetIcmpHeader().GetId() : aIp6Headers.GetSourcePort();
|
||||
AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Headers.GetSourceAddress(), srcPortOrId);
|
||||
#else
|
||||
AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp6Headers.GetSourceAddress());
|
||||
#endif
|
||||
|
||||
// Exit if we found a valid mapping.
|
||||
VerifyOrExit(mapping == nullptr);
|
||||
|
||||
mapping = AllocateMapping(aIp6Addr);
|
||||
mapping = AllocateMapping(aIp6Headers);
|
||||
|
||||
exit:
|
||||
return mapping;
|
||||
}
|
||||
|
||||
Translator::AddressMapping *Translator::FindMapping(const Ip4::Address &aIp4Addr)
|
||||
Translator::AddressMapping *Translator::FindMapping(const Ip4::Headers &aIp4Headers)
|
||||
{
|
||||
AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Addr);
|
||||
uint16_t dstPortOrId =
|
||||
aIp4Headers.IsIcmp4() ? aIp4Headers.GetIcmpHeader().GetId() : aIp4Headers.GetDestinationPort();
|
||||
|
||||
#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Headers.GetDestinationAddress(), dstPortOrId);
|
||||
#else
|
||||
AddressMapping *mapping = mActiveAddressMappings.FindMatching(aIp4Headers.GetDestinationAddress());
|
||||
OT_UNUSED_VARIABLE(dstPortOrId);
|
||||
#endif
|
||||
|
||||
if (mapping != nullptr)
|
||||
{
|
||||
mapping->Touch(TimerMilli::GetNow());
|
||||
mapping->Touch(TimerMilli::GetNow(), aIp4Headers.GetIpProto());
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
Error Translator::TranslateIcmp4(Message &aMessage)
|
||||
void Translator::AddressMapping::Touch(TimeMilli aNow, uint8_t aProtocol)
|
||||
{
|
||||
if ((aProtocol == Ip6::kProtoIcmp6) || (aProtocol == Ip4::kProtoIcmp))
|
||||
{
|
||||
mExpiry = aNow + kAddressMappingIcmpIdleTimeoutMsec;
|
||||
}
|
||||
else
|
||||
{
|
||||
mExpiry = aNow + kAddressMappingIdleTimeoutMsec;
|
||||
}
|
||||
}
|
||||
|
||||
Error Translator::TranslateIcmp4(Message &aMessage, uint16_t aOriginalId)
|
||||
{
|
||||
Error err = kErrorNone;
|
||||
Ip4::Icmp::Header icmp4Header;
|
||||
@@ -417,6 +519,7 @@ Error Translator::TranslateIcmp4(Message &aMessage)
|
||||
// ICMP6 header and set the message type.
|
||||
SuccessOrExit(err = aMessage.Read(0, icmp6Header));
|
||||
icmp6Header.SetType(Ip6::Icmp::Header::Type::kTypeEchoReply);
|
||||
icmp6Header.SetId(aOriginalId);
|
||||
aMessage.Write(0, icmp6Header);
|
||||
break;
|
||||
}
|
||||
@@ -429,7 +532,7 @@ exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
Error Translator::TranslateIcmp6(Message &aMessage)
|
||||
Error Translator::TranslateIcmp6(Message &aMessage, uint16_t aTranslatedId)
|
||||
{
|
||||
Error err = kErrorNone;
|
||||
Ip4::Icmp::Header icmp4Header;
|
||||
@@ -447,6 +550,7 @@ Error Translator::TranslateIcmp6(Message &aMessage)
|
||||
// ICMP6 header and set the message type.
|
||||
SuccessOrExit(err = aMessage.Read(0, icmp4Header));
|
||||
icmp4Header.SetType(Ip4::Icmp::Header::Type::kTypeEchoRequest);
|
||||
icmp4Header.SetId(aTranslatedId);
|
||||
aMessage.Write(0, icmp4Header);
|
||||
break;
|
||||
}
|
||||
@@ -556,7 +660,7 @@ void Translator::HandleMappingExpirerTimer(void)
|
||||
|
||||
LogInfo("Released %u expired mappings", numReleased);
|
||||
|
||||
mMappingExpirerTimer.Start(kAddressMappingIdleTimeoutMsec);
|
||||
mMappingExpirerTimer.Start(Min(kAddressMappingIcmpIdleTimeoutMsec, kAddressMappingIdleTimeoutMsec));
|
||||
|
||||
OT_UNUSED_VARIABLE(numReleased);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,17 @@ class Translator : public InstanceLocator, private NonCopyable
|
||||
public:
|
||||
static constexpr uint32_t kAddressMappingIdleTimeoutMsec =
|
||||
OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS * Time::kOneSecondInMsec;
|
||||
static constexpr uint32_t kAddressMappingPoolSize = OPENTHREAD_CONFIG_NAT64_MAX_MAPPINGS;
|
||||
// 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 otNat64AddressMappingIterator AddressMappingIterator; ///< Address mapping Iterator.
|
||||
|
||||
@@ -328,7 +338,7 @@ private:
|
||||
|
||||
typedef String<Ip6::Address::kInfoStringSize + Ip4::Address::kAddressStringSize + 4> InfoString;
|
||||
|
||||
void Touch(TimeMilli aNow) { mExpiry = aNow + kAddressMappingIdleTimeoutMsec; }
|
||||
void Touch(TimeMilli aNow, uint8_t aProtocol);
|
||||
InfoString ToString(void) const;
|
||||
void CopyTo(otNat64AddressMapping &aMapping, TimeMilli aNow) const;
|
||||
|
||||
@@ -336,6 +346,8 @@ private:
|
||||
|
||||
Ip4::Address mIp4;
|
||||
Ip6::Address mIp6;
|
||||
uint16_t mSrcPortOrId;
|
||||
uint16_t mTranslatedPortOrId;
|
||||
TimeMilli mExpiry; // The timestamp when this mapping expires, in milliseconds.
|
||||
|
||||
ProtocolCounters mCounters;
|
||||
@@ -343,22 +355,34 @@ private:
|
||||
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);
|
||||
Error TranslateIcmp6(Message &aMessage);
|
||||
Error TranslateIcmp4(Message &aMessage, uint16_t aOriginalId);
|
||||
Error TranslateIcmp6(Message &aMessage, uint16_t aTranslatedId);
|
||||
|
||||
#if OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE
|
||||
uint16_t AllocateSourcePort(uint16_t aSrcPort);
|
||||
#endif
|
||||
uint16_t ReleaseMappings(LinkedList<AddressMapping> &aMappings);
|
||||
void ReleaseMapping(AddressMapping &aMapping);
|
||||
uint16_t ReleaseExpiredMappings(void);
|
||||
AddressMapping *AllocateMapping(const Ip6::Address &aIp6Addr);
|
||||
AddressMapping *FindOrAllocateMapping(const Ip6::Address &aIp6Addr);
|
||||
AddressMapping *FindMapping(const Ip4::Address &aIp4Addr);
|
||||
|
||||
void HandleMappingExpirerTimer(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<Translator, &Translator::HandleMappingExpirerTimer>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user