NAT44: endpoint dependent mode (VPP-1273) 30/13030/4
authorMatus Fabian <matfabia@cisco.com>
Wed, 13 Jun 2018 12:39:07 +0000 (05:39 -0700)
committerOle Trøan <otroan@employees.org>
Fri, 15 Jun 2018 06:53:24 +0000 (06:53 +0000)
To enable NAT plugin endpoint dependent mode add following to statrup config:
nat { endpoint-dependent }

Enable endpoint dependent filtering and mapping for all sessions.
Move some existing functionality such as service load balancing, twice nat,
out2in-only static mappings and unknown protocol dynamic translations, which
use endpoint dependent lookup hash tables before. Basically split to vanilla
NAT44 and extra features NAT44.

Change-Id: I3925eb5ddcc8f1ec4cf6af4e2a618a7ec7aa9735
Signed-off-by: Matus Fabian <matfabia@cisco.com>
src/plugins/nat/in2out.c
src/plugins/nat/nat.c
src/plugins/nat/nat.h
src/plugins/nat/nat44_cli.c
src/plugins/nat/nat_api.c
src/plugins/nat/nat_inlines.h
src/plugins/nat/out2in.c
src/scripts/vnet/nat44_lb [new file with mode: 0644]
src/scripts/vnet/nat44_static_with_port
test/test_nat.py

index c724553..7c2977c 100755 (executable)
@@ -116,7 +116,13 @@ vlib_node_registration_t snat_hairpin_dst_node;
 vlib_node_registration_t snat_hairpin_src_node;
 vlib_node_registration_t nat44_hairpinning_node;
 vlib_node_registration_t nat44_in2out_reass_node;
-
+vlib_node_registration_t nat44_ed_in2out_node;
+vlib_node_registration_t nat44_ed_in2out_slowpath_node;
+vlib_node_registration_t nat44_ed_in2out_output_node;
+vlib_node_registration_t nat44_ed_in2out_output_slowpath_node;
+vlib_node_registration_t nat44_ed_hairpin_dst_node;
+vlib_node_registration_t nat44_ed_hairpin_src_node;
+vlib_node_registration_t nat44_ed_hairpinning_node;
 
 #define foreach_snat_in2out_error                       \
 _(UNSUPPORTED_PROTOCOL, "Unsupported protocol")         \
@@ -445,115 +451,6 @@ snat_in2out_error_t icmp_get_key(ip4_header_t *ip0,
   return -1; /* success */
 }
 
-static_always_inline int
-icmp_get_ed_key(ip4_header_t *ip0, nat_ed_ses_key_t *p_key0)
-{
-  icmp46_header_t *icmp0;
-  nat_ed_ses_key_t key0;
-  icmp_echo_header_t *echo0, *inner_echo0 = 0;
-  ip4_header_t *inner_ip0 = 0;
-  void *l4_header = 0;
-  icmp46_header_t *inner_icmp0;
-
-  icmp0 = (icmp46_header_t *) ip4_next_header (ip0);
-  echo0 = (icmp_echo_header_t *)(icmp0+1);
-
-  if (!icmp_is_error_message (icmp0))
-    {
-      key0.proto = IP_PROTOCOL_ICMP;
-      key0.l_addr = ip0->src_address;
-      key0.r_addr = ip0->dst_address;
-      key0.l_port = key0.r_port = echo0->identifier;
-    }
-  else
-    {
-      inner_ip0 = (ip4_header_t *)(echo0+1);
-      l4_header = ip4_next_header (inner_ip0);
-      key0.proto = inner_ip0->protocol;
-      key0.r_addr = inner_ip0->src_address;
-      key0.l_addr = inner_ip0->dst_address;
-      switch (ip_proto_to_snat_proto (inner_ip0->protocol))
-        {
-        case SNAT_PROTOCOL_ICMP:
-          inner_icmp0 = (icmp46_header_t*)l4_header;
-          inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1);
-          key0.r_port = key0.l_port = inner_echo0->identifier;
-          break;
-        case SNAT_PROTOCOL_UDP:
-        case SNAT_PROTOCOL_TCP:
-          key0.l_port = ((tcp_udp_header_t*)l4_header)->dst_port;
-          key0.r_port = ((tcp_udp_header_t*)l4_header)->src_port;
-          break;
-        default:
-          return SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL;
-        }
-    }
-  *p_key0 = key0;
-  return 0;
-}
-
-static inline int
-nat_not_translate_output_feature_fwd (snat_main_t * sm, ip4_header_t * ip,
-                                      u32 thread_index)
-{
-  nat_ed_ses_key_t key;
-  clib_bihash_kv_16_8_t kv, value;
-  udp_header_t *udp;
-  snat_session_t *s = 0;
-  f64 now = vlib_time_now (sm->vlib_main);
-
-  if (!sm->forwarding_enabled)
-    return 0;
-
-  if (ip->protocol == IP_PROTOCOL_ICMP)
-    {
-      if (icmp_get_ed_key (ip, &key))
-        return 0;
-    }
-  else if (ip->protocol == IP_PROTOCOL_UDP || ip->protocol == IP_PROTOCOL_TCP)
-    {
-      udp = ip4_next_header(ip);
-      key.l_addr = ip->src_address;
-      key.r_addr = ip->dst_address;
-      key.proto = ip->protocol;
-      key.r_port = udp->dst_port;
-      key.l_port = udp->src_port;
-    }
-  else
-    {
-      key.l_addr = ip->src_address;
-      key.r_addr = ip->dst_address;
-      key.proto = ip->protocol;
-      key.l_port = key.r_port = 0;
-    }
-  key.fib_index = 0;
-  kv.key[0] = key.as_u64[0];
-  kv.key[1] = key.as_u64[1];
-
-  if (!clib_bihash_search_16_8 (&sm->in2out_ed, &kv, &value))
-    {
-      s = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, value.value);
-      if (is_fwd_bypass_session (s))
-        {
-          if (ip->protocol == IP_PROTOCOL_TCP)
-            {
-              tcp_header_t *tcp = ip4_next_header(ip);
-              if (nat44_set_tcp_session_state_i2o (sm, s, tcp, thread_index))
-                return 1;
-            }
-          /* Per-user LRU list maintenance */
-          nat44_session_update_lru (sm, s, thread_index);
-          /* Accounting */
-          nat44_session_update_counters (s, now, 0);
-          return 1;
-        }
-      else
-        return 0;
-    }
-
-  return 0;
-}
-
 /**
  * Get address and port values to be used for ICMP packet translation
  * and create session if needed
@@ -604,8 +501,8 @@ u32 icmp_match_in2out_slow(snat_main_t *sm, vlib_node_runtime_t *node,
     {
       if (vnet_buffer(b0)->sw_if_index[VLIB_TX] != ~0)
         {
-          if (PREDICT_FALSE(nat_not_translate_output_feature(sm,
-              ip0, SNAT_PROTOCOL_ICMP, key0.port, key0.port, thread_index, sw_if_index0)))
+          if (PREDICT_FALSE(nat_not_translate_output_feature(sm, ip0,
+              key0.protocol, key0.port, key0.port, thread_index, sw_if_index0)))
             {
               dont_translate = 1;
               goto out;
@@ -645,34 +542,8 @@ u32 icmp_match_in2out_slow(snat_main_t *sm, vlib_node_runtime_t *node,
           goto out;
         }
 
-      if (PREDICT_FALSE (value0.value == ~0ULL))
-        {
-          nat_ed_ses_key_t key;
-          clib_bihash_kv_16_8_t s_kv, s_value;
-
-          key.as_u64[0] = 0;
-          key.as_u64[1] = 0;
-          if (icmp_get_ed_key (ip0, &key))
-            {
-              b0->error = node->errors[SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL];
-              next0 = SNAT_IN2OUT_NEXT_DROP;
-              goto out;
-            }
-          key.fib_index = rx_fib_index0;
-          s_kv.key[0] = key.as_u64[0];
-          s_kv.key[1] = key.as_u64[1];
-          if (!clib_bihash_search_16_8 (&sm->in2out_ed, &s_kv, &s_value))
-            s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
-                                    s_value.value);
-          else
-           {
-              next0 = SNAT_IN2OUT_NEXT_DROP;
-              goto out;
-           }
-        }
-      else
-        s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
-                                value0.value);
+      s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                              value0.value);
     }
 
 out:
@@ -910,7 +781,8 @@ snat_hairpinning (snat_main_t *sm,
                   ip4_header_t * ip0,
                   udp_header_t * udp0,
                   tcp_header_t * tcp0,
-                  u32 proto0)
+                  u32 proto0,
+                  int is_ed)
 {
   snat_session_key_t key0, sm0;
   snat_session_t * s0;
@@ -918,6 +790,7 @@ snat_hairpinning (snat_main_t *sm,
   ip_csum_t sum0;
   u32 new_dst_addr0 = 0, old_dst_addr0, ti = 0, si;
   u16 new_dst_port0, old_dst_port0;
+  int rv;
 
   key0.addr = ip0->dst_address;
   key0.port = udp0->dst_port;
@@ -940,15 +813,29 @@ snat_hairpinning (snat_main_t *sm,
       else
         ti = sm->num_workers;
 
-      if (!clib_bihash_search_8_8 (&sm->per_thread_data[ti].out2in, &kv0, &value0))
+      if (is_ed)
+        {
+          clib_bihash_kv_16_8_t ed_kv, ed_value;
+          make_ed_kv (&ed_kv, &ip0->dst_address, &ip0->src_address,
+                      ip0->protocol, sm->outside_fib_index, udp0->dst_port,
+                      udp0->src_port);
+          rv = clib_bihash_search_16_8 (&sm->per_thread_data[ti].out2in_ed,
+                                        &ed_kv, &ed_value);
+          si = ed_value.value;
+        }
+      else
         {
+          rv = clib_bihash_search_8_8 (&sm->per_thread_data[ti].out2in, &kv0,
+                                       &value0);
           si = value0.value;
-
-          s0 = pool_elt_at_index (sm->per_thread_data[ti].sessions, si);
-          new_dst_addr0 = s0->in2out.addr.as_u32;
-          new_dst_port0 = s0->in2out.port;
-          vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index;
         }
+      if (rv)
+        return 0;
+
+      s0 = pool_elt_at_index (sm->per_thread_data[ti].sessions, si);
+      new_dst_addr0 = s0->in2out.addr.as_u32;
+      new_dst_port0 = s0->in2out.port;
+      vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index;
     }
 
   /* Destination is behind the same NAT, use internal address and port */
@@ -999,13 +886,15 @@ static inline void
 snat_icmp_hairpinning (snat_main_t *sm,
                        vlib_buffer_t * b0,
                        ip4_header_t * ip0,
-                       icmp46_header_t * icmp0)
+                       icmp46_header_t * icmp0,
+                       int is_ed)
 {
   snat_session_key_t key0, sm0;
   clib_bihash_kv_8_8_t kv0, value0;
   u32 new_dst_addr0 = 0, old_dst_addr0, si, ti = 0;
   ip_csum_t sum0;
   snat_session_t *s0;
+  int rv;
 
   if (!icmp_is_error_message (icmp0))
     {
@@ -1023,8 +912,22 @@ snat_icmp_hairpinning (snat_main_t *sm,
         ti = sm->num_workers;
 
       /* Check if destination is in active sessions */
-      if (clib_bihash_search_8_8 (&sm->per_thread_data[ti].out2in, &kv0,
-                                  &value0))
+      if (is_ed)
+        {
+          clib_bihash_kv_16_8_t ed_kv, ed_value;
+          make_ed_kv (&ed_kv, &ip0->dst_address, &ip0->src_address,
+                      IP_PROTOCOL_ICMP, sm->outside_fib_index, icmp_id0, 0);
+          rv = clib_bihash_search_16_8 (&sm->per_thread_data[ti].out2in_ed,
+                                        &ed_kv, &ed_value);
+          si = ed_value.value;
+        }
+      else
+        {
+          rv = clib_bihash_search_8_8 (&sm->per_thread_data[ti].out2in, &kv0,
+                                       &value0);
+          si = value0.value;
+        }
+      if (rv)
         {
           /* or static mappings */
           if (!snat_static_mapping_match(sm, key0, &sm0, 1, 0, 0, 0))
@@ -1035,8 +938,6 @@ snat_icmp_hairpinning (snat_main_t *sm,
         }
       else
         {
-          si = value0.value;
-
           s0 = pool_elt_at_index (sm->per_thread_data[ti].sessions, si);
           new_dst_addr0 = s0->in2out.addr.as_u32;
           vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index;
@@ -1080,7 +981,7 @@ static inline u32 icmp_in2out_slow_path (snat_main_t *sm,
     {
       /* Hairpinning */
       if (vnet_buffer(b0)->sw_if_index[VLIB_TX] == 0)
-        snat_icmp_hairpinning(sm, b0, ip0, icmp0);
+        snat_icmp_hairpinning(sm, b0, ip0, icmp0, sm->endpoint_dependent);
       /* Accounting */
       nat44_session_update_counters (s0, now,
                                      vlib_buffer_length_in_chain (sm->vlib_main, b0));
@@ -1089,438 +990,99 @@ static inline u32 icmp_in2out_slow_path (snat_main_t *sm,
     }
   return next0;
 }
+
 static inline void
-snat_hairpinning_unknown_proto (snat_main_t *sm,
-                                vlib_buffer_t * b,
-                                ip4_header_t * ip)
+nat_hairpinning_sm_unknown_proto (snat_main_t * sm,
+                                  vlib_buffer_t * b,
+                                  ip4_header_t * ip)
 {
-  u32 old_addr, new_addr = 0, ti = 0;
   clib_bihash_kv_8_8_t kv, value;
-  clib_bihash_kv_16_8_t s_kv, s_value;
-  nat_ed_ses_key_t key;
-  snat_session_key_t m_key;
   snat_static_mapping_t *m;
+  u32 old_addr, new_addr;
   ip_csum_t sum;
-  snat_session_t *s;
 
-  old_addr = ip->dst_address.as_u32;
-  key.l_addr.as_u32 = ip->dst_address.as_u32;
-  key.r_addr.as_u32 = ip->src_address.as_u32;
-  key.fib_index = sm->outside_fib_index;
-  key.proto = ip->protocol;
-  key.r_port = 0;
-  key.l_port = 0;
-  s_kv.key[0] = key.as_u64[0];
-  s_kv.key[1] = key.as_u64[1];
-  if (clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-    {
-      m_key.addr = ip->dst_address;
-      m_key.fib_index = sm->outside_fib_index;
-      m_key.port = 0;
-      m_key.protocol = 0;
-      kv.key = m_key.as_u64;
-      if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
-        return;
+  make_sm_kv (&kv, &ip->dst_address, 0, sm->outside_fib_index, 0);
+  if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+    return;
 
-      m = pool_elt_at_index (sm->static_mappings, value.value);
-      if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
-        vnet_buffer(b)->sw_if_index[VLIB_TX] = m->fib_index;
-      new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32;
-    }
-  else
-    {
-      if (sm->num_workers > 1)
-        ti = sm->worker_out2in_cb (ip, sm->outside_fib_index);
-      else
-        ti = sm->num_workers;
+  m = pool_elt_at_index (sm->static_mappings, value.value);
 
-      s = pool_elt_at_index (sm->per_thread_data[ti].sessions, s_value.value);
-      if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
-        vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index;
-      new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32;
-    }
+  old_addr = ip->dst_address.as_u32;
+  new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32;
   sum = ip->checksum;
   sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
   ip->checksum = ip_csum_fold (sum);
+
+  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
+    vnet_buffer(b)->sw_if_index[VLIB_TX] = m->fib_index;
 }
 
-static snat_session_t *
-snat_in2out_unknown_proto (snat_main_t *sm,
-                           vlib_buffer_t * b,
-                           ip4_header_t * ip,
-                           u32 rx_fib_index,
-                           u32 thread_index,
-                           f64 now,
-                           vlib_main_t * vm,
-                           vlib_node_runtime_t * node)
+static int
+nat_in2out_sm_unknown_proto (snat_main_t *sm,
+                             vlib_buffer_t * b,
+                             ip4_header_t * ip,
+                             u32 rx_fib_index)
 {
   clib_bihash_kv_8_8_t kv, value;
-  clib_bihash_kv_16_8_t s_kv, s_value;
   snat_static_mapping_t *m;
   snat_session_key_t m_key;
-  u32 old_addr, new_addr = 0;
+  u32 old_addr, new_addr;
   ip_csum_t sum;
-  snat_user_t *u;
-  dlist_elt_t *head, *elt;
-  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  u32 elt_index, head_index, ses_index;
-  snat_session_t * s;
-  nat_ed_ses_key_t key;
-  u32 address_index = ~0;
-  int i;
-  u8 is_sm = 0;
+
+  m_key.addr = ip->src_address;
+  m_key.port = 0;
+  m_key.protocol = 0;
+  m_key.fib_index = rx_fib_index;
+  kv.key = m_key.as_u64;
+  if (clib_bihash_search_8_8 (&sm->static_mapping_by_local, &kv, &value))
+    return 1;
+
+  m = pool_elt_at_index (sm->static_mappings, value.value);
 
   old_addr = ip->src_address.as_u32;
+  new_addr = ip->src_address.as_u32 = m->external_addr.as_u32;
+  sum = ip->checksum;
+  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, src_address);
+  ip->checksum = ip_csum_fold (sum);
 
-  key.l_addr = ip->src_address;
-  key.r_addr = ip->dst_address;
-  key.fib_index = rx_fib_index;
-  key.proto = ip->protocol;
-  key.l_port = 0;
-  key.r_port = 0;
-  s_kv.key[0] = key.as_u64[0];
-  s_kv.key[1] = key.as_u64[1];
 
-  if (!clib_bihash_search_16_8 (&sm->in2out_ed, &s_kv, &s_value))
+  /* Hairpinning */
+  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
     {
-      s = pool_elt_at_index (tsm->sessions, s_value.value);
-      new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32;
+      nat_hairpinning_sm_unknown_proto (sm, b, ip);
+      vnet_buffer(b)->sw_if_index[VLIB_TX] = sm->outside_fib_index;
     }
-  else
-    {
-      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
-        {
-          b->error = node->errors[SNAT_IN2OUT_ERROR_MAX_SESSIONS_EXCEEDED];
-          nat_ipfix_logging_max_sessions(sm->max_translations);
-          nat_log_notice ("maximum sessions exceeded");
-          return 0;
-        }
 
-      u = nat_user_get_or_create (sm, &ip->src_address, rx_fib_index,
-                                  thread_index);
-      if (!u)
-        {
-          nat_log_warn ("create NAT user failed");
-          return 0;
-        }
+  return 0;
+}
 
-      m_key.addr = ip->src_address;
-      m_key.port = 0;
-      m_key.protocol = 0;
-      m_key.fib_index = rx_fib_index;
-      kv.key = m_key.as_u64;
+static inline uword
+snat_in2out_node_fn_inline (vlib_main_t * vm,
+                            vlib_node_runtime_t * node,
+                            vlib_frame_t * frame, int is_slow_path,
+                            int is_output_feature)
+{
+  u32 n_left_from, * from, * to_next;
+  snat_in2out_next_t next_index;
+  u32 pkts_processed = 0;
+  snat_main_t * sm = &snat_main;
+  f64 now = vlib_time_now (vm);
+  u32 stats_node_index;
+  u32 thread_index = vlib_get_thread_index ();
 
-      /* Try to find static mapping first */
-      if (!clib_bihash_search_8_8 (&sm->static_mapping_by_local, &kv, &value))
-        {
-          m = pool_elt_at_index (sm->static_mappings, value.value);
-          new_addr = ip->src_address.as_u32 = m->external_addr.as_u32;
-          is_sm = 1;
-          goto create_ses;
-        }
-      /* Fallback to 3-tuple key */
-      else
-        {
-          /* Choose same out address as for TCP/UDP session to same destination */
-          head_index = u->sessions_per_user_list_head_index;
-          head = pool_elt_at_index (tsm->list_pool, head_index);
-          elt_index = head->next;
-         if (PREDICT_FALSE (elt_index == ~0))
-           ses_index = ~0;
-         else
-           {
-             elt = pool_elt_at_index (tsm->list_pool, elt_index);
-             ses_index = elt->value;
-           }
+  stats_node_index = is_slow_path ? snat_in2out_slowpath_node.index :
+    snat_in2out_node.index;
 
-          while (ses_index != ~0)
-            {
-              s =  pool_elt_at_index (tsm->sessions, ses_index);
-              elt_index = elt->next;
-              elt = pool_elt_at_index (tsm->list_pool, elt_index);
-              ses_index = elt->value;
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
 
-              if (s->ext_host_addr.as_u32 == ip->dst_address.as_u32)
-                {
-                  new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32;
-                  address_index = s->outside_address_index;
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
 
-                  key.fib_index = sm->outside_fib_index;
-                  key.l_addr.as_u32 = new_addr;
-                  s_kv.key[0] = key.as_u64[0];
-                  s_kv.key[1] = key.as_u64[1];
-                  if (clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-                    break;
-
-                  goto create_ses;
-                }
-            }
-          key.fib_index = sm->outside_fib_index;
-          for (i = 0; i < vec_len (sm->addresses); i++)
-            {
-              key.l_addr.as_u32 = sm->addresses[i].addr.as_u32;
-              s_kv.key[0] = key.as_u64[0];
-              s_kv.key[1] = key.as_u64[1];
-              if (clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-                {
-                  new_addr = ip->src_address.as_u32 = key.l_addr.as_u32;
-                  address_index = i;
-                  goto create_ses;
-                }
-            }
-          return 0;
-        }
-
-create_ses:
-      s = nat_session_alloc_or_recycle (sm, u, thread_index);
-      if (!s)
-        {
-          nat_log_warn ("create NAT session failed");
-          return 0;
-        }
-
-      s->ext_host_addr.as_u32 = ip->dst_address.as_u32;
-      s->flags |= SNAT_SESSION_FLAG_UNKNOWN_PROTO;
-      s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
-      s->outside_address_index = address_index;
-      s->out2in.addr.as_u32 = new_addr;
-      s->out2in.fib_index = sm->outside_fib_index;
-      s->in2out.addr.as_u32 = old_addr;
-      s->in2out.fib_index = rx_fib_index;
-      s->in2out.port = s->out2in.port = ip->protocol;
-      if (is_sm)
-       s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
-      user_session_increment (sm, u, is_sm);
-
-      /* Add to lookup tables */
-      key.l_addr.as_u32 = old_addr;
-      key.r_addr = ip->dst_address;
-      key.proto = ip->protocol;
-      key.fib_index = rx_fib_index;
-      s_kv.key[0] = key.as_u64[0];
-      s_kv.key[1] = key.as_u64[1];
-      s_kv.value = s - tsm->sessions;
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &s_kv, 1))
-        nat_log_notice ("in2out key add failed");
-
-      key.l_addr.as_u32 = new_addr;
-      key.fib_index = sm->outside_fib_index;
-      s_kv.key[0] = key.as_u64[0];
-      s_kv.key[1] = key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &s_kv, 1))
-        nat_log_notice ("out2in key add failed");
-  }
-
-  /* Update IP checksum */
-  sum = ip->checksum;
-  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, src_address);
-  ip->checksum = ip_csum_fold (sum);
-
-  /* Accounting */
-  nat44_session_update_counters (s, now, vlib_buffer_length_in_chain (vm, b));
-  /* Per-user LRU list maintenance */
-  nat44_session_update_lru (sm, s, thread_index);
-
-  /* Hairpinning */
-  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
-    snat_hairpinning_unknown_proto(sm, b, ip);
-
-  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
-    vnet_buffer(b)->sw_if_index[VLIB_TX] = sm->outside_fib_index;
-
-  return s;
-}
-
-static snat_session_t *
-snat_in2out_lb (snat_main_t *sm,
-                vlib_buffer_t * b,
-                ip4_header_t * ip,
-                u32 rx_fib_index,
-                u32 thread_index,
-                f64 now,
-                vlib_main_t * vm,
-                vlib_node_runtime_t * node)
-{
-  nat_ed_ses_key_t key;
-  clib_bihash_kv_16_8_t s_kv, s_value;
-  udp_header_t *udp = ip4_next_header (ip);
-  tcp_header_t *tcp = (tcp_header_t *) udp;
-  snat_session_t *s = 0;
-  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  u32 old_addr, new_addr;
-  u16 new_port, old_port;
-  ip_csum_t sum;
-  u32 proto = ip_proto_to_snat_proto (ip->protocol);
-  snat_session_key_t e_key, l_key;
-  snat_user_t *u;
-  u8 lb;
-
-  old_addr = ip->src_address.as_u32;
-
-  key.l_addr = ip->src_address;
-  key.r_addr = ip->dst_address;
-  key.fib_index = rx_fib_index;
-  key.proto = ip->protocol;
-  key.r_port = udp->dst_port;
-  key.l_port = udp->src_port;
-  s_kv.key[0] = key.as_u64[0];
-  s_kv.key[1] = key.as_u64[1];
-
-  if (!clib_bihash_search_16_8 (&sm->in2out_ed, &s_kv, &s_value))
-    {
-      s = pool_elt_at_index (tsm->sessions, s_value.value);
-      if (is_fwd_bypass_session (s))
-        {
-          if (ip->protocol == IP_PROTOCOL_TCP)
-            {
-              if (nat44_set_tcp_session_state_i2o (sm, s, tcp, thread_index))
-                return 0;
-            }
-          /* Per-user LRU list maintenance */
-          nat44_session_update_lru (sm, s, thread_index);
-          return 0;
-        }
-    }
-  else
-    {
-      if (PREDICT_FALSE (maximum_sessions_exceeded (sm, thread_index)))
-        {
-          b->error = node->errors[SNAT_IN2OUT_ERROR_MAX_SESSIONS_EXCEEDED];
-          nat_ipfix_logging_max_sessions(sm->max_translations);
-          nat_log_notice ("maximum sessions exceeded");
-          return 0;
-        }
-
-      l_key.addr = ip->src_address;
-      l_key.port = udp->src_port;
-      l_key.protocol = proto;
-      l_key.fib_index = rx_fib_index;
-      if (snat_static_mapping_match(sm, l_key, &e_key, 0, 0, 0, &lb))
-        return 0;
-
-      u = nat_user_get_or_create (sm, &ip->src_address, rx_fib_index,
-                                  thread_index);
-      if (!u)
-        {
-          nat_log_warn ("create NAT user failed");
-          return 0;
-        }
-
-      s = nat_session_alloc_or_recycle (sm, u, thread_index);
-      if (!s)
-        {
-          nat_log_warn ("create NAT session failed");
-          return 0;
-        }
-
-      s->ext_host_addr.as_u32 = ip->dst_address.as_u32;
-      s->ext_host_port = udp->dst_port;
-      s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
-      if (lb)
-        s->flags |= SNAT_SESSION_FLAG_LOAD_BALANCING;
-      s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
-      s->outside_address_index = ~0;
-      s->in2out = l_key;
-      s->out2in = e_key;
-      s->out2in.protocol = l_key.protocol;
-      user_session_increment (sm, u, 1 /* static */);
-
-      /* Add to lookup tables */
-      s_kv.value = s - tsm->sessions;
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &s_kv, 1))
-        nat_log_notice ("in2out-ed key add failed");
-
-      key.l_addr = e_key.addr;
-      key.fib_index = e_key.fib_index;
-      key.l_port = e_key.port;
-      s_kv.key[0] = key.as_u64[0];
-      s_kv.key[1] = key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &s_kv, 1))
-        nat_log_notice ("out2in-ed key add failed");
-    }
-
-  new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32;
-
-  /* Update IP checksum */
-  sum = ip->checksum;
-  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, src_address);
-  if (is_twice_nat_session (s))
-    sum = ip_csum_update (sum, ip->dst_address.as_u32,
-                          s->ext_host_addr.as_u32, ip4_header_t, dst_address);
-  ip->checksum = ip_csum_fold (sum);
-
-  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
-    vnet_buffer(b)->sw_if_index[VLIB_TX] = sm->outside_fib_index;
-
-  if (PREDICT_TRUE(proto == SNAT_PROTOCOL_TCP))
-    {
-      old_port = tcp->src_port;
-      tcp->src_port = s->out2in.port;
-      new_port = tcp->src_port;
-
-      sum = tcp->checksum;
-      sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, src_address);
-      sum = ip_csum_update (sum, old_port, new_port, ip4_header_t, length);
-      if (is_twice_nat_session (s))
-        {
-          sum = ip_csum_update (sum, ip->dst_address.as_u32,
-                                s->ext_host_addr.as_u32, ip4_header_t,
-                                dst_address);
-          sum = ip_csum_update (sum, tcp->dst_port, s->ext_host_port,
-                                ip4_header_t, length);
-          tcp->dst_port = s->ext_host_port;
-          ip->dst_address.as_u32 = s->ext_host_addr.as_u32;
-        }
-      tcp->checksum = ip_csum_fold(sum);
-      if (nat44_set_tcp_session_state_i2o (sm, s, tcp, thread_index))
-        return s;
-    }
-  else
-    {
-      udp->src_port = s->out2in.port;
-      if (is_twice_nat_session (s))
-        {
-          udp->dst_port = s->ext_host_port;
-          ip->dst_address.as_u32 = s->ext_host_addr.as_u32;
-        }
-      udp->checksum = 0;
-    }
-
-  /* Accounting */
-  nat44_session_update_counters (s, now, vlib_buffer_length_in_chain (vm, b));
-  /* Per-user LRU list maintenance */
-  nat44_session_update_lru (sm, s, thread_index);
-
-  return s;
-}
-
-static inline uword
-snat_in2out_node_fn_inline (vlib_main_t * vm,
-                            vlib_node_runtime_t * node,
-                            vlib_frame_t * frame, int is_slow_path,
-                            int is_output_feature)
-{
-  u32 n_left_from, * from, * to_next;
-  snat_in2out_next_t next_index;
-  u32 pkts_processed = 0;
-  snat_main_t * sm = &snat_main;
-  f64 now = vlib_time_now (vm);
-  u32 stats_node_index;
-  u32 thread_index = vlib_get_thread_index ();
-
-  stats_node_index = is_slow_path ? snat_in2out_slowpath_node.index :
-    snat_in2out_node.index;
-
-  from = vlib_frame_vector_args (frame);
-  n_left_from = frame->n_vectors;
-  next_index = node->cached_next_index;
-
-  while (n_left_from > 0)
-    {
-      u32 n_left_to_next;
-
-      vlib_get_next_frame (vm, node, next_index,
-                          to_next, n_left_to_next);
+      vlib_get_next_frame (vm, node, next_index,
+                          to_next, n_left_to_next);
 
       while (n_left_from >= 4 && n_left_to_next >= 2)
        {
@@ -1600,10 +1162,11 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
             {
               if (PREDICT_FALSE (proto0 == ~0))
                 {
-                  s0 = snat_in2out_unknown_proto (sm, b0, ip0, rx_fib_index0,
-                                                  thread_index, now, vm, node);
-                  if (!s0)
-                    next0 = SNAT_IN2OUT_NEXT_DROP;
+                  if (nat_in2out_sm_unknown_proto (sm, b0, ip0, rx_fib_index0))
+                    {
+                      next0 = SNAT_IN2OUT_NEXT_DROP;
+                      b0->error = node->errors[SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL];
+                    }
                   goto trace00;
                 }
 
@@ -1617,12 +1180,6 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
             }
           else
             {
-              if (is_output_feature)
-                {
-                  if (PREDICT_FALSE(nat_not_translate_output_feature_fwd(sm, ip0, thread_index)))
-                    goto trace00;
-                }
-
               if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP))
                 {
                   next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
@@ -1673,30 +1230,8 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
                 }
             }
           else
-            {
-              if (PREDICT_FALSE (value0.value == ~0ULL))
-                {
-                  if (is_slow_path)
-                    {
-                      s0 = snat_in2out_lb(sm, b0, ip0, rx_fib_index0,
-                                          thread_index, now, vm, node);
-                      if (!s0 && !sm->forwarding_enabled)
-                        next0 = SNAT_IN2OUT_NEXT_DROP;
-                      goto trace00;
-                    }
-                  else
-                    {
-                      next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
-                      goto trace00;
-                    }
-                }
-              else
-                {
-                  s0 = pool_elt_at_index (
-                    sm->per_thread_data[thread_index].sessions,
-                    value0.value);
-                }
-            }
+            s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                                    value0.value);
 
           b0->flags |= VNET_BUFFER_F_IS_NATED;
 
@@ -1787,10 +1322,11 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
             {
               if (PREDICT_FALSE (proto1 == ~0))
                 {
-                  s1 = snat_in2out_unknown_proto (sm, b1, ip1, rx_fib_index1,
-                                                  thread_index, now, vm, node);
-                  if (!s1)
-                    next1 = SNAT_IN2OUT_NEXT_DROP;
+                  if (nat_in2out_sm_unknown_proto (sm, b1, ip1, rx_fib_index1))
+                    {
+                      next1 = SNAT_IN2OUT_NEXT_DROP;
+                      b1->error = node->errors[SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL];
+                    }
                   goto trace01;
                 }
 
@@ -1804,12 +1340,6 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
             }
           else
             {
-              if (is_output_feature)
-                {
-                  if (PREDICT_FALSE(nat_not_translate_output_feature_fwd(sm, ip1, thread_index)))
-                    goto trace01;
-                }
-
               if (PREDICT_FALSE (proto1 == ~0 || proto1 == SNAT_PROTOCOL_ICMP))
                 {
                   next1 = SNAT_IN2OUT_NEXT_SLOW_PATH;
@@ -1860,30 +1390,8 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
                 }
             }
           else
-            {
-              if (PREDICT_FALSE (value1.value == ~0ULL))
-                {
-                  if (is_slow_path)
-                    {
-                      s1 = snat_in2out_lb(sm, b1, ip1, rx_fib_index1,
-                                          thread_index, now, vm, node);
-                      if (!s1 && !sm->forwarding_enabled)
-                        next1 = SNAT_IN2OUT_NEXT_DROP;
-                      goto trace01;
-                    }
-                  else
-                    {
-                      next1 = SNAT_IN2OUT_NEXT_SLOW_PATH;
-                      goto trace01;
-                    }
-                }
-              else
-                {
-                  s1 = pool_elt_at_index (
-                    sm->per_thread_data[thread_index].sessions,
-                    value1.value);
-                }
-            }
+            s1 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                                    value1.value);
 
           b1->flags |= VNET_BUFFER_F_IS_NATED;
 
@@ -2010,10 +1518,11 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
             {
               if (PREDICT_FALSE (proto0 == ~0))
                 {
-                  s0 = snat_in2out_unknown_proto (sm, b0, ip0, rx_fib_index0,
-                                                  thread_index, now, vm, node);
-                  if (!s0)
-                    next0 = SNAT_IN2OUT_NEXT_DROP;
+                  if (nat_in2out_sm_unknown_proto (sm, b0, ip0, rx_fib_index0))
+                    {
+                      next0 = SNAT_IN2OUT_NEXT_DROP;
+                      b0->error = node->errors[SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL];
+                    }
                   goto trace0;
                 }
 
@@ -2027,12 +1536,6 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
             }
           else
             {
-               if (is_output_feature)
-                {
-                  if (PREDICT_FALSE(nat_not_translate_output_feature_fwd(sm, ip0, thread_index)))
-                    goto trace0;
-                }
-
               if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP))
                 {
                   next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
@@ -2084,30 +1587,8 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
                 }
             }
           else
-            {
-              if (PREDICT_FALSE (value0.value == ~0ULL))
-                {
-                  if (is_slow_path)
-                    {
-                      s0 = snat_in2out_lb(sm, b0, ip0, rx_fib_index0,
-                                          thread_index, now, vm, node);
-                      if (!s0 && !sm->forwarding_enabled)
-                        next0 = SNAT_IN2OUT_NEXT_DROP;
-                      goto trace0;
-                    }
-                  else
-                    {
-                      next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
-                      goto trace0;
-                    }
-                }
-              else
-                {
-                  s0 = pool_elt_at_index (
-                    sm->per_thread_data[thread_index].sessions,
-                    value0.value);
-                }
-            }
+          s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                                  value0.value);
 
           b0->flags |= VNET_BUFFER_F_IS_NATED;
 
@@ -2323,12 +1804,13 @@ VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_output_slowpath_node,
 
 extern vnet_feature_arc_registration_t vnet_feat_arc_ip4_local;
 
-static uword
-nat44_hairpinning_fn (vlib_main_t * vm,
-                      vlib_node_runtime_t * node,
-                      vlib_frame_t * frame)
+static inline uword
+nat44_hairpinning_fn_inline (vlib_main_t * vm,
+                             vlib_node_runtime_t * node,
+                             vlib_frame_t * frame,
+                             int is_ed)
 {
-  u32 n_left_from, * from, * to_next;
+  u32 n_left_from, * from, * to_next, stats_node_index;
   snat_in2out_next_t next_index;
   u32 pkts_processed = 0;
   snat_main_t * sm = &snat_main;
@@ -2336,6 +1818,8 @@ nat44_hairpinning_fn (vlib_main_t * vm,
   u8 arc_index = vnet_feat_arc_ip4_local.feature_arc_index;
   vnet_feature_config_main_t *cm = &fm->feature_config_mains[arc_index];
 
+  stats_node_index = is_ed ? nat44_ed_hairpinning_node.index :
+    nat44_hairpinning_node.index;
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
   next_index = node->cached_next_index;
@@ -2375,7 +1859,7 @@ nat44_hairpinning_fn (vlib_main_t * vm,
           vnet_get_config_data (&cm->config_main, &b0->current_config_index,
                                 &next0, 0);
 
-          if (snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0))
+          if (snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0, is_ed))
             next0 = SNAT_IN2OUT_NEXT_LOOKUP;
 
           pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
@@ -2389,12 +1873,20 @@ nat44_hairpinning_fn (vlib_main_t * vm,
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
 
-  vlib_node_increment_counter (vm, nat44_hairpinning_node.index,
+  vlib_node_increment_counter (vm, stats_node_index,
                                SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
                                pkts_processed);
   return frame->n_vectors;
 }
 
+static uword
+nat44_hairpinning_fn (vlib_main_t * vm,
+                      vlib_node_runtime_t * node,
+                      vlib_frame_t * frame)
+{
+  return nat44_hairpinning_fn_inline (vm, node, frame, 0);
+}
+
 VLIB_REGISTER_NODE (nat44_hairpinning_node) = {
   .function = nat44_hairpinning_fn,
   .name = "nat44-hairpinning",
@@ -2412,9 +1904,34 @@ VLIB_REGISTER_NODE (nat44_hairpinning_node) = {
 VLIB_NODE_FUNCTION_MULTIARCH (nat44_hairpinning_node,
                               nat44_hairpinning_fn);
 
-static inline void
-nat44_reass_hairpinning (snat_main_t *sm,
-                         vlib_buffer_t * b0,
+static uword
+nat44_ed_hairpinning_fn (vlib_main_t * vm,
+                         vlib_node_runtime_t * node,
+                         vlib_frame_t * frame)
+{
+  return nat44_hairpinning_fn_inline (vm, node, frame, 1);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_hairpinning_node) = {
+  .function = nat44_ed_hairpinning_fn,
+  .name = "nat44-ed-hairpinning",
+  .vector_size = sizeof (u32),
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+  .n_next_nodes = 2,
+  .next_nodes = {
+    [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+    [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_hairpinning_node,
+                              nat44_ed_hairpinning_fn);
+
+static inline void
+nat44_reass_hairpinning (snat_main_t *sm,
+                         vlib_buffer_t * b0,
                          ip4_header_t * ip0,
                          u16 sport,
                          u16 dport,
@@ -2639,46 +2156,1351 @@ nat44_in2out_reass_node_fn (vlib_main_t * vm,
                                       reass0->sess_index);
             }
 
-          old_addr0 = ip0->src_address.as_u32;
-          ip0->src_address = s0->out2in.addr;
-          new_addr0 = ip0->src_address.as_u32;
-          vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->out2in.fib_index;
+          old_addr0 = ip0->src_address.as_u32;
+          ip0->src_address = s0->out2in.addr;
+          new_addr0 = ip0->src_address.as_u32;
+          vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->out2in.fib_index;
+
+          sum0 = ip0->checksum;
+          sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
+                                 ip4_header_t,
+                                 src_address /* changed member */);
+          ip0->checksum = ip_csum_fold (sum0);
+
+          if (PREDICT_FALSE (ip4_is_first_fragment (ip0)))
+            {
+              if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP))
+                {
+                  old_port0 = tcp0->src_port;
+                  tcp0->src_port = s0->out2in.port;
+                  new_port0 = tcp0->src_port;
+
+                  sum0 = tcp0->checksum;
+                  sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
+                                         ip4_header_t,
+                                         dst_address /* changed member */);
+                  sum0 = ip_csum_update (sum0, old_port0, new_port0,
+                                         ip4_header_t /* cheat */,
+                                         length /* changed member */);
+                  tcp0->checksum = ip_csum_fold(sum0);
+                }
+              else
+                {
+                  old_port0 = udp0->src_port;
+                  udp0->src_port = s0->out2in.port;
+                  udp0->checksum = 0;
+                }
+            }
+
+          /* Hairpinning */
+          nat44_reass_hairpinning (sm, b0, ip0, s0->out2in.port,
+                                   s0->ext_host_port, proto0);
+
+          /* Accounting */
+          nat44_session_update_counters (s0, now,
+                                         vlib_buffer_length_in_chain (vm, b0));
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s0, thread_index);
+
+        trace0:
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b0->flags & VLIB_BUFFER_IS_TRACED)))
+            {
+              nat44_in2out_reass_trace_t *t =
+                 vlib_add_trace (vm, node, b0, sizeof (*t));
+              t->cached = cached0;
+              t->sw_if_index = sw_if_index0;
+              t->next_index = next0;
+            }
+
+          if (cached0)
+            {
+              n_left_to_next++;
+              to_next--;
+            }
+          else
+            {
+              pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
+
+              /* verify speculative enqueue, maybe switch current next frame */
+              vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                               to_next, n_left_to_next,
+                                               bi0, next0);
+            }
+
+         if (n_left_from == 0 && vec_len (fragments_to_loopback))
+           {
+             from = vlib_frame_vector_args (frame);
+             u32 len = vec_len (fragments_to_loopback);
+             if (len <= VLIB_FRAME_SIZE)
+               {
+                 clib_memcpy (from, fragments_to_loopback, sizeof (u32) * len);
+                 n_left_from = len;
+                 vec_reset_length (fragments_to_loopback);
+               }
+             else
+               {
+                 clib_memcpy (from,
+                               fragments_to_loopback + (len - VLIB_FRAME_SIZE),
+                               sizeof (u32) * VLIB_FRAME_SIZE);
+                 n_left_from = VLIB_FRAME_SIZE;
+                 _vec_len (fragments_to_loopback) = len - VLIB_FRAME_SIZE;
+               }
+           }
+       }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  vlib_node_increment_counter (vm, nat44_in2out_reass_node.index,
+                               SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
+                               pkts_processed);
+
+  nat_send_all_to_node (vm, fragments_to_drop, node,
+                        &node->errors[SNAT_IN2OUT_ERROR_DROP_FRAGMENT],
+                        SNAT_IN2OUT_NEXT_DROP);
+
+  vec_free (fragments_to_drop);
+  vec_free (fragments_to_loopback);
+  return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (nat44_in2out_reass_node) = {
+  .function = nat44_in2out_reass_node_fn,
+  .name = "nat44-in2out-reass",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_in2out_reass_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+
+  .n_next_nodes = SNAT_IN2OUT_N_NEXT,
+  .next_nodes = {
+    [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+    [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+    [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-slowpath",
+    [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [SNAT_IN2OUT_NEXT_REASS] = "nat44-in2out-reass",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_in2out_reass_node,
+                              nat44_in2out_reass_node_fn);
+
+/*******************************/
+/*** endpoint-dependent mode ***/
+/*******************************/
+
+static_always_inline int
+icmp_get_ed_key(ip4_header_t *ip0, nat_ed_ses_key_t *p_key0)
+{
+  icmp46_header_t *icmp0;
+  nat_ed_ses_key_t key0;
+  icmp_echo_header_t *echo0, *inner_echo0 = 0;
+  ip4_header_t *inner_ip0 = 0;
+  void *l4_header = 0;
+  icmp46_header_t *inner_icmp0;
+
+  icmp0 = (icmp46_header_t *) ip4_next_header (ip0);
+  echo0 = (icmp_echo_header_t *)(icmp0+1);
+
+  if (!icmp_is_error_message (icmp0))
+    {
+      key0.proto = IP_PROTOCOL_ICMP;
+      key0.l_addr = ip0->src_address;
+      key0.r_addr = ip0->dst_address;
+      key0.l_port = echo0->identifier;
+      key0.r_port = 0;
+    }
+  else
+    {
+      inner_ip0 = (ip4_header_t *)(echo0+1);
+      l4_header = ip4_next_header (inner_ip0);
+      key0.proto = inner_ip0->protocol;
+      key0.r_addr = inner_ip0->src_address;
+      key0.l_addr = inner_ip0->dst_address;
+      switch (ip_proto_to_snat_proto (inner_ip0->protocol))
+        {
+        case SNAT_PROTOCOL_ICMP:
+          inner_icmp0 = (icmp46_header_t*)l4_header;
+          inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1);
+          key0.r_port = 0;
+          key0.l_port = inner_echo0->identifier;
+          break;
+        case SNAT_PROTOCOL_UDP:
+        case SNAT_PROTOCOL_TCP:
+          key0.l_port = ((tcp_udp_header_t*)l4_header)->dst_port;
+          key0.r_port = ((tcp_udp_header_t*)l4_header)->src_port;
+          break;
+        default:
+          return SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL;
+        }
+    }
+  *p_key0 = key0;
+  return 0;
+}
+
+static u32
+slow_path_ed (snat_main_t *sm,
+              vlib_buffer_t *b,
+              u32 rx_fib_index,
+              clib_bihash_kv_16_8_t *kv,
+              snat_session_t ** sessionp,
+              vlib_node_runtime_t * node,
+              u32 next,
+              u32 thread_index)
+{
+  snat_session_t *s;
+  snat_user_t *u;
+  snat_session_key_t key0, key1;
+  u8 lb = 0, is_sm = 0;
+  u32 address_index = ~0;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  nat_ed_ses_key_t *key = (nat_ed_ses_key_t *) kv->key;
+  u32 proto = ip_proto_to_snat_proto (key->proto);
+
+  if (PREDICT_FALSE (maximum_sessions_exceeded (sm, thread_index)))
+    {
+      b->error = node->errors[SNAT_IN2OUT_ERROR_MAX_SESSIONS_EXCEEDED];
+      nat_ipfix_logging_max_sessions(sm->max_translations);
+      nat_log_notice ("maximum sessions exceeded");
+      return SNAT_IN2OUT_NEXT_DROP;
+    }
+
+  key0.addr = key->l_addr;
+  key0.port = key->l_port;
+  key1.protocol = key0.protocol = proto;
+  key0.fib_index = rx_fib_index;
+  key1.fib_index = sm->outside_fib_index;
+  /* First try to match static mapping by local address and port */
+  if (snat_static_mapping_match (sm, key0, &key1, 0, 0, 0, &lb))
+    {
+      /* Try to create dynamic translation */
+      if (snat_alloc_outside_address_and_port (sm->addresses, rx_fib_index,
+                                               thread_index, &key1,
+                                               &address_index,
+                                               sm->port_per_thread,
+                                               tsm->snat_thread_index))
+        {
+          nat_log_notice ("addresses exhausted");
+          b->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS];
+          return SNAT_IN2OUT_NEXT_DROP;
+        }
+    }
+  else
+    is_sm = 1;
+
+  u = nat_user_get_or_create (sm, &key->l_addr, rx_fib_index, thread_index);
+  if (!u)
+    {
+      nat_log_warn ("create NAT user failed");
+      return SNAT_IN2OUT_NEXT_DROP;
+    }
+
+  s = nat_session_alloc_or_recycle (sm, u, thread_index);
+  if (!s)
+    {
+      nat_log_warn ("create NAT session failed");
+      return SNAT_IN2OUT_NEXT_DROP;
+    }
+
+  user_session_increment (sm, u, is_sm);
+  if (is_sm)
+    s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
+  if (lb)
+    s->flags |= SNAT_SESSION_FLAG_LOAD_BALANCING;
+  s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
+  s->outside_address_index = address_index;
+  s->ext_host_addr = key->r_addr;
+  s->ext_host_port = key->r_port;
+  s->in2out = key0;
+  s->out2in = key1;
+  s->out2in.protocol = key0.protocol;
+
+  /* Add to lookup tables */
+  kv->value = s - tsm->sessions;
+  if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, kv, 1))
+    nat_log_notice ("in2out-ed key add failed");
+
+  make_ed_kv (kv, &key1.addr, &key->r_addr, key->proto, key1.fib_index,
+              key1.port, key->r_port);
+  kv->value = s - tsm->sessions;
+  if (clib_bihash_add_del_16_8 (&tsm->out2in_ed, kv, 1))
+    nat_log_notice ("out2in-ed key add failed");
+
+  *sessionp = s;
+
+  /* log NAT event */
+  snat_ipfix_logging_nat44_ses_create(s->in2out.addr.as_u32,
+                                      s->out2in.addr.as_u32,
+                                      s->in2out.protocol,
+                                      s->in2out.port,
+                                      s->out2in.port,
+                                      s->in2out.fib_index);
+  return next;
+}
+
+static_always_inline int
+nat44_ed_not_translate (snat_main_t * sm, vlib_node_runtime_t *node,
+                        u32 sw_if_index, ip4_header_t * ip, u32 proto,
+                        u32 rx_fib_index, u32 thread_index)
+{
+  udp_header_t *udp = ip4_next_header (ip);
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  clib_bihash_kv_16_8_t kv, value;
+  snat_session_key_t key0, key1;
+
+  make_ed_kv (&kv, &ip->dst_address, &ip->src_address, ip->protocol,
+              sm->outside_fib_index, udp->dst_port, udp->src_port);
+
+  /* NAT packet aimed at external address if */
+  /* has active sessions */
+  if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv, &value))
+    {
+      key0.addr = ip->dst_address;
+      key0.port = udp->dst_port;
+      key0.protocol = proto;
+      key0.fib_index = sm->outside_fib_index;
+      /* or is static mappings */
+      if (!snat_static_mapping_match(sm, key0, &key1, 1, 0, 0, 0))
+        return 0;
+    }
+  else
+    return 0;
+
+  if (sm->forwarding_enabled)
+    return 1;
+
+  return snat_not_translate_fast(sm, node, sw_if_index, ip, proto, rx_fib_index);
+}
+
+static_always_inline int
+nat_not_translate_output_feature_fwd (snat_main_t * sm, ip4_header_t * ip,
+                                      u32 thread_index, f64 now,
+                                      vlib_main_t * vm, vlib_buffer_t * b)
+{
+  nat_ed_ses_key_t key;
+  clib_bihash_kv_16_8_t kv, value;
+  udp_header_t *udp;
+  snat_session_t *s = 0;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+
+  if (!sm->forwarding_enabled)
+    return 0;
+
+  if (ip->protocol == IP_PROTOCOL_ICMP)
+    {
+      key.as_u64[0] = key.as_u64[1] = 0;
+      if (icmp_get_ed_key (ip, &key))
+        return 0;
+      key.fib_index = 0;
+      kv.key[0] = key.as_u64[0];
+      kv.key[1] = key.as_u64[1];
+    }
+  else if (ip->protocol == IP_PROTOCOL_UDP || ip->protocol == IP_PROTOCOL_TCP)
+    {
+      udp = ip4_next_header(ip);
+      make_ed_kv (&kv, &ip->src_address, &ip->dst_address, ip->protocol, 0,
+                  udp->src_port, udp->dst_port);
+    }
+  else
+    {
+      make_ed_kv (&kv, &ip->src_address, &ip->dst_address, ip->protocol, 0, 0,
+                  0);
+    }
+
+  if (!clib_bihash_search_16_8 (&tsm->in2out_ed, &kv, &value))
+    {
+      s = pool_elt_at_index (tsm->sessions, value.value);
+      if (is_fwd_bypass_session (s))
+        {
+          if (ip->protocol == IP_PROTOCOL_TCP)
+            {
+              tcp_header_t *tcp = ip4_next_header(ip);
+              if (nat44_set_tcp_session_state_i2o (sm, s, tcp, thread_index))
+                return 1;
+            }
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s, thread_index);
+          /* Accounting */
+          nat44_session_update_counters (s, now,
+                                         vlib_buffer_length_in_chain (vm, b));
+          return 1;
+        }
+      else
+        return 0;
+    }
+
+  return 0;
+}
+
+static_always_inline int
+nat44_ed_not_translate_output_feature (snat_main_t * sm, ip4_header_t * ip,
+                                       u8 proto, u16 src_port, u16 dst_port,
+                                       u32 thread_index, u32 sw_if_index)
+{
+  clib_bihash_kv_16_8_t kv, value;
+  snat_main_per_thread_data_t *tsm = tsm = &sm->per_thread_data[thread_index];
+  snat_interface_t *i;
+
+  /* src NAT check */
+  make_ed_kv (&kv, &ip->src_address, &ip->dst_address, proto,
+              sm->outside_fib_index, src_port, dst_port);
+  if (!clib_bihash_search_16_8 (&tsm->out2in_ed, &kv, &value))
+    return 1;
+
+  /* dst NAT check */
+  make_ed_kv (&kv, &ip->dst_address, &ip->src_address, proto,
+              sm->inside_fib_index, dst_port, src_port);
+  if (!clib_bihash_search_16_8 (&tsm->in2out_ed, &kv, &value))
+  {
+    /* hairpinning */
+    pool_foreach (i, sm->output_feature_interfaces,
+    ({
+      if ((nat_interface_is_inside(i)) && (sw_if_index == i->sw_if_index))
+        return 0;
+    }));
+    return 1;
+  }
+
+  return 0;
+}
+
+u32
+icmp_match_in2out_ed(snat_main_t *sm, vlib_node_runtime_t *node,
+                     u32 thread_index, vlib_buffer_t *b, ip4_header_t *ip,
+                     u8 *p_proto, snat_session_key_t *p_value,
+                     u8 *p_dont_translate, void *d, void *e)
+{
+  icmp46_header_t *icmp;
+  u32 sw_if_index;
+  u32 rx_fib_index;
+  nat_ed_ses_key_t key;
+  snat_session_t *s = 0;
+  u8 dont_translate = 0;
+  clib_bihash_kv_16_8_t kv, value;
+  u32 next = ~0;
+  int err;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+
+  icmp = (icmp46_header_t *) ip4_next_header (ip);
+  sw_if_index = vnet_buffer(b)->sw_if_index[VLIB_RX];
+  rx_fib_index = ip4_fib_table_get_index_for_sw_if_index (sw_if_index);
+
+  key.as_u64[0] = key.as_u64[1] = 0;
+  err = icmp_get_ed_key (ip, &key);
+  if (err != 0)
+    {
+      b->error = node->errors[err];
+      next = SNAT_IN2OUT_NEXT_DROP;
+      goto out;
+    }
+  key.fib_index = rx_fib_index;
+
+  kv.key[0] = key.as_u64[0];
+  kv.key[1] = key.as_u64[1];
+
+  if (clib_bihash_search_16_8 (&tsm->in2out_ed, &kv, &value))
+    {
+      if (vnet_buffer(b)->sw_if_index[VLIB_TX] != ~0)
+        {
+          if (PREDICT_FALSE(nat44_ed_not_translate_output_feature(sm, ip,
+              key.proto, key.l_port, key.r_port, thread_index, sw_if_index)))
+            {
+              dont_translate = 1;
+              goto out;
+            }
+        }
+      else
+        {
+          if (PREDICT_FALSE(nat44_ed_not_translate(sm, node, sw_if_index,
+              ip, SNAT_PROTOCOL_ICMP, rx_fib_index, thread_index)))
+            {
+              dont_translate = 1;
+              goto out;
+            }
+        }
+
+      if (PREDICT_FALSE(icmp_is_error_message (icmp)))
+        {
+          b->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE];
+          next = SNAT_IN2OUT_NEXT_DROP;
+          goto out;
+        }
+
+      next = slow_path_ed (sm, b, rx_fib_index, &kv, &s, node, next,
+                           thread_index);
+
+      if (PREDICT_FALSE (next == SNAT_IN2OUT_NEXT_DROP))
+        goto out;
+    }
+  else
+    {
+      if (PREDICT_FALSE(icmp->type != ICMP4_echo_request &&
+                        icmp->type != ICMP4_echo_reply &&
+                        !icmp_is_error_message (icmp)))
+        {
+          b->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE];
+          next = SNAT_IN2OUT_NEXT_DROP;
+          goto out;
+        }
+
+      s = pool_elt_at_index (tsm->sessions, value.value);
+    }
+
+  *p_proto = ip_proto_to_snat_proto (key.proto);
+out:
+  if (s)
+    *p_value = s->out2in;
+  *p_dont_translate = dont_translate;
+  if (d)
+    *(snat_session_t**)d = s;
+  return next;
+}
+
+static inline void
+nat44_ed_hairpinning_unknown_proto (snat_main_t *sm,
+                                    vlib_buffer_t * b,
+                                    ip4_header_t * ip)
+{
+  u32 old_addr, new_addr = 0, ti = 0;
+  clib_bihash_kv_8_8_t kv, value;
+  clib_bihash_kv_16_8_t s_kv, s_value;
+  snat_static_mapping_t *m;
+  ip_csum_t sum;
+  snat_session_t *s;
+  snat_main_per_thread_data_t *tsm;
+
+  if (sm->num_workers > 1)
+    ti = sm->worker_out2in_cb (ip, sm->outside_fib_index);
+  else
+    ti = sm->num_workers;
+  tsm = &sm->per_thread_data[ti];
+
+  old_addr = ip->dst_address.as_u32;
+  make_ed_kv (&s_kv, &ip->dst_address, &ip->src_address, ip->protocol,
+              sm->outside_fib_index, 0, 0);
+  if (clib_bihash_search_16_8 (&tsm->out2in_ed, &s_kv, &s_value))
+    {
+      make_sm_kv (&kv, &ip->dst_address, 0, sm->outside_fib_index, 0);
+      if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+        return;
+
+      m = pool_elt_at_index (sm->static_mappings, value.value);
+      if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
+        vnet_buffer(b)->sw_if_index[VLIB_TX] = m->fib_index;
+      new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32;
+    }
+  else
+    {
+      s = pool_elt_at_index (sm->per_thread_data[ti].sessions, s_value.value);
+      if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
+        vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index;
+      new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32;
+    }
+  sum = ip->checksum;
+  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
+  ip->checksum = ip_csum_fold (sum);
+}
+
+static snat_session_t *
+nat44_ed_in2out_unknown_proto (snat_main_t *sm,
+                               vlib_buffer_t * b,
+                               ip4_header_t * ip,
+                               u32 rx_fib_index,
+                               u32 thread_index,
+                               f64 now,
+                               vlib_main_t * vm,
+                               vlib_node_runtime_t * node)
+{
+  clib_bihash_kv_8_8_t kv, value;
+  clib_bihash_kv_16_8_t s_kv, s_value;
+  snat_static_mapping_t *m;
+  u32 old_addr, new_addr = 0;
+  ip_csum_t sum;
+  snat_user_t *u;
+  dlist_elt_t *head, *elt;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  u32 elt_index, head_index, ses_index;
+  snat_session_t * s;
+  u32 address_index = ~0;
+  int i;
+  u8 is_sm = 0;
+
+  old_addr = ip->src_address.as_u32;
+
+  make_ed_kv (&s_kv, &ip->src_address, &ip->dst_address, ip->protocol,
+              rx_fib_index, 0, 0);
+
+  if (!clib_bihash_search_16_8 (&tsm->in2out_ed, &s_kv, &s_value))
+    {
+      s = pool_elt_at_index (tsm->sessions, s_value.value);
+      new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32;
+    }
+  else
+    {
+      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
+        {
+          b->error = node->errors[SNAT_IN2OUT_ERROR_MAX_SESSIONS_EXCEEDED];
+          nat_ipfix_logging_max_sessions(sm->max_translations);
+          nat_log_notice ("maximum sessions exceeded");
+          return 0;
+        }
+
+      u = nat_user_get_or_create (sm, &ip->src_address, rx_fib_index,
+                                  thread_index);
+      if (!u)
+        {
+          nat_log_warn ("create NAT user failed");
+          return 0;
+        }
+
+      make_sm_kv (&kv, &ip->src_address, 0, rx_fib_index, 0);
+
+      /* Try to find static mapping first */
+      if (!clib_bihash_search_8_8 (&sm->static_mapping_by_local, &kv, &value))
+        {
+          m = pool_elt_at_index (sm->static_mappings, value.value);
+          new_addr = ip->src_address.as_u32 = m->external_addr.as_u32;
+          is_sm = 1;
+          goto create_ses;
+        }
+      /* Fallback to 3-tuple key */
+      else
+        {
+          /* Choose same out address as for TCP/UDP session to same destination */
+          head_index = u->sessions_per_user_list_head_index;
+          head = pool_elt_at_index (tsm->list_pool, head_index);
+          elt_index = head->next;
+         if (PREDICT_FALSE (elt_index == ~0))
+           ses_index = ~0;
+         else
+           {
+             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_index = elt->next;
+              elt = pool_elt_at_index (tsm->list_pool, elt_index);
+              ses_index = elt->value;
+
+              if (s->ext_host_addr.as_u32 == ip->dst_address.as_u32)
+                {
+                  new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32;
+                  address_index = s->outside_address_index;
+
+                  make_ed_kv (&s_kv, &s->out2in.addr, &ip->dst_address,
+                              ip->protocol, sm->outside_fib_index, 0, 0);
+                  if (clib_bihash_search_16_8 (&tsm->out2in_ed, &s_kv, &s_value))
+                    goto create_ses;
+
+                  break;
+                }
+            }
+
+          for (i = 0; i < vec_len (sm->addresses); i++)
+            {
+              make_ed_kv (&s_kv, &sm->addresses[i].addr, &ip->dst_address,
+                          ip->protocol, sm->outside_fib_index, 0, 0);
+              if (clib_bihash_search_16_8 (&tsm->out2in_ed, &s_kv, &s_value))
+                {
+                  new_addr = ip->src_address.as_u32 =
+                    sm->addresses[i].addr.as_u32;
+                  address_index = i;
+                  goto create_ses;
+                }
+            }
+          return 0;
+        }
+
+create_ses:
+      s = nat_session_alloc_or_recycle (sm, u, thread_index);
+      if (!s)
+        {
+          nat_log_warn ("create NAT session failed");
+          return 0;
+        }
+
+      s->ext_host_addr.as_u32 = ip->dst_address.as_u32;
+      s->flags |= SNAT_SESSION_FLAG_UNKNOWN_PROTO;
+      s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
+      s->outside_address_index = address_index;
+      s->out2in.addr.as_u32 = new_addr;
+      s->out2in.fib_index = sm->outside_fib_index;
+      s->in2out.addr.as_u32 = old_addr;
+      s->in2out.fib_index = rx_fib_index;
+      s->in2out.port = s->out2in.port = ip->protocol;
+      if (is_sm)
+       s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
+      user_session_increment (sm, u, is_sm);
+
+      /* Add to lookup tables */
+      make_ed_kv (&s_kv, &s->in2out.addr, &ip->dst_address, ip->protocol,
+                  rx_fib_index, 0, 0);
+      s_kv.value = s - tsm->sessions;
+      if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &s_kv, 1))
+        nat_log_notice ("in2out key add failed");
+
+      make_ed_kv (&s_kv, &s->out2in.addr, &ip->dst_address, ip->protocol,
+                  sm->outside_fib_index, 0, 0);
+      s_kv.value = s - tsm->sessions;
+      if (clib_bihash_add_del_16_8 (&tsm->out2in_ed, &s_kv, 1))
+        nat_log_notice ("out2in key add failed");
+  }
+
+  /* Update IP checksum */
+  sum = ip->checksum;
+  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, src_address);
+  ip->checksum = ip_csum_fold (sum);
+
+  /* Accounting */
+  nat44_session_update_counters (s, now, vlib_buffer_length_in_chain (vm, b));
+  /* Per-user LRU list maintenance */
+  nat44_session_update_lru (sm, s, thread_index);
+
+  /* Hairpinning */
+  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
+    nat44_ed_hairpinning_unknown_proto(sm, b, ip);
+
+  if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0)
+    vnet_buffer(b)->sw_if_index[VLIB_TX] = sm->outside_fib_index;
+
+  return s;
+}
+
+static inline uword
+nat44_ed_in2out_node_fn_inline (vlib_main_t * vm,
+                                vlib_node_runtime_t * node,
+                                vlib_frame_t * frame, int is_slow_path,
+                                int is_output_feature)
+{
+  u32 n_left_from, *from, *to_next, pkts_processed = 0, stats_node_index;
+  snat_in2out_next_t next_index;
+  snat_main_t *sm = &snat_main;
+  f64 now = vlib_time_now (vm);
+  u32 thread_index = vlib_get_thread_index ();
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+
+  stats_node_index = is_slow_path ? nat44_ed_in2out_slowpath_node.index :
+    nat44_ed_in2out_node.index;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from >= 4 && n_left_to_next >= 2)
+       {
+          u32 bi0, bi1;
+         vlib_buffer_t *b0, *b1;
+          u32 next0, sw_if_index0, rx_fib_index0, iph_offset0 = 0, proto0,
+              new_addr0, old_addr0;
+          u32 next1, sw_if_index1, rx_fib_index1, iph_offset1 = 0, proto1,
+              new_addr1, old_addr1;
+          u16 old_port0, new_port0, old_port1, new_port1;
+          ip4_header_t *ip0, *ip1;
+          udp_header_t *udp0, *udp1;
+          tcp_header_t *tcp0, *tcp1;
+          icmp46_header_t *icmp0, *icmp1;
+          snat_session_t *s0 = 0, *s1 = 0;
+          clib_bihash_kv_16_8_t kv0, value0, kv1, value1;
+          ip_csum_t sum0, sum1;
+
+         /* Prefetch next iteration. */
+         {
+           vlib_buffer_t * p2, * p3;
+
+           p2 = vlib_get_buffer (vm, from[2]);
+           p3 = vlib_get_buffer (vm, from[3]);
+
+           vlib_prefetch_buffer_header (p2, LOAD);
+           vlib_prefetch_buffer_header (p3, LOAD);
+
+           CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
+           CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
+         }
+
+          /* speculatively enqueue b0 and b1 to the current next frame */
+         to_next[0] = bi0 = from[0];
+         to_next[1] = bi1 = from[1];
+         from += 2;
+         to_next += 2;
+         n_left_from -= 2;
+         n_left_to_next -= 2;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         b1 = vlib_get_buffer (vm, bi1);
+
+          next0 = SNAT_IN2OUT_NEXT_LOOKUP;
+
+          if (is_output_feature)
+            iph_offset0 = vnet_buffer (b0)->ip.save_rewrite_length;
+
+          ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) +
+                 iph_offset0);
+
+          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
+         rx_fib_index0 = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                               sw_if_index0);
+
+          if (PREDICT_FALSE(ip0->ttl == 1))
+            {
+              vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0;
+              icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded,
+                                           ICMP4_time_exceeded_ttl_exceeded_in_transit,
+                                           0);
+              next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR;
+              goto trace00;
+            }
+
+          udp0 = ip4_next_header (ip0);
+          tcp0 = (tcp_header_t *) udp0;
+          icmp0 = (icmp46_header_t *) udp0;
+          proto0 = ip_proto_to_snat_proto (ip0->protocol);
+
+          if (is_slow_path)
+            {
+              if (PREDICT_FALSE (proto0 == ~0))
+                {
+                  s0 = nat44_ed_in2out_unknown_proto (sm, b0, ip0,
+                                                      rx_fib_index0,
+                                                      thread_index, now, vm,
+                                                      node);
+                  if (!s0)
+                    next0 = SNAT_IN2OUT_NEXT_DROP;
+                  goto trace00;
+                }
+
+              if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = icmp_in2out_slow_path
+                    (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node,
+                     next0, now, thread_index, &s0);
+                  goto trace00;
+                }
+            }
+          else
+            {
+               if (is_output_feature)
+                {
+                  if (PREDICT_FALSE(nat_not_translate_output_feature_fwd(
+                      sm, ip0, thread_index, now, vm, b0)))
+                    goto trace00;
+                }
+
+              if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
+                  goto trace00;
+                }
+
+              if (ip4_is_fragment (ip0))
+                {
+                  b0->error = node->errors[SNAT_IN2OUT_ERROR_DROP_FRAGMENT];
+                  next0 = SNAT_IN2OUT_NEXT_DROP;
+                  goto trace00;
+                }
+            }
+
+          make_ed_kv (&kv0, &ip0->src_address, &ip0->dst_address, ip0->protocol,
+                      rx_fib_index0, udp0->src_port, udp0->dst_port);
+
+          if (clib_bihash_search_16_8 (&tsm->in2out_ed, &kv0, &value0))
+            {
+              if (is_slow_path)
+                {
+                  if (is_output_feature)
+                    {
+                      if (PREDICT_FALSE(nat44_ed_not_translate_output_feature(
+                          sm, ip0, ip0->protocol, udp0->src_port,
+                          udp0->dst_port, thread_index, sw_if_index0)))
+                        goto trace00;
+                    }
+                  else
+                    {
+                      if (PREDICT_FALSE(nat44_ed_not_translate(sm, node,
+                          sw_if_index0, ip0, proto0, rx_fib_index0,
+                          thread_index)))
+                        goto trace00;
+                    }
+
+                  next0 = slow_path_ed (sm, b0, rx_fib_index0, &kv0, &s0, node,
+                                        next0, thread_index);
+
+                  if (PREDICT_FALSE (next0 == SNAT_IN2OUT_NEXT_DROP))
+                    goto trace00;
+                }
+              else
+                {
+                  next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
+                  goto trace00;
+                }
+            }
+          else
+            {
+              s0 = pool_elt_at_index (tsm->sessions, value0.value);
+            }
+
+          b0->flags |= VNET_BUFFER_F_IS_NATED;
+
+          if (!is_output_feature)
+            vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->out2in.fib_index;
+
+          old_addr0 = ip0->src_address.as_u32;
+          new_addr0 = ip0->src_address.as_u32 = s0->out2in.addr.as_u32;
+          sum0 = ip0->checksum;
+          sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                 src_address);
+          if (PREDICT_FALSE (is_twice_nat_session (s0)))
+            sum0 = ip_csum_update (sum0, ip0->dst_address.as_u32,
+                                   s0->ext_host_addr.as_u32, ip4_header_t,
+                                   dst_address);
+          ip0->checksum = ip_csum_fold (sum0);
+
+          if (PREDICT_TRUE (proto0 == SNAT_PROTOCOL_TCP))
+            {
+              old_port0 = tcp0->src_port;
+              new_port0 = tcp0->src_port = s0->out2in.port;
+
+              sum0 = tcp0->checksum;
+              sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                     dst_address);
+              sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t,
+                                     length);
+              if (PREDICT_FALSE (is_twice_nat_session (s0)))
+                {
+                  sum0 = ip_csum_update (sum0, ip0->dst_address.as_u32,
+                                         s0->ext_host_addr.as_u32,
+                                         ip4_header_t, dst_address);
+                  sum0 = ip_csum_update (sum0, tcp0->dst_port,
+                                         s0->ext_host_port, ip4_header_t,
+                                         length);
+                  tcp0->dst_port = s0->ext_host_port;
+                  ip0->dst_address.as_u32 = s0->ext_host_addr.as_u32;
+                }
+              tcp0->checksum = ip_csum_fold(sum0);
+              if (nat44_set_tcp_session_state_i2o (sm, s0, tcp0, thread_index))
+                goto trace00;
+            }
+          else
+            {
+              udp0->src_port = s0->out2in.port;
+              udp0->checksum = 0;
+              if (PREDICT_FALSE (is_twice_nat_session (s0)))
+                {
+                  udp0->dst_port = s0->ext_host_port;
+                  ip0->dst_address.as_u32 = s0->ext_host_addr.as_u32;
+                }
+            }
+
+          /* Accounting */
+          nat44_session_update_counters (s0, now,
+                                         vlib_buffer_length_in_chain (vm, b0));
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s0, thread_index);
+
+        trace00:
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b0->flags & VLIB_BUFFER_IS_TRACED)))
+            {
+              snat_in2out_trace_t *t =
+                vlib_add_trace (vm, node, b0, sizeof (*t));
+              t->is_slow_path = is_slow_path;
+              t->sw_if_index = sw_if_index0;
+              t->next_index = next0;
+              t->session_index = ~0;
+              if (s0)
+                t->session_index = s0 - tsm->sessions;
+            }
+
+          pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
+
+
+          next1 = SNAT_IN2OUT_NEXT_LOOKUP;
+
+          if (is_output_feature)
+            iph_offset1 = vnet_buffer (b1)->ip.save_rewrite_length;
+
+          ip1 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b1) +
+                 iph_offset1);
+
+          sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX];
+         rx_fib_index1 = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                               sw_if_index1);
+
+          if (PREDICT_FALSE(ip1->ttl == 1))
+            {
+              vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0;
+              icmp4_error_set_vnet_buffer (b1, ICMP4_time_exceeded,
+                                           ICMP4_time_exceeded_ttl_exceeded_in_transit,
+                                           0);
+              next1 = SNAT_IN2OUT_NEXT_ICMP_ERROR;
+              goto trace01;
+            }
+
+          udp1 = ip4_next_header (ip1);
+          tcp1 = (tcp_header_t *) udp1;
+          icmp1 = (icmp46_header_t *) udp1;
+          proto1 = ip_proto_to_snat_proto (ip1->protocol);
+
+          if (is_slow_path)
+            {
+              if (PREDICT_FALSE (proto1 == ~0))
+                {
+                  s1 = nat44_ed_in2out_unknown_proto (sm, b1, ip1,
+                                                      rx_fib_index1,
+                                                      thread_index, now, vm,
+                                                      node);
+                  if (!s1)
+                    next1 = SNAT_IN2OUT_NEXT_DROP;
+                  goto trace01;
+                }
+
+              if (PREDICT_FALSE (proto1 == SNAT_PROTOCOL_ICMP))
+                {
+                  next1 = icmp_in2out_slow_path
+                    (sm, b1, ip1, icmp1, sw_if_index1, rx_fib_index1, node,
+                     next1, now, thread_index, &s1);
+                  goto trace01;
+                }
+            }
+          else
+            {
+               if (is_output_feature)
+                {
+                  if (PREDICT_FALSE(nat_not_translate_output_feature_fwd(
+                      sm, ip1, thread_index, now, vm, b1)))
+                    goto trace01;
+                }
+
+              if (PREDICT_FALSE (proto1 == ~0 || proto1 == SNAT_PROTOCOL_ICMP))
+                {
+                  next1 = SNAT_IN2OUT_NEXT_SLOW_PATH;
+                  goto trace01;
+                }
+
+              if (ip4_is_fragment (ip1))
+                {
+                  b1->error = node->errors[SNAT_IN2OUT_ERROR_DROP_FRAGMENT];
+                  next1 = SNAT_IN2OUT_NEXT_DROP;
+                  goto trace01;
+                }
+            }
+
+          make_ed_kv (&kv1, &ip1->src_address, &ip1->dst_address, ip1->protocol,
+                      rx_fib_index1, udp1->src_port, udp1->dst_port);
+
+          if (clib_bihash_search_16_8 (&tsm->in2out_ed, &kv1, &value1))
+            {
+              if (is_slow_path)
+                {
+                  if (is_output_feature)
+                    {
+                      if (PREDICT_FALSE(nat44_ed_not_translate_output_feature(
+                          sm, ip1, ip1->protocol, udp1->src_port,
+                          udp1->dst_port, thread_index, sw_if_index1)))
+                        goto trace01;
+                    }
+                  else
+                    {
+                      if (PREDICT_FALSE(nat44_ed_not_translate(sm, node,
+                          sw_if_index1, ip1, proto1, rx_fib_index1,
+                          thread_index)))
+                        goto trace01;
+                    }
+
+                  next1 = slow_path_ed (sm, b1, rx_fib_index1, &kv1, &s1, node,
+                                        next1, thread_index);
+
+                  if (PREDICT_FALSE (next1 == SNAT_IN2OUT_NEXT_DROP))
+                    goto trace01;
+                }
+              else
+                {
+                  next1 = SNAT_IN2OUT_NEXT_SLOW_PATH;
+                  goto trace01;
+                }
+            }
+          else
+            {
+              s1 = pool_elt_at_index (tsm->sessions, value1.value);
+            }
+
+          b1->flags |= VNET_BUFFER_F_IS_NATED;
+
+          if (!is_output_feature)
+            vnet_buffer(b1)->sw_if_index[VLIB_TX] = s1->out2in.fib_index;
+
+          old_addr1 = ip1->src_address.as_u32;
+          new_addr1 = ip1->src_address.as_u32 = s1->out2in.addr.as_u32;
+          sum1 = ip1->checksum;
+          sum1 = ip_csum_update (sum1, old_addr1, new_addr1, ip4_header_t,
+                                 src_address);
+          if (PREDICT_FALSE (is_twice_nat_session (s1)))
+            sum1 = ip_csum_update (sum1, ip1->dst_address.as_u32,
+                                   s1->ext_host_addr.as_u32, ip4_header_t,
+                                   dst_address);
+          ip1->checksum = ip_csum_fold (sum1);
+
+          if (PREDICT_TRUE (proto1 == SNAT_PROTOCOL_TCP))
+            {
+              old_port1 = tcp1->src_port;
+              new_port1 = tcp1->src_port = s1->out2in.port;
+
+              sum1 = tcp1->checksum;
+              sum1 = ip_csum_update (sum1, old_addr1, new_addr1, ip4_header_t,
+                                     dst_address);
+              sum1 = ip_csum_update (sum1, old_port1, new_port1, ip4_header_t,
+                                     length);
+              if (PREDICT_FALSE (is_twice_nat_session (s1)))
+                {
+                  sum1 = ip_csum_update (sum1, ip1->dst_address.as_u32,
+                                         s1->ext_host_addr.as_u32,
+                                         ip4_header_t, dst_address);
+                  sum1 = ip_csum_update (sum1, tcp1->dst_port,
+                                         s1->ext_host_port, ip4_header_t,
+                                         length);
+                  tcp1->dst_port = s1->ext_host_port;
+                  ip1->dst_address.as_u32 = s1->ext_host_addr.as_u32;
+                }
+              tcp1->checksum = ip_csum_fold(sum1);
+              if (nat44_set_tcp_session_state_i2o (sm, s1, tcp1, thread_index))
+                goto trace01;
+            }
+          else
+            {
+              udp1->src_port = s1->out2in.port;
+              udp1->checksum = 0;
+              if (PREDICT_FALSE (is_twice_nat_session (s1)))
+                {
+                  udp1->dst_port = s1->ext_host_port;
+                  ip1->dst_address.as_u32 = s1->ext_host_addr.as_u32;
+                }
+            }
+
+          /* Accounting */
+          nat44_session_update_counters (s1, now,
+                                         vlib_buffer_length_in_chain (vm, b1));
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s1, thread_index);
+
+        trace01:
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b1->flags & VLIB_BUFFER_IS_TRACED)))
+            {
+              snat_in2out_trace_t *t =
+                vlib_add_trace (vm, node, b1, sizeof (*t));
+              t->is_slow_path = is_slow_path;
+              t->sw_if_index = sw_if_index1;
+              t->next_index = next1;
+              t->session_index = ~0;
+              if (s1)
+                t->session_index = s1 - tsm->sessions;
+            }
+
+          pkts_processed += next1 != SNAT_IN2OUT_NEXT_DROP;
+
+          /* verify speculative enqueues, maybe switch current next frame */
+          vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
+                                           to_next, n_left_to_next,
+                                           bi0, bi1, next0, next1);
+        }
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+          u32 bi0;
+         vlib_buffer_t *b0;
+          u32 next0, sw_if_index0, rx_fib_index0, iph_offset0 = 0, proto0,
+              new_addr0, old_addr0;
+          u16 old_port0, new_port0;
+          ip4_header_t *ip0;
+          udp_header_t *udp0;
+          tcp_header_t *tcp0;
+          icmp46_header_t * icmp0;
+          snat_session_t *s0 = 0;
+          clib_bihash_kv_16_8_t kv0, value0;
+          ip_csum_t sum0;
+
+          /* speculatively enqueue b0 to the current next frame */
+         bi0 = from[0];
+         to_next[0] = bi0;
+         from += 1;
+         to_next += 1;
+         n_left_from -= 1;
+         n_left_to_next -= 1;
+
+         b0 = vlib_get_buffer (vm, bi0);
+          next0 = SNAT_IN2OUT_NEXT_LOOKUP;
+
+          if (is_output_feature)
+            iph_offset0 = vnet_buffer (b0)->ip.save_rewrite_length;
+
+          ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) +
+                 iph_offset0);
+
+          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
+         rx_fib_index0 = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                               sw_if_index0);
+
+          if (PREDICT_FALSE(ip0->ttl == 1))
+            {
+              vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0;
+              icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded,
+                                           ICMP4_time_exceeded_ttl_exceeded_in_transit,
+                                           0);
+              next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR;
+              goto trace0;
+            }
+
+          udp0 = ip4_next_header (ip0);
+          tcp0 = (tcp_header_t *) udp0;
+          icmp0 = (icmp46_header_t *) udp0;
+          proto0 = ip_proto_to_snat_proto (ip0->protocol);
+
+          if (is_slow_path)
+            {
+              if (PREDICT_FALSE (proto0 == ~0))
+                {
+                  s0 = nat44_ed_in2out_unknown_proto (sm, b0, ip0,
+                                                      rx_fib_index0,
+                                                      thread_index, now, vm,
+                                                      node);
+                  if (!s0)
+                    next0 = SNAT_IN2OUT_NEXT_DROP;
+                  goto trace0;
+                }
+
+              if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = icmp_in2out_slow_path
+                    (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node,
+                     next0, now, thread_index, &s0);
+                  goto trace0;
+                }
+            }
+          else
+            {
+               if (is_output_feature)
+                {
+                  if (PREDICT_FALSE(nat_not_translate_output_feature_fwd(
+                      sm, ip0, thread_index, now, vm, b0)))
+                    goto trace0;
+                }
+
+              if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
+                  goto trace0;
+                }
+
+              if (ip4_is_fragment (ip0))
+                {
+                  b0->error = node->errors[SNAT_IN2OUT_ERROR_DROP_FRAGMENT];
+                  next0 = SNAT_IN2OUT_NEXT_DROP;
+                  goto trace0;
+                }
+            }
+
+          make_ed_kv (&kv0, &ip0->src_address, &ip0->dst_address, ip0->protocol,
+                      rx_fib_index0, udp0->src_port, udp0->dst_port);
+
+          if (clib_bihash_search_16_8 (&tsm->in2out_ed, &kv0, &value0))
+            {
+              if (is_slow_path)
+                {
+                  if (is_output_feature)
+                    {
+                      if (PREDICT_FALSE(nat44_ed_not_translate_output_feature(
+                          sm, ip0, ip0->protocol, udp0->src_port,
+                          udp0->dst_port, thread_index, sw_if_index0)))
+                        goto trace0;
+                    }
+                  else
+                    {
+                      if (PREDICT_FALSE(nat44_ed_not_translate(sm, node,
+                          sw_if_index0, ip0, proto0, rx_fib_index0,
+                          thread_index)))
+                        goto trace0;
+                    }
+
+                  next0 = slow_path_ed (sm, b0, rx_fib_index0, &kv0, &s0, node,
+                                        next0, thread_index);
+
+                  if (PREDICT_FALSE (next0 == SNAT_IN2OUT_NEXT_DROP))
+                    goto trace0;
+                }
+              else
+                {
+                  next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
+                  goto trace0;
+                }
+            }
+          else
+            {
+              s0 = pool_elt_at_index (tsm->sessions, value0.value);
+            }
+
+          b0->flags |= VNET_BUFFER_F_IS_NATED;
+
+          if (!is_output_feature)
+            vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->out2in.fib_index;
 
