NAT44: TCP connection close detection (VPP-1266)
[vpp.git] / src / plugins / nat / nat.c
old mode 100644 (file)
new mode 100755 (executable)
index 5c324f2..68b43c0
@@ -24,6 +24,7 @@
 #include <nat/nat_ipfix_logging.h>
 #include <nat/nat_det.h>
 #include <nat/nat64.h>
+#include <nat/nat66.h>
 #include <nat/dslite.h>
 #include <nat/nat_reass.h>
 #include <vnet/fib/fib_table.h>
@@ -38,79 +39,79 @@ snat_main_t snat_main;
 VNET_FEATURE_INIT (ip4_snat_in2out, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-in2out",
-  .runs_before = VNET_FEATURES ("nat44-out2in"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_out2in, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_nat_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-classify",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_det_in2out, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-in2out",
-  .runs_before = VNET_FEATURES ("nat44-det-out2in"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_det_out2in, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-out2in",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_nat_det_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-classify",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_in2out_worker_handoff, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-in2out-worker-handoff",
-  .runs_before = VNET_FEATURES ("nat44-out2in-worker-handoff"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_out2in_worker_handoff, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in-worker-handoff",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_nat_handoff_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-handoff-classify",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_in2out_fast, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-in2out-fast",
-  .runs_before = VNET_FEATURES ("nat44-out2in-fast"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_out2in_fast, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in-fast",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_hairpin_dst, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-hairpin-dst",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 
 /* Hook up output features */
 VNET_FEATURE_INIT (ip4_snat_in2out_output, static) = {
   .arc_name = "ip4-output",
   .node_name = "nat44-in2out-output",
-  .runs_before = VNET_FEATURES ("interface-output"),
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_in2out_output_worker_handoff, static) = {
   .arc_name = "ip4-output",
   .node_name = "nat44-in2out-output-worker-handoff",
-  .runs_before = VNET_FEATURES ("interface-output"),
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_hairpin_src, static) = {
   .arc_name = "ip4-output",
   .node_name = "nat44-hairpin-src",
-  .runs_before = VNET_FEATURES ("interface-output"),
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
 
 /* Hook up ip4-local features */
@@ -151,6 +152,22 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
   snat_main_per_thread_data_t *tsm =
     vec_elt_at_index (sm->per_thread_data, thread_index);
 
+  if (is_fwd_bypass_session (s))
+    {
+      ed_key.l_addr = s->in2out.addr;
+      ed_key.r_addr = s->ext_host_addr;
+      ed_key.l_port = s->in2out.port;
+      ed_key.r_port = s->ext_host_port;
+      ed_key.proto = snat_proto_to_ip_proto (s->in2out.protocol);
+      ed_key.fib_index = 0;
+      ed_kv.key[0] = ed_key.as_u64[0];
+      ed_kv.key[1] = ed_key.as_u64[1];
+      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0) &&
+          s->state != SNAT_SESSION_TCP_CLOSED)
+        clib_warning ("in2out_ed key del failed");
+      return;
+    }
+
   /* Endpoint dependent session lookup tables */
   if (is_ed_session (s))
     {
@@ -171,7 +188,8 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
         }
       ed_kv.key[0] = ed_key.as_u64[0];
       ed_kv.key[1] = ed_key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &ed_kv, 0))
+      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &ed_kv, 0) &&
+          s->state != SNAT_SESSION_TCP_CLOSED)
         clib_warning ("out2in_ed key del failed");
 
       ed_key.l_addr = s->in2out.addr;
@@ -185,7 +203,8 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
         }
       ed_kv.key[0] = ed_key.as_u64[0];
       ed_kv.key[1] = ed_key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0))
+      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0) &&
+          s->state != SNAT_SESSION_TCP_CLOSED)
         clib_warning ("in2out_ed key del failed");
     }
 
@@ -201,7 +220,7 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
                                       s->in2out.fib_index);
 
   /* Twice NAT address and port for external host */
-  if (is_twice_nat_session (s))
+  if (is_twice_nat_session (s) && s->state != SNAT_SESSION_TCP_CLOSED)
     {
       for (i = 0; i < vec_len (sm->twice_nat_addresses); i++)
         {
@@ -222,16 +241,18 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
 
   /* Session lookup tables */
   kv.key = s->in2out.as_u64;
-  if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 0))
+  if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 0) &&
+      s->state != SNAT_SESSION_TCP_CLOSED)
     clib_warning ("in2out key del failed");
   kv.key = s->out2in.as_u64;
-  if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0))
+  if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0) &&
+      s->state != SNAT_SESSION_TCP_CLOSED)
     clib_warning ("out2in key del failed");
 
   if (snat_is_session_static (s))
     return;
 
