net/nat: Support IPv6 Masquerading (NAT66)

Notes:
1. This version of NAT66 is a stateful one like NAT44, corresponding to Linux's MASQUERADE target of ip6tables.  We can support stateless NAT66 & NPTv6 later by slightly modify the address & port selection logic (maybe just match the rules and skip the entry find).
2. We're using same flag `IFF_NAT` for both NAT44 & NAT66 to make control easier.  Which means, if we enable NAT, both NAT44 & NAT66 will be enabled.  If we don't want one of them, we can just disable that one in Kconfig.
3. Maybe we can accelerate the checksum adjustment by pre-calculate a difference of checksum, and apply it to each packet, instead of calling `net_chksum_adjust` each time.  Just a thought, maybe do it later.
4. IP fragment segments on NAT66 connections are not supported yet.

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
This commit is contained in:
Zhe Weng 2024-03-13 17:04:55 +08:00 committed by Xiang Xiao
parent 676826cb7c
commit f3b34c84c2
14 changed files with 1439 additions and 97 deletions

View File

@ -46,10 +46,13 @@ Configuration Options
``CONFIG_NET_NAT``
Enable or disable Network Address Translation (NAT) function.
Depends on ``CONFIG_NET_IPFORWARD``.
``CONFIG_NET_NAT_FULL_CONE``
``CONFIG_NET_NAT44`` & ``CONFIG_NET_NAT66``
Enable or disable NAT on IPv4 / IPv6.
Depends on ``CONFIG_NET_NAT``.
``CONFIG_NET_NAT44_FULL_CONE`` & ``CONFIG_NET_NAT66_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``
``CONFIG_NET_NAT44_SYMMETRIC`` & ``CONFIG_NET_NAT66_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``
@ -63,6 +66,8 @@ Configuration Options
The expiration time for idle UDP entry in NAT.
``CONFIG_NET_NAT_ICMP_EXPIRE_SEC``
The expiration time for idle ICMP entry in NAT.
``CONFIG_NET_NAT_ICMPv6_EXPIRE_SEC``
The expiration time for idle ICMPv6 entry in NAT.
``CONFIG_NET_NAT_ENTRY_RECLAIM_SEC``
The time to auto reclaim all expired NAT entries. A value of zero will
disable auto reclaiming.
@ -133,6 +138,10 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
ifconfig eth1 10.0.10.2
ifup eth1
# IPv6 if you need
ifconfig eth0 inet6 add fc00:1::2/64 gw fc00:1::1
ifconfig eth1 inet6 add fc00:10::2/64
4. Configure IP & namespace & route on host side (maybe need to be root, then try ``sudo -i``)
.. code-block:: bash
@ -162,6 +171,22 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
iptables -A FORWARD -i $IF_0 -o $IF_HOST -j ACCEPT
sysctl -w net.ipv4.ip_forward=1
# IPv6 if you need
IP6_HOST_0="fc00:1::1"
IP6_HOST_1="fc00:10::1"
IP6_NUTTX_1="fc00:10::2"
# add address and set default route
ip -6 addr add $IP6_HOST_0/64 dev $IF_0
ip netns exec LAN ip -6 addr add $IP6_HOST_1/64 dev $IF_1
ip netns exec LAN ip -6 route add default dev $IF_1 via $IP6_NUTTX_1
# nat to allow NuttX to access the internet
ip6tables -t nat -A POSTROUTING -o $IF_HOST -j MASQUERADE
ip6tables -A FORWARD -i $IF_HOST -o $IF_0 -j ACCEPT
ip6tables -A FORWARD -i $IF_0 -o $IF_HOST -j ACCEPT
sysctl -w net.ipv6.conf.all.forwarding=1
5. Do anything in the LAN namespace will go through NAT
.. code-block:: shell
@ -174,20 +199,26 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
.. code-block:: shell
# Host side
python3 -m http.server
python3 -m http.server -b ::
# LAN side
for i in {1..20000}; do sudo ip netns exec LAN curl 'http://10.0.1.1:8000/' > /dev/null 2>1; done
for i in {1..20000}; do sudo ip netns exec LAN curl 'http://[fc00:1::1]:8000/' > /dev/null 2>1; done
.. code-block:: shell
# LAN side
sudo ip netns exec LAN ping 8.8.8.8
sudo ip netns exec LAN ping 2001:4860:4860::8888
.. code-block:: shell
# LAN side
sudo ip netns exec LAN traceroute -n 8.8.8.8 # ICMP error msg of UDP
sudo ip netns exec LAN traceroute -n -T 8.8.8.8 # ICMP error msg of TCP
sudo ip netns exec LAN traceroute -n -I 8.8.8.8 # ICMP error msg of ICMP
sudo ip netns exec LAN traceroute -n 2001:4860:4860::8888
sudo ip netns exec LAN traceroute -n -T 2001:4860:4860::8888
sudo ip netns exec LAN traceroute -n -I 2001:4860:4860::8888
.. code-block:: shell

View File

@ -235,14 +235,10 @@ static int ipv4_in(FAR struct net_driver_s *dev)
goto drop;
}
#ifdef CONFIG_NET_NAT
#ifdef CONFIG_NET_NAT44
/* Try NAT inbound, rule matching will be performed in NAT module. */
if (ipv4_nat_inbound(dev, ipv4) < 0)
{
nwarn("WARNING: Performing NAT inbound failed!\n");
goto drop;
}
ipv4_nat_inbound(dev, ipv4);
#endif
/* Get the destination IP address in a friendlier form */

View File

@ -47,6 +47,7 @@
#include "pkt/pkt.h"
#include "icmpv6/icmpv6.h"
#include "nat/nat.h"
#include "netdev/netdev.h"
#include "ipforward/ipforward.h"
#include "inet/inet.h"
@ -296,6 +297,12 @@ static int ipv6_in(FAR struct net_driver_s *dev)
nxthdr = exthdr->nxthdr;
}
#ifdef CONFIG_NET_NAT66
/* Try NAT inbound, rule matching will be performed in NAT module. */
ipv6_nat_inbound(dev, ipv6);
#endif
#ifdef CONFIG_NET_BROADCAST
/* Check for a multicast packet, which may be destined to us (even if
* there is no IP address yet assigned to the device). We only expect

View File

@ -286,13 +286,13 @@ static int ipv4_dev_forward(FAR struct net_driver_s *dev,
goto errout_with_fwd;
}
#ifdef CONFIG_NET_NAT
#ifdef CONFIG_NET_NAT44
/* Try NAT outbound, rule matching will be performed in NAT module. */
ret = ipv4_nat_outbound(fwd->f_dev, ipv4, NAT_MANIP_SRC);
if (ret < 0)
{
nwarn("WARNING: Performing NAT outbound failed, dropping!\n");
nwarn("WARNING: Performing NAT44 outbound failed, dropping!\n");
goto errout_with_fwd;
}
#endif
@ -532,13 +532,13 @@ drop:
#ifdef CONFIG_NET_ICMP
reply:
# ifdef CONFIG_NET_NAT
# ifdef CONFIG_NET_NAT44
/* Before we reply ICMP, call NAT outbound to try to translate destination
* address & port back to original status.
*/
ipv4_nat_outbound(dev, ipv4, NAT_MANIP_DST);
# endif /* CONFIG_NET_NAT */
# endif /* CONFIG_NET_NAT44 */
icmp_reply(dev, icmp_reply_type, icmp_reply_code);
return OK;

View File