+          old_addr0 = ip0->src_address.as_u32;
+          new_addr0 = ip0->src_address.as_u32 = s0->out2in.addr.as_u32;
           sum0 = ip0->checksum;
-          sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-                                 ip4_header_t,
-                                 src_address /* changed member */);
+          sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                 src_address);
+          if (PREDICT_FALSE (is_twice_nat_session (s0)))
+            sum0 = ip_csum_update (sum0, ip0->dst_address.as_u32,
+                                   s0->ext_host_addr.as_u32, ip4_header_t,
+                                   dst_address);
           ip0->checksum = ip_csum_fold (sum0);
 
-          if (PREDICT_FALSE (ip4_is_first_fragment (ip0)))
+          if (PREDICT_TRUE (proto0 == SNAT_PROTOCOL_TCP))
             {
-              if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP))
-                {
-                  old_port0 = tcp0->src_port;
-                  tcp0->src_port = s0->out2in.port;
-                  new_port0 = tcp0->src_port;
+              old_port0 = tcp0->src_port;
+              new_port0 = tcp0->src_port = s0->out2in.port;
 
-                  sum0 = tcp0->checksum;
-                  sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-                                         ip4_header_t,
-                                         dst_address /* changed member */);
-                  sum0 = ip_csum_update (sum0, old_port0, new_port0,
-                                         ip4_header_t /* cheat */,
-                                         length /* changed member */);
-                  tcp0->checksum = ip_csum_fold(sum0);
+              sum0 = tcp0->checksum;
+              sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                     dst_address);
+              sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t,
+                                     length);
+              if (PREDICT_FALSE (is_twice_nat_session (s0)))
+                {
+                  sum0 = ip_csum_update (sum0, ip0->dst_address.as_u32,
+                                         s0->ext_host_addr.as_u32,
+                                         ip4_header_t, dst_address);
+                  sum0 = ip_csum_update (sum0, tcp0->dst_port,
+                                         s0->ext_host_port, ip4_header_t,
+                                         length);
+                  tcp0->dst_port = s0->ext_host_port;
+                  ip0->dst_address.as_u32 = s0->ext_host_addr.as_u32;
                 }
-              else
+              tcp0->checksum = ip_csum_fold(sum0);
+              if (nat44_set_tcp_session_state_i2o (sm, s0, tcp0, thread_index))
+                goto trace0;
+            }
+          else
+            {
+              udp0->src_port = s0->out2in.port;
+              udp0->checksum = 0;
+              if (PREDICT_FALSE (is_twice_nat_session (s0)))
                 {
-                  old_port0 = udp0->src_port;
-                  udp0->src_port = s0->out2in.port;
-                  udp0->checksum = 0;
+                  udp0->dst_port = s0->ext_host_port;
+                  ip0->dst_address.as_u32 = s0->ext_host_addr.as_u32;
                 }
             }
 
-          /* Hairpinning */
-          nat44_reass_hairpinning (sm, b0, ip0, s0->out2in.port,
-                                   s0->ext_host_port, proto0);
-
           /* Accounting */
           nat44_session_update_counters (s0, now,
                                          vlib_buffer_length_in_chain (vm, b0));
@@ -2689,87 +3511,171 @@ nat44_in2out_reass_node_fn (vlib_main_t * vm,
           if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
                             && (b0->flags & VLIB_BUFFER_IS_TRACED)))
             {
-              nat44_in2out_reass_trace_t *t =
-                 vlib_add_trace (vm, node, b0, sizeof (*t));
-              t->cached = cached0;
+              snat_in2out_trace_t *t =
+                vlib_add_trace (vm, node, b0, sizeof (*t));
+              t->is_slow_path = is_slow_path;
               t->sw_if_index = sw_if_index0;
               t->next_index = next0;
+              t->session_index = ~0;
+              if (s0)
+                t->session_index = s0 - tsm->sessions;
             }
 
-          if (cached0)
-            {
-              n_left_to_next++;
-              to_next--;
-            }
-          else
-            {
-              pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
-
-              /* verify speculative enqueue, maybe switch current next frame */
-              vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
-                                               to_next, n_left_to_next,
-                                               bi0, next0);
-            }
+          pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
 
-         if (n_left_from == 0 && vec_len (fragments_to_loopback))
-           {
-             from = vlib_frame_vector_args (frame);
-             u32 len = vec_len (fragments_to_loopback);
-             if (len <= VLIB_FRAME_SIZE)
-               {
-                 clib_memcpy (from, fragments_to_loopback, sizeof (u32) * len);
-                 n_left_from = len;
-                 vec_reset_length (fragments_to_loopback);
-               }
-             else
-               {
-                 clib_memcpy (from,
-                               fragments_to_loopback + (len - VLIB_FRAME_SIZE),
-                               sizeof (u32) * VLIB_FRAME_SIZE);
-                 n_left_from = VLIB_FRAME_SIZE;
-                 _vec_len (fragments_to_loopback) = len - VLIB_FRAME_SIZE;
-               }
-           }
-       }
+          /* verify speculative enqueue, maybe switch current next frame */
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, next0);
+        }
 
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
 
-  vlib_node_increment_counter (vm, nat44_in2out_reass_node.index,
+  vlib_node_increment_counter (vm, stats_node_index,
                                SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
                                pkts_processed);
+  return frame->n_vectors;
+}
 
-  nat_send_all_to_node (vm, fragments_to_drop, node,
-                        &node->errors[SNAT_IN2OUT_ERROR_DROP_FRAGMENT],
-                        SNAT_IN2OUT_NEXT_DROP);
+static uword
+nat44_ed_in2out_fast_path_fn (vlib_main_t * vm,
+                              vlib_node_runtime_t * node,
+                              vlib_frame_t * frame)
+{
+  return nat44_ed_in2out_node_fn_inline (vm, node, frame, 0, 0);
+}
 
-  vec_free (fragments_to_drop);
-  vec_free (fragments_to_loopback);
-  return frame->n_vectors;
+VLIB_REGISTER_NODE (nat44_ed_in2out_node) = {
+  .function = nat44_ed_in2out_fast_path_fn,
+  .name = "nat44-ed-in2out",
+  .vector_size = sizeof (u32),
+  .format_trace = format_snat_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+
+  .runtime_data_bytes = sizeof (snat_runtime_t),
+
+  .n_next_nodes = SNAT_IN2OUT_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+    [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+    [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-ed-in2out-slowpath",
+    [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [SNAT_IN2OUT_NEXT_REASS] = "nat44-in2out-reass",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_in2out_node, nat44_ed_in2out_fast_path_fn);
+
+static uword
+nat44_ed_in2out_output_fast_path_fn (vlib_main_t * vm,
+                                     vlib_node_runtime_t * node,
+                                     vlib_frame_t * frame)
+{
+  return nat44_ed_in2out_node_fn_inline (vm, node, frame, 0, 1);
 }
 
-VLIB_REGISTER_NODE (nat44_in2out_reass_node) = {
-  .function = nat44_in2out_reass_node_fn,
-  .name = "nat44-in2out-reass",
+VLIB_REGISTER_NODE (nat44_ed_in2out_output_node) = {
+  .function = nat44_ed_in2out_output_fast_path_fn,
+  .name = "nat44-ed-in2out-output",
   .vector_size = sizeof (u32),
-  .format_trace = format_nat44_in2out_reass_trace,
+  .format_trace = format_snat_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+
+  .runtime_data_bytes = sizeof (snat_runtime_t),
+
+  .n_next_nodes = SNAT_IN2OUT_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+    [SNAT_IN2OUT_NEXT_LOOKUP] = "interface-output",
+    [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-ed-in2out-output-slowpath",
+    [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [SNAT_IN2OUT_NEXT_REASS] = "nat44-in2out-reass",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_in2out_output_node,
+                              nat44_ed_in2out_output_fast_path_fn);
+
+static uword
+nat44_ed_in2out_slow_path_fn (vlib_main_t * vm,
+                              vlib_node_runtime_t * node,
+                              vlib_frame_t * frame)
+{
+  return nat44_ed_in2out_node_fn_inline (vm, node, frame, 1, 0);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_in2out_slowpath_node) = {
+  .function = nat44_ed_in2out_slow_path_fn,
+  .name = "nat44-ed-in2out-slowpath",
+  .vector_size = sizeof (u32),
+  .format_trace = format_snat_in2out_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
 
   .n_errors = ARRAY_LEN(snat_in2out_error_strings),
   .error_strings = snat_in2out_error_strings,
 
+  .runtime_data_bytes = sizeof (snat_runtime_t),
+
   .n_next_nodes = SNAT_IN2OUT_N_NEXT,
+
+  /* edit / add dispositions here */
   .next_nodes = {
     [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
     [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
-    [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-slowpath",
+    [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-ed-in2out-slowpath",
     [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
     [SNAT_IN2OUT_NEXT_REASS] = "nat44-in2out-reass",
   },
 };
 
-VLIB_NODE_FUNCTION_MULTIARCH (nat44_in2out_reass_node,
-                              nat44_in2out_reass_node_fn);
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_in2out_slowpath_node,
+                              nat44_ed_in2out_slow_path_fn);
+
+static uword
+nat44_ed_in2out_output_slow_path_fn (vlib_main_t * vm,
+                                     vlib_node_runtime_t * node,
+                                     vlib_frame_t * frame)
+{
+  return nat44_ed_in2out_node_fn_inline (vm, node, frame, 1, 1);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_in2out_output_slowpath_node) = {
+  .function = nat44_ed_in2out_output_slow_path_fn,
+  .name = "nat44-ed-in2out-output-slowpath",
+  .vector_size = sizeof (u32),
+  .format_trace = format_snat_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+
+  .runtime_data_bytes = sizeof (snat_runtime_t),
+
+  .n_next_nodes = SNAT_IN2OUT_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+    [SNAT_IN2OUT_NEXT_LOOKUP] = "interface-output",
+    [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-ed-in2out-output-slowpath",
+    [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [SNAT_IN2OUT_NEXT_REASS] = "nat44-in2out-reass",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_in2out_output_slowpath_node,
+                              nat44_ed_in2out_output_slow_path_fn);
 
 /**************************/
 /*** deterministic mode ***/
@@ -3791,16 +4697,20 @@ is_hairpinning (snat_main_t *sm, ip4_address_t * dst_addr)
   return 0;
 }
 
-static uword
-snat_hairpin_dst_fn (vlib_main_t * vm,
-                     vlib_node_runtime_t * node,
-                     vlib_frame_t * frame)
+static inline uword
+snat_hairpin_dst_fn_inline (vlib_main_t * vm,
+                            vlib_node_runtime_t * node,
+                            vlib_frame_t * frame,
+                            int is_ed)
 {
-  u32 n_left_from, * from, * to_next;
+  u32 n_left_from, * from, * to_next, stats_node_index;
   snat_in2out_next_t next_index;
   u32 pkts_processed = 0;
   snat_main_t * sm = &snat_main;
 
+  stats_node_index = is_ed ? nat44_ed_hairpin_dst_node.index :
+    snat_hairpin_dst_node.index;
+
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
   next_index = node->cached_next_index;
@@ -3842,17 +4752,20 @@ snat_hairpin_dst_fn (vlib_main_t * vm,
                   udp_header_t * udp0 = ip4_next_header (ip0);
                   tcp_header_t * tcp0 = (tcp_header_t *) udp0;
 
-                  snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0);
+                  snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0, is_ed);
                 }
               else if (proto0 == SNAT_PROTOCOL_ICMP)
                 {
                   icmp46_header_t * icmp0 = ip4_next_header (ip0);
 
-                  snat_icmp_hairpinning (sm, b0, ip0, icmp0);
+                  snat_icmp_hairpinning (sm, b0, ip0, icmp0, is_ed);
                 }
               else
                 {
-                  snat_hairpinning_unknown_proto (sm, b0, ip0);
+                  if (is_ed)
+                    nat44_ed_hairpinning_unknown_proto (sm, b0, ip0);
+                  else
+                    nat_hairpinning_sm_unknown_proto (sm, b0, ip0);
                 }
 
               vnet_buffer (b0)->snat.flags = SNAT_FLAG_HAIRPINNING;
@@ -3869,12 +4782,20 @@ snat_hairpin_dst_fn (vlib_main_t * vm,
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
 
-  vlib_node_increment_counter (vm, snat_hairpin_dst_node.index,
+  vlib_node_increment_counter (vm, stats_node_index,
                                SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
                                pkts_processed);
   return frame->n_vectors;
 }
 
+static uword
+snat_hairpin_dst_fn (vlib_main_t * vm,
+                     vlib_node_runtime_t * node,
+                     vlib_frame_t * frame)
+{
+  return snat_hairpin_dst_fn_inline (vm, node, frame, 0);
+}
+
 VLIB_REGISTER_NODE (snat_hairpin_dst_node) = {
   .function = snat_hairpin_dst_fn,
   .name = "nat44-hairpin-dst",
@@ -3893,15 +4814,44 @@ VLIB_NODE_FUNCTION_MULTIARCH (snat_hairpin_dst_node,
                               snat_hairpin_dst_fn);
 
 static uword
-snat_hairpin_src_fn (vlib_main_t * vm,
-                     vlib_node_runtime_t * node,
-                     vlib_frame_t * frame)
+nat44_ed_hairpin_dst_fn (vlib_main_t * vm,
+                         vlib_node_runtime_t * node,
+                         vlib_frame_t * frame)
 {
-  u32 n_left_from, * from, * to_next;
+  return snat_hairpin_dst_fn_inline (vm, node, frame, 1);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_hairpin_dst_node) = {
+  .function = nat44_ed_hairpin_dst_fn,
+  .name = "nat44-ed-hairpin-dst",
+  .vector_size = sizeof (u32),
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+  .n_next_nodes = 2,
+  .next_nodes = {
+    [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+    [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_hairpin_dst_node,
+                              nat44_ed_hairpin_dst_fn);
+
+static inline uword
+snat_hairpin_src_fn_inline (vlib_main_t * vm,
+                            vlib_node_runtime_t * node,
+                            vlib_frame_t * frame,
+                            int is_ed)
+{
+  u32 n_left_from, * from, * to_next, stats_node_index;
   snat_in2out_next_t next_index;
   u32 pkts_processed = 0;
   snat_main_t *sm = &snat_main;
 
+  stats_node_index = is_ed ? nat44_ed_hairpin_src_node.index :
+    snat_hairpin_src_node.index;
+
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
   next_index = node->cached_next_index;
@@ -3961,12 +4911,20 @@ snat_hairpin_src_fn (vlib_main_t * vm,
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
 
-  vlib_node_increment_counter (vm, snat_hairpin_src_node.index,
+  vlib_node_increment_counter (vm, stats_node_index,
                                SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
                                pkts_processed);
   return frame->n_vectors;
 }
 
+static uword
+snat_hairpin_src_fn (vlib_main_t * vm,
+                     vlib_node_runtime_t * node,
+                     vlib_frame_t * frame)
+{
+  return snat_hairpin_src_fn_inline (vm, node, frame, 0);
+}
+
 VLIB_REGISTER_NODE (snat_hairpin_src_node) = {
   .function = snat_hairpin_src_fn,
   .name = "nat44-hairpin-src",
@@ -3986,6 +4944,33 @@ VLIB_REGISTER_NODE (snat_hairpin_src_node) = {
 VLIB_NODE_FUNCTION_MULTIARCH (snat_hairpin_src_node,
                               snat_hairpin_src_fn);
 
+static uword
+nat44_ed_hairpin_src_fn (vlib_main_t * vm,
+                         vlib_node_runtime_t * node,
+                         vlib_frame_t * frame)
+{
+  return snat_hairpin_src_fn_inline (vm, node, frame, 1);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_hairpin_src_node) = {
+  .function = nat44_ed_hairpin_src_fn,
+  .name = "nat44-ed-hairpin-src",
+  .vector_size = sizeof (u32),
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+  .error_strings = snat_in2out_error_strings,
+  .n_next_nodes = SNAT_HAIRPIN_SRC_N_NEXT,
+  .next_nodes = {
+     [SNAT_HAIRPIN_SRC_NEXT_DROP] = "error-drop",
+     [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT] = "nat44-ed-in2out-output",
+     [SNAT_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT] = "interface-output",
+     [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH] = "nat44-in2out-output-worker-handoff",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_hairpin_src_node,
+                              nat44_ed_hairpin_src_fn);
+
 static uword
 snat_in2out_fast_static_map_fn (vlib_main_t * vm,
                                 vlib_node_runtime_t * node,
@@ -4128,7 +5113,7 @@ snat_in2out_fast_static_map_fn (vlib_main_t * vm,
             }
 
           /* Hairpinning */
-          snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0);
+          snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0, 0);
 
         trace0:
           if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
index 5953c42..f236568 100755 (executable)
@@ -45,7 +45,7 @@ VNET_FEATURE_INIT (ip4_snat_in2out, static) = {
 VNET_FEATURE_INIT (ip4_snat_out2in, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa", 
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
                                "ip4-dhcp-client-detect"),
 };
 VNET_FEATURE_INIT (ip4_nat_classify, static) = {
@@ -61,13 +61,30 @@ VNET_FEATURE_INIT (ip4_snat_det_in2out, static) = {
 VNET_FEATURE_INIT (ip4_snat_det_out2in, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-out2in",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
+                               "ip4-dhcp-client-detect"),
 };
 VNET_FEATURE_INIT (ip4_nat_det_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-classify",
   .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
+VNET_FEATURE_INIT (ip4_nat44_ed_in2out, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "nat44-ed-in2out",
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
+};
+VNET_FEATURE_INIT (ip4_nat44_ed_out2in, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "nat44-ed-out2in",
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
+                               "ip4-dhcp-client-detect"),
+};
+VNET_FEATURE_INIT (ip4_nat44_ed_classify, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "nat44-ed-classify",
+  .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",
@@ -76,7 +93,8 @@ VNET_FEATURE_INIT (ip4_snat_in2out_worker_handoff, static) = {
 VNET_FEATURE_INIT (ip4_snat_out2in_worker_handoff, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in-worker-handoff",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
+                               "ip4-dhcp-client-detect"),
 };
 VNET_FEATURE_INIT (ip4_nat_handoff_classify, static) = {
   .arc_name = "ip4-unicast",
@@ -91,13 +109,19 @@ VNET_FEATURE_INIT (ip4_snat_in2out_fast, static) = {
 VNET_FEATURE_INIT (ip4_snat_out2in_fast, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in-fast",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
+                               "ip4-dhcp-client-detect"),
 };
 VNET_FEATURE_INIT (ip4_snat_hairpin_dst, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-hairpin-dst",
   .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
+VNET_FEATURE_INIT (ip4_nat44_ed_hairpin_dst, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "nat44-ed-hairpin-dst",
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
+};
 
 /* Hook up output features */
 VNET_FEATURE_INIT (ip4_snat_in2out_output, static) = {
@@ -115,6 +139,16 @@ VNET_FEATURE_INIT (ip4_snat_hairpin_src, static) = {
   .node_name = "nat44-hairpin-src",
   .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
+VNET_FEATURE_INIT (ip4_nat44_ed_in2out_output, static) = {
+  .arc_name = "ip4-output",
+  .node_name = "nat44-ed-in2out-output",
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
+};
+VNET_FEATURE_INIT (ip4_nat44_ed_hairpin_src, static) = {
+  .arc_name = "ip4-output",
+  .node_name = "nat44-ed-hairpin-src",
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
+};
 
 /* Hook up ip4-local features */
 VNET_FEATURE_INIT (ip4_nat_hairpinning, static) =
@@ -123,6 +157,12 @@ VNET_FEATURE_INIT (ip4_nat_hairpinning, static) =
   .node_name = "nat44-hairpinning",
   .runs_before = VNET_FEATURES("ip4-local-end-of-arc"),
 };
+VNET_FEATURE_INIT (ip4_nat44_ed_hairpinning, static) =
+{
+  .arc_name = "ip4-local",
+  .node_name = "nat44-ed-hairpinning",
+  .runs_before = VNET_FEATURES("ip4-local-end-of-arc"),
+};
 
 
 /* *INDENT-OFF* */
@@ -133,6 +173,7 @@ VLIB_PLUGIN_REGISTER () = {
 /* *INDENT-ON* */
 
 vlib_node_registration_t nat44_classify_node;
+vlib_node_registration_t nat44_ed_classify_node;
 vlib_node_registration_t nat44_det_classify_node;
 vlib_node_registration_t nat44_handoff_classify_node;
 
@@ -164,12 +205,12 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
       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))
+      if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &ed_kv, 0))
         nat_log_warn ("in2out_ed key del failed");
       return;
     }
 
-  /* Endpoint dependent session lookup tables */
+  /* session lookup tables */
   if (is_ed_session (s))
     {
       ed_key.l_addr = s->out2in.addr;
@@ -189,7 +230,7 @@ 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 (&tsm->out2in_ed, &ed_kv, 0))
         nat_log_warn ("out2in_ed key del failed");
 
       ed_key.l_addr = s->in2out.addr;
@@ -203,9 +244,18 @@ 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 (&tsm->in2out_ed, &ed_kv, 0))
         nat_log_warn ("in2out_ed key del failed");
     }
+  else
+    {
+      kv.key = s->in2out.as_u64;
+      if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 0))
+        nat_log_warn ("in2out key del failed");
+      kv.key = s->out2in.as_u64;
+      if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0))
+        nat_log_warn ("out2in key del failed");
+    }
 
   if (snat_is_unk_proto_session (s))
     return;
@@ -235,17 +285,6 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
         }
     }
 
-  if (is_ed_session (s))
-    return;
-
-  /* Session lookup tables */
-  kv.key = s->in2out.as_u64;
-  if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 0))
-    nat_log_warn ("in2out key del failed");
-  kv.key = s->out2in.as_u64;
-  if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0))
-    nat_log_warn ("out2in key del failed");
-
   if (snat_is_session_static (s))
     return;
 
@@ -507,6 +546,29 @@ VLIB_REGISTER_NODE (nat44_classify_node) = {
 
 VLIB_NODE_FUNCTION_MULTIARCH (nat44_classify_node,
                               nat44_classify_node_fn);
+static uword
+nat44_ed_classify_node_fn (vlib_main_t * vm,
+                           vlib_node_runtime_t * node,
+                           vlib_frame_t * frame)
+{
+  return nat44_classify_node_fn_inline (vm, node, frame);
+};
+
+VLIB_REGISTER_NODE (nat44_ed_classify_node) = {
+  .function = nat44_ed_classify_node_fn,
+  .name = "nat44-ed-classify",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_classify_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_next_nodes = NAT44_CLASSIFY_N_NEXT,
+  .next_nodes = {
+    [NAT44_CLASSIFY_NEXT_IN2OUT] = "nat44-ed-in2out",
+    [NAT44_CLASSIFY_NEXT_OUT2IN] = "nat44-ed-out2in",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_classify_node,
+                              nat44_ed_classify_node_fn);
 
 static uword
 nat44_det_classify_node_fn (vlib_main_t * vm,
@@ -601,18 +663,21 @@ snat_add_del_addr_to_fib (ip4_address_t * addr, u8 p_len, u32 sw_if_index,
                            FIB_SOURCE_PLUGIN_LOW);
 }
 
-void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
+int snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
                        u8 twice_nat)
 {
   snat_address_t * ap;
   snat_interface_t *i;
   vlib_thread_main_t *tm = vlib_get_thread_main ();
 
+  if (twice_nat && !sm->endpoint_dependent)
+    return VNET_API_ERROR_FEATURE_DISABLED;
+
   /* Check if address already exists */
   vec_foreach (ap, twice_nat ? sm->twice_nat_addresses : sm->addresses)
     {
       if (ap->addr.as_u32 == addr->as_u32)
-        return;
+        return VNET_API_ERROR_VALUE_EXIST;
     }
 
   if (twice_nat)
@@ -635,7 +700,7 @@ void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
 #undef _
 
   if (twice_nat)
-    return;
+    return 0;
 
   /* Add external address to FIB */
   pool_foreach (i, sm->interfaces,
@@ -654,6 +719,8 @@ void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
     snat_add_del_addr_to_fib(addr, 32, i->sw_if_index, 1);
     break;
   }));
+
+  return 0;
 }
 
 static int is_snat_address_used_in_static_mapping (snat_main_t *sm,
@@ -751,6 +818,12 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
   snat_session_t * s;
   snat_static_map_resolve_t *rp, *rp_match = 0;
 
+  if (!sm->endpoint_dependent)
+    {
+      if (twice_nat || out2in_only)
+        return VNET_API_ERROR_FEATURE_DISABLED;
+    }
+
   /* If the external address is a specific interface address */
   if (sw_if_index != ~0)
     {
@@ -936,13 +1009,13 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
           m->proto = proto;
         }
 
-      if (sm->workers)
+      if (sm->num_workers > 1)
         {
           ip4_header_t ip = {
             .src_address = m->local_addr,
           };
-          m->worker_index = sm->worker_in2out_cb (&ip, m->fib_index);
-          tsm = vec_elt_at_index (sm->per_thread_data, m->worker_index);
+          vec_add1 (m->workers, sm->worker_in2out_cb (&ip, m->fib_index));
+          tsm = vec_elt_at_index (sm->per_thread_data, m->workers[0]);
         }
       else
         tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers);
@@ -955,14 +1028,6 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
       kv.value = m - sm->static_mappings;
       if (!out2in_only)
         clib_bihash_add_del_8_8(&sm->static_mapping_by_local, &kv, 1);
-      if (twice_nat || out2in_only)
-        {
-          m_key.port = clib_host_to_net_u16 (m->local_port);
-          kv.key = m_key.as_u64;
-          kv.value = ~0ULL;
-          if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 1))
-            nat_log_warn ("in2out key add failed");
-        }
 
       m_key.addr = m->external_addr;
       m_key.port = m->external_port;
@@ -970,14 +1035,6 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
       kv.key = m_key.as_u64;
       kv.value = m - sm->static_mappings;
       clib_bihash_add_del_8_8(&sm->static_mapping_by_external, &kv, 1);
-      if (twice_nat || out2in_only)
-        {
-          m_key.port = clib_host_to_net_u16 (e_port);
-          kv.key = m_key.as_u64;
-          kv.value = ~0ULL;
-          if (clib_bihash_add_del_8_8(&tsm->out2in, &kv, 1))
-            nat_log_warn ("out2in key add failed");
-        }
 
       /* Delete dynamic sessions matching local address (+ local port) */
       if (!(sm->static_mapping_only))
@@ -1059,7 +1116,7 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
         }
 
       if (sm->num_workers > 1)
-        tsm = vec_elt_at_index (sm->per_thread_data, m->worker_index);
+        tsm = vec_elt_at_index (sm->per_thread_data, m->workers[0]);
       else
         tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers);
 
@@ -1070,28 +1127,12 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
       kv.key = m_key.as_u64;
       if (!out2in_only)
         clib_bihash_add_del_8_8(&sm->static_mapping_by_local, &kv, 0);
-      if (twice_nat || out2in_only)
-        {
-          m_key.port = clib_host_to_net_u16 (m->local_port);
-          kv.key = m_key.as_u64;
-          kv.value = ~0ULL;
-          if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 0))
-            nat_log_warn ("in2out key del failed");
-        }
 
       m_key.addr = m->external_addr;
       m_key.port = m->external_port;
       m_key.fib_index = sm->outside_fib_index;
       kv.key = m_key.as_u64;
       clib_bihash_add_del_8_8(&sm->static_mapping_by_external, &kv, 0);
-      if (twice_nat || out2in_only)
-        {
-          m_key.port = clib_host_to_net_u16 (m->external_port);
-          kv.key = m_key.as_u64;
-          kv.value = ~0ULL;
-          if (clib_bihash_add_del_8_8(&tsm->out2in, &kv, 0))
-            nat_log_warn ("out2in key del failed");
-        }
 
       /* Delete session(s) for static mapping if exist */
       if (!(sm->static_mapping_only) ||
@@ -1146,6 +1187,7 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
         }
 
       vec_free (m->tag);
+      vec_free (m->workers);
       /* Delete static mapping from pool */
       pool_put (sm->static_mappings, m);
     }
