net/nat: Support Symmetric NAT

The symmetric NAT limits one external port to be used with only one peer ip:port.

Note:
1. To avoid using too much #ifdef, we're always passing peer_ip and peer_port as arguments, but won't use them under full cone NAT, let the compiler optimize them.
2. We need to find port binding without peer ip:port, so don't add peer ip:port into hash key.
3. Symmetric NAT needs to *select another external port if a port is used by any other NAT entry*, this behavior is exactly same as Full Cone NAT, so we don't need to change anything related to `ipv4_nat_port_inuse`.

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
This commit is contained in:
Zhe Weng
2024-03-14 15:22:13 +08:00
committed by Xiang Xiao
parent 0ede3fc377
commit a1a09f271f
5 changed files with 108 additions and 11 deletions
+13 -2
View File
@@ -2,7 +2,7 @@
Network Address Translation (NAT)
=================================
NuttX supports full cone NAT logic, which currently supports
NuttX supports full cone or symmetric NAT logic, which currently supports
- TCP
@@ -46,6 +46,12 @@ Configuration Options
``CONFIG_NET_NAT``
Enable or disable Network Address Translation (NAT) function.
Depends on ``CONFIG_NET_IPFORWARD``.
``CONFIG_NET_NAT_FULL_CONE``
Enable Full Cone NAT logic. Full Cone NAT is easier to traverse than
Symmetric NAT, and uses less resources than Symmetric NAT.
``CONFIG_NET_NAT_SYMMETRIC``
Enable Symmetric NAT logic. Symmetric NAT will be safer than Full Cone NAT,
be more difficult to traverse, and has more entries which may lead to heavier load.
``CONFIG_NET_NAT_HASH_BITS``
The bits of the hashtable of NAT entries, hashtable has (1 << bits) buckets.
``CONFIG_NET_NAT_TCP_EXPIRE_SEC``
@@ -101,7 +107,8 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
# CONFIG_SIM_NET_BRIDGE is not set
CONFIG_SIM_NETDEV_NUMBER=2
2. Call ``ipv4_nat_enable`` on one dev on startup
2. Call ``ipv4_nat_enable`` on one dev on startup, or manually enable NAT
with ``iptables`` command (either may work).
.. code-block:: c
@@ -113,6 +120,10 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
...
}
.. code-block:: shell
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
3. Set IP Address for NuttX on startup
.. code-block:: shell
+19
View File
@@ -15,6 +15,25 @@ config NET_NAT
and NAT may need a continuous buffer of at least 68 Bytes
(IPv4 20B + ICMP 8B + IPv4 20B + TCP 20B).
choice
prompt "NAT Type"
default NET_NAT_FULL_CONE
depends on NET_NAT
config NET_NAT_FULL_CONE
bool "Full Cone NAT"
---help---
Full Cone NAT is easier to traverse than Symmetric NAT, and uses
less resources than Symmetric NAT.
config NET_NAT_SYMMETRIC
bool "Symmetric NAT"
---help---
Symmetric NAT will be safer than Full Cone NAT, be more difficult
to traverse, and has more entries which may lead to heavier load.
endchoice
config NET_NAT_HASH_BITS
int "The bits of NAT entry hashtable"
default 5
+31 -3
View File
@@ -58,6 +58,14 @@
#define MANIP_PORT(l4hdr,manip_type) \
((manip_type) == NAT_MANIP_SRC ? &(l4hdr)->srcport : &(l4hdr)->destport)
/* Getting peer IP & Port (just other than MANIP) */
#define PEER_IPADDR(iphdr,manip_type) \
((manip_type) != NAT_MANIP_SRC ? (iphdr)->srcipaddr : (iphdr)->destipaddr)
#define PEER_PORT(l4hdr,manip_type) \
((manip_type) != NAT_MANIP_SRC ? &(l4hdr)->srcport : &(l4hdr)->destport)
/* Getting L4 header from IPv4 header. */
#define L4_HDR(ipv4) \
@@ -204,10 +212,14 @@ ipv4_nat_inbound_tcp(FAR struct ipv4_hdr_s *ipv4,
FAR struct tcp_hdr_s *tcp = L4_HDR(ipv4);
FAR uint16_t *external_ip = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *external_port = MANIP_PORT(tcp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv4, manip_type);
FAR uint16_t *peer_port = PEER_PORT(tcp, manip_type);
FAR struct ipv4_nat_entry *entry =
ipv4_nat_inbound_entry_find(IP_PROTO_TCP,
net_ip4addr_conv32(external_ip),
*external_port, true);
*external_port,
net_ip4addr_conv32(peer_ip),
*peer_port, true);
if (!entry)
{
return NULL;
@@ -253,11 +265,15 @@ ipv4_nat_inbound_udp(FAR struct ipv4_hdr_s *ipv4,
FAR struct udp_hdr_s *udp = L4_HDR(ipv4);
FAR uint16_t *external_ip = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *external_port = MANIP_PORT(udp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv4, manip_type);
FAR uint16_t *peer_port = PEER_PORT(udp, manip_type);
FAR uint16_t *udpchksum;
FAR struct ipv4_nat_entry *entry =
ipv4_nat_inbound_entry_find(IP_PROTO_UDP,
net_ip4addr_conv32(external_ip),
*external_port, true);
*external_port,
net_ip4addr_conv32(peer_ip),
*peer_port, true);
if (!entry)
{
@@ -302,6 +318,7 @@ ipv4_nat_inbound_icmp(FAR struct ipv4_hdr_s *ipv4,
{
FAR struct icmp_hdr_s *icmp = L4_HDR(ipv4);
FAR uint16_t *external_ip;
FAR uint16_t *peer_ip;
FAR struct ipv4_nat_entry *entry;
switch (icmp->type)
@@ -309,8 +326,11 @@ ipv4_nat_inbound_icmp(FAR struct ipv4_hdr_s *ipv4,
case ICMP_ECHO_REQUEST:
case ICMP_ECHO_REPLY:
external_ip = MANIP_IPADDR(ipv4, manip_type);
peer_ip = PEER_IPADDR(ipv4, manip_type);
entry = ipv4_nat_inbound_entry_find(IP_PROTO_ICMP,
net_ip4addr_conv32(external_ip),
icmp->id,
net_ip4addr_conv32(peer_ip),
icmp->id, true);
if (!entry)
{
@@ -424,12 +444,15 @@ ipv4_nat_outbound_tcp(FAR struct net_driver_s *dev,
FAR struct tcp_hdr_s *tcp = L4_HDR(ipv4);
FAR uint16_t *local_ip = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *local_port = MANIP_PORT(tcp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv4, manip_type);
FAR uint16_t *peer_port = PEER_PORT(tcp, manip_type);
FAR struct ipv4_nat_entry *entry;
/* Only create entry when it's the outermost packet (manip type is SRC). */
entry = ipv4_nat_outbound_entry_find(dev, IP_PROTO_TCP,
net_ip4addr_conv32(local_ip), *local_port,
net_ip4addr_conv32(peer_ip), *peer_port,
(manip_type == NAT_MANIP_SRC));
if (!entry)
{
@@ -477,6 +500,8 @@ ipv4_nat_outbound_udp(FAR struct net_driver_s *dev,
FAR struct udp_hdr_s *udp = L4_HDR(ipv4);
FAR uint16_t *local_ip = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *local_port = MANIP_PORT(udp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv4, manip_type);
FAR uint16_t *peer_port = PEER_PORT(udp, manip_type);
FAR uint16_t *udpchksum;
FAR struct ipv4_nat_entry *entry;
@@ -484,6 +509,7 @@ ipv4_nat_outbound_udp(FAR struct net_driver_s *dev,
entry = ipv4_nat_outbound_entry_find(dev, IP_PROTO_UDP,
net_ip4addr_conv32(local_ip), *local_port,
net_ip4addr_conv32(peer_ip), *peer_port,
(manip_type == NAT_MANIP_SRC));
if (!entry)
{
@@ -529,6 +555,7 @@ ipv4_nat_outbound_icmp(FAR struct net_driver_s *dev,
{
FAR struct icmp_hdr_s *icmp = L4_HDR(ipv4);
FAR uint16_t *local_ip = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv4, manip_type);
FAR struct ipv4_nat_entry *entry;
switch (icmp->type)
@@ -542,6 +569,7 @@ ipv4_nat_outbound_icmp(FAR struct net_driver_s *dev,
entry = ipv4_nat_outbound_entry_find(dev, IP_PROTO_ICMP,
net_ip4addr_conv32(local_ip), icmp->id,
net_ip4addr_conv32(peer_ip), icmp->id,
(manip_type == NAT_MANIP_SRC));
if (!entry)
{
@@ -894,7 +922,7 @@ int ipv4_nat_outbound(FAR struct net_driver_s *dev,
bool ipv4_nat_port_inuse(uint8_t protocol, in_addr_t ip, uint16_t port)
{
FAR struct ipv4_nat_entry *entry =
ipv4_nat_inbound_entry_find(protocol, ip, port, false);
ipv4_nat_inbound_entry_find(protocol, ip, port, INADDR_ANY, 0, false);
return entry != NULL;
}
+31 -5
View File
@@ -295,6 +295,8 @@ static void ipv4_nat_entry_refresh(FAR struct ipv4_nat_entry *entry)
* external_port - The external port of the packet.
* local_ip - The local ip of the packet.
* local_port - The local port of the packet.
* peer_ip - The peer ip of the packet.
* peer_port - The peer port of the packet.
*
* Returned Value:
* Pointer to entry on success; null on failure
@@ -304,7 +306,8 @@ static void ipv4_nat_entry_refresh(FAR struct ipv4_nat_entry *entry)
static FAR struct ipv4_nat_entry *
ipv4_nat_entry_create(uint8_t protocol,
in_addr_t external_ip, uint16_t external_port,
in_addr_t local_ip, uint16_t local_port)
in_addr_t local_ip, uint16_t local_port,
in_addr_t peer_ip, uint16_t peer_port)
{
FAR struct ipv4_nat_entry *entry =
kmm_malloc(sizeof(struct ipv4_nat_entry));
@@ -319,6 +322,10 @@ ipv4_nat_entry_create(uint8_t protocol,
entry->external_port = external_port;
entry->local_ip = local_ip;
entry->local_port = local_port;
#ifdef CONFIG_NET_NAT_SYMMETRIC
entry->peer_ip = peer_ip;
entry->peer_port = peer_port;
#endif
ipv4_nat_entry_refresh(entry);
@@ -456,6 +463,8 @@ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev)
* protocol - The L4 protocol of the packet.
* external_ip - The external ip of the packet, supports INADDR_ANY.
* external_port - The external port of the packet.
* peer_ip - The peer ip of the packet.
* peer_port - The peer port of the packet.
* refresh - Whether to refresh the selected entry.
*
* Returned Value:
@@ -465,11 +474,15 @@ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev)
FAR struct ipv4_nat_entry *
ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
uint16_t external_port, bool refresh)
uint16_t external_port, in_addr_t peer_ip,
uint16_t peer_port, bool refresh)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
bool skip_ip = net_ipv4addr_cmp(external_ip, INADDR_ANY);
#ifdef CONFIG_NET_NAT_SYMMETRIC
bool skip_peer = net_ipv4addr_cmp(peer_ip, INADDR_ANY);
#endif
int32_t current_time = TICK2SEC(clock_systime_ticks());
#if CONFIG_NET_NAT_ENTRY_RECLAIM_SEC > 0
@@ -492,7 +505,12 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
if (entry->protocol == protocol &&
(skip_ip || net_ipv4addr_cmp(entry->external_ip, external_ip)) &&
entry->external_port == external_port)
entry->external_port == external_port
#ifdef CONFIG_NET_NAT_SYMMETRIC
&& (skip_peer || (net_ipv4addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port))
#endif
)
{
if (refresh)
{
@@ -525,6 +543,8 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
* protocol - The L4 protocol of the packet.
* local_ip - The local ip of the packet.
* local_port - The local port of the packet.
* peer_ip - The peer ip of the packet.
* peer_port - The peer port of the packet.
* try_create - Try create the entry if no entry found.
*
* Returned Value:
@@ -535,6 +555,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
FAR struct ipv4_nat_entry *
ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
in_addr_t local_ip, uint16_t local_port,
in_addr_t peer_ip, uint16_t peer_port,
bool try_create)
{
FAR hash_node_t *p;
@@ -562,7 +583,12 @@ ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
if (entry->protocol == protocol &&
net_ipv4addr_cmp(entry->external_ip, dev->d_ipaddr) &&
net_ipv4addr_cmp(entry->local_ip, local_ip) &&
entry->local_port == local_port)
entry->local_port == local_port
#ifdef CONFIG_NET_NAT_SYMMETRIC
&& net_ipv4addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port
#endif
)
{
ipv4_nat_entry_refresh(entry);
return entry;
@@ -588,7 +614,7 @@ ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
}
return ipv4_nat_entry_create(protocol, dev->d_ipaddr, external_port,
local_ip, local_port);
local_ip, local_port, peer_ip, peer_port);
}
#endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */
+14 -1
View File
@@ -55,13 +55,20 @@ struct ipv4_nat_entry
* |----------------|
*
* Full cone NAT only need to save local ip:port and external ip:port.
* Symmetric NAT need to save peer ip:port as well.
* For ICMP, save id in port field.
*/
in_addr_t local_ip; /* IP address of the local (private) host. */
in_addr_t external_ip; /* External IP address. */
#ifdef CONFIG_NET_NAT_SYMMETRIC
in_addr_t peer_ip; /* Peer IP address. */
#endif
uint16_t local_port; /* Port of the local (private) host. */
uint16_t external_port; /* The external port of local (private) host. */
#ifdef CONFIG_NET_NAT_SYMMETRIC
uint16_t peer_port; /* Peer port. */
#endif
uint8_t protocol; /* L4 protocol (TCP, UDP etc). */
int32_t expire_time; /* The expiration time of this entry. */
@@ -203,6 +210,8 @@ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev);
* protocol - The L4 protocol of the packet.
* external_ip - The external ip of the packet, supports INADDR_ANY.
* external_port - The external port of the packet.
* peer_ip - The peer ip of the packet.
* peer_port - The peer port of the packet.
* refresh - Whether to refresh the selected entry.
*
* Returned Value:
@@ -212,7 +221,8 @@ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev);
FAR struct ipv4_nat_entry *
ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
uint16_t external_port, bool refresh);
uint16_t external_port, in_addr_t peer_ip,
uint16_t peer_port, bool refresh);
/****************************************************************************
* Name: ipv4_nat_outbound_entry_find
@@ -226,6 +236,8 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
* protocol - The L4 protocol of the packet.
* local_ip - The local ip of the packet.
* local_port - The local port of the packet.
* peer_ip - The peer ip of the packet.
* peer_port - The peer port of the packet.
* try_create - Try create the entry if no entry found.
*
* Returned Value:
@@ -236,6 +248,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
FAR struct ipv4_nat_entry *
ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
in_addr_t local_ip, uint16_t local_port,
in_addr_t peer_ip, uint16_t peer_port,
bool try_create);
#endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */