diff --git a/src/core/init.c b/src/core/init.c
index b3737a35..9c2d1dcf 100644
--- a/src/core/init.c
+++ b/src/core/init.c
@@ -57,6 +57,7 @@
 #include "lwip/nd6.h"
 #include "lwip/mld6.h"
 #include "lwip/api.h"
+#include "lwip/napt.h"
 
 #include "netif/ppp/ppp_opts.h"
 #include "netif/ppp/ppp_impl.h"
diff --git a/src/core/ipv4/ip4.c b/src/core/ipv4/ip4.c
index 26c26a91..c8948b9c 100644
--- a/src/core/ipv4/ip4.c
+++ b/src/core/ipv4/ip4.c
@@ -42,6 +42,7 @@
 
 #if LWIP_IPV4
 
+#include "lwip/sys.h"
 #include "lwip/ip.h"
 #include "lwip/def.h"
 #include "lwip/mem.h"
@@ -56,6 +57,7 @@
 #include "lwip/autoip.h"
 #include "lwip/stats.h"
 #include "lwip/prot/iana.h"
+#include "lwip/napt.h"
 
 #include <string.h>
 
@@ -228,6 +230,739 @@ ip4_route(const ip4_addr_t *dest)
 }
 
 #if IP_FORWARD
+#if IP_NAPT
+
+#define NO_IDX ((u16_t)-1)
+#define NT(x) ((x) == NO_IDX ? NULL : &napt_data->table[x])
+
+struct napt_data {
+  struct napt_table *table;
+  struct portmap_table *portmap_table;
+  int nr_active_tcp;
+  int nr_active_udp;
+  int nr_active_icmp;
+  u16_t list;
+  u16_t list_last;
+  u16_t free;
+  u16_t napt_max;
+  u8_t portmap_max;
+};
+
+struct napt_data *napt_data = NULL;
+
+#if NAPT_DEBUG
+/* Print NAPT table using LWIP_DEBUGF
+*/
+void
+napt_debug_print()
+{
+  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"));
+
+  for (i = napt_data->list; i != NO_IDX; i = next) {
+     struct napt_table *t = &napt_data->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)));
+
+  }
+}
+#endif /* NAPT_DEBUG */
+
+err_t
+ip_napt_init(u16_t nat_max, u8_t portmap_max)
+{
+  u16_t i;
+
+  if (napt_data)
+    return ERR_BUF;
+
+  napt_data = (struct napt_data*)calloc(1, sizeof(struct napt_data));
+  if (!napt_data) {
+    napt_data = NULL;
+    return ERR_MEM;
+  }
+  napt_data->table = (struct napt_table*)calloc(1, sizeof(struct napt_table[nat_max]));
+  if (!napt_data->table) {
+    free(napt_data);
+    napt_data = NULL;
+    return ERR_MEM;
+  }
+  napt_data->portmap_table = (struct portmap_table*)calloc(1, sizeof(struct portmap_table[portmap_max]));
+  if (!napt_data->portmap_table) {
+    free(napt_data->table);
+    free(napt_data);
+    napt_data = NULL;
+    return ERR_MEM;
+  }
+
+  napt_data->list = NO_IDX;
+  napt_data->list_last = NO_IDX;
+  napt_data->free = 0;
+  napt_data->napt_max = nat_max;
+  napt_data->portmap_max = portmap_max;
+  for (i = 0; i < nat_max - 1; i++)
+    napt_data->table[i].next = i + 1;
+  napt_data->table[i].next = NO_IDX;
+
+  return ERR_OK;
+}
+
+err_t
+ip_napt_enable(u32_t addr, int enable)
+{
+  struct netif *netif;
+  for (netif = netif_list; netif; netif = netif->next) {
+    if (netif_is_up(netif) && !ip_addr_isany(&netif->ip_addr) && ip_2_ip4(&netif->ip_addr)->addr == addr) {
+      netif->napt = !!enable;
+      return ERR_OK;
+    }
+  }
+  return ERR_VAL;
+}
+
+err_t
+ip_napt_enable_no(u8_t number, int enable)
+{
+  struct netif *netif;
+  for (netif = netif_list; netif; netif = netif->next) {
+    if (netif->num == number) {
+      netif->napt = !!enable;
+      return ERR_OK;
+    }
+  }
+  return ERR_VAL;
+}
+
+void
+checksumadjust (u8_t *chksum, u8_t *optr, int olen, u8_t *nptr, int nlen)
+   /* - chksum points to the chksum in the packet
+      - optr points to the old data in the packet
+      - nptr points to the new data in the packet
+   */
+{
+  s32_t x, old, new;
+  x = chksum[0] * 256 + chksum[1];
+  x = ~x & 0xFFFF;
+  while (olen) {
+    old = (optr[0] << 8) + optr[1];
+    optr += 2;
+    x -= old & 0xffff;
+    if (x <= 0) {
+      x--;
+      x &= 0xffff;
+    }
+    olen -= 2;
+  }
+  while (nlen) {
+    new = (nptr[0] << 8) + nptr[1];
+    nptr += 2;
+    x += new & 0xffff;
+    if (x & 0x10000) {
+      x++;
+      x &= 0xffff;
+    }
+    nlen -= 2;
+  }
+  x = ~x & 0xFFFF;
+  chksum[0] = x >> 8;
+  chksum[1] = x & 0xff;
+}
+
+/* t must be indexed by napt_data->free */
+static void
+ip_napt_insert(struct napt_table *t)
+{
+  u16_t ti = t - napt_data->table;
+  LWIP_ASSERT("napt: free indexes mismatch", ti == napt_data->free);
+  napt_data->free = t->next;
+  t->prev = NO_IDX;
+  t->next = napt_data->list;
+  if (napt_data->list != NO_IDX)
+    NT(napt_data->list)->prev = ti;
+  napt_data->list = ti;
+  if (napt_data->list_last == NO_IDX)
+    napt_data->list_last = ti;
+
+#if LWIP_TCP
+  if (t->proto == IP_PROTO_TCP)
+    napt_data->nr_active_tcp++;
+#endif
+#if LWIP_UDP
+  if (t->proto == IP_PROTO_UDP)
+    napt_data->nr_active_udp++;
+#endif
+#if LWIP_ICMP
+  if (t->proto == IP_PROTO_ICMP)
+    napt_data->nr_active_icmp++;
+#endif
+  LWIP_DEBUGF(LWIP_DBG_ON, ("napt: T: %d, U: %d, I: %d\r\n", napt_data->nr_active_tcp, napt_data->nr_active_udp, napt_data->nr_active_icmp));
+}
+
+static void
+ip_napt_free(struct napt_table *t)
+{
+  u16_t ti = t - napt_data->table;
+  if (ti == napt_data->list)
+    napt_data->list = t->next;
+  if (ti == napt_data->list_last)
+    napt_data->list_last = t->prev;
+  if (t->next != NO_IDX)
+    NT(t->next)->prev = t->prev;
+  if (t->prev != NO_IDX)
+    NT(t->prev)->next = t->next;
+  t->prev = NO_IDX;
+  t->next = napt_data->free;
+  napt_data->free = ti;
+
+#if LWIP_TCP
+  if (t->proto == IP_PROTO_TCP)
+    napt_data->nr_active_tcp--;
+#endif
+#if LWIP_UDP
+  if (t->proto == IP_PROTO_UDP)
+    napt_data->nr_active_udp--;
+#endif
+#if LWIP_ICMP
+  if (t->proto == IP_PROTO_ICMP)
+    napt_data->nr_active_icmp--;
+#endif
+  LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_free\n"));
+#if NAPT_DEBUG
+  napt_debug_print();
+#endif
+}
+
+#if LWIP_TCP
+static u8_t
+ip_napt_find_port(u8_t proto, u16_t port)
+{
+  int i, next;
+  for (i = napt_data->list; i != NO_IDX; i = next) {
+    struct napt_table *t = &napt_data->table[i];
+    next = t->next;
+    if (t->proto == proto && t->mport == port)
+      return 1;
+  }
+  return 0;
+}
+
+static struct portmap_table *
+ip_portmap_find(u8_t proto, u16_t mport);
+
+static u8_t
+tcp_listening(u16_t port)
+{
+  struct tcp_pcb_listen *t;
+  for (t = tcp_listen_pcbs.listen_pcbs; t; t = t->next)
+    if (t->local_port == port)
+      return 1;
+  if (ip_portmap_find(IP_PROTO_TCP, port))
+    return 1;
+  return 0;
+}
+#endif // LWIP_TCP
+
+#if LWIP_UDP
+static u8_t
+udp_listening(u16_t port)
+{
+  struct udp_pcb *pcb;
+  for (pcb = udp_pcbs; pcb; pcb = pcb->next)
+    if (pcb->local_port == port)
+      return 1;
+  if (ip_portmap_find(IP_PROTO_UDP, port))
+    return 1;
+  return 0;
+}
+#endif // LWIP_UDP
+
+static u16_t
+ip_napt_new_port(u8_t proto, u16_t port)
+{
+  if (PP_NTOHS(port) >= IP_NAPT_PORT_RANGE_START && PP_NTOHS(port) <= IP_NAPT_PORT_RANGE_END)
+    if (!ip_napt_find_port(proto, port) && !tcp_listening(port))
+      return port;
+  for (;;) {
+    port = PP_HTONS(IP_NAPT_PORT_RANGE_START +
+                    LWIP_RAND() % (IP_NAPT_PORT_RANGE_END - IP_NAPT_PORT_RANGE_START + 1));
+    if (ip_napt_find_port(proto, port))
+      continue;
+#if LWIP_TCP
+    if (proto == IP_PROTO_TCP && tcp_listening(port))
+      continue;
+#endif // LWIP_TCP
+#if LWIP_UDP
+    if (proto == IP_PROTO_UDP && udp_listening(port))
+      continue;
+#endif // LWIP_UDP
+
+    return port;
+  }
+}
+
+static struct napt_table*
+ip_napt_find(u8_t proto, u32_t addr, u16_t port, u16_t mport, u8_t dest)
+{
+  u16_t i, next;
+  struct napt_table *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: %lu, mport: %lu\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)));
+#if NAPT_DEBUG
+  napt_debug_print();
+#endif
+
+  u32_t now = sys_now();
+  for (i = napt_data->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) {
+      t->last = now;
+      LWIP_DEBUGF(NAPT_DEBUG, ("found\n"));
+      return t;
+    }
+    if (dest == 1 && t->proto == proto && t->dest == addr && t->dport == port
+        && t->mport == mport) {
+      t->last = now;
+      LWIP_DEBUGF(NAPT_DEBUG, ("found\n"));
+      return t;
+    }
+  }
+
+  LWIP_DEBUGF(NAPT_DEBUG, ("not found\n"));
+  return NULL;
+}
+
+static u16_t
+ip_napt_add(u8_t proto, u32_t src, u16_t sport, u32_t dest, u16_t dport)
+{
+  struct napt_table *t = ip_napt_find(proto, src, sport, 0, 0);
+  if (t) {
+    t->last = sys_now();
+    t->dest = dest;
+    t->dport = dport;
+    /* move this entry to the top of list */
+    ip_napt_free(t);
+    ip_napt_insert(t);
+
+    LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_add\n"));
+#if NAPT_DEBUG
+    napt_debug_print();
+#endif
+
+    return t->mport;
+  }
+  t = NT(napt_data->free);
+  if (t) {
+    u16_t mport = sport;
+#if LWIP_TCP
+    if (proto == IP_PROTO_TCP)
+      mport = ip_napt_new_port(IP_PROTO_TCP, sport);
+#endif
+#if LWIP_UDP
+    if (proto == IP_PROTO_UDP)
+      mport = ip_napt_new_port(IP_PROTO_UDP, sport);
+#endif
+    t->last = sys_now();
+    t->src = src;
+    t->dest = dest;
+    t->sport = sport;
+    t->dport = dport;
+    t->mport = mport;
+    t->proto = proto;
+    t->fin1 = t->fin2 = t->finack1 = t->finack2 = t->synack = t->rst = 0;
+    ip_napt_insert(t);
+
+    LWIP_DEBUGF(NAPT_DEBUG, ("ip_napt_add\n"));
+#if NAPT_DEBUG
+    napt_debug_print();
+#endif
+
+    return mport;
+  }
+  return 0;
+}
+
+u8_t
+ip_portmap_add(u8_t proto, u32_t maddr, u16_t mport, u32_t daddr, u16_t dport)
+{
+  mport = PP_HTONS(mport);
+  dport = PP_HTONS(dport);
+  int i;
+
+  for (i = 0; i < napt_data->portmap_max; i++) {
+    struct portmap_table *p = &napt_data->portmap_table[i];
+    if (p->valid && p->proto == proto && p->mport == mport) {
+      p->dport = dport;
+      p->daddr = daddr;
+    } else if (!p->valid) {
+      p->maddr = maddr;
+      p->daddr = daddr;
+      p->mport = mport;
+      p->dport = dport;
+      p->proto = proto;
+      p->valid = 1;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static struct portmap_table *
+ip_portmap_find(u8_t proto, u16_t mport)
+{
+  int i;
+  for (i = 0; i < napt_data->portmap_max; i++) {
+    struct portmap_table *p = &napt_data->portmap_table[i];
+    if (!p->valid)
+      return NULL;
+    if (p->proto == proto && p->mport == mport)
+      return p;
+  }
+  return NULL;
+}
+
+static struct portmap_table *
+ip_portmap_find_dest(u8_t proto, u16_t dport, u32_t daddr)
+{
+  int i;
+  for (i = 0; i < napt_data->portmap_max; i++) {
+    struct portmap_table *p = &napt_data->portmap_table[i];
+    if (!p->valid)
+      return NULL;
+    if (p->proto == proto && p->dport == dport && p->daddr == daddr)
+      return p;
+  }
+  return NULL;
+}
+
+u8_t
+ip_portmap_remove(u8_t proto, u16_t mport)
+{
+  mport = PP_HTONS(mport);
+  struct portmap_table *last = &napt_data->portmap_table[napt_data->portmap_max - 1];
+  struct portmap_table *m = ip_portmap_find(proto, mport);
+  if (!m)
+    return 0;
+  for (; m != last; m++)
+    memcpy(m, m + 1, sizeof(*m));
+  last->valid = 0;
+  return 1;
+}
+
+#if LWIP_TCP
+void
+ip_napt_modify_port_tcp(struct tcp_hdr *tcphdr, u8_t dest, u16_t newval)
+{
+  if (dest) {
+    checksumadjust((unsigned char *)&tcphdr->chksum, (unsigned char *)&tcphdr->dest, 2, (unsigned char *)&newval, 2);
+    tcphdr->dest = newval;
+  } else {
+    checksumadjust((unsigned char *)&tcphdr->chksum, (unsigned char *)&tcphdr->src, 2, (unsigned char *)&newval, 2);
+    tcphdr->src = newval;
+  }
+}
+
+void
+ip_napt_modify_addr_tcp(struct tcp_hdr *tcphdr, ip4_addr_p_t *oldval, u32_t newval)
+{
+  checksumadjust((unsigned char *)&tcphdr->chksum, (unsigned char *)&oldval->addr, 4, (unsigned char *)&newval, 4);
+}
+#endif // LWIP_TCP
+
+#if LWIP_UDP
+void
+ip_napt_modify_port_udp(struct udp_hdr *udphdr, u8_t dest, u16_t newval)
+{
+  if (dest) {
+    checksumadjust((unsigned char *)&udphdr->chksum, (unsigned char *)&udphdr->dest, 2, (unsigned char *)&newval, 2);
+    udphdr->dest = newval;
+  } else {
+    checksumadjust((unsigned char *)&udphdr->chksum, (unsigned char *)&udphdr->src, 2, (unsigned char *)&newval, 2);
+    udphdr->src = newval;
+  }
+}
+
+void
+ip_napt_modify_addr_udp(struct udp_hdr *udphdr, ip4_addr_p_t *oldval, u32_t newval)
+{
+  checksumadjust( (unsigned char *)&udphdr->chksum, (unsigned char *)&oldval->addr, 4, (unsigned char *)&newval, 4);
+}
+#endif // LWIP_UDP
+
+void
+ip_napt_modify_addr(struct ip_hdr *iphdr, ip4_addr_p_t *field, u32_t newval)
+{
+  checksumadjust((unsigned char *)&IPH_CHKSUM(iphdr), (unsigned char *)&field->addr, 4, (unsigned char *)&newval, 4);
+  field->addr = newval;
+}
+
+/**
+ * NAPT for an input packet. It checks weather the destination is on NAPT
+ * table and modifythe packet destination 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
+ */
+static void
+ip_napt_recv(struct pbuf *p, struct ip_hdr *iphdr)
+{
+  struct portmap_table *m;
+  struct napt_table *t;
+
+  if (!napt_data)
+    return;
+
+#if LWIP_ICMP
+  /* NAPT for ICMP Echo Request using identifier */
+  if (IPH_PROTO(iphdr) == IP_PROTO_ICMP) {
+    struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4);
+    if (iecho->type == ICMP_ER) {
+      t = ip_napt_find(IP_PROTO_ICMP, iphdr->src.addr, iecho->id, iecho->id, 1);
+      if (!t)
+        return;
+      ip_napt_modify_addr(iphdr, &iphdr->dest, t->src);
+      return;
+    }
+
+    return;
+  }
+#endif // LWIP_ICMP
+
+#if LWIP_TCP
+  if (IPH_PROTO(iphdr) == IP_PROTO_TCP) {
+    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)));
+
+    LWIP_DEBUGF(NAPT_DEBUG, ("sport %u, dport: %u\n",
+			     lwip_htons(tcphdr->src),
+			     lwip_htons(tcphdr->dest)));
+
+    m = ip_portmap_find(IP_PROTO_TCP, tcphdr->dest);
+    if (m) {
+      /* packet to mapped port: rewrite destination */
+      if (m->dport != tcphdr->dest)
+        ip_napt_modify_port_tcp(tcphdr, 1, m->dport);
+      ip_napt_modify_addr_tcp(tcphdr, &iphdr->dest, m->daddr);
+      ip_napt_modify_addr(iphdr, &iphdr->dest, m->daddr);
+      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->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;
+  }
+#endif // LWIP_TCP
+
+#if LWIP_UDP
+  if (IPH_PROTO(iphdr) == IP_PROTO_UDP) {
+    struct udp_hdr *udphdr = (struct udp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4);
+    m = ip_portmap_find(IP_PROTO_UDP, udphdr->dest);
+    if (m) {
+      /* packet to mapped port: rewrite destination */
+      if (m->dport != udphdr->dest)
+        ip_napt_modify_port_udp(udphdr, 1, m->dport);
+      ip_napt_modify_addr_udp(udphdr, &iphdr->dest, m->daddr);
+      ip_napt_modify_addr(iphdr, &iphdr->dest, m->daddr);
+      return;
+    }
+    t = ip_napt_find(IP_PROTO_UDP, iphdr->src.addr, udphdr->src, udphdr->dest, 1);
+      if (!t)
+        return; /* Unknown session; do nothing */
+
+      if (t->sport != udphdr->dest)
+        ip_napt_modify_port_udp(udphdr, 1, t->sport);
+      ip_napt_modify_addr_udp(udphdr, &iphdr->dest, t->src);
+      ip_napt_modify_addr(iphdr, &iphdr->dest, t->src);
+      return;
+  }
+#endif // LWIP_UDP
+}
+
+/**
+ * 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
+ */
+static err_t
+ip_napt_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp, struct netif *outp)
+{
+  if (!inp->napt){
+    return ERR_OK;
+  }
+
+#if LWIP_ICMP
+  /* NAPT for ICMP Echo Request using identifier */
+  if (IPH_PROTO(iphdr) == IP_PROTO_ICMP) {
+    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_modify_addr(iphdr, &iphdr->src, ip_2_ip4(&outp->ip_addr)->addr);
+    }
+
+    return ERR_OK;
+  }
+#endif
+
+#if LWIP_TCP
+  if (IPH_PROTO(iphdr) == IP_PROTO_TCP) {
+    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);
+    if (m) {
+      /* packet from port-mapped dest addr/port: rewrite source to this node */
+      if (m->mport != tcphdr->src)
+        ip_napt_modify_port_tcp(tcphdr, 0, m->mport);
+      ip_napt_modify_addr_tcp(tcphdr, &iphdr->src, m->maddr);
+      ip_napt_modify_addr(iphdr, &iphdr->src, m->maddr);
+      return ERR_OK;
+    }
+    if ((TCPH_FLAGS(tcphdr) & (TCP_SYN|TCP_ACK)) == TCP_SYN &&
+        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);
+    } else {
+      struct napt_table *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);
+#endif
+      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))
+        t->finack1 = 1; /* FIXME: Currently ignoring ACK seq... */
+    if (TCPH_FLAGS(tcphdr) & TCP_RST)
+        t->rst = 1;
+    }
+
+    if (mport != tcphdr->src)
+      ip_napt_modify_port_tcp(tcphdr, 0, mport);
+    ip_napt_modify_addr_tcp(tcphdr, &iphdr->src, ip_2_ip4(&outp->ip_addr)->addr);
+    ip_napt_modify_addr(iphdr, &iphdr->src, ip_2_ip4(&outp->ip_addr)->addr);
+
+    return ERR_OK;
+  }
+#endif
+
+#if LWIP_UDP
+  if (IPH_PROTO(iphdr) == IP_PROTO_UDP) {
+    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);
+    if (m) {
+      /* packet from port-mapped dest addr/port: rewrite source to this node */
+      if (m->mport != udphdr->src)
+        ip_napt_modify_port_udp(udphdr, 0, m->mport);
+      ip_napt_modify_addr_udp(udphdr, &iphdr->src, m->maddr);
+      ip_napt_modify_addr(iphdr, &iphdr->src, m->maddr);
+      return ERR_OK;
+    }
+    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);
+    } else {
+      struct napt_table *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);
+#endif
+        return ERR_RTE; /* Drop unknown UDP session */
+      }
+      mport = t->mport;
+    }
+
+    if (mport != udphdr->src)
+      ip_napt_modify_port_udp(udphdr, 0, mport);
+    ip_napt_modify_addr_udp(udphdr, &iphdr->src, ip_2_ip4(&outp->ip_addr)->addr);
+    ip_napt_modify_addr(iphdr, &iphdr->src, ip_2_ip4(&outp->ip_addr)->addr);
+    return ERR_OK;
+  }
+#endif
+
+  return ERR_OK;
+}
+#endif // IP_NAPT
+
 /**
  * Determine whether an IP address is in a reserved set of addresses
  * that may not be forwarded, or whether datagrams to that destination
@@ -329,6 +1064,11 @@ ip4_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp)
     return;
   }
 
+#if IP_NAPT
+  if (ip_napt_forward(p, iphdr, inp, netif) != ERR_OK)
+    return;
+#endif
+
   /* Incrementally update the IP checksum. */
   if (IPH_CHKSUM(iphdr) >= PP_HTONS(0xffffU - 0x100)) {
     IPH_CHKSUM_SET(iphdr, (u16_t)(IPH_CHKSUM(iphdr) + PP_HTONS(0x100) + 1));
@@ -511,6 +1251,13 @@ ip4_input(struct pbuf *p, struct netif *inp)
   }
 #endif
 