@@ -1174,33 +1216,6 @@ 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,
@@ -1215,12 +1230,16 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
   snat_address_t *a = 0;
   int i;
   nat44_lb_addr_port_t *local;
-  u32 worker_index = 0, elt_index, head_index, ses_index;
+  u32 elt_index, head_index, ses_index;
   snat_main_per_thread_data_t *tsm;
   snat_user_key_t u_key;
   snat_user_t *u;
   snat_session_t * s;
   dlist_elt_t * head, * elt;
+  uword *bitmap = 0;
+
+  if (!sm->endpoint_dependent)
+    return VNET_API_ERROR_FEATURE_DISABLED;
 
   m_key.addr = e_addr;
   m_key.port = e_port;
@@ -1305,26 +1324,6 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
           return VNET_API_ERROR_UNSPECIFIED;
         }
 
-      /* Assign worker */
-      if (sm->workers)
-        {
-          worker_index = sm->first_worker_index +
-            sm->workers[sm->next_worker++ % vec_len (sm->workers)];
-          tsm = vec_elt_at_index (sm->per_thread_data, worker_index);
-          m->worker_index = worker_index;
-        }
-      else
-        tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers);
-
-      m_key.port = clib_host_to_net_u16 (m->external_port);
-      kv.key = m_key.as_u64;
-      kv.value = ~0ULL;
-      if (clib_bihash_add_del_8_8(&tsm->out2in, &kv, 1))
-        {
-          nat_log_err ("out2in key add failed");
-          return VNET_API_ERROR_UNSPECIFIED;
-        }
-
       m_key.fib_index = m->fib_index;
       for (i = 0; i < vec_len (locals); i++)
         {
@@ -1339,16 +1338,24 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
           locals[i].prefix = (i == 0) ? locals[i].probability :\
             (locals[i - 1].prefix + locals[i].probability);
           vec_add1 (m->locals, locals[i]);
-
-          m_key.port = clib_host_to_net_u16 (locals[i].port);
-          kv.key = m_key.as_u64;
-          kv.value = ~0ULL;
-          if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 1))
+          if (sm->num_workers > 1)
             {
-              nat_log_err ("in2out key add failed");
-              return VNET_API_ERROR_UNSPECIFIED;
+              ip4_header_t ip = {
+                .src_address = locals[i].addr,
+              };
+              bitmap = clib_bitmap_set (
+                bitmap, sm->worker_in2out_cb (&ip, m->fib_index), 1);
             }
         }
+
+      /* Assign workers */
+      if (sm->num_workers > 1)
+        {
+          clib_bitmap_foreach (i, bitmap,
+            ({
+               vec_add1(m->workers, i);
+            }));
+        }
     }
   else
     {
@@ -1387,7 +1394,6 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
             }
         }
 
-      tsm = vec_elt_at_index (sm->per_thread_data, m->worker_index);
       m_key.addr = m->external_addr;
       m_key.port = m->external_port;
       m_key.protocol = m->proto;
@@ -1399,14 +1405,6 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
           return VNET_API_ERROR_UNSPECIFIED;
         }
 
-      m_key.port = clib_host_to_net_u16 (m->external_port);
-      kv.key = m_key.as_u64;
-      if (clib_bihash_add_del_8_8(&tsm->out2in, &kv, 0))
-        {
-          nat_log_err ("outi2in key del failed");
-          return VNET_API_ERROR_UNSPECIFIED;
-        }
-
       vec_foreach (local, m->locals)
         {
           m_key.addr = local->addr;
@@ -1422,16 +1420,17 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                 }
             }
 
-          if (!lb_local_exists(local, &e_addr, e_port))
+          if (sm->num_workers > 1)
             {
-              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))
-                {
-                  nat_log_err ("in2out key del failed");
-                  return VNET_API_ERROR_UNSPECIFIED;
-                }
+              ip4_header_t ip = {
+                .src_address = local->addr,
+              };
+              tsm = vec_elt_at_index (sm->per_thread_data,
+                                      sm->worker_in2out_cb (&ip, m->fib_index));
             }
+          else
+            tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers);
+
           /* Delete sessions */
           u_key.addr = local->addr;
           u_key.fib_index = m->fib_index;
@@ -1467,6 +1466,7 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
         }
       vec_free(m->locals);
       vec_free(m->tag);
+      vec_free(m->workers);
 
       pool_put (sm->static_mappings, m);
     }
@@ -1604,6 +1604,8 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
         feature_name = is_inside ?  "nat44-in2out-worker-handoff" : "nat44-out2in-worker-handoff";
       else if (sm->deterministic)
         feature_name = is_inside ?  "nat44-det-in2out" : "nat44-det-out2in";
+      else if (sm->endpoint_dependent)
+        feature_name = is_inside ?  "nat44-ed-in2out" : "nat44-ed-out2in";
       else
         feature_name = is_inside ?  "nat44-in2out" : "nat44-out2in";
     }
@@ -1641,6 +1643,12 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
                     feature_name = !is_inside ?  "nat44-det-in2out" :
                                                  "nat44-det-out2in";
                   }
+                else if (sm->endpoint_dependent)
+                  {
+                    del_feature_name = "nat44-ed-classify";
+                    feature_name = !is_inside ?  "nat44-ed-in2out" :
+                                                 "nat44-ed-out2in";
+                  }
                 else
                   {
                     del_feature_name = "nat44-classify";
@@ -1652,8 +1660,16 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
                 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);
+                  {
+                    if (sm->endpoint_dependent)
+                      vnet_feature_enable_disable ("ip4-local",
+                                                   "nat44-ed-hairpinning",
+                                                   sw_if_index, 1, 0, 0);
+                    else
+                      vnet_feature_enable_disable ("ip4-local",
+                                                   "nat44-hairpinning",
+                                                   sw_if_index, 1, 0, 0);
+                  }
               }
             else
               {
@@ -1661,8 +1677,16 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
                                              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);
+                  {
+                    if (sm->endpoint_dependent)
+                      vnet_feature_enable_disable ("ip4-local",
+                                                   "nat44-ed-hairpinning",
+                                                   sw_if_index, 0, 0, 0);
+                    else
+                      vnet_feature_enable_disable ("ip4-local",
+                                                   "nat44-hairpinning",
+                                                   sw_if_index, 0, 0, 0);
+                  }
               }
           }
         else
@@ -1683,6 +1707,12 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
                                                  "nat44-det-out2in";
                 feature_name = "nat44-det-classify";
               }
+            else if (sm->endpoint_dependent)
+              {
+                del_feature_name = !is_inside ?  "nat44-ed-in2out" :
+                                                 "nat44-ed-out2in";
+                feature_name = "nat44-ed-classify";
+              }
             else
               {
                 del_feature_name = !is_inside ?  "nat44-in2out" : "nat44-out2in";
@@ -1694,8 +1724,14 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
             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);
+              {
+                if (sm->endpoint_dependent)
+                  vnet_feature_enable_disable ("ip4-local", "nat44-ed-hairpinning",
+                                               sw_if_index, 0, 0, 0);
+                else
+                  vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
+                                               sw_if_index, 0, 0, 0);
+              }
             goto set_flags;
           }
 
@@ -1712,8 +1748,14 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
   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);
+    {
+      if (sm->endpoint_dependent)
+        vnet_feature_enable_disable ("ip4-local", "nat44-ed-hairpinning",
+                                     sw_if_index, 1, 0, 0);
+      else
+        vnet_feature_enable_disable ("ip4-local", "nat44-hairpinning",
+                                     sw_if_index, 1, 0, 0);
+    }
 
 set_flags:
   if (is_inside)
@@ -1766,16 +1808,27 @@ int snat_interface_add_del_output_feature (u32 sw_if_index,
 
   if (is_inside)
     {
-      vnet_feature_enable_disable ("ip4-unicast", "nat44-hairpin-dst",
-                                   sw_if_index, !is_del, 0, 0);
-      vnet_feature_enable_disable ("ip4-output", "nat44-hairpin-src",
-                                   sw_if_index, !is_del, 0, 0);
+      if (sm->endpoint_dependent)
+        {
+          vnet_feature_enable_disable ("ip4-unicast", "nat44-ed-hairpin-dst",
+                                       sw_if_index, !is_del, 0, 0);
+          vnet_feature_enable_disable ("ip4-output", "nat44-ed-hairpin-src",
+                                       sw_if_index, !is_del, 0, 0);
+        }
+      else
+        {
+          vnet_feature_enable_disable ("ip4-unicast", "nat44-hairpin-dst",
+                                       sw_if_index, !is_del, 0, 0);
+          vnet_feature_enable_disable ("ip4-output", "nat44-hairpin-src",
+                                       sw_if_index, !is_del, 0, 0);
+        }
       goto fq;
     }
 
   if (sm->num_workers > 1)
     {
-      vnet_feature_enable_disable ("ip4-unicast", "nat44-out2in-worker-handoff",
+      vnet_feature_enable_disable ("ip4-unicast",
+                                   "nat44-out2in-worker-handoff",
                                    sw_if_index, !is_del, 0, 0);
       vnet_feature_enable_disable ("ip4-output",
                                    "nat44-in2out-output-worker-handoff",
@@ -1783,10 +1836,20 @@ int snat_interface_add_del_output_feature (u32 sw_if_index,
     }
   else
     {
-      vnet_feature_enable_disable ("ip4-unicast", "nat44-out2in", sw_if_index,
-                                   !is_del, 0, 0);
-      vnet_feature_enable_disable ("ip4-output", "nat44-in2out-output",
-                                   sw_if_index, !is_del, 0, 0);
+      if (sm->endpoint_dependent)
+        {
+          vnet_feature_enable_disable ("ip4-unicast", "nat44-ed-out2in",
+                                       sw_if_index, !is_del, 0, 0);
+          vnet_feature_enable_disable ("ip4-output", "nat44-ed-in2out-output",
+                                       sw_if_index, !is_del, 0, 0);
+        }
+      else
+        {
+          vnet_feature_enable_disable ("ip4-unicast", "nat44-out2in",
+                                       sw_if_index, !is_del, 0, 0);
+          vnet_feature_enable_disable ("ip4-output", "nat44-in2out-output",
+                                       sw_if_index, !is_del, 0, 0);
+        }
     }
 
 fq:
@@ -1913,7 +1976,6 @@ static clib_error_t * snat_init (vlib_main_t * vm)
   sm->ip4_lookup_main = lm;
   sm->api_main = &api_main;
   sm->first_worker_index = 0;
-  sm->next_worker = 0;
   sm->num_workers = 0;
   sm->num_snat_thread = 1;
   sm->workers = 0;
@@ -2076,6 +2138,7 @@ int snat_static_mapping_match (snat_main_t * sm,
     {
       if (vec_len (m->locals))
         {
+get_local:
           hi = vec_len (m->locals) - 1;
           rand = 1 + (random_u32 (&sm->random_seed) % m->locals[hi].prefix);
           while (lo < hi)
@@ -2085,6 +2148,14 @@ int snat_static_mapping_match (snat_main_t * sm,
             }
           if (!(m->locals[lo].prefix >= rand))
             return 1;
+          if (PREDICT_FALSE (sm->num_workers > 1))
+            {
+              ip4_header_t ip = {
+                .src_address = m->locals[lo].addr,
+              };
+              if (sm->worker_in2out_cb (&ip, m->fib_index) != vlib_get_thread_index ())
+                goto get_local;
+            }
           mapping->addr = m->locals[lo].addr;
           mapping->port = clib_host_to_net_u16 (m->locals[lo].port);
         }
@@ -2431,11 +2502,6 @@ snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0)
   snat_session_key_t m_key;
   clib_bihash_kv_8_8_t kv, value;
   snat_static_mapping_t *m;
-  nat_ed_ses_key_t key;
-  clib_bihash_kv_16_8_t s_kv, s_value;
-  snat_main_per_thread_data_t *tsm;
-  snat_session_t *s;
-  int i;
   u32 proto;
   u32 next_worker_index = 0;
 
@@ -2450,7 +2516,7 @@ snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0)
       if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
         {
           m = pool_elt_at_index (sm->static_mappings, value.value);
-          return m->worker_index;
+          return m->workers[0];
         }
     }
 
@@ -2480,32 +2546,7 @@ snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0)
   /* unknown protocol */
   if (PREDICT_FALSE (proto == ~0))
     {
-      key.l_addr = ip0->dst_address;
-      key.r_addr = ip0->src_address;
-      key.fib_index = rx_fib_index0;
-      key.proto = ip0->protocol;
-      key.r_port = 0;
-      key.l_port = 0;
-      s_kv.key[0] = key.as_u64[0];
-      s_kv.key[1] = key.as_u64[1];
-
-      if (!clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-        {
-          for (i = 0; i < _vec_len (sm->per_thread_data); i++)
-            {
-              tsm = vec_elt_at_index (sm->per_thread_data, i);
-              if (!pool_is_free_index(tsm->sessions, s_value.value))
-                {
-                  s = pool_elt_at_index (tsm->sessions, s_value.value);
-                  if (s->out2in.addr.as_u32 == ip0->dst_address.as_u32 &&
-                      s->out2in.port == ip0->protocol &&
-                      snat_is_unk_proto_session (s))
-                    return i;
-                }
-            }
-         }
-
-      /* if no session use current thread */
+      /* use current thread */
       return vlib_get_thread_index ();
     }
 
@@ -2548,7 +2589,7 @@ snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0)
       if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
         {
           m = pool_elt_at_index (sm->static_mappings, value.value);
-          return m->worker_index;
+          return m->workers[0];
         }
     }
 
@@ -2559,6 +2600,97 @@ snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0)
   return next_worker_index;
 }
 