@ -35,6 +35,7 @@
#include <nuttx/net/netdev.h>
#include <nuttx/net/netstats.h>
#include "nat/nat.h"
#include "netdev/netdev.h"
#include "sixlowpan/sixlowpan.h"
#include "devif/devif.h"
@ -423,6 +424,17 @@ static int ipv6_dev_forward(FAR struct net_driver_s *dev,
goto errout_with_fwd;
}
#ifdef CONFIG_NET_NAT66
/* Try NAT outbound, rule matching will be performed in NAT module. */
ret = ipv6_nat_outbound(fwd->f_dev, ipv6, NAT_MANIP_SRC);
if (ret < 0)
{
nwarn("WARNING: Performing NAT66 outbound failed, dropping!\n");
goto errout_with_fwd;
}
#endif
/* Then set up to forward the packet according to the protocol. */
ret = ipfwd_forward(fwd);
@ -560,6 +572,11 @@ int ipv6_forward(FAR struct net_driver_s *dev, FAR struct ipv6_hdr_s *ipv6)
{
FAR struct net_driver_s *fwddev;
int ret;
#ifdef CONFIG_NET_ICMPv6
int icmpv6_reply_type;
int icmpv6_reply_code;
int icmpv6_reply_data;
#endif /* CONFIG_NET_ICMP */
/* Search for a device that can forward this packet. */
@ -659,18 +676,22 @@ drop:
switch (ret)
{
case -ENETUNREACH:
icmpv6_reply(dev, ICMPv6_DEST_UNREACHABLE, ICMPv6_ADDR_UNREACH, 0);
return OK;
icmpv6_reply_type = ICMPv6_DEST_UNREACHABLE;
icmpv6_reply_code = ICMPv6_ADDR_UNREACH;
icmpv6_reply_data = 0;
goto reply;
case -EFBIG:
icmpv6_reply(dev, ICMPv6_PACKET_TOO_BIG, 0,
NETDEV_PKTSIZE(fwddev) - NET_LL_HDRLEN(fwddev));
return OK;
icmpv6_reply_type = ICMPv6_PACKET_TOO_BIG;
icmpv6_reply_code = 0;
icmpv6_reply_data = NETDEV_PKTSIZE(fwddev) - NET_LL_HDRLEN(fwddev);
goto reply;
case -EMULTIHOP:
icmpv6_reply(dev, ICMPv6_PACKET_TIME_EXCEEDED, ICMPV6_EXC_HOPLIMIT,
0);
return OK;
icmpv6_reply_type = ICMPv6_PACKET_TIME_EXCEEDED;
icmpv6_reply_code = ICMPV6_EXC_HOPLIMIT;
icmpv6_reply_data = 0;
goto reply;
default:
break; /* We don't know how to reply, just go on (to drop). */
@ -679,6 +700,20 @@ drop:
dev->d_len = 0;
return ret;
#ifdef CONFIG_NET_ICMPv6
reply:
# ifdef CONFIG_NET_NAT66
/* Before we reply ICMPv6, call NAT outbound to try to translate
* destination address & port back to original status.
*/
ipv6_nat_outbound(dev, ipv6, NAT_MANIP_DST);
# endif /* CONFIG_NET_NAT66 */
icmpv6_reply(dev, icmpv6_reply_type, icmpv6_reply_code, icmpv6_reply_data);
return OK;
#endif /* CONFIG_NET_ICMP */
}
/****************************************************************************

View File

@ -24,10 +24,14 @@ if(CONFIG_NET_NAT)
list(APPEND SRCS nat.c)
if(CONFIG_NET_IPv4)
if(CONFIG_NET_NAT44)
list(APPEND SRCS ipv4_nat.c ipv4_nat_entry.c)
endif()
if(CONFIG_NET_NAT66)
list(APPEND SRCS ipv6_nat.c ipv6_nat_entry.c)
endif()
target_sources(net PRIVATE ${SRCS})
endif()

View File

@ -6,27 +6,58 @@
config NET_NAT
bool "Network Address Translation (NAT)"
default n
depends on NET_IPFORWARD && IOB_BUFSIZE >= 68
depends on NET_IPFORWARD
---help---
Enable or disable Network Address Translation (NAT) function.
Note: When forwarding IPv4 packet and applying NAT, NAT may be
applied directly on a single I/O buffer containing L3 packet header,
and NAT may need a continuous buffer of at least 68 Bytes
(IPv4 20B + ICMP 8B + IPv4 20B + TCP 20B).
(IPv4 20B + ICMP 8B + IPv4 20B + TCP 20B). And 108 Bytes for IPv6.
config NET_NAT44
bool "IPv4-to-IPv4 NAT (NAT44)"
default y
depends on NET_IPv4 && NET_NAT
depends on IOB_BUFSIZE >= 68
choice
prompt "NAT Type"
default NET_NAT_FULL_CONE
depends on NET_NAT
prompt "NAT44 Type"
default NET_NAT44_FULL_CONE
depends on NET_NAT44
config NET_NAT_FULL_CONE
config NET_NAT44_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
config NET_NAT44_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_NAT66
bool "IPv6-to-IPv6 NAT (NAT66)"
default y
depends on NET_IPv6 && NET_NAT
depends on IOB_BUFSIZE >= 108
choice
prompt "NAT66 Type"
default NET_NAT66_FULL_CONE
depends on NET_NAT66
config NET_NAT66_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_NAT66_SYMMETRIC
bool "Symmetric NAT"
---help---
Symmetric NAT will be safer than Full Cone NAT, be more difficult
@ -73,6 +104,13 @@ config NET_NAT_ICMP_EXPIRE_SEC
Note: The default value 60 is suggested by RFC5508, Section 3.2,
Page 8.
config NET_NAT_ICMPv6_EXPIRE_SEC
int "ICMPv6 NAT entry expiration seconds"
default 60
depends on NET_NAT
---help---
The expiration time for idle ICMPv6 entry in NAT.
config NET_NAT_ENTRY_RECLAIM_SEC
int "The time to auto reclaim all expired entries"
default 3600

View File

@ -24,10 +24,14 @@ ifeq ($(CONFIG_NET_NAT),y)
NET_CSRCS += nat.c
ifeq ($(CONFIG_NET_IPv4),y)
ifeq ($(CONFIG_NET_NAT44),y)
NET_CSRCS += ipv4_nat.c ipv4_nat_entry.c
endif
ifeq ($(CONFIG_NET_NAT66),y)
NET_CSRCS += ipv6_nat.c ipv6_nat_entry.c
endif
# Include NAT build support
DEPPATH += --dep-path nat

View File

@ -37,7 +37,7 @@
#include "nat/nat.h"
#include "utils/utils.h"
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
#ifdef CONFIG_NET_NAT44
/****************************************************************************
* Pre-processor Definitions
@ -732,15 +732,10 @@ ipv4_nat_outbound_internal(FAR struct net_driver_s *dev,
* dev - The device on which the packet is received.
* ipv4 - Points to the IPv4 header with dev->d_buf.
*
* Returned Value:
* Zero is returned if NAT is successfully applied, or is not enabled for
* this packet;
* A negated errno value is returned if error occured.
*
****************************************************************************/
int ipv4_nat_inbound(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4)
void ipv4_nat_inbound(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4)
{
/* We only process packets from NAT device and targeting at the address
* assigned to the device.
@ -755,11 +750,9 @@ int ipv4_nat_inbound(FAR struct net_driver_s *dev,
{
/* Inbound without entry is OK (e.g. towards NuttX itself), skip. */
return OK;
return;
}
}
return OK;
}
/****************************************************************************
@ -801,11 +794,11 @@ int ipv4_nat_outbound(FAR struct net_driver_s *dev,
{
/* Outbound entry creation failed, should have entry. */
return -ENOMEM;
return -ENOENT;
}
}
return OK;
}
#endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */
#endif /* CONFIG_NET_NAT44 */

View File