-  if (s->outside_address_index != ~0)
+  if (s->outside_address_index != ~0 && s->state != SNAT_SESSION_TCP_CLOSED)
     snat_free_outside_address_and_port (sm->addresses, thread_index,
                                         &s->out2in, s->outside_address_index);
 }
@@ -317,6 +338,11 @@ nat_session_alloc_or_recycle (snat_main_t *sm, snat_user_t *u, u32 thread_index)
       s->flags = 0;
       s->total_bytes = 0;
       s->total_pkts = 0;
+      s->state = 0;
+      s->ext_host_addr.as_u32 = 0;
+      s->ext_host_port = 0;
+      s->ext_host_nat_addr.as_u32 = 0;
+      s->ext_host_nat_port = 0;
     }
   else
     {
@@ -349,6 +375,7 @@ nat44_classify_node_fn_inline (vlib_main_t * vm,
   u32 n_left_from, * from, * to_next;
   nat44_classify_next_t next_index;
   snat_main_t *sm = &snat_main;
+  snat_static_mapping_t *m;
 
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
@@ -400,7 +427,9 @@ nat44_classify_node_fn_inline (vlib_main_t * vm,
               kv0.key = m_key0.as_u64;
               if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv0, &value0))
                 {
-                  next0 = NAT44_CLASSIFY_NEXT_OUT2IN;
+                  m = pool_elt_at_index (sm->static_mappings, value0.value);
+                  if (m->local_addr.as_u32 != m->external_addr.as_u32)
+                    next0 = NAT44_CLASSIFY_NEXT_OUT2IN;
                   goto enqueue0;
                 }
               udp_header_t * udp0 = ip4_next_header (ip0);
@@ -408,7 +437,11 @@ nat44_classify_node_fn_inline (vlib_main_t * vm,
               m_key0.protocol = ip_proto_to_snat_proto (ip0->protocol);
               kv0.key = m_key0.as_u64;
               if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv0, &value0))
-                next0 = NAT44_CLASSIFY_NEXT_OUT2IN;
+                {
+                  m = pool_elt_at_index (sm->static_mappings, value0.value);
+                  if (m->local_addr.as_u32 != m->external_addr.as_u32)
+                    next0 = NAT44_CLASSIFY_NEXT_OUT2IN;
+                }
             }
 
         enqueue0:
@@ -521,7 +554,7 @@ snat_add_del_addr_to_fib (ip4_address_t * addr, u8 p_len, u32 sw_if_index,
   if (is_add)
     fib_table_entry_update_one_path(fib_index,
                                     &prefix,
-                                    FIB_SOURCE_PLUGIN_HI,
+                                    FIB_SOURCE_PLUGIN_LOW,
                                     (FIB_ENTRY_FLAG_CONNECTED |
                                      FIB_ENTRY_FLAG_LOCAL |
                                      FIB_ENTRY_FLAG_EXCLUSIVE),
@@ -535,7 +568,7 @@ snat_add_del_addr_to_fib (ip4_address_t * addr, u8 p_len, u32 sw_if_index,
   else
     fib_table_entry_delete(fib_index,
                            &prefix,
-                           FIB_SOURCE_PLUGIN_HI);
+                           FIB_SOURCE_PLUGIN_LOW);
 }
 
 void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
@@ -561,7 +594,7 @@ void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
   if (vrf_id != ~0)
     ap->fib_index =
       fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, vrf_id,
-                                         FIB_SOURCE_PLUGIN_HI);
+                                         FIB_SOURCE_PLUGIN_LOW);
   else
     ap->fib_index = ~0;
 #define _(N, i, n, s) \
