mirror of
https://github.com/apache/nuttx.git
synced 2026-05-27 19:36:35 +08:00
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:
@@ -5,8 +5,14 @@ Network Address Translation (NAT)
|
|||||||
NuttX supports full cone NAT logic, which currently supports
|
NuttX supports full cone NAT logic, which currently supports
|
||||||
|
|
||||||
- TCP
|
- TCP
|
||||||
|
|
||||||
- UDP
|
- UDP
|
||||||
- ICMP ECHO (REQUEST & REPLY)
|
|
||||||
|
- ICMP
|
||||||
|
|
||||||
|
- ECHO (REQUEST & REPLY)
|
||||||
|
|
||||||
|
- Error Messages (DEST_UNREACHABLE & TIME_EXCEEDED & PARAMETER_PROBLEM)
|
||||||
|
|
||||||
Workflow
|
Workflow
|
||||||
========
|
========
|
||||||
@@ -161,6 +167,12 @@ Validated on Ubuntu 22.04 x86_64 with NuttX SIM by following steps:
|
|||||||
# LAN side
|
# LAN side
|
||||||
sudo ip netns exec LAN ping 8.8.8.8
|
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
|
.. code-block:: shell
|
||||||
|
|
||||||
# Host side
|
# Host side
|
||||||
|
|||||||
+6
-1
@@ -6,10 +6,15 @@
|
|||||||
config NET_NAT
|
config NET_NAT
|
||||||
bool "Network Address Translation (NAT)"
|
bool "Network Address Translation (NAT)"
|
||||||
default n
|
default n
|
||||||
depends on NET_IPFORWARD
|
depends on NET_IPFORWARD && IOB_BUFSIZE >= 68
|
||||||
---help---
|
---help---
|
||||||
Enable or disable Network Address Translation (NAT) function.
|
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
|
config NET_NAT_TCP_EXPIRE_SEC
|
||||||
int "TCP NAT entry expiration seconds"
|
int "TCP NAT entry expiration seconds"
|
||||||
default 86400
|
default 86400
|
||||||
|
|||||||
+175
-11
@@ -24,9 +24,11 @@
|
|||||||
|
|
||||||
#include <nuttx/config.h>
|
#include <nuttx/config.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <debug.h>
|
#include <debug.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include <nuttx/net/icmp.h>
|
#include <nuttx/net/icmp.h>
|
||||||
@@ -62,6 +64,18 @@
|
|||||||
#define L4_HDR(ipv4) \
|
#define L4_HDR(ipv4) \
|
||||||
(FAR void *)((FAR uint8_t *)(ipv4) + (((ipv4)->vhl & IPv4_HLMASK) << 2))
|
(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
|
* Private Types
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@@ -76,6 +90,19 @@ enum nat_manip_type_e
|
|||||||
NAT_MANIP_DST
|
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
|
* Private Functions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@@ -167,6 +194,11 @@ ipv4_nat_inbound_tcp(FAR struct ipv4_hdr_s *ipv4,
|
|||||||
return NULL;
|
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_port_adjust(&tcp->tcpchksum, external_port, entry->local_port);
|
||||||
ipv4_nat_ip_adjust(ipv4, &tcp->tcpchksum, entry->local_ip, manip_type);
|
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)
|
switch (icmp->type)
|
||||||
{
|
{
|
||||||
/* TODO: Support other ICMP types. */
|
|
||||||
|
|
||||||
case ICMP_ECHO_REQUEST:
|
case ICMP_ECHO_REQUEST:
|
||||||
case ICMP_ECHO_REPLY:
|
case ICMP_ECHO_REPLY:
|
||||||
entry = ipv4_nat_inbound_entry_find(IP_PROTO_ICMP, icmp->id, true);
|
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);
|
&icmp->id, entry->local_port);
|
||||||
ipv4_nat_ip_adjust(ipv4, NULL, entry->local_ip, manip_type);
|
ipv4_nat_ip_adjust(ipv4, NULL, entry->local_ip, manip_type);
|
||||||
return entry;
|
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;
|
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 struct tcp_hdr_s *tcp = L4_HDR(ipv4);
|
||||||
FAR uint16_t (*local_ip)[2] = MANIP_IPADDR(ipv4, manip_type);
|
FAR uint16_t (*local_ip)[2] = MANIP_IPADDR(ipv4, manip_type);
|
||||||
FAR uint16_t *local_port = MANIP_PORT(tcp, manip_type);
|
FAR uint16_t *local_port = MANIP_PORT(tcp, manip_type);
|
||||||
FAR struct ipv4_nat_entry *entry = ipv4_nat_outbound_entry_find(
|
FAR struct ipv4_nat_entry *entry;
|
||||||
dev, IP_PROTO_TCP, net_ip4addr_conv32(*local_ip), *local_port);
|
|
||||||
|
/* 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)
|
if (!entry)
|
||||||
{
|
{
|
||||||
return NULL;
|
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_port_adjust(&tcp->tcpchksum, local_port, entry->external_port);
|
||||||
ipv4_nat_ip_adjust(ipv4, &tcp->tcpchksum, dev->d_ipaddr, manip_type);
|
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_ip)[2] = MANIP_IPADDR(ipv4, manip_type);
|
||||||
FAR uint16_t *local_port = MANIP_PORT(udp, manip_type);
|
FAR uint16_t *local_port = MANIP_PORT(udp, manip_type);
|
||||||
FAR uint16_t *udpchksum;
|
FAR uint16_t *udpchksum;
|
||||||
FAR struct ipv4_nat_entry *entry = ipv4_nat_outbound_entry_find(
|
FAR struct ipv4_nat_entry *entry;
|
||||||
dev, IP_PROTO_UDP, net_ip4addr_conv32(*local_ip), *local_port);
|
|
||||||
|
/* 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)
|
if (!entry)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -393,13 +496,16 @@ ipv4_nat_outbound_icmp(FAR struct net_driver_s *dev,
|
|||||||
|
|
||||||
switch (icmp->type)
|
switch (icmp->type)
|
||||||
{
|
{
|
||||||
/* TODO: Support other ICMP types. */
|
|
||||||
|
|
||||||
case ICMP_ECHO_REQUEST:
|
case ICMP_ECHO_REQUEST:
|
||||||
case ICMP_ECHO_REPLY:
|
case ICMP_ECHO_REPLY:
|
||||||
entry = ipv4_nat_outbound_entry_find(
|
|
||||||
dev, IP_PROTO_ICMP, net_ip4addr_conv32(*local_ip),
|
/* Note: Only create new entry when it's the outermost packet (that
|
||||||
icmp->id);
|
* 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)
|
if (!entry)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -409,6 +515,64 @@ ipv4_nat_outbound_icmp(FAR struct net_driver_s *dev,
|
|||||||
&icmp->id, entry->external_port);
|
&icmp->id, entry->external_port);
|
||||||
ipv4_nat_ip_adjust(ipv4, NULL, dev->d_ipaddr, manip_type);
|
ipv4_nat_ip_adjust(ipv4, NULL, dev->d_ipaddr, manip_type);
|
||||||
return entry;
|
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;
|
return NULL;
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
|
|||||||
* protocol - The L4 protocol of the packet.
|
* protocol - The L4 protocol of the packet.
|
||||||
* local_ip - The local ip of the packet.
|
* local_ip - The local ip of the packet.
|
||||||
* local_port - The local port of the packet.
|
* local_port - The local port of the packet.
|
||||||
|
* try_create - Try create the entry if no entry found.
|
||||||
*
|
*
|
||||||
* Returned Value:
|
* Returned Value:
|
||||||
* Pointer to entry on success; null on failure
|
* 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 *
|
FAR struct ipv4_nat_entry *
|
||||||
ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
|
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 *p;
|
||||||
FAR sq_entry_t *tmp;
|
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. */
|
/* Failed to find the entry, create one. */
|
||||||
|
|
||||||
ninfo("INFO: Failed to find IPv4 outbound NAT entry for "
|
ninfo("INFO: Failed to find IPv4 outbound NAT entry for "
|
||||||
|
|||||||
+3
-1
@@ -208,6 +208,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
|
|||||||
* protocol - The L4 protocol of the packet.
|
* protocol - The L4 protocol of the packet.
|
||||||
* local_ip - The local ip of the packet.
|
* local_ip - The local ip of the packet.
|
||||||
* local_port - The local port of the packet.
|
* local_port - The local port of the packet.
|
||||||
|
* try_create - Try create the entry if no entry found.
|
||||||
*
|
*
|
||||||
* Returned Value:
|
* Returned Value:
|
||||||
* Pointer to entry on success; null on failure
|
* 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 *
|
FAR struct ipv4_nat_entry *
|
||||||
ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
|
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 /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */
|
||||||
#endif /* __NET_NAT_NAT_H */
|
#endif /* __NET_NAT_NAT_H */
|
||||||
|
|||||||
Reference in New Issue
Block a user