From 53cee1579eb0bee09142a26285461638a796e99d Mon Sep 17 00:00:00 2001
From: Filip Varga <fivarga@cisco.com>
Date: Tue, 18 Jan 2022 13:17:07 -0800
Subject: nat: nat44-ei hairpinning code cleanup

Removing obsolete unused nat44-ei nodes and functions.

Type: refactor
Change-Id: I1e03e283091c3c0d92908d04037cba00a348351a
Signed-off-by: Filip Varga <fivarga@cisco.com>
---
 src/plugins/nat/CMakeLists.txt                  |    2 -
 src/plugins/nat/nat44-ei/nat44_ei.c             |   49 +-
 src/plugins/nat/nat44-ei/nat44_ei.h             |   19 +-
 src/plugins/nat/nat44-ei/nat44_ei_hairpinning.c |  759 ----------------
 src/plugins/nat/nat44-ei/nat44_ei_hairpinning.h |   92 --
 src/plugins/nat/nat44-ei/nat44_ei_in2out.c      | 1102 ++++++++++++++---------
 src/plugins/nat/nat44-ei/nat44_ei_out2in.c      |  197 ----
 7 files changed, 673 insertions(+), 1547 deletions(-)
 delete mode 100644 src/plugins/nat/nat44-ei/nat44_ei_hairpinning.c
 delete mode 100644 src/plugins/nat/nat44-ei/nat44_ei_hairpinning.h

(limited to 'src')

diff --git a/src/plugins/nat/CMakeLists.txt b/src/plugins/nat/CMakeLists.txt
index 54c15374291..c53e0e39c7c 100644
--- a/src/plugins/nat/CMakeLists.txt
+++ b/src/plugins/nat/CMakeLists.txt
@@ -62,12 +62,10 @@ add_vpp_plugin(nat44_ei
   nat44-ei/nat44_ei_in2out.c
   nat44-ei/nat44_ei_out2in.c
   nat44-ei/nat44_ei_handoff.c
-  nat44-ei/nat44_ei_hairpinning.c
 
   MULTIARCH_SOURCES
   nat44-ei/nat44_ei_in2out.c
   nat44-ei/nat44_ei_out2in.c
-  nat44-ei/nat44_ei_hairpinning.c
 
   API_FILES
   nat44-ei/nat44_ei.api
diff --git a/src/plugins/nat/nat44-ei/nat44_ei.c b/src/plugins/nat/nat44-ei/nat44_ei.c
index 7336a95408b..10b04ce6d71 100644
--- a/src/plugins/nat/nat44-ei/nat44_ei.c
+++ b/src/plugins/nat/nat44-ei/nat44_ei.c
@@ -40,7 +40,6 @@
 nat44_ei_main_t nat44_ei_main;
 
 extern vlib_node_registration_t nat44_ei_hairpinning_node;
-extern vlib_node_registration_t nat44_ei_hairpin_dst_node;
 extern vlib_node_registration_t
   nat44_ei_in2out_hairpinning_finish_ip4_lookup_node;
 extern vlib_node_registration_t
@@ -111,31 +110,6 @@ VNET_FEATURE_INIT (ip4_nat44_ei_in2out_output, static) = {
   .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa",
 			       "ip4-sv-reassembly-output-feature"),
 };
-VNET_FEATURE_INIT (ip4_nat44_ei_in2out_fast, static) = {
-  .arc_name = "ip4-unicast",
-  .node_name = "nat44-ei-in2out-fast",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
-			       "ip4-sv-reassembly-feature"),
-};
-VNET_FEATURE_INIT (ip4_nat44_ei_out2in_fast, static) = {
-  .arc_name = "ip4-unicast",
-  .node_name = "nat44-ei-out2in-fast",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
-			       "ip4-sv-reassembly-feature",
-			       "ip4-dhcp-client-detect"),
-};
-VNET_FEATURE_INIT (ip4_nat44_ei_hairpin_dst, static) = {
-  .arc_name = "ip4-unicast",
-  .node_name = "nat44-ei-hairpin-dst",
-  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa",
-			       "ip4-sv-reassembly-feature"),
-};
-VNET_FEATURE_INIT (ip4_nat44_ei_hairpin_src, static) = {
-  .arc_name = "ip4-output",
-  .node_name = "nat44-ei-hairpin-src",
-  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa",
-			       "ip4-sv-reassembly-output-feature"),
-};
 VNET_FEATURE_INIT (ip4_nat44_ei_hairpinning, static) = {
   .arc_name = "ip4-local",
   .node_name = "nat44-ei-hairpinning",
@@ -482,8 +456,6 @@ nat44_ei_init (vlib_main_t *vm)
 
   nm->hairpinning_fq_index =
     vlib_frame_queue_main_init (nat44_ei_hairpinning_node.index, 0);
-  nm->hairpin_dst_fq_index =
-    vlib_frame_queue_main_init (nat44_ei_hairpin_dst_node.index, 0);
   nm->in2out_hairpinning_finish_ip4_lookup_node_fq_index =
     vlib_frame_queue_main_init (
       nat44_ei_in2out_hairpinning_finish_ip4_lookup_node.index, 0);
@@ -546,8 +518,6 @@ nat44_ei_plugin_enable (nat44_ei_config_t c)
 		    nm->user_buckets);
   nat44_ei_set_alloc_default ();
 
-  // TODO: zero simple counter for all counters missing
-
   vlib_zero_simple_counter (&nm->total_users, 0);
   vlib_zero_simple_counter (&nm->total_sessions, 0);
   vlib_zero_simple_counter (&nm->user_limit_reached, 0);
@@ -605,6 +575,13 @@ nat44_ei_get_interface (nat44_ei_interface_t *interfaces, u32 sw_if_index)
   return 0;
 }
 
+static_always_inline int
+nat44_ei_hairpinning_enable (u32 sw_if_index, u8 is_enable)
+{
+  return vnet_feature_enable_disable ("ip4-local", "nat44-ei-hairpinning",
+				      sw_if_index, is_enable, 0, 0);
+}
+
 int
 nat44_ei_add_interface (u32 sw_if_index, u8 is_inside)
 {
@@ -671,8 +648,7 @@ nat44_ei_add_interface (u32 sw_if_index, u8 is_inside)
 	}
       if (!is_inside)
 	{
-	  rv = vnet_feature_enable_disable (
-	    "ip4-local", "nat44-ei-hairpinning", sw_if_index, 0, 0, 0);
+	  rv = nat44_ei_hairpinning_enable (sw_if_index, 0);
 	  if (rv)
 	    {
 	      return rv;
@@ -705,8 +681,7 @@ nat44_ei_add_interface (u32 sw_if_index, u8 is_inside)
 	}
       if (is_inside && !nm->out2in_dpo)
 	{
-	  rv = vnet_feature_enable_disable (
-	    "ip4-local", "nat44-ei-hairpinning", sw_if_index, 1, 0, 0);
+	  rv = nat44_ei_hairpinning_enable (sw_if_index, 1);
 	  if (rv)
 	    {
 	      return rv;
@@ -811,8 +786,7 @@ nat44_ei_del_interface (u32 sw_if_index, u8 is_inside)
 	}
       else
 	{
-	  rv = vnet_feature_enable_disable (
-	    "ip4-local", "nat44-ei-hairpinning", sw_if_index, 1, 0, 0);
+	  rv = nat44_ei_hairpinning_enable (sw_if_index, 1);
 	  if (rv)
 	    {
 	      return rv;
@@ -845,8 +819,7 @@ nat44_ei_del_interface (u32 sw_if_index, u8 is_inside)
 	}
       if (is_inside)
 	{
-	  rv = vnet_feature_enable_disable (
-	    "ip4-local", "nat44-ei-hairpinning", sw_if_index, 0, 0, 0);
+	  rv = nat44_ei_hairpinning_enable (sw_if_index, 0);
 	  if (rv)
 	    {
 	      return rv;
diff --git a/src/plugins/nat/nat44-ei/nat44_ei.h b/src/plugins/nat/nat44-ei/nat44_ei.h
index b8b339b8e89..f353f48c482 100644
--- a/src/plugins/nat/nat44-ei/nat44_ei.h
+++ b/src/plugins/nat/nat44-ei/nat44_ei.h
@@ -470,12 +470,14 @@ typedef struct nat44_ei_main_s
   /* nat44 plugin enabled */
   u8 enabled;
 
+  /* hairpinning registration counter */
+  u32 hairpin_reg;
+
   nat44_ei_config_t rconfig;
 
   u32 in2out_hairpinning_finish_ip4_lookup_node_fq_index;
   u32 in2out_hairpinning_finish_interface_output_node_fq_index;
   u32 hairpinning_fq_index;
-  u32 hairpin_dst_fq_index;
 
   vnet_main_t *vnet_main;
 } nat44_ei_main_t;
@@ -626,21 +628,6 @@ void nat44_ei_delete_session (nat44_ei_main_t *nm, nat44_ei_session_t *ses,
 int nat44_i2o_is_idle_session_cb (clib_bihash_kv_8_8_t *kv, void *arg);
 int nat44_o2i_is_idle_session_cb (clib_bihash_kv_8_8_t *kv, void *arg);
 
-int nat44_ei_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node,
-			  nat44_ei_main_t *nm, u32 thread_index,
-			  vlib_buffer_t *b0, ip4_header_t *ip0,
-			  udp_header_t *udp0, tcp_header_t *tcp0, u32 proto0,
-			  int do_trace, u32 *required_thread_index);
-
-void nat44_ei_hairpinning_sm_unknown_proto (nat44_ei_main_t *nm,
-					    vlib_buffer_t *b,
-					    ip4_header_t *ip);
-
-u32 nat44_ei_icmp_hairpinning (nat44_ei_main_t *nm, vlib_buffer_t *b0,
-			       u32 thread_index, ip4_header_t *ip0,
-			       icmp46_header_t *icmp0,
-			       u32 *required_thread_index);
-
 int nat44_ei_set_frame_queue_nelts (u32 frame_queue_nelts);
 
 always_inline bool
diff --git a/src/plugins/nat/nat44-ei/nat44_ei_hairpinning.c b/src/plugins/nat/nat44-ei/nat44_ei_hairpinning.c
deleted file mode 100644
index c3d3cfba386..00000000000
--- a/src/plugins/nat/nat44-ei/nat44_ei_hairpinning.c
+++ /dev/null
@@ -1,759 +0,0 @@
-/*
- * nat44_ei.c - nat44 endpoint dependent plugin
- * * Copyright (c) 2020 Cisco and/or its affiliates.  * Licensed under the
- * Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License
- * at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-
-#include <vlib/vlib.h>
-#include <vnet/vnet.h>
-#include <vnet/fib/ip4_fib.h>
-
-#include <nat/nat44-ei/nat44_ei.h>
-#include <nat/nat44-ei/nat44_ei_inlines.h>
-#include <nat/nat44-ei/nat44_ei_hairpinning.h>
-
-/* NAT buffer flags */
-#define NAT44_EI_FLAG_HAIRPINNING (1 << 0)
-
-typedef enum
-{
-  NAT44_EI_HAIRPIN_SRC_NEXT_DROP,
-  NAT44_EI_HAIRPIN_SRC_NEXT_SNAT_IN2OUT,
-  NAT44_EI_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH,
-  NAT44_EI_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT,
-  NAT44_EI_HAIRPIN_SRC_N_NEXT,
-} nat44_ei_hairpin_src_next_t;
-
-typedef enum
-{
-  NAT44_EI_HAIRPIN_NEXT_LOOKUP,
-  NAT44_EI_HAIRPIN_NEXT_DROP,
-  NAT44_EI_HAIRPIN_NEXT_HANDOFF,
-  NAT44_EI_HAIRPIN_N_NEXT,
-} nat44_ei_hairpin_next_t;
-
-typedef struct
-{
-  ip4_address_t addr;
-  u16 port;
-  u32 fib_index;
-  u32 session_index;
-} nat44_ei_hairpin_trace_t;
-
-static u8 *
-format_nat44_ei_hairpin_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_ei_hairpin_trace_t *t = va_arg (*args, nat44_ei_hairpin_trace_t *);
-
-  s = format (s, "new dst addr %U port %u fib-index %u", format_ip4_address,
-	      &t->addr, clib_net_to_host_u16 (t->port), t->fib_index);
-  if (~0 == t->session_index)
-    {
-      s = format (s, " is-static-mapping");
-    }
-  else
-    {
-      s = format (s, " session-index %u", t->session_index);
-    }
-
-  return s;
-}
-
-extern vnet_feature_arc_registration_t vnet_feat_arc_ip4_local;
-
-static_always_inline int
-nat44_ei_is_hairpinning (nat44_ei_main_t *nm, ip4_address_t *dst_addr)
-{
-  nat44_ei_address_t *ap;
-  clib_bihash_kv_8_8_t kv, value;
-
-  vec_foreach (ap, nm->addresses)
-    {
-      if (ap->addr.as_u32 == dst_addr->as_u32)
-	return 1;
-    }
-
-  init_nat_k (&kv, *dst_addr, 0, 0, 0);
-  if (!clib_bihash_search_8_8 (&nm->static_mapping_by_external, &kv, &value))
-    return 1;
-
-  return 0;
-}
-
-#ifndef CLIB_MARCH_VARIANT
-void
-nat44_ei_hairpinning_sm_unknown_proto (nat44_ei_main_t *nm, vlib_buffer_t *b,
-				       ip4_header_t *ip)
-{
-  clib_bihash_kv_8_8_t kv, value;
-  nat44_ei_static_mapping_t *m;
-  u32 old_addr, new_addr;
-  ip_csum_t sum;
-
-  init_nat_k (&kv, ip->dst_address, 0, 0, 0);
-  if (clib_bihash_search_8_8 (&nm->static_mapping_by_external, &kv, &value))
-    return;
-
-  m = pool_elt_at_index (nm->static_mappings, value.value);
-
-  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;
-}
-#endif
-
-#ifndef CLIB_MARCH_VARIANT
-int
-nat44_ei_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node,
-		      nat44_ei_main_t *nm, u32 thread_index, vlib_buffer_t *b0,
-		      ip4_header_t *ip0, udp_header_t *udp0,
-		      tcp_header_t *tcp0, u32 proto0, int do_trace,
-		      u32 *required_thread_index)
-{
-  nat44_ei_session_t *s0 = NULL;
-  clib_bihash_kv_8_8_t kv0, value0;
-  ip_csum_t sum0;
-  u32 new_dst_addr0 = 0, old_dst_addr0, si = ~0;
-  u16 new_dst_port0 = ~0, old_dst_port0;
-  int rv;
-  ip4_address_t sm0_addr;
-  u16 sm0_port;
-  u32 sm0_fib_index;
-  u32 old_sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
-
-  /* Check if destination is static mappings */
-  if (!nat44_ei_static_mapping_match (
-	ip0->dst_address, udp0->dst_port, nm->outside_fib_index, proto0,
-	&sm0_addr, &sm0_port, &sm0_fib_index, 1 /* by external */, 0, 0))
-    {
-      new_dst_addr0 = sm0_addr.as_u32;
-      new_dst_port0 = sm0_port;
-      vnet_buffer (b0)->sw_if_index[VLIB_TX] = sm0_fib_index;
-    }
-  /* or active session */
-  else
-    {
-      init_nat_k (&kv0, ip0->dst_address, udp0->dst_port,
-		  nm->outside_fib_index, proto0);
-      rv = clib_bihash_search_8_8 (&nm->out2in, &kv0, &value0);
-      if (rv)
-	{
-	  rv = 0;
-	  goto trace;
-	}
-
-      if (thread_index != nat_value_get_thread_index (&value0))
-	{
-	  *required_thread_index = nat_value_get_thread_index (&value0);
-	  return 0;
-	}
-
-      si = nat_value_get_session_index (&value0);
-      s0 = pool_elt_at_index (nm->per_thread_data[thread_index].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;
-    }
-
-  /* Check if anything has changed and if not, then return 0. This
-     helps avoid infinite loop, repeating the three nodes
-     nat44-hairpinning-->ip4-lookup-->ip4-local, in case nothing has
-     changed. */
-  old_dst_addr0 = ip0->dst_address.as_u32;
-  old_dst_port0 = tcp0->dst;
-  if (new_dst_addr0 == old_dst_addr0 && new_dst_port0 == old_dst_port0 &&
-      vnet_buffer (b0)->sw_if_index[VLIB_TX] == old_sw_if_index)
-    return 0;
-
-  /* Destination is behind the same NAT, use internal address and port */
-  if (new_dst_addr0)
-    {
-      old_dst_addr0 = ip0->dst_address.as_u32;
-      ip0->dst_address.as_u32 = new_dst_addr0;
-      sum0 = ip0->checksum;
-      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, ip4_header_t,
-			     dst_address);
-      ip0->checksum = ip_csum_fold (sum0);
-
-      old_dst_port0 = tcp0->dst;
-      if (PREDICT_TRUE (new_dst_port0 != old_dst_port0))
-	{
-	  if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
-	    {
-	      tcp0->dst = new_dst_port0;
-	      sum0 = tcp0->checksum;
-	      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0,
-				     ip4_header_t, dst_address);
-	      sum0 = ip_csum_update (sum0, old_dst_port0, new_dst_port0,
-				     ip4_header_t /* cheat */, length);
-	      tcp0->checksum = ip_csum_fold (sum0);
-	    }
-	  else
-	    {
-	      udp0->dst_port = new_dst_port0;
-	      udp0->checksum = 0;
-	    }
-	}
-      else
-	{
-	  if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
-	    {
-	      sum0 = tcp0->checksum;
-	      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0,
-				     ip4_header_t, dst_address);
-	      tcp0->checksum = ip_csum_fold (sum0);
-	    }
-	}
-      rv = 1;
-      goto trace;
-    }
-  rv = 0;
-trace:
-  if (do_trace && PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
-				 (b0->flags & VLIB_BUFFER_IS_TRACED)))
-    {
-      nat44_ei_hairpin_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
-      t->addr.as_u32 = new_dst_addr0;
-      t->port = new_dst_port0;
-      t->fib_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
-      if (s0)
-	{
-	  t->session_index = si;
-	}
-      else
-	{
-	  t->session_index = ~0;
-	}
-    }
-  return rv;
-}
-#endif
-
-#ifndef CLIB_MARCH_VARIANT
-u32
-nat44_ei_icmp_hairpinning (nat44_ei_main_t *nm, vlib_buffer_t *b0,
-			   u32 thread_index, ip4_header_t *ip0,
-			   icmp46_header_t *icmp0, u32 *required_thread_index)
-{
-  clib_bihash_kv_8_8_t kv0, value0;
-  u32 old_dst_addr0, new_dst_addr0;
-  u32 old_addr0, new_addr0;
-  u16 old_port0, new_port0;
-  u16 old_checksum0, new_checksum0;
-  u32 si, ti = 0;
-  ip_csum_t sum0;
-  nat44_ei_session_t *s0;
-  nat44_ei_static_mapping_t *m0;
-
-  if (icmp_type_is_error_message (
-	vnet_buffer (b0)->ip.reass.icmp_type_or_tcp_flags))
-    {
-      ip4_header_t *inner_ip0 = 0;
-      tcp_udp_header_t *l4_header = 0;
-
-      inner_ip0 = (ip4_header_t *) ((icmp_echo_header_t *) (icmp0 + 1) + 1);
-      l4_header = ip4_next_header (inner_ip0);
-      u32 protocol = ip_proto_to_nat_proto (inner_ip0->protocol);
-
-      if (protocol != NAT_PROTOCOL_TCP && protocol != NAT_PROTOCOL_UDP)
-	return 1;
-
-      init_nat_k (&kv0, ip0->dst_address, l4_header->src_port,
-		  nm->outside_fib_index, protocol);
-      if (clib_bihash_search_8_8 (&nm->out2in, &kv0, &value0))
-	return 1;
-      ti = nat_value_get_thread_index (&value0);
-      if (ti != thread_index)
-	{
-	  *required_thread_index = ti;
-	  return 1;
-	}
-      si = nat_value_get_session_index (&value0);
-      s0 = pool_elt_at_index (nm->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;
-
-      /* update inner source IP address */
-      old_addr0 = inner_ip0->src_address.as_u32;
-      inner_ip0->src_address.as_u32 = new_dst_addr0;
-      new_addr0 = inner_ip0->src_address.as_u32;
-      sum0 = icmp0->checksum;
-      sum0 =
-	ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, src_address);
-      icmp0->checksum = ip_csum_fold (sum0);
-
-      /* update inner IP header checksum */
-      old_checksum0 = inner_ip0->checksum;
-      sum0 = inner_ip0->checksum;
-      sum0 =
-	ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, src_address);
-      inner_ip0->checksum = ip_csum_fold (sum0);
-      new_checksum0 = inner_ip0->checksum;
-      sum0 = icmp0->checksum;
-      sum0 = ip_csum_update (sum0, old_checksum0, new_checksum0, ip4_header_t,
-			     checksum);
-      icmp0->checksum = ip_csum_fold (sum0);
-
-      /* update inner source port */
-      old_port0 = l4_header->src_port;
-      l4_header->src_port = s0->in2out.port;
-      new_port0 = l4_header->src_port;
-      sum0 = icmp0->checksum;
-      sum0 = ip_csum_update (sum0, old_port0, new_port0, tcp_udp_header_t,
-			     src_port);
-      icmp0->checksum = ip_csum_fold (sum0);
-    }
-  else
-    {
-      init_nat_k (&kv0, ip0->dst_address, 0, nm->outside_fib_index, 0);
-      if (clib_bihash_search_8_8 (&nm->static_mapping_by_external, &kv0,
-				  &value0))
-	{
-	  icmp_echo_header_t *echo0 = (icmp_echo_header_t *) (icmp0 + 1);
-	  u16 icmp_id0 = echo0->identifier;
-	  init_nat_k (&kv0, ip0->dst_address, icmp_id0, nm->outside_fib_index,
-		      NAT_PROTOCOL_ICMP);
-	  int rv = clib_bihash_search_8_8 (&nm->out2in, &kv0, &value0);
-	  if (!rv)
-	    {
-	      ti = nat_value_get_thread_index (&value0);
-	      if (ti != thread_index)
-		{
-		  *required_thread_index = ti;
-		  return 1;
-		}
-	      si = nat_value_get_session_index (&value0);
-	      s0 = pool_elt_at_index (nm->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;
-	      echo0->identifier = s0->in2out.port;
-	      sum0 = icmp0->checksum;
-	      sum0 = ip_csum_update (sum0, icmp_id0, s0->in2out.port,
-				     icmp_echo_header_t, identifier);
-	      icmp0->checksum = ip_csum_fold (sum0);
-	      goto change_addr;
-	    }
-
-	  return 1;
-	}
-
-      m0 = pool_elt_at_index (nm->static_mappings, value0.value);
-
-      new_dst_addr0 = m0->local_addr.as_u32;
-      if (vnet_buffer (b0)->sw_if_index[VLIB_TX] == ~0)
-	vnet_buffer (b0)->sw_if_index[VLIB_TX] = m0->fib_index;
-    }
-change_addr:
-  /* Destination is behind the same NAT, use internal address and port */
-  if (new_dst_addr0)
-    {
-      old_dst_addr0 = ip0->dst_address.as_u32;
-      ip0->dst_address.as_u32 = new_dst_addr0;
-      sum0 = ip0->checksum;
-      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, ip4_header_t,
-			     dst_address);
-      ip0->checksum = ip_csum_fold (sum0);
-    }
-  return 0;
-}
-#endif
-
-void nat44_ei_hairpinning_unknown_proto (nat44_ei_main_t *nm, vlib_buffer_t *b,
-					 ip4_header_t *ip);
-
-#ifndef CLIB_MARCH_VARIANT
-void
-nat44_ei_hairpinning_unknown_proto (nat44_ei_main_t *nm, vlib_buffer_t *b,
-				    ip4_header_t *ip)
-{
-  clib_bihash_kv_8_8_t kv, value;
-  nat44_ei_static_mapping_t *m;
-  u32 old_addr, new_addr;
-  ip_csum_t sum;
-
-  init_nat_k (&kv, ip->dst_address, 0, 0, 0);
-  if (clib_bihash_search_8_8 (&nm->static_mapping_by_external, &kv, &value))
-    return;
-
-  m = pool_elt_at_index (nm->static_mappings, value.value);
-
-  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;
-}
-#endif
-
-VLIB_NODE_FN (nat44_ei_hairpin_src_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  u32 n_left_from, *from, *to_next;
-  nat44_ei_hairpin_src_next_t next_index;
-  nat44_ei_main_t *nm = &nat44_ei_main;
-
-  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 > 0 && n_left_to_next > 0)
-	{
-	  u32 bi0;
-	  vlib_buffer_t *b0;
-	  u32 next0;
-	  nat44_ei_interface_t *i;
-	  u32 rx_sw_if_index0;
-	  u32 tx_sw_if_index0;
-
-	  /* 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);
-	  rx_sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
-	  tx_sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
-
-	  pool_foreach (i, nm->output_feature_interfaces)
-	    {
-	      /* Only packets from NAT inside interface */
-	      if ((nat44_ei_interface_is_inside (i)) &&
-		  (rx_sw_if_index0 == i->sw_if_index))
-		{
-		  if (PREDICT_FALSE ((vnet_buffer (b0)->snat.flags) &
-				     NAT44_EI_FLAG_HAIRPINNING))
-		    {
-		      if (PREDICT_TRUE (nm->num_workers > 1))
-			{
-			  next0 = NAT44_EI_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH;
-			  goto skip_feature_next;
-			}
-		      else
-			{
-			  next0 = NAT44_EI_HAIRPIN_SRC_NEXT_SNAT_IN2OUT;
-			  goto skip_feature_next;
-			}
-		    }
-		  break;
-		}
-	    }
-
-	  vnet_feature_next (&next0, b0);
-	skip_feature_next:
-
-	  if (next0 != NAT44_EI_HAIRPIN_SRC_NEXT_DROP)
-	    {
-	      vlib_increment_simple_counter (&nm->counters.hairpinning,
-					     vm->thread_index, tx_sw_if_index0,
-					     1);
-	    }
-
-	  /* 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);
-    }
-
-  return frame->n_vectors;
-}
-
-VLIB_NODE_FN (nat44_ei_hairpin_dst_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  u32 n_left_from, *from, *to_next;
-  u32 thread_index = vm->thread_index;
-  nat44_ei_hairpin_next_t next_index;
-  nat44_ei_main_t *nm = &nat44_ei_main;
-
-  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 > 0 && n_left_to_next > 0)
-	{
-	  u32 bi0;
-	  vlib_buffer_t *b0;
-	  u32 next0;
-	  ip4_header_t *ip0;
-	  u32 proto0;
-	  u32 sw_if_index0;
-	  u32 required_thread_index = thread_index;
-
-	  /* 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_EI_HAIRPIN_NEXT_LOOKUP;
-	  ip0 = vlib_buffer_get_current (b0);
-	  sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
-
-	  proto0 = ip_proto_to_nat_proto (ip0->protocol);
-
-	  vnet_buffer (b0)->snat.flags = 0;
-	  if (PREDICT_FALSE (nat44_ei_is_hairpinning (nm, &ip0->dst_address)))
-	    {
-	      if (proto0 == NAT_PROTOCOL_TCP || proto0 == NAT_PROTOCOL_UDP)
-		{
-		  udp_header_t *udp0 = ip4_next_header (ip0);
-		  tcp_header_t *tcp0 = (tcp_header_t *) udp0;
-
-		  nat44_ei_hairpinning (vm, node, nm, thread_index, b0, ip0,
-					udp0, tcp0, proto0, 1 /* do_trace */,
-					&required_thread_index);
-		}
-	      else if (proto0 == NAT_PROTOCOL_ICMP)
-		{
-		  icmp46_header_t *icmp0 = ip4_next_header (ip0);
-
-		  nat44_ei_icmp_hairpinning (nm, b0, thread_index, ip0, icmp0,
-					     &required_thread_index);
-		}
-	      else
-		{
-		  nat44_ei_hairpinning_unknown_proto (nm, b0, ip0);
-		}
-
-	      vnet_buffer (b0)->snat.flags = NAT44_EI_FLAG_HAIRPINNING;
-	    }
-
-	  if (thread_index != required_thread_index)
-	    {
-	      vnet_buffer (b0)->snat.required_thread_index =
-		required_thread_index;
-	      next0 = NAT44_EI_HAIRPIN_NEXT_HANDOFF;
-	    }
-
-	  if (next0 != NAT44_EI_HAIRPIN_NEXT_DROP)
-	    {
-	      vlib_increment_simple_counter (
-		&nm->counters.hairpinning, vm->thread_index, sw_if_index0, 1);
-	    }
-
-	  /* 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);
-    }
-
-  return frame->n_vectors;
-}
-
-VLIB_NODE_FN (nat44_ei_hairpinning_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  u32 n_left_from, *from, *to_next;
-  u32 thread_index = vm->thread_index;
-  nat44_ei_hairpin_next_t next_index;
-  nat44_ei_main_t *nm = &nat44_ei_main;
-  vnet_feature_main_t *fm = &feature_main;
-  u8 arc_index = vnet_feat_arc_ip4_local.feature_arc_index;
-  vnet_feature_config_main_t *cm = &fm->feature_config_mains[arc_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 > 0 && n_left_to_next > 0)
-	{
-	  u32 bi0;
-	  vlib_buffer_t *b0;
-	  u32 next0;
-	  ip4_header_t *ip0;
-	  u32 proto0;
-	  udp_header_t *udp0;
-	  tcp_header_t *tcp0;
-	  u32 sw_if_index0;
-	  u32 required_thread_index = thread_index;
-
-	  /* 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);
-	  ip0 = vlib_buffer_get_current (b0);
-	  udp0 = ip4_next_header (ip0);
-	  tcp0 = (tcp_header_t *) udp0;
-	  sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
-
-	  proto0 = ip_proto_to_nat_proto (ip0->protocol);
-	  int next0_resolved = 0;
-
-	  if (nat44_ei_hairpinning (vm, node, nm, thread_index, b0, ip0, udp0,
-				    tcp0, proto0, 1 /* do_trace */,
-				    &required_thread_index))
-	    {
-	      next0 = NAT44_EI_HAIRPIN_NEXT_LOOKUP;
-	      next0_resolved = 1;
-	    }
-
-	  if (thread_index != required_thread_index)
-	    {
-	      vnet_buffer (b0)->snat.required_thread_index =
-		required_thread_index;
-	      next0 = NAT44_EI_HAIRPIN_NEXT_HANDOFF;
-	      next0_resolved = 1;
-	    }
-
-	  if (!next0_resolved)
-	    vnet_get_config_data (&cm->config_main, &b0->current_config_index,
-				  &next0, 0);
-
-	  if (next0 != NAT44_EI_HAIRPIN_NEXT_DROP)
-	    {
-	      vlib_increment_simple_counter (
-		&nm->counters.hairpinning, vm->thread_index, sw_if_index0, 1);
-	    }
-
-	  /* 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);
-    }
-
-  return frame->n_vectors;
-}
-
-VLIB_NODE_FN (nat44_ei_hairpinning_dst_handoff_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_hairpinning_handoff_fn_inline (
-    vm, node, frame, nat44_ei_main.hairpin_dst_fq_index);
-}
-
-VLIB_NODE_FN (nat44_ei_hairpinning_handoff_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_hairpinning_handoff_fn_inline (
-    vm, node, frame, nat44_ei_main.hairpinning_fq_index);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_hairpinning_dst_handoff_node) = {
-  .name = "nat44-ei-hairpin-dst-handoff",
-  .vector_size = sizeof (u32),
-  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
-  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
-  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
-
-  .n_next_nodes = 1,
-
-  .next_nodes = {
-    [0] = "error-drop",
-  },
-};
-
-VLIB_REGISTER_NODE (nat44_ei_hairpinning_handoff_node) = {
-  .name = "nat44-ei-hairpinning-handoff",
-  .vector_size = sizeof (u32),
-  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
-  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
-  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
-
-  .n_next_nodes = 1,
-
-  .next_nodes = {
-    [0] = "error-drop",
-  },
-};
-
-VLIB_REGISTER_NODE (nat44_ei_hairpin_src_node) = {
-  .name = "nat44-ei-hairpin-src",
-  .vector_size = sizeof (u32),
-  .type = VLIB_NODE_TYPE_INTERNAL,
-  .n_next_nodes = NAT44_EI_HAIRPIN_SRC_N_NEXT,
-  .next_nodes = {
-     [NAT44_EI_HAIRPIN_SRC_NEXT_DROP] = "error-drop",
-     [NAT44_EI_HAIRPIN_SRC_NEXT_SNAT_IN2OUT] = "nat44-ei-in2out-output",
-     [NAT44_EI_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT] = "interface-output",
-     [NAT44_EI_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH] = "nat44-ei-in2out-output-worker-handoff",
-  },
-};
-
-VLIB_REGISTER_NODE (nat44_ei_hairpin_dst_node) = {
-  .name = "nat44-ei-hairpin-dst",
-  .vector_size = sizeof (u32),
-  .type = VLIB_NODE_TYPE_INTERNAL,
-  .format_trace = format_nat44_ei_hairpin_trace,
-  .n_next_nodes = NAT44_EI_HAIRPIN_N_NEXT,
-  .next_nodes = {
-    [NAT44_EI_HAIRPIN_NEXT_DROP] = "error-drop",
-    [NAT44_EI_HAIRPIN_NEXT_LOOKUP] = "ip4-lookup",
-    [NAT44_EI_HAIRPIN_NEXT_HANDOFF] = "nat44-ei-hairpin-dst-handoff",
-  },
-};
-
-VLIB_REGISTER_NODE (nat44_ei_hairpinning_node) = {
-  .name = "nat44-ei-hairpinning",
-  .vector_size = sizeof (u32),
-  .type = VLIB_NODE_TYPE_INTERNAL,
-  .format_trace = format_nat44_ei_hairpin_trace,
-  .n_next_nodes = NAT44_EI_HAIRPIN_N_NEXT,
-  .next_nodes = {
-    [NAT44_EI_HAIRPIN_NEXT_DROP] = "error-drop",
-    [NAT44_EI_HAIRPIN_NEXT_LOOKUP] = "ip4-lookup",
-    [NAT44_EI_HAIRPIN_NEXT_HANDOFF] = "nat44-ei-hairpinning-handoff",
-  },
-};
-
-/*
- * fd.io coding-style-patch-verification: ON
- *
- * Local Variables:
- * eval: (c-set-style "gnu")
- * End:
- */
diff --git a/src/plugins/nat/nat44-ei/nat44_ei_hairpinning.h b/src/plugins/nat/nat44-ei/nat44_ei_hairpinning.h
deleted file mode 100644
index 908e6b2cfc9..00000000000
--- a/src/plugins/nat/nat44-ei/nat44_ei_hairpinning.h
+++ /dev/null
@@ -1,92 +0,0 @@
-#ifndef __included_nat44_ei_hairpinning_h__
-#define __included_nat44_ei_hairpinning_h__
-
-#include <nat/nat44-ei/nat44_ei.h>
-
-#define foreach_nat44_ei_hairpinning_handoff_error                            \
-  _ (CONGESTION_DROP, "congestion drop")
-
-typedef enum
-{
-#define _(sym, str) NAT44_EI_HAIRPINNING_HANDOFF_ERROR_##sym,
-  foreach_nat44_ei_hairpinning_handoff_error
-#undef _
-    NAT44_EI_HAIRPINNING_HANDOFF_N_ERROR,
-} nat44_ei_hairpinning_handoff_error_t;
-
-static char *nat44_ei_hairpinning_handoff_error_strings[] = {
-#define _(sym, string) string,
-  foreach_nat44_ei_hairpinning_handoff_error
-#undef _
-};
-
-typedef struct
-{
-  u32 next_worker_index;
-} nat44_ei_hairpinning_handoff_trace_t;
-
-static u8 *
-format_nat44_ei_hairpinning_handoff_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_ei_hairpinning_handoff_trace_t *t =
-    va_arg (*args, nat44_ei_hairpinning_handoff_trace_t *);
-
-  s = format (s, "nat44-ei-hairpinning-handoff: next-worker %d",
-	      t->next_worker_index);
-
-  return s;
-}
-
-always_inline uword
-nat44_ei_hairpinning_handoff_fn_inline (vlib_main_t *vm,
-					vlib_node_runtime_t *node,
-					vlib_frame_t *frame, u32 fq_index)
-{
-  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
-  u32 n_enq, n_left_from, *from;
-  u16 thread_indices[VLIB_FRAME_SIZE], *ti;
-
-  from = vlib_frame_vector_args (frame);
-  n_left_from = frame->n_vectors;
-  vlib_get_buffers (vm, from, bufs, n_left_from);
-
-  b = bufs;
-  ti = thread_indices;
-
-  while (n_left_from > 0)
-    {
-      ti[0] = vnet_buffer (b[0])->snat.required_thread_index;
-
-      if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
-			 (b[0]->flags & VLIB_BUFFER_IS_TRACED)))
-	{
-	  nat44_ei_hairpinning_handoff_trace_t *t =
-	    vlib_add_trace (vm, node, b[0], sizeof (*t));
-	  t->next_worker_index = ti[0];
-	}
-
-      n_left_from -= 1;
-      ti += 1;
-      b += 1;
-    }
-  n_enq = vlib_buffer_enqueue_to_thread (vm, node, fq_index, from,
-					 thread_indices, frame->n_vectors, 1);
-
-  if (n_enq < frame->n_vectors)
-    vlib_node_increment_counter (
-      vm, node->node_index, NAT44_EI_HAIRPINNING_HANDOFF_ERROR_CONGESTION_DROP,
-      frame->n_vectors - n_enq);
-  return frame->n_vectors;
-}
-
-#endif // __included_nat44_ei_hairpinning_h__
-
-/*
- * fd.io coding-style-patch-verification: ON
- *
- * Local Variables:
- * eval: (c-set-style "gnu")
- * End:
- */
diff --git a/src/plugins/nat/nat44-ei/nat44_ei_in2out.c b/src/plugins/nat/nat44-ei/nat44_ei_in2out.c
index a2fc5910584..7506063855d 100644
--- a/src/plugins/nat/nat44-ei/nat44_ei_in2out.c
+++ b/src/plugins/nat/nat44-ei/nat44_ei_in2out.c
@@ -34,50 +34,8 @@
 #include <nat/lib/nat_inlines.h>
 #include <nat/nat44-ei/nat44_ei_inlines.h>
 #include <nat/nat44-ei/nat44_ei.h>
-#include <nat/nat44-ei/nat44_ei_hairpinning.h>
 
-typedef struct
-{
-  u32 sw_if_index;
-  u32 next_index;
-  u32 session_index;
-  u32 is_slow_path;
-  u32 is_hairpinning;
-} nat44_ei_in2out_trace_t;
-
-/* packet trace format function */
-static u8 *
-format_nat44_ei_in2out_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_ei_in2out_trace_t *t = va_arg (*args, nat44_ei_in2out_trace_t *);
-  char *tag;
-
-  tag = t->is_slow_path ? "NAT44_IN2OUT_SLOW_PATH" : "NAT44_IN2OUT_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);
-  if (t->is_hairpinning)
-    {
-      s = format (s, ", with-hairpinning");
-    }
-
-  return s;
-}
-
-static u8 *
-format_nat44_ei_in2out_fast_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_ei_in2out_trace_t *t = va_arg (*args, nat44_ei_in2out_trace_t *);
-
-  s = format (s, "NAT44_IN2OUT_FAST: sw_if_index %d, next index %d",
-	      t->sw_if_index, t->next_index);
-
-  return s;
-}
+extern vnet_feature_arc_registration_t vnet_feat_arc_ip4_local;
 
 #define foreach_nat44_ei_in2out_error                                         \
   _ (UNSUPPORTED_PROTOCOL, "unsupported protocol")                            \