@@ -623,7 +656,8 @@ snat_add_static_mapping_when_resolved (snat_main_t * sm,
                                        u32 vrf_id,
                                        snat_protocol_t proto,
                                        int addr_only,
-                                       int is_add)
+                                       int is_add,
+                                       u8 * tag)
 {
   snat_static_map_resolve_t *rp;
 
@@ -636,6 +670,7 @@ snat_add_static_mapping_when_resolved (snat_main_t * sm,
   rp->proto = proto;
   rp->addr_only = addr_only;
   rp->is_add = is_add;
+  rp->tag = vec_dup (tag);
 }
 
 /**
@@ -651,15 +686,21 @@ snat_add_static_mapping_when_resolved (snat_main_t * sm,
  * @param addr_only If 0 address port and pair mapping, otherwise address only.
  * @param sw_if_index External port instead of specific IP address.
  * @param is_add If 0 delete static mapping, otherwise add.
- * @param twice_nat If 1 translate external host address and port.
+ * @param twice_nat If value is TWICE_NAT then translate external host address
+ *                  and port.
+ *                  If value is TWICE_NAT_SELF then translate external host
+ *                  address and port whenever external host address equals
+ *                  local address of internal host.
  * @param out2in_only If 1 rule match only out2in direction
+ * @param tag - opaque string tag
  *
  * @returns
  */
 int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
                             u16 l_port, u16 e_port, u32 vrf_id, int addr_only,
                             u32 sw_if_index, snat_protocol_t proto, int is_add,
-                            u8 twice_nat, u8 out2in_only)
+                            twice_nat_type_t twice_nat, u8 out2in_only,
+                            u8 * tag)
 {
   snat_main_t * sm = &snat_main;
   snat_static_mapping_t *m;
@@ -671,30 +712,80 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
   snat_interface_t *interface;
   int i;
   snat_main_per_thread_data_t *tsm;
+  snat_user_key_t u_key;
+  snat_user_t *u;
+  dlist_elt_t * head, * elt;
+  u32 elt_index, head_index;
+  u32 ses_index;
+  u64 user_index;
+  snat_session_t * s;
+  snat_static_map_resolve_t *rp, *rp_match = 0;
 
   /* If the external address is a specific interface address */
   if (sw_if_index != ~0)
     {
       ip4_address_t * first_int_addr;
 
+      for (i = 0; i < vec_len (sm->to_resolve); i++)
+        {
+          rp = sm->to_resolve + i;
+          if (rp->sw_if_index != sw_if_index &&
+              rp->l_addr.as_u32 != l_addr.as_u32 &&
+              rp->vrf_id != vrf_id && rp->addr_only != addr_only)
+            continue;
+
+          if (!addr_only)
+            {
+              if (rp->l_port != l_port && rp->e_port != e_port && rp->proto != proto)
+                continue;
+            }
+
+          rp_match = rp;
+          break;
+        }
+
       /* Might be already set... */
       first_int_addr = ip4_interface_first_address
         (sm->ip4_main, sw_if_index, 0 /* just want the address*/);
 
-      /* DHCP resolution required? */
-      if (first_int_addr == 0)
+      if (is_add)
         {
+          if (rp_match)
+            return VNET_API_ERROR_VALUE_EXIST;
+
           snat_add_static_mapping_when_resolved
             (sm, l_addr, l_port, sw_if_index, e_port, vrf_id, proto,
-             addr_only,  is_add);
-          return 0;
+             addr_only,  is_add, tag);
+
+          /* DHCP resolution required? */
+          if (first_int_addr == 0)
+            {
+              return 0;
+            }
+          else
+            {
+              e_addr.as_u32 = first_int_addr->as_u32;
+              /* Identity mapping? */
+              if (l_addr.as_u32 == 0)
+                l_addr.as_u32 = e_addr.as_u32;
+            }
         }
-        else
+      else
         {
-          e_addr.as_u32 = first_int_addr->as_u32;
-          /* Identity mapping? */
-          if (l_addr.as_u32 == 0)
-            l_addr.as_u32 = e_addr.as_u32;
+          if (!rp_match)
+            return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+          vec_del1 (sm->to_resolve, i);
+
+          if (first_int_addr)
+            {
+              e_addr.as_u32 = first_int_addr->as_u32;
+              /* Identity mapping? */
+              if (l_addr.as_u32 == 0)
+                l_addr.as_u32 = e_addr.as_u32;
+            }
+          else
+            return 0;
         }
     }
 
@@ -731,6 +822,17 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
           vrf_id = sm->inside_vrf_id;
         }
 
+      if (!out2in_only)
+        {
+          m_key.addr = l_addr;
+          m_key.port = addr_only ? 0 : l_port;
+          m_key.protocol = addr_only ? 0 : proto;
+          m_key.fib_index = fib_index;
+          kv.key = m_key.as_u64;
+          if (!clib_bihash_search_8_8 (&sm->static_mapping_by_local, &kv, &value))
+            return VNET_API_ERROR_VALUE_EXIST;
+        }
+
       /* Find external address in allocated addresses and reserve port for
          address and port pair mapping when dynamic translations enabled */
       if (!(addr_only || sm->static_mapping_only || out2in_only))
@@ -764,12 +866,32 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
                 }
             }
           /* External address must be allocated */
-          if (!a)
-            return VNET_API_ERROR_NO_SUCH_ENTRY;
+          if (!a && (l_addr.as_u32 != e_addr.as_u32))
+            {
+              if (sw_if_index != ~0)
+                {
+                  for (i = 0; i < vec_len (sm->to_resolve); i++)
+                    {
+                      rp = sm->to_resolve + i;
+                      if (rp->addr_only)
+                         continue;
+                      if (rp->sw_if_index != sw_if_index &&
+                          rp->l_addr.as_u32 != l_addr.as_u32 &&
+                          rp->vrf_id != vrf_id && rp->l_port != l_port &&
+                          rp->e_port != e_port && rp->proto != proto)
+                        continue;
+
+                      vec_del1 (sm->to_resolve, i);
+                      break;
+                    }
+                }
+              return VNET_API_ERROR_NO_SUCH_ENTRY;
+            }
         }
 
       pool_get (sm->static_mappings, m);
       memset (m, 0, sizeof (*m));