@ -34,14 +34,14 @@
#include "nat/nat.h"
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
#ifdef CONFIG_NET_NAT44
/****************************************************************************
* Private Data
****************************************************************************/
static DECLARE_HASHTABLE(g_table_inbound, CONFIG_NET_NAT_HASH_BITS);
static DECLARE_HASHTABLE(g_table_outbound, CONFIG_NET_NAT_HASH_BITS);
static DECLARE_HASHTABLE(g_nat44_inbound, CONFIG_NET_NAT_HASH_BITS);
static DECLARE_HASHTABLE(g_nat44_outbound, CONFIG_NET_NAT_HASH_BITS);
/****************************************************************************
* Private Functions
@ -134,16 +134,16 @@ 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
#ifdef CONFIG_NET_NAT44_SYMMETRIC
entry->peer_ip = peer_ip;
entry->peer_port = peer_port;
#endif
ipv4_nat_entry_refresh(entry);
hashtable_add(g_table_inbound, &entry->hash_inbound,
hashtable_add(g_nat44_inbound, &entry->hash_inbound,
ipv4_nat_inbound_key(external_ip, external_port, protocol));
hashtable_add(g_table_outbound, &entry->hash_outbound,
hashtable_add(g_nat44_outbound, &entry->hash_outbound,
ipv4_nat_outbound_key(local_ip, local_port, protocol));
return entry;
@ -162,16 +162,16 @@ ipv4_nat_entry_create(uint8_t protocol,
static void ipv4_nat_entry_delete(FAR struct ipv4_nat_entry *entry)
{
ninfo("INFO: Removing NAT entry proto=%" PRIu8
ninfo("INFO: Removing NAT44 entry proto=%" PRIu8
", local=%" PRIx32 ":%" PRIu16 ", external=:%" PRIu16 "\n",
entry->protocol, entry->local_ip, entry->local_port,
entry->external_port);
hashtable_delete(g_table_inbound, &entry->hash_inbound,
hashtable_delete(g_nat44_inbound, &entry->hash_inbound,
ipv4_nat_inbound_key(entry->external_ip,
entry->external_port,
entry->protocol));
hashtable_delete(g_table_outbound, &entry->hash_outbound,
hashtable_delete(g_nat44_outbound, &entry->hash_outbound,
ipv4_nat_outbound_key(entry->local_ip,
entry->local_port,
entry->protocol));
@ -207,9 +207,9 @@ static void ipv4_nat_reclaim_entry(int32_t current_time)
int count = 0;
int i;
ninfo("INFO: Reclaiming all expired NAT entries.\n");
ninfo("INFO: Reclaiming all expired NAT44 entries.\n");
hashtable_for_every_safe(g_table_inbound, p, tmp, i)
hashtable_for_every_safe(g_nat44_inbound, p, tmp, i)
{
FAR struct ipv4_nat_entry *entry =
container_of(p, struct ipv4_nat_entry, hash_inbound);
@ -221,10 +221,12 @@ static void ipv4_nat_reclaim_entry(int32_t current_time)
}
}
ninfo("INFO: %d expired NAT entries reclaimed.\n", count);
ninfo("INFO: %d expired NAT44 entries reclaimed.\n", count);
next_reclaim_time = current_time + CONFIG_NET_NAT_ENTRY_RECLAIM_SEC;
}
}
#else
# define ipv4_nat_reclaim_entry(t)
#endif
/****************************************************************************
@ -239,7 +241,7 @@ static void ipv4_nat_reclaim_entry(int32_t current_time)
* any device.
*
* Input Parameters:
* dev - The device on which NAT entries will be cleared.
* dev - The device on which NAT entries will be cleared.
*
* Assumptions:
* NAT is initialized.
@ -252,9 +254,9 @@ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev)
FAR hash_node_t *tmp;
int i;
ninfo("INFO: Clearing all NAT entries for %s\n", dev->d_ifname);
ninfo("INFO: Clearing all NAT44 entries for %s\n", dev->d_ifname);
hashtable_for_every_safe(g_table_inbound, p, tmp, i)
hashtable_for_every_safe(g_nat44_inbound, p, tmp, i)
{
FAR struct ipv4_nat_entry *entry =
container_of(p, struct ipv4_nat_entry, hash_inbound);
@ -293,16 +295,14 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
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
#ifdef CONFIG_NET_NAT44_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
ipv4_nat_reclaim_entry(current_time);
#endif
hashtable_for_every_possible_safe(g_table_inbound, p, tmp,
hashtable_for_every_possible_safe(g_nat44_inbound, p, tmp,
ipv4_nat_inbound_key(external_ip, external_port, protocol))
{
FAR struct ipv4_nat_entry *entry =
@ -319,7 +319,7 @@ 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
#ifdef CONFIG_NET_NAT_SYMMETRIC
#ifdef CONFIG_NET_NAT44_SYMMETRIC
&& (skip_peer || (net_ipv4addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port))
#endif
@ -376,11 +376,9 @@ ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
uint16_t external_port;
int32_t current_time = TICK2SEC(clock_systime_ticks());
#if CONFIG_NET_NAT_ENTRY_RECLAIM_SEC > 0
ipv4_nat_reclaim_entry(current_time);
#endif
hashtable_for_every_possible_safe(g_table_outbound, p, tmp,
hashtable_for_every_possible_safe(g_nat44_outbound, p, tmp,
ipv4_nat_outbound_key(local_ip, local_port, protocol))
{
FAR struct ipv4_nat_entry *entry =
@ -398,7 +396,7 @@ ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
net_ipv4addr_cmp(entry->external_ip, dev->d_ipaddr) &&
net_ipv4addr_cmp(entry->local_ip, local_ip) &&
entry->local_port == local_port
#ifdef CONFIG_NET_NAT_SYMMETRIC
#ifdef CONFIG_NET_NAT44_SYMMETRIC
&& net_ipv4addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port
#endif
@ -432,4 +430,4 @@ ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
local_ip, local_port, peer_ip, peer_port);
}
#endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */
#endif /* CONFIG_NET_NAT44 */

692
net/nat/ipv6_nat.c Normal file
View File

