net/nat: Add support for ICMP Error Message

Support DEST_UNREACHABLE, TIME_EXCEEDED and PARAMETER_PROBLEM ICMP types in NAT.

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
This commit is contained in:
Zhe Weng 2022-11-15 17:49:43 +08:00 committed by Xiang Xiao
parent 23dfdeacab
commit 9aefd6717c
5 changed files with 205 additions and 15 deletions

View File

@ -5,8 +5,14 @@ Network Address Translation (NAT)
NuttX supports full cone NAT logic, which currently supports
- TCP
- UDP
- ICMP ECHO (REQUEST & REPLY)
- ICMP
- ECHO (REQUEST & REPLY)
- Error Messages (DEST_UNREACHABLE & TIME_EXCEEDED & PARAMETER_PROBLEM)
Workflow
========
@ -161,6 +167,12 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
# LAN side
sudo ip netns exec LAN ping 8.8.8.8
.. 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
.. code-block:: shell
# Host side

View File

@ -6,10 +6,15 @@
config NET_NAT
bool "Network Address Translation (NAT)"
default n
depends on NET_IPFORWARD
depends on NET_IPFORWARD && IOB_BUFSIZE >= 68
---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).
config NET_NAT_TCP_EXPIRE_SEC
int "TCP NAT entry expiration seconds"
default 86400

View File

