diff --git a/Documentation/components/net/nat.rst b/Documentation/components/net/nat.rst index 621e7b738b..abd1b380f1 100644 --- a/Documentation/components/net/nat.rst +++ b/Documentation/components/net/nat.rst @@ -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 diff --git a/net/devif/ipv4_input.c b/net/devif/ipv4_input.c index 4e7f2eb16f..0875c786a8 100644 --- a/net/devif/ipv4_input.c +++ b/net/devif/ipv4_input.c @@ -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 */ diff --git a/net/devif/ipv6_input.c b/net/devif/ipv6_input.c index 3be7d875a9..b6e133c78d 100644 --- a/net/devif/ipv6_input.c +++ b/net/devif/ipv6_input.c @@ -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 diff --git a/net/ipforward/ipv4_forward.c b/net/ipforward/ipv4_forward.c index 11ffb88613..8554546933 100644 --- a/net/ipforward/ipv4_forward.c +++ b/net/ipforward/ipv4_forward.c @@ -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; diff --git a/net/ipforward/ipv6_forward.c b/net/ipforward/ipv6_forward.c index 769fce8138..5f39a0ad08 100644 --- a/net/ipforward/ipv6_forward.c +++ b/net/ipforward/ipv6_forward.c @@ -35,6 +35,7 @@ #include #include +#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 */ } /**************************************************************************** diff --git a/net/nat/CMakeLists.txt b/net/nat/CMakeLists.txt index ebf100a46a..9bb2c7443a 100644 --- a/net/nat/CMakeLists.txt +++ b/net/nat/CMakeLists.txt @@ -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() diff --git a/net/nat/Kconfig b/net/nat/Kconfig index 0ce9e71b1d..dd95682772 100644 --- a/net/nat/Kconfig +++ b/net/nat/Kconfig @@ -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 diff --git a/net/nat/Make.defs b/net/nat/Make.defs index ee4fe9cbe2..b5316ca4c9 100644 --- a/net/nat/Make.defs +++ b/net/nat/Make.defs @@ -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 diff --git a/net/nat/ipv4_nat.c b/net/nat/ipv4_nat.c index 06c627bd4e..ac4e6e45a1 100644 --- a/net/nat/ipv4_nat.c +++ b/net/nat/ipv4_nat.c @@ -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 */ diff --git a/net/nat/ipv4_nat_entry.c b/net/nat/ipv4_nat_entry.c index 7b033a5d0f..1534e448f4 100644 --- a/net/nat/ipv4_nat_entry.c +++ b/net/nat/ipv4_nat_entry.c @@ -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 */ diff --git a/net/nat/ipv6_nat.c b/net/nat/ipv6_nat.c new file mode 100644 index 0000000000..c03a3c6f87 --- /dev/null +++ b/net/nat/ipv6_nat.c @@ -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 + +#include +#include +#include +#include + +#include +#include +#include + +#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 */ diff --git a/net/nat/ipv6_nat_entry.c b/net/nat/ipv6_nat_entry.c new file mode 100644 index 0000000000..e9e4a00a43 --- /dev/null +++ b/net/nat/ipv6_nat_entry.c @@ -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 + +#include +#include + +#include +#include +#include +#include + +#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 */ diff --git a/net/nat/nat.c b/net/nat/nat.c index 0c3dc02907..a44f91b37b 100644 --- a/net/nat/nat.c +++ b/net/nat/nat.c @@ -27,6 +27,8 @@ #include #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; diff --git a/net/nat/nat.h b/net/nat/nat.h index c5921c77db..ed4acd60f1 100644 --- a/net/nat/nat.h +++ b/net/nat/nat.h @@ -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 */