@ -0,0 +1,692 @@
/****************************************************************************
* net/nat/ipv6_nat.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <nuttx/net/icmpv6.h>
#include <nuttx/net/tcp.h>
#include <nuttx/net/udp.h>
#include "nat/nat.h"
#include "utils/utils.h"
#ifdef CONFIG_NET_NAT66
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static FAR struct ipv6_nat_entry *
ipv6_nat_inbound_internal(FAR struct ipv6_hdr_s *ipv6,
enum nat_manip_type_e manip_type);
static FAR struct ipv6_nat_entry *
ipv6_nat_outbound_internal(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6,
enum nat_manip_type_e manip_type);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ipv6_nat_ip_adjust
*
* Description:
* Adjust address and checksum for network packet.
*
* Input Parameters:
* l4chksum - Points to the L4 checksum to adjust, NULL for not adjust.
* old_ip - The IP to be set.
* new_ip - The IP to set into header.
*
****************************************************************************/
static void ipv6_nat_ip_adjust(FAR uint16_t *l4chksum, FAR uint16_t *old_ip,
net_ipv6addr_t new_ip)
{
/* TODO: Maybe we can accelerate the checksum adjustment by pre-calculate a
* difference of checksum, and apply it to each packet, instead of calling
* chksum_adjust each time.
*/
if (l4chksum != NULL)
{
nat_chksum_adjust(l4chksum, old_ip, new_ip, sizeof(net_ipv6addr_t));
}
net_ipv6addr_hdrcopy(old_ip, new_ip);
}
/****************************************************************************
* Name: ipv6_nat_port_adjust
*
* Description:
* Adjust port and checksum for network packet.
*
* Input Parameters:
* l4chksum - Points to the L4 checksum to adjust, NULL for not adjust.
* old_port - The port to be set.
* new_port - The port to set into header.
*
****************************************************************************/
static void ipv6_nat_port_adjust(FAR uint16_t *l4chksum,
FAR uint16_t *old_port, uint16_t new_port)
{
if (l4chksum != NULL)
{
nat_chksum_adjust(l4chksum, old_port, &new_port, sizeof(new_port));
}
*old_port = new_port;
}
/****************************************************************************
* Name: ipv6_nat_inbound_tcp
*
* Description:
* Check if a received TCP packet belongs to a NAT entry. If so, translate
* the external IP/Port to local IP/Port.
*
* Input Parameters:
* ipv6 - Points to the IPv6 header to translate.
* tcp - Points to the TCP header to translate.
* manip_type - Whether external IP/Port is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet is received on NAT device and is targeting at the address
* assigned to the device.
*
****************************************************************************/
#ifdef CONFIG_NET_TCP
static FAR struct ipv6_nat_entry *
ipv6_nat_inbound_tcp(FAR struct ipv6_hdr_s *ipv6, FAR struct tcp_hdr_s *tcp,
enum nat_manip_type_e manip_type)
{
FAR uint16_t *external_ip = MANIP_IPADDR(ipv6, manip_type);
FAR uint16_t *external_port = MANIP_PORT(tcp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv6, manip_type);
FAR uint16_t *peer_port = PEER_PORT(tcp, manip_type);
FAR struct ipv6_nat_entry *entry =
ipv6_nat_inbound_entry_find(IP_PROTO_TCP,
external_ip, *external_port,
peer_ip, *peer_port, true);
if (!entry)
{
return NULL;
}
/* Note: Field tcpchksum is not guaranteed exists in TCP header inside
* ICMPv6 Error MSG, but we manually guarantee that it is inside valid
* address (IOB >= IP + ICMPv6 + IP + TCP), so we can update it safely.
*/
ipv6_nat_port_adjust(&tcp->tcpchksum, external_port, entry->local_port);
ipv6_nat_ip_adjust(&tcp->tcpchksum, external_ip, entry->local_ip);
return entry;
}
#endif
/****************************************************************************
* Name: ipv6_nat_inbound_udp
*
* Description:
* Check if a received UDP packet belongs to a NAT entry. If so, translate
* the external IP/Port to local IP/Port.
*
* Input Parameters:
* ipv6 - Points to the IPv6 header to translate.
* udp - Points to the UDP header to translate.
* manip_type - Whether external IP/Port is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet is received on NAT device and is targeting at the address
* assigned to the device.
*
****************************************************************************/
#ifdef CONFIG_NET_UDP
static FAR struct ipv6_nat_entry *
ipv6_nat_inbound_udp(FAR struct ipv6_hdr_s *ipv6, FAR struct udp_hdr_s *udp,
enum nat_manip_type_e manip_type)
{
FAR uint16_t *external_ip = MANIP_IPADDR(ipv6, manip_type);
FAR uint16_t *external_port = MANIP_PORT(udp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv6, manip_type);
FAR uint16_t *peer_port = PEER_PORT(udp, manip_type);
FAR uint16_t *udpchksum;
FAR struct ipv6_nat_entry *entry =
ipv6_nat_inbound_entry_find(IP_PROTO_UDP,
external_ip, *external_port,
peer_ip, *peer_port, true);
if (!entry)
{
return NULL;
}
/* UDP checksum has special case 0 (no checksum) */
udpchksum = udp->udpchksum != 0 ? &udp->udpchksum : NULL;
ipv6_nat_port_adjust(udpchksum, external_port, entry->local_port);
ipv6_nat_ip_adjust(udpchksum, external_ip, entry->local_ip);
return entry;
}
#endif
/****************************************************************************
* Name: ipv6_nat_inbound_icmpv6
*
* Description:
* Check if a received ICMPv6 packet belongs to a NAT entry. If so,
* translate the external IP/ID to local IP/ID.
*
* Input Parameters:
* ipv6 - Points to the IPv6 header to translate.
* icmpv6 - Points to the ICMPv6 header to translate.
* manip_type - Whether external IP is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet is received on g_dev and is targeting at the address assigned to
* g_dev.
*
****************************************************************************/
#ifdef CONFIG_NET_ICMPv6
static FAR struct ipv6_nat_entry *
ipv6_nat_inbound_icmpv6(FAR struct ipv6_hdr_s *ipv6,
FAR struct icmpv6_hdr_s *icmpv6,
enum nat_manip_type_e manip_type)
{
FAR uint16_t *external_ip = MANIP_IPADDR(ipv6, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv6, manip_type);
FAR struct ipv6_nat_entry *entry;
switch (icmpv6->type)
{
case ICMPv6_ECHO_REQUEST:
case ICMPv6_ECHO_REPLY:
entry = ipv6_nat_inbound_entry_find(IP_PROTO_ICMP6,
external_ip, icmpv6->data[0],
peer_ip, icmpv6->data[0], true);
if (!entry)
{
return NULL;
}
ipv6_nat_port_adjust(&icmpv6->chksum,
&icmpv6->data[0], entry->local_port);
ipv6_nat_ip_adjust(&icmpv6->chksum, external_ip, entry->local_ip);
return entry;
case ICMPv6_DEST_UNREACHABLE:
case ICMPv6_PACKET_TOO_BIG:
case ICMPv6_PACKET_TIME_EXCEEDED:
case ICMPv6_PACKET_PARAM_PROBLEM:
/* ICMPv6 Error MSG inside another ICMPv6 Error MSG is forbidden by
* RFC4443, Section 2.4, Page 6, so we only process the outermost
* ICMPv6 Error MSG (manip type is DST).
*/
if (manip_type == NAT_MANIP_DST)
{
/* The payload in the ICMPv6 packet is the origin packet we sent.
* We don't need to check or backup any inner L4 data, because
* every ICMPv6 error message (type < 128) MUST include as much
* of the IPv6 offending (invoking) packet as possible. And the
* inner packet will be translated by the inbound process
* without needed to modify any outer packet checksum.
*/
FAR struct ipv6_hdr_s *inner =
(FAR struct ipv6_hdr_s *)(icmpv6 + 1);
/* Find entry and translate inner. */
entry = ipv6_nat_inbound_internal(inner, NAT_MANIP_SRC);
if (!entry)
{
return NULL;
}
/* Adjust outer IP */
ipv6_nat_ip_adjust(&icmpv6->chksum, external_ip,
entry->local_ip);
return entry;
}
}
return NULL;
}
#endif
/****************************************************************************
* Name: ipv6_nat_outbound_tcp
*
* Description:
* Check if we want to perform NAT with this outbound TCP packet before
* sending it. If so, translate the local IP/Port to external IP/Port.
*
* Input Parameters:
* dev - The device to sent the packet (to get external IP).
* ipv6 - Points to the IPv6 header to translate.
* tcp - Points to the TCP header to translate.
* manip_type - Whether local IP/Port is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet will be sent on NAT device.
*
****************************************************************************/
#ifdef CONFIG_NET_TCP
static FAR struct ipv6_nat_entry *
ipv6_nat_outbound_tcp(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6, FAR struct tcp_hdr_s *tcp,
enum nat_manip_type_e manip_type)
{
FAR uint16_t *local_ip = MANIP_IPADDR(ipv6, manip_type);
FAR uint16_t *local_port = MANIP_PORT(tcp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv6, manip_type);
FAR uint16_t *peer_port = PEER_PORT(tcp, manip_type);
FAR struct ipv6_nat_entry *entry;
/* Only create entry when it's the outermost packet (manip type is SRC). */
entry = ipv6_nat_outbound_entry_find(dev, IP_PROTO_TCP,
local_ip, *local_port, peer_ip, *peer_port,
manip_type == NAT_MANIP_SRC);
if (!entry)
{
return NULL;
}
/* Note: Field tcpchksum is not guaranteed exists in TCP header inside
* ICMPv6 Error MSG, but we manually guarantee that it is inside valid
* address (IOB >= IP + ICMPv6 + IP + TCP), so we can update it safely.
*/
ipv6_nat_port_adjust(&tcp->tcpchksum, local_port, entry->external_port);
ipv6_nat_ip_adjust(&tcp->tcpchksum, local_ip, entry->external_ip);
return entry;
}
#endif
/****************************************************************************
* Name: ipv6_nat_outbound_udp
*
* Description:
* Check if we want to perform NAT with this outbound UDP packet before
* sending it. If so, translate the local IP/Port to external IP/Port.
*
* Input Parameters:
* dev - The device to sent the packet (to get external IP).
* ipv6 - Points to the IPv6 header to translate.
* udp - Points to the UDP header to translate.
* manip_type - Whether local IP/Port is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet will be sent on NAT device.
*
****************************************************************************/
#ifdef CONFIG_NET_UDP
static FAR struct ipv6_nat_entry *
ipv6_nat_outbound_udp(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6, FAR struct udp_hdr_s *udp,
enum nat_manip_type_e manip_type)
{
FAR uint16_t *local_ip = MANIP_IPADDR(ipv6, manip_type);
FAR uint16_t *local_port = MANIP_PORT(udp, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv6, manip_type);
FAR uint16_t *peer_port = PEER_PORT(udp, manip_type);
FAR uint16_t *udpchksum;
FAR struct ipv6_nat_entry *entry;
/* Only create entry when it's the outermost packet (manip type is SRC). */
entry = ipv6_nat_outbound_entry_find(dev, IP_PROTO_UDP,
local_ip, *local_port, peer_ip, *peer_port,
manip_type == NAT_MANIP_SRC);
if (!entry)
{
return NULL;
}
/* UDP checksum has special case 0 (no checksum) */
udpchksum = udp->udpchksum != 0 ? &udp->udpchksum : NULL;
ipv6_nat_port_adjust(udpchksum, local_port, entry->external_port);
ipv6_nat_ip_adjust(udpchksum, local_ip, entry->external_ip);
return entry;
}
#endif
/****************************************************************************
* Name: ipv6_nat_outbound_icmpv6
*
* Description:
* Check if we want to perform NAT with this outbound ICMPv6 packet before
* sending it. If so, translate the local IP/ID to external IP/ID.
*
* Input Parameters:
* dev - The device to sent the packet (to get external IP).
* ipv6 - Points to the IPv6 header to translate.
* icmpv6 - Points to the ICMPv6 header to translate.
* manip_type - Whether local IP is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet will be sent on NAT device.
*
****************************************************************************/
#ifdef CONFIG_NET_ICMPv6
static FAR struct ipv6_nat_entry *
ipv6_nat_outbound_icmpv6(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6,
FAR struct icmpv6_hdr_s *icmpv6,
enum nat_manip_type_e manip_type)
{
FAR uint16_t *local_ip = MANIP_IPADDR(ipv6, manip_type);
FAR uint16_t *peer_ip = PEER_IPADDR(ipv6, manip_type);
FAR struct ipv6_nat_entry *entry;
switch (icmpv6->type)
{
case ICMPv6_ECHO_REQUEST:
case ICMPv6_ECHO_REPLY:
/* Note: Only create new entry when it's the outermost packet (that
* is, manip type is SRC).
*/
entry = ipv6_nat_outbound_entry_find(dev, IP_PROTO_ICMP6,
local_ip, icmpv6->data[0], peer_ip, icmpv6->data[0],
manip_type == NAT_MANIP_SRC);
if (!entry)
{
return NULL;
}
ipv6_nat_port_adjust(&icmpv6->chksum,
&icmpv6->data[0], entry->external_port);
ipv6_nat_ip_adjust(&icmpv6->chksum, local_ip, entry->external_ip);
return entry;
case ICMPv6_DEST_UNREACHABLE:
case ICMPv6_PACKET_TOO_BIG:
case ICMPv6_PACKET_TIME_EXCEEDED:
case ICMPv6_PACKET_PARAM_PROBLEM:
/* ICMPv6 Error MSG inside another ICMPv6 Error MSG is forbidden by
* RFC4443, Section 2.4, Page 6, so we only process the outermost
* ICMPv6 Error MSG (manip type is DST).
*/
if (manip_type == NAT_MANIP_SRC)
{
/* The payload in the ICMPv6 packet is the origin packet we got.
* We don't need to check or backup any inner L4 data, because
* every ICMPv6 error message (type < 128) MUST include as much
* of the IPv6 offending (invoking) packet as possible. And the
* inner packet will be translated by the inbound process
* without needed to modify any outer packet checksum.
*/
FAR struct ipv6_hdr_s *inner =
(FAR struct ipv6_hdr_s *)(icmpv6 + 1);
/* Find entry and translate inner. */
entry = ipv6_nat_outbound_internal(dev, inner, NAT_MANIP_DST);
if (!entry)
{
return NULL;
}
/* Adjust outer IP */
ipv6_nat_ip_adjust(&icmpv6->chksum, local_ip,
entry->external_ip);
return entry;
}
}
return NULL;
}
#endif
/****************************************************************************
* Name: ipv6_nat_inbound_internal
*
* Description:
* Check if a received packet belongs to a NAT entry. If so, translate
* the external IP/Port to local IP/Port.
*
* Input Parameters:
* ipv6 - Points to the IPv6 header to translate.
* manip_type - Whether external IP/Port is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet is received on NAT device and is targeting at the address
* assigned to the device.
*
****************************************************************************/
static FAR struct ipv6_nat_entry *
ipv6_nat_inbound_internal(FAR struct ipv6_hdr_s *ipv6,
enum nat_manip_type_e manip_type)
{
uint8_t proto;
FAR void *l4hdr = net_ipv6_payload(ipv6, &proto);
switch (ipv6->proto)
{
#ifdef CONFIG_NET_TCP
case IP_PROTO_TCP:
return ipv6_nat_inbound_tcp(ipv6, l4hdr, manip_type);
#endif
#ifdef CONFIG_NET_UDP
case IP_PROTO_UDP:
return ipv6_nat_inbound_udp(ipv6, l4hdr, manip_type);
#endif
#ifdef CONFIG_NET_ICMPv6
case IP_PROTO_ICMP6:
return ipv6_nat_inbound_icmpv6(ipv6, l4hdr, manip_type);
#endif
}
return NULL;
}
/****************************************************************************
* Name: ipv6_nat_outbound_internal
*
* Description:
* Check if we want to perform NAT with this outbound packet before
* sending it. If so, translate the local IP/Port to external IP/Port.
*
* Input Parameters:
* dev - The device to sent the packet (to get external IP).
* ipv6 - Points to the IPv6 header to translate.
* manip_type - Whether local IP/Port is in source or destination.
*
* Returned Value:
* The corresponding NAT entry of the packet.
*
* Assumptions:
* Packet will be sent on NAT device.
*
****************************************************************************/
static FAR struct ipv6_nat_entry *
ipv6_nat_outbound_internal(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6,
enum nat_manip_type_e manip_type)
{
uint8_t proto;
FAR void *l4hdr = net_ipv6_payload(ipv6, &proto);
switch (proto)
{
#ifdef CONFIG_NET_TCP
case IP_PROTO_TCP:
return ipv6_nat_outbound_tcp(dev, ipv6, l4hdr, manip_type);
#endif
#ifdef CONFIG_NET_UDP
case IP_PROTO_UDP:
return ipv6_nat_outbound_udp(dev, ipv6, l4hdr, manip_type);
#endif
#ifdef CONFIG_NET_ICMPv6
case IP_PROTO_ICMP6:
return ipv6_nat_outbound_icmpv6(dev, ipv6, l4hdr, manip_type);
#endif
}
return NULL;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ipv6_nat_inbound
*
* Description:
* Check if a received packet belongs to a NAT entry. If so, translate it.
*
* Input Parameters:
* dev - The device on which the packet is received.
* ipv6 - Points to the IPv6 header with dev->d_buf.
*
****************************************************************************/
void ipv6_nat_inbound(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6)
{
/* We only process packets from NAT device and targeting at the address
* assigned to the device.
*/
if (IFF_IS_NAT(dev->d_flags) &&
NETDEV_IS_MY_V6ADDR(dev, ipv6->destipaddr))
{
FAR struct ipv6_nat_entry *entry =
ipv6_nat_inbound_internal(ipv6, NAT_MANIP_DST);
if (!entry)
{
/* Inbound without entry is OK (e.g. towards NuttX itself), skip. */
return;
}
}
}
/****************************************************************************
* Name: ipv6_nat_outbound
*
* Description:
* Check if we want to perform NAT with this outbound packet before sending
* it. If so, translate it.
*
* Input Parameters:
* dev - The device on which the packet will be sent.
* ipv6 - Points to the IPv6 header to be filled into dev->d_buf later.
* manip_type - Whether local IP/Port is in source or destination.
*
* Returned Value:
* Zero is returned if NAT is successfully applied, or is not enabled for
* this packet;
* A negated errno value is returned if error occured.
*
****************************************************************************/
int ipv6_nat_outbound(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6,
enum nat_manip_type_e manip_type)
{
/* We only process packets targeting at NAT device but not targeting at the
* address assigned to the device.
*/
if (IFF_IS_NAT(dev->d_flags) &&
!NETDEV_IS_MY_V6ADDR(dev, ipv6->srcipaddr) &&
!NETDEV_IS_MY_V6ADDR(dev, ipv6->destipaddr))
{
FAR struct ipv6_nat_entry *entry =
ipv6_nat_outbound_internal(dev, ipv6, manip_type);
if (manip_type == NAT_MANIP_SRC && !entry)
{
/* Outbound entry creation failed, should have entry. */
return -ENOENT;
}
}
return OK;
}
#endif /* CONFIG_NET_NAT66 */

437
net/nat/ipv6_nat_entry.c Normal file
View File

@ -0,0 +1,437 @@
/****************************************************************************
* net/nat/ipv6_nat_entry.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <stdint.h>
#include <nuttx/clock.h>
#include <nuttx/hashtable.h>
#include <nuttx/kmalloc.h>
#include <nuttx/nuttx.h>
#include "inet/inet.h"
#include "nat/nat.h"
#ifdef CONFIG_NET_NAT66
/****************************************************************************
* Private Data
****************************************************************************/
static DECLARE_HASHTABLE(g_nat66_inbound, CONFIG_NET_NAT_HASH_BITS);
static DECLARE_HASHTABLE(g_nat66_outbound, CONFIG_NET_NAT_HASH_BITS);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ipv6_nat_hash_key
*
* Description:
* Create a hash key for NAT66.
*
****************************************************************************/
static inline uint32_t ipv6_nat_hash_key(const net_ipv6addr_t ip,
uint16_t port, uint8_t protocol)
{
uint32_t key = (((uint32_t)ip[0] << 16) | ip[1]) ^
(((uint32_t)ip[2] << 16) | ip[3]) ^
(((uint32_t)ip[4] << 16) | ip[5]) ^
(((uint32_t)ip[6] << 16) | ip[7]);
return key ^ ((uint32_t)protocol << 16) ^ port;
}
/****************************************************************************
* Name: ipv6_nat_entry_refresh
*
* Description:
* Refresh a NAT entry, update its expiration time.
*
* Input Parameters:
* entry - The entry to refresh.
*
****************************************************************************/
static void ipv6_nat_entry_refresh(FAR struct ipv6_nat_entry *entry)
{
entry->expire_time = nat_expire_time(entry->protocol);
}
/****************************************************************************
* Name: ipv6_nat_entry_create
*
* Description:
* Create a NAT entry and insert into entry list.
*
* Input Parameters:
* protocol - The L4 protocol of the packet.
* external_ip - The external ip of the packet.
* 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
*
****************************************************************************/
static FAR struct ipv6_nat_entry *
ipv6_nat_entry_create(uint8_t protocol, const net_ipv6addr_t external_ip,
uint16_t external_port, const net_ipv6addr_t local_ip,
uint16_t local_port, const net_ipv6addr_t peer_ip,
uint16_t peer_port)
{
FAR struct ipv6_nat_entry *entry =
kmm_malloc(sizeof(struct ipv6_nat_entry));
if (entry == NULL)
{
nwarn("WARNING: Failed to allocate IPv6 NAT entry\n");
return NULL;
}
entry->protocol = protocol;
entry->external_port = external_port;
entry->local_port = local_port;
#ifdef CONFIG_NET_NAT66_SYMMETRIC
entry->peer_port = peer_port;
#endif
net_ipv6addr_copy(entry->external_ip, external_ip);
net_ipv6addr_copy(entry->local_ip, local_ip);
#ifdef CONFIG_NET_NAT66_SYMMETRIC
net_ipv6addr_copy(entry->peer_ip, peer_ip);
#endif
ipv6_nat_entry_refresh(entry);
hashtable_add(g_nat66_inbound, &entry->hash_inbound,
ipv6_nat_hash_key(external_ip, external_port, protocol));
hashtable_add(g_nat66_outbound, &entry->hash_outbound,
ipv6_nat_hash_key(local_ip, local_port, protocol));
return entry;
}
/****************************************************************************
* Name: ipv6_nat_entry_delete
*
* Description:
* Delete a NAT entry and remove from entry list.
*
* Input Parameters:
* entry - The entry to remove.
*
****************************************************************************/
static void ipv6_nat_entry_delete(FAR struct ipv6_nat_entry *entry)
{
ninfo("INFO: Removing NAT66 entry proto=%" PRIu8
", local=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%" PRIu16
", external=:%" PRIu16 "\n",
entry->protocol, entry->local_ip[0], entry->local_ip[1],
entry->local_ip[2], entry->local_ip[3], entry->local_ip[4],
entry->local_ip[5], entry->local_ip[6], entry->local_ip[7],
entry->local_port, entry->external_port);
hashtable_delete(g_nat66_inbound, &entry->hash_inbound,
ipv6_nat_hash_key(entry->external_ip,
entry->external_port,
entry->protocol));
hashtable_delete(g_nat66_outbound, &entry->hash_outbound,
ipv6_nat_hash_key(entry->local_ip,
entry->local_port,
entry->protocol));
kmm_free(entry);
}
/****************************************************************************
* Name: ipv6_nat_reclaim_entry
*
* Description:
* Try reclaim all expired NAT entries.
* Only works after every CONFIG_NET_NAT_ENTRY_RECLAIM_SEC (low frequency).
*
* Although expired entries will be automatically reclaimed when matching
* inbound/outbound entries, there might be some situations that entries
* will be kept in memory, e.g. big hashtable with only a few connections.
*
* Assumptions:
* NAT is initialized.
*
****************************************************************************/
#if CONFIG_NET_NAT_ENTRY_RECLAIM_SEC > 0
static void ipv6_nat_reclaim_entry(int32_t current_time)
{
static int32_t next_reclaim_time = CONFIG_NET_NAT_ENTRY_RECLAIM_SEC;
if (next_reclaim_time - current_time <= 0)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
int count = 0;
int i;
ninfo("INFO: Reclaiming all expired NAT66 entries.\n");
hashtable_for_every_safe(g_nat66_inbound, p, tmp, i)
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_inbound);
if (entry->expire_time - current_time <= 0)
{
ipv6_nat_entry_delete(entry);
count++;
}
}
ninfo("INFO: %d expired NAT66 entries reclaimed.\n", count);
next_reclaim_time = current_time + CONFIG_NET_NAT_ENTRY_RECLAIM_SEC;
}
}
#else
# define ipv6_nat_reclaim_entry(t)
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ipv6_nat_entry_clear
*
* Description:
* Clear all entries related to dev. Called when NAT will be disabled on
* any device.
*
* Input Parameters:
* dev - The device on which NAT entries will be cleared.
*
* Assumptions:
* NAT is initialized.
*
****************************************************************************/
void ipv6_nat_entry_clear(FAR struct net_driver_s *dev)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
int i;
ninfo("INFO: Clearing all NAT66 entries for %s\n", dev->d_ifname);
hashtable_for_every_safe(g_nat66_inbound, p, tmp, i)
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_inbound);
if (NETDEV_IS_MY_V6ADDR(dev, entry->external_ip))
{
ipv6_nat_entry_delete(entry);
}
}
}
/****************************************************************************
* Name: ipv6_nat_inbound_entry_find
*
* Description:
* Find the inbound entry in NAT entry list.
*
* Input Parameters:
* 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:
* Pointer to entry on success; null on failure
*
****************************************************************************/
FAR struct ipv6_nat_entry *
ipv6_nat_inbound_entry_find(uint8_t protocol,
const net_ipv6addr_t external_ip,
uint16_t external_port,
const net_ipv6addr_t peer_ip,
uint16_t peer_port, bool refresh)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
bool skip_ip = net_ipv6addr_cmp(external_ip, g_ipv6_unspecaddr);
#ifdef CONFIG_NET_NAT66_SYMMETRIC
bool skip_peer = net_ipv6addr_cmp(peer_ip, g_ipv6_unspecaddr);
#endif
int32_t current_time = TICK2SEC(clock_systime_ticks());
ipv6_nat_reclaim_entry(current_time);
hashtable_for_every_possible_safe(g_nat66_inbound, p, tmp,
ipv6_nat_hash_key(external_ip, external_port, protocol))
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_inbound);
/* Remove expired entries. */
if (entry->expire_time - current_time <= 0)
{
ipv6_nat_entry_delete(entry);
continue;
}
if (entry->protocol == protocol &&
(skip_ip || net_ipv6addr_cmp(entry->external_ip, external_ip)) &&
entry->external_port == external_port
#ifdef CONFIG_NET_NAT66_SYMMETRIC
&& (skip_peer || (net_ipv6addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port))
#endif
)
{
if (refresh)
{
ipv6_nat_entry_refresh(entry);
}
return entry;
}
}
if (refresh) /* false = a test of whether entry exists, no need to warn */
{
nwarn("WARNING: Failed to find IPv6 inbound NAT entry for proto="
"%" PRIu8 ",external=[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]:"
"%" PRIu16 "\n",
protocol, external_ip[0], external_ip[1], external_ip[2],
external_ip[3], external_ip[4], external_ip[5], external_ip[6],
external_ip[7], external_port);
}
return NULL;
}
/****************************************************************************
* Name: ipv6_nat_outbound_entry_find
*
* Description:
* Find the outbound entry in NAT entry list. Create one if corresponding
* entry does not exist.
*
* Input Parameters:
* dev - The device on which the packet will be sent.
* 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:
* Pointer to entry on success; null on failure
*
****************************************************************************/
FAR struct ipv6_nat_entry *
ipv6_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
const net_ipv6addr_t local_ip,
uint16_t local_port,
const net_ipv6addr_t peer_ip,
uint16_t peer_port, bool try_create)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
FAR union ip_addr_u *external_ip;
uint16_t external_port;
int32_t current_time = TICK2SEC(clock_systime_ticks());
ipv6_nat_reclaim_entry(current_time);
hashtable_for_every_possible_safe(g_nat66_outbound, p, tmp,
ipv6_nat_hash_key(local_ip, local_port, protocol))
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_outbound);
/* Remove expired entries. */
if (entry->expire_time - current_time <= 0)
{
ipv6_nat_entry_delete(entry);
continue;
}
if (entry->protocol == protocol &&
NETDEV_IS_MY_V6ADDR(dev, entry->external_ip) &&
net_ipv6addr_cmp(entry->local_ip, local_ip) &&
entry->local_port == local_port
#ifdef CONFIG_NET_NAT66_SYMMETRIC
&& net_ipv6addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port
#endif
)
{
ipv6_nat_entry_refresh(entry);
return entry;
}
}
if (!try_create)
{
return NULL;
}
/* Failed to find the entry, create one. */
ninfo("INFO: Failed to find IPv6 outbound NAT entry for proto=%" PRIu8
", local=[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]:%" PRIu16
", try create one.\n",
protocol, local_ip[0], local_ip[1], local_ip[2], local_ip[3],
local_ip[4], local_ip[5], local_ip[6], local_ip[7], local_port);
external_ip = (FAR union ip_addr_u *)netdev_ipv6_srcaddr(dev, peer_ip);
external_port = nat_port_select(dev, PF_INET6, protocol,
external_ip, local_port);
if (!external_port)
{
nwarn("WARNING: Failed to find an available port!\n");
return NULL;
}
return ipv6_nat_entry_create(protocol, external_ip->ipv6, external_port,
local_ip, local_port, peer_ip, peer_port);
}
#endif /* CONFIG_NET_NAT66 */