@ -24,9 +24,11 @@
#include <nuttx/config.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <nuttx/net/icmp.h>
@ -62,6 +64,18 @@
#define L4_HDR(ipv4) \
(FAR void *)((FAR uint8_t *)(ipv4) + (((ipv4)->vhl & IPv4_HLMASK) << 2))
#define L4_HDRLEN(proto) \
((proto) == IP_PROTO_TCP ? TCP_HDRLEN : \
(proto) == IP_PROTO_UDP ? UDP_HDRLEN : ICMP_HDRLEN)
#if defined(CONFIG_NET_TCP)
# define L4_MAXHDRLEN TCP_HDRLEN
#elif defined(CONFIG_NET_UDP)
# define L4_MAXHDRLEN UDP_HDRLEN
#elif defined(CONFIG_NET_ICMP)
# define L4_MAXHDRLEN ICMP_HDRLEN
#endif
/****************************************************************************
* Private Types
****************************************************************************/
@ -76,6 +90,19 @@ enum nat_manip_type_e
NAT_MANIP_DST
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static FAR struct ipv4_nat_entry *
ipv4_nat_inbound_internal(FAR struct ipv4_hdr_s *ipv4,
enum nat_manip_type_e manip_type);
static FAR struct ipv4_nat_entry *
ipv4_nat_outbound_internal(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4,
enum nat_manip_type_e manip_type);
/****************************************************************************
* Private Functions
****************************************************************************/
@ -167,6 +194,11 @@ ipv4_nat_inbound_tcp(FAR struct ipv4_hdr_s *ipv4,
return NULL;
}
/* Note: Field tcpchksum is not guaranteed exists in TCP header inside
* ICMP Error MSG, but we manually guarantee that it is inside valid memory
* address (IOB >= IP + ICMP + IP + TCP), so we can update it safely.
*/
ipv4_nat_port_adjust(&tcp->tcpchksum, external_port, entry->local_port);
ipv4_nat_ip_adjust(ipv4, &tcp->tcpchksum, entry->local_ip, manip_type);
@ -250,8 +282,6 @@ ipv4_nat_inbound_icmp(FAR struct ipv4_hdr_s *ipv4,
switch (icmp->type)
{
/* TODO: Support other ICMP types. */
case ICMP_ECHO_REQUEST:
case ICMP_ECHO_REPLY:
entry = ipv4_nat_inbound_entry_find(IP_PROTO_ICMP, icmp->id, true);
@ -264,6 +294,64 @@ ipv4_nat_inbound_icmp(FAR struct ipv4_hdr_s *ipv4,
&icmp->id, entry->local_port);
ipv4_nat_ip_adjust(ipv4, NULL, entry->local_ip, manip_type);
return entry;
case ICMP_DEST_UNREACHABLE:
case ICMP_TIME_EXCEEDED:
case ICMP_PARAMETER_PROBLEM:
/* ICMP Error MSG inside another ICMP Error MSG is forbidden by
* RFC1122, Section 3.2.2, Page 38, so we only process the outermost
* ICMP Error MSG (manip type is DST).
*/
if (manip_type == NAT_MANIP_DST)
{
/* The payload in the ICMP packet is the origin packet we sent. */
FAR struct ipv4_hdr_s *inner =
(FAR struct ipv4_hdr_s *)(icmp + 1);
FAR void *inner_l4 = L4_HDR(inner);
int16_t inner_l4len = ((ipv4->len[0] << 8) + ipv4->len[1]) -
((intptr_t)inner_l4 - (intptr_t)ipv4);
uint16_t inner_l4hdrbak[L4_MAXHDRLEN / 2];
uint16_t inner_l4hdrlen;
if (inner_l4len < 8)
{
/* RFC792: The original L4 data should be at least 64 bits. */
return NULL;
}
/* Try backup origin L4 header for later checksum update. */
inner_l4hdrlen = MIN(inner_l4len, L4_HDRLEN(inner->proto));
DEBUGASSERT((intptr_t)inner_l4 - (intptr_t)ipv4 + inner_l4hdrlen
<= CONFIG_IOB_BUFSIZE);
memcpy(inner_l4hdrbak, inner_l4, inner_l4hdrlen);
/* Find entry and translate inner. */
entry = ipv4_nat_inbound_internal(inner, NAT_MANIP_SRC);
if (!entry)
{
return NULL;
}
/* Adjust outer IP */
ipv4_nat_ip_adjust(ipv4, NULL, entry->local_ip, manip_type);
/* Recalculate ICMP checksum, we only need to re-calc data in L4
* header, because the inner IPv4 header's checksum is updated,
* and the overall checksum of IPv4 header will not change.
*/
net_chksum_adjust(&icmp->icmpchksum, inner_l4hdrbak,
inner_l4hdrlen, inner_l4, inner_l4hdrlen);
return entry;
}
}
return NULL;
@ -299,13 +387,23 @@ ipv4_nat_outbound_tcp(FAR struct net_driver_s *dev,
FAR struct tcp_hdr_s *tcp = L4_HDR(ipv4);
FAR uint16_t (*local_ip)[2] = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *local_port = MANIP_PORT(tcp, manip_type);
FAR struct ipv4_nat_entry *entry = ipv4_nat_outbound_entry_find(
dev, IP_PROTO_TCP, net_ip4addr_conv32(*local_ip), *local_port);
FAR struct ipv4_nat_entry *entry;
/* Only create entry when it's the outermost packet (manip type is SRC). */
entry = ipv4_nat_outbound_entry_find(dev, IP_PROTO_TCP,
net_ip4addr_conv32(*local_ip), *local_port,
(manip_type == NAT_MANIP_SRC));
if (!entry)
{
return NULL;
}
/* Note: Field tcpchksum is not guaranteed exists in TCP header inside
* ICMP Error MSG, but we manually guarantee that it is inside valid memory
* address (IOB >= IP + ICMP + IP + TCP), so we can update it safely.
*/
ipv4_nat_port_adjust(&tcp->tcpchksum, local_port, entry->external_port);
ipv4_nat_ip_adjust(ipv4, &tcp->tcpchksum, dev->d_ipaddr, manip_type);
@ -343,8 +441,13 @@ ipv4_nat_outbound_udp(FAR struct net_driver_s *dev,
FAR uint16_t (*local_ip)[2] = MANIP_IPADDR(ipv4, manip_type);
FAR uint16_t *local_port = MANIP_PORT(udp, manip_type);
FAR uint16_t *udpchksum;
FAR struct ipv4_nat_entry *entry = ipv4_nat_outbound_entry_find(
dev, IP_PROTO_UDP, net_ip4addr_conv32(*local_ip), *local_port);
FAR struct ipv4_nat_entry *entry;
/* Only create entry when it's the outermost packet (manip type is SRC). */
entry = ipv4_nat_outbound_entry_find(dev, IP_PROTO_UDP,
net_ip4addr_conv32(*local_ip), *local_port,
(manip_type == NAT_MANIP_SRC));
if (!entry)
{
return NULL;
@ -393,13 +496,16 @@ ipv4_nat_outbound_icmp(FAR struct net_driver_s *dev,
switch (icmp->type)
{
/* TODO: Support other ICMP types. */
case ICMP_ECHO_REQUEST:
case ICMP_ECHO_REPLY:
entry = ipv4_nat_outbound_entry_find(
dev, IP_PROTO_ICMP, net_ip4addr_conv32(*local_ip),
icmp->id);
/* Note: Only create new entry when it's the outermost packet (that
* is, manip type is SRC).
*/
entry = ipv4_nat_outbound_entry_find(dev, IP_PROTO_ICMP,
net_ip4addr_conv32(*local_ip), icmp->id,
(manip_type == NAT_MANIP_SRC));
if (!entry)
{
return NULL;
@ -409,6 +515,64 @@ ipv4_nat_outbound_icmp(FAR struct net_driver_s *dev,
&icmp->id, entry->external_port);
ipv4_nat_ip_adjust(ipv4, NULL, dev->d_ipaddr, manip_type);
return entry;
case ICMP_DEST_UNREACHABLE:
case ICMP_TIME_EXCEEDED:
case ICMP_PARAMETER_PROBLEM:
/* ICMP Error MSG inside another ICMP Error MSG is forbidden by
* RFC1122, Section 3.2.2, Page 38, so we only process the outermost
* ICMP Error MSG (manip type is SRC).
*/
if (manip_type == NAT_MANIP_SRC)
{
/* The payload in the ICMP packet is the origin packet we got. */
FAR struct ipv4_hdr_s *inner =
(FAR struct ipv4_hdr_s *)(icmp + 1);
FAR void *inner_l4 = L4_HDR(inner);
int16_t inner_l4len = ((ipv4->len[0] << 8) + ipv4->len[1]) -
((intptr_t)inner_l4 - (intptr_t)ipv4);
uint16_t inner_l4hdrbak[L4_MAXHDRLEN / 2];
uint16_t inner_l4hdrlen;
if (inner_l4len < 8)
{
/* RFC792: The original L4 data should be at least 64 bits. */
return NULL;
}
/* Try backup origin L4 header for later checksum update. */
inner_l4hdrlen = MIN(inner_l4len, L4_HDRLEN(inner->proto));
DEBUGASSERT((intptr_t)inner_l4 - (intptr_t)ipv4 + inner_l4hdrlen
<= CONFIG_IOB_BUFSIZE);
memcpy(inner_l4hdrbak, inner_l4, inner_l4hdrlen);
/* Find entry and translate inner. */
entry = ipv4_nat_outbound_internal(dev, inner, NAT_MANIP_DST);
if (!entry)
{
return NULL;
}
/* Adjust outer IP */
ipv4_nat_ip_adjust(ipv4, NULL, dev->d_ipaddr, manip_type);
/* Recalculate ICMP checksum, we only need to re-calc data in L4
* header, because the inner IPv4 header's checksum is updated,
* and the overall checksum of IPv4 header will not change.
*/
net_chksum_adjust(&icmp->icmpchksum, inner_l4hdrbak,
inner_l4hdrlen, inner_l4, inner_l4hdrlen);
return entry;
}
}
return NULL;

View File

@ -375,6 +375,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
* protocol - The L4 protocol of the packet.
* local_ip - The local ip of the packet.
* local_port - The local port of the packet.
* try_create - Try create the entry if no entry found.
*
* Returned Value:
* Pointer to entry on success; null on failure
@ -383,7 +384,8 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
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 local_ip, uint16_t local_port,
bool try_create)
{
FAR sq_entry_t *p;
FAR sq_entry_t *tmp;
@ -412,6 +414,11 @@ ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
}
}
if (!try_create)
{
return NULL;
}
/* Failed to find the entry, create one. */
ninfo("INFO: Failed to find IPv4 outbound NAT entry for "

View File

@ -208,6 +208,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
* protocol - The L4 protocol of the packet.
* local_ip - The local ip of the packet.
* local_port - The local port of the packet.
* try_create - Try create the entry if no entry found.
*
* Returned Value:
* Pointer to entry on success; null on failure
@ -216,7 +217,8 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
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 local_ip, uint16_t local_port,
bool try_create);
#endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */
#endif /* __NET_NAT_NAT_H */