+      m->tag = vec_dup (tag);
       m->local_addr = l_addr;
       m->external_addr = e_addr;
       m->addr_only = addr_only;
@@ -827,11 +949,61 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
             clib_warning ("out2in key add failed");
         }
 
+      /* Delete dynamic sessions matching local address (+ local port) */
+      if (!(sm->static_mapping_only))
+        {
+          u_key.addr = m->local_addr;
+          u_key.fib_index = m->fib_index;
+          kv.key = u_key.as_u64;
+          if (!clib_bihash_search_8_8 (&tsm->user_hash, &kv, &value))
+            {
+              user_index = value.value;
+              u = pool_elt_at_index (tsm->users, user_index);
+              if (u->nsessions)
+                {
+                  head_index = u->sessions_per_user_list_head_index;
+                  head = pool_elt_at_index (tsm->list_pool, head_index);
+                  elt_index = head->next;
+                  elt = pool_elt_at_index (tsm->list_pool, elt_index);
+                  ses_index = elt->value;
+                  while (ses_index != ~0)
+                    {
+                      s =  pool_elt_at_index (tsm->sessions, ses_index);
+                      elt = pool_elt_at_index (tsm->list_pool, elt->next);
+                      ses_index = elt->value;
+
+                      if (snat_is_session_static (s))
+                        continue;
+
+                      if (!addr_only)
+                        {
+                          if ((s->out2in.addr.as_u32 != e_addr.as_u32) &&
+                              (clib_net_to_host_u16 (s->out2in.port) != e_port))
+                            continue;
+                        }
+
+                      nat_free_session_data (sm, s, tsm - sm->per_thread_data);
+                      clib_dlist_remove (tsm->list_pool, s->per_user_index);
+                      pool_put_index (tsm->list_pool, s->per_user_index);
+                      pool_put (tsm->sessions, s);
+                      u->nsessions--;
+
+                      if (!addr_only)
+                        break;
+                    }
+                }
+            }
+        }
     }
   else
     {
       if (!m)
-        return VNET_API_ERROR_NO_SUCH_ENTRY;
+        {
+          if (sw_if_index != ~0)
+            return 0;
+          else
+            return VNET_API_ERROR_NO_SUCH_ENTRY;
+        }
 
       /* Free external address port */
       if (!(addr_only || sm->static_mapping_only || out2in_only))
@@ -902,14 +1074,6 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
       if (!(sm->static_mapping_only) ||
           (sm->static_mapping_only && sm->static_mapping_connection_tracking))
         {
-          snat_user_key_t u_key;
-          snat_user_t *u;
-          dlist_elt_t * head, * elt;
-          u32 elt_index, head_index;
-          u32 ses_index;
-          u64 user_index;
-          snat_session_t * s;
-
           u_key.addr = m->local_addr;
           u_key.fib_index = m->fib_index;
           kv.key = u_key.as_u64;
@@ -937,6 +1101,9 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
                             continue;
                         }
 
+                      if (is_lb_session (s))
+                        continue;
+
                       nat_free_session_data (sm, s, tsm - sm->per_thread_data);
                       clib_dlist_remove (tsm->list_pool, s->per_user_index);
                       pool_put_index (tsm->list_pool, s->per_user_index);
@@ -946,7 +1113,7 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
                       if (!addr_only)
                         break;
                     }
-                  if (addr_only)
+                  if (addr_only && (u->nstaticsessions == 0))
                     {
                       pool_put (tsm->users, u);
                       clib_bihash_add_del_8_8 (&tsm->user_hash, &kv, 0);
@@ -955,6 +1122,7 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
             }
         }
 
+      vec_free (m->tag);
       /* Delete static mapping from pool */
       pool_put (sm->static_mappings, m);
     }
@@ -983,10 +1151,38 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
   return 0;
 }
 
+static int lb_local_exists (nat44_lb_addr_port_t * local,
+                            ip4_address_t * e_addr, u16 e_port)
+{
+  snat_main_t *sm = &snat_main;
+  snat_static_mapping_t *m;
+  nat44_lb_addr_port_t *ap;
+
+  /* *INDENT-OFF* */
+  pool_foreach (m, sm->static_mappings,
+  ({
+      if (vec_len(m->locals))
+        {
+          if (m->external_port == e_port && m->external_addr.as_u32 == e_addr->as_u32)
+            continue;
+
+          vec_foreach (ap, m->locals)
+          {
+            if (ap->port == local->port && ap->addr.as_u32 == local->addr.as_u32)
+              return 1;
+          }
+        }
+  }));
+  /* *INDENT-ON* */
+
+  return 0;
+}
+
 int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                                      snat_protocol_t proto, u32 vrf_id,
                                      nat44_lb_addr_port_t *locals, u8 is_add,