View File

@ -27,6 +27,8 @@
#include <debug.h>
#include "icmp/icmp.h"
#include "icmpv6/icmpv6.h"
#include "inet/inet.h"
#include "nat/nat.h"
#include "tcp/tcp.h"
#include "udp/udp.h"
@ -57,7 +59,8 @@
#if (defined(CONFIG_NET_TCP) && defined(CONFIG_NET_TCP_NO_STACK)) || \
(defined(CONFIG_NET_UDP) && defined(CONFIG_NET_UDP_NO_STACK)) || \
(defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_SOCKET))
(defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_SOCKET)) || \
(defined(CONFIG_NET_ICMPv6) && !defined(CONFIG_NET_ICMPv6_SOCKET))
static uint16_t nat_port_select_without_stack(
uint8_t domain, uint8_t protocol, FAR const union ip_addr_u *ip,
@ -145,9 +148,12 @@ int nat_disable(FAR struct net_driver_s *dev)
/* Clear entries related to dev. */
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_NAT44
ipv4_nat_entry_clear(dev);
#endif
#ifdef CONFIG_NET_NAT66
ipv6_nat_entry_clear(dev);
#endif
IFF_CLR_NAT(dev->d_flags);
@ -175,7 +181,7 @@ int nat_disable(FAR struct net_driver_s *dev)
bool nat_port_inuse(uint8_t domain, uint8_t protocol,
FAR const union ip_addr_u *ip, uint16_t port)
{
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_NAT44
if (domain == PF_INET)
{
return !!ipv4_nat_inbound_entry_find(protocol, ip->ipv4, port,
@ -183,6 +189,14 @@ bool nat_port_inuse(uint8_t domain, uint8_t protocol,
}
#endif
#ifdef CONFIG_NET_NAT66
if (domain == PF_INET6)
{
return !!ipv6_nat_inbound_entry_find(protocol, ip->ipv6, port,
g_ipv6_unspecaddr, 0, false);
}
#endif
return false;
}
@ -239,8 +253,25 @@ uint16_t nat_port_select(FAR struct net_driver_s *dev,
{
#ifndef CONFIG_NET_UDP_NO_STACK
union ip_binding_u u;
u.ipv4.laddr = external_ip->ipv4;
u.ipv4.raddr = INADDR_ANY;
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
if (domain == PF_INET)
#endif
{
u.ipv4.laddr = external_ip->ipv4;
u.ipv4.raddr = INADDR_ANY;
}
#endif
#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
else
#endif
{
net_ipv6addr_copy(u.ipv6.laddr, external_ip->ipv6);
net_ipv6addr_copy(u.ipv6.raddr, g_ipv6_unspecaddr);
}
#endif
/* TODO: Try keep origin port as possible. */
@ -278,6 +309,33 @@ uint16_t nat_port_select(FAR struct net_driver_s *dev,
#endif
}
#endif
#ifdef CONFIG_NET_ICMPv6
case IP_PROTO_ICMP6:
{
#ifdef CONFIG_NET_ICMPv6_SOCKET
uint16_t id = local_port;
uint16_t hid = NTOHS(id);
while (icmpv6_active(id) ||
nat_port_inuse(domain, IP_PROTO_ICMP6, external_ip, id))
{
++hid;
if (hid >= CONFIG_NET_DEFAULT_MAX_PORT ||
hid < CONFIG_NET_DEFAULT_MIN_PORT)
{
hid = CONFIG_NET_DEFAULT_MIN_PORT;
}
id = HTONS(hid);
}
return id;
#else
return nat_port_select_without_stack(domain, IP_PROTO_ICMP6,
external_ip, local_port);
#endif
}
#endif
}
/* Select original port for unsupported protocol. */
@ -332,6 +390,12 @@ uint32_t nat_expire_time(uint8_t protocol)
CONFIG_NET_NAT_ICMP_EXPIRE_SEC;
#endif
#ifdef CONFIG_NET_ICMPv6
case IP_PROTO_ICMP6:
return TICK2SEC(clock_systime_ticks()) +
CONFIG_NET_NAT_ICMPv6_EXPIRE_SEC;
#endif
default:
nwarn("WARNING: Unsupported protocol %" PRIu8 "\n", protocol);
return 0;

View File

@ -87,12 +87,12 @@ struct ipv4_nat_entry
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
#ifdef CONFIG_NET_NAT44_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
#ifdef CONFIG_NET_NAT44_SYMMETRIC
uint16_t peer_port; /* Peer port. */
#endif
uint8_t protocol; /* L4 protocol (TCP, UDP etc). */
@ -100,6 +100,26 @@ struct ipv4_nat_entry
int32_t expire_time; /* The expiration time of this entry. */
};
struct ipv6_nat_entry
{
hash_node_t hash_inbound;
hash_node_t hash_outbound;
net_ipv6addr_t local_ip; /* IP address of the local host. */
net_ipv6addr_t external_ip; /* External IP address. */
#ifdef CONFIG_NET_NAT66_SYMMETRIC
net_ipv6addr_t peer_ip; /* Peer IP address. */
#endif
uint16_t local_port; /* Port of the local host. */
uint16_t external_port; /* The external port of local host. */
#ifdef CONFIG_NET_NAT66_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. */
};
/* NAT IP/Port manipulate type, to indicate whether to manipulate source or
* destination IP/Port in a packet.
*/
@ -149,37 +169,36 @@ int nat_enable(FAR struct net_driver_s *dev);
int nat_disable(FAR struct net_driver_s *dev);
/****************************************************************************
* Name: ipv4_nat_inbound
* Name: ipv4/ipv6_nat_inbound
*
* Description:
* Check if a received packet belongs to a NAT entry. If so, translate it.
*
* Input Parameters:
* dev - The device on which the packet is received.
* ipv4 - Points to the IPv4 header with dev->d_buf.
*
* Returned Value:
* Zero is returned if NAT is successfully applied, or is not enabled for
* this packet;
* A negated errno value is returned if error occured.
* dev - The device on which the packet is received.
* ipv4/ipv6 - Points to the IP header with dev->d_buf.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
int ipv4_nat_inbound(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4);
#ifdef CONFIG_NET_NAT44
void ipv4_nat_inbound(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4);
#endif
#ifdef CONFIG_NET_NAT66
void ipv6_nat_inbound(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6);
#endif
/****************************************************************************
* Name: ipv4_nat_outbound
* Name: ipv4/ipv6_nat_outbound
*
* Description:
* Check if we want to perform NAT with this outbound packet before sending
* it. If so, translate it.
*
* Input Parameters:
* dev - The device on which the packet will be sent.
* ipv4 - Points to the IPv4 header to be filled into dev->d_buf later.
* dev - The device on which the packet will be sent.
* ipv4/ipv6 - Points to the IP header to be filled into dev->d_buf later.
* manip_type - Whether local IP/Port is in source or destination.
*
* Returned Value:
@ -189,11 +208,16 @@ int ipv4_nat_inbound(FAR struct net_driver_s *dev,
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_NAT44
int ipv4_nat_outbound(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4,
enum nat_manip_type_e manip_type);
#endif
#ifdef CONFIG_NET_NAT66
int ipv6_nat_outbound(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6,
enum nat_manip_type_e manip_type);
#endif
/****************************************************************************
* Name: nat_port_inuse
@ -255,26 +279,29 @@ uint16_t nat_port_select(FAR struct net_driver_s *dev,
uint32_t nat_expire_time(uint8_t protocol);
/****************************************************************************
* Name: ipv4_nat_entry_clear
* Name: ipv4/ipv6_nat_entry_clear
*
* Description:
* Clear all entries related to dev. Called when NAT will be disabled on
* any device.
*
* Input Parameters:
* dev - The device on which NAT entries will be cleared.
* dev - The device on which NAT entries will be cleared.
*
* Assumptions:
* NAT is initialized.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_NAT44
void ipv4_nat_entry_clear(FAR struct net_driver_s *dev);
#endif
#ifdef CONFIG_NET_NAT66
void ipv6_nat_entry_clear(FAR struct net_driver_s *dev);
#endif
/****************************************************************************
* Name: ipv4_nat_inbound_entry_find
* Name: ipv4/ipv6_nat_inbound_entry_find
*
* Description:
* Find the inbound entry in NAT entry list.
@ -292,15 +319,23 @@ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev);
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_NAT44
FAR struct ipv4_nat_entry *
ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
uint16_t external_port, in_addr_t peer_ip,
uint16_t peer_port, bool refresh);
#endif
#ifdef CONFIG_NET_NAT66
FAR struct ipv6_nat_entry *
ipv6_nat_inbound_entry_find(uint8_t protocol,
const net_ipv6addr_t external_ip,
uint16_t external_port,
const net_ipv6addr_t peer_ip,
uint16_t peer_port, bool refresh);
#endif
/****************************************************************************
* Name: ipv4_nat_outbound_entry_find
* Name: ipv4/ipv6_nat_outbound_entry_find
*
* Description:
* Find the outbound entry in NAT entry list. Create one if corresponding
@ -320,13 +355,21 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip,
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_NAT44
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
#ifdef CONFIG_NET_NAT66
FAR struct ipv6_nat_entry *
ipv6_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
const net_ipv6addr_t local_ip,
uint16_t local_port,
const net_ipv6addr_t peer_ip,
uint16_t peer_port, bool try_create);
#endif
#endif /* CONFIG_NET_NAT */
#endif /* __NET_NAT_NAT_H */