@@ -88,6 +46,9 @@ format_nat44_ei_in2out_fast_trace (u8 *s, va_list *args)
   _ (MAX_SESSIONS_EXCEEDED, "maximum sessions exceeded")                      \
   _ (CANNOT_CREATE_USER, "cannot create NAT user")
 
+#define foreach_nat44_ei_hairpinning_handoff_error                            \
+  _ (CONGESTION_DROP, "congestion drop")
+
 typedef enum
 {
 #define _(sym, str) NAT44_EI_IN2OUT_ERROR_##sym,
@@ -102,6 +63,20 @@ static char *nat44_ei_in2out_error_strings[] = {
 #undef _
 };
 
+typedef enum
+{
+#define _(sym, str) NAT44_EI_HAIRPINNING_HANDOFF_ERROR_##sym,
+  foreach_nat44_ei_hairpinning_handoff_error
+#undef _
+    NAT44_EI_HAIRPINNING_HANDOFF_N_ERROR,
+} nat44_ei_hairpinning_handoff_error_t;
+
+static char *nat44_ei_hairpinning_handoff_error_strings[] = {
+#define _(sym, string) string,
+  foreach_nat44_ei_hairpinning_handoff_error
+#undef _
+};
+
 typedef enum
 {
   NAT44_EI_IN2OUT_NEXT_LOOKUP,
@@ -119,7 +94,98 @@ typedef enum
   NAT44_EI_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
 } nat44_ei_in2out_hairpinnig_finish_next_t;
 
-static inline int
+typedef enum
+{
+  NAT44_EI_HAIRPIN_NEXT_LOOKUP,
+  NAT44_EI_HAIRPIN_NEXT_DROP,
+  NAT44_EI_HAIRPIN_NEXT_HANDOFF,
+  NAT44_EI_HAIRPIN_N_NEXT,
+} nat44_ei_hairpin_next_t;
+
+typedef struct
+{
+  u32 sw_if_index;
+  u32 next_index;
+  u32 session_index;
+  u32 is_slow_path;
+  u32 is_hairpinning;
+} nat44_ei_in2out_trace_t;
+
+typedef struct
+{
+  ip4_address_t addr;
+  u16 port;
+  u32 fib_index;
+  u32 session_index;
+} nat44_ei_hairpin_trace_t;
+
+typedef struct
+{
+  u32 next_worker_index;
+} nat44_ei_hairpinning_handoff_trace_t;
+
+static u8 *
+format_nat44_ei_in2out_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_ei_in2out_trace_t *t = va_arg (*args, nat44_ei_in2out_trace_t *);
+  char *tag;
+  tag = t->is_slow_path ? "NAT44_IN2OUT_SLOW_PATH" : "NAT44_IN2OUT_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);
+  if (t->is_hairpinning)
+    s = format (s, ", with-hairpinning");
+  return s;
+}
+
+static u8 *
+format_nat44_ei_in2out_fast_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_ei_in2out_trace_t *t = va_arg (*args, nat44_ei_in2out_trace_t *);
+  s = format (s, "NAT44_IN2OUT_FAST: sw_if_index %d, next index %d",
+	      t->sw_if_index, t->next_index);
+  return s;
+}
+
+static u8 *
+format_nat44_ei_hairpin_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_ei_hairpin_trace_t *t = va_arg (*args, nat44_ei_hairpin_trace_t *);
+
+  s = format (s, "new dst addr %U port %u fib-index %u", format_ip4_address,
+	      &t->addr, clib_net_to_host_u16 (t->port), t->fib_index);
+  if (~0 == t->session_index)
+    {
+      s = format (s, " is-static-mapping");
+    }
+  else
+    {
+      s = format (s, " session-index %u", t->session_index);
+    }
+
+  return s;
+}
+
+static u8 *
+format_nat44_ei_hairpinning_handoff_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_ei_hairpinning_handoff_trace_t *t =
+    va_arg (*args, nat44_ei_hairpinning_handoff_trace_t *);
+
+  s = format (s, "nat44-ei-hairpinning-handoff: next-worker %d",
+	      t->next_worker_index);
+
+  return s;
+}
+
+static_always_inline int
 nat44_ei_not_translate_fast (vlib_node_runtime_t *node, u32 sw_if_index0,
 			     ip4_header_t *ip0, u32 proto0, u32 rx_fib_index0)
 {
@@ -177,7 +243,7 @@ nat44_ei_not_translate_fast (vlib_node_runtime_t *node, u32 sw_if_index0,
   return 1;
 }
 
-static inline int
+static_always_inline int
 nat44_ei_not_translate (nat44_ei_main_t *nm, vlib_node_runtime_t *node,
 			u32 sw_if_index0, ip4_header_t *ip0, u32 proto0,
 			u32 rx_fib_index0, u32 thread_index)
@@ -212,7 +278,7 @@ nat44_ei_not_translate (nat44_ei_main_t *nm, vlib_node_runtime_t *node,
 				      rx_fib_index0);
 }
 