+static u32
+nat44_ed_get_worker_out2in_cb (ip4_header_t * ip, u32 rx_fib_index)
+{
+  snat_main_t *sm = &snat_main;
+  clib_bihash_kv_8_8_t kv, value;
+  u32 proto, next_worker_index = 0;
+  udp_header_t *udp;
+  u16 port;
+  snat_static_mapping_t *m;
+  u32 hash;
+
+  /* first try static mappings without port */
+  if (PREDICT_FALSE (pool_elts (sm->static_mappings)))
+    {
+      make_sm_kv (&kv, &ip->dst_address, 0, rx_fib_index, 0);
+      if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+        {
+          m = pool_elt_at_index (sm->static_mappings, value.value);
+          return m->workers[0];
+        }
+    }
+
+  proto = ip_proto_to_snat_proto (ip->protocol);
+
+  /* unknown protocol */
+  if (PREDICT_FALSE (proto == ~0))
+    {
+      /* use current thread */
+      return vlib_get_thread_index ();
+    }
+
+  udp = ip4_next_header (ip);
+  port = udp->dst_port;
+
+  if (PREDICT_FALSE (ip->protocol == IP_PROTOCOL_ICMP))
+    {
+      icmp46_header_t * icmp = (icmp46_header_t *) udp;
+      icmp_echo_header_t *echo = (icmp_echo_header_t *)(icmp + 1);
+      if (!icmp_is_error_message (icmp))
+        port = echo->identifier;
+      else
+        {
+          ip4_header_t *inner_ip = (ip4_header_t *)(echo + 1);
+          proto = ip_proto_to_snat_proto (inner_ip->protocol);
+          void *l4_header = ip4_next_header (inner_ip);
+          switch (proto)
+            {
+            case SNAT_PROTOCOL_ICMP:
+              icmp = (icmp46_header_t*)l4_header;
+              echo = (icmp_echo_header_t *)(icmp + 1);
+              port = echo->identifier;
+              break;
+            case SNAT_PROTOCOL_UDP:
+            case SNAT_PROTOCOL_TCP:
+              port = ((tcp_udp_header_t*)l4_header)->src_port;
+              break;
+            default:
+              return vlib_get_thread_index ();
+            }
+        }
+    }
+
+  /* try static mappings with port */
+  if (PREDICT_FALSE (pool_elts (sm->static_mappings)))
+    {
+      make_sm_kv (&kv, &ip->dst_address, proto, rx_fib_index,
+                  clib_net_to_host_u16 (port));
+      if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+        {
+          m = pool_elt_at_index (sm->static_mappings, value.value);
+          if (!vec_len(m->locals))
+            return m->workers[0];
+
+          hash = ip->src_address.as_u32 + (ip->src_address.as_u32 >> 8) +
+                 (ip->src_address.as_u32 >> 16) + (ip->src_address.as_u32 >>24);
+
+          if (PREDICT_TRUE (is_pow2 (_vec_len (m->workers))))
+            return m->workers[hash & (_vec_len (m->workers) - 1)];
+          else
+            return m->workers[hash % _vec_len (m->workers)];
+        }
+    }
+
+  /* worker by outside port */
+  next_worker_index = sm->first_worker_index;
+  next_worker_index +=
+    sm->workers[(clib_net_to_host_u16 (port) - 1024) / sm->port_per_thread];
+
+  return next_worker_index;
+}
+
 static clib_error_t *
 snat_config (vlib_main_t * vm, unformat_input_t * input)
 {
@@ -2585,6 +2717,7 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
 
   sm->deterministic = 0;
   sm->out2in_dpo = 0;
+  sm->endpoint_dependent = 0;
 
   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
     {
@@ -2632,6 +2765,8 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
         sm->out2in_dpo = 1;
       else if (unformat (input, "dslite ce"))
         dslite_set_ce(dm, 1);
+      else if (unformat (input, "endpoint-dependent"))
+        sm->endpoint_dependent = 1;
       else
        return clib_error_return (0, "unknown input '%U'",
                                  format_unformat_error, input);
@@ -2673,28 +2808,59 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
     }
   else
     {
-      sm->worker_in2out_cb = snat_get_worker_in2out_cb;
-      sm->worker_out2in_cb = snat_get_worker_out2in_cb;
-      sm->in2out_node_index = snat_in2out_node.index;
-      sm->in2out_output_node_index = snat_in2out_output_node.index;
-      sm->out2in_node_index = snat_out2in_node.index;
-      if (!static_mapping_only ||
-          (static_mapping_only && static_mapping_connection_tracking))
+      if (sm->endpoint_dependent)
         {
+          sm->worker_in2out_cb = snat_get_worker_in2out_cb;
+          sm->worker_out2in_cb = nat44_ed_get_worker_out2in_cb;
+          sm->in2out_node_index = nat44_ed_in2out_node.index;
+          sm->in2out_output_node_index = nat44_ed_in2out_output_node.index;
+          sm->out2in_node_index = nat44_ed_out2in_node.index;
+          sm->icmp_match_in2out_cb = icmp_match_in2out_ed;
+          sm->icmp_match_out2in_cb = icmp_match_out2in_ed;
+        }
+      else
+        {
+          sm->worker_in2out_cb = snat_get_worker_in2out_cb;
+          sm->worker_out2in_cb = snat_get_worker_out2in_cb;
+          sm->in2out_node_index = snat_in2out_node.index;
+          sm->in2out_output_node_index = snat_in2out_output_node.index;
+          sm->out2in_node_index = snat_out2in_node.index;
           sm->icmp_match_in2out_cb = icmp_match_in2out_slow;
           sm->icmp_match_out2in_cb = icmp_match_out2in_slow;
-
+        }
+      if (!static_mapping_only ||
+          (static_mapping_only && static_mapping_connection_tracking))
+        {
           vec_foreach (tsm, sm->per_thread_data)
             {
-              clib_bihash_init_8_8 (&tsm->in2out, "in2out", translation_buckets,
-                                    translation_memory_size);
-              clib_bihash_set_kvp_format_fn_8_8 (&tsm->in2out,
-                                                 format_session_kvp);
-
-              clib_bihash_init_8_8 (&tsm->out2in, "out2in", translation_buckets,
-                                    translation_memory_size);
-              clib_bihash_set_kvp_format_fn_8_8 (&tsm->out2in,
-                                                 format_session_kvp);
+              if (sm->endpoint_dependent)
+                {
+                  clib_bihash_init_16_8 (&tsm->in2out_ed, "in2out-ed",
+                                         translation_buckets,
+                                         translation_memory_size);
+                  clib_bihash_set_kvp_format_fn_16_8 (&tsm->in2out_ed,
+                                                      format_ed_session_kvp);
+
+                  clib_bihash_init_16_8 (&tsm->out2in_ed, "out2in-ed",
+                                         translation_buckets,
+                                         translation_memory_size);
+                  clib_bihash_set_kvp_format_fn_16_8 (&tsm->out2in_ed,
+                                                      format_ed_session_kvp);
+                }
+              else
+                {
+                  clib_bihash_init_8_8 (&tsm->in2out, "in2out",
+                                        translation_buckets,
+                                        translation_memory_size);
+                  clib_bihash_set_kvp_format_fn_8_8 (&tsm->in2out,
+                                                     format_session_kvp);
+
+                  clib_bihash_init_8_8 (&tsm->out2in, "out2in",
+                                        translation_buckets,
+                                        translation_memory_size);
+                  clib_bihash_set_kvp_format_fn_8_8 (&tsm->out2in,
+                                                     format_session_kvp);
+                }
 
               clib_bihash_init_8_8 (&tsm->user_hash, "users", user_buckets,
                                     user_memory_size);
@@ -2702,15 +2868,6 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
                                                  format_user_kvp);
             }
 
-          clib_bihash_init_16_8 (&sm->in2out_ed, "in2out-ed",
-                                 translation_buckets, translation_memory_size);
-          clib_bihash_set_kvp_format_fn_16_8 (&sm->in2out_ed,
-                                              format_ed_session_kvp);
-
-          clib_bihash_init_16_8 (&sm->out2in_ed, "out2in-ed",
-                                 translation_buckets, translation_memory_size);
-          clib_bihash_set_kvp_format_fn_16_8 (&sm->out2in_ed,
-                                              format_ed_session_kvp);
         }
       else
         {
@@ -3063,7 +3220,7 @@ match:
         if (addresses[j].addr.as_u32 == address->as_u32)
           return;
 
-      snat_add_address (sm, address, ~0, twice_nat);
+      (void) snat_add_address (sm, address, ~0, twice_nat);
       /* Scan static map resolution vector */
       for (j = 0; j < vec_len (sm->to_resolve); j++)
         {
@@ -3164,7 +3321,7 @@ int snat_add_interface_address (snat_main_t *sm, u32 sw_if_index, int is_del,
 
   /* If the address is already bound - or static - add it now */
   if (first_int_addr)
-      snat_add_address (sm, first_int_addr, ~0, twice_nat);
+      (void) snat_add_address (sm, first_int_addr, ~0, twice_nat);
 
   return 0;
 }
@@ -3181,6 +3338,9 @@ nat44_del_session (snat_main_t *sm, ip4_address_t *addr, u16 port,
   snat_session_t *s;
   clib_bihash_8_8_t *t;
 
+  if (sm->endpoint_dependent)
+    return VNET_API_ERROR_UNSUPPORTED;
+
   ip.dst_address.as_u32 = ip.src_address.as_u32 = addr->as_u32;
   if (sm->num_workers > 1)
     tsm =
@@ -3218,17 +3378,22 @@ nat44_del_ed_session (snat_main_t *sm, ip4_address_t *addr, u16 port,
   clib_bihash_16_8_t *t;
   nat_ed_ses_key_t key;
   clib_bihash_kv_16_8_t kv, value;
-  u32 thread_index;
   u32 fib_index = fib_table_find (FIB_PROTOCOL_IP4, vrf_id);
   snat_session_t *s;
+  snat_main_per_thread_data_t *tsm;
+
+  if (!sm->endpoint_dependent)
+    return VNET_API_ERROR_FEATURE_DISABLED;
 
   ip.dst_address.as_u32 = ip.src_address.as_u32 = addr->as_u32;
   if (sm->num_workers > 1)
-    thread_index = sm->worker_in2out_cb (&ip, fib_index);
+    tsm =
+      vec_elt_at_index (sm->per_thread_data,
+                       sm->worker_in2out_cb (&ip, fib_index));
   else
-    thread_index = sm->num_workers;
+    tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers);
 
-  t = is_in ? &sm->in2out_ed : &sm->out2in_ed;
+  t = is_in ? &tsm->in2out_ed : &tsm->out2in_ed;
   key.l_addr.as_u32 = addr->as_u32;
   key.r_addr.as_u32 = eh_addr->as_u32;
   key.l_port = clib_host_to_net_u16 (port);
@@ -3240,11 +3405,11 @@ nat44_del_ed_session (snat_main_t *sm, ip4_address_t *addr, u16 port,
   if (clib_bihash_search_16_8 (t, &kv, &value))
     return VNET_API_ERROR_NO_SUCH_ENTRY;
 
-  if (pool_is_free_index (sm->per_thread_data[thread_index].sessions, value.value))
+  if (pool_is_free_index (tsm->sessions, value.value))
     return VNET_API_ERROR_UNSPECIFIED;
-  s = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, value.value);
-  nat_free_session_data (sm, s, thread_index);
-  nat44_delete_session (sm, s, thread_index);
+  s = pool_elt_at_index (tsm->sessions, value.value);
+  nat_free_session_data (sm, s, tsm - sm->per_thread_data);
+  nat44_delete_session (sm, s, tsm - sm->per_thread_data);
   return 0;
 }
 
index 9de65d9..bd00a52 100644 (file)
@@ -245,7 +245,7 @@ typedef struct {
   u32 vrf_id;
   u32 fib_index;
   snat_protocol_t proto;
-  u32 worker_index;
+  u32 *workers;
   u8 *tag;
   nat44_lb_addr_port_t *locals;
 } snat_static_mapping_t;
@@ -273,6 +273,10 @@ typedef struct {
   clib_bihash_8_8_t out2in;
   clib_bihash_8_8_t in2out;
 
+  /* Endpoint dependent sessions lookup tables */
+  clib_bihash_16_8_t out2in_ed;
+  clib_bihash_16_8_t in2out_ed;
+
   /* Find-a-user => src address lookup */
   clib_bihash_8_8_t user_hash;
 
@@ -312,16 +316,11 @@ typedef int nat_alloc_out_addr_and_port_function_t (snat_address_t * addresses,
                                                     u32 snat_thread_index);
 
 typedef struct snat_main_s {
-  /* Endpoint address dependent sessions lookup tables */
-  clib_bihash_16_8_t out2in_ed;
-  clib_bihash_16_8_t in2out_ed;
-
   snat_icmp_match_function_t * icmp_match_in2out_cb;
   snat_icmp_match_function_t * icmp_match_out2in_cb;
 
   u32 num_workers;
   u32 first_worker_index;
-  u32 next_worker;
   u32 * workers;
   snat_get_worker_function_t * worker_in2out_cb;
   snat_get_worker_function_t * worker_out2in_cb;
@@ -386,6 +385,7 @@ typedef struct snat_main_s {
   u8 static_mapping_connection_tracking;
   u8 deterministic;
   u8 out2in_dpo;
+  u8 endpoint_dependent;
   u32 translation_buckets;
   u32 translation_memory_size;
   u32 max_translations;
@@ -430,6 +430,14 @@ extern vlib_node_registration_t snat_det_in2out_node;
 extern vlib_node_registration_t snat_det_out2in_node;
 extern vlib_node_registration_t snat_hairpin_dst_node;
 extern vlib_node_registration_t snat_hairpin_src_node;
+extern vlib_node_registration_t nat44_ed_in2out_node;
+extern vlib_node_registration_t nat44_ed_in2out_output_node;
+extern vlib_node_registration_t nat44_ed_out2in_node;
+extern vlib_node_registration_t nat44_ed_hairpin_dst_node;
+extern vlib_node_registration_t nat44_ed_hairpin_src_node;
+extern vlib_node_registration_t nat44_ed_in2out_worker_handoff_node;
+extern vlib_node_registration_t nat44_ed_in2out_output_worker_handoff_node;
+extern vlib_node_registration_t nat44_ed_out2in_worker_handoff_node;
 
 void snat_free_outside_address_and_port (snat_address_t * addresses,
                                          u32 thread_index,
@@ -548,6 +556,11 @@ u32 icmp_match_in2out_det(snat_main_t *sm, vlib_node_runtime_t *node,
                           ip4_header_t *ip0, u8 *p_proto,
                           snat_session_key_t *p_value,
                           u8 *p_dont_translate, void *d, void *e);
+u32 icmp_match_in2out_ed(snat_main_t *sm, vlib_node_runtime_t *node,
+                         u32 thread_index, vlib_buffer_t *b0,
+                         ip4_header_t *ip0, u8 *p_proto,
+                         snat_session_key_t *p_value,
+                         u8 *p_dont_translate, void *d, void *e);
 u32 icmp_match_out2in_fast(snat_main_t *sm, vlib_node_runtime_t *node,
                            u32 thread_index, vlib_buffer_t *b0,
                            ip4_header_t *ip0, u8 *p_proto,
@@ -563,9 +576,14 @@ u32 icmp_match_out2in_det(snat_main_t *sm, vlib_node_runtime_t *node,
                           ip4_header_t *ip0, u8 *p_proto,
                           snat_session_key_t *p_value,
                           u8 *p_dont_translate, void *d, void *e);
+u32 icmp_match_out2in_ed(snat_main_t *sm, vlib_node_runtime_t *node,
+                         u32 thread_index, vlib_buffer_t *b0,
+                         ip4_header_t *ip0, u8 *p_proto,
+                         snat_session_key_t *p_value,
+                         u8 *p_dont_translate, void *d, void *e);
 void increment_v4_address(ip4_address_t * a);
-void snat_add_address(snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
-                      u8 twice_nat);
+int snat_add_address(snat_main_t *sm, ip4_address_t *addr, u32 vrf_id,
+                     u8 twice_nat);
 int snat_del_address(snat_main_t *sm, ip4_address_t addr, u8 delete_sm,
                      u8 twice_nat);
 void nat44_add_del_address_dpo (ip4_address_t addr, u8 is_add);
index 7a8be98..aa733a0 100644 (file)
@@ -172,8 +172,6 @@ nat44_show_hash_commnad_fn (vlib_main_t * vm, unformat_input_t * input,
   else if (unformat (input, "verbose"))
     verbose = 2;
 
-  vlib_cli_output (vm, "%U", format_bihash_16_8, &sm->in2out_ed, verbose);
-  vlib_cli_output (vm, "%U", format_bihash_16_8, &sm->out2in_ed, verbose);
   vlib_cli_output (vm, "%U", format_bihash_8_8, &sm->static_mapping_by_local,
                   verbose);
   vlib_cli_output (vm, "%U",
@@ -184,8 +182,18 @@ nat44_show_hash_commnad_fn (vlib_main_t * vm, unformat_input_t * input,
     tsm = vec_elt_at_index (sm->per_thread_data, i);
     vlib_cli_output (vm, "-------- thread %d %s --------\n",
                     i, vlib_worker_threads[i].name);
-    vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->in2out, verbose);
-    vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->out2in, verbose);
+    if (sm->endpoint_dependent)
+      {
+       vlib_cli_output (vm, "%U", format_bihash_16_8, &tsm->in2out_ed,
+                        verbose);
+       vlib_cli_output (vm, "%U", format_bihash_16_8, &tsm->out2in_ed,
+                        verbose);
+      }
+    else
+      {
+       vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->in2out, verbose);
+       vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->out2in, verbose);
+      }
     vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->user_hash, verbose);
   }
 
@@ -304,18 +312,26 @@ add_address_command_fn (vlib_main_t * vm,
   for (i = 0; i < count; i++)
     {
       if (is_add)
-       snat_add_address (sm, &this_addr, vrf_id, twice_nat);
+       rv = snat_add_address (sm, &this_addr, vrf_id, twice_nat);
       else
        rv = snat_del_address (sm, this_addr, 0, twice_nat);
 
       switch (rv)
        {
+       case VNET_API_ERROR_VALUE_EXIST:
+         error = clib_error_return (0, "NAT address already in use.");
+         goto done;
        case VNET_API_ERROR_NO_SUCH_ENTRY:
-         error = clib_error_return (0, "S-NAT address not exist.");
+         error = clib_error_return (0, "NAT address not exist.");
          goto done;
        case VNET_API_ERROR_UNSPECIFIED:
          error =
-           clib_error_return (0, "S-NAT address used in static mapping.");
+           clib_error_return (0, "NAT address used in static mapping.");
+         goto done;
+       case VNET_API_ERROR_FEATURE_DISABLED:
+         error =
+           clib_error_return (0,
+                              "twice NAT available only for endpoint-dependent mode.");
          goto done;
        default:
          break;
@@ -621,6 +637,11 @@ add_static_mapping_command_fn (vlib_main_t * vm,
     case VNET_API_ERROR_VALUE_EXIST:
       error = clib_error_return (0, "Mapping already exist.");
       goto done;
+    case VNET_API_ERROR_FEATURE_DISABLED:
+      error =
+       clib_error_return (0,
+                          "twice-nat/out2in-only available only for endpoint-dependent mode.");
+      goto done;
     default:
       break;
     }
@@ -800,6 +821,10 @@ add_lb_static_mapping_command_fn (vlib_main_t * vm,
     case VNET_API_ERROR_VALUE_EXIST:
       error = clib_error_return (0, "Mapping already exist.");
       goto done;
+    case VNET_API_ERROR_FEATURE_DISABLED:
+      error =
+       clib_error_return (0, "Available only for endpoint-dependent mode.");
+      goto done;
     default:
       break;
     }
index f5f4161..6d4d0d9 100644 (file)
@@ -454,7 +454,7 @@ static void
   for (i = 0; i < count; i++)
     {
       if (mp->is_add)
-       snat_add_address (sm, &this_addr, vrf_id, mp->twice_nat);
+       rv = snat_add_address (sm, &this_addr, vrf_id, mp->twice_nat);
       else
        rv = snat_del_address (sm, this_addr, 0, mp->twice_nat);
 
index db5b295..3724986 100644 (file)
@@ -239,6 +239,38 @@ nat44_session_update_lru (snat_main_t * sm, snat_session_t * s,
                      s->per_user_list_head_index, s->per_user_index);
 }
 
+always_inline void
+make_ed_kv (clib_bihash_kv_16_8_t * kv, ip4_address_t * l_addr,
+           ip4_address_t * r_addr, u8 proto, u32 fib_index, u16 l_port,
+           u16 r_port)
+{
+  nat_ed_ses_key_t *key = (nat_ed_ses_key_t *) kv->key;
+
+  key->l_addr.as_u32 = l_addr->as_u32;
+  key->r_addr.as_u32 = r_addr->as_u32;
+  key->fib_index = fib_index;
+  key->proto = proto;
+  key->l_port = l_port;
+  key->r_port = r_port;
+
+  kv->value = ~0ULL;
+}
+
+always_inline void
+make_sm_kv (clib_bihash_kv_8_8_t * kv, ip4_address_t * addr, u8 proto,
+           u32 fib_index, u16 port)
+{
+  snat_session_key_t key;
+
+  key.addr.as_u32 = addr->as_u32;
+  key.port = port;
+  key.protocol = proto;
+  key.fib_index = fib_index;
+
+  kv->key = key.as_u64;
+  kv->value = ~0ULL;
+}
+
 #endif /* __included_nat_inlines_h__ */
 
 /*
index 83e1426..80465b0 100755 (executable)
@@ -104,6 +104,8 @@ vlib_node_registration_t snat_out2in_fast_node;
 vlib_node_registration_t snat_out2in_worker_handoff_node;
 vlib_node_registration_t snat_det_out2in_node;
 vlib_node_registration_t nat44_out2in_reass_node;
+vlib_node_registration_t nat44_ed_out2in_node;
+vlib_node_registration_t nat44_ed_out2in_slowpath_node;
 
 #define foreach_snat_out2in_error                       \
 _(UNSUPPORTED_PROTOCOL, "Unsupported protocol")         \
@@ -135,7 +137,6 @@ typedef enum {
   SNAT_OUT2IN_NEXT_LOOKUP,
   SNAT_OUT2IN_NEXT_ICMP_ERROR,
   SNAT_OUT2IN_NEXT_REASS,
-  SNAT_OUT2IN_NEXT_IN2OUT,
   SNAT_OUT2IN_N_NEXT,
 } snat_out2in_next_t;
 
@@ -268,161 +269,6 @@ snat_out2in_error_t icmp_get_key(ip4_header_t *ip0,
   return -1; /* success */
 }
 
-static_always_inline int
-icmp_get_ed_key(ip4_header_t *ip0, nat_ed_ses_key_t *p_key0)
-{
-  icmp46_header_t *icmp0;
-  nat_ed_ses_key_t key0;
-  icmp_echo_header_t *echo0, *inner_echo0 = 0;
-  ip4_header_t *inner_ip0;
-  void *l4_header = 0;
-  icmp46_header_t *inner_icmp0;
-
-  icmp0 = (icmp46_header_t *) ip4_next_header (ip0);
-  echo0 = (icmp_echo_header_t *)(icmp0+1);
-
-  if (!icmp_is_error_message (icmp0))
-    {
-      key0.proto = IP_PROTOCOL_ICMP;
-      key0.l_addr = ip0->dst_address;
-      key0.r_addr = ip0->src_address;
-      key0.l_port = key0.r_port = echo0->identifier;
-    }
-  else
-    {
-      inner_ip0 = (ip4_header_t *)(echo0+1);
-      l4_header = ip4_next_header (inner_ip0);
-      key0.proto = inner_ip0->protocol;
-      key0.l_addr = inner_ip0->src_address;
-      key0.r_addr = inner_ip0->dst_address;
-      switch (ip_proto_to_snat_proto (inner_ip0->protocol))
-        {
-        case SNAT_PROTOCOL_ICMP:
-          inner_icmp0 = (icmp46_header_t*)l4_header;
-          inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1);
-          key0.l_port = key0.r_port = inner_echo0->identifier;
-          break;
-        case SNAT_PROTOCOL_UDP:
-        case SNAT_PROTOCOL_TCP:
-          key0.l_port = ((tcp_udp_header_t*)l4_header)->src_port;
-          key0.r_port = ((tcp_udp_header_t*)l4_header)->dst_port;
-          break;
-        default:
-          return -1;
-        }
-    }
-  *p_key0 = key0;
-  return 0;
-}
-
-static int
-next_src_nat (snat_main_t * sm, ip4_header_t * ip, u32 proto, u16 src_port,
-              u32 thread_index)
-{
-  snat_session_key_t key;
-  clib_bihash_kv_8_8_t kv, value;
-
-  key.addr = ip->src_address;
-  key.port = src_port;
-  key.protocol = proto;
-  key.fib_index = sm->inside_fib_index;
-  kv.key = key.as_u64;
-
-  if (!clib_bihash_search_8_8 (&sm->per_thread_data[thread_index].in2out, &kv,
-                               &value))
-    return 1;
-
-  return 0;
-}
-
-static void
-create_bypass_for_fwd(snat_main_t * sm, ip4_header_t * ip, u32 rx_fib_index,
-                      u32 thread_index)
-{
-  nat_ed_ses_key_t key;
-  clib_bihash_kv_16_8_t kv, value;
-  udp_header_t *udp;
-  snat_user_t *u;
-  snat_session_t *s = 0;
-  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  f64 now = vlib_time_now (sm->vlib_main);
-
-  if (ip->protocol == IP_PROTOCOL_ICMP)
-    {
-      if (icmp_get_ed_key (ip, &key))
-        return;
-    }
-  else if (ip->protocol == IP_PROTOCOL_UDP || ip->protocol == IP_PROTOCOL_TCP)
-    {
-      udp = ip4_next_header(ip);
-      key.r_addr = ip->src_address;
-      key.l_addr = ip->dst_address;
-      key.proto = ip->protocol;
-      key.l_port = udp->dst_port;
-      key.r_port = udp->src_port;
-    }
-  else
-    {
-      key.r_addr = ip->src_address;
-      key.l_addr = ip->dst_address;
-      key.proto = ip->protocol;
-      key.l_port = key.r_port = 0;
-    }
-  key.fib_index = 0;
-  kv.key[0] = key.as_u64[0];
-  kv.key[1] = key.as_u64[1];
-
-  if (!clib_bihash_search_16_8 (&sm->in2out_ed, &kv, &value))
-    {
-      s = pool_elt_at_index (tsm->sessions, value.value);
-    }
-  else
-    {
-      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
-        return;
-
-      u = nat_user_get_or_create (sm, &ip->dst_address, sm->inside_fib_index, thread_index);
-      if (!u)
-        {
-          nat_log_warn ("create NAT user failed");
-          return;
-        }
-
-      s = nat_session_alloc_or_recycle (sm, u, thread_index);
-      if (!s)
-        {
-          nat_log_warn ("create NAT session failed");
-          return;
-        }
-
-      s->ext_host_addr = key.r_addr;
-      s->ext_host_port = key.r_port;
-      s->flags |= SNAT_SESSION_FLAG_FWD_BYPASS;
-      s->outside_address_index = ~0;
-      s->out2in.addr = key.l_addr;
-      s->out2in.port = key.l_port;
-      s->out2in.protocol = ip_proto_to_snat_proto (key.proto);
-      s->out2in.fib_index = 0;
-      s->in2out = s->out2in;
-      user_session_increment (sm, u, 0);
-
-      kv.value = s - tsm->sessions;
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &kv, 1))
-        nat_log_notice ("in2out_ed key add failed");
-    }
-
-  if (ip->protocol == IP_PROTOCOL_TCP)
-    {
-      tcp_header_t *tcp = ip4_next_header(ip);
-      if (nat44_set_tcp_session_state_o2i (sm, s, tcp, thread_index))
-        return;
-    }
-  /* Per-user LRU list maintenance */
-  nat44_session_update_lru (sm, s, thread_index);
-  /* Accounting */
-  nat44_session_update_counters (s, now, 0);
-}
-
 /**
  * Get address and port values to be used for ICMP packet translation
  * and create session if needed
@@ -495,12 +341,6 @@ u32 icmp_match_out2in_slow(snat_main_t *sm, vlib_node_runtime_t *node,
           else
             {
               dont_translate = 1;
-              if (next_src_nat(sm, ip0, key0.protocol, key0.port, thread_index))
-                {
-                  next0 = SNAT_OUT2IN_NEXT_IN2OUT;
-                  goto out;
-                }
-              create_bypass_for_fwd(sm, ip0, rx_fib_index0, thread_index);
               goto out;
             }
         }
@@ -534,34 +374,8 @@ u32 icmp_match_out2in_slow(snat_main_t *sm, vlib_node_runtime_t *node,
           goto out;
         }
 
-      if (PREDICT_FALSE (value0.value == ~0ULL))
-        {
-          nat_ed_ses_key_t key;
-          clib_bihash_kv_16_8_t s_kv, s_value;
-
-          key.as_u64[0] = 0;
-          key.as_u64[1] = 0;
-          if (icmp_get_ed_key (ip0, &key))
-            {
-              b0->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL];
-              next0 = SNAT_OUT2IN_NEXT_DROP;
-              goto out;
-            }
-          key.fib_index = rx_fib_index0;
-          s_kv.key[0] = key.as_u64[0];
-          s_kv.key[1] = key.as_u64[1];
-          if (!clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-            s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
-                                    s_value.value);
-          else
-           {
-              next0 = SNAT_OUT2IN_NEXT_DROP;
-              goto out;
-           }
-        }
-      else
-        s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
-                                value0.value);
+      s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                              value0.value);
     }
 
 out:
@@ -797,306 +611,49 @@ static inline u32 icmp_out2in_slow_path (snat_main_t *sm,
   return next0;
 }
 
-static snat_session_t *
-snat_out2in_unknown_proto (snat_main_t *sm,
-                           vlib_buffer_t * b,
-                           ip4_header_t * ip,
-                           u32 rx_fib_index,
-                           u32 thread_index,
-                           f64 now,
-                           vlib_main_t * vm,
-                           vlib_node_runtime_t * node)
+static int
+nat_out2in_sm_unknown_proto (snat_main_t *sm,
+                             vlib_buffer_t * b,
+                             ip4_header_t * ip,
+                             u32 rx_fib_index)
 {
   clib_bihash_kv_8_8_t kv, value;
-  clib_bihash_kv_16_8_t s_kv, s_value;
   snat_static_mapping_t *m;
   snat_session_key_t m_key;
   u32 old_addr, new_addr;
   ip_csum_t sum;
-  nat_ed_ses_key_t key;
-  snat_session_t * s;
-  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  snat_user_t *u;
-
-  old_addr = ip->dst_address.as_u32;
-
-  key.l_addr = ip->dst_address;
-  key.r_addr = ip->src_address;
-  key.fib_index = rx_fib_index;
-  key.proto = ip->protocol;
-  key.r_port = 0;
-  key.l_port = 0;
-  s_kv.key[0] = key.as_u64[0];
-  s_kv.key[1] = key.as_u64[1];
-
-  if (!clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-    {
-      s = pool_elt_at_index (tsm->sessions, s_value.value);
-      new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32;
-    }
-  else
-    {
-      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
-        {
-          b->error = node->errors[SNAT_OUT2IN_ERROR_MAX_SESSIONS_EXCEEDED];
-          nat_log_notice ("maximum sessions exceeded");
-          return 0;
-        }
-
-      m_key.addr = ip->dst_address;
-      m_key.port = 0;
-      m_key.protocol = 0;
-      m_key.fib_index = rx_fib_index;
-      kv.key = m_key.as_u64;
-      if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
-        {
-          b->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
-          return 0;
-        }
-
-      m = pool_elt_at_index (sm->static_mappings, value.value);
-
-      new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32;
-
-      u = nat_user_get_or_create (sm, &ip->src_address, m->fib_index,
-                                  thread_index);
-      if (!u)
-        {
-          nat_log_warn ("create NAT user failed");
-          return 0;
-        }
-
-      /* Create a new session */
-      s = nat_session_alloc_or_recycle (sm, u, thread_index);
-      if (!s)
-        {
-          nat_log_warn ("create NAT session failed");
-          return 0;
-        }
-
-      s->ext_host_addr.as_u32 = ip->src_address.as_u32;
-      s->flags |= SNAT_SESSION_FLAG_UNKNOWN_PROTO;
-      s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
-      s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
-      s->outside_address_index = ~0;
-      s->out2in.addr.as_u32 = old_addr;
-      s->out2in.fib_index = rx_fib_index;
-      s->in2out.addr.as_u32 = new_addr;
-      s->in2out.fib_index = m->fib_index;
-      s->in2out.port = s->out2in.port = ip->protocol;
-      user_session_increment (sm, u, 1 /* static */);
 
-      /* Add to lookup tables */
-      s_kv.value = s - tsm->sessions;
-      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &s_kv, 1))
-        nat_log_notice ("out2in key add failed");
+  m_key.addr = ip->dst_address;
+  m_key.port = 0;
+  m_key.protocol = 0;
+  m_key.fib_index = rx_fib_index;
+  kv.key = m_key.as_u64;
+  if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+    return 1;
 
-      key.l_addr = ip->dst_address;
-      key.fib_index = m->fib_index;
-      s_kv.key[0] = key.as_u64[0];
-      s_kv.key[1] = key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &s_kv, 1))
-        nat_log_notice ("in2out key add failed");
-   }
+  m = pool_elt_at_index (sm->static_mappings, value.value);
 
-  /* Update IP checksum */
+  old_addr = ip->dst_address.as_u32;
+  new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32;
   sum = ip->checksum;
   sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
   ip->checksum = ip_csum_fold (sum);
 
-  vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index;
-
-  /* Accounting */
-  nat44_session_update_counters (s, now,
-                                 vlib_buffer_length_in_chain (vm, b));
-  /* Per-user LRU list maintenance */
-  nat44_session_update_lru (sm, s, thread_index);
-
-  return s;
+  vnet_buffer(b)->sw_if_index[VLIB_TX] = m->fib_index;
+  return 0;
 }
 
-static snat_session_t *
-snat_out2in_lb (snat_main_t *sm,
-                vlib_buffer_t * b,
-                ip4_header_t * ip,
-                u32 rx_fib_index,
-                u32 thread_index,
-                f64 now,
-                vlib_main_t * vm,
-                vlib_node_runtime_t * node)
+static uword
+snat_out2in_node_fn (vlib_main_t * vm,
+                 vlib_node_runtime_t * node,
+                 vlib_frame_t * frame)
 {
-  nat_ed_ses_key_t key;
-  clib_bihash_kv_16_8_t s_kv, s_value;
-  udp_header_t *udp = ip4_next_header (ip);
-  tcp_header_t *tcp = (tcp_header_t *) udp;
-  snat_session_t *s = 0;
-  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  snat_session_key_t e_key, l_key;
-  u32 old_addr, new_addr;
-  u32 proto = ip_proto_to_snat_proto (ip->protocol);
-  u16 new_port, old_port;
-  ip_csum_t sum;
-  snat_user_t *u;
-  u32 address_index;
-  snat_session_key_t eh_key;
-  twice_nat_type_t twice_nat;
-  u8 lb;
-
-  old_addr = ip->dst_address.as_u32;
-
-  key.l_addr = ip->dst_address;
-  key.r_addr = ip->src_address;
-  key.fib_index = rx_fib_index;
-  key.proto = ip->protocol;
-  key.r_port = udp->src_port;
-  key.l_port = udp->dst_port;
-  s_kv.key[0] = key.as_u64[0];
-  s_kv.key[1] = key.as_u64[1];
-
-  if (!clib_bihash_search_16_8 (&sm->out2in_ed, &s_kv, &s_value))
-    {
-      s = pool_elt_at_index (tsm->sessions, s_value.value);
-    }
-  else
-    {
-      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
-        {
-          b->error = node->errors[SNAT_OUT2IN_ERROR_MAX_SESSIONS_EXCEEDED];
-          nat_log_notice ("maximum sessions exceeded");
-          return 0;
-        }
-
-      e_key.addr = ip->dst_address;
-      e_key.port = udp->dst_port;
-      e_key.protocol = proto;
-      e_key.fib_index = rx_fib_index;
-      if (snat_static_mapping_match(sm, e_key, &l_key, 1, 0, &twice_nat, &lb))
-        return 0;
-
-      u = nat_user_get_or_create (sm, &l_key.addr, l_key.fib_index,
-                                  thread_index);
-      if (!u)
-      {
-        nat_log_warn ("create NAT user failed");
-        return 0;
-      }
-
-      s = nat_session_alloc_or_recycle (sm, u, thread_index);
-      if (!s)
-        {
-          nat_log_warn ("create NAT session failed");
-          return 0;
-        }
-
-      s->ext_host_addr.as_u32 = ip->src_address.as_u32;
-      s->ext_host_port = udp->src_port;
-      s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
-      if (lb)
-        s->flags |= SNAT_SESSION_FLAG_LOAD_BALANCING;
-      s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
-      s->outside_address_index = ~0;
-      s->out2in = e_key;
-      s->in2out = l_key;
-      user_session_increment (sm, u, 1 /* static */);
-
-      /* Add to lookup tables */
-      s_kv.value = s - tsm->sessions;
-      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &s_kv, 1))
-        nat_log_notice ("out2in-ed key add failed");
-
-      if (twice_nat == TWICE_NAT ||
-          (twice_nat == TWICE_NAT_SELF &&
-           ip->src_address.as_u32 == l_key.addr.as_u32))
-        {
-          eh_key.protocol = proto;
-          if (snat_alloc_outside_address_and_port (sm->twice_nat_addresses, 0,
-                                                   thread_index, &eh_key,
-                                                   &address_index,
-                                                   sm->port_per_thread,
-                                                   sm->per_thread_data[thread_index].snat_thread_index))
-            {
-              b->error = node->errors[SNAT_OUT2IN_ERROR_OUT_OF_PORTS];
-              return 0;
-            }
-          key.r_addr.as_u32 = s->ext_host_nat_addr.as_u32 = eh_key.addr.as_u32;
-          key.r_port = s->ext_host_nat_port = eh_key.port;
-          s->flags |= SNAT_SESSION_FLAG_TWICE_NAT;
-        }
-      key.l_addr = l_key.addr;
-      key.fib_index = l_key.fib_index;
-      key.l_port = l_key.port;
-      s_kv.key[0] = key.as_u64[0];
-      s_kv.key[1] = key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &s_kv, 1))
-        nat_log_notice ("in2out-ed key add failed");
-    }
-
-  new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32;
-
-  /* Update IP checksum */
-  sum = ip->checksum;
-  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
-  if (is_twice_nat_session (s))
-    sum = ip_csum_update (sum, ip->src_address.as_u32,
-                          s->ext_host_nat_addr.as_u32, ip4_header_t,
-                          src_address);
-  ip->checksum = ip_csum_fold (sum);
-
-  vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index;
-
-  if (PREDICT_TRUE(proto == SNAT_PROTOCOL_TCP))
-    {
-      old_port = tcp->dst_port;
-      tcp->dst_port = s->in2out.port;
-      new_port = tcp->dst_port;
-
-      sum = tcp->checksum;
-      sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
-      sum = ip_csum_update (sum, old_port, new_port, ip4_header_t, length);
-      if (is_twice_nat_session (s))
-        {
-          sum = ip_csum_update (sum, ip->src_address.as_u32,
-                                s->ext_host_nat_addr.as_u32, ip4_header_t,
-                                dst_address);
-          sum = ip_csum_update (sum, tcp->src_port, s->ext_host_nat_port,
-                                ip4_header_t, length);
-          tcp->src_port = s->ext_host_nat_port;
-          ip->src_address.as_u32 = s->ext_host_nat_addr.as_u32;
-        }
-      tcp->checksum = ip_csum_fold(sum);
-      if (nat44_set_tcp_session_state_o2i (sm, s, tcp, thread_index))
-        return s;
-    }
-  else
-    {
-      udp->dst_port = s->in2out.port;
-      if (is_twice_nat_session (s))
-        {
-          udp->src_port = s->ext_host_nat_port;
-          ip->src_address.as_u32 = s->ext_host_nat_addr.as_u32;
-        }
-      udp->checksum = 0;
-    }
-
-  /* Accounting */
-  nat44_session_update_counters (s, now, vlib_buffer_length_in_chain (vm, b));
-  /* Per-user LRU list maintenance */
-  nat44_session_update_lru (sm, s, thread_index);
-
-  return s;
-}
-
-static uword
-snat_out2in_node_fn (vlib_main_t * vm,
-                 vlib_node_runtime_t * node,
-                 vlib_frame_t * frame)
-{
-  u32 n_left_from, * from, * to_next;
-  snat_out2in_next_t next_index;
-  u32 pkts_processed = 0;
-  snat_main_t * sm = &snat_main;
-  f64 now = vlib_time_now (vm);
-  u32 thread_index = vlib_get_thread_index ();
+  u32 n_left_from, * from, * to_next;
+  snat_out2in_next_t next_index;
+  u32 pkts_processed = 0;
+  snat_main_t * sm = &snat_main;
+  f64 now = vlib_time_now (vm);
+  u32 thread_index = vlib_get_thread_index ();
 
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
@@ -1182,11 +739,14 @@ snat_out2in_node_fn (vlib_main_t * vm,
 
           if (PREDICT_FALSE (proto0 == ~0))
             {
-              s0 = snat_out2in_unknown_proto(sm, b0, ip0, rx_fib_index0,
-                                             thread_index, now, vm, node);
-             if (!sm->forwarding_enabled)
-               if (!s0)
-                 next0 = SNAT_OUT2IN_NEXT_DROP;
+              if (nat_out2in_sm_unknown_proto(sm, b0, ip0, rx_fib_index0))
+                {
+                  if (!sm->forwarding_enabled)
+                    {
+                      b0->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL];
+                      next0 = SNAT_OUT2IN_NEXT_DROP;
+                    }
+                }
               goto trace0;
             }
 
@@ -1237,16 +797,6 @@ snat_out2in_node_fn (vlib_main_t * vm,
                       next0 = SNAT_OUT2IN_NEXT_DROP;
                       goto trace0;
                     }
-                  else
-                    {
-                      if (next_src_nat(sm, ip0, proto0, udp0->src_port, thread_index))
-                        {
-                          next0 = SNAT_OUT2IN_NEXT_IN2OUT;
-                          goto trace0;
-                        }
-                      create_bypass_for_fwd(sm, ip0, rx_fib_index0, thread_index);
-                      goto trace0;
-                    }
                 }
 
               /* Create session initiated by host from external network */
@@ -1259,22 +809,8 @@ snat_out2in_node_fn (vlib_main_t * vm,
                 }
             }
           else
-            {
-              if (PREDICT_FALSE (value0.value == ~0ULL))
-                {
-                  s0 = snat_out2in_lb(sm, b0, ip0, rx_fib_index0, thread_index,
-                                      now, vm, node);
-                  if (!s0)
-                    next0 = SNAT_OUT2IN_NEXT_DROP;
-                  goto trace0;
-                }
-              else
-                {
-                  s0 = pool_elt_at_index (
-                    sm->per_thread_data[thread_index].sessions,
-                    value0.value);
-                }
-            }
+            s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                                    value0.value);
 
           old_addr0 = ip0->dst_address.as_u32;
           ip0->dst_address = s0->in2out.addr;
@@ -1355,11 +891,14 @@ snat_out2in_node_fn (vlib_main_t * vm,
 
           if (PREDICT_FALSE (proto1 == ~0))
             {
-              s1 = snat_out2in_unknown_proto(sm, b1, ip1, rx_fib_index1,
-                                             thread_index, now, vm, node);
-             if (!sm->forwarding_enabled)
-               if (!s1)
-                 next1 = SNAT_OUT2IN_NEXT_DROP;
+              if (nat_out2in_sm_unknown_proto(sm, b1, ip1, rx_fib_index1))
+                {
+                  if (!sm->forwarding_enabled)
+                    {
+                      b1->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL];
+                      next1 = SNAT_OUT2IN_NEXT_DROP;
+                    }
+                }
               goto trace1;
             }
 
@@ -1410,16 +949,6 @@ snat_out2in_node_fn (vlib_main_t * vm,
                       next1 = SNAT_OUT2IN_NEXT_DROP;
                       goto trace1;
                     }
-                  else
-                    {
-                      if (next_src_nat(sm, ip1, proto1, udp1->src_port, thread_index))
-                        {
-                          next1 = SNAT_OUT2IN_NEXT_IN2OUT;
-                          goto trace1;
-                        }
-                      create_bypass_for_fwd(sm, ip1, rx_fib_index1, thread_index);
-                      goto trace1;
-                    }
                 }
 
               /* Create session initiated by host from external network */
@@ -1432,22 +961,8 @@ snat_out2in_node_fn (vlib_main_t * vm,
                 }
             }
           else
-            {
-              if (PREDICT_FALSE (value1.value == ~0ULL))
-                {
-                  s1 = snat_out2in_lb(sm, b1, ip1, rx_fib_index1, thread_index,
-                                      now, vm, node);
-                  if (!s1)
-                    next1 = SNAT_OUT2IN_NEXT_DROP;
-                  goto trace1;
-                }
-              else
-                {
-                  s1 = pool_elt_at_index (
-                    sm->per_thread_data[thread_index].sessions,
-                    value1.value);
-                }
-            }
+            s1 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                                    value1.value);
 
           old_addr1 = ip1->dst_address.as_u32;
           ip1->dst_address = s1->in2out.addr;
@@ -1554,11 +1069,14 @@ snat_out2in_node_fn (vlib_main_t * vm,
 
           if (PREDICT_FALSE (proto0 == ~0))
             {
-              s0 = snat_out2in_unknown_proto(sm, b0, ip0, rx_fib_index0,
-                                             thread_index, now, vm, node);
-             if (!sm->forwarding_enabled)
-               if (!s0)
-                 next0 = SNAT_OUT2IN_NEXT_DROP;
+              if (nat_out2in_sm_unknown_proto(sm, b0, ip0, rx_fib_index0))
+                {
+                  if (!sm->forwarding_enabled)
+                    {
+                      b0->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL];
+                      next0 = SNAT_OUT2IN_NEXT_DROP;
+                    }
+                }
               goto trace00;
             }
 
