From 18cd40cd5d6ecf8d99fa345dc8f71cb385aab281 Mon Sep 17 00:00:00 2001 From: Shunchao Hu Date: Wed, 29 Apr 2026 14:31:00 +0800 Subject: [PATCH] net/nat: Update NAT rst documentation. Update the NAT documentation to make the validation flow easier to follow. The existing document already contains useful details, but the test setup is hard to read for beginners. - In particular, the NuttX-side commands and Linux host commands can be confused - Some commands are redundant - Some required steps are missing - The Internet access example is not necessary for NAT behavior itself Simplify the validation case to use a local topology with Linux network namespaces. Add an ICMP test case and the required explanations around the NuttX iptables command and interface roles. Remove the IPv6 validation case from this doc, since the NAT66 flow follows the same structure as the NAT44 case. Signed-off-by: Shunchao Hu --- Documentation/components/net/nat.rst | 388 +++++++++++++++++---------- 1 file changed, 252 insertions(+), 136 deletions(-) diff --git a/Documentation/components/net/nat.rst b/Documentation/components/net/nat.rst index abd1b380f1c..1d12cf3e55a 100644 --- a/Documentation/components/net/nat.rst +++ b/Documentation/components/net/nat.rst @@ -2,17 +2,35 @@ Network Address Translation (NAT) ================================= -NuttX supports full cone or symmetric NAT logic, which currently supports +Network Address Translation (NAT) modifies addresses, ports, or ICMP +identifiers in forwarded packets so that traffic from one network can use +the address of another interface. -- TCP +NuttX supports build-time selectable NAT modes for NAT44 and NAT66: -- UDP +``full-cone NAT`` + Maps a local address and port, or ICMP identifier, to an external + address and port or identifier. The peer endpoint is not part of the NAT + entry key. -- ICMP +``symmetric NAT`` + Maps a local endpoint to an external endpoint for a specific peer + endpoint. The peer endpoint is part of the NAT entry key, so traffic from + the same local endpoint to different peers may use different mappings. - - ECHO (REQUEST & REPLY) +The Nuttx NAT implementation supports: - - Error Messages (DEST_UNREACHABLE & TIME_EXCEEDED & PARAMETER_PROBLEM) + * TCP + * UDP + * ICMP + + * ECHO (REQUEST / REPLY) + * Error Messages (DEST_UNREACHABLE / TIME_EXCEEDED / PARAMETER_PROBLEM) + + * ICMPv6 + + * ECHO (REQUEST / REPLY) + * Error Messages (DEST_UNREACHABLE / PACKET_TOO_BIG / TIME_EXCEEDED / PARAMETER_PROBLEM) Workflow ======== @@ -44,185 +62,283 @@ Configuration Options ===================== ``CONFIG_NET_NAT`` - Enable or disable Network Address Translation (NAT) function. - Depends on ``CONFIG_NET_IPFORWARD``. -``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_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. + Enable Network Address Translation. This option depends on + ``CONFIG_NET_IPFORWARD``. +``CONFIG_NET_NAT44`` / ``CONFIG_NET_NAT66`` + Enable IPv4-to-IPv4 / IPv6-to-IPv6 NAT. This option depends on + ``CONFIG_NET_NAT``. +``CONFIG_NET_NAT44_FULL_CONE`` / ``CONFIG_NET_NAT66_FULL_CONE`` + Select full-cone NAT mode for NAT44 / NAT66. +``CONFIG_NET_NAT44_SYMMETRIC`` / ``CONFIG_NET_NAT66_SYMMETRIC`` + Select symmetric NAT mode for NAT44 / NAT66. ``CONFIG_NET_NAT_HASH_BITS`` - The bits of the hashtable of NAT entries, hashtable has (1 << bits) buckets. + Set the number of bits used for the NAT entry hash table. The hash + table has ``1 << CONFIG_NET_NAT_HASH_BITS`` buckets. ``CONFIG_NET_NAT_TCP_EXPIRE_SEC`` - The expiration time for idle TCP entry in NAT. - The default value 86400 is suggested by RFC2663, Section 2.6, - Page 5. But we may set it to shorter time like 240s for better - performance. + Set the expiration time, in seconds, for idle TCP NAT entries. The + default value is 86400 seconds, as suggested by RFC 2663, Section 2.6, + Page 5. But we may set it to shorter time like 240s for better + performance. ``CONFIG_NET_NAT_UDP_EXPIRE_SEC`` - The expiration time for idle UDP entry in NAT. + Set the expiration time, in seconds, for idle UDP NAT entries. ``CONFIG_NET_NAT_ICMP_EXPIRE_SEC`` - The expiration time for idle ICMP entry in NAT. + Set the expiration time, in seconds, for idle ICMP NAT entries. ``CONFIG_NET_NAT_ICMPv6_EXPIRE_SEC`` - The expiration time for idle ICMPv6 entry in NAT. + Set the expiration time, in seconds, for idle ICMPv6 NAT entries. ``CONFIG_NET_NAT_ENTRY_RECLAIM_SEC`` - The time to auto reclaim all expired NAT entries. A value of zero will - disable auto reclaiming. - Expired entries will be automatically reclaimed when matching - inbound/outbound entries, so this config does not have significant - impact when NAT is normally used, but very useful when the hashtable - is big and there are only a few connections using NAT (which will - only trigger reclaiming on a few chains in hashtable). + Set the time to auto reclaim all expired NAT entries. A value of zero + will disable auto reclaiming. + Because expired entries will be automatically reclaimed when matching + inbound/outbound entries, so this config does not have significant + impact when NAT is normally used, but very useful when the hashtable + is big and there are only a few connections using NAT (which will + only trigger reclaiming on a few chains in hashtable). Usage ===== - - :c:func:`nat_enable()` - - :c:func:`nat_disable()` +- NAT can be enabled directly from C code: -.. c:function:: int nat_enable(FAR struct net_driver_s *dev); + .. c:function:: int nat_enable(FAR struct net_driver_s *dev) - Enable NAT function on a network device, on which the outbound packets - will be masqueraded. + Enable NAT on a network device. Outbound packets forwarded through this + device may be translated. - :return: Zero is returned if NAT function is successfully enabled on - the device; A negated errno value is returned if failed. + :return: Zero is returned if NAT is successfully enabled on the device. + A negated errno value is returned on failure. -.. c:function:: int nat_disable(FAR struct net_driver_s *dev); + .. c:function:: int nat_disable(FAR struct net_driver_s *dev) - Disable NAT function on a network device. + Disable NAT on a network device. - :return: Zero is returned if NAT function is successfully disabled on - the device; A negated errno value is returned if failed. + :return: Zero is returned if NAT is successfully disabled on the device. + A negated errno value is returned on failure. + +- NAT can also be enabled from the NSH with the ``iptables`` command. + The rule is added to the NAT table, and the output interface + passed to ``-o`` is the NuttX external interface: + + .. code-block:: console + + nsh> iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE + + To remove the rule, use the same rule specification with ``-D``: + + .. code-block:: console + + nsh> iptables -t nat -D POSTROUTING -o eth1 -j MASQUERADE + + .. note:: + + The commands above are NuttX ``iptables`` commands. They configure NAT + inside NuttX. They are separate from Linux host ``iptables`` commands. Validation ========== -Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps: +The following setup validates NAT with the NuttX simulator on an Ubuntu +22.04 x86_64 Linux host. IPv4 is used as the main example. -1. Configure NuttX with >=2 TAP devices (host route mode) and NAT enabled: +The test uses two Linux network namespaces: - .. code-block:: Kconfig + * ``private`` represents a host behind NuttX NAT. + * ``public`` represents a host on the external side of NuttX NAT. - CONFIG_NET_IPFORWARD=y - CONFIG_NET_NAT=y - # CONFIG_SIM_NET_BRIDGE is not set - CONFIG_SIM_NETDEV_NUMBER=2 +This keeps the test local to the host and does not require Internet access. -2. Call ``nat_enable`` on one dev on startup, or manually enable NAT - with ``iptables`` command (either may work). +The topology is: - .. code-block:: c +:: - /* arch/sim/src/sim/up_netdriver.c */ - int netdriver_init(void) - { - ... - nat_enable(&g_sim_dev[0]); - ... - } + Private Namespace NuttX simulator Public Namespace + |----------------------------------| + tap0 | eth0: 192.168.0.2/24 | + 192.168.0.1/24 ------| | + | | + | eth1: 10.0.1.2/24 |------ 10.0.1.1/24 + | (NAT enabled) | tap1 + |----------------------------------| - .. code-block:: shell +In this topology: - iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE + * ``eth0`` is the NuttX private-side interface. + * ``eth1`` is the NuttX external interface. NAT is enabled on ``eth1``. + * ``tap0`` is moved into the Linux ``private`` namespace and is the peer of + ``eth0``. + * ``tap1`` is moved into the Linux ``public`` namespace and is the peer of + ``eth1``. -3. Set IP Address for NuttX on startup +Step 1: Configure NuttX simulator +--------------------------------- - .. code-block:: shell +Configure NuttX simulator with at least two TAP devices, IP forwarding and NAT: - ifconfig eth0 10.0.1.2 - ifup eth0 - ifconfig eth1 10.0.10.2 - ifup eth1 +.. code-block:: kconfig - # IPv6 if you need - ifconfig eth0 inet6 add fc00:1::2/64 gw fc00:1::1 - ifconfig eth1 inet6 add fc00:10::2/64 + CONFIG_NET_IPFORWARD=y + CONFIG_NET_NAT=y + CONFIG_NET_NAT44=y + CONFIG_simulator_NETDEV_NUMBER=2 -4. Configure IP & namespace & route on host side (maybe need to be root, then try ``sudo -i``) +Step 2: Start NuttX simulator +----------------------------- - .. code-block:: bash +Start the NuttX simulator and make sure it creates two TAP interfaces on the +Linux host. Creating or configuring TAP interfaces may require root privileges +or capabilities such as ``CAP_NET_ADMIN``. The interface names are assumed to +be ``tap0`` and ``tap1`` in the commands below. - IF_HOST="enp1s0" - IF_0="tap0" - IP_HOST_0="10.0.1.1" - IF_1="tap1" - IP_HOST_1="10.0.10.1" - IP_NUTTX_1="10.0.10.2" +Step 3: Configure Linux namespaces +---------------------------------- - # add net namespace LAN for $IF_1 - ip netns add LAN - ip netns exec LAN sysctl -w net.ipv4.ip_forward=1 - ip link set $IF_1 netns LAN - ip netns exec LAN ip link set $IF_1 up - ip netns exec LAN ip link set lo up +Run the following commands on the Linux host: - # add address and set default route - ip addr add $IP_HOST_0/24 dev $IF_0 - ip netns exec LAN ip addr add $IP_HOST_1/24 dev $IF_1 - ip netns exec LAN ip route add default dev $IF_1 via $IP_NUTTX_1 +.. code-block:: bash - # nat to allow NuttX to access the internet - iptables -t nat -A POSTROUTING -o $IF_HOST -j MASQUERADE - iptables -A FORWARD -i $IF_HOST -o $IF_0 -j ACCEPT - iptables -A FORWARD -i $IF_0 -o $IF_HOST -j ACCEPT - sysctl -w net.ipv4.ip_forward=1 + NS_PRIVATE="private" + NS_PUBLIC="public" - # IPv6 if you need - IP6_HOST_0="fc00:1::1" - IP6_HOST_1="fc00:10::1" - IP6_NUTTX_1="fc00:10::2" + IF_PRIVATE="tap0" + IP_PRIVATE="192.168.0.1" + IP_NUTTX_PRIVATE="192.168.0.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 + IF_PUBLIC="tap1" + IP_PUBLIC="10.0.1.1" + IP_NUTTX_PUBLIC="10.0.1.2" - # 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 + sudo ip netns add "$NS_PRIVATE" + sudo ip netns add "$NS_PUBLIC" -5. Do anything in the LAN namespace will go through NAT + sudo ip link set "$IF_PRIVATE" netns "$NS_PRIVATE" + sudo ip link set "$IF_PUBLIC" netns "$NS_PUBLIC" - .. code-block:: shell + # Private namespace + sudo ip netns exec "$NS_PRIVATE" ip link set lo up + sudo ip netns exec "$NS_PRIVATE" ip link set "$IF_PRIVATE" up + sudo ip netns exec "$NS_PRIVATE" ip addr add "$IP_PRIVATE/24" dev "$IF_PRIVATE" + sudo ip netns exec "$NS_PRIVATE" ip route add default via "$IP_NUTTX_PRIVATE" dev "$IF_PRIVATE" - # Host side - iperf -B 10.0.1.1 -s -i 1 - # LAN side - sudo ip netns exec LAN iperf -B 10.0.10.1 -c 10.0.1.1 -i 1 + # Public namespace + sudo ip netns exec "$NS_PUBLIC" ip link set lo up + sudo ip netns exec "$NS_PUBLIC" ip link set "$IF_PUBLIC" up + sudo ip netns exec "$NS_PUBLIC" ip addr add "$IP_PUBLIC/24" dev "$IF_PUBLIC" - .. code-block:: shell +The ``public`` namespace does not need a route back to ``192.168.0.0/24`` for +normal NAT validation, because return traffic is addressed to the translated +external address ``10.0.1.2``. - # Host side - 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 +Step 4: Configure NuttX network interfaces +------------------------------------------ - .. code-block:: shell +Configure the NuttX interface addresses from NSH: - # LAN side - sudo ip netns exec LAN ping 8.8.8.8 - sudo ip netns exec LAN ping 2001:4860:4860::8888 +.. code-block:: console - .. code-block:: shell + nsh> ifconfig eth0 192.168.0.2 + nsh> ifup eth0 + nsh> ifconfig eth1 10.0.1.2 + nsh> ifup eth1 + nsh> addroute default 10.0.1.1 eth1 - # 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 +Enable NAT on NuttX ``eth1``. Either call ``nat_enable()`` during network +initialization or run this command from NSH: - .. code-block:: shell +.. code-block:: console - # Host side - tcpdump -nn -i tap0 - # LAN side - sudo ip netns exec LAN tcpdump -nn -i tap1 + nsh> iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE + +Step 5: Validate basic connectivity +----------------------------------- + +Before testing NAT, verify that each direct link works. + +From NuttX, ping the private-side peer: + +.. code-block:: console + + nsh> ping 192.168.0.1 + +From NuttX, ping the public-side peer: + +.. code-block:: console + + nsh> ping 10.0.1.1 + +From the private namespace, ping the NuttX private-side interface: + +.. code-block:: bash + + sudo ip netns exec private ping 192.168.0.2 + +From the public namespace, ping the NuttX external interface: + +.. code-block:: bash + + sudo ip netns exec public ping 10.0.1.2 + +Step 6: Test TCP NAT +-------------------- + +Start an iperf server in the ``public`` namespace: + +.. code-block:: bash + + sudo ip netns exec public iperf -B 10.0.1.1 -s -i 1 + +Run the iperf client from the ``private`` namespace: + +.. code-block:: bash + + sudo ip netns exec private iperf -B 192.168.0.1 -c 10.0.1.1 -i 1 + +The server should see the connection as coming from the NuttX external +address ``10.0.1.2``, not from the private address ``192.168.0.1``. + +Step 7: Test ICMP NAT +--------------------- + +Run ping from the ``private`` namespace to the ``public`` namespace: + +.. code-block:: bash + + sudo ip netns exec private ping 10.0.1.1 + +On the public side, the ICMP Echo Request should be translated to use the +NuttX external address ``10.0.1.2`` as the source address. + +Step 8: Test HTTP NAT +--------------------- + +Start a simple HTTP server in the ``public`` namespace: + +.. code-block:: bash + + sudo ip netns exec public python3 -m http.server 8000 -b 10.0.1.1 + +Run HTTP requests from the ``private`` namespace: + +.. code-block:: bash + + for i in $(seq 1 20000); do + sudo ip netns exec private curl -sS -o /dev/null 'http://10.0.1.1:8000/' + done + +Step 9: Capture packets +----------------------- + +Capture on the private side: + +.. code-block:: bash + + sudo ip netns exec private tcpdump -nn -i tap0 + +Capture on the public side: + +.. code-block:: bash + + sudo ip netns exec public tcpdump -nn -i tap1 + +For outbound IPv4 traffic, ``tap0`` should show packets sourced from +``192.168.0.1`` and ``tap1`` should show the same flow after translation, +sourced from ``10.0.1.2``. The inbound return path should show the reverse +translation.