/********************************************************************* PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved. See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage. . Authors: Kristof Roelants, Brecht Van Cauwenberghe, Simon Maes, Philippe Mariman *********************************************************************/ #include "pico_stack.h" #include "pico_frame.h" #include "pico_tcp.h" #include "pico_udp.h" #include "pico_ipv4.h" #include "pico_addressing.h" #include "pico_nat.h" #ifdef PICO_SUPPORT_IPV4 #ifdef PICO_SUPPORT_NAT #ifdef DEBUG_NAT #define nat_dbg dbg #else #define nat_dbg(...) do {} while(0) #endif #define PICO_NAT_TIMEWAIT 240000 /* msec (4 mins) */ #define PICO_NAT_INBOUND 0 #define PICO_NAT_OUTBOUND 1 struct pico_nat_tuple { uint8_t proto; uint16_t conn_active : 11; uint16_t portforward : 1; uint16_t rst : 1; uint16_t syn : 1; uint16_t fin_in : 1; uint16_t fin_out : 1; uint16_t src_port; uint16_t dst_port; uint16_t nat_port; struct pico_ip4 src_addr; struct pico_ip4 dst_addr; struct pico_ip4 nat_addr; }; static struct pico_ipv4_link *nat_link = NULL; static int nat_cmp_natport(struct pico_nat_tuple *a, struct pico_nat_tuple *b) { if (a->nat_port < b->nat_port) return -1; if (a->nat_port > b->nat_port) return 1; return 0; } static int nat_cmp_srcport(struct pico_nat_tuple *a, struct pico_nat_tuple *b) { if (a->src_port < b->src_port) return -1; if (a->src_port > b->src_port) return 1; return 0; } static int nat_cmp_proto(struct pico_nat_tuple *a, struct pico_nat_tuple *b) { if (a->proto < b->proto) return -1; if (a->proto > b->proto) return 1; return 0; } static int nat_cmp_address(struct pico_nat_tuple *a, struct pico_nat_tuple *b) { return pico_ipv4_compare(&a->src_addr, &b->src_addr); } static int nat_cmp_inbound(void *ka, void *kb) { struct pico_nat_tuple *a = ka, *b = kb; int cport = nat_cmp_natport(a, b); if (cport) return cport; return nat_cmp_proto(a, b); } static int nat_cmp_outbound(void *ka, void *kb) { struct pico_nat_tuple *a = ka, *b = kb; int caddr, cport; caddr = nat_cmp_address(a, b); if (caddr) return caddr; cport = nat_cmp_srcport(a, b); if (cport) return cport; return nat_cmp_proto(a, b); } static PICO_TREE_DECLARE(NATOutbound, nat_cmp_outbound); static PICO_TREE_DECLARE(NATInbound, nat_cmp_inbound); void pico_ipv4_nat_print_table(void) { struct pico_nat_tuple *t = NULL; struct pico_tree_node *index = NULL; (void)t; nat_dbg("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); nat_dbg("+ NAT table +\n"); nat_dbg("+------------------------------------------------------------------------------------------------------------------------+\n"); nat_dbg("+ src_addr | src_port | dst_addr | dst_port | nat_addr | nat_port | proto | conn active | FIN1 | FIN2 | SYN | RST | FORW +\n"); nat_dbg("+------------------------------------------------------------------------------------------------------------------------+\n"); pico_tree_foreach(index, &NATOutbound) { t = index->keyValue; nat_dbg("+ %08X | %05u | %08X | %05u | %08X | %05u | %03u | %03u | %u | %u | %u | %u | %u +\n", long_be(t->src_addr.addr), t->src_port, long_be(t->dst_addr.addr), t->dst_port, long_be(t->nat_addr.addr), t->nat_port, t->proto, t->conn_active, t->fin_in, t->fin_out, t->syn, t->rst, t->portforward); } nat_dbg("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); } /* 2 options: find on nat_port and proto find on src_addr, src_port and proto zero the unused parameters */ static struct pico_nat_tuple *pico_ipv4_nat_find_tuple(uint16_t nat_port, struct pico_ip4 *src_addr, uint16_t src_port, uint8_t proto) { struct pico_nat_tuple *found = NULL, test = { 0 }; test.nat_port = nat_port; test.src_port = src_port; test.proto = proto; if (src_addr) test.src_addr = *src_addr; if (nat_port) found = pico_tree_findKey(&NATInbound, &test); else found = pico_tree_findKey(&NATOutbound, &test); if (found) return found; else return NULL; } int pico_ipv4_nat_find(uint16_t nat_port, struct pico_ip4 *src_addr, uint16_t src_port, uint8_t proto) { struct pico_nat_tuple *t = NULL; t = pico_ipv4_nat_find_tuple(nat_port, src_addr, src_port, proto); if (t) return 1; else return 0; } static struct pico_nat_tuple *pico_ipv4_nat_add(struct pico_ip4 dst_addr, uint16_t dst_port, struct pico_ip4 src_addr, uint16_t src_port, struct pico_ip4 nat_addr, uint16_t nat_port, uint8_t proto) { struct pico_nat_tuple *t = PICO_ZALLOC(sizeof(struct pico_nat_tuple)); if (!t) { pico_err = PICO_ERR_ENOMEM; return NULL; } t->dst_addr = dst_addr; t->dst_port = dst_port; t->src_addr = src_addr; t->src_port = src_port; t->nat_addr = nat_addr; t->nat_port = nat_port; t->proto = proto; t->conn_active = 1; t->portforward = 0; t->rst = 0; t->syn = 0; t->fin_in = 0; t->fin_out = 0; if (pico_tree_insert(&NATOutbound, t)) { PICO_FREE(t); return NULL; } if (pico_tree_insert(&NATInbound, t)) { pico_tree_delete(&NATOutbound, t); PICO_FREE(t); return NULL; } return t; } static int pico_ipv4_nat_del(uint16_t nat_port, uint8_t proto) { struct pico_nat_tuple *t = NULL; t = pico_ipv4_nat_find_tuple(nat_port, NULL, 0, proto); if (t) { pico_tree_delete(&NATOutbound, t); pico_tree_delete(&NATInbound, t); PICO_FREE(t); } return 0; } static struct pico_trans *pico_nat_generate_tuple_trans(struct pico_ipv4_hdr *net, struct pico_frame *f) { struct pico_trans *trans = NULL; switch (net->proto) { case PICO_PROTO_TCP: { struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; trans = (struct pico_trans *)&tcp->trans; break; } case PICO_PROTO_UDP: { struct pico_udp_hdr *udp = (struct pico_udp_hdr *)f->transport_hdr; trans = (struct pico_trans *)&udp->trans; break; } case PICO_PROTO_ICMP4: /* XXX: implement */ break; } return trans; } static struct pico_nat_tuple *pico_ipv4_nat_generate_tuple(struct pico_frame *f) { struct pico_trans *trans = NULL; struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; uint16_t nport = 0; uint8_t retry = 32; /* generate NAT port */ do { uint32_t rand = pico_rand(); nport = (uint16_t) (rand & 0xFFFFU); nport = (uint16_t)((nport % (65535 - 1024)) + 1024U); nport = short_be(nport); if (pico_is_port_free(net->proto, nport, NULL, &pico_proto_ipv4)) break; } while (--retry); if (!retry) return NULL; trans = pico_nat_generate_tuple_trans(net, f); if(!trans) return NULL; return pico_ipv4_nat_add(net->dst, trans->dport, net->src, trans->sport, nat_link->address, nport, net->proto); /* XXX return pico_ipv4_nat_add(nat_link->address, port, net->src, trans->sport, net->proto); */ } static inline void pico_ipv4_nat_set_tcp_flags(struct pico_nat_tuple *t, struct pico_frame *f, uint8_t direction) { struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; if (tcp->flags & PICO_TCP_SYN) t->syn = 1; if (tcp->flags & PICO_TCP_RST) t->rst = 1; if ((tcp->flags & PICO_TCP_FIN) && (direction == PICO_NAT_INBOUND)) t->fin_in = 1; if ((tcp->flags & PICO_TCP_FIN) && (direction == PICO_NAT_OUTBOUND)) t->fin_out = 1; } static int pico_ipv4_nat_sniff_session(struct pico_nat_tuple *t, struct pico_frame *f, uint8_t direction) { struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; switch (net->proto) { case PICO_PROTO_TCP: { pico_ipv4_nat_set_tcp_flags(t, f, direction); break; } case PICO_PROTO_UDP: t->conn_active = 1; break; case PICO_PROTO_ICMP4: /* XXX: implement */ break; default: return -1; } return 0; } static void pico_ipv4_nat_table_cleanup(pico_time now, void *_unused) { struct pico_tree_node *index = NULL, *_tmp = NULL; struct pico_nat_tuple *t = NULL; IGNORE_PARAMETER(now); IGNORE_PARAMETER(_unused); nat_dbg("NAT: before table cleanup:\n"); pico_ipv4_nat_print_table(); pico_tree_foreach_reverse_safe(index, &NATOutbound, _tmp) { t = index->keyValue; switch (t->proto) { case PICO_PROTO_TCP: if (t->portforward) break; else if (t->conn_active == 0 || t->conn_active > 360) /* conn active for > 24 hours */ pico_ipv4_nat_del(t->nat_port, t->proto); else if (t->rst || (t->fin_in && t->fin_out)) t->conn_active = 0; else t->conn_active++; break; case PICO_PROTO_UDP: if (t->portforward) break; else if (t->conn_active > 1) pico_ipv4_nat_del(t->nat_port, t->proto); else t->conn_active++; break; case PICO_PROTO_ICMP4: if (t->conn_active > 1) pico_ipv4_nat_del(t->nat_port, t->proto); else t->conn_active++; break; default: /* unknown protocol in NAT table, delete when it has existed NAT_TIMEWAIT */ if (t->conn_active > 1) pico_ipv4_nat_del(t->nat_port, t->proto); else t->conn_active++; } } nat_dbg("NAT: after table cleanup:\n"); pico_ipv4_nat_print_table(); if (!pico_timer_add(PICO_NAT_TIMEWAIT, pico_ipv4_nat_table_cleanup, NULL)) { nat_dbg("NAT: Failed to start cleanup timer\n"); /* TODO no more NAT table cleanup now */ } } int pico_ipv4_port_forward(struct pico_ip4 nat_addr, uint16_t nat_port, struct pico_ip4 src_addr, uint16_t src_port, uint8_t proto, uint8_t flag) { struct pico_nat_tuple *t = NULL; struct pico_ip4 any_addr = { 0 }; uint16_t any_port = 0; switch (flag) { case PICO_NAT_PORT_FORWARD_ADD: t = pico_ipv4_nat_add(any_addr, any_port, src_addr, src_port, nat_addr, nat_port, proto); if (!t) { pico_err = PICO_ERR_EAGAIN; return -1; } t->portforward = 1; break; case PICO_NAT_PORT_FORWARD_DEL: return pico_ipv4_nat_del(nat_port, proto); default: pico_err = PICO_ERR_EINVAL; return -1; } pico_ipv4_nat_print_table(); return 0; } int pico_ipv4_nat_inbound(struct pico_frame *f, struct pico_ip4 *link_addr) { struct pico_nat_tuple *tuple = NULL; struct pico_trans *trans = NULL; struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; if (!pico_ipv4_nat_is_enabled(link_addr)) return -1; switch (net->proto) { #ifdef PICO_SUPPORT_TCP case PICO_PROTO_TCP: { struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; trans = (struct pico_trans *)&tcp->trans; tuple = pico_ipv4_nat_find_tuple(trans->dport, 0, 0, net->proto); if (!tuple) return -1; /* replace dst IP and dst PORT */ net->dst = tuple->src_addr; trans->dport = tuple->src_port; /* recalculate CRC */ tcp->crc = 0; tcp->crc = short_be(pico_tcp_checksum_ipv4(f)); break; } #endif #ifdef PICO_SUPPORT_UDP case PICO_PROTO_UDP: { struct pico_udp_hdr *udp = (struct pico_udp_hdr *)f->transport_hdr; trans = (struct pico_trans *)&udp->trans; tuple = pico_ipv4_nat_find_tuple(trans->dport, 0, 0, net->proto); if (!tuple) return -1; /* replace dst IP and dst PORT */ net->dst = tuple->src_addr; trans->dport = tuple->src_port; /* recalculate CRC */ udp->crc = 0; udp->crc = short_be(pico_udp_checksum_ipv4(f)); break; } #endif case PICO_PROTO_ICMP4: /* XXX reimplement */ break; default: nat_dbg("NAT ERROR: inbound NAT on erroneous protocol\n"); return -1; } pico_ipv4_nat_sniff_session(tuple, f, PICO_NAT_INBOUND); net->crc = 0; net->crc = short_be(pico_checksum(net, f->net_len)); nat_dbg("NAT: inbound translation {dst.addr, dport}: {%08X,%u} -> {%08X,%u}\n", tuple->nat_addr.addr, short_be(tuple->nat_port), tuple->src_addr.addr, short_be(tuple->src_port)); return 0; } int pico_ipv4_nat_outbound(struct pico_frame *f, struct pico_ip4 *link_addr) { struct pico_nat_tuple *tuple = NULL; struct pico_trans *trans = NULL; struct pico_ipv4_hdr *net = (struct pico_ipv4_hdr *)f->net_hdr; if (!pico_ipv4_nat_is_enabled(link_addr)) return -1; switch (net->proto) { #ifdef PICO_SUPPORT_TCP case PICO_PROTO_TCP: { struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)f->transport_hdr; trans = (struct pico_trans *)&tcp->trans; tuple = pico_ipv4_nat_find_tuple(0, &net->src, trans->sport, net->proto); if (!tuple) tuple = pico_ipv4_nat_generate_tuple(f); /* replace src IP and src PORT */ net->src = tuple->nat_addr; trans->sport = tuple->nat_port; /* recalculate CRC */ tcp->crc = 0; tcp->crc = short_be(pico_tcp_checksum_ipv4(f)); break; } #endif #ifdef PICO_SUPPORT_UDP case PICO_PROTO_UDP: { struct pico_udp_hdr *udp = (struct pico_udp_hdr *)f->transport_hdr; trans = (struct pico_trans *)&udp->trans; tuple = pico_ipv4_nat_find_tuple(0, &net->src, trans->sport, net->proto); if (!tuple) tuple = pico_ipv4_nat_generate_tuple(f); /* replace src IP and src PORT */ net->src = tuple->nat_addr; trans->sport = tuple->nat_port; /* recalculate CRC */ udp->crc = 0; udp->crc = short_be(pico_udp_checksum_ipv4(f)); break; } #endif case PICO_PROTO_ICMP4: /* XXX reimplement */ break; default: nat_dbg("NAT ERROR: outbound NAT on erroneous protocol\n"); return -1; } pico_ipv4_nat_sniff_session(tuple, f, PICO_NAT_OUTBOUND); net->crc = 0; net->crc = short_be(pico_checksum(net, f->net_len)); nat_dbg("NAT: outbound translation {src.addr, sport}: {%08X,%u} -> {%08X,%u}\n", tuple->src_addr.addr, short_be(tuple->src_port), tuple->nat_addr.addr, short_be(tuple->nat_port)); return 0; } int pico_ipv4_nat_enable(struct pico_ipv4_link *link) { if (link == NULL) { pico_err = PICO_ERR_EINVAL; return -1; } if (!pico_timer_add(PICO_NAT_TIMEWAIT, pico_ipv4_nat_table_cleanup, NULL)) { nat_dbg("NAT: Failed to start cleanup timer\n"); return -1; } nat_link = link; return 0; } int pico_ipv4_nat_disable(void) { nat_link = NULL; return 0; } int pico_ipv4_nat_is_enabled(struct pico_ip4 *link_addr) { if (!nat_link) return 0; if (nat_link->address.addr != link_addr->addr) return 0; return 1; } #endif #endif