-                                     u8 twice_nat, u8 out2in_only)
+                                     twice_nat_type_t twice_nat, u8 out2in_only,
+                                     u8 *tag)
 {
   snat_main_t * sm = &snat_main;
   snat_static_mapping_t *m;
@@ -1023,7 +1219,7 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
 
       fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4,
                                                      vrf_id,
-                                                     FIB_SOURCE_PLUGIN_HI);
+                                                     FIB_SOURCE_PLUGIN_LOW);
 
       /* Find external address in allocated addresses and reserve port for
          address and port pair mapping when dynamic translations enabled */
@@ -1064,6 +1260,7 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
 
       pool_get (sm->static_mappings, m);
       memset (m, 0, sizeof (*m));
+      m->tag = vec_dup (tag);
       m->external_addr = e_addr;
       m->addr_only = 0;
       m->vrf_id = vrf_id;
@@ -1135,7 +1332,7 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
       if (!m)
         return VNET_API_ERROR_NO_SUCH_ENTRY;
 
-      fib_table_unlock (m->fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_PLUGIN_HI);
+      fib_table_unlock (m->fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_PLUGIN_LOW);
 
       /* Free external address port */
       if (!(sm->static_mapping_only || out2in_only))
@@ -1202,12 +1399,15 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                 }
             }
 
-          m_key.port = clib_host_to_net_u16 (local->port);
-          kv.key = m_key.as_u64;
-          if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 0))
+          if (!lb_local_exists(local, &e_addr, e_port))
             {
-              clib_warning ("in2out key del failed");
-              return VNET_API_ERROR_UNSPECIFIED;
+              m_key.port = clib_host_to_net_u16 (local->port);
+              kv.key = m_key.as_u64;
+              if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 0))
+                {
+                  clib_warning ("in2out key del failed");
+                  return VNET_API_ERROR_UNSPECIFIED;
+                }
             }
           /* Delete sessions */
           u_key.addr = local->addr;
@@ -1229,6 +1429,9 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                       elt = pool_elt_at_index (tsm->list_pool, elt->next);
                       ses_index = elt->value;
 
+                      if (!(is_lb_session (s)))
+                        continue;
+
                       if ((s->in2out.addr.as_u32 != local->addr.as_u32) &&
                           (clib_net_to_host_u16 (s->in2out.port) != local->port))
                         continue;
@@ -1243,6 +1446,7 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
             }
         }
       vec_free(m->locals);
+      vec_free(m->tag);
 
       pool_put (sm->static_mappings, m);
     }
@@ -1287,7 +1491,7 @@ snat_del_address (snat_main_t *sm, ip4_address_t addr, u8 delete_sm,
                                             m->local_port, m->external_port,
                                             m->vrf_id, m->addr_only, ~0,
                                             m->proto, 0, m->twice_nat,
-                                            m->out2in_only);
+                                            m->out2in_only, m->tag);
       }));
     }
   else
@@ -1302,7 +1506,7 @@ snat_del_address (snat_main_t *sm, ip4_address_t addr, u8 delete_sm,
 
   if (a->fib_index != ~0)
     fib_table_unlock(a->fib_index, FIB_PROTOCOL_IP4,
-                     FIB_SOURCE_PLUGIN_HI);
+                     FIB_SOURCE_PLUGIN_LOW);
 
   /* Delete sessions using address */
   if (a->busy_tcp_ports || a->busy_udp_ports || a->busy_icmp_ports)
@@ -1376,6 +1580,12 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
   if (sm->out2in_dpo && !is_inside)
     return VNET_API_ERROR_UNSUPPORTED;
 
+  pool_foreach (i, sm->output_feature_interfaces,
+  ({
+    if (i->sw_if_index == sw_if_index)
+      return VNET_API_ERROR_VALUE_EXIST;
+  }));
+
   if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking))
     feature_name = is_inside ?  "nat44-in2out-fast" : "nat44-out2in-fast";
   else
@@ -1429,12 +1639,18 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
                                              sw_if_index, 0, 0, 0);
                 vnet_feature_enable_disable ("ip4-unicast", feature_name,
                                              sw_if_index, 1, 0, 0);
+                if (!is_inside)
+                  vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
+                                               sw_if_index, 1, 0, 0);
               }
             else
               {
                 vnet_feature_enable_disable ("ip4-unicast", feature_name,
                                              sw_if_index, 0, 0, 0);
                 pool_put (sm->interfaces, i);
+                if (is_inside)
+                  vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
+                                               sw_if_index, 0, 0, 0);
               }
           }
         else
@@ -1465,6 +1681,9 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
                                          sw_if_index, 0, 0, 0);
             vnet_feature_enable_disable ("ip4-unicast", feature_name,
                                          sw_if_index, 1, 0, 0);