-static inline int
+static_always_inline int
 nat44_ei_not_translate_output_feature (nat44_ei_main_t *nm, ip4_header_t *ip0,
 				       u32 proto0, u16 src_port, u16 dst_port,
 				       u32 thread_index, u32 sw_if_index)
@@ -443,7 +509,6 @@ slow_path (nat44_ei_main_t *nm, vlib_buffer_t *b0, ip4_header_t *ip0,
   return next0;
 }
 
-#ifndef CLIB_MARCH_VARIANT
 static_always_inline nat44_ei_in2out_error_t
 icmp_get_key (vlib_buffer_t *b, ip4_header_t *ip0, ip4_address_t *addr,
 	      u16 *port, nat_protocol_t *nat_proto)
@@ -488,22 +553,7 @@ icmp_get_key (vlib_buffer_t *b, ip4_header_t *ip0, ip4_address_t *addr,
   return -1;			/* success */
 }
 
-/**
- * Get address and port values to be used for ICMP packet translation
- * and create session if needed
- *
- * @param[in,out] nm             NAT main
- * @param[in,out] node           NAT node runtime
- * @param[in] thread_index       thread index
- * @param[in,out] b0             buffer containing packet to be translated
- * @param[in,out] ip0            ip header
- * @param[out] p_proto           protocol used for matching
- * @param[out] p_value           address and port after NAT translation
- * @param[out] p_dont_translate  if packet should not be translated
- * @param d                      optional parameter
- * @param e                      optional parameter
- */
-u32
+static_always_inline u32
 nat44_ei_icmp_match_in2out_slow (vlib_node_runtime_t *node, u32 thread_index,
 				 vlib_buffer_t *b0, ip4_header_t *ip0,
 				 ip4_address_t *addr, u16 *port,
@@ -605,10 +655,8 @@ out:
     *p_s0 = s0;
   return next0;
 }
-#endif
 
-#ifndef CLIB_MARCH_VARIANT
-u32
+static_always_inline u32
 nat44_ei_icmp_match_in2out_fast (vlib_node_runtime_t *node, u32 thread_index,
 				 vlib_buffer_t *b0, ip4_header_t *ip0,
 				 ip4_address_t *addr, u16 *port,
@@ -674,16 +722,135 @@ nat44_ei_icmp_match_in2out_fast (vlib_node_runtime_t *node, u32 thread_index,
 out:
   return next0;
 }
-#endif
 
-u32 nat44_ei_icmp_in2out (vlib_buffer_t *b0, ip4_header_t *ip0,
-			  icmp46_header_t *icmp0, u32 sw_if_index0,
-			  u32 rx_fib_index0, vlib_node_runtime_t *node,
-			  u32 next0, u32 thread_index,
-			  nat44_ei_session_t **p_s0);
+static_always_inline u32
+nat44_ei_icmp_hairpinning (nat44_ei_main_t *nm, vlib_buffer_t *b0,
+			   u32 thread_index, ip4_header_t *ip0,
+			   icmp46_header_t *icmp0, u32 *required_thread_index)
+{
+  clib_bihash_kv_8_8_t kv0, value0;
+  u32 old_dst_addr0, new_dst_addr0;
+  u32 old_addr0, new_addr0;
+  u16 old_port0, new_port0;
+  u16 old_checksum0, new_checksum0;
+  u32 si, ti = 0;
+  ip_csum_t sum0;
+  nat44_ei_session_t *s0;
+  nat44_ei_static_mapping_t *m0;
 
-#ifndef CLIB_MARCH_VARIANT
-u32
+  if (icmp_type_is_error_message (
+	vnet_buffer (b0)->ip.reass.icmp_type_or_tcp_flags))
+    {
+      ip4_header_t *inner_ip0 = 0;
+      tcp_udp_header_t *l4_header = 0;
+
+      inner_ip0 = (ip4_header_t *) ((icmp_echo_header_t *) (icmp0 + 1) + 1);
+      l4_header = ip4_next_header (inner_ip0);
+      u32 protocol = ip_proto_to_nat_proto (inner_ip0->protocol);
+
+      if (protocol != NAT_PROTOCOL_TCP && protocol != NAT_PROTOCOL_UDP)
+	return 1;
+
+      init_nat_k (&kv0, ip0->dst_address, l4_header->src_port,
+		  nm->outside_fib_index, protocol);
+      if (clib_bihash_search_8_8 (&nm->out2in, &kv0, &value0))
+	return 1;
+      ti = nat_value_get_thread_index (&value0);
+      if (ti != thread_index)
+	{
+	  *required_thread_index = ti;
+	  return 1;
+	}
+      si = nat_value_get_session_index (&value0);
+      s0 = pool_elt_at_index (nm->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;
+
+      /* update inner source IP address */
+      old_addr0 = inner_ip0->src_address.as_u32;
+      inner_ip0->src_address.as_u32 = new_dst_addr0;
+      new_addr0 = inner_ip0->src_address.as_u32;
+      sum0 = icmp0->checksum;
+      sum0 =
+	ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, src_address);
+      icmp0->checksum = ip_csum_fold (sum0);
+
+      /* update inner IP header checksum */
+      old_checksum0 = inner_ip0->checksum;
+      sum0 = inner_ip0->checksum;
+      sum0 =
+	ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, src_address);
+      inner_ip0->checksum = ip_csum_fold (sum0);
+      new_checksum0 = inner_ip0->checksum;
+      sum0 = icmp0->checksum;
+      sum0 = ip_csum_update (sum0, old_checksum0, new_checksum0, ip4_header_t,
+			     checksum);
+      icmp0->checksum = ip_csum_fold (sum0);
+
+      /* update inner source port */
+      old_port0 = l4_header->src_port;
+      l4_header->src_port = s0->in2out.port;
+      new_port0 = l4_header->src_port;
+      sum0 = icmp0->checksum;
+      sum0 = ip_csum_update (sum0, old_port0, new_port0, tcp_udp_header_t,
+			     src_port);
+      icmp0->checksum = ip_csum_fold (sum0);
+    }
+  else
+    {
+      init_nat_k (&kv0, ip0->dst_address, 0, nm->outside_fib_index, 0);
+      if (clib_bihash_search_8_8 (&nm->static_mapping_by_external, &kv0,
+				  &value0))
+	{
+	  icmp_echo_header_t *echo0 = (icmp_echo_header_t *) (icmp0 + 1);
+	  u16 icmp_id0 = echo0->identifier;
+	  init_nat_k (&kv0, ip0->dst_address, icmp_id0, nm->outside_fib_index,
+		      NAT_PROTOCOL_ICMP);
+	  int rv = clib_bihash_search_8_8 (&nm->out2in, &kv0, &value0);
+	  if (!rv)
+	    {
+	      ti = nat_value_get_thread_index (&value0);
+	      if (ti != thread_index)
+		{
+		  *required_thread_index = ti;
+		  return 1;
+		}
+	      si = nat_value_get_session_index (&value0);
+	      s0 = pool_elt_at_index (nm->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;
+	      echo0->identifier = s0->in2out.port;
+	      sum0 = icmp0->checksum;
+	      sum0 = ip_csum_update (sum0, icmp_id0, s0->in2out.port,
+				     icmp_echo_header_t, identifier);
+	      icmp0->checksum = ip_csum_fold (sum0);
+	      goto change_addr;
+	    }
+
+	  return 1;
+	}
+
+      m0 = pool_elt_at_index (nm->static_mappings, value0.value);
+
+      new_dst_addr0 = m0->local_addr.as_u32;
+      if (vnet_buffer (b0)->sw_if_index[VLIB_TX] == ~0)
+	vnet_buffer (b0)->sw_if_index[VLIB_TX] = m0->fib_index;
+    }
+change_addr:
+  /* Destination is behind the same NAT, use internal address and port */
+  if (new_dst_addr0)
+    {
+      old_dst_addr0 = ip0->dst_address.as_u32;
+      ip0->dst_address.as_u32 = new_dst_addr0;
+      sum0 = ip0->checksum;
+      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, ip4_header_t,
+			     dst_address);
+      ip0->checksum = ip_csum_fold (sum0);
+    }
+  return 0;
+}
+
+static_always_inline u32
 nat44_ei_icmp_in2out (vlib_buffer_t *b0, ip4_header_t *ip0,
 		      icmp46_header_t *icmp0, u32 sw_if_index0,
 		      u32 rx_fib_index0, vlib_node_runtime_t *node, u32 next0,
@@ -854,7 +1021,6 @@ nat44_ei_icmp_in2out (vlib_buffer_t *b0, ip4_header_t *ip0,
 out:
   return next0;
 }
-#endif
 
 static_always_inline u32
 nat44_ei_icmp_in2out_slow_path (nat44_ei_main_t *nm, vlib_buffer_t *b0,
@@ -879,6 +1045,31 @@ nat44_ei_icmp_in2out_slow_path (nat44_ei_main_t *nm, vlib_buffer_t *b0,
   return next0;
 }
 
+static_always_inline void
+nat44_ei_hairpinning_sm_unknown_proto (nat44_ei_main_t *nm, vlib_buffer_t *b,
+				       ip4_header_t *ip)
+{
+  clib_bihash_kv_8_8_t kv, value;
+  nat44_ei_static_mapping_t *m;
+  u32 old_addr, new_addr;
+  ip_csum_t sum;
+
+  init_nat_k (&kv, ip->dst_address, 0, 0, 0);
+  if (clib_bihash_search_8_8 (&nm->static_mapping_by_external, &kv, &value))
+    return;
+
+  m = pool_elt_at_index (nm->static_mappings, value.value);
+
+  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 int
 nat_in2out_sm_unknown_proto (nat44_ei_main_t *nm, vlib_buffer_t *b,
 			     ip4_header_t *ip, u32 rx_fib_index)
@@ -911,7 +1102,174 @@ nat_in2out_sm_unknown_proto (nat44_ei_main_t *nm, vlib_buffer_t *b,
   return 0;
 }
 
-static inline uword
+static_always_inline int
+nat44_ei_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node,
+		      nat44_ei_main_t *nm, u32 thread_index, vlib_buffer_t *b0,
+		      ip4_header_t *ip0, udp_header_t *udp0,
+		      tcp_header_t *tcp0, u32 proto0, int do_trace,
+		      u32 *required_thread_index)
+{
+  nat44_ei_session_t *s0 = NULL;
+  clib_bihash_kv_8_8_t kv0, value0;
+  ip_csum_t sum0;
+  u32 new_dst_addr0 = 0, old_dst_addr0, si = ~0;
+  u16 new_dst_port0 = ~0, old_dst_port0;
+  int rv;
+  ip4_address_t sm0_addr;
+  u16 sm0_port;
+  u32 sm0_fib_index;
+  u32 old_sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
+
+  /* Check if destination is static mappings */
+  if (!nat44_ei_static_mapping_match (
+	ip0->dst_address, udp0->dst_port, nm->outside_fib_index, proto0,
+	&sm0_addr, &sm0_port, &sm0_fib_index, 1 /* by external */, 0, 0))
+    {
+      new_dst_addr0 = sm0_addr.as_u32;
+      new_dst_port0 = sm0_port;
+      vnet_buffer (b0)->sw_if_index[VLIB_TX] = sm0_fib_index;
+    }
+  /* or active session */
+  else
+    {
+      init_nat_k (&kv0, ip0->dst_address, udp0->dst_port,
+		  nm->outside_fib_index, proto0);
+      rv = clib_bihash_search_8_8 (&nm->out2in, &kv0, &value0);
+      if (rv)
+	{
+	  rv = 0;
+	  goto trace;
+	}
+
+      if (thread_index != nat_value_get_thread_index (&value0))
+	{
+	  *required_thread_index = nat_value_get_thread_index (&value0);
+	  return 0;
+	}
+
+      si = nat_value_get_session_index (&value0);
+      s0 = pool_elt_at_index (nm->per_thread_data[thread_index].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;
+    }
+
+  /* Check if anything has changed and if not, then return 0. This
+     helps avoid infinite loop, repeating the three nodes
+     nat44-hairpinning-->ip4-lookup-->ip4-local, in case nothing has
+     changed. */
+  old_dst_addr0 = ip0->dst_address.as_u32;
+  old_dst_port0 = tcp0->dst;
+  if (new_dst_addr0 == old_dst_addr0 && new_dst_port0 == old_dst_port0 &&
+      vnet_buffer (b0)->sw_if_index[VLIB_TX] == old_sw_if_index)
+    return 0;
+
+  /* Destination is behind the same NAT, use internal address and port */
+  if (new_dst_addr0)
+    {
+      old_dst_addr0 = ip0->dst_address.as_u32;
+      ip0->dst_address.as_u32 = new_dst_addr0;
+      sum0 = ip0->checksum;
+      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, ip4_header_t,
+			     dst_address);
+      ip0->checksum = ip_csum_fold (sum0);
+
+      old_dst_port0 = tcp0->dst;
+      if (PREDICT_TRUE (new_dst_port0 != old_dst_port0))
+	{
+	  if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
+	    {
+	      tcp0->dst = new_dst_port0;
+	      sum0 = tcp0->checksum;
+	      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0,
+				     ip4_header_t, dst_address);
+	      sum0 = ip_csum_update (sum0, old_dst_port0, new_dst_port0,
+				     ip4_header_t /* cheat */, length);
+	      tcp0->checksum = ip_csum_fold (sum0);
+	    }
+	  else
+	    {
+	      udp0->dst_port = new_dst_port0;
+	      udp0->checksum = 0;
+	    }
+	}
+      else
+	{
+	  if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
+	    {
+	      sum0 = tcp0->checksum;
+	      sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0,
+				     ip4_header_t, dst_address);
+	      tcp0->checksum = ip_csum_fold (sum0);
+	    }
+	}
+      rv = 1;
+      goto trace;
+    }
+  rv = 0;
+trace:
+  if (do_trace && PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
+				 (b0->flags & VLIB_BUFFER_IS_TRACED)))
+    {
+      nat44_ei_hairpin_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
+      t->addr.as_u32 = new_dst_addr0;
+      t->port = new_dst_port0;
+      t->fib_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
+      if (s0)
+	{
+	  t->session_index = si;
+	}
+      else
+	{
+	  t->session_index = ~0;
+	}
+    }
+  return rv;
+}
+
+static_always_inline uword
+nat44_ei_hairpinning_handoff_fn_inline (vlib_main_t *vm,
+					vlib_node_runtime_t *node,
+					vlib_frame_t *frame, u32 fq_index)
+{
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  u32 n_enq, n_left_from, *from;
+  u16 thread_indices[VLIB_FRAME_SIZE], *ti;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  vlib_get_buffers (vm, from, bufs, n_left_from);
+
+  b = bufs;
+  ti = thread_indices;
+
+  while (n_left_from > 0)
+    {
+      ti[0] = vnet_buffer (b[0])->snat.required_thread_index;
+
+      if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
+			 (b[0]->flags & VLIB_BUFFER_IS_TRACED)))
+	{
+	  nat44_ei_hairpinning_handoff_trace_t *t =
+	    vlib_add_trace (vm, node, b[0], sizeof (*t));
+	  t->next_worker_index = ti[0];
+	}
+
+      n_left_from -= 1;
+      ti += 1;
+      b += 1;
+    }
+  n_enq = vlib_buffer_enqueue_to_thread (vm, node, fq_index, from,
+					 thread_indices, frame->n_vectors, 1);
+
+  if (n_enq < frame->n_vectors)
+    vlib_node_increment_counter (
+      vm, node->node_index, NAT44_EI_HAIRPINNING_HANDOFF_ERROR_CONGESTION_DROP,
+      frame->n_vectors - n_enq);
+  return frame->n_vectors;
+}
+
+static_always_inline uword
 nat44_ei_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)