@@ -1619,16 +1137,6 @@ snat_out2in_node_fn (vlib_main_t * vm,
                       next0 = SNAT_OUT2IN_NEXT_DROP;
                       goto trace00;
                     }
-                  else
-                    {
-                      if (next_src_nat(sm, ip0, proto0, udp0->src_port, thread_index))
-                        {
-                          next0 = SNAT_OUT2IN_NEXT_IN2OUT;
-                          goto trace00;
-                        }
-                      create_bypass_for_fwd(sm, ip0, rx_fib_index0, thread_index);
-                      goto trace00;
-                    }
                 }
 
               /* Create session initiated by host from external network */
@@ -1641,22 +1149,8 @@ snat_out2in_node_fn (vlib_main_t * vm,
                 }
             }
           else
-            {
-              if (PREDICT_FALSE (value0.value == ~0ULL))
-                {
-                  s0 = snat_out2in_lb(sm, b0, ip0, rx_fib_index0, thread_index,
-                                      now, vm, node);
-                  if (!s0)
-                    next0 = SNAT_OUT2IN_NEXT_DROP;
-                  goto trace00;
-                }
-              else
-                {
-                  s0 = pool_elt_at_index (
-                    sm->per_thread_data[thread_index].sessions,
-                    value0.value);
-                }
-            }
+            s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions,
+                                    value0.value);
 
           old_addr0 = ip0->dst_address.as_u32;
           ip0->dst_address = s0->in2out.addr;
@@ -1748,7 +1242,6 @@ VLIB_REGISTER_NODE (snat_out2in_node) = {
     [SNAT_OUT2IN_NEXT_LOOKUP] = "ip4-lookup",
     [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
     [SNAT_OUT2IN_NEXT_REASS] = "nat44-out2in-reass",
-    [SNAT_OUT2IN_NEXT_IN2OUT] = "nat44-in2out",
   },
 };
 VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_node, snat_out2in_node_fn);
@@ -1869,18 +1362,8 @@ nat44_out2in_reass_node_fn (vlib_main_t * vm,
                         {
                           b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
                           next0 = SNAT_OUT2IN_NEXT_DROP;
-                          goto trace0;
-                        }
-                      else
-                        {
-                          if (next_src_nat(sm, ip0, proto0, udp0->src_port, thread_index))
-                            {
-                              next0 = SNAT_OUT2IN_NEXT_IN2OUT;
-                              goto trace0;
-                            }
-                          create_bypass_for_fwd(sm, ip0, rx_fib_index0, thread_index);
-                          goto trace0;
                         }
+                      goto trace0;
                     }
 
                   /* Create session initiated by host from external network */
@@ -2045,12 +1528,1303 @@ VLIB_REGISTER_NODE (nat44_out2in_reass_node) = {
     [SNAT_OUT2IN_NEXT_LOOKUP] = "ip4-lookup",
     [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
     [SNAT_OUT2IN_NEXT_REASS] = "nat44-out2in-reass",
-    [SNAT_OUT2IN_NEXT_IN2OUT] = "nat44-in2out",
   },
 };
 VLIB_NODE_FUNCTION_MULTIARCH (nat44_out2in_reass_node,
                               nat44_out2in_reass_node_fn);
 
