From fb1f3552349bfa53d2977409f204d385ed0aa217 Mon Sep 17 00:00:00 2001 From: "Deomid \"rojer\" Ryabkov" Date: Thu, 24 Feb 2022 23:28:50 +0000 Subject: [PATCH] napt: Fixes and improvements 1. Fix enable/disable to properly allocate and deallocate tables. Current algorithm is just broken. 2. Introduce eviction policy when table gets full: oldest connection is evicted, instead of new ones getting silently dropped. this results in much better behavior with small tables than before. When TCP connection is dropped, RSTs are sent both ways to inform parties instead of dropping silently. thiw requires additional 8 bytes per entry but is, again, a big improvement for clients in terms of usability. 3. FIxed handling of timestamp wraparound (every ~50 days of uptime). 3. Added ip_portmap_get() to retrieve current port mapping settings. 4. Added ip_napt_get_stats() for some insight into the state of NAT. --- src/core/ipv4/dhcp.c | 3 +- src/core/ipv4/ip4_napt.c | 490 ++++++++++++++++++++++---------- src/include/lwip/ip4_napt.h | 24 +- src/include/lwip/lwip_napt.h | 41 ++- test/unit/core/test_ip4_route.c | 3 +- 5 files changed, 390 insertions(+), 171 deletions(-) diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c index 16d806fab..1bb9c3da1 100644 --- a/src/core/ipv4/dhcp.c +++ b/src/core/ipv4/dhcp.c @@ -364,6 +364,7 @@ dhcp_check(struct netif *netif) static void dhcp_handle_offer(struct netif *netif, struct dhcp_msg *msg_in) { + u8_t n; struct dhcp *dhcp = netif_dhcp_data(netif); LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_handle_offer(netif=%p) %c%c%"U16_F"\n", @@ -371,7 +372,7 @@ dhcp_handle_offer(struct netif *netif, struct dhcp_msg *msg_in) /* Vendor Specific Information */ #if ESP_DHCP && !ESP_DHCP_DISABLE_VENDOR_CLASS_IDENTIFIER - for (u8_t n = 0; (n < DHCP_OPTION_VSI_MAX) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_VSI + n); n++) { + for (n = 0; (n < DHCP_OPTION_VSI_MAX) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_VSI + n); n++) { dhcp_option_vsi[n] = lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_VSI + n)); } #endif /* ESP_DHCP && !ESP_DHCP_DISABLE_VENDOR_CLASS_IDENTIFIER */ diff --git a/src/core/ipv4/ip4_napt.c b/src/core/ipv4/ip4_napt.c index 2d224fe58..a364f424d 100644 --- a/src/core/ipv4/ip4_napt.c +++ b/src/core/ipv4/ip4_napt.c @@ -39,9 +39,11 @@ * */ -#if ESP_LWIP -#if LWIP_IPV4 -#if IP_NAPT +#if ESP_LWIP && LWIP_IPV4 && IP_NAPT + +#include +#include +#include #include "lwip/sys.h" #include "lwip/ip.h" @@ -53,30 +55,37 @@ #include "lwip/priv/tcp_priv.h" #include "lwip/lwip_napt.h" #include "lwip/ip4_napt.h" -#include "string.h" -#include "assert.h" +#include "lwip/timeouts.h" #define NO_IDX ((u16_t)-1) #define NT(x) ((x) == NO_IDX ? NULL : &ip_napt_table[x]) -struct napt_table { +#pragma GCC diagnostic push +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wc90-c99-compat" /* To allow u8_t bit fields */ +#endif +struct ip_napt_entry { u32_t last; - u32_t src; - u32_t dest; - u16_t sport; - u16_t dport; - u16_t mport; + u32_t src; /* net */ + u32_t dest; /* net */ + u16_t sport; /* net */ + u16_t dport; /* net */ + u16_t mport; /* net */ u8_t proto; - unsigned int fin1 : 1; - unsigned int fin2 : 1; - unsigned int finack1 : 1; - unsigned int finack2 : 1; - unsigned int synack : 1; - unsigned int rst : 1; + u8_t fin1 : 1; + u8_t fin2 : 1; + u8_t finack1 : 1; + u8_t finack2 : 1; + u8_t synack : 1; + u8_t rst : 1; + u8_t _unused : 2; + u32_t src_seqno; /* host */ + u32_t dest_seqno; /* host */ u16_t next, prev; }; +#pragma GCC diagnostic pop -struct portmap_table { +struct ip_portmap_entry { u32_t maddr; u32_t daddr; u16_t mport; @@ -86,47 +95,67 @@ struct portmap_table { }; static u16_t napt_list = NO_IDX, napt_list_last = NO_IDX, napt_free = 0; +static u8_t ip_portmap_max = 0; -static struct napt_table *ip_napt_table = NULL; -static struct portmap_table *ip_portmap_table = NULL; +static struct ip_napt_entry *ip_napt_table = NULL; +static struct ip_portmap_entry *ip_portmap_table = NULL; -static int nr_active_napt_tcp = 0, nr_active_napt_udp = 0, nr_active_napt_icmp = 0; -static uint16_t ip_napt_max = 0; -static uint8_t ip_portmap_max = 0; +static struct ip_napt_stats napt_stats; + +static void ip_napt_gc(uint32_t now, bool force); +static void ip_napt_tmr(void *arg); + +#define WRAPPED_AROUND(a, b) ((((a) ^ (b)) & (1UL << 31)) != 0) #if NAPT_DEBUG -/* Print NAPT table using LWIP_DEBUGF -*/ +/* Print NAPT table using LWIP_DEBUGF */ +#define DPRINTF(m) LWIP_DEBUGF(NAPT_DEBUG, m) +/* #define DPRINTF(m) printf m */ static void napt_debug_print(void) { - int i, next; - LWIP_DEBUGF(NAPT_DEBUG, ("NAPT table:\n")); - LWIP_DEBUGF(NAPT_DEBUG, (" src dest sport dport mport \n")); - LWIP_DEBUGF(NAPT_DEBUG, ("+-----------------------+-----------------------+-------+-------+-------+\n")); - + int i, next, p; + u32_t now = sys_now(); + u32_t nr_total = napt_stats.nr_active_tcp + napt_stats.nr_active_udp + napt_stats.nr_active_icmp; + DPRINTF(("NAPT table (%"U16_F"+%"U16_F"+%"U16_F"=%"U32_F" / %"U16_F"):\n", + napt_stats.nr_active_tcp, napt_stats.nr_active_udp, napt_stats.nr_active_icmp, nr_total, napt_stats.max_entries)); + if (nr_total == 0) return; + + DPRINTF(("+-----------------------+-----------------------+-------+---------+----------+\n")); + DPRINTF(("| src | dest | mport | flags | age |\n")); + DPRINTF(("+-----------------------+-----------------------+-------+---------+----------+\n")); for (i = napt_list; i != NO_IDX; i = next) { - struct napt_table *t = &ip_napt_table[i]; + struct ip_napt_entry *t = &ip_napt_table[i]; next = t->next; - LWIP_DEBUGF(NAPT_DEBUG, ("| %3"U16_F" | %3"U16_F" | %3"U16_F" | %3"U16_F" |", - ((const u8_t*) (&t->src))[0], - ((const u8_t*) (&t->src))[1], - ((const u8_t*) (&t->src))[2], - ((const u8_t*) (&t->src))[3])); - - LWIP_DEBUGF(NAPT_DEBUG, (" %3"U16_F" | %3"U16_F" | %3"U16_F" | %3"U16_F" |", - ((const u8_t*) (&t->dest))[0], - ((const u8_t*) (&t->dest))[1], - ((const u8_t*) (&t->dest))[2], - ((const u8_t*) (&t->dest))[3])); - - LWIP_DEBUGF(NAPT_DEBUG, (" %5"U16_F" | %5"U16_F" | %5"U16_F" |\n", - lwip_htons(t->sport), - lwip_htons(t->dport), - lwip_htons(t->mport))); + DPRINTF(("| %3"U16_F".%3"U16_F".%3"U16_F".%3"U16_F":%5"U16_F" ", + ((const u8_t*) (&t->src))[0], + ((const u8_t*) (&t->src))[1], + ((const u8_t*) (&t->src))[2], + ((const u8_t*) (&t->src))[3], + lwip_ntohs(t->sport))); + + DPRINTF(("| %3"U16_F".%3"U16_F".%3"U16_F".%3"U16_F":%5"U16_F" ", + ((const u8_t*) (&t->dest))[0], + ((const u8_t*) (&t->dest))[1], + ((const u8_t*) (&t->dest))[2], + ((const u8_t*) (&t->dest))[3], + lwip_ntohs(t->dport))); + + p = t->proto; + DPRINTF(("| %5"U16_F" | %c%c%c%c%c%c%c | %8"U32_F" |\n", + lwip_ntohs(t->mport), + (p == IP_PROTO_TCP ? 'T' : (p == IP_PROTO_UDP ? 'U' : (p == IP_PROTO_ICMP ? 'I' : '?'))), + (t->fin1 ? 'f' : '.'), + (t->fin2 ? 'F' : '.'), + (t->finack1 ? 'a' : '.'), + (t->finack2 ? 'A' : '.'), + (t->synack ? 'S' : '.'), + (t->rst ? 'R' : '.'), + now - t->last)); } + DPRINTF(("+-----------------------+-----------------------+-------+---------+----------+\n")); } #endif /* NAPT_DEBUG */ @@ -137,10 +166,14 @@ napt_debug_print(void) static void ip_napt_deinit(void) { + napt_list = NO_IDX; + napt_stats.max_entries = 0; + ip_portmap_max = 0; mem_free(ip_napt_table); + ip_napt_table = NULL; mem_free(ip_portmap_table); ip_portmap_table = NULL; - ip_napt_table = NULL; + sys_untimeout(ip_napt_tmr, NULL); } /** @@ -155,16 +188,18 @@ ip_napt_init(uint16_t max_nat, uint8_t max_portmap) u16_t i; if (ip_portmap_table == NULL && ip_napt_table == NULL) { - ip_napt_max = max_nat; - ip_portmap_max = max_portmap; - - ip_napt_table = (struct napt_table*)mem_calloc(ip_napt_max, sizeof(struct napt_table[1])); - ip_portmap_table = (struct portmap_table*)mem_calloc(ip_portmap_max, sizeof(struct portmap_table[1])); + ip_napt_table = (struct ip_napt_entry *) mem_calloc(max_nat, sizeof(*ip_napt_table)); + ip_portmap_table = (struct ip_portmap_entry *) mem_calloc(max_portmap, sizeof(*ip_portmap_table)); assert(ip_portmap_table != NULL && ip_napt_table != NULL); - for (i = 0; i < ip_napt_max - 1; i++) + for (i = 0; i < max_nat - 1; i++) ip_napt_table[i].next = i + 1; ip_napt_table[i].next = NO_IDX; + + napt_stats.max_entries = max_nat; + ip_portmap_max = max_portmap; + + sys_timeout(NAPT_TMR_INTERVAL, ip_napt_tmr, NULL); } } @@ -174,17 +209,16 @@ ip_napt_enable(u32_t addr, int enable) struct netif *netif; int napt_in_any_netif = 0; for (netif = netif_list; netif; netif = netif->next) { - if (netif->napt) + if (netif_is_up(netif) && !ip_addr_isany(&netif->ip_addr) && (ip_2_ip4(&netif->ip_addr)->addr) == addr) { + netif->napt = enable; + } + if (netif->napt) { napt_in_any_netif = 1; - if (netif_is_up(netif) && !ip_addr_isany(&netif->ip_addr) && (ip_2_ip4(&netif->ip_addr)->addr) == addr && enable) { - netif->napt = 1; - ip_napt_init(IP_NAPT_MAX, IP_PORTMAP_MAX); - break; } } - if (!enable && !napt_in_any_netif) { - for (netif = netif_list; netif; netif = netif->next) - netif->napt = 0; + if (napt_in_any_netif) { + ip_napt_init(IP_NAPT_MAX, IP_PORTMAP_MAX); + } else { ip_napt_deinit(); } } @@ -234,9 +268,40 @@ checksumadjust(u8_t *chksum, u8_t *optr, int olen, u8_t *nptr, int nlen) chksum[0]=x/256; chksum[1]=x & 0xFFU; } + +static void +ip_napt_send_rst(u32_t src_be, u16_t sport_be, u32_t dst_be, u16_t dport_be, u32_t seqno_le, u32_t ackno_le) +{ + struct pbuf *p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM); + struct tcp_hdr *tcphdr; + struct netif *netif; + ip_addr_t src, dst; + if (p == NULL) return; + tcphdr = (struct tcp_hdr *)p->payload; + tcphdr->src = sport_be; + tcphdr->dest = dport_be; + tcphdr->seqno = lwip_htonl(seqno_le); + tcphdr->ackno = lwip_htonl(ackno_le); + TCPH_HDRLEN_FLAGS_SET(tcphdr, 5, (TCP_RST | TCP_ACK)); + tcphdr->wnd = lwip_htons(512); + tcphdr->urgp = 0; + tcphdr->chksum = 0; + ip_addr_set_ip4_u32_val(src, src_be); + ip_addr_set_ip4_u32_val(dst, dst_be); + tcphdr->chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len, &src, &dst); + netif = ip4_route(ip_2_ip4(&dst)); + if (netif != NULL) { + err_t res = ip4_output_if(p, ip_2_ip4(&src), ip_2_ip4(&dst), ICMP_TTL, 0, IP_PROTO_TCP, netif); + LWIP_DEBUGF(NAPT_DEBUG, ("SEND RST to %#x:%u from %#x:%u seq %u ack %u res %d\n", + lwip_ntohl(src_be), lwip_ntohs(sport_be), lwip_ntohl(dst_be), lwip_ntohs(dport_be), + seqno_le, ackno_le, res)); + } + pbuf_free(p); +} + /* t must be indexed by napt_free */ static void -ip_napt_insert(struct napt_table *t) +ip_napt_insert(struct ip_napt_entry *t) { u16_t ti = t - ip_napt_table; assert(ti == napt_free); @@ -251,21 +316,22 @@ ip_napt_insert(struct napt_table *t) #if LWIP_TCP if (t->proto == IP_PROTO_TCP) - nr_active_napt_tcp++; + napt_stats.nr_active_tcp++; #endif #if LWIP_UDP if (t->proto == IP_PROTO_UDP) - nr_active_napt_udp++; + napt_stats.nr_active_udp++; #endif #if LWIP_ICMP if (t->proto == IP_PROTO_ICMP) - nr_active_napt_icmp++; + napt_stats.nr_active_icmp++; #endif - LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_insert(): TCP=%d, UDP=%d, ICMP=%d\n", nr_active_napt_tcp, nr_active_napt_udp, nr_active_napt_icmp)); + LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_insert(): TCP=%d, UDP=%d, ICMP=%d\n", + napt_stats.nr_active_tcp, napt_stats.nr_active_udp, napt_stats.nr_active_icmp)); } static void -ip_napt_free(struct napt_table *t) +ip_napt_free(struct ip_napt_entry *t) { u16_t ti = t - ip_napt_table; if (ti == napt_list) @@ -282,20 +348,26 @@ ip_napt_free(struct napt_table *t) #if LWIP_TCP if (t->proto == IP_PROTO_TCP) - nr_active_napt_tcp--; + napt_stats.nr_active_tcp--; #endif #if LWIP_UDP if (t->proto == IP_PROTO_UDP) - nr_active_napt_udp--; + napt_stats.nr_active_udp--; #endif #if LWIP_ICMP if (t->proto == IP_PROTO_ICMP) - nr_active_napt_icmp--; + napt_stats.nr_active_icmp--; #endif LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_free\n")); #if NAPT_DEBUG napt_debug_print(); #endif + /* Send RST to both sides to let them know connection is being evicted */ + if (t->proto == IP_PROTO_TCP && t->synack && !(t->fin1 || t->fin2 || t-> rst)) { + /* Send RST both ways. */ + ip_napt_send_rst(t->dest, t->dport, t->src, t->sport, t->dest_seqno, t->src_seqno); + ip_napt_send_rst(t->src, t->sport, t->dest, t->dport, t->src_seqno, t->dest_seqno); + } } #if LWIP_TCP @@ -304,7 +376,7 @@ ip_napt_find_port(u8_t proto, u16_t port) { int i, next; for (i = napt_list; i != NO_IDX; i = next) { - struct napt_table *t = &ip_napt_table[i]; + struct ip_napt_entry *t = &ip_napt_table[i]; next = t->next; if (t->proto == proto && t->mport == port) return 1; @@ -312,7 +384,7 @@ ip_napt_find_port(u8_t proto, u16_t port) return 0; } -static struct portmap_table * +static struct ip_portmap_entry * ip_portmap_find(u8_t proto, u16_t mport); static u8_t @@ -366,20 +438,20 @@ ip_napt_new_port(u8_t proto, u16_t port) } } -static struct napt_table* +static struct ip_napt_entry* ip_napt_find(u8_t proto, u32_t addr, u16_t port, u16_t mport, u8_t dest) { u16_t i, next; u32_t now; - struct napt_table *t; + struct ip_napt_entry *t; LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_find\n")); LWIP_DEBUGF(NAPT_DEBUG, ("looking up in table %s: %"U16_F".%"U16_F".%"U16_F".%"U16_F", port: %u, mport: %u\n", - (dest ? "dest" : "src"), - ((const u8_t*) (&addr))[0], ((const u8_t*) (&addr))[1], - ((const u8_t*) (&addr))[2], ((const u8_t*) (&addr))[3], - PP_HTONS(port), - PP_HTONS(mport))); + (dest ? "dest" : "src"), + ((const u8_t*) (&addr))[0], ((const u8_t*) (&addr))[1], + ((const u8_t*) (&addr))[2], ((const u8_t*) (&addr))[3], + PP_HTONS(port), + PP_HTONS(mport))); #if NAPT_DEBUG napt_debug_print(); #endif @@ -388,33 +460,12 @@ ip_napt_find(u8_t proto, u32_t addr, u16_t port, u16_t mport, u8_t dest) for (i = napt_list; i != NO_IDX; i = next) { t = NT(i); next = t->next; -#if LWIP_TCP - if (t->proto == IP_PROTO_TCP && - ((((t->finack1 && t->finack2) || !t->synack) && - now - t->last > IP_NAPT_TIMEOUT_MS_TCP_DISCON) || - now - t->last > IP_NAPT_TIMEOUT_MS_TCP)) { - ip_napt_free(t); - continue; - } -#endif -#if LWIP_UDP - if (t->proto == IP_PROTO_UDP && now - t->last > IP_NAPT_TIMEOUT_MS_UDP) { - ip_napt_free(t); - continue; - } -#endif -#if LWIP_ICMP - if (t->proto == IP_PROTO_ICMP && now - t->last > IP_NAPT_TIMEOUT_MS_ICMP) { - ip_napt_free(t); - continue; - } -#endif - if (dest == 0 && t->proto == proto && t->src == addr && t->sport == port) { + if (!dest && t->proto == proto && t->src == addr && t->sport == port) { t->last = now; LWIP_DEBUGF(NAPT_DEBUG, ("found\n")); return t; } - if (dest == 1 && t->proto == proto && t->dest == addr && t->dport == port + if (dest && t->proto == proto && t->dest == addr && t->dport == port && t->mport == mport) { t->last = now; LWIP_DEBUGF(NAPT_DEBUG, ("found\n")); @@ -427,9 +478,9 @@ ip_napt_find(u8_t proto, u32_t addr, u16_t port, u16_t mport, u8_t dest) } static u16_t -ip_napt_add(u8_t proto, u32_t src, u16_t sport, u32_t dest, u16_t dport) +ip_napt_add(u8_t proto, u32_t src, u16_t sport, u32_t dest, u16_t dport, u32_t seqno) { - struct napt_table *t = ip_napt_find(proto, src, sport, 0, 0); + struct ip_napt_entry *t = ip_napt_find(proto, src, sport, 0, 0); if (t) { t->last = sys_now(); t->dest = dest; @@ -446,6 +497,10 @@ ip_napt_add(u8_t proto, u32_t src, u16_t sport, u32_t dest, u16_t dport) return t->mport; } t = NT(napt_free); + if (!t) { + ip_napt_gc(sys_now(), true /* make_room */); + t = NT(napt_free); + } if (t) { u16_t mport = sport; #if LWIP_TCP @@ -464,6 +519,8 @@ ip_napt_add(u8_t proto, u32_t src, u16_t sport, u32_t dest, u16_t dport) t->mport = mport; t->proto = proto; t->fin1 = t->fin2 = t->finack1 = t->finack2 = t->synack = t->rst = 0; + t->src_seqno = ntohl(seqno); + t->dest_seqno = 0; ip_napt_insert(t); LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_add\n")); @@ -480,11 +537,12 @@ ip_napt_add(u8_t proto, u32_t src, u16_t sport, u32_t dest, u16_t dport) u8_t ip_portmap_add(u8_t proto, u32_t maddr, u16_t mport, u32_t daddr, u16_t dport) { + int i; mport = PP_HTONS(mport); dport = PP_HTONS(dport); - for (int i = 0; i < ip_portmap_max; i++) { - struct portmap_table *p = &ip_portmap_table[i]; + for (i = 0; i < ip_portmap_max; i++) { + struct ip_portmap_entry *p = &ip_portmap_table[i]; if (p->valid && p->proto == proto && p->mport == mport) { p->dport = dport; p->daddr = daddr; @@ -501,12 +559,12 @@ ip_portmap_add(u8_t proto, u32_t maddr, u16_t mport, u32_t daddr, u16_t dport) return 0; } -static struct portmap_table * +static struct ip_portmap_entry * ip_portmap_find(u8_t proto, u16_t mport) { int i; for (i = 0; i < ip_portmap_max; i++) { - struct portmap_table *p = &ip_portmap_table[i]; + struct ip_portmap_entry *p = &ip_portmap_table[i]; if (!p->valid) return 0; if (p->proto == proto && p->mport == mport) @@ -515,12 +573,12 @@ ip_portmap_find(u8_t proto, u16_t mport) return NULL; } -static struct portmap_table * +static struct ip_portmap_entry * ip_portmap_find_dest(u8_t proto, u16_t dport, u32_t daddr) { int i; for (i = 0; i < ip_portmap_max; i++) { - struct portmap_table *p = &ip_portmap_table[i]; + struct ip_portmap_entry *p = &ip_portmap_table[i]; if (!p->valid) return 0; if (p->proto == proto && p->dport == dport && p->daddr == daddr) @@ -529,11 +587,23 @@ ip_portmap_find_dest(u8_t proto, u16_t dport, u32_t daddr) return NULL; } +u8_t +ip_portmap_get(u8_t proto, u16_t mport, u32_t *maddr, u32_t *daddr, u16_t *dport) +{ + struct ip_portmap_entry *m = ip_portmap_find(proto, PP_HTONS(mport)); + if (!m) + return 0; + *maddr = m->maddr; + *daddr = m->daddr; + *dport = PP_NTOHS(m->dport); + return 1; +} + u8_t ip_portmap_remove(u8_t proto, u16_t mport) { - struct portmap_table *last = &ip_portmap_table[ip_portmap_max - 1]; - struct portmap_table *m = ip_portmap_find(proto, PP_HTONS(mport)); + struct ip_portmap_entry *last = &ip_portmap_table[ip_portmap_max - 1]; + struct ip_portmap_entry *m = ip_portmap_find(proto, PP_HTONS(mport)); if (!m) return 0; for (; m != last; m++) @@ -595,8 +665,10 @@ ip_napt_modify_addr(struct ip_hdr *iphdr, ip4_addr_p_t *field, u32_t newval) void ip_napt_recv(struct pbuf *p, struct ip_hdr *iphdr) { - struct portmap_table *m; - struct napt_table *t; + struct ip_portmap_entry *m; + struct ip_napt_entry *t; + if (napt_stats.max_entries == 0) return; + #if LWIP_ICMP /* NAPT for ICMP Echo Request using identifier */ if (IPH_PROTO(iphdr) == IP_PROTO_ICMP) { @@ -615,19 +687,20 @@ ip_napt_recv(struct pbuf *p, struct ip_hdr *iphdr) #if LWIP_TCP if (IPH_PROTO(iphdr) == IP_PROTO_TCP) { + uint32_t seqno, dest_seqno; struct tcp_hdr *tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_recv\n")); LWIP_DEBUGF(NAPT_DEBUG, ("src: %"U16_F".%"U16_F".%"U16_F".%"U16_F", dest: %"U16_F".%"U16_F".%"U16_F".%"U16_F", \n", - ip4_addr1_16(&iphdr->src), ip4_addr2_16(&iphdr->src), - ip4_addr3_16(&iphdr->src), ip4_addr4_16(&iphdr->src), - ip4_addr1_16(&iphdr->dest), ip4_addr2_16(&iphdr->dest), - ip4_addr3_16(&iphdr->dest), ip4_addr4_16(&iphdr->dest))); + ip4_addr1_16(&iphdr->src), ip4_addr2_16(&iphdr->src), + ip4_addr3_16(&iphdr->src), ip4_addr4_16(&iphdr->src), + ip4_addr1_16(&iphdr->dest), ip4_addr2_16(&iphdr->dest), + ip4_addr3_16(&iphdr->dest), ip4_addr4_16(&iphdr->dest))); LWIP_DEBUGF(NAPT_DEBUG, ("sport %u, dport: %u\n", - lwip_htons(tcphdr->src), - lwip_htons(tcphdr->dest))); + lwip_htons(tcphdr->src), + lwip_htons(tcphdr->dest))); m = ip_portmap_find(IP_PROTO_TCP, tcphdr->dest); if (m) { @@ -639,23 +712,28 @@ ip_napt_recv(struct pbuf *p, struct ip_hdr *iphdr) return; } t = ip_napt_find(IP_PROTO_TCP, iphdr->src.addr, tcphdr->src, tcphdr->dest, 1); - if (!t) - return; /* Unknown TCP session; do nothing */ + if (!t) + return; /* Unknown TCP session; do nothing */ - if (t->sport != tcphdr->dest) - ip_napt_modify_port_tcp(tcphdr, 1, t->sport); - ip_napt_modify_addr_tcp(tcphdr, &iphdr->dest, t->src); - ip_napt_modify_addr(iphdr, &iphdr->dest, t->src); + if (t->sport != tcphdr->dest) + ip_napt_modify_port_tcp(tcphdr, 1, t->sport); + ip_napt_modify_addr_tcp(tcphdr, &iphdr->dest, t->src); + ip_napt_modify_addr(iphdr, &iphdr->dest, t->src); - if ((TCPH_FLAGS(tcphdr) & (TCP_SYN|TCP_ACK)) == (TCP_SYN|TCP_ACK)) - t->synack = 1; - if ((TCPH_FLAGS(tcphdr) & TCP_FIN)) - t->fin1 = 1; - if (t->fin2 && (TCPH_FLAGS(tcphdr) & TCP_ACK)) - t->finack2 = 1; /* FIXME: Currently ignoring ACK seq... */ - if (TCPH_FLAGS(tcphdr) & TCP_RST) - t->rst = 1; - return; + if ((TCPH_FLAGS(tcphdr) & (TCP_SYN|TCP_ACK)) == (TCP_SYN|TCP_ACK)) + t->synack = 1; + if ((TCPH_FLAGS(tcphdr) & TCP_FIN)) + t->fin1 = 1; + if (t->fin2 && (TCPH_FLAGS(tcphdr) & TCP_ACK)) + t->finack2 = 1; /* FIXME: Currently ignoring ACK seq... */ + if (TCPH_FLAGS(tcphdr) & TCP_RST) + t->rst = 1; + seqno = ntohl(tcphdr->seqno); + dest_seqno = t->dest_seqno; + if (seqno >= dest_seqno || WRAPPED_AROUND(seqno, dest_seqno)) { + t->dest_seqno = seqno + (p->tot_len - IPH_HL(iphdr) * 4 - TCPH_HDRLEN_BYTES(tcphdr)); + } + return; } #endif /* LWIP_TCP */ @@ -696,7 +774,7 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); if (iecho->type == ICMP_ECHO) { /* register src addr and iecho->id and dest info */ - ip_napt_add(IP_PROTO_ICMP, iphdr->src.addr, iecho->id, iphdr->dest.addr, iecho->id); + ip_napt_add(IP_PROTO_ICMP, iphdr->src.addr, iecho->id, iphdr->dest.addr, iecho->id, 0); ip_napt_modify_addr(iphdr, &iphdr->src, ip_2_ip4(&outp->ip_addr)->addr); } @@ -710,7 +788,7 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct struct tcp_hdr *tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); u16_t mport; - struct portmap_table *m = ip_portmap_find_dest(IP_PROTO_TCP, tcphdr->src, iphdr->src.addr); + struct ip_portmap_entry *m = ip_portmap_find_dest(IP_PROTO_TCP, tcphdr->src, iphdr->src.addr); if (m) { /* packet from port-mapped dest addr/port: rewrite source to this node */ if (m->mport != tcphdr->src) @@ -723,24 +801,34 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct PP_NTOHS(tcphdr->src) >= 1024) { /* Register new TCP session to NAPT */ mport = ip_napt_add(IP_PROTO_TCP, iphdr->src.addr, tcphdr->src, - iphdr->dest.addr, tcphdr->dest); - if (mport == 0) + iphdr->dest.addr, tcphdr->dest, tcphdr->seqno); + if (mport == 0) { +#if LWIP_ICMP + icmp_dest_unreach(p, ICMP_DUR_PORT); +#endif return ERR_RTE; /* routing err if add entry failed */ + } } else { - struct napt_table *t = ip_napt_find(IP_PROTO_TCP, iphdr->src.addr, tcphdr->src, 0, 0); + uint32_t seqno, src_seqno; + struct ip_napt_entry *t = ip_napt_find(IP_PROTO_TCP, iphdr->src.addr, tcphdr->src, 0, 0); if (!t || t->dest != iphdr->dest.addr || t->dport != tcphdr->dest) { #if LWIP_ICMP - icmp_dest_unreach(p, ICMP_DUR_PORT); + icmp_dest_unreach(p, ICMP_DUR_PORT); #endif - return ERR_RTE; /* Drop unknown TCP session */ - } - mport = t->mport; - if ((TCPH_FLAGS(tcphdr) & TCP_FIN)) + return ERR_RTE; /* Drop unknown TCP session */ + } + mport = t->mport; + if ((TCPH_FLAGS(tcphdr) & TCP_FIN)) t->fin2 = 1; - if (t->fin1 && (TCPH_FLAGS(tcphdr) & TCP_ACK)) + if (t->fin1 && (TCPH_FLAGS(tcphdr) & TCP_ACK)) t->finack1 = 1; /* FIXME: Currently ignoring ACK seq... */ - if (TCPH_FLAGS(tcphdr) & TCP_RST) + if (TCPH_FLAGS(tcphdr) & TCP_RST) t->rst = 1; + seqno = ntohl(tcphdr->seqno); + src_seqno = t->src_seqno; + if (seqno >= t->src_seqno || WRAPPED_AROUND(seqno, src_seqno)) { + t->src_seqno = seqno + (p->tot_len - IPH_HL(iphdr) * 4 - TCPH_HDRLEN_BYTES(tcphdr)); + } } if (mport != tcphdr->src) @@ -757,7 +845,7 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct struct udp_hdr *udphdr = (struct udp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); u16_t mport; - struct portmap_table *m = ip_portmap_find_dest(IP_PROTO_UDP, udphdr->src, iphdr->src.addr); + struct ip_portmap_entry *m = ip_portmap_find_dest(IP_PROTO_UDP, udphdr->src, iphdr->src.addr); if (m) { /* packet from port-mapped dest addr/port: rewrite source to this node */ if (m->mport != udphdr->src) @@ -770,11 +858,11 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct if (PP_NTOHS(udphdr->src) >= 1024) { /* Register new UDP session */ mport = ip_napt_add(IP_PROTO_UDP, iphdr->src.addr, udphdr->src, - iphdr->dest.addr, udphdr->dest); + iphdr->dest.addr, udphdr->dest, 0); if (mport == 0) return ERR_RTE; /* routing err if add entry failed */ } else { - struct napt_table *t = ip_napt_find(IP_PROTO_UDP, iphdr->src.addr, udphdr->src, 0, 0); + struct ip_napt_entry *t = ip_napt_find(IP_PROTO_UDP, iphdr->src.addr, udphdr->src, 0, 0); if (!t || t->dest != iphdr->dest.addr || t->dport != udphdr->dest) { #if LWIP_ICMP icmp_dest_unreach(p, ICMP_DUR_PORT); @@ -794,6 +882,100 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct return ERR_OK; } -#endif /* IP_NAPT */ -#endif /* LWIP_IPV4 */ -#endif /* ESP_LWIP */ \ No newline at end of file + +static void +ip_napt_gc(uint32_t now, bool force) +{ + u16_t i, next, oldest = NO_IDX; + u32_t age = 0, oldest_age = 0; + int checked = 0, evicted = 0, forced = 0; + for (i = napt_list; i != NO_IDX; i = next) { + struct ip_napt_entry *t = &ip_napt_table[i]; + checked++; + next = t->next; + age = now - t->last; + if (age > oldest_age) { + oldest = i; + oldest_age = age; + } +#if LWIP_TCP + if (t->proto == IP_PROTO_TCP) { + if (age > IP_NAPT_TIMEOUT_MS_TCP_DISCON) { + if ((t->finack1 || t->finack2 || !t->synack || t->rst) || + age > IP_NAPT_TIMEOUT_MS_TCP) { + ip_napt_free(t); + evicted++; + if (force) break; + } + } + continue; + } +#endif +#if LWIP_UDP + if (t->proto == IP_PROTO_UDP) { + if (age > IP_NAPT_TIMEOUT_MS_UDP) { + ip_napt_free(t); + evicted++; + if (force) break; + } + continue; + } +#endif +#if LWIP_ICMP + if (t->proto == IP_PROTO_ICMP) { + if (age > IP_NAPT_TIMEOUT_MS_ICMP) { + ip_napt_free(t); + evicted++; + if (force) break; + } + continue; + } +#endif + } + if (napt_free == NO_IDX && force && oldest != NO_IDX) { + ip_napt_free(&ip_napt_table[oldest]); + evicted++; + forced++; + napt_stats.nr_forced_evictions++; + } + LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_gc(%d): chk %d evict %d (forced %d), oldest %u\n", + force, checked, evicted, forced, oldest_age)); +} + +static void +ip_napt_maint(void) +{ + static uint32_t s_last_now = 0; + uint32_t now; + if (napt_list == NO_IDX) return; + now = sys_now(); + /* Check for timestamp wraparound (happens every ~49.7 days). */ + if (WRAPPED_AROUND(s_last_now, now)) { + u16_t i; + struct ip_napt_entry *t; + for (i = napt_list; i != NO_IDX; i = t->next) { + t = &ip_napt_table[i]; + /* It's a very simplistic way of dealing with it + * but it's fine for our purposes. */ + t->last = now; + } + /* Skip until next tick, nothing to be done here anyway. */ + return; + } + ip_napt_gc(now, false /* make_room */); + s_last_now = now; +} + +static void +ip_napt_tmr(void *arg) { + ip_napt_maint(); + sys_timeout(NAPT_TMR_INTERVAL, ip_napt_tmr, arg); +} + +void +ip_napt_get_stats(struct ip_napt_stats *stats) +{ + *stats = napt_stats; +} + +#endif /* ESP_LWIP && LWIP_IPV4 && IP_NAPT */ diff --git a/src/include/lwip/ip4_napt.h b/src/include/lwip/ip4_napt.h index 8d98e120d..8246d6fd3 100644 --- a/src/include/lwip/ip4_napt.h +++ b/src/include/lwip/ip4_napt.h @@ -60,16 +60,21 @@ extern "C" { #include "lwip/err.h" #include "lwip/ip4.h" + +#ifndef NAPT_TMR_INTERVAL +#define NAPT_TMR_INTERVAL 2000 +#endif + /** -* NAPT for a forwarded packet. It checks weather we need NAPT and modify -* the packet source address and port if needed. -* -* @param p the packet to forward (p->payload points to IP header) -* @param iphdr the IP header of the input packet -* @param inp the netif on which this packet was received -* @param outp the netif on which this packet will be sent -* @return ERR_OK if packet should be sent, or ERR_RTE if it should be dropped -*/ + * NAPT for a forwarded packet. It checks weather we need NAPT and modify + * the packet source address and port if needed. + * + * @param p the packet to forward (p->payload points to IP header) + * @param iphdr the IP header of the input packet + * @param inp the netif on which this packet was received + * @param outp the netif on which this packet will be sent + * @return ERR_OK if packet should be sent, or ERR_RTE if it should be dropped + */ err_t ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct netif *outp); @@ -79,7 +84,6 @@ ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct * * @param p the packet to forward (p->payload points to IP header) * @param iphdr the IP header of the input packet - * @param inp the netif on which this packet was received */ void ip_napt_recv(struct pbuf *p, struct ip_hdr *iphdr); diff --git a/src/include/lwip/lwip_napt.h b/src/include/lwip/lwip_napt.h index a1816d420..2c233fe3e 100644 --- a/src/include/lwip/lwip_napt.h +++ b/src/include/lwip/lwip_napt.h @@ -59,13 +59,24 @@ extern "C" { #endif /* Timeouts in sec for the various protocol types */ +#ifndef IP_NAPT_TIMEOUT_MS_TCP #define IP_NAPT_TIMEOUT_MS_TCP (30*60*1000) -#define IP_NAPT_TIMEOUT_MS_TCP_DISCON (20*1000) +#endif +#ifndef IP_NAPT_TIMEOUT_MS_TCP_DISCON +#define IP_NAPT_TIMEOUT_MS_TCP_DISCON (TCP_MSL) +#endif +#ifndef IP_NAPT_TIMEOUT_MS_UDP #define IP_NAPT_TIMEOUT_MS_UDP (2*1000) +#endif +#ifndef IP_NAPT_TIMEOUT_MS_ICMP #define IP_NAPT_TIMEOUT_MS_ICMP (2*1000) - +#endif +#ifndef IP_NAPT_PORT_RANGE_START #define IP_NAPT_PORT_RANGE_START 49152 +#endif +#ifndef IP_NAPT_PORT_RANGE_END #define IP_NAPT_PORT_RANGE_END 61439 +#endif /** * Enable/Disable NAPT for a specified interface. @@ -80,13 +91,12 @@ ip_napt_enable(u32_t addr, int enable); /** * Enable/Disable NAPT for a specified interface. * - * @param netif number of the interface + * @param number number of the interface * @param enable non-zero to enable NAPT, or 0 to disable. */ void ip_napt_enable_no(u8_t number, int enable); - /** * Register port mapping on the external interface to internal interface. * When the same port mapping is registered again, the old mapping is overwritten. @@ -101,16 +111,37 @@ ip_napt_enable_no(u8_t number, int enable); u8_t ip_portmap_add(u8_t proto, u32_t maddr, u16_t mport, u32_t daddr, u16_t dport); +u8_t +ip_portmap_get(u8_t proto, u16_t mport, u32_t *maddr, u32_t *daddr, u16_t *dport); + /** * Unregister port mapping on the external interface to internal interface. * * @param proto target protocol - * @param maddr ip address of the external interface + * @param mport mapped port on the external interface, in host byte order. */ u8_t ip_portmap_remove(u8_t proto, u16_t mport); + +struct ip_napt_stats { + u16_t max_entries; + u16_t nr_active_tcp; + u16_t nr_active_udp; + u16_t nr_active_icmp; + u16_t nr_forced_evictions; +}; + +/** + * Get statistics. + * + * @param stats struct to receive current stats + */ +void +ip_napt_get_stats(struct ip_napt_stats *stats); + + #endif /* IP_NAPT */ #endif /* IP_FORWARD */ #endif /* ESP_LWIP */ diff --git a/test/unit/core/test_ip4_route.c b/test/unit/core/test_ip4_route.c index f366c7975..4076ca833 100644 --- a/test/unit/core/test_ip4_route.c +++ b/test/unit/core/test_ip4_route.c @@ -474,6 +474,7 @@ END_TEST START_TEST(test_ip4_route_netif_max_napt) { #define TCP_PORT 2222 + int i; packet_type_t packet_type = PACKET_PBUF_RAM; ip4_addr_t addr, src_addr, sta_addr; ip4_addr_t netmask; @@ -499,7 +500,7 @@ START_TEST(test_ip4_route_netif_max_napt) /* create packet and send it to the AP */ IP4_ADDR(&addr, 1, 2, 4, 100); IP4_ADDR(&src_addr, 10, 0, 0, 2); - for (int i=0; i