@@ -1667,128 +2025,10 @@ nat44_ei_in2out_node_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
   return frame->n_vectors;
 }
 
-VLIB_NODE_FN (nat44_ei_in2out_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 0 /* is_slow_path */,
-					 0);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_in2out_node) = {
-  .name = "nat44-ei-in2out",
-  .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_in2out_trace,
-  .type = VLIB_NODE_TYPE_INTERNAL,
-
-  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
-  .error_strings = nat44_ei_in2out_error_strings,
-
-  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
-  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
-
-  /* edit / add dispositions here */
-  .next_nodes = {
-    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
-    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
-    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-slowpath",
-    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
-    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
-  },
-};
-
-VLIB_NODE_FN (nat44_ei_in2out_output_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 0 /* is_slow_path */,
-					 1);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_in2out_output_node) = {
-  .name = "nat44-ei-in2out-output",
-  .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_in2out_trace,
-  .type = VLIB_NODE_TYPE_INTERNAL,
-
-  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
-  .error_strings = nat44_ei_in2out_error_strings,
-
-  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
-  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
-
-  /* edit / add dispositions here */
-  .next_nodes = {
-    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
-    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "interface-output",
-    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-output-slowpath",
-    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
-    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-interface-output",
-  },
-};
-
-VLIB_NODE_FN (nat44_ei_in2out_slowpath_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 1 /* is_slow_path */,
-					 0);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_in2out_slowpath_node) = {
-  .name = "nat44-ei-in2out-slowpath",
-  .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_in2out_trace,
-  .type = VLIB_NODE_TYPE_INTERNAL,
-
-  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
-  .error_strings = nat44_ei_in2out_error_strings,
-
-  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
-  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
-
-  /* edit / add dispositions here */
-  .next_nodes = {
-    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
-    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
-    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-slowpath",
-    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
-    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
-  },
-};
-
-VLIB_NODE_FN (nat44_ei_in2out_output_slowpath_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 1 /* is_slow_path */,
-					 1);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_in2out_output_slowpath_node) = {
-  .name = "nat44-ei-in2out-output-slowpath",
-  .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_in2out_trace,
-  .type = VLIB_NODE_TYPE_INTERNAL,
-
-  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
-  .error_strings = nat44_ei_in2out_error_strings,
-
-  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
-  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
-
-  /* edit / add dispositions here */
-  .next_nodes = {
-    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
-    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "interface-output",
-    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-output-slowpath",
-    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
-    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-interface-output",
-  },
-};
-
-VLIB_NODE_FN (nat44_ei_in2out_fast_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+static_always_inline uword
+nat44_ei_in2out_hairpinning_finish_inline (vlib_main_t *vm,
+					   vlib_node_runtime_t *node,
+					   vlib_frame_t *frame)
 {
   u32 n_left_from, *from, *to_next;
   u32 thread_index = vm->thread_index;
@@ -1813,20 +2053,12 @@ VLIB_NODE_FN (nat44_ei_in2out_fast_node)
 	  u32 next0;
 	  u32 sw_if_index0;
 	  ip4_header_t *ip0;
-	  ip_csum_t sum0;
-	  u32 new_addr0, old_addr0;
-	  u16 old_port0, new_port0;
 	  udp_header_t *udp0;
 	  tcp_header_t *tcp0;
 	  icmp46_header_t *icmp0;
 	  u32 proto0;
-	  u32 rx_fib_index0;
-	  ip4_address_t sm0_addr;
-	  u16 sm0_port;
-	  u32 sm0_fib_index;
 	  u32 required_thread_index = thread_index;
 
-	  /* speculatively enqueue b0 to the current next frame */
 	  bi0 = from[0];
 	  to_next[0] = bi0;
 	  from += 1;
@@ -1835,7 +2067,7 @@ VLIB_NODE_FN (nat44_ei_in2out_fast_node)
 	  n_left_to_next -= 1;
 
 	  b0 = vlib_get_buffer (vm, bi0);
-	  next0 = NAT44_EI_IN2OUT_NEXT_LOOKUP;
+	  next0 = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP;
 
 	  ip0 = vlib_buffer_get_current (b0);
 	  udp0 = ip4_next_header (ip0);
@@ -1843,117 +2075,36 @@ VLIB_NODE_FN (nat44_ei_in2out_fast_node)
 	  icmp0 = (icmp46_header_t *) udp0;
 
 	  sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
-	  rx_fib_index0 =
-	    ip4_fib_table_get_index_for_sw_if_index (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_EI_IN2OUT_NEXT_ICMP_ERROR;
-	      goto trace0;
-	    }
-
 	  proto0 = ip_proto_to_nat_proto (ip0->protocol);
 
-	  if (PREDICT_FALSE (proto0 == NAT_PROTOCOL_OTHER))
-	    goto trace0;
-
-	  if (PREDICT_FALSE (proto0 == NAT_PROTOCOL_ICMP))
-	    {
-	      next0 = nat44_ei_icmp_in2out (b0, ip0, icmp0, sw_if_index0,
-					    rx_fib_index0, node, next0, ~0, 0);
-	      goto trace0;
-	    }
-
-	  if (nat44_ei_static_mapping_match (
-		ip0->src_address, udp0->src_port, rx_fib_index0, proto0,
-		&sm0_addr, &sm0_port, &sm0_fib_index, 0, 0, 0))
-	    {
-	      b0->error = node->errors[NAT44_EI_IN2OUT_ERROR_NO_TRANSLATION];
-	      next0 = NAT44_EI_IN2OUT_NEXT_DROP;
-	      goto trace0;
-	    }
-
-	  new_addr0 = sm0_addr.as_u32;
-	  new_port0 = sm0_port;
-	  vnet_buffer (b0)->sw_if_index[VLIB_TX] = sm0_fib_index;
-	  old_addr0 = ip0->src_address.as_u32;
-	  ip0->src_address.as_u32 = new_addr0;
-
-	  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 (new_port0 != udp0->dst_port))
-	    {
-	      old_port0 = udp0->src_port;
-	      udp0->src_port = new_port0;
-
-	      if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
-		{
-		  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 */ );
-		  mss_clamping (nm->mss_clamping, tcp0, &sum0);
-		  tcp0->checksum = ip_csum_fold (sum0);
-		}
-	      else if (udp0->checksum)
-		{
-		  sum0 = udp0->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 */ );
-		  udp0->checksum = ip_csum_fold (sum0);
-		}
-	    }
-	  else
+	  switch (proto0)
 	    {
-	      if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
-		{
-		  sum0 = tcp0->checksum;
-		  sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-					 ip4_header_t,
-					 dst_address /* changed member */ );
-		  mss_clamping (nm->mss_clamping, tcp0, &sum0);
-		  tcp0->checksum = ip_csum_fold (sum0);
-		}
-	      else if (udp0->checksum)
-		{
-		  sum0 = udp0->checksum;
-		  sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-					 ip4_header_t,
-					 dst_address /* changed member */ );
-		  udp0->checksum = ip_csum_fold (sum0);
-		}
+	    case NAT_PROTOCOL_TCP:
+	      // fallthrough
+	    case NAT_PROTOCOL_UDP:
+	      is_hairpinning = nat44_ei_hairpinning (
+		vm, node, nm, thread_index, b0, ip0, udp0, tcp0, proto0,
+		0 /* do_trace */, &required_thread_index);
+	      break;
+	    case NAT_PROTOCOL_ICMP:
+	      is_hairpinning = (0 == nat44_ei_icmp_hairpinning (
+				       nm, b0, thread_index, ip0, icmp0,
+				       &required_thread_index));
+	      break;
+	    case NAT_PROTOCOL_OTHER:
+	      // this should never happen
+	      next0 = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP;
+	      break;
 	    }
 