+#if IP_NAPT
+  /* for unicast packet, check NAPT table and modify dest if needed */
+  /* iphdr is const, use p->payload instead */
+  if (!inp->napt && ip4_addr_cmp(&iphdr->dest, netif_ip4_addr(inp)))
+    ip_napt_recv(p, (struct ip_hdr *)p->payload);
+#endif
+
   /* copy IP addresses to aligned ip_addr_t */
   ip_addr_copy_from_ip4(ip_data.current_iphdr_dest, iphdr->dest);
   ip_addr_copy_from_ip4(ip_data.current_iphdr_src, iphdr->src);
@@ -621,6 +1368,16 @@ ip4_input(struct pbuf *p, struct netif *inp)
 #if IP_FORWARD
     /* non-broadcast packet? */
     if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp)) {
+      /* the header modification in ip4_forward may not work on PBUF_REF */
+      /* so make a copy of packet */
+      if (p->type_internal == PBUF_REF) {
+          struct pbuf* q = pbuf_alloc(PBUF_IP, p->len + 16, PBUF_RAM);
+          if (q != NULL) {
+              pbuf_copy(q, p);
+              pbuf_free(p);
+              p = q;
+          }
+      }
       /* try to forward IP packet on (other) interfaces */
       ip4_forward(p, (struct ip_hdr *)p->payload, inp);
     } else