+/*******************************/
+/*** endpoint-dependent mode ***/
+/*******************************/
+typedef enum {
+  NAT44_ED_OUT2IN_NEXT_DROP,
+  NAT44_ED_OUT2IN_NEXT_LOOKUP,
+  NAT44_ED_OUT2IN_NEXT_ICMP_ERROR,
+  NAT44_ED_OUT2IN_NEXT_IN2OUT,
+  NAT44_ED_OUT2IN_NEXT_SLOW_PATH,
+  NAT44_ED_OUT2IN_N_NEXT,
+} nat44_ed_out2in_next_t;
+
+typedef struct {
+  u32 sw_if_index;
+  u32 next_index;
+  u32 session_index;
+  u32 is_slow_path;
+} nat44_ed_out2in_trace_t;
+
+static u8 *
+format_nat44_ed_out2in_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  nat44_ed_out2in_trace_t *t = va_arg (*args, nat44_ed_out2in_trace_t *);
+  char * tag;
+
+  tag = t->is_slow_path ? "NAT44_OUT2IN_SLOW_PATH" : "NAT44_OUT2IN_FAST_PATH";
+
+  s = format (s, "%s: sw_if_index %d, next index %d, session %d", tag,
+              t->sw_if_index, t->next_index, t->session_index);
+
+  return s;
+}
+
+static snat_session_t *
+create_session_for_static_mapping_ed (snat_main_t * sm,
+                                      vlib_buffer_t *b,
+                                      snat_session_key_t l_key,
+                                      snat_session_key_t e_key,
+                                      vlib_node_runtime_t * node,
+                                      u32 thread_index,
+                                      twice_nat_type_t twice_nat,
+                                      u8 is_lb)
+{
+  snat_session_t *s;
+  snat_user_t *u;
+  ip4_header_t *ip;
+  udp_header_t *udp;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  clib_bihash_kv_16_8_t kv;
+  snat_session_key_t eh_key;
+  u32 address_index;
+
+  if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
+    {
+      b->error = node->errors[SNAT_OUT2IN_ERROR_MAX_SESSIONS_EXCEEDED];
+      nat_log_notice ("maximum sessions exceeded");
+      return 0;
+    }
+
+  u = nat_user_get_or_create (sm, &l_key.addr, l_key.fib_index, thread_index);
+  if (!u)
+    {
+      nat_log_warn ("create NAT user failed");
+      return 0;
+    }
+
+  s = nat_session_alloc_or_recycle (sm, u, thread_index);
+  if (!s)
+    {
+      nat_log_warn ("create NAT session failed");
+      return 0;
+    }
+
+  ip = vlib_buffer_get_current (b);
+  udp = ip4_next_header (ip);
+
+  s->ext_host_addr.as_u32 = ip->src_address.as_u32;
+  s->ext_host_port = e_key.protocol == SNAT_PROTOCOL_ICMP ? 0 : udp->src_port;
+  s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
+  if (is_lb)
+    s->flags |= SNAT_SESSION_FLAG_LOAD_BALANCING;
+  s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
+  s->outside_address_index = ~0;
+  s->out2in = e_key;
+  s->in2out = l_key;
+  s->in2out.protocol = s->out2in.protocol;
+  user_session_increment (sm, u, 1);
+
+  /* Add to lookup tables */
+  make_ed_kv (&kv, &e_key.addr, &s->ext_host_addr, ip->protocol,
+              e_key.fib_index, e_key.port, s->ext_host_port);
+  kv.value = s - tsm->sessions;
+  if (clib_bihash_add_del_16_8 (&tsm->out2in_ed, &kv, 1))
+    nat_log_notice ("out2in-ed key add failed");
+
+  if (twice_nat == TWICE_NAT || (twice_nat == TWICE_NAT_SELF &&
+      ip->src_address.as_u32 == l_key.addr.as_u32))
+    {
+      eh_key.protocol = e_key.protocol;
+      if (snat_alloc_outside_address_and_port (sm->twice_nat_addresses, 0,
+                                               thread_index, &eh_key,
+                                               &address_index,
+                                               sm->port_per_thread,
+                                               tsm->snat_thread_index))
+        {
+          b->error = node->errors[SNAT_OUT2IN_ERROR_OUT_OF_PORTS];
+          nat44_delete_session (sm, s, thread_index);
+          if (clib_bihash_add_del_16_8 (&tsm->out2in_ed, &kv, 0))
+            nat_log_notice ("out2in-ed key del failed");
+          return 0;
+        }
+      s->ext_host_nat_addr.as_u32 = eh_key.addr.as_u32;
+      s->ext_host_nat_port = eh_key.port;
+      s->flags |= SNAT_SESSION_FLAG_TWICE_NAT;
+      make_ed_kv (&kv, &l_key.addr, &s->ext_host_nat_addr, ip->protocol,
+                  l_key.fib_index, l_key.port, s->ext_host_nat_port);
+    }
+  else
+    {
+      make_ed_kv (&kv, &l_key.addr, &s->ext_host_addr, ip->protocol,
+                  l_key.fib_index, l_key.port, s->ext_host_port);
+    }
+  kv.value = s - tsm->sessions;
+  if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &kv, 1))
+    nat_log_notice ("in2out-ed key add failed");
+
+  return s;
+}
+
+static_always_inline int
+icmp_get_ed_key(ip4_header_t *ip0, nat_ed_ses_key_t *p_key0)
+{
+  icmp46_header_t *icmp0;
+  nat_ed_ses_key_t key0;
+  icmp_echo_header_t *echo0, *inner_echo0 = 0;
+  ip4_header_t *inner_ip0;
+  void *l4_header = 0;
+  icmp46_header_t *inner_icmp0;
+
+  icmp0 = (icmp46_header_t *) ip4_next_header (ip0);
+  echo0 = (icmp_echo_header_t *)(icmp0+1);
+
+  if (!icmp_is_error_message (icmp0))
+    {
+      key0.proto = IP_PROTOCOL_ICMP;
+      key0.l_addr = ip0->dst_address;
+      key0.r_addr = ip0->src_address;
+      key0.l_port = echo0->identifier;
+      key0.r_port = 0;
+    }
+  else
+    {
+      inner_ip0 = (ip4_header_t *)(echo0+1);
+      l4_header = ip4_next_header (inner_ip0);
+      key0.proto = inner_ip0->protocol;
+      key0.l_addr = inner_ip0->src_address;
+      key0.r_addr = inner_ip0->dst_address;
+      switch (ip_proto_to_snat_proto (inner_ip0->protocol))
+        {
+        case SNAT_PROTOCOL_ICMP:
+          inner_icmp0 = (icmp46_header_t*)l4_header;
+          inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1);
+          key0.l_port = inner_echo0->identifier;
+          key0.r_port = 0;
+          break;
+        case SNAT_PROTOCOL_UDP:
+        case SNAT_PROTOCOL_TCP:
+          key0.l_port = ((tcp_udp_header_t*)l4_header)->src_port;
+          key0.r_port = ((tcp_udp_header_t*)l4_header)->dst_port;
+          break;
+        default:
+          return -1;
+        }
+    }
+  *p_key0 = key0;
+  return 0;
+}
+
+static int
+next_src_nat (snat_main_t * sm, ip4_header_t * ip, u8 proto, u16 src_port,
+              u16 dst_port, u32 thread_index)
+{
+  clib_bihash_kv_16_8_t kv, value;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+
+  make_ed_kv (&kv, &ip->src_address, &ip->dst_address, proto,
+              sm->inside_fib_index, src_port, dst_port);
+  if (!clib_bihash_search_16_8 (&tsm->in2out_ed, &kv, &value))
+    return 1;
+
+  return 0;
+}
+
+static void
+create_bypass_for_fwd(snat_main_t * sm, ip4_header_t * ip, u32 rx_fib_index,
+                      u32 thread_index)
+{
+  nat_ed_ses_key_t key;
+  clib_bihash_kv_16_8_t kv, value;
+  udp_header_t *udp;
+  snat_user_t *u;
+  snat_session_t *s = 0;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  f64 now = vlib_time_now (sm->vlib_main);
+
+  if (ip->protocol == IP_PROTOCOL_ICMP)
+    {
+      if (icmp_get_ed_key (ip, &key))
+        return;
+    }
+  else if (ip->protocol == IP_PROTOCOL_UDP || ip->protocol == IP_PROTOCOL_TCP)
+    {
+      udp = ip4_next_header(ip);
+      key.r_addr = ip->src_address;
+      key.l_addr = ip->dst_address;
+      key.proto = ip->protocol;
+      key.l_port = udp->dst_port;
+      key.r_port = udp->src_port;
+    }
+  else
+    {
+      key.r_addr = ip->src_address;
+      key.l_addr = ip->dst_address;
+      key.proto = ip->protocol;
+      key.l_port = key.r_port = 0;
+    }
+  key.fib_index = 0;
+  kv.key[0] = key.as_u64[0];
+  kv.key[1] = key.as_u64[1];
+
+  if (!clib_bihash_search_16_8 (&tsm->in2out_ed, &kv, &value))
+    {
+      s = pool_elt_at_index (tsm->sessions, value.value);
+    }
+  else
+    {
+      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
+        return;
+
+      u = nat_user_get_or_create (sm, &ip->dst_address, sm->inside_fib_index,
+                                  thread_index);
+      if (!u)
+        {
+          nat_log_warn ("create NAT user failed");
+          return;
+        }
+
+      s = nat_session_alloc_or_recycle (sm, u, thread_index);
+      if (!s)
+        {
+          nat_log_warn ("create NAT session failed");
+          return;
+        }
+
+      s->ext_host_addr = key.r_addr;
+      s->ext_host_port = key.r_port;
+      s->flags |= SNAT_SESSION_FLAG_FWD_BYPASS;
+      s->outside_address_index = ~0;
+      s->out2in.addr = key.l_addr;
+      s->out2in.port = key.l_port;
+      s->out2in.protocol = ip_proto_to_snat_proto (key.proto);
+      s->out2in.fib_index = 0;
+      s->in2out = s->out2in;
+      user_session_increment (sm, u, 0);
+
+      kv.value = s - tsm->sessions;
+      if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &kv, 1))
+        nat_log_notice ("in2out_ed key add failed");
+    }
+
+  if (ip->protocol == IP_PROTOCOL_TCP)
+    {
+      tcp_header_t *tcp = ip4_next_header(ip);
+      if (nat44_set_tcp_session_state_o2i (sm, s, tcp, thread_index))
+        return;
+    }
+
+  /* Per-user LRU list maintenance */
+  nat44_session_update_lru (sm, s, thread_index);
+  /* Accounting */
+  nat44_session_update_counters (s, now, 0);
+}
+
+u32
+icmp_match_out2in_ed (snat_main_t * sm, vlib_node_runtime_t * node,
+                      u32 thread_index, vlib_buffer_t * b, ip4_header_t * ip,
+                      u8 * p_proto, snat_session_key_t * p_value,
+                      u8 * p_dont_translate, void * d, void * e)
+{
+  u32 next = ~0, sw_if_index, rx_fib_index;
+  icmp46_header_t *icmp;
+  nat_ed_ses_key_t key;
+  clib_bihash_kv_16_8_t kv, value;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  snat_session_t *s = 0;
+  u8 dont_translate = 0, is_addr_only;
+  snat_session_key_t e_key, l_key;
+
+  icmp = (icmp46_header_t *) ip4_next_header (ip);
+  sw_if_index = vnet_buffer(b)->sw_if_index[VLIB_RX];
+  rx_fib_index = ip4_fib_table_get_index_for_sw_if_index (sw_if_index);
+
+  if (icmp_get_ed_key (ip, &key))
+    {
+      b->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL];
+      next = SNAT_OUT2IN_NEXT_DROP;
+      goto out;
+    }
+  key.fib_index = rx_fib_index;
+  kv.key[0] = key.as_u64[0];
+  kv.key[1] = key.as_u64[1];
+
+  if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv, &value))
+    {
+      /* Try to match static mapping */
+      e_key.addr = ip->dst_address;
+      e_key.port = key.l_port;
+      e_key.protocol = ip_proto_to_snat_proto (key.proto);
+      e_key.fib_index = rx_fib_index;
+      if (snat_static_mapping_match(sm, e_key, &l_key, 1, &is_addr_only, 0, 0))
+        {
+          if (!sm->forwarding_enabled)
+            {
+              /* Don't NAT packet aimed at the intfc address */
+              if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index,
+                                                  ip->dst_address.as_u32)))
+                {
+                  dont_translate = 1;
+                  goto out;
+                }
+              b->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+              next = NAT44_ED_OUT2IN_NEXT_DROP;
+              goto out;
+            }
+          else
+            {
+              dont_translate = 1;
+              if (next_src_nat(sm, ip, key.proto, key.l_port, key.r_port, thread_index))
+                {
+                  next = NAT44_ED_OUT2IN_NEXT_IN2OUT;
+                  goto out;
+                }
+              create_bypass_for_fwd(sm, ip, rx_fib_index, thread_index);
+              goto out;
+            }
+        }
+
+      if (PREDICT_FALSE(icmp->type != ICMP4_echo_reply &&
+                        (icmp->type != ICMP4_echo_request || !is_addr_only)))
+        {
+          b->error = node->errors[SNAT_OUT2IN_ERROR_BAD_ICMP_TYPE];
+          next = NAT44_ED_OUT2IN_NEXT_DROP;
+          goto out;
+        }
+
+      /* Create session initiated by host from external network */
+      s = create_session_for_static_mapping_ed(sm, b, l_key, e_key, node,
+                                               thread_index, 0, 0);
+
+      if (!s)
+        {
+          next = NAT44_ED_OUT2IN_NEXT_DROP;
+          goto out;
+        }
+    }
+  else
+    {
+      if (PREDICT_FALSE(icmp->type != ICMP4_echo_reply &&
+                        icmp->type != ICMP4_echo_request &&
+                        !icmp_is_error_message (icmp)))
+        {
+          b->error = node->errors[SNAT_OUT2IN_ERROR_BAD_ICMP_TYPE];
+          next = SNAT_OUT2IN_NEXT_DROP;
+          goto out;
+        }
+
+      s = pool_elt_at_index (tsm->sessions, value.value);
+    }
+
+  *p_proto = ip_proto_to_snat_proto (key.proto);
+out:
+  if (s)
+    *p_value = s->in2out;
+  *p_dont_translate = dont_translate;
+  if (d)
+    *(snat_session_t**)d = s;
+  return next;
+}
+
+static snat_session_t *
+nat44_ed_out2in_unknown_proto (snat_main_t *sm,
+                               vlib_buffer_t * b,
+                               ip4_header_t * ip,
+                               u32 rx_fib_index,
+                               u32 thread_index,
+                               f64 now,
+                               vlib_main_t * vm,
+                               vlib_node_runtime_t * node)
+{
+  clib_bihash_kv_8_8_t kv, value;
+  clib_bihash_kv_16_8_t s_kv, s_value;
+  snat_static_mapping_t *m;
+  u32 old_addr, new_addr;
+  ip_csum_t sum;
+  snat_session_t * s;
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+  snat_user_t *u;
+
+  old_addr = ip->dst_address.as_u32;
+
+  make_ed_kv (&s_kv, &ip->dst_address, &ip->src_address, ip->protocol,
+              rx_fib_index, 0, 0);
+
+  if (!clib_bihash_search_16_8 (&tsm->out2in_ed, &s_kv, &s_value))
+    {
+      s = pool_elt_at_index (tsm->sessions, s_value.value);
+      new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32;
+    }
+  else
+    {
+      if (PREDICT_FALSE (maximum_sessions_exceeded(sm, thread_index)))
+        {
+          b->error = node->errors[SNAT_OUT2IN_ERROR_MAX_SESSIONS_EXCEEDED];
+          nat_log_notice ("maximum sessions exceeded");
+          return 0;
+        }
+
+      make_sm_kv (&kv, &ip->dst_address, 0, rx_fib_index, 0);
+      if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+        {
+          b->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+          return 0;
+        }
+
+      m = pool_elt_at_index (sm->static_mappings, value.value);
+
+      new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32;
+
+      u = nat_user_get_or_create (sm, &ip->src_address, m->fib_index,
+                                  thread_index);
+      if (!u)
+        {
+          nat_log_warn ("create NAT user failed");
+          return 0;
+        }
+
+      /* Create a new session */
+      s = nat_session_alloc_or_recycle (sm, u, thread_index);
+      if (!s)
+        {
+          nat_log_warn ("create NAT session failed");
+          return 0;
+        }
+
+      s->ext_host_addr.as_u32 = ip->src_address.as_u32;
+      s->flags |= SNAT_SESSION_FLAG_UNKNOWN_PROTO;
+      s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING;
+      s->flags |= SNAT_SESSION_FLAG_ENDPOINT_DEPENDENT;
+      s->outside_address_index = ~0;
+      s->out2in.addr.as_u32 = old_addr;
+      s->out2in.fib_index = rx_fib_index;
+      s->in2out.addr.as_u32 = new_addr;
+      s->in2out.fib_index = m->fib_index;
+      s->in2out.port = s->out2in.port = ip->protocol;
+      user_session_increment (sm, u, 1);
+
+      /* Add to lookup tables */
+      s_kv.value = s - tsm->sessions;
+      if (clib_bihash_add_del_16_8 (&tsm->out2in_ed, &s_kv, 1))
+        nat_log_notice ("out2in key add failed");
+
+      make_ed_kv (&s_kv, &ip->dst_address, &ip->src_address, ip->protocol,
+                  m->fib_index, 0, 0);
+      s_kv.value = s - tsm->sessions;
+      if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &s_kv, 1))
+        nat_log_notice ("in2out key add failed");
+   }
+
+  /* Update IP checksum */
+  sum = ip->checksum;
+  sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
+  ip->checksum = ip_csum_fold (sum);
+
+  vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index;
+
+  /* Accounting */
+  nat44_session_update_counters (s, now,
+                                 vlib_buffer_length_in_chain (vm, b));
+  /* Per-user LRU list maintenance */
+  nat44_session_update_lru (sm, s, thread_index);
+
+  return s;
+}
+
+static inline uword
+nat44_ed_out2in_node_fn_inline (vlib_main_t * vm,
+                                vlib_node_runtime_t * node,
+                                vlib_frame_t * frame, int is_slow_path)
+{
+  u32 n_left_from, *from, *to_next, pkts_processed = 0, stats_node_index;
+  nat44_ed_out2in_next_t next_index;
+  snat_main_t *sm = &snat_main;
+  f64 now = vlib_time_now (vm);
+  u32 thread_index = vlib_get_thread_index ();
+  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
+
+  stats_node_index = is_slow_path ? nat44_ed_out2in_slowpath_node.index :
+    nat44_ed_out2in_node.index;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from >= 4 && n_left_to_next >= 2)
+       {
+          u32 bi0, bi1;
+         vlib_buffer_t *b0, *b1;
+          u32 next0, sw_if_index0, rx_fib_index0, proto0, old_addr0, new_addr0;
+          u32 next1, sw_if_index1, rx_fib_index1, proto1, old_addr1, new_addr1;
+          u16 old_port0, new_port0, old_port1, new_port1;
+          ip4_header_t *ip0, *ip1;
+          udp_header_t *udp0, *udp1;
+          tcp_header_t *tcp0, *tcp1;
+          icmp46_header_t *icmp0, *icmp1;
+          snat_session_t *s0 = 0, *s1 = 0;
+          clib_bihash_kv_16_8_t kv0, value0, kv1, value1;
+          ip_csum_t sum0, sum1;
+          snat_session_key_t e_key0, l_key0, e_key1, l_key1;
+          u8 is_lb0, is_lb1;
+          twice_nat_type_t twice_nat0, twice_nat1;
+
+         /* Prefetch next iteration. */
+         {
+           vlib_buffer_t * p2, * p3;
+
+           p2 = vlib_get_buffer (vm, from[2]);
+           p3 = vlib_get_buffer (vm, from[3]);
+
+           vlib_prefetch_buffer_header (p2, LOAD);
+           vlib_prefetch_buffer_header (p3, LOAD);
+
+           CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
+           CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
+         }
+
+          /* speculatively enqueue b0 and b1 to the current next frame */
+         to_next[0] = bi0 = from[0];
+         to_next[1] = bi1 = from[1];
+         from += 2;
+         to_next += 2;
+         n_left_from -= 2;
+         n_left_to_next -= 2;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         b1 = vlib_get_buffer (vm, bi1);
+
+          next0 = NAT44_ED_OUT2IN_NEXT_LOOKUP;
+          vnet_buffer (b0)->snat.flags = 0;
+          ip0 = vlib_buffer_get_current (b0);
+
+          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
+         rx_fib_index0 = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                               sw_if_index0);
+
+          if (PREDICT_FALSE(ip0->ttl == 1))
+            {
+              vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0;
+              icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded,
+                                           ICMP4_time_exceeded_ttl_exceeded_in_transit,
+                                           0);
+              next0 = NAT44_ED_OUT2IN_NEXT_ICMP_ERROR;
+              goto trace00;
+            }
+
+          udp0 = ip4_next_header (ip0);
+          tcp0 = (tcp_header_t *) udp0;
+          icmp0 = (icmp46_header_t *) udp0;
+          proto0 = ip_proto_to_snat_proto (ip0->protocol);
+
+          if (is_slow_path)
+            {
+              if (PREDICT_FALSE (proto0 == ~0))
+                {
+                  s0 = nat44_ed_out2in_unknown_proto(sm, b0, ip0, rx_fib_index0,
+                                                     thread_index, now, vm, node);
+                  if (!sm->forwarding_enabled)
+                    {
+                      if (!s0)
+                        next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                      goto trace00;
+                    }
+                }
+
+              if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = icmp_out2in_slow_path
+                    (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node,
+                     next0, now, thread_index, &s0);
+                  goto trace00;
+                }
+            }
+          else
+            {
+              if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = NAT44_ED_OUT2IN_NEXT_SLOW_PATH;
+                  goto trace00;
+                }
+
+              if (ip4_is_fragment (ip0))
+                {
+                  b0->error = node->errors[SNAT_OUT2IN_ERROR_DROP_FRAGMENT];
+                  next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                  goto trace00;
+                }
+            }
+
+          make_ed_kv (&kv0, &ip0->dst_address, &ip0->src_address, ip0->protocol,
+                      rx_fib_index0, udp0->dst_port, udp0->src_port);
+
+          if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv0, &value0))
+            {
+              if (is_slow_path)
+                {
+                  /* Try to match static mapping by external address and port,
+                     destination address and port in packet */
+                  e_key0.addr = ip0->dst_address;
+                  e_key0.port = udp0->dst_port;
+                  e_key0.protocol = proto0;
+                  e_key0.fib_index = rx_fib_index0;
+                  if (snat_static_mapping_match(sm, e_key0, &l_key0, 1, 0,
+                      &twice_nat0, &is_lb0))
+                    {
+                      /*
+                       * Send DHCP packets to the ipv4 stack, or we won't
+                       * be able to use dhcp client on the outside interface
+                       */
+                      if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_UDP
+                          && (udp0->dst_port ==
+                          clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client))))
+                        {
+                          vnet_feature_next
+                            (vnet_buffer (b0)->sw_if_index[VLIB_RX], &next0, b0);
+                          goto trace00;
+                        }
+
+                      if (!sm->forwarding_enabled)
+                        {
+                          b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                          next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                        }
+                      else
+                        {
+                          if (next_src_nat(sm, ip0, ip0->protocol,
+                                           udp0->src_port, udp0->dst_port,
+                                           thread_index))
+                            {
+                              next0 = NAT44_ED_OUT2IN_NEXT_IN2OUT;
+                              goto trace00;
+                            }
+                          create_bypass_for_fwd(sm, ip0, rx_fib_index0,
+                                                thread_index);
+                        }
+                      goto trace00;
+                    }
+
+                  /* Create session initiated by host from external network */
+                  s0 = create_session_for_static_mapping_ed(sm, b0, l_key0,
+                                                            e_key0, node,
+                                                            thread_index,
+                                                            twice_nat0, is_lb0);
+
+                  if (!s0)
+                    {
+                      next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                      goto trace00;
+                    }
+                }
+              else
+                {
+                  next0 = NAT44_ED_OUT2IN_NEXT_SLOW_PATH;
+                  goto trace00;
+                }
+            }
+          else
+            {
+              s0 = pool_elt_at_index (tsm->sessions, value0.value);
+            }
+
+          old_addr0 = ip0->dst_address.as_u32;
+          new_addr0 = ip0->dst_address.as_u32 = s0->in2out.addr.as_u32;
+          vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index;
+
+          sum0 = ip0->checksum;
+          sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                 dst_address);
+          if (PREDICT_FALSE (is_twice_nat_session (s0)))
+            sum0 = ip_csum_update (sum0, ip0->src_address.as_u32,
+                                   s0->ext_host_nat_addr.as_u32, ip4_header_t,
+                                   src_address);
+          ip0->checksum = ip_csum_fold (sum0);
+
+          if (PREDICT_TRUE (proto0 == SNAT_PROTOCOL_TCP))
+            {
+              old_port0 = tcp0->dst_port;
+              new_port0 = tcp0->dst_port = s0->in2out.port;
+
+              sum0 = tcp0->checksum;
+              sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                     dst_address);
+              sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t,
+                                     length);
+              if (is_twice_nat_session (s0))
+                {
+                  sum0 = ip_csum_update (sum0, ip0->src_address.as_u32,
+                                         s0->ext_host_nat_addr.as_u32,
+                                         ip4_header_t, dst_address);
+                  sum0 = ip_csum_update (sum0, tcp0->src_port,
+                                         s0->ext_host_nat_port, ip4_header_t,
+                                         length);
+                  tcp0->src_port = s0->ext_host_nat_port;
+                  ip0->src_address.as_u32 = s0->ext_host_nat_addr.as_u32;
+                }
+              tcp0->checksum = ip_csum_fold(sum0);
+              if (nat44_set_tcp_session_state_o2i (sm, s0, tcp0, thread_index))
+                goto trace00;
+            }
+          else
+            {
+              udp0->dst_port = s0->in2out.port;
+              if (is_twice_nat_session (s0))
+                {
+                  udp0->src_port = s0->ext_host_nat_port;
+                  ip0->src_address.as_u32 = s0->ext_host_nat_addr.as_u32;
+                }
+              udp0->checksum = 0;
+            }
+
+          /* Accounting */
+          nat44_session_update_counters (s0, now,
+                                         vlib_buffer_length_in_chain (vm, b0));
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s0, thread_index);
+
+        trace00:
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b0->flags & VLIB_BUFFER_IS_TRACED)))
+            {
+              nat44_ed_out2in_trace_t *t =
+                vlib_add_trace (vm, node, b0, sizeof (*t));
+              t->is_slow_path = is_slow_path;
+              t->sw_if_index = sw_if_index0;
+              t->next_index = next0;
+              t->session_index = ~0;
+              if (s0)
+                t->session_index = s0 - tsm->sessions;
+            }
+
+          pkts_processed += next0 != NAT44_ED_OUT2IN_NEXT_DROP;
+
+          next1 = NAT44_ED_OUT2IN_NEXT_LOOKUP;
+          vnet_buffer (b1)->snat.flags = 0;
+          ip1 = vlib_buffer_get_current (b1);
+
+          sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX];
+         rx_fib_index1 = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                               sw_if_index1);
+
+          if (PREDICT_FALSE(ip1->ttl == 1))
+            {
+              vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0;
+              icmp4_error_set_vnet_buffer (b1, ICMP4_time_exceeded,
+                                           ICMP4_time_exceeded_ttl_exceeded_in_transit,
+                                           0);
+              next1 = NAT44_ED_OUT2IN_NEXT_ICMP_ERROR;
+              goto trace01;
+            }
+
+          udp1 = ip4_next_header (ip1);
+          tcp1 = (tcp_header_t *) udp1;
+          icmp1 = (icmp46_header_t *) udp1;
+          proto1 = ip_proto_to_snat_proto (ip1->protocol);
+
+          if (is_slow_path)
+            {
+              if (PREDICT_FALSE (proto1 == ~0))
+                {
+                  s1 = nat44_ed_out2in_unknown_proto(sm, b1, ip1, rx_fib_index1,
+                                                     thread_index, now, vm, node);
+                  if (!sm->forwarding_enabled)
+                    {
+                      if (!s1)
+                        next1 = NAT44_ED_OUT2IN_NEXT_DROP;
+                      goto trace01;
+                    }
+                }
+
+              if (PREDICT_FALSE (proto1 == SNAT_PROTOCOL_ICMP))
+                {
+                  next1 = icmp_out2in_slow_path
+                    (sm, b1, ip1, icmp1, sw_if_index1, rx_fib_index1, node,
+                     next1, now, thread_index, &s1);
+                  goto trace01;
+                }
+            }
+          else
+            {
+              if (PREDICT_FALSE (proto1 == ~0 || proto1 == SNAT_PROTOCOL_ICMP))
+                {
+                  next1 = NAT44_ED_OUT2IN_NEXT_SLOW_PATH;
+                  goto trace01;
+                }
+
+              if (ip4_is_fragment (ip1))
+                {
+                  b1->error = node->errors[SNAT_OUT2IN_ERROR_DROP_FRAGMENT];
+                  next1 = NAT44_ED_OUT2IN_NEXT_DROP;
+                  goto trace01;
+                }
+            }
+
+          make_ed_kv (&kv1, &ip1->dst_address, &ip1->src_address, ip1->protocol,
+                      rx_fib_index1, udp1->dst_port, udp1->src_port);
+
+          if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv1, &value1))
+            {
+              if (is_slow_path)
+                {
+                  /* Try to match static mapping by external address and port,
+                     destination address and port in packet */
+                  e_key1.addr = ip1->dst_address;
+                  e_key1.port = udp1->dst_port;
+                  e_key1.protocol = proto1;
+                  e_key1.fib_index = rx_fib_index1;
+                  if (snat_static_mapping_match(sm, e_key1, &l_key1, 1, 0,
+                      &twice_nat1, &is_lb1))
+                    {
+                      /*
+                       * Send DHCP packets to the ipv4 stack, or we won't
+                       * be able to use dhcp client on the outside interface
+                       */
+                      if (PREDICT_FALSE (proto1 == SNAT_PROTOCOL_UDP
+                          && (udp1->dst_port ==
+                          clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client))))
+                        {
+                          vnet_feature_next
+                            (vnet_buffer (b1)->sw_if_index[VLIB_RX], &next1, b1);
+                          goto trace01;
+                        }
+
+                      if (!sm->forwarding_enabled)
+                        {
+                          b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                          next1 = NAT44_ED_OUT2IN_NEXT_DROP;
+                        }
+                      else
+                        {
+                          if (next_src_nat(sm, ip1, ip1->protocol,
+                                           udp1->src_port, udp1->dst_port,
+                                           thread_index))
+                            {
+                              next1 = NAT44_ED_OUT2IN_NEXT_IN2OUT;
+                              goto trace01;
+                            }
+                          create_bypass_for_fwd(sm, ip1, rx_fib_index1,
+                                                thread_index);
+                        }
+                      goto trace01;
+                    }
+
+                  /* Create session initiated by host from external network */
+                  s1 = create_session_for_static_mapping_ed(sm, b1, l_key1,
+                                                            e_key1, node,
+                                                            thread_index,
+                                                            twice_nat1, is_lb1);
+
+                  if (!s1)
+                    {
+                      next1 = NAT44_ED_OUT2IN_NEXT_DROP;
+                      goto trace01;
+                    }
+                }
+              else
+                {
+                  next1 = NAT44_ED_OUT2IN_NEXT_SLOW_PATH;
+                  goto trace01;
+                }
+            }
+          else
+            {
+              s1 = pool_elt_at_index (tsm->sessions, value1.value);
+            }
+
+          old_addr1 = ip1->dst_address.as_u32;
+          new_addr1 = ip1->dst_address.as_u32 = s1->in2out.addr.as_u32;
+          vnet_buffer(b1)->sw_if_index[VLIB_TX] = s1->in2out.fib_index;
+
+          sum1 = ip1->checksum;
+          sum1 = ip_csum_update (sum1, old_addr1, new_addr1, ip4_header_t,
+                                 dst_address);
+          if (PREDICT_FALSE (is_twice_nat_session (s1)))
+            sum1 = ip_csum_update (sum1, ip1->src_address.as_u32,
+                                   s1->ext_host_nat_addr.as_u32, ip4_header_t,
+                                   src_address);
+          ip1->checksum = ip_csum_fold (sum1);
+
+          if (PREDICT_TRUE (proto1 == SNAT_PROTOCOL_TCP))
+            {
+              old_port1 = tcp1->dst_port;
+              new_port1 = tcp1->dst_port = s1->in2out.port;
+
+              sum1 = tcp1->checksum;
+              sum1 = ip_csum_update (sum1, old_addr1, new_addr1, ip4_header_t,
+                                     dst_address);
+              sum1 = ip_csum_update (sum1, old_port1, new_port1, ip4_header_t,
+                                     length);
+              if (is_twice_nat_session (s1))
+                {
+                  sum1 = ip_csum_update (sum1, ip1->src_address.as_u32,
+                                         s1->ext_host_nat_addr.as_u32,
+                                         ip4_header_t, dst_address);
+                  sum1 = ip_csum_update (sum1, tcp1->src_port,
+                                         s1->ext_host_nat_port, ip4_header_t,
+                                         length);
+                  tcp1->src_port = s1->ext_host_nat_port;
+                  ip1->src_address.as_u32 = s1->ext_host_nat_addr.as_u32;
+                }
+              tcp1->checksum = ip_csum_fold(sum1);
+              if (nat44_set_tcp_session_state_o2i (sm, s1, tcp1, thread_index))
+                goto trace01;
+            }
+          else
+            {
+              udp1->dst_port = s1->in2out.port;
+              if (is_twice_nat_session (s1))
+                {
+                  udp1->src_port = s1->ext_host_nat_port;
+                  ip1->src_address.as_u32 = s1->ext_host_nat_addr.as_u32;
+                }
+              udp1->checksum = 0;
+            }
+
+          /* Accounting */
+          nat44_session_update_counters (s1, now,
+                                         vlib_buffer_length_in_chain (vm, b1));
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s1, thread_index);
+
+        trace01:
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b1->flags & VLIB_BUFFER_IS_TRACED)))
+            {
+              nat44_ed_out2in_trace_t *t =
+                vlib_add_trace (vm, node, b1, sizeof (*t));
+              t->is_slow_path = is_slow_path;
+              t->sw_if_index = sw_if_index1;
+              t->next_index = next1;
+              t->session_index = ~0;
+              if (s1)
+                t->session_index = s1 - tsm->sessions;
+            }
+
+          pkts_processed += next1 != NAT44_ED_OUT2IN_NEXT_DROP;
+
+          /* verify speculative enqueues, maybe switch current next frame */
+          vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
+                                           to_next, n_left_to_next,
+                                           bi0, bi1, next0, next1);
+        }
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+          u32 bi0;
+         vlib_buffer_t *b0;
+          u32 next0, sw_if_index0, rx_fib_index0, proto0, old_addr0, new_addr0;
+          u16 old_port0, new_port0;
+          ip4_header_t *ip0;
+          udp_header_t *udp0;
+          tcp_header_t *tcp0;
+          icmp46_header_t * icmp0;
+          snat_session_t *s0 = 0;
+          clib_bihash_kv_16_8_t kv0, value0;
+          ip_csum_t sum0;
+          snat_session_key_t e_key0, l_key0;
+          u8 is_lb0;
+          twice_nat_type_t twice_nat0;
+
+          /* speculatively enqueue b0 to the current next frame */
+         bi0 = from[0];
+         to_next[0] = bi0;
+         from += 1;
+         to_next += 1;
+         n_left_from -= 1;
+         n_left_to_next -= 1;
+
+         b0 = vlib_get_buffer (vm, bi0);
+          next0 = NAT44_ED_OUT2IN_NEXT_LOOKUP;
+          vnet_buffer (b0)->snat.flags = 0;
+          ip0 = vlib_buffer_get_current (b0);
+
+          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
+         rx_fib_index0 = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                               sw_if_index0);
+
+          if (PREDICT_FALSE(ip0->ttl == 1))
+            {
+              vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0;
+              icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded,
+                                           ICMP4_time_exceeded_ttl_exceeded_in_transit,
+                                           0);
+              next0 = NAT44_ED_OUT2IN_NEXT_ICMP_ERROR;
+              goto trace0;
+            }
+
+          udp0 = ip4_next_header (ip0);
+          tcp0 = (tcp_header_t *) udp0;
+          icmp0 = (icmp46_header_t *) udp0;
+          proto0 = ip_proto_to_snat_proto (ip0->protocol);
+
+          if (is_slow_path)
+            {
+              if (PREDICT_FALSE (proto0 == ~0))
+                {
+                  s0 = nat44_ed_out2in_unknown_proto(sm, b0, ip0, rx_fib_index0,
+                                                     thread_index, now, vm, node);
+                  if (!sm->forwarding_enabled)
+                    {
+                      if (!s0)
+                        next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                      goto trace0;
+                    }
+                }
+
+              if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = icmp_out2in_slow_path
+                    (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node,
+                     next0, now, thread_index, &s0);
+                  goto trace0;
+                }
+            }
+          else
+            {
+              if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP))
+                {
+                  next0 = NAT44_ED_OUT2IN_NEXT_SLOW_PATH;
+                  goto trace0;
+                }
+
+              if (ip4_is_fragment (ip0))
+                {
+                  b0->error = node->errors[SNAT_OUT2IN_ERROR_DROP_FRAGMENT];
+                  next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                  goto trace0;
+                }
+            }
+
+          make_ed_kv (&kv0, &ip0->dst_address, &ip0->src_address, ip0->protocol,
+                      rx_fib_index0, udp0->dst_port, udp0->src_port);
+
+          if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv0, &value0))
+            {
+              if (is_slow_path)
+                {
+                  /* Try to match static mapping by external address and port,
+                     destination address and port in packet */
+                  e_key0.addr = ip0->dst_address;
+                  e_key0.port = udp0->dst_port;
+                  e_key0.protocol = proto0;
+                  e_key0.fib_index = rx_fib_index0;
+                  if (snat_static_mapping_match(sm, e_key0, &l_key0, 1, 0,
+                      &twice_nat0, &is_lb0))
+                    {
+                      /*
+                       * Send DHCP packets to the ipv4 stack, or we won't
+                       * be able to use dhcp client on the outside interface
+                       */
+                      if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_UDP
+                          && (udp0->dst_port ==
+                          clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client))))
+                        {
+                          vnet_feature_next
+                            (vnet_buffer (b0)->sw_if_index[VLIB_RX], &next0, b0);
+                          goto trace0;
+                        }
+
+                      if (!sm->forwarding_enabled)
+                        {
+                          b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                          next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                        }
+                      else
+                        {
+                          if (next_src_nat(sm, ip0, ip0->protocol,
+                                           udp0->src_port, udp0->dst_port,
+                                           thread_index))
+                            {
+                              next0 = NAT44_ED_OUT2IN_NEXT_IN2OUT;
+                              goto trace0;
+                            }
+                          create_bypass_for_fwd(sm, ip0, rx_fib_index0,
+                                                thread_index);
+                        }
+                      goto trace0;
+                    }
+
+                  /* Create session initiated by host from external network */
+                  s0 = create_session_for_static_mapping_ed(sm, b0, l_key0,
+                                                            e_key0, node,
+                                                            thread_index,
+                                                            twice_nat0, is_lb0);
+
+                  if (!s0)
+                    {
+                      next0 = NAT44_ED_OUT2IN_NEXT_DROP;
+                      goto trace0;
+                    }
+                }
+              else
+                {
+                  next0 = NAT44_ED_OUT2IN_NEXT_SLOW_PATH;
+                  goto trace0;
+                }
+            }
+          else
+            {
+              s0 = pool_elt_at_index (tsm->sessions, value0.value);
+            }
+
+          old_addr0 = ip0->dst_address.as_u32;
+          new_addr0 = ip0->dst_address.as_u32 = s0->in2out.addr.as_u32;
+          vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index;
+
+          sum0 = ip0->checksum;
+          sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                 dst_address);
+          if (PREDICT_FALSE (is_twice_nat_session (s0)))
+            sum0 = ip_csum_update (sum0, ip0->src_address.as_u32,
+                                   s0->ext_host_nat_addr.as_u32, ip4_header_t,
+                                   src_address);
+          ip0->checksum = ip_csum_fold (sum0);
+
+          if (PREDICT_TRUE (proto0 == SNAT_PROTOCOL_TCP))
+            {
+              old_port0 = tcp0->dst_port;
+              new_port0 = tcp0->dst_port = s0->in2out.port;
+
+              sum0 = tcp0->checksum;
+              sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
+                                     dst_address);
+              sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t,
+                                     length);
+              if (is_twice_nat_session (s0))
+                {
+                  sum0 = ip_csum_update (sum0, ip0->src_address.as_u32,
+                                         s0->ext_host_nat_addr.as_u32,
+                                         ip4_header_t, dst_address);
+                  sum0 = ip_csum_update (sum0, tcp0->src_port,
+                                         s0->ext_host_nat_port, ip4_header_t,
+                                         length);
+                  tcp0->src_port = s0->ext_host_nat_port;
+                  ip0->src_address.as_u32 = s0->ext_host_nat_addr.as_u32;
+                }
+              tcp0->checksum = ip_csum_fold(sum0);
+              if (nat44_set_tcp_session_state_o2i (sm, s0, tcp0, thread_index))
+                goto trace0;
+            }
+          else
+            {
+              udp0->dst_port = s0->in2out.port;
+              if (is_twice_nat_session (s0))
+                {
+                  udp0->src_port = s0->ext_host_nat_port;
+                  ip0->src_address.as_u32 = s0->ext_host_nat_addr.as_u32;
+                }
+              udp0->checksum = 0;
+            }
+
+          /* Accounting */
+          nat44_session_update_counters (s0, now,
+                                         vlib_buffer_length_in_chain (vm, b0));
+          /* Per-user LRU list maintenance */
+          nat44_session_update_lru (sm, s0, thread_index);
+
+        trace0:
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b0->flags & VLIB_BUFFER_IS_TRACED)))
+            {
+              nat44_ed_out2in_trace_t *t =
+                vlib_add_trace (vm, node, b0, sizeof (*t));
+              t->is_slow_path = is_slow_path;
+              t->sw_if_index = sw_if_index0;
+              t->next_index = next0;
+              t->session_index = ~0;
+              if (s0)
+                t->session_index = s0 - tsm->sessions;
+            }
+
+          pkts_processed += next0 != NAT44_ED_OUT2IN_NEXT_DROP;
+
+          /* verify speculative enqueue, maybe switch current next frame */
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, next0);
+        }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  vlib_node_increment_counter (vm, stats_node_index,
+                               SNAT_OUT2IN_ERROR_OUT2IN_PACKETS,
+                               pkts_processed);
+  return frame->n_vectors;
+}
+
+static uword
+nat44_ed_out2in_fast_path_fn (vlib_main_t * vm,
+                              vlib_node_runtime_t * node,
+                              vlib_frame_t * frame)
+{
+  return nat44_ed_out2in_node_fn_inline (vm, node, frame, 0);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_out2in_node) = {
+  .function = nat44_ed_out2in_fast_path_fn,
+  .name = "nat44-ed-out2in",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_ed_out2in_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(snat_out2in_error_strings),
+  .error_strings = snat_out2in_error_strings,
+
+  .runtime_data_bytes = sizeof (snat_runtime_t),
+
+  .n_next_nodes = NAT44_ED_OUT2IN_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [NAT44_ED_OUT2IN_NEXT_DROP] = "error-drop",
+    [NAT44_ED_OUT2IN_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT44_ED_OUT2IN_NEXT_SLOW_PATH] = "nat44-ed-out2in-slowpath",
+    [NAT44_ED_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [NAT44_ED_OUT2IN_NEXT_IN2OUT] = "nat44-ed-in2out",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_out2in_node, nat44_ed_out2in_fast_path_fn);
+
+static uword
+nat44_ed_out2in_slow_path_fn (vlib_main_t * vm,
+                              vlib_node_runtime_t * node,
+                              vlib_frame_t * frame)
+{
+  return nat44_ed_out2in_node_fn_inline (vm, node, frame, 1);
+}
+
+VLIB_REGISTER_NODE (nat44_ed_out2in_slowpath_node) = {
+  .function = nat44_ed_out2in_slow_path_fn,
+  .name = "nat44-ed-out2in-slowpath",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_ed_out2in_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(snat_out2in_error_strings),
+  .error_strings = snat_out2in_error_strings,
+
+  .runtime_data_bytes = sizeof (snat_runtime_t),
+
+  .n_next_nodes = NAT44_ED_OUT2IN_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [NAT44_ED_OUT2IN_NEXT_DROP] = "error-drop",
+    [NAT44_ED_OUT2IN_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT44_ED_OUT2IN_NEXT_SLOW_PATH] = "nat44-ed-out2in-slowpath",
+    [NAT44_ED_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [NAT44_ED_OUT2IN_NEXT_IN2OUT] = "nat44-ed-in2out",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat44_ed_out2in_slowpath_node,
+                              nat44_ed_out2in_slow_path_fn);
+
 /**************************/
 /*** deterministic mode ***/
 /**************************/
@@ -2535,7 +3309,6 @@ VLIB_REGISTER_NODE (snat_det_out2in_node) = {
     [SNAT_OUT2IN_NEXT_LOOKUP] = "ip4-lookup",
     [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
     [SNAT_OUT2IN_NEXT_REASS] = "nat44-out2in-reass",
-    [SNAT_OUT2IN_NEXT_IN2OUT] = "nat44-in2out",
   },
 };
 VLIB_NODE_FUNCTION_MULTIARCH (snat_det_out2in_node, snat_det_out2in_node_fn);
@@ -3058,7 +3831,6 @@ VLIB_REGISTER_NODE (snat_out2in_fast_node) = {
     [SNAT_OUT2IN_NEXT_DROP] = "error-drop",
     [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
     [SNAT_OUT2IN_NEXT_REASS] = "nat44-out2in-reass",
-    [SNAT_OUT2IN_NEXT_IN2OUT] = "nat44-in2out",
   },
 };
 VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_fast_node, snat_out2in_fast_node_fn);
diff --git a/src/scripts/vnet/nat44_lb b/src/scripts/vnet/nat44_lb
new file mode 100644 (file)
index 0000000..f66c13c
--- /dev/null
@@ -0,0 +1,48 @@
+create packet-generator interface pg0
+create packet-generator interface pg1
+
+packet-generator new {
+  name f1
+  limit 1000000
+  node ip4-input
+  size 64-64
+  no-recycle
+  worker 0
+  interface pg1
+  data {
+    UDP: 172.16.1.11 -> 172.16.1.3
+    UDP: 3001 -> 3000
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+
+packet-generator new {
+  name f2
+  limit 1000000
+  node ip4-input
+  size 64-64
+  no-recycle
+  worker 1
+  interface pg1
+  data {
+    UDP: 172.16.1.10 -> 172.16.1.3
+    UDP: 3001 -> 3000
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+nat44 add address 172.16.1.3
+nat44 add load-balancing static mapping protocol udp external 172.16.1.3:3000 local 10.0.0.10:30000 probability 25 local 10.0.0.11:30000 probability 25 local 10.0.0.12:30000 probability 25 local 10.0.0.13:30000 probability 25
+set int ip address pg0 10.0.0.1/24
+set int ip address pg1 172.16.1.1/24
+set int state pg0 up
+set int state pg1 up
+set ip arp static pg0 10.0.0.10 abcd.abcd.abcd
+set ip arp static pg0 10.0.0.11 abcd.abcd.abce
+set ip arp static pg0 10.0.0.12 abce.abcd.abcd
+set ip arp static pg0 10.0.0.13 abce.abcd.abce
+set ip arp static pg1 172.16.1.10 cdef.abcd.abcd
+set ip arp static pg1 172.16.1.11 cdef.abcd.abce
+set int nat44 in pg0 out pg1
+trace add pg-input 10
index 15bef1b..25e16e9 100644 (file)
@@ -33,7 +33,7 @@ packet-generator new {
 }
 
 nat44 add address 172.16.1.3
-nat44 add static mapping local 10.0.0.3 3000 external 172.16.1.3 3000
+nat44 add static mapping udp local 10.0.0.3 3000 external 172.16.1.3 3000
 set int ip address pg0 10.0.0.1/24
 set int ip address pg1 172.16.1.1/24
 set int state pg0 up
index ad2b964..8012350 100644 (file)
@@ -25,6 +25,174 @@ from util import mactobinary
 class MethodHolder(VppTestCase):
     """ NAT create capture and verify method holder """
 
+    def clear_nat44(self):
+        """
+        Clear NAT44 configuration.
+        """
+        if hasattr(self, 'pg7') and hasattr(self, 'pg8'):
+            # I found no elegant way to do this
+            self.vapi.ip_add_del_route(
+                dst_address=self.pg7.remote_ip4n,
+                dst_address_length=32,
+                next_hop_address=self.pg7.remote_ip4n,
+                next_hop_sw_if_index=self.pg7.sw_if_index,
+                is_add=0)
+            self.vapi.ip_add_del_route(
+                dst_address=self.pg8.remote_ip4n,
+                dst_address_length=32,
+                next_hop_address=self.pg8.remote_ip4n,
+                next_hop_sw_if_index=self.pg8.sw_if_index,
+                is_add=0)
+
+            for intf in [self.pg7, self.pg8]:
+                neighbors = self.vapi.ip_neighbor_dump(intf.sw_if_index)
+                for n in neighbors:
+                    self.vapi.ip_neighbor_add_del(intf.sw_if_index,
+                                                  n.mac_address,
+                                                  n.ip_address,
+                                                  is_add=0)
+
+            if self.pg7.has_ip4_config:
+                self.pg7.unconfig_ip4()
+
+        self.vapi.nat44_forwarding_enable_disable(0)
+
+        interfaces = self.vapi.nat44_interface_addr_dump()
+        for intf in interfaces:
+            self.vapi.nat44_add_interface_addr(intf.sw_if_index,
+                                               twice_nat=intf.twice_nat,
+                                               is_add=0)
+
+        self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port,
+                            domain_id=self.ipfix_domain_id)
+        self.ipfix_src_port = 4739
+        self.ipfix_domain_id = 1
+
+        interfaces = self.vapi.nat44_interface_dump()
+        for intf in interfaces:
+            if intf.is_inside > 1:
+                self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
+                                                          0,
+                                                          is_add=0)
+            self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
+                                                      intf.is_inside,
+                                                      is_add=0)
+
+        interfaces = self.vapi.nat44_interface_output_feature_dump()
+        for intf in interfaces:
+            self.vapi.nat44_interface_add_del_output_feature(intf.sw_if_index,
+                                                             intf.is_inside,
+                                                             is_add=0)
+
+        static_mappings = self.vapi.nat44_static_mapping_dump()
+        for sm in static_mappings:
+            self.vapi.nat44_add_del_static_mapping(
+                sm.local_ip_address,
+                sm.external_ip_address,
+                local_port=sm.local_port,
+                external_port=sm.external_port,
+                addr_only=sm.addr_only,
+                vrf_id=sm.vrf_id,
+                protocol=sm.protocol,
+                twice_nat=sm.twice_nat,
+                self_twice_nat=sm.self_twice_nat,
+                out2in_only=sm.out2in_only,
+                tag=sm.tag,
+                external_sw_if_index=sm.external_sw_if_index,
+                is_add=0)
+
+        lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
+        for lb_sm in lb_static_mappings:
+            self.vapi.nat44_add_del_lb_static_mapping(
+                lb_sm.external_addr,
+                lb_sm.external_port,
+                lb_sm.protocol,
+                vrf_id=lb_sm.vrf_id,
+                twice_nat=lb_sm.twice_nat,
+                self_twice_nat=lb_sm.self_twice_nat,
+                out2in_only=lb_sm.out2in_only,
+                tag=lb_sm.tag,
+                is_add=0,
+                local_num=0,
+                locals=[])
+
+        identity_mappings = self.vapi.nat44_identity_mapping_dump()
+        for id_m in identity_mappings:
+            self.vapi.nat44_add_del_identity_mapping(
+                addr_only=id_m.addr_only,
+                ip=id_m.ip_address,
+                port=id_m.port,
+                sw_if_index=id_m.sw_if_index,
+                vrf_id=id_m.vrf_id,
+                protocol=id_m.protocol,
+                is_add=0)
+
+        adresses = self.vapi.nat44_address_dump()
+        for addr in adresses:
+            self.vapi.nat44_add_del_address_range(addr.ip_address,
+                                                  addr.ip_address,
+                                                  twice_nat=addr.twice_nat,
+                                                  is_add=0)
+
+        self.vapi.nat_set_reass()
+        self.vapi.nat_set_reass(is_ip6=1)
+
+    def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0',
+                                 local_port=0, external_port=0, vrf_id=0,
+                                 is_add=1, external_sw_if_index=0xFFFFFFFF,
+                                 proto=0, twice_nat=0, self_twice_nat=0,
+                                 out2in_only=0, tag=""):
+        """
+        Add/delete NAT44 static mapping
+
+        :param local_ip: Local IP address
+        :param external_ip: External IP address
+        :param local_port: Local port number (Optional)
+        :param external_port: External port number (Optional)
+        :param vrf_id: VRF ID (Default 0)
+        :param is_add: 1 if add, 0 if delete (Default add)
+        :param external_sw_if_index: External interface instead of IP address
+        :param proto: IP protocol (Mandatory if port specified)
+        :param twice_nat: 1 if translate external host address and port
+        :param self_twice_nat: 1 if translate external host address and port
+                               whenever external host address equals
+                               local address of internal host
+        :param out2in_only: if 1 rule is matching only out2in direction
+        :param tag: Opaque string tag
+        """
+        addr_only = 1
+        if local_port and external_port:
+            addr_only = 0
+        l_ip = socket.inet_pton(socket.AF_INET, local_ip)
+        e_ip = socket.inet_pton(socket.AF_INET, external_ip)
+        self.vapi.nat44_add_del_static_mapping(
+            l_ip,
+            e_ip,
+            external_sw_if_index,
+            local_port,
+            external_port,
+            addr_only,
+            vrf_id,
+            proto,
+            twice_nat,
+            self_twice_nat,
+            out2in_only,
+            tag,
+            is_add)
+
+    def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF, twice_nat=0):
+        """
+        Add/delete NAT44 address
+
+        :param ip: IP address
+        :param is_add: 1 if add, 0 if delete (Default add)
+        :param twice_nat: twice NAT address for extenal hosts
+        """
+        nat_addr = socket.inet_pton(socket.AF_INET, ip)
+        self.vapi.nat44_add_del_address_range(nat_addr, nat_addr, is_add,
+                                              vrf_id=vrf_id,
+                                              twice_nat=twice_nat)
+
     def create_stream_in(self, in_if, out_if, dst_ip=None, ttl=64):
         """
         Create packet stream for inside network
@@ -895,215 +1063,50 @@ class TestNAT44(MethodHolder):
             super(TestNAT44, cls).tearDownClass()
             raise
 
-    def clear_nat44(self):
-        """
-        Clear NAT44 configuration.
-        """
-        # I found no elegant way to do this
-        self.vapi.ip_add_del_route(dst_address=self.pg7.remote_ip4n,
-                                   dst_address_length=32,
-                                   next_hop_address=self.pg7.remote_ip4n,
-                                   next_hop_sw_if_index=self.pg7.sw_if_index,
-                                   is_add=0)
-        self.vapi.ip_add_del_route(dst_address=self.pg8.remote_ip4n,
-                                   dst_address_length=32,
-                                   next_hop_address=self.pg8.remote_ip4n,
-                                   next_hop_sw_if_index=self.pg8.sw_if_index,
-                                   is_add=0)
-
-        for intf in [self.pg7, self.pg8]:
-            neighbors = self.vapi.ip_neighbor_dump(intf.sw_if_index)
-            for n in neighbors:
-                self.vapi.ip_neighbor_add_del(intf.sw_if_index,
-                                              n.mac_address,
-                                              n.ip_address,
-                                              is_add=0)
+    def test_dynamic(self):
+        """ NAT44 dynamic translation test """
 
-        if self.pg7.has_ip4_config:
-            self.pg7.unconfig_ip4()
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
 
-        self.vapi.nat44_forwarding_enable_disable(0)
+        # in2out
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
 
-        interfaces = self.vapi.nat44_interface_addr_dump()
-        for intf in interfaces:
-            self.vapi.nat44_add_interface_addr(intf.sw_if_index,
-                                               twice_nat=intf.twice_nat,
-                                               is_add=0)
+        # out2in
+        pkts = self.create_stream_out(self.pg1)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
 
-        self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port,
-                            domain_id=self.ipfix_domain_id)
-        self.ipfix_src_port = 4739
-        self.ipfix_domain_id = 1
+    def test_dynamic_icmp_errors_in2out_ttl_1(self):
+        """ NAT44 handling of client packets with TTL=1 """
 
-        interfaces = self.vapi.nat44_interface_dump()
-        for intf in interfaces:
-            if intf.is_inside > 1:
-                self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
-                                                          0,
-                                                          is_add=0)
-            self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
-                                                      intf.is_inside,
-                                                      is_add=0)
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
 
-        interfaces = self.vapi.nat44_interface_output_feature_dump()
-        for intf in interfaces:
-            self.vapi.nat44_interface_add_del_output_feature(intf.sw_if_index,
-                                                             intf.is_inside,
-                                                             is_add=0)
+        # Client side - generate traffic
+        pkts = self.create_stream_in(self.pg0, self.pg1, ttl=1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
 
-        static_mappings = self.vapi.nat44_static_mapping_dump()
-        for sm in static_mappings:
-            self.vapi.nat44_add_del_static_mapping(
-                sm.local_ip_address,
-                sm.external_ip_address,
-                local_port=sm.local_port,
-                external_port=sm.external_port,
-                addr_only=sm.addr_only,
-                vrf_id=sm.vrf_id,
-                protocol=sm.protocol,
-                twice_nat=sm.twice_nat,
-                self_twice_nat=sm.self_twice_nat,
-                out2in_only=sm.out2in_only,
-                tag=sm.tag,
-                external_sw_if_index=sm.external_sw_if_index,
-                is_add=0)
+        # Client side - verify ICMP type 11 packets
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in_with_icmp_errors(capture, self.pg0)
 
-        lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
-        for lb_sm in lb_static_mappings:
-            self.vapi.nat44_add_del_lb_static_mapping(
-                lb_sm.external_addr,
-                lb_sm.external_port,
-                lb_sm.protocol,
-                vrf_id=lb_sm.vrf_id,
-                twice_nat=lb_sm.twice_nat,
-                self_twice_nat=lb_sm.self_twice_nat,
-                out2in_only=lb_sm.out2in_only,
-                tag=lb_sm.tag,
-                is_add=0,
-                local_num=0,
-                locals=[])
-
-        identity_mappings = self.vapi.nat44_identity_mapping_dump()
-        for id_m in identity_mappings:
-            self.vapi.nat44_add_del_identity_mapping(
-                addr_only=id_m.addr_only,
-                ip=id_m.ip_address,
-                port=id_m.port,
-                sw_if_index=id_m.sw_if_index,
-                vrf_id=id_m.vrf_id,
-                protocol=id_m.protocol,
-                is_add=0)
-
-        adresses = self.vapi.nat44_address_dump()
-        for addr in adresses:
-            self.vapi.nat44_add_del_address_range(addr.ip_address,
-                                                  addr.ip_address,
-                                                  twice_nat=addr.twice_nat,
-                                                  is_add=0)
-
-        self.vapi.nat_set_reass()
-        self.vapi.nat_set_reass(is_ip6=1)
-
-    def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0',
-                                 local_port=0, external_port=0, vrf_id=0,
-                                 is_add=1, external_sw_if_index=0xFFFFFFFF,
-                                 proto=0, twice_nat=0, self_twice_nat=0,
-                                 out2in_only=0, tag=""):
-        """
-        Add/delete NAT44 static mapping
-
-        :param local_ip: Local IP address
-        :param external_ip: External IP address
-        :param local_port: Local port number (Optional)
-        :param external_port: External port number (Optional)
-        :param vrf_id: VRF ID (Default 0)
-        :param is_add: 1 if add, 0 if delete (Default add)
-        :param external_sw_if_index: External interface instead of IP address
-        :param proto: IP protocol (Mandatory if port specified)
-        :param twice_nat: 1 if translate external host address and port
-        :param self_twice_nat: 1 if translate external host address and port
-                               whenever external host address equals
-                               local address of internal host
-        :param out2in_only: if 1 rule is matching only out2in direction
-        :param tag: Opaque string tag
-        """
-        addr_only = 1
-        if local_port and external_port:
-            addr_only = 0
-        l_ip = socket.inet_pton(socket.AF_INET, local_ip)
-        e_ip = socket.inet_pton(socket.AF_INET, external_ip)
-        self.vapi.nat44_add_del_static_mapping(
-            l_ip,
-            e_ip,
-            external_sw_if_index,
-            local_port,
-            external_port,
-            addr_only,
-            vrf_id,
-            proto,
-            twice_nat,
-            self_twice_nat,
-            out2in_only,
-            tag,
-            is_add)
-
-    def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF, twice_nat=0):
-        """
-        Add/delete NAT44 address
-
-        :param ip: IP address
-        :param is_add: 1 if add, 0 if delete (Default add)
-        :param twice_nat: twice NAT address for extenal hosts
-        """
-        nat_addr = socket.inet_pton(socket.AF_INET, ip)
-        self.vapi.nat44_add_del_address_range(nat_addr, nat_addr, is_add,
-                                              vrf_id=vrf_id,
-                                              twice_nat=twice_nat)
-
-    def test_dynamic(self):
-        """ NAT44 dynamic translation test """
-
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        # in2out
-        pkts = self.create_stream_in(self.pg0, self.pg1)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
-        self.verify_capture_out(capture)
-
-        # out2in
-        pkts = self.create_stream_out(self.pg1)
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(len(pkts))
-        self.verify_capture_in(capture, self.pg0)
-
-    def test_dynamic_icmp_errors_in2out_ttl_1(self):
-        """ NAT44 handling of client packets with TTL=1 """
-
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        # Client side - generate traffic
-        pkts = self.create_stream_in(self.pg0, self.pg1, ttl=1)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-
-        # Client side - verify ICMP type 11 packets
-        capture = self.pg0.get_capture(len(pkts))
-        self.verify_capture_in_with_icmp_errors(capture, self.pg0)
-
-    def test_dynamic_icmp_errors_out2in_ttl_1(self):
-        """ NAT44 handling of server packets with TTL=1 """
+    def test_dynamic_icmp_errors_out2in_ttl_1(self):
+        """ NAT44 handling of server packets with TTL=1 """
 
         self.nat44_add_address(self.nat_addr)
         self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
@@ -1249,7 +1252,7 @@ class TestNAT44(MethodHolder):
         self.verify_capture_out(capture, same_port=True, packet_num=1)
         self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
 
-    def test_forwarding(self):
+    def _test_forwarding(self):
         """ NAT44 forwarding test """
 
         self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
@@ -1464,132 +1467,6 @@ class TestNAT44(MethodHolder):
         capture = self.pg1.get_capture(len(pkts))
         self.verify_capture_out(capture)
 
-    def test_static_with_port_out2(self):
-        """ 1:1 NAPT symmetrical rule """
-
-        external_port = 80
-        local_port = 8080
-
-        self.vapi.nat44_forwarding_enable_disable(1)
-        self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
-                                      local_port, external_port,
-                                      proto=IP_PROTOS.tcp, out2in_only=1)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        # from client to service
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             TCP(sport=12345, dport=external_port))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.dst, self.pg0.remote_ip4)
-            self.assertEqual(tcp.dport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # ICMP error
-        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             ICMP(type=11) / capture[0][IP])
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            self.assertEqual(p[IP].src, self.nat_addr)
-            inner = p[IPerror]
-            self.assertEqual(inner.dst, self.nat_addr)
-            self.assertEqual(inner[TCPerror].dport, external_port)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from service back to client
-        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=local_port, dport=12345))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.sport, external_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # ICMP error
-        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             ICMP(type=11) / capture[0][IP])
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        try:
-            self.assertEqual(p[IP].dst, self.pg0.remote_ip4)
-            inner = p[IPerror]
-            self.assertEqual(inner.src, self.pg0.remote_ip4)
-            self.assertEqual(inner[TCPerror].sport, local_port)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from client to server (no translation)
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
-             TCP(sport=12346, dport=local_port))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.dst, self.pg0.remote_ip4)
-            self.assertEqual(tcp.dport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from service back to client (no translation)
-        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=local_port, dport=12346))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.pg0.remote_ip4)
-            self.assertEqual(tcp.sport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
     def test_static_vrf_aware(self):
         """ 1:1 NAT VRF awareness """
 
@@ -1683,243 +1560,8 @@ class TestNAT44(MethodHolder):
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-    def test_static_lb(self):
-        """ NAT44 local service load balancing """
-        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
-        external_port = 80
-        local_port = 8080
-        server1 = self.pg0.remote_hosts[0]
-        server2 = self.pg0.remote_hosts[1]
-
-        locals = [{'addr': server1.ip4n,
-                   'port': local_port,
-                   'probability': 70},
-                  {'addr': server2.ip4n,
-                   'port': local_port,
-                   'probability': 30}]
-
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
-                                                  external_port,
-                                                  IP_PROTOS.tcp,
-                                                  local_num=len(locals),
-                                                  locals=locals)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        # from client to service
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             TCP(sport=12345, dport=external_port))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        server = None
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
-            if ip.dst == server1.ip4:
-                server = server1
-            else:
-                server = server2
-            self.assertEqual(tcp.dport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from service back to client
-        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
-             IP(src=server.ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=local_port, dport=12345))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.sport, external_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
-        self.assertEqual(len(sessions), 1)
-        self.assertTrue(sessions[0].ext_host_valid)
-        self.vapi.nat44_del_session(
-            sessions[0].inside_ip_address,
-            sessions[0].inside_port,
-            sessions[0].protocol,
-            ext_host_address=sessions[0].ext_host_address,
-            ext_host_port=sessions[0].ext_host_port)
-        sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
-        self.assertEqual(len(sessions), 0)
-
-    @unittest.skipUnless(running_extended_tests(), "part of extended tests")
-    def test_static_lb_multi_clients(self):
-        """ NAT44 local service load balancing - multiple clients"""
-
-        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
-        external_port = 80
-        local_port = 8080
-        server1 = self.pg0.remote_hosts[0]
-        server2 = self.pg0.remote_hosts[1]
-
-        locals = [{'addr': server1.ip4n,
-                   'port': local_port,
-                   'probability': 90},
-                  {'addr': server2.ip4n,
-                   'port': local_port,
-                   'probability': 10}]
-
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
-                                                  external_port,
-                                                  IP_PROTOS.tcp,
-                                                  local_num=len(locals),
-                                                  locals=locals)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        server1_n = 0
-        server2_n = 0
-        clients = ip4_range(self.pg1.remote_ip4, 10, 50)
-        pkts = []
-        for client in clients:
-            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-                 IP(src=client, dst=self.nat_addr) /
-                 TCP(sport=12345, dport=external_port))
-            pkts.append(p)
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(len(pkts))
-        for p in capture:
-            if p[IP].dst == server1.ip4:
-                server1_n += 1
-            else:
-                server2_n += 1
-        self.assertTrue(server1_n > server2_n)
-
-    def test_static_lb_2(self):
-        """ NAT44 local service load balancing (asymmetrical rule) """
-        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
-        external_port = 80
-        local_port = 8080
-        server1 = self.pg0.remote_hosts[0]
-        server2 = self.pg0.remote_hosts[1]
-
-        locals = [{'addr': server1.ip4n,
-                   'port': local_port,
-                   'probability': 70},
-                  {'addr': server2.ip4n,
-                   'port': local_port,
-                   'probability': 30}]
-
-        self.vapi.nat44_forwarding_enable_disable(1)
-        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
-                                                  external_port,
-                                                  IP_PROTOS.tcp,
-                                                  out2in_only=1,
-                                                  local_num=len(locals),
-                                                  locals=locals)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        # from client to service
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             TCP(sport=12345, dport=external_port))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        server = None
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
-            if ip.dst == server1.ip4:
-                server = server1
-            else:
-                server = server2
-            self.assertEqual(tcp.dport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from service back to client
-        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
-             IP(src=server.ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=local_port, dport=12345))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.sport, external_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from client to server (no translation)
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=server1.ip4) /
-             TCP(sport=12346, dport=local_port))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        server = None
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.dst, server1.ip4)
-            self.assertEqual(tcp.dport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from service back to client (no translation)
-        p = (Ether(src=server1.mac, dst=self.pg0.local_mac) /
-             IP(src=server1.ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=local_port, dport=12346))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, server1.ip4)
-            self.assertEqual(tcp.sport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-    def test_multiple_inside_interfaces(self):
-        """ NAT44 multiple non-overlapping address space inside interfaces """
+    def test_multiple_inside_interfaces(self):
+        """ NAT44 multiple non-overlapping address space inside interfaces """
 
         self.nat44_add_address(self.nat_addr)
         self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
@@ -3042,144 +2684,25 @@ class TestNAT44(MethodHolder):
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
 
-    def test_unknown_proto(self):
-        """ NAT44 translate packet with unknown protocol """
+    def test_output_feature(self):
+        """ NAT44 interface output feature (in2out postrouting) """
         self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg3.sw_if_index,
+                                                         is_inside=0)
 
         # in2out
-        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=self.tcp_port_in, dport=20))
-        self.pg0.add_stream(p)
+        pkts = self.create_stream_in(self.pg0, self.pg3)
+        self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        p = self.pg1.get_capture(1)
+        capture = self.pg3.get_capture(len(pkts))
+        self.verify_capture_out(capture)
 
-        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             GRE() /
-             IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg1.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, self.nat_addr)
-            self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.assert_packet_checksums_valid(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-        # out2in
-        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             GRE() /
-             IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg0.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
-            self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.assert_packet_checksums_valid(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-    def test_hairpinning_unknown_proto(self):
-        """ NAT44 translate packet with unknown protocol - hairpinning """
-        host = self.pg0.remote_hosts[0]
-        server = self.pg0.remote_hosts[1]
-        host_in_port = 1234
-        server_out_port = 8765
-        server_nat_ip = "10.0.0.11"
-
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        # add static mapping for server
-        self.nat44_add_static_mapping(server.ip4, server_nat_ip)
-
-        # host to server
-        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
-             IP(src=host.ip4, dst=server_nat_ip) /
-             TCP(sport=host_in_port, dport=server_out_port))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg0.get_capture(1)
-
-        p = (Ether(dst=self.pg0.local_mac, src=host.mac) /
-             IP(src=host.ip4, dst=server_nat_ip) /
-             GRE() /
-             IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg0.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, self.nat_addr)
-            self.assertEqual(packet[IP].dst, server.ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.assert_packet_checksums_valid(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-        # server to host
-        p = (Ether(dst=self.pg0.local_mac, src=server.mac) /
-             IP(src=server.ip4, dst=self.nat_addr) /
-             GRE() /
-             IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg0.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, server_nat_ip)
-            self.assertEqual(packet[IP].dst, host.ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.assert_packet_checksums_valid(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-    def test_output_feature(self):
-        """ NAT44 interface output feature (in2out postrouting) """
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_output_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index)
-        self.vapi.nat44_interface_add_del_output_feature(self.pg3.sw_if_index,
-                                                         is_inside=0)
-
-        # in2out
-        pkts = self.create_stream_in(self.pg0, self.pg3)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg3.get_capture(len(pkts))
-        self.verify_capture_out(capture)
-
-        # out2in
-        pkts = self.create_stream_out(self.pg3)
-        self.pg3.add_stream(pkts)
+        # out2in
+        pkts = self.create_stream_out(self.pg3)
+        self.pg3.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg0.get_capture(len(pkts))
@@ -3310,336 +2833,100 @@ class TestNAT44(MethodHolder):
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-    def test_output_feature_and_service(self):
-        """ NAT44 interface output feature and services """
-        external_addr = '1.2.3.4'
-        external_port = 80
-        local_port = 8080
+    def test_one_armed_nat44(self):
+        """ One armed NAT44 """
+        remote_host = self.pg9.remote_hosts[0]
+        local_host = self.pg9.remote_hosts[1]
+        external_port = 0
 
-        self.vapi.nat44_forwarding_enable_disable(1)
         self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_add_del_identity_mapping(ip=self.pg1.remote_ip4n)
-        self.nat44_add_static_mapping(self.pg0.remote_ip4, external_addr,
-                                      local_port, external_port,
-                                      proto=IP_PROTOS.tcp, out2in_only=1)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index,
+        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index,
                                                   is_inside=0)
-        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
-                                                         is_inside=0)
 
-        # from client to service
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=external_addr) /
-             TCP(sport=12345, dport=external_port))
-        self.pg1.add_stream(p)
+        # in2out
+        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
+             IP(src=local_host.ip4, dst=remote_host.ip4) /
+             TCP(sport=12345, dport=80))
+        self.pg9.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg0.get_capture(1)
+        capture = self.pg9.get_capture(1)
         p = capture[0]
         try:
             ip = p[IP]
             tcp = p[TCP]
-            self.assertEqual(ip.dst, self.pg0.remote_ip4)
-            self.assertEqual(tcp.dport, local_port)
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(ip.dst, remote_host.ip4)
+            self.assertNotEqual(tcp.sport, 12345)
+            external_port = tcp.sport
+            self.assertEqual(tcp.dport, 80)
             self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-        # from service back to client
-        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=local_port, dport=12345))
-        self.pg0.add_stream(p)
+        # out2in
+        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
+             IP(src=remote_host.ip4, dst=self.nat_addr) /
+             TCP(sport=80, dport=external_port))
+        self.pg9.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(1)
+        capture = self.pg9.get_capture(1)
         p = capture[0]
         try:
             ip = p[IP]
             tcp = p[TCP]
-            self.assertEqual(ip.src, external_addr)
-            self.assertEqual(tcp.sport, external_port)
+            self.assertEqual(ip.src, remote_host.ip4)
+            self.assertEqual(ip.dst, local_host.ip4)
+            self.assertEqual(tcp.sport, 80)
+            self.assertEqual(tcp.dport, 12345)
             self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-        # from local network host to external network
-        pkts = self.create_stream_in(self.pg0, self.pg1)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
-        self.verify_capture_out(capture)
+    def test_del_session(self):
+        """ Delete NAT44 session """
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
         pkts = self.create_stream_in(self.pg0, self.pg1)
         self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
-        self.verify_capture_out(capture)
+        self.pg1.get_capture(len(pkts))
 
-        # from external network back to local network host
-        pkts = self.create_stream_out(self.pg1)
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(len(pkts))
-        self.verify_capture_in(capture, self.pg0)
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        nsessions = len(sessions)
 
-    def test_output_feature_and_service2(self):
-        """ NAT44 interface output feature and service host direct access """
-        self.vapi.nat44_forwarding_enable_disable(1)
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
-                                                         is_inside=0)
+        self.vapi.nat44_del_session(sessions[0].inside_ip_address,
+                                    sessions[0].inside_port,
+                                    sessions[0].protocol)
+        self.vapi.nat44_del_session(sessions[1].outside_ip_address,
+                                    sessions[1].outside_port,
+                                    sessions[1].protocol,
+                                    is_in=0)
 
-        # session initiaded from service host - translate
-        pkts = self.create_stream_in(self.pg0, self.pg1)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
-        self.verify_capture_out(capture)
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        self.assertEqual(nsessions - len(sessions), 2)
 
-        pkts = self.create_stream_out(self.pg1)
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(len(pkts))
-        self.verify_capture_in(capture, self.pg0)
+    def test_set_get_reass(self):
+        """ NAT44 set/get virtual fragmentation reassembly """
+        reas_cfg1 = self.vapi.nat_get_reass()
 
-        # session initiaded from remote host - do not translate
-        pkts = self.create_stream_out(self.pg1,
-                                      self.pg0.remote_ip4,
-                                      use_inside_ports=True)
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(len(pkts))
-        self.verify_capture_in(capture, self.pg0)
+        self.vapi.nat_set_reass(timeout=reas_cfg1.ip4_timeout + 5,
+                                max_reass=reas_cfg1.ip4_max_reass * 2,
+                                max_frag=reas_cfg1.ip4_max_frag * 2)
 
-        pkts = self.create_stream_in(self.pg0, self.pg1)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
-        self.verify_capture_out(capture, nat_ip=self.pg0.remote_ip4,
-                                same_port=True)
+        reas_cfg2 = self.vapi.nat_get_reass()
 
-    def test_output_feature_and_service3(self):
-        """ NAT44 interface output feature and DST NAT """
-        external_addr = '1.2.3.4'
-        external_port = 80
-        local_port = 8080
-
-        self.vapi.nat44_forwarding_enable_disable(1)
-        self.nat44_add_address(self.nat_addr)
-        self.nat44_add_static_mapping(self.pg1.remote_ip4, external_addr,
-                                      local_port, external_port,
-                                      proto=IP_PROTOS.tcp, out2in_only=1)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index,
-                                                  is_inside=0)
-        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
-                                                         is_inside=0)
-
-        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
-             IP(src=self.pg0.remote_ip4, dst=external_addr) /
-             TCP(sport=12345, dport=external_port))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.pg0.remote_ip4)
-            self.assertEqual(tcp.sport, 12345)
-            self.assertEqual(ip.dst, self.pg1.remote_ip4)
-            self.assertEqual(tcp.dport, local_port)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
-             TCP(sport=local_port, dport=12345))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, external_addr)
-            self.assertEqual(tcp.sport, external_port)
-            self.assertEqual(ip.dst, self.pg0.remote_ip4)
-            self.assertEqual(tcp.dport, 12345)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-    def test_one_armed_nat44(self):
-        """ One armed NAT44 """
-        remote_host = self.pg9.remote_hosts[0]
-        local_host = self.pg9.remote_hosts[1]
-        external_port = 0
-
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index,
-                                                  is_inside=0)
-
-        # in2out
-        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
-             IP(src=local_host.ip4, dst=remote_host.ip4) /
-             TCP(sport=12345, dport=80))
-        self.pg9.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg9.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(ip.dst, remote_host.ip4)
-            self.assertNotEqual(tcp.sport, 12345)
-            external_port = tcp.sport
-            self.assertEqual(tcp.dport, 80)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # out2in
-        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
-             IP(src=remote_host.ip4, dst=self.nat_addr) /
-             TCP(sport=80, dport=external_port))
-        self.pg9.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg9.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, remote_host.ip4)
-            self.assertEqual(ip.dst, local_host.ip4)
-            self.assertEqual(tcp.sport, 80)
-            self.assertEqual(tcp.dport, 12345)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-    def test_one_armed_nat44_static(self):
-        """ One armed NAT44 and 1:1 NAPT symmetrical rule """
-        remote_host = self.pg9.remote_hosts[0]
-        local_host = self.pg9.remote_hosts[1]
-        external_port = 80
-        local_port = 8080
-        eh_port_in = 0
-
-        self.vapi.nat44_forwarding_enable_disable(1)
-        self.nat44_add_address(self.nat_addr, twice_nat=1)
-        self.nat44_add_static_mapping(local_host.ip4, self.nat_addr,
-                                      local_port, external_port,
-                                      proto=IP_PROTOS.tcp, out2in_only=1,
-                                      twice_nat=1)
-        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index,
-                                                  is_inside=0)
-
-        # from client to service
-        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
-             IP(src=remote_host.ip4, dst=self.nat_addr) /
-             TCP(sport=12345, dport=external_port))
-        self.pg9.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg9.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.dst, local_host.ip4)
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.dport, local_port)
-            self.assertNotEqual(tcp.sport, 12345)
-            eh_port_in = tcp.sport
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-        # from service back to client
-        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
-             IP(src=local_host.ip4, dst=self.nat_addr) /
-             TCP(sport=local_port, dport=eh_port_in))
-        self.pg9.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg9.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(ip.dst, remote_host.ip4)
-            self.assertEqual(tcp.sport, external_port)
-            self.assertEqual(tcp.dport, 12345)
-            self.assert_packet_checksums_valid(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-    def test_del_session(self):
-        """ Delete NAT44 session """
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-
-        pkts = self.create_stream_in(self.pg0, self.pg1)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg1.get_capture(len(pkts))
-
-        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
-        nsessions = len(sessions)
-
-        self.vapi.nat44_del_session(sessions[0].inside_ip_address,
-                                    sessions[0].inside_port,
-                                    sessions[0].protocol)
-        self.vapi.nat44_del_session(sessions[1].outside_ip_address,
-                                    sessions[1].outside_port,
-                                    sessions[1].protocol,
-                                    is_in=0)
-
-        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
-        self.assertEqual(nsessions - len(sessions), 2)
-
-    def test_set_get_reass(self):
-        """ NAT44 set/get virtual fragmentation reassembly """
-        reas_cfg1 = self.vapi.nat_get_reass()
-
-        self.vapi.nat_set_reass(timeout=reas_cfg1.ip4_timeout + 5,
-                                max_reass=reas_cfg1.ip4_max_reass * 2,
-                                max_frag=reas_cfg1.ip4_max_frag * 2)
-
-        reas_cfg2 = self.vapi.nat_get_reass()
-
-        self.assertEqual(reas_cfg1.ip4_timeout + 5, reas_cfg2.ip4_timeout)
-        self.assertEqual(reas_cfg1.ip4_max_reass * 2, reas_cfg2.ip4_max_reass)
-        self.assertEqual(reas_cfg1.ip4_max_frag * 2, reas_cfg2.ip4_max_frag)
+        self.assertEqual(reas_cfg1.ip4_timeout + 5, reas_cfg2.ip4_timeout)
+        self.assertEqual(reas_cfg1.ip4_max_reass * 2, reas_cfg2.ip4_max_reass)
+        self.assertEqual(reas_cfg1.ip4_max_frag * 2, reas_cfg2.ip4_max_frag)
 
         self.vapi.nat_set_reass(drop_frag=1)
         self.assertTrue(self.vapi.nat_get_reass().ip4_drop_frag)
@@ -3787,22 +3074,766 @@ class TestNAT44(MethodHolder):
         self.vapi.cli("nat addr-port-assignment-alg map-e psid 10 "
                       "psid-offset 6 psid-len 6")
 
-        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=4567, dport=22))
-        self.pg0.add_stream(p)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=4567, dport=22))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg1.remote_ip4)
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.dport, 22)
+            self.assertNotEqual(tcp.sport, 4567)
+            self.assertEqual((tcp.sport >> 6) & 63, 10)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_ipfix_max_frags(self):
+        """ IPFIX logging maximum fragments pending reassembly exceeded """
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat_set_reass(max_frag=0)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
+
+        data = "A" * 4 + "B" * 16 + "C" * 3
+        self.tcp_port_in = random.randint(1025, 65535)
+        pkts = self.create_stream_frag(self.pg0,
+                                       self.pg1.remote_ip4,
+                                       self.tcp_port_in,
+                                       20,
+                                       data)
+        self.pg0.add_stream(pkts[-1])
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.assert_nothing_captured()
+        sleep(1)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(9)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_fragments_ip4(data, 0,
+                                                    self.pg0.remote_ip4n)
+
+    def tearDown(self):
+        super(TestNAT44, self).tearDown()
+        if not self.vpp_dead:
+            self.logger.info(self.vapi.cli("show nat44 addresses"))
+            self.logger.info(self.vapi.cli("show nat44 interfaces"))
+            self.logger.info(self.vapi.cli("show nat44 static mappings"))
+            self.logger.info(self.vapi.cli("show nat44 interface address"))
+            self.logger.info(self.vapi.cli("show nat44 sessions detail"))
+            self.logger.info(self.vapi.cli("show nat virtual-reassembly"))
+            self.logger.info(self.vapi.cli("show nat44 hash tables detail"))
+            self.vapi.cli("nat addr-port-assignment-alg default")
+            self.clear_nat44()
+            self.vapi.cli("clear logging")
+
+
+class TestNAT44EndpointDependent(MethodHolder):
+    """ Endpoint-Dependent mapping and filtering test cases """
+
+    @classmethod
+    def setUpConstants(cls):
+        super(TestNAT44EndpointDependent, cls).setUpConstants()
+        cls.vpp_cmdline.extend(["nat", "{", "endpoint-dependent", "}"])
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNAT44EndpointDependent, cls).setUpClass()
+        cls.vapi.cli("set log class nat level debug")
+        try:
+            cls.tcp_port_in = 6303
+            cls.tcp_port_out = 6303
+            cls.udp_port_in = 6304
+            cls.udp_port_out = 6304
+            cls.icmp_id_in = 6305
+            cls.icmp_id_out = 6305
+            cls.nat_addr = '10.0.0.3'
+            cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr)
+            cls.ipfix_src_port = 4739
+            cls.ipfix_domain_id = 1
+            cls.tcp_external_port = 80
+
+            cls.create_pg_interfaces(range(5))
+            cls.interfaces = list(cls.pg_interfaces[0:3])
+
+            for i in cls.interfaces:
+                i.admin_up()
+                i.config_ip4()
+                i.resolve_arp()
+
+            cls.pg0.generate_remote_hosts(3)
+            cls.pg0.configure_ipv4_neighbors()
+
+            cls.pg3.admin_up()
+
+            cls.pg4.generate_remote_hosts(2)
+            cls.pg4.config_ip4()
+            ip_addr_n = socket.inet_pton(socket.AF_INET, "10.0.0.1")
+            cls.vapi.sw_interface_add_del_address(cls.pg4.sw_if_index,
+                                                  ip_addr_n,
+                                                  24)
+            cls.pg4.admin_up()
+            cls.pg4.resolve_arp()
+            cls.pg4._remote_hosts[1]._ip4 = cls.pg4._remote_hosts[0]._ip4
+            cls.pg4.resolve_arp()
+
+        except Exception:
+            super(TestNAT44EndpointDependent, cls).tearDownClass()
+            raise
+
+    def test_dynamic(self):
+        """ NAT44 dynamic translation test """
+
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # in2out
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+
+        # out2in
+        pkts = self.create_stream_out(self.pg1)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+    def test_forwarding(self):
+        """ NAT44 forwarding test """
+
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_forwarding_enable_disable(1)
+
+        real_ip = self.pg0.remote_ip4n
+        alias_ip = self.nat_addr_n
+        self.vapi.nat44_add_del_static_mapping(local_ip=real_ip,
+                                               external_ip=alias_ip)
+
+        try:
+            # in2out - static mapping match
+
+            pkts = self.create_stream_out(self.pg1)
+            self.pg1.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            capture = self.pg0.get_capture(len(pkts))
+            self.verify_capture_in(capture, self.pg0)
+
+            pkts = self.create_stream_in(self.pg0, self.pg1)
+            self.pg0.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            capture = self.pg1.get_capture(len(pkts))
+            self.verify_capture_out(capture, same_port=True)
+
+            # in2out - no static mapping match
+
+            host0 = self.pg0.remote_hosts[0]
+            self.pg0.remote_hosts[0] = self.pg0.remote_hosts[1]
+            try:
+                pkts = self.create_stream_out(self.pg1,
+                                              dst_ip=self.pg0.remote_ip4,
+                                              use_inside_ports=True)
+                self.pg1.add_stream(pkts)
+                self.pg_enable_capture(self.pg_interfaces)
+                self.pg_start()
+                capture = self.pg0.get_capture(len(pkts))
+                self.verify_capture_in(capture, self.pg0)
+
+                pkts = self.create_stream_in(self.pg0, self.pg1)
+                self.pg0.add_stream(pkts)
+                self.pg_enable_capture(self.pg_interfaces)
+                self.pg_start()
+                capture = self.pg1.get_capture(len(pkts))
+                self.verify_capture_out(capture, nat_ip=self.pg0.remote_ip4,
+                                        same_port=True)
+            finally:
+                self.pg0.remote_hosts[0] = host0
+
+            user = self.pg0.remote_hosts[1]
+            sessions = self.vapi.nat44_user_session_dump(user.ip4n, 0)
+            self.assertEqual(len(sessions), 3)
+            self.assertTrue(sessions[0].ext_host_valid)
+            self.vapi.nat44_del_session(
+                sessions[0].inside_ip_address,
+                sessions[0].inside_port,
+                sessions[0].protocol,
+                ext_host_address=sessions[0].ext_host_address,
+                ext_host_port=sessions[0].ext_host_port)
+            sessions = self.vapi.nat44_user_session_dump(user.ip4n, 0)
+            self.assertEqual(len(sessions), 2)
+
+        finally:
+            self.vapi.nat44_forwarding_enable_disable(0)
+            self.vapi.nat44_add_del_static_mapping(local_ip=real_ip,
+                                                   external_ip=alias_ip,
+                                                   is_add=0)
+
+    def test_static_lb(self):
+        """ NAT44 local service load balancing """
+        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+        external_port = 80
+        local_port = 8080
+        server1 = self.pg0.remote_hosts[0]
+        server2 = self.pg0.remote_hosts[1]
+
+        locals = [{'addr': server1.ip4n,
+                   'port': local_port,
+                   'probability': 70},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 30}]
+
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  local_num=len(locals),
+                                                  locals=locals)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
+            if ip.dst == server1.ip4:
+                server = server1
+            else:
+                server = server2
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+             IP(src=server.ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
+        self.assertEqual(len(sessions), 1)
+        self.assertTrue(sessions[0].ext_host_valid)
+        self.vapi.nat44_del_session(
+            sessions[0].inside_ip_address,
+            sessions[0].inside_port,
+            sessions[0].protocol,
+            ext_host_address=sessions[0].ext_host_address,
+            ext_host_port=sessions[0].ext_host_port)
+        sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
+        self.assertEqual(len(sessions), 0)
+
+    @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+    def test_static_lb_multi_clients(self):
+        """ NAT44 local service load balancing - multiple clients"""
+
+        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+        external_port = 80
+        local_port = 8080
+        server1 = self.pg0.remote_hosts[0]
+        server2 = self.pg0.remote_hosts[1]
+
+        locals = [{'addr': server1.ip4n,
+                   'port': local_port,
+                   'probability': 90},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 10}]
+
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  local_num=len(locals),
+                                                  locals=locals)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        server1_n = 0
+        server2_n = 0
+        clients = ip4_range(self.pg1.remote_ip4, 10, 50)
+        pkts = []
+        for client in clients:
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=client, dst=self.nat_addr) /
+                 TCP(sport=12345, dport=external_port))
+            pkts.append(p)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for p in capture:
+            if p[IP].dst == server1.ip4:
+                server1_n += 1
+            else:
+                server2_n += 1
+        self.assertTrue(server1_n > server2_n)
+
+    def test_static_lb_2(self):
+        """ NAT44 local service load balancing (asymmetrical rule) """
+        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+        external_port = 80
+        local_port = 8080
+        server1 = self.pg0.remote_hosts[0]
+        server2 = self.pg0.remote_hosts[1]
+
+        locals = [{'addr': server1.ip4n,
+                   'port': local_port,
+                   'probability': 70},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 30}]
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  out2in_only=1,
+                                                  local_num=len(locals),
+                                                  locals=locals)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
+            if ip.dst == server1.ip4:
+                server = server1
+            else:
+                server = server2
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+             IP(src=server.ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client to server (no translation)
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=server1.ip4) /
+             TCP(sport=12346, dport=local_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, server1.ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (no translation)
+        p = (Ether(src=server1.mac, dst=self.pg0.local_mac) /
+             IP(src=server1.ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12346))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, server1.ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_unknown_proto(self):
+        """ NAT44 translate packet with unknown protocol """
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # in2out
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=self.tcp_port_in, dport=20))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg1.get_capture(1)
+
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg1.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, self.nat_addr)
+            self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+        # out2in
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg0.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
+            self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+    def test_hairpinning_unknown_proto(self):
+        """ NAT44 translate packet with unknown protocol - hairpinning """
+        host = self.pg0.remote_hosts[0]
+        server = self.pg0.remote_hosts[1]
+        host_in_port = 1234
+        server_out_port = 8765
+        server_nat_ip = "10.0.0.11"
+
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # add static mapping for server
+        self.nat44_add_static_mapping(server.ip4, server_nat_ip)
+
+        # host to server
+        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+             IP(src=host.ip4, dst=server_nat_ip) /
+             TCP(sport=host_in_port, dport=server_out_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg0.get_capture(1)
+
+        p = (Ether(dst=self.pg0.local_mac, src=host.mac) /
+             IP(src=host.ip4, dst=server_nat_ip) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg0.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, self.nat_addr)
+            self.assertEqual(packet[IP].dst, server.ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+        # server to host
+        p = (Ether(dst=self.pg0.local_mac, src=server.mac) /
+             IP(src=server.ip4, dst=self.nat_addr) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg0.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, server_nat_ip)
+            self.assertEqual(packet[IP].dst, host.ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+    def test_output_feature_and_service(self):
+        """ NAT44 interface output feature and services """
+        external_addr = '1.2.3.4'
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_add_del_identity_mapping(ip=self.pg1.remote_ip4n)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, external_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=external_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, external_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from local network host to external network
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+
+        # from external network back to local network host
+        pkts = self.create_stream_out(self.pg1)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+    def test_output_feature_and_service2(self):
+        """ NAT44 interface output feature and service host direct access """
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+
+        # session initiaded from service host - translate
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+
+        pkts = self.create_stream_out(self.pg1)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+        # session initiaded from remote host - do not translate
+        self.tcp_port_in = 60303
+        self.udp_port_in = 60304
+        self.icmp_id_in = 60305
+        pkts = self.create_stream_out(self.pg1,
+                                      self.pg0.remote_ip4,
+                                      use_inside_ports=True)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture, nat_ip=self.pg0.remote_ip4,
+                                same_port=True)
+
+    def test_output_feature_and_service3(self):
+        """ NAT44 interface output feature and DST NAT """
+        external_addr = '1.2.3.4'
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        self.nat44_add_static_mapping(self.pg1.remote_ip4, external_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=external_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg0.remote_ip4)
+            self.assertEqual(tcp.sport, 12345)
+            self.assertEqual(ip.dst, self.pg1.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(1)
+        capture = self.pg0.get_capture(1)
         p = capture[0]
         try:
             ip = p[IP]
             tcp = p[TCP]
-            self.assertEqual(ip.dst, self.pg1.remote_ip4)
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.dport, 22)
-            self.assertNotEqual(tcp.sport, 4567)
-            self.assertEqual((tcp.sport >> 6) & 63, 10)
+            self.assertEqual(ip.src, external_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, 12345)
             self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
@@ -3979,71 +4010,24 @@ class TestNAT44(MethodHolder):
 
     def test_twice_nat_interface_addr(self):
         """ Acquire twice NAT44 addresses from interface """
-        self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index, twice_nat=1)
+        self.vapi.nat44_add_interface_addr(self.pg3.sw_if_index, twice_nat=1)
 
         # no address in NAT pool
         adresses = self.vapi.nat44_address_dump()
         self.assertEqual(0, len(adresses))
 
         # configure interface address and check NAT address pool
-        self.pg7.config_ip4()
+        self.pg3.config_ip4()
         adresses = self.vapi.nat44_address_dump()
         self.assertEqual(1, len(adresses))
-        self.assertEqual(adresses[0].ip_address[0:4], self.pg7.local_ip4n)
+        self.assertEqual(adresses[0].ip_address[0:4], self.pg3.local_ip4n)
         self.assertEqual(adresses[0].twice_nat, 1)
 
         # remove interface address and check NAT address pool
-        self.pg7.unconfig_ip4()
+        self.pg3.unconfig_ip4()
         adresses = self.vapi.nat44_address_dump()
         self.assertEqual(0, len(adresses))
 
-    def test_ipfix_max_frags(self):
-        """ IPFIX logging maximum fragments pending reassembly exceeded """
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
-                                                  is_inside=0)
-        self.vapi.nat_set_reass(max_frag=0)
-        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
-                                     src_address=self.pg3.local_ip4n,
-                                     path_mtu=512,
-                                     template_interval=10)
-        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
-                            src_port=self.ipfix_src_port)
-
-        data = "A" * 4 + "B" * 16 + "C" * 3
-        self.tcp_port_in = random.randint(1025, 65535)
-        pkts = self.create_stream_frag(self.pg0,
-                                       self.pg1.remote_ip4,
-                                       self.tcp_port_in,
-                                       20,
-                                       data)
-        self.pg0.add_stream(pkts[-1])
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg1.assert_nothing_captured()
-        sleep(1)
-        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
-        capture = self.pg3.get_capture(9)
-        ipfix = IPFIXDecoder()
-        # first load template
-        for p in capture:
-            self.assertTrue(p.haslayer(IPFIX))
-            self.assertEqual(p[IP].src, self.pg3.local_ip4)
-            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
-            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
-            self.assertEqual(p[UDP].dport, 4739)
-            self.assertEqual(p[IPFIX].observationDomainID,
-                             self.ipfix_domain_id)
-            if p.haslayer(Template):
-                ipfix.add_template(p.getlayer(Template))
-        # verify events in data set
-        for p in capture:
-            if p.haslayer(Data):
-                data = ipfix.decode_data_set(p.getlayer(Set))
-                self.verify_ipfix_max_fragments_ip4(data, 0,
-                                                    self.pg0.remote_ip4n)
-
     def test_tcp_session_close_in(self):
         """ Close TCP session from inside network """
         self.tcp_port_out = 10505
@@ -4225,17 +4209,202 @@ class TestNAT44(MethodHolder):
                                                      0)
         self.assertEqual(len(sessions) - start_sessnum, 0)
 
+    def test_one_armed_nat44_static(self):
+        """ One armed NAT44 and 1:1 NAPT asymmetrical rule """
+        remote_host = self.pg4.remote_hosts[0]
+        local_host = self.pg4.remote_hosts[1]
+        external_port = 80
+        local_port = 8080
+        eh_port_in = 0
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr, twice_nat=1)
+        self.nat44_add_static_mapping(local_host.ip4, self.nat_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1,
+                                      twice_nat=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg4.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg4.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac) /
+             IP(src=remote_host.ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg4.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg4.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, local_host.ip4)
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.dport, local_port)
+            self.assertNotEqual(tcp.sport, 12345)
+            eh_port_in = tcp.sport
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac) /
+             IP(src=local_host.ip4, dst=self.nat_addr) /
+             TCP(sport=local_port, dport=eh_port_in))
+        self.pg4.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg4.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(ip.dst, remote_host.ip4)
+            self.assertEqual(tcp.sport, external_port)
+            self.assertEqual(tcp.dport, 12345)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_static_with_port_out2(self):
+        """ 1:1 NAPT asymmetrical rule """
+
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # ICMP error
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             ICMP(type=11) / capture[0][IP])
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            self.assertEqual(p[IP].src, self.nat_addr)
+            inner = p[IPerror]
+            self.assertEqual(inner.dst, self.nat_addr)
+            self.assertEqual(inner[TCPerror].dport, external_port)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # ICMP error
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             ICMP(type=11) / capture[0][IP])
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            self.assertEqual(p[IP].dst, self.pg0.remote_ip4)
+            inner = p[IPerror]
+            self.assertEqual(inner.src, self.pg0.remote_ip4)
+            self.assertEqual(inner[TCPerror].sport, local_port)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client to server (no translation)
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=12346, dport=local_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (no translation)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12346))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg0.remote_ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
     def tearDown(self):
-        super(TestNAT44, self).tearDown()
+        super(TestNAT44EndpointDependent, self).tearDown()
         if not self.vpp_dead:
             self.logger.info(self.vapi.cli("show nat44 addresses"))
             self.logger.info(self.vapi.cli("show nat44 interfaces"))
             self.logger.info(self.vapi.cli("show nat44 static mappings"))
             self.logger.info(self.vapi.cli("show nat44 interface address"))
             self.logger.info(self.vapi.cli("show nat44 sessions detail"))
-            self.logger.info(self.vapi.cli("show nat virtual-reassembly"))
             self.logger.info(self.vapi.cli("show nat44 hash tables detail"))
-            self.vapi.cli("nat addr-port-assignment-alg default")
             self.clear_nat44()
             self.vapi.cli("clear logging")