-	  /* Hairpinning */
-	  is_hairpinning = nat44_ei_hairpinning (
-	    vm, node, nm, thread_index, b0, ip0, udp0, tcp0, proto0,
-	    0 /* do_trace */, &required_thread_index);
-
 	  if (thread_index != required_thread_index)
 	    {
-	      vnet_buffer (b0)->snat.required_thread_index =
-		required_thread_index;
-	      next0 = NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF;
+	      // but we already did a handoff ...
+	      next0 = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP;
 	    }
 
-	trace0:
-	  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
-			     && (b0->flags & VLIB_BUFFER_IS_TRACED)))
+	  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
+			     (b0->flags & VLIB_BUFFER_IS_TRACED)))
 	    {
 	      nat44_ei_in2out_trace_t *t =
 		vlib_add_trace (vm, node, b0, sizeof (*t));
@@ -1962,18 +2113,15 @@ VLIB_NODE_FN (nat44_ei_in2out_fast_node)
 	      t->is_hairpinning = is_hairpinning;
 	    }
 
-	  if (next0 != NAT44_EI_IN2OUT_NEXT_DROP)
+	  if (next0 != NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP)
 	    {
-
 	      vlib_increment_simple_counter (
 		&nm->counters.fastpath.in2out.other, sw_if_index0,
 		vm->thread_index, 1);
 	    }
 
-	  /* 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_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);
@@ -1982,83 +2130,16 @@ VLIB_NODE_FN (nat44_ei_in2out_fast_node)
   return frame->n_vectors;
 }
 
-VLIB_REGISTER_NODE (nat44_ei_in2out_fast_node) = {
-  .name = "nat44-ei-in2out-fast",
-  .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_in2out_fast_trace,
-  .type = VLIB_NODE_TYPE_INTERNAL,
-
-  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
-  .error_strings = nat44_ei_in2out_error_strings,
-
-  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
-  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
-
-  /* edit / add dispositions here */
-  .next_nodes = {
-    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
-    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
-    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-slowpath",
-    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
-    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
-  },
-};
-
-VLIB_NODE_FN (nat44_ei_in2out_hairpinning_handoff_ip4_lookup_node)
+VLIB_NODE_FN (nat44_ei_hairpinning_node)
 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_hairpinning_handoff_fn_inline (
-    vm, node, frame,
-    nat44_ei_main.in2out_hairpinning_finish_ip4_lookup_node_fq_index);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_handoff_ip4_lookup_node) = {
-  .name = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
-  .vector_size = sizeof (u32),
-  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
-  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
-  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
-
-  .n_next_nodes = 1,
-
-  .next_nodes = {
-    [0] = "error-drop",
-  },
-};
-
-VLIB_NODE_FN (nat44_ei_in2out_hairpinning_handoff_interface_output_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_hairpinning_handoff_fn_inline (
-    vm, node, frame,
-    nat44_ei_main.in2out_hairpinning_finish_interface_output_node_fq_index);
-}
-
-VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_handoff_interface_output_node) = {
-  .name = "nat44-ei-in2out-hairpinning-handoff-interface-output",
-  .vector_size = sizeof (u32),
-  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
-  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
-  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
-
-  .n_next_nodes = 1,
-
-  .next_nodes = {
-    [0] = "error-drop",
-  },
-};
-
-static_always_inline int
-nat44_ei_in2out_hairpinning_finish_inline (vlib_main_t *vm,
-					   vlib_node_runtime_t *node,
-					   vlib_frame_t *frame)
 {
   u32 n_left_from, *from, *to_next;
   u32 thread_index = vm->thread_index;
-  nat44_ei_in2out_next_t next_index;
+  nat44_ei_hairpin_next_t next_index;
   nat44_ei_main_t *nm = &nat44_ei_main;
-  int is_hairpinning = 0;
+  vnet_feature_main_t *fm = &feature_main;
+  u8 arc_index = vnet_feat_arc_ip4_local.feature_arc_index;
+  vnet_feature_config_main_t *cm = &fm->feature_config_mains[arc_index];
 
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
@@ -2075,15 +2156,13 @@ nat44_ei_in2out_hairpinning_finish_inline (vlib_main_t *vm,
 	  u32 bi0;
 	  vlib_buffer_t *b0;
 	  u32 next0;
-	  u32 sw_if_index0;
 	  ip4_header_t *ip0;
+	  u32 proto0;
 	  udp_header_t *udp0;
 	  tcp_header_t *tcp0;
-	  icmp46_header_t *icmp0;
-	  u32 proto0;
+	  u32 sw_if_index0;
 	  u32 required_thread_index = thread_index;
 
-	  /* speculatively enqueue b0 to the current next frame */
 	  bi0 = from[0];
 	  to_next[0] = bi0;
 	  from += 1;
@@ -2092,60 +2171,39 @@ nat44_ei_in2out_hairpinning_finish_inline (vlib_main_t *vm,
 	  n_left_to_next -= 1;
 
 	  b0 = vlib_get_buffer (vm, bi0);
-	  next0 = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP;
-
 	  ip0 = vlib_buffer_get_current (b0);
 	  udp0 = ip4_next_header (ip0);
 	  tcp0 = (tcp_header_t *) udp0;
-	  icmp0 = (icmp46_header_t *) udp0;
-
 	  sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+
 	  proto0 = ip_proto_to_nat_proto (ip0->protocol);
+	  int next0_resolved = 0;
 
-	  switch (proto0)
+	  if (nat44_ei_hairpinning (vm, node, nm, thread_index, b0, ip0, udp0,
+				    tcp0, proto0, 1, &required_thread_index))
 	    {
-	    case NAT_PROTOCOL_TCP:
-	      // fallthrough
-	    case NAT_PROTOCOL_UDP:
-	      is_hairpinning = nat44_ei_hairpinning (
-		vm, node, nm, thread_index, b0, ip0, udp0, tcp0, proto0,
-		0 /* do_trace */, &required_thread_index);
-	      break;
-	    case NAT_PROTOCOL_ICMP:
-	      is_hairpinning = (0 == nat44_ei_icmp_hairpinning (
-				       nm, b0, thread_index, ip0, icmp0,
-				       &required_thread_index));
-	      break;
-	    case NAT_PROTOCOL_OTHER:
-	      // this should never happen
-	      next0 = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP;
-	      break;
+	      next0 = NAT44_EI_HAIRPIN_NEXT_LOOKUP;
+	      next0_resolved = 1;
 	    }
 
 	  if (thread_index != required_thread_index)
 	    {
-	      // but we already did a handoff ...
-	      next0 = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP;
+	      vnet_buffer (b0)->snat.required_thread_index =
+		required_thread_index;
+	      next0 = NAT44_EI_HAIRPIN_NEXT_HANDOFF;
+	      next0_resolved = 1;
 	    }
 
-	  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
-			     (b0->flags & VLIB_BUFFER_IS_TRACED)))
-	    {
-	      nat44_ei_in2out_trace_t *t =
-		vlib_add_trace (vm, node, b0, sizeof (*t));
-	      t->sw_if_index = sw_if_index0;
-	      t->next_index = next0;
-	      t->is_hairpinning = is_hairpinning;
-	    }
+	  if (!next0_resolved)
+	    vnet_get_config_data (&cm->config_main, &b0->current_config_index,
+				  &next0, 0);
 
-	  if (next0 != NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP)
+	  if (next0 != NAT44_EI_HAIRPIN_NEXT_DROP)
 	    {
 	      vlib_increment_simple_counter (
-		&nm->counters.fastpath.in2out.other, sw_if_index0,
-		vm->thread_index, 1);
+		&nm->counters.hairpinning, vm->thread_index, sw_if_index0, 1);
 	    }
 
-	  /* 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);
 	}
@@ -2156,58 +2214,216 @@ nat44_ei_in2out_hairpinning_finish_inline (vlib_main_t *vm,
   return frame->n_vectors;
 }
 
+VLIB_NODE_FN (nat44_ei_in2out_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 0, 0);
+}
+
+VLIB_NODE_FN (nat44_ei_in2out_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 0, 1);
+}
+
+VLIB_NODE_FN (nat44_ei_in2out_slowpath_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 1, 0);
+}
+
+VLIB_NODE_FN (nat44_ei_in2out_output_slowpath_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_in2out_node_fn_inline (vm, node, frame, 1, 1);
+}
+
+VLIB_NODE_FN (nat44_ei_in2out_hairpinning_handoff_ip4_lookup_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_hairpinning_handoff_fn_inline (
+    vm, node, frame,
+    nat44_ei_main.in2out_hairpinning_finish_ip4_lookup_node_fq_index);
+}
+
+VLIB_NODE_FN (nat44_ei_in2out_hairpinning_handoff_interface_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_hairpinning_handoff_fn_inline (
+    vm, node, frame,
+    nat44_ei_main.in2out_hairpinning_finish_interface_output_node_fq_index);
+}
+
 VLIB_NODE_FN (nat44_ei_in2out_hairpinning_finish_ip4_lookup_node)
 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
 {
   return nat44_ei_in2out_hairpinning_finish_inline (vm, node, frame);
 }
 
-VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_finish_ip4_lookup_node) = {
-  .name = "nat44-ei-in2out-hairpinning-finish-ip4-lookup",
+VLIB_NODE_FN (nat44_ei_in2out_hairpinning_finish_interface_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_in2out_hairpinning_finish_inline (vm, node, frame);
+}
+
+VLIB_NODE_FN (nat44_ei_hairpinning_handoff_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_ei_hairpinning_handoff_fn_inline (
+    vm, node, frame, nat44_ei_main.hairpinning_fq_index);
+}
+
+VLIB_REGISTER_NODE (nat44_ei_in2out_node) = {
+  .name = "nat44-ei-in2out",
   .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_in2out_fast_trace,
+  .format_trace = format_nat44_ei_in2out_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
+  .error_strings = nat44_ei_in2out_error_strings,
+  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
+  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
+  .next_nodes = {
+    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
+    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-slowpath",
+    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
+  },
+};
 
+VLIB_REGISTER_NODE (nat44_ei_in2out_output_node) = {
+  .name = "nat44-ei-in2out-output",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_ei_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
   .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
   .error_strings = nat44_ei_in2out_error_strings,
+  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
+  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
+  .next_nodes = {
+    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
+    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "interface-output",
+    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-output-slowpath",
+    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-interface-output",
+  },
+};
 
+VLIB_REGISTER_NODE (nat44_ei_in2out_slowpath_node) = {
+  .name = "nat44-ei-in2out-slowpath",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_ei_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
+  .error_strings = nat44_ei_in2out_error_strings,
   .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
+  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
+  .next_nodes = {
+    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
+    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-slowpath",
+    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
+  },
+};
 
-  .n_next_nodes = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
+VLIB_REGISTER_NODE (nat44_ei_in2out_output_slowpath_node) = {
+  .name = "nat44-ei-in2out-output-slowpath",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_ei_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
+  .error_strings = nat44_ei_in2out_error_strings,
+  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
+  .n_next_nodes = NAT44_EI_IN2OUT_N_NEXT,
+  .next_nodes = {
+    [NAT44_EI_IN2OUT_NEXT_DROP] = "error-drop",
+    [NAT44_EI_IN2OUT_NEXT_LOOKUP] = "interface-output",
+    [NAT44_EI_IN2OUT_NEXT_SLOW_PATH] = "nat44-ei-in2out-output-slowpath",
+    [NAT44_EI_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [NAT44_EI_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-ei-in2out-hairpinning-handoff-interface-output",
+  },
+};
+
+VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_handoff_ip4_lookup_node) = {
+  .name = "nat44-ei-in2out-hairpinning-handoff-ip4-lookup",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
+  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
+  .n_next_nodes = 1,
+  .next_nodes = {
+    [0] = "error-drop",
+  },
+};
 
-  /* edit / add dispositions here */
+VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_handoff_interface_output_node) = {
+  .name = "nat44-ei-in2out-hairpinning-handoff-interface-output",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
+  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
+  .n_next_nodes = 1,
+  .next_nodes = {
+    [0] = "error-drop",
+  },
+};
+
+VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_finish_ip4_lookup_node) = {
+  .name = "nat44-ei-in2out-hairpinning-finish-ip4-lookup",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat44_ei_in2out_fast_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
+  .error_strings = nat44_ei_in2out_error_strings,
+  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
+  .n_next_nodes = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
   .next_nodes = {
     [NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP] = "error-drop",
     [NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP] = "ip4-lookup",
   },
 };
 