+            if (!is_inside)
+              vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
+                                           sw_if_index, 0, 0, 0);
             goto set_flags;
           }
 
@@ -1480,21 +1699,21 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
   i->flags = 0;
   vnet_feature_enable_disable ("ip4-unicast", feature_name, sw_if_index, 1, 0, 0);
 
+  if (is_inside && !sm->out2in_dpo)
+    vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
+                                 sw_if_index, 1, 0, 0);
+
 set_flags:
   if (is_inside)
-    i->flags |= NAT_INTERFACE_FLAG_IS_INSIDE;
+    {
+      i->flags |= NAT_INTERFACE_FLAG_IS_INSIDE;
+      return 0;
+    }
   else
     i->flags |= NAT_INTERFACE_FLAG_IS_OUTSIDE;
 
   /* Add/delete external addresses to FIB */
 fib:
-  if (is_inside && !sm->out2in_dpo)
-    {
-      vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
-                                   sw_if_index, !is_del, 0, 0);
-      return 0;
-    }
-
   vec_foreach (ap, sm->addresses)
     snat_add_del_addr_to_fib(&ap->addr, 32, sw_if_index, !is_del);
 
@@ -1527,6 +1746,12 @@ int snat_interface_add_del_output_feature (u32 sw_if_index,
       (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)))
     return VNET_API_ERROR_UNSUPPORTED;
 