diff --git a/src/core/netif.c b/src/core/netif.c
index 15200a27..c52fea26 100644
--- a/src/core/netif.c
+++ b/src/core/netif.c
@@ -363,6 +363,10 @@ netif_add(struct netif *netif,
   netif->loop_cnt_current = 0;
 #endif /* ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS */
 
+#if IP_NAPT
+  netif->napt = 0;
+#endif /* IP_NAPT */
+
 #if LWIP_IPV4
   netif_set_addr(netif, ipaddr, netmask, gw);
 #endif /* LWIP_IPV4 */
diff --git a/src/include/lwip/ip.h b/src/include/lwip/ip.h
index 653c3b2f..230bfdcf 100644
--- a/src/include/lwip/ip.h
+++ b/src/include/lwip/ip.h
@@ -189,6 +189,12 @@ extern struct ip_globals ip_data;
 /** Destination IP4 address of current_header */
 #define ip4_current_dest_addr()    (&ip_data.current_iphdr_dest)
 
+#if NAPT_DEBUG
+void napt_debug_print()ICACHE_FLASH_ATTR;
+#else
+#define napt_debug_print(p)
+#endif /* NAPT_DEBUG */
+
 #elif LWIP_IPV6 /* LWIP_IPV4 && LWIP_IPV6 */
 
 /** Get the IPv6 header of the current packet.
diff --git a/src/include/lwip/netif.h b/src/include/lwip/netif.h
index 911196ab..c29a9876 100644
--- a/src/include/lwip/netif.h
+++ b/src/include/lwip/netif.h
@@ -387,6 +387,9 @@ struct netif {
   u16_t loop_cnt_current;
 #endif /* LWIP_LOOPBACK_MAX_PBUFS */
 #endif /* ENABLE_LOOPBACK */
+#if LWIP_IPV4 && IP_NAPT
+  u8_t napt;
+#endif
 };
 
 #if LWIP_CHECKSUM_CTRL_PER_NETIF
diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h
index 82c420c1..371286b6 100644
--- a/src/include/lwip/opt.h
+++ b/src/include/lwip/opt.h
@@ -764,6 +764,10 @@
 #define IP_FRAG                         0
 #endif /* !LWIP_IPV4 */
 
+#ifndef IP_NAPT
+#define IP_NAPT                         0
+#endif
+
 /**
  * IP_OPTIONS_ALLOWED: Defines the behavior for IP options.
  *      IP_OPTIONS_ALLOWED==0: All packets with IP options are dropped.
@@ -3484,6 +3488,14 @@
 #if !defined DHCP6_DEBUG || defined __DOXYGEN__
 #define DHCP6_DEBUG                     LWIP_DBG_OFF
 #endif
+
+/**
+ * NAPT_DEBUG: Enable debugging for NAPT.
+ */
+#ifndef NAPT_DEBUG
+#define NAPT_DEBUG                       LWIP_DBG_OFF
+#endif
+
 /**
  * @}
  */