-VLIB_NODE_FN (nat44_ei_in2out_hairpinning_finish_interface_output_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  return nat44_ei_in2out_hairpinning_finish_inline (vm, node, frame);
-}
-
 VLIB_REGISTER_NODE (nat44_ei_in2out_hairpinning_finish_interface_output_node) = {
   .name = "nat44-ei-in2out-hairpinning-finish-interface-output",
   .vector_size = sizeof (u32),
   .format_trace = format_nat44_ei_in2out_fast_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
-
   .n_errors = ARRAY_LEN(nat44_ei_in2out_error_strings),
   .error_strings = nat44_ei_in2out_error_strings,
-
   .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
   .n_next_nodes = NAT44_EI_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
-
-  /* edit / add dispositions here */
   .next_nodes = {
     [NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP] = "error-drop",
     [NAT44_EI_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP] = "interface-output",
   },
 };
 
+VLIB_REGISTER_NODE (nat44_ei_hairpinning_handoff_node) = {
+  .name = "nat44-ei-hairpinning-handoff",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_ei_hairpinning_handoff_error_strings),
+  .error_strings = nat44_ei_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_ei_hairpinning_handoff_trace,
+  .n_next_nodes = 1,
+  .next_nodes = {
+    [0] = "error-drop",
+  },
+};
+
+VLIB_REGISTER_NODE (nat44_ei_hairpinning_node) = {
+  .name = "nat44-ei-hairpinning",
+  .vector_size = sizeof (u32),
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .format_trace = format_nat44_ei_hairpin_trace,
+  .n_next_nodes = NAT44_EI_HAIRPIN_N_NEXT,
+  .next_nodes = {
+    [NAT44_EI_HAIRPIN_NEXT_DROP] = "error-drop",
+    [NAT44_EI_HAIRPIN_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT44_EI_HAIRPIN_NEXT_HANDOFF] = "nat44-ei-hairpinning-handoff",
+  },
+};
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
diff --git a/src/plugins/nat/nat44-ei/nat44_ei_out2in.c b/src/plugins/nat/nat44-ei/nat44_ei_out2in.c
index 7858811fde5..5d91cb04f7c 100644
--- a/src/plugins/nat/nat44-ei/nat44_ei_out2in.c
+++ b/src/plugins/nat/nat44-ei/nat44_ei_out2in.c
@@ -56,18 +56,6 @@ format_nat44_ei_out2in_trace (u8 *s, va_list *args)
   return s;
 }
 
-static u8 *
-format_nat44_ei_out2in_fast_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_ei_out2in_trace_t *t = va_arg (*args, nat44_ei_out2in_trace_t *);
-
-  s = format (s, "NAT44_OUT2IN_FAST: sw_if_index %d, next index %d",
-	      t->sw_if_index, t->next_index);
-  return s;
-}
-
 #define foreach_nat44_ei_out2in_error                                         \
   _ (UNSUPPORTED_PROTOCOL, "unsupported protocol")                            \
   _ (OUT_OF_PORTS, "out of ports")                                            \
@@ -1336,7 +1324,6 @@ VLIB_NODE_FN (nat44_ei_out2in_node)
   return frame->n_vectors;
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (nat44_ei_out2in_node) = {
   .name = "nat44-ei-out2in",
   .vector_size = sizeof (u32),
@@ -1357,190 +1344,6 @@ VLIB_REGISTER_NODE (nat44_ei_out2in_node) = {
     [NAT44_EI_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
   },
 };
-/* *INDENT-ON* */
-
-VLIB_NODE_FN (nat44_ei_out2in_fast_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
-{
-  u32 n_left_from, *from;
-  nat44_ei_main_t *nm = &nat44_ei_main;
-
-  from = vlib_frame_vector_args (frame);
-  n_left_from = frame->n_vectors;
-
-  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
-  u16 nexts[VLIB_FRAME_SIZE], *next = nexts;
-  vlib_get_buffers (vm, from, b, n_left_from);
-  while (n_left_from > 0)
-    {
-      vlib_buffer_t *b0;
-      u32 next0 = NAT44_EI_OUT2IN_NEXT_DROP;
-      u32 sw_if_index0;
-      ip4_header_t *ip0;
-      ip_csum_t sum0;
-      u32 new_addr0, old_addr0;
-      u16 new_port0, old_port0;
-      udp_header_t *udp0;
-      tcp_header_t *tcp0;
-      icmp46_header_t *icmp0;
-      u32 proto0;
-      u32 rx_fib_index0;
-      ip4_address_t sm_addr0;
-      u16 sm_port0;
-      u32 sm_fib_index0;
-
-      b0 = *b;
-      b++;
-
-      ip0 = vlib_buffer_get_current (b0);
-      udp0 = ip4_next_header (ip0);
-      tcp0 = (tcp_header_t *) udp0;
-      icmp0 = (icmp46_header_t *) udp0;
-
-      sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
-      rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0);
-
-      vnet_feature_next (&next0, b0);
-
-      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_EI_OUT2IN_NEXT_ICMP_ERROR;
-	  goto trace00;
-	}
-
-      proto0 = ip_proto_to_nat_proto (ip0->protocol);
-
-      if (PREDICT_FALSE (proto0 == NAT_PROTOCOL_OTHER))
-	goto trace00;
-
-      if (PREDICT_FALSE (proto0 == NAT_PROTOCOL_ICMP))
-	{
-	  next0 = nat44_ei_icmp_out2in (b0, ip0, icmp0, sw_if_index0,
-					rx_fib_index0, node, next0, ~0, 0);
-	  goto trace00;
-	}
-
-      if (nat44_ei_static_mapping_match (ip0->dst_address, udp0->dst_port,
-					 rx_fib_index0, proto0, &sm_addr0,
-					 &sm_port0, &sm_fib_index0, 1, 0, 0))
-	{
-	  b0->error = node->errors[NAT44_EI_OUT2IN_ERROR_NO_TRANSLATION];
-	  goto trace00;
-	}
-
-      new_addr0 = sm_addr0.as_u32;
-      new_port0 = sm_port0;
-      vnet_buffer (b0)->sw_if_index[VLIB_TX] = sm_fib_index0;
-      old_addr0 = ip0->dst_address.as_u32;
-      ip0->dst_address.as_u32 = new_addr0;
-
-      sum0 = ip0->checksum;
-      sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-			     ip4_header_t, dst_address /* changed member */ );
-      ip0->checksum = ip_csum_fold (sum0);
-
-      if (PREDICT_FALSE (new_port0 != udp0->dst_port))
-	{
-	  old_port0 = udp0->dst_port;
-	  udp0->dst_port = new_port0;
-
-	  if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
-	    {
-	      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 if (udp0->checksum)
-	    {
-	      sum0 = udp0->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 */ );
-	      udp0->checksum = ip_csum_fold (sum0);
-	    }
-	}
-      else
-	{
-	  if (PREDICT_TRUE (proto0 == NAT_PROTOCOL_TCP))
-	    {
-	      sum0 = tcp0->checksum;
-	      sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-				     ip4_header_t,
-				     dst_address /* changed member */ );
-	      tcp0->checksum = ip_csum_fold (sum0);
-	    }
-	  else if (udp0->checksum)
-	    {
-	      sum0 = udp0->checksum;
-	      sum0 = ip_csum_update (sum0, old_addr0, new_addr0,
-				     ip4_header_t,
-				     dst_address /* changed member */ );
-	      udp0->checksum = ip_csum_fold (sum0);
-	    }
-	}
-
-    trace00:
-
-      if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
-			 && (b0->flags & VLIB_BUFFER_IS_TRACED)))
-	{
-	  nat44_ei_out2in_trace_t *t =
-	    vlib_add_trace (vm, node, b0, sizeof (*t));
-	  t->sw_if_index = sw_if_index0;
-	  t->next_index = next0;
-	}
-
-      if (next0 == NAT44_EI_OUT2IN_NEXT_DROP)
-	{
-	  vlib_increment_simple_counter (&nm->counters.fastpath.out2in.drops,
-					 vm->thread_index, sw_if_index0, 1);
-	}
-
-      n_left_from--;
-      next[0] = next0;
-      next++;
-    }
-
-  vlib_buffer_enqueue_to_next (vm, node, from, (u16 *) nexts,
-			       frame->n_vectors);
-
-  return frame->n_vectors;
-}
-
-/* *INDENT-OFF* */
-VLIB_REGISTER_NODE (nat44_ei_out2in_fast_node) = {
-  .name = "nat44-ei-out2in-fast",
-  .vector_size = sizeof (u32),
-  .format_trace = format_nat44_ei_out2in_fast_trace,
-  .type = VLIB_NODE_TYPE_INTERNAL,
-
-  .n_errors = ARRAY_LEN(nat44_ei_out2in_error_strings),
-  .error_strings = nat44_ei_out2in_error_strings,
-
-  .runtime_data_bytes = sizeof (nat44_ei_runtime_t),
-
-  .n_next_nodes = NAT44_EI_OUT2IN_N_NEXT,
-
-  /* edit / add dispositions here */
-  .next_nodes = {
-    [NAT44_EI_OUT2IN_NEXT_LOOKUP] = "ip4-lookup",
-    [NAT44_EI_OUT2IN_NEXT_DROP] = "error-drop",
-    [NAT44_EI_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error",
-  },
-};
-/* *INDENT-ON* */
 
 /*
  * fd.io coding-style-patch-verification: ON
-- 
cgit