+  pool_foreach (i, sm->interfaces,
+  ({
+    if (i->sw_if_index == sw_if_index)
+      return VNET_API_ERROR_VALUE_EXIST;
+  }));
+
   if (is_inside)
     {
       vnet_feature_enable_disable ("ip4-unicast", "nat44-hairpin-dst",
@@ -1594,7 +1819,7 @@ fib:
 
   pool_foreach (m, sm->static_mappings,
   ({
-    if (!(m->addr_only))
+    if (!(m->addr_only)  || (m->local_addr.as_u32 == m->external_addr.as_u32))
       continue;
 
     snat_add_del_addr_to_fib(&m->external_addr, 32, sw_if_index, !is_del);
@@ -1638,6 +1863,15 @@ snat_ip4_add_del_interface_address_cb (ip4_main_t * im,
                                        u32 if_address_index,
                                        u32 is_delete);
 
+static void
+nat_ip4_add_del_addr_only_sm_cb (ip4_main_t * im,
+                                 uword opaque,
+                                 u32 sw_if_index,
+                                 ip4_address_t * address,
+                                 u32 address_length,
+                                 u32 if_address_index,
+                                 u32 is_delete);
+
 static int
 nat_alloc_addr_and_port_default (snat_address_t * addresses,
                                  u32 fib_index,
@@ -1716,6 +1950,11 @@ static clib_error_t * snat_init (vlib_main_t * vm)
 
   vec_add1 (im->add_del_interface_address_callbacks, cb4);
 
+  cb4.function = nat_ip4_add_del_addr_only_sm_cb;
+  cb4.function_opaque = 0;
+
+  vec_add1 (im->add_del_interface_address_callbacks, cb4);
+
   nat_dpo_module_init ();
 
   /* Init IPFIX logging */
@@ -1728,6 +1967,8 @@ static clib_error_t * snat_init (vlib_main_t * vm)
 
   dslite_init(vm);
 
+  nat66_init();
+
   /* Init virtual fragmenentation reassembly */
   return nat_reass_init(vm);
 }
@@ -1775,6 +2016,7 @@ void snat_free_outside_address_and_port (snat_address_t * addresses,
  *                    address.
  * @param is_addr_only If matched mapping is address only
  * @param twice_nat If matched mapping is twice NAT.
+ * @param lb If matched mapping is load-balanced.
  *
  * @returns 0 if match found otherwise 1.
  */
@@ -1783,7 +2025,8 @@ int snat_static_mapping_match (snat_main_t * sm,
                                snat_session_key_t * mapping,
                                u8 by_external,
                                u8 *is_addr_only,
-                               u8 *twice_nat)
+                               twice_nat_type_t *twice_nat,
+                               u8 *lb)
 {
   clib_bihash_kv_8_8_t kv, value;
   snat_static_mapping_t *m;
@@ -1854,6 +2097,9 @@ int snat_static_mapping_match (snat_main_t * sm,
   if (PREDICT_FALSE(twice_nat != 0))
     *twice_nat = m->twice_nat;
 
+  if (PREDICT_FALSE(lb != 0))
+    *lb = vec_len (m->locals) > 0;
+
   return 0;
 }
 
@@ -2242,12 +2488,14 @@ static clib_error_t *
 snat_config (vlib_main_t * vm, unformat_input_t * input)
 {
   snat_main_t * sm = &snat_main;
+  nat66_main_t * nm = &nat66_main;
   u32 translation_buckets = 1024;
   u32 translation_memory_size = 128<<20;
   u32 user_buckets = 128;
   u32 user_memory_size = 64<<20;
   u32 max_translations_per_user = 100;
   u32 outside_vrf_id = 0;
+  u32 outside_ip6_vrf_id = 0;
   u32 inside_vrf_id = 0;
   u32 static_mapping_buckets = 1024;
   u32 static_mapping_memory_size = 64<<20;
@@ -2280,6 +2528,9 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
       else if (unformat (input, "outside VRF id %d",
                          &outside_vrf_id))
         ;
+      else if (unformat (input, "outside ip6 VRF id %d",
+                         &outside_ip6_vrf_id))
+        ;
       else if (unformat (input, "inside VRF id %d",
                          &inside_vrf_id))
         ;
@@ -2323,6 +2574,10 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
   sm->outside_fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4,
                                                              outside_vrf_id,
                                                              FIB_SOURCE_PLUGIN_HI);
+  nm->outside_vrf_id = outside_ip6_vrf_id;
+  nm->outside_fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP6,
+                                                             outside_ip6_vrf_id,
+                                                             FIB_SOURCE_PLUGIN_HI);
   sm->inside_vrf_id = inside_vrf_id;
   sm->inside_fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4,
                                                             inside_vrf_id,
@@ -2451,12 +2706,18 @@ u8 * format_snat_session (u8 * s, va_list * args)
   else
     {
       if (sess->ext_host_addr.as_u32)
-          s = format (s, "       external host %U\n",
-                      format_ip4_address, &sess->ext_host_addr);
+          s = format (s, "       external host %U:%u\n",
+                      format_ip4_address, &sess->ext_host_addr,
+                      clib_net_to_host_u16 (sess->ext_host_port));
     }
   s = format (s, "       last heard %.2f\n", sess->last_heard);
   s = format (s, "       total pkts %d, total bytes %lld\n",
               sess->total_pkts, sess->total_bytes);
+  if (sess->in2out.protocol == SNAT_PROTOCOL_TCP)
+    {
+      s = format (s, "       state %s\n",
+                  sess->state == SNAT_SESSION_TCP_CLOSED ? "closed" : "open");
+    }
   if (snat_is_session_static (sess))
     s = format (s, "       static translation\n");
   else
@@ -2515,10 +2776,13 @@ u8 * format_snat_static_mapping (u8 * s, va_list * args)
   nat44_lb_addr_port_t *local;
 
   if (m->addr_only)
-      s = format (s, "local %U external %U vrf %d %s",
+      s = format (s, "local %U external %U vrf %d %s %s",
                   format_ip4_address, &m->local_addr,
                   format_ip4_address, &m->external_addr,
-                  m->vrf_id, m->twice_nat ? "twice-nat" : "");
+                  m->vrf_id,
+                  m->twice_nat == TWICE_NAT ? "twice-nat" :
+                  m->twice_nat == TWICE_NAT_SELF ? "self-twice-nat" : "",
+                  m->out2in_only ? "out2in-only" : "");
   else
    {
       if (vec_len (m->locals))
@@ -2527,7 +2791,8 @@ u8 * format_snat_static_mapping (u8 * s, va_list * args)
                       format_snat_protocol, m->proto,
                       m->vrf_id,
                       format_ip4_address, &m->external_addr, m->external_port,
-                      m->twice_nat ? "twice-nat" : "",
+                      m->twice_nat == TWICE_NAT ? "twice-nat" :
+                      m->twice_nat == TWICE_NAT_SELF ? "self-twice-nat" : "",
                       m->out2in_only ? "out2in-only" : "");
           vec_foreach (local, m->locals)
             s = format (s, "\n  local %U:%d probability %d\%",
@@ -2539,7 +2804,9 @@ u8 * format_snat_static_mapping (u8 * s, va_list * args)
                     format_snat_protocol, m->proto,
                     format_ip4_address, &m->local_addr, m->local_port,
                     format_ip4_address, &m->external_addr, m->external_port,
-                    m->vrf_id, m->twice_nat ? "twice-nat" : "",
+                    m->vrf_id,
+                    m->twice_nat == TWICE_NAT ? "twice-nat" :
+                    m->twice_nat == TWICE_NAT_SELF ? "self-twice-nat" : "",
                     m->out2in_only ? "out2in-only" : "");
    }
   return s;
@@ -2553,16 +2820,14 @@ u8 * format_snat_static_map_to_resolve (u8 * s, va_list * args)
   if (m->addr_only)
       s = format (s, "local %U external %U vrf %d",
                   format_ip4_address, &m->l_addr,
-                  format_vnet_sw_interface_name, vnm,
-                  vnet_get_sw_interface (vnm, m->sw_if_index),
+                  format_vnet_sw_if_index_name, vnm, m->sw_if_index,
                   m->vrf_id);
   else
       s = format (s, "%U local %U:%d external %U:%d vrf %d",
                   format_snat_protocol, m->proto,
                   format_ip4_address, &m->l_addr, m->l_port,
-                  format_vnet_sw_interface_name, vnm,
-                  vnet_get_sw_interface (vnm, m->sw_if_index), m->e_port,
-                  m->vrf_id);
+                  format_vnet_sw_if_index_name, vnm, m->sw_if_index,
+                  m->e_port, m->vrf_id);
 
   return s;
 }
@@ -2596,6 +2861,77 @@ u8 * format_det_map_ses (u8 * s, va_list * args)
   return s;
 }
 
+static void
+nat_ip4_add_del_addr_only_sm_cb (ip4_main_t * im,
+                                 uword opaque,
+                                 u32 sw_if_index,
+                                 ip4_address_t * address,
+                                 u32 address_length,
+                                 u32 if_address_index,
+                                 u32 is_delete)
+{
+  snat_main_t *sm = &snat_main;
+  snat_static_map_resolve_t *rp;
+  snat_static_mapping_t *m;
+  snat_session_key_t m_key;
+  clib_bihash_kv_8_8_t kv, value;
+  int i, rv;
+  ip4_address_t l_addr;
+
+  for (i = 0; i < vec_len (sm->to_resolve); i++)
+    {
+      rp = sm->to_resolve + i;
+      if (rp->addr_only == 0)
+        continue;
+      if (rp->sw_if_index == sw_if_index)
+        goto match;
+    }
+
+  return;
+
+match:
+  m_key.addr.as_u32 = address->as_u32;
+  m_key.port = rp->addr_only ? 0 : rp->e_port;
+  m_key.protocol = rp->addr_only ? 0 : rp->proto;
+  m_key.fib_index = sm->outside_fib_index;
+  kv.key = m_key.as_u64;
+  if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+    m = 0;
+  else
+    m = pool_elt_at_index (sm->static_mappings, value.value);
+
+  if (!is_delete)
+    {
+      /* Don't trip over lease renewal, static config */
+      if (m)
+        return;
+    }
+  else
+    {
+      if (!m)
+        return;
+    }
+
+  /* Indetity mapping? */
+  if (rp->l_addr.as_u32 == 0)
+    l_addr.as_u32 = address[0].as_u32;
+  else
+    l_addr.as_u32 = rp->l_addr.as_u32;
+  /* Add the static mapping */
+  rv = snat_add_static_mapping (l_addr,
+                                address[0],
+                                rp->l_port,
+                                rp->e_port,
+                                rp->vrf_id,
+                                rp->addr_only,
+                                ~0 /* sw_if_index */,
+                                rp->proto,
+                                !is_delete,
+                                0, 0, rp->tag);
+  if (rv)
+    clib_warning ("snat_add_static_mapping returned %d", rv);
+}
+
 static void
 snat_ip4_add_del_interface_address_cb (ip4_main_t * im,
                                        uword opaque,
@@ -2607,7 +2943,6 @@ snat_ip4_add_del_interface_address_cb (ip4_main_t * im,
 {
   snat_main_t *sm = &snat_main;
   snat_static_map_resolve_t *rp;
-  u32 *indices_to_delete = 0;
   ip4_address_t l_addr;
   int i, j;
   int rv;
@@ -2643,6 +2978,8 @@ match:
       for (j = 0; j < vec_len (sm->to_resolve); j++)
         {
           rp = sm->to_resolve + j;
+          if (rp->addr_only)
+            continue;
           /* On this interface? */
           if (rp->sw_if_index == sw_if_index)
             {
@@ -2661,21 +2998,12 @@ match:
                                             ~0 /* sw_if_index */,
                                             rp->proto,
                                             rp->is_add,
-                                            0, 0);
+                                            0, 0, rp->tag);
               if (rv)
                 clib_warning ("snat_add_static_mapping returned %d",
                               rv);
-              vec_add1 (indices_to_delete, j);
             }
         }
-      /* If we resolved any of the outstanding static mappings */
-      if (vec_len(indices_to_delete))
-        {
-          /* Delete them */
-          for (j = vec_len(indices_to_delete)-1; j >= 0; j--)
-            vec_delete(sm->to_resolve, 1, j);
-          vec_free(indices_to_delete);
-        }
       return;
     }
   else
@@ -2767,7 +3095,7 @@ nat44_del_session (snat_main_t *sm, ip4_address_t *addr, u16 port,
   snat_user_t *u;
 
   ip.dst_address.as_u32 = ip.src_address.as_u32 = addr->as_u32;
-  if (sm->num_workers)
+  if (sm->num_workers > 1)
     tsm =
       vec_elt_at_index (sm->per_thread_data,
                        sm->worker_in2out_cb (&ip, fib_index));