nat: fix EI hairpinning thread safety 74/31174/4
authorKlement Sekera <ksekera@cisco.com>
Tue, 2 Feb 2021 12:25:40 +0000 (13:25 +0100)
committerOle Tr�an <otroan@employees.org>
Wed, 10 Feb 2021 13:12:33 +0000 (13:12 +0000)
Avoid doing inter-thread reads without locks by doing a handoff before
destination address rewrite. Destination address is read from a session
which is possibly owned by a different thread. By splitting the work in
two parts with a handoff in the middle, we can do both in a thread safe
way.

Type: improvement
Signed-off-by: Klement Sekera <ksekera@cisco.com>
Change-Id: I1c50d188393a610f5564fa230c75771a8065f273

src/plugins/nat/nat.c
src/plugins/nat/nat.h
src/plugins/nat/nat44-ei/nat44_ei_in2out.c
src/plugins/nat/nat44_hairpinning.c
src/plugins/nat/nat44_hairpinning.h [new file with mode: 0644]
src/plugins/nat/test/test_nat44_ei.py
src/vnet/buffer.h
test/run_tests.py

index 11e2d19..85d4775 100644 (file)
@@ -2514,6 +2514,13 @@ do                                        \
     vlib_zero_simple_counter (&c, 0);     \
   } while (0);
 
+extern vlib_node_registration_t nat44_hairpinning_node;
+extern vlib_node_registration_t snat_hairpin_dst_node;
+extern vlib_node_registration_t
+  nat44_in2out_hairpinning_finish_ip4_lookup_node;
+extern vlib_node_registration_t
+  nat44_in2out_hairpinning_finish_interface_output_node;
+
 static clib_error_t *
 nat_init (vlib_main_t * vm)
 {
@@ -2632,6 +2639,17 @@ nat_init (vlib_main_t * vm)
   nat_ha_init (vm, sm->num_workers, num_threads);
 
   test_key_calc_split ();
+
+  sm->nat44_hairpinning_fq_index =
+    vlib_frame_queue_main_init (nat44_hairpinning_node.index, 0);
+  sm->snat_hairpin_dst_fq_index =
+    vlib_frame_queue_main_init (snat_hairpin_dst_node.index, 0);
+  sm->nat44_in2out_hairpinning_finish_ip4_lookup_node_fq_index =
+    vlib_frame_queue_main_init (
+      nat44_in2out_hairpinning_finish_ip4_lookup_node.index, 0);
+  sm->nat44_in2out_hairpinning_finish_interface_output_node_fq_index =
+    vlib_frame_queue_main_init (
+      nat44_in2out_hairpinning_finish_interface_output_node.index, 0);
   return nat44_api_hookup (vm);
 }
 
index e913484..c1dc31e 100644 (file)
@@ -783,6 +783,11 @@ typedef struct snat_main_s
   u8 enabled;
 
   vnet_main_t *vnet_main;
+
+  u32 nat44_in2out_hairpinning_finish_ip4_lookup_node_fq_index;
+  u32 nat44_in2out_hairpinning_finish_interface_output_node_fq_index;
+  u32 nat44_hairpinning_fq_index;
+  u32 snat_hairpin_dst_fq_index;
 } snat_main_t;
 
 typedef struct
@@ -1149,14 +1154,17 @@ u32 icmp_match_out2in_slow (snat_main_t *sm, vlib_node_runtime_t *node,
 
 /* hairpinning functions */
 u32 snat_icmp_hairpinning (snat_main_t *sm, vlib_buffer_t *b0,
-                          ip4_header_t *ip0, icmp46_header_t *icmp0);
+                          u32 thread_index, ip4_header_t *ip0,
+                          icmp46_header_t *icmp0, u32 *required_thread_index);
 
 void nat_hairpinning_sm_unknown_proto (snat_main_t * sm, vlib_buffer_t * b,
                                       ip4_header_t * ip);
+
 int snat_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node,
-                     snat_main_t *sm, vlib_buffer_t *b0, ip4_header_t *ip0,
-                     udp_header_t *udp0, tcp_header_t *tcp0, u32 proto0,
-                     int do_trace);
+                     snat_main_t *sm, 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);
 
 /* Call back functions for clib_bihash_add_or_overwrite_stale */
 int nat44_i2o_is_idle_session_cb (clib_bihash_kv_8_8_t * kv, void *arg);
index ad0007b..54ed1a9 100644 (file)
@@ -35,6 +35,7 @@
 #include <vppinfra/error.h>
 #include <vppinfra/elog.h>
 #include <nat/lib/nat_inlines.h>
+#include <nat/nat44_hairpinning.h>
 
 typedef struct
 {
@@ -108,9 +109,17 @@ typedef enum
   SNAT_IN2OUT_NEXT_DROP,
   SNAT_IN2OUT_NEXT_ICMP_ERROR,
   SNAT_IN2OUT_NEXT_SLOW_PATH,
+  SNAT_IN2OUT_NEXT_HAIRPINNING_HANDOFF,
   SNAT_IN2OUT_N_NEXT,
 } snat_in2out_next_t;
 
+typedef enum
+{
+  NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP,
+  NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP,
+  NAT44_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
+} nat44_in2out_hairpinnig_finish_next_t;
+
 static inline int
 snat_not_translate (snat_main_t * sm, vlib_node_runtime_t * node,
                    u32 sw_if_index0, ip4_header_t * ip0, u32 proto0,
@@ -167,13 +176,11 @@ nat_not_translate_output_feature (snat_main_t * sm, ip4_header_t * ip0,
   if (!clib_bihash_search_8_8 (&sm->in2out, &kv0, &value0))
     {
       /* hairpinning */
-    /* *INDENT-OFF* */
-    pool_foreach (i, sm->output_feature_interfaces)
-     {
-      if ((nat_interface_is_inside(i)) && (sw_if_index == i->sw_if_index))
-        return 0;
-    }
-    /* *INDENT-ON* */
+      pool_foreach (i, sm->output_feature_interfaces)
+       {
+         if ((nat_interface_is_inside (i)) && (sw_if_index == i->sw_if_index))
+           return 0;
+       }
       return 1;
     }
 
@@ -328,7 +335,6 @@ slow_path (snat_main_t * sm, vlib_buffer_t * b0,
       s->out2in.fib_index = sm->outside_fibs[0].fib_index;
       break;
     default:
-      /* *INDENT-OFF* */
       vec_foreach (outside_fib, sm->outside_fibs)
         {
           fei = fib_table_lookup (outside_fib->fib_index, &pfx);
@@ -341,7 +347,6 @@ slow_path (snat_main_t * sm, vlib_buffer_t * b0,
                 }
             }
         }
-      /* *INDENT-ON* */
       break;
     }
   s->ext_host_addr.as_u32 = ip0->dst_address.as_u32;
@@ -660,6 +665,7 @@ icmp_in2out (snat_main_t *sm, vlib_buffer_t *b0, ip4_header_t *ip0,
   ip_csum_t sum0;
   u16 checksum0;
   u32 next0_tmp;
+  u32 required_thread_index = thread_index;
 
   echo0 = (icmp_echo_header_t *) (icmp0 + 1);
 
@@ -784,8 +790,14 @@ icmp_in2out (snat_main_t *sm, vlib_buffer_t *b0, ip4_header_t *ip0,
 
   if (vnet_buffer (b0)->sw_if_index[VLIB_TX] == ~0)
     {
-      if (0 != snat_icmp_hairpinning (sm, b0, ip0, icmp0))
+      if (0 != snat_icmp_hairpinning (sm, b0, thread_index, ip0, icmp0,
+                                     &required_thread_index))
        vnet_buffer (b0)->sw_if_index[VLIB_TX] = fib_index;
+      if (thread_index != required_thread_index)
+       {
+         vnet_buffer (b0)->snat.required_thread_index = required_thread_index;
+         next0 = SNAT_IN2OUT_NEXT_HAIRPINNING_HANDOFF;
+       }
     }
 
 out:
@@ -1623,7 +1635,6 @@ VLIB_NODE_FN (snat_in2out_node) (vlib_main_t * vm,
                                     0);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_in2out_node) = {
   .name = "nat44-in2out",
   .vector_size = sizeof (u32),
@@ -1643,9 +1654,9 @@ VLIB_REGISTER_NODE (snat_in2out_node) = {
     [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_HAIRPINNING_HANDOFF] = "nat44-in2out-hairpinning-handoff-ip4-lookup",
   },
 };
-/* *INDENT-ON* */
 
 VLIB_NODE_FN (snat_in2out_output_node) (vlib_main_t * vm,
                                        vlib_node_runtime_t * node,
@@ -1655,7 +1666,6 @@ VLIB_NODE_FN (snat_in2out_output_node) (vlib_main_t * vm,
                                     1);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_in2out_output_node) = {
   .name = "nat44-in2out-output",
   .vector_size = sizeof (u32),
@@ -1675,9 +1685,9 @@ VLIB_REGISTER_NODE (snat_in2out_output_node) = {
     [SNAT_IN2OUT_NEXT_LOOKUP] = "interface-output",
     [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-output-slowpath",
     [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [SNAT_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-in2out-hairpinning-handoff-interface-output",
   },
 };
-/* *INDENT-ON* */
 
 VLIB_NODE_FN (snat_in2out_slowpath_node) (vlib_main_t * vm,
                                          vlib_node_runtime_t * node,
@@ -1687,7 +1697,6 @@ VLIB_NODE_FN (snat_in2out_slowpath_node) (vlib_main_t * vm,
                                     0);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_in2out_slowpath_node) = {
   .name = "nat44-in2out-slowpath",
   .vector_size = sizeof (u32),
@@ -1707,9 +1716,9 @@ VLIB_REGISTER_NODE (snat_in2out_slowpath_node) = {
     [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_HAIRPINNING_HANDOFF] = "nat44-in2out-hairpinning-handoff-ip4-lookup",
   },
 };
-/* *INDENT-ON* */
 
 VLIB_NODE_FN (snat_in2out_output_slowpath_node) (vlib_main_t * vm,
                                                 vlib_node_runtime_t * node,
@@ -1719,7 +1728,6 @@ VLIB_NODE_FN (snat_in2out_output_slowpath_node) (vlib_main_t * vm,
                                     1);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_in2out_output_slowpath_node) = {
   .name = "nat44-in2out-output-slowpath",
   .vector_size = sizeof (u32),
@@ -1739,15 +1747,16 @@ VLIB_REGISTER_NODE (snat_in2out_output_slowpath_node) = {
     [SNAT_IN2OUT_NEXT_LOOKUP] = "interface-output",
     [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-output-slowpath",
     [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error",
+    [SNAT_IN2OUT_NEXT_HAIRPINNING_HANDOFF] = "nat44-in2out-hairpinning-handoff-interface-output",
   },
 };
-/* *INDENT-ON* */
 
 VLIB_NODE_FN (snat_in2out_fast_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;
   snat_in2out_next_t next_index;
   snat_main_t *sm = &snat_main;
   int is_hairpinning = 0;
@@ -1780,7 +1789,7 @@ VLIB_NODE_FN (snat_in2out_fast_node) (vlib_main_t * vm,
          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];
@@ -1896,8 +1905,16 @@ VLIB_NODE_FN (snat_in2out_fast_node) (vlib_main_t * vm,
            }
 
          /* Hairpinning */
-         is_hairpinning = snat_hairpinning (vm, node, sm, b0, ip0, udp0, tcp0,
-                                            proto0, 0 /* do_trace */);
+         is_hairpinning = snat_hairpinning (
+           vm, node, sm, 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 = SNAT_IN2OUT_NEXT_HAIRPINNING_HANDOFF;
+           }
 
        trace0:
          if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
@@ -1930,8 +1947,6 @@ VLIB_NODE_FN (snat_in2out_fast_node) (vlib_main_t * vm,
   return frame->n_vectors;
 }
 
-
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_in2out_fast_node) = {
   .name = "nat44-in2out-fast",
   .vector_size = sizeof (u32),
@@ -1951,9 +1966,212 @@ VLIB_REGISTER_NODE (snat_in2out_fast_node) = {
     [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_HAIRPINNING_HANDOFF] = "nat44-in2out-hairpinning-handoff-ip4-lookup",
+  },
+};
+
+VLIB_NODE_FN (nat44_in2out_hairpinning_handoff_ip4_lookup_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_hairpinning_handoff_fn_inline (
+    vm, node, frame,
+    snat_main.nat44_in2out_hairpinning_finish_ip4_lookup_node_fq_index);
+}
+
+VLIB_REGISTER_NODE (nat44_in2out_hairpinning_handoff_ip4_lookup_node) = {
+  .name = "nat44-in2out-hairpinning-handoff-ip4-lookup",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_hairpinning_handoff_error_strings),
+  .error_strings = nat44_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_hairpinning_handoff_trace,
+
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "error-drop",
+  },
+};
+
+VLIB_NODE_FN (nat44_in2out_hairpinning_handoff_interface_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_hairpinning_handoff_fn_inline (
+    vm, node, frame,
+    snat_main.nat44_in2out_hairpinning_finish_interface_output_node_fq_index);
+}
+
+VLIB_REGISTER_NODE (nat44_in2out_hairpinning_handoff_interface_output_node) = {
+  .name = "nat44-in2out-hairpinning-handoff-interface-output",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_hairpinning_handoff_error_strings),
+  .error_strings = nat44_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_hairpinning_handoff_trace,
+
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "error-drop",
+  },
+};
+
+static_always_inline int
+nat44_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;
+  snat_in2out_next_t next_index;
+  snat_main_t *sm = &snat_main;
+  int is_hairpinning = 0;
+
+  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;
+         u32 sw_if_index0;
+         ip4_header_t *ip0;
+         udp_header_t *udp0;
+         tcp_header_t *tcp0;
+         icmp46_header_t *icmp0;
+         u32 proto0;
+         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_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);
+
+         switch (proto0)
+           {
+           case NAT_PROTOCOL_TCP:
+             // fallthrough
+           case NAT_PROTOCOL_UDP:
+             is_hairpinning = snat_hairpinning (
+               vm, node, sm, thread_index, b0, ip0, udp0, tcp0, proto0,
+               0 /* do_trace */, &required_thread_index);
+             break;
+           case NAT_PROTOCOL_ICMP:
+             is_hairpinning =
+               (0 == snat_icmp_hairpinning (sm, b0, thread_index, ip0, icmp0,
+                                            &required_thread_index));
+             break;
+           case NAT_PROTOCOL_OTHER:
+             // this should never happen
+             next0 = NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP;
+             break;
+           }
+
+         if (thread_index != required_thread_index)
+           {
+             // but we already did a handoff ...
+             next0 = NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP;
+           }
+
+         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->sw_if_index = sw_if_index0;
+             t->next_index = next0;
+             t->is_hairpinning = is_hairpinning;
+           }
+
+         if (next0 != NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP)
+           {
+             vlib_increment_simple_counter (
+               &sm->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_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (nat44_in2out_hairpinning_finish_ip4_lookup_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_in2out_hairpinning_finish_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (nat44_in2out_hairpinning_finish_ip4_lookup_node) = {
+  .name = "nat44-in2out-hairpinning-finish-ip4-lookup",
+  .vector_size = sizeof (u32),
+  .format_trace = format_snat_in2out_fast_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 = NAT44_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP] = "error-drop",
+    [NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP] = "ip4-lookup",
+  },
+};
+
+VLIB_NODE_FN (nat44_in2out_hairpinning_finish_interface_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_in2out_hairpinning_finish_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (nat44_in2out_hairpinning_finish_interface_output_node) = {
+  .name = "nat44-in2out-hairpinning-finish-interface-output",
+  .vector_size = sizeof (u32),
+  .format_trace = format_snat_in2out_fast_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 = NAT44_IN2OUT_HAIRPINNING_FINISH_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_DROP] = "error-drop",
+    [NAT44_IN2OUT_HAIRPINNING_FINISH_NEXT_LOOKUP] = "interface-output",
   },
 };
-/* *INDENT-ON* */
 
 /*
  * fd.io coding-style-patch-verification: ON
index f458909..a2cb2eb 100644 (file)
@@ -22,6 +22,7 @@
 #include <vnet/fib/ip4_fib.h>
 #include <nat/nat.h>
 #include <nat/nat_inlines.h>
+#include <nat/nat44_hairpinning.h>
 
 typedef enum
 {
@@ -36,6 +37,7 @@ typedef enum
 {
   NAT_HAIRPIN_NEXT_LOOKUP,
   NAT_HAIRPIN_NEXT_DROP,
+  NAT_HAIRPIN_NEXT_HANDOFF,
   NAT_HAIRPIN_N_NEXT,
 } nat_hairpin_next_t;
 
@@ -77,13 +79,11 @@ is_hairpinning (snat_main_t * sm, ip4_address_t * dst_addr)
   snat_address_t *ap;
   clib_bihash_kv_8_8_t kv, value;
 
-  /* *INDENT-OFF* */
   vec_foreach (ap, sm->addresses)
     {
       if (ap->addr.as_u32 == dst_addr->as_u32)
         return 1;
     }
-  /* *INDENT-ON* */
 
   init_nat_k (&kv, *dst_addr, 0, 0, 0);
   if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
@@ -95,13 +95,14 @@ is_hairpinning (snat_main_t * sm, ip4_address_t * dst_addr)
 #ifndef CLIB_MARCH_VARIANT
 int
 snat_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node, snat_main_t *sm,
-                 vlib_buffer_t *b0, ip4_header_t *ip0, udp_header_t *udp0,
-                 tcp_header_t *tcp0, u32 proto0, int do_trace)
+                 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)
 {
   snat_session_t *s0 = NULL;
   clib_bihash_kv_8_8_t kv0, value0;
   ip_csum_t sum0;
-  u32 new_dst_addr0 = 0, old_dst_addr0, ti = 0, si = ~0;
+  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;
@@ -120,13 +121,6 @@ snat_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node, snat_main_t *sm,
   /* or active session */
   else
     {
-      if (sm->num_workers > 1)
-       ti =
-         (clib_net_to_host_u16 (udp0->dst_port) -
-          1024) / sm->port_per_thread;
-      else
-       ti = sm->num_workers;
-
       init_nat_k (&kv0, ip0->dst_address, udp0->dst_port,
                  sm->outside_fib_index, proto0);
       rv = clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0);
@@ -136,8 +130,14 @@ snat_hairpinning (vlib_main_t *vm, vlib_node_runtime_t *node, snat_main_t *sm,
          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 (sm->per_thread_data[ti].sessions, si);
+      s0 = pool_elt_at_index (sm->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;
@@ -220,8 +220,9 @@ trace:
 
 #ifndef CLIB_MARCH_VARIANT
 u32
-snat_icmp_hairpinning (snat_main_t *sm, vlib_buffer_t *b0, ip4_header_t *ip0,
-                      icmp46_header_t *icmp0)
+snat_icmp_hairpinning (snat_main_t *sm, 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;
@@ -250,6 +251,12 @@ snat_icmp_hairpinning (snat_main_t *sm, vlib_buffer_t *b0, ip4_header_t *ip0,
                  sm->outside_fib_index, protocol);
       if (clib_bihash_search_8_8 (&sm->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 (sm->per_thread_data[ti].sessions, si);
       new_dst_addr0 = s0->in2out.addr.as_u32;
@@ -295,14 +302,15 @@ snat_icmp_hairpinning (snat_main_t *sm, vlib_buffer_t *b0, ip4_header_t *ip0,
          u16 icmp_id0 = echo0->identifier;
          init_nat_k (&kv0, ip0->dst_address, icmp_id0, sm->outside_fib_index,
                      NAT_PROTOCOL_ICMP);
-         if (sm->num_workers > 1)
-           ti =
-             (clib_net_to_host_u16 (icmp_id0) - 1024) / sm->port_per_thread;
-         else
-           ti = sm->num_workers;
          int rv = clib_bihash_search_8_8 (&sm->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 (sm->per_thread_data[ti].sessions, si);
              new_dst_addr0 = s0->in2out.addr.as_u32;
@@ -371,6 +379,7 @@ nat44_hairpinning_fn_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;
   nat_hairpin_next_t next_index;
   snat_main_t *sm = &snat_main;
   vnet_feature_main_t *fm = &feature_main;
@@ -397,6 +406,7 @@ nat44_hairpinning_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
          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];
@@ -413,13 +423,27 @@ nat44_hairpinning_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
          sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
 
          proto0 = ip_proto_to_nat_proto (ip0->protocol);
+         int next0_resolved = 0;
+
+         if (snat_hairpinning (vm, node, sm, thread_index, b0, ip0, udp0,
+                               tcp0, proto0, 1 /* do_trace */,
+                               &required_thread_index))
+           {
+             next0 = NAT_HAIRPIN_NEXT_LOOKUP;
+             next0_resolved = 1;
+           }
 
-         vnet_get_config_data (&cm->config_main, &b0->current_config_index,
-                               &next0, 0);
+         if (thread_index != required_thread_index)
+           {
+             vnet_buffer (b0)->snat.required_thread_index =
+               required_thread_index;
+             next0 = NAT_HAIRPIN_NEXT_HANDOFF;
+             next0_resolved = 1;
+           }
 
-         if (snat_hairpinning (vm, node, sm, b0, ip0, udp0, tcp0, proto0,
-                               1 /* do_trace */))
-           next0 = NAT_HAIRPIN_NEXT_LOOKUP;
+         if (!next0_resolved)
+           vnet_get_config_data (&cm->config_main, &b0->current_config_index,
+                                 &next0, 0);
 
          if (next0 != NAT_HAIRPIN_NEXT_DROP)
            {
@@ -447,7 +471,6 @@ VLIB_NODE_FN (nat44_hairpinning_node) (vlib_main_t * vm,
   return nat44_hairpinning_fn_inline (vm, node, frame);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (nat44_hairpinning_node) = {
   .name = "nat44-hairpinning",
   .vector_size = sizeof (u32),
@@ -457,15 +480,37 @@ VLIB_REGISTER_NODE (nat44_hairpinning_node) = {
   .next_nodes = {
     [NAT_HAIRPIN_NEXT_DROP] = "error-drop",
     [NAT_HAIRPIN_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT_HAIRPIN_NEXT_HANDOFF] = "nat44-hairpinning-handoff",
+  },
+};
+
+VLIB_NODE_FN (nat44_hairpinning_handoff_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_hairpinning_handoff_fn_inline (
+    vm, node, frame, snat_main.nat44_hairpinning_fq_index);
+}
+
+VLIB_REGISTER_NODE (nat44_hairpinning_handoff_node) = {
+  .name = "nat44-hairpinning-handoff",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_hairpinning_handoff_error_strings),
+  .error_strings = nat44_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_hairpinning_handoff_trace,
+
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "error-drop",
   },
 };
-/* *INDENT-ON* */
 
 static inline uword
 snat_hairpin_dst_fn_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;
   nat_hairpin_next_t next_index;
   snat_main_t *sm = &snat_main;
 
@@ -487,6 +532,7 @@ snat_hairpin_dst_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
          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];
@@ -511,14 +557,16 @@ snat_hairpin_dst_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
                  udp_header_t *udp0 = ip4_next_header (ip0);
                  tcp_header_t *tcp0 = (tcp_header_t *) udp0;
 
-                 snat_hairpinning (vm, node, sm, b0, ip0, udp0, tcp0, proto0,
-                                   1 /* do_trace */);
+                 snat_hairpinning (vm, node, sm, 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);
 
-                 snat_icmp_hairpinning (sm, b0, ip0, icmp0);
+                 snat_icmp_hairpinning (sm, b0, thread_index, ip0, icmp0,
+                                        &required_thread_index);
                }
              else
                {
@@ -528,6 +576,12 @@ snat_hairpin_dst_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
              vnet_buffer (b0)->snat.flags = SNAT_FLAG_HAIRPINNING;
            }
 
+         if (thread_index != required_thread_index)
+           {
+             vnet_buffer (b0)->snat.required_thread_index =
+               required_thread_index;
+             next0 = NAT_HAIRPIN_NEXT_HANDOFF;
+           }
 
          if (next0 != NAT_HAIRPIN_NEXT_DROP)
            {
@@ -555,7 +609,6 @@ VLIB_NODE_FN (snat_hairpin_dst_node) (vlib_main_t * vm,
   return snat_hairpin_dst_fn_inline (vm, node, frame);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_hairpin_dst_node) = {
   .name = "nat44-hairpin-dst",
   .vector_size = sizeof (u32),
@@ -565,9 +618,30 @@ VLIB_REGISTER_NODE (snat_hairpin_dst_node) = {
   .next_nodes = {
     [NAT_HAIRPIN_NEXT_DROP] = "error-drop",
     [NAT_HAIRPIN_NEXT_LOOKUP] = "ip4-lookup",
+    [NAT_HAIRPIN_NEXT_HANDOFF] = "nat44-hairpin-dst-handoff",
+  },
+};
+
+VLIB_NODE_FN (nat44_hairpinning_dst_handoff_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat44_hairpinning_handoff_fn_inline (
+    vm, node, frame, snat_main.snat_hairpin_dst_fq_index);
+}
+
+VLIB_REGISTER_NODE (nat44_hairpinning_dst_handoff_node) = {
+  .name = "nat44-hairpin-dst-handoff",
+  .vector_size = sizeof (u32),
+  .n_errors = ARRAY_LEN(nat44_hairpinning_handoff_error_strings),
+  .error_strings = nat44_hairpinning_handoff_error_strings,
+  .format_trace = format_nat44_hairpinning_handoff_trace,
+
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "error-drop",
   },
 };
-/* *INDENT-ON* */
 
 static inline uword
 snat_hairpin_src_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
@@ -607,7 +681,6 @@ snat_hairpin_src_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
          sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
          vnet_feature_next (&next0, b0);
 
-          /* *INDENT-OFF* */
           pool_foreach (i, sm->output_feature_interfaces)
            {
             /* Only packets from NAT inside interface */
@@ -624,7 +697,6 @@ snat_hairpin_src_fn_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
                 break;
               }
           }
-          /* *INDENT-ON* */
 
          if (next0 != SNAT_HAIRPIN_SRC_NEXT_DROP)
            {
@@ -652,7 +724,6 @@ VLIB_NODE_FN (snat_hairpin_src_node) (vlib_main_t * vm,
   return snat_hairpin_src_fn_inline (vm, node, frame);
 }
 
-/* *INDENT-OFF* */
 VLIB_REGISTER_NODE (snat_hairpin_src_node) = {
   .name = "nat44-hairpin-src",
   .vector_size = sizeof (u32),
@@ -665,7 +736,6 @@ VLIB_REGISTER_NODE (snat_hairpin_src_node) = {
      [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH] = "nat44-in2out-output-worker-handoff",
   },
 };
-/* *INDENT-ON* */
 
 /*
  * fd.io coding-style-patch-verification: ON
diff --git a/src/plugins/nat/nat44_hairpinning.h b/src/plugins/nat/nat44_hairpinning.h
new file mode 100644 (file)
index 0000000..2a8b73d
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef __included_nat44_hairpinning_h__
+#define __included_nat44_hairpinning_h__
+
+#include <nat/nat.h>
+
+#define foreach_nat44_hairpinning_handoff_error                               \
+  _ (CONGESTION_DROP, "congestion drop")
+
+typedef enum
+{
+#define _(sym, str) NAT44_HAIRPINNING_HANDOFF_ERROR_##sym,
+  foreach_nat44_hairpinning_handoff_error
+#undef _
+    NAT44_HAIRPINNING_HANDOFF_N_ERROR,
+} nat44_hairpinning_handoff_error_t;
+
+static char *nat44_hairpinning_handoff_error_strings[] = {
+#define _(sym, string) string,
+  foreach_nat44_hairpinning_handoff_error
+#undef _
+};
+
+typedef struct
+{
+  u32 next_worker_index;
+} nat44_hairpinning_handoff_trace_t;
+
+static u8 *
+format_nat44_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_hairpinning_handoff_trace_t *t =
+    va_arg (*args, nat44_hairpinning_handoff_trace_t *);
+
+  s = format (s, "nat-hairpinning-handoff: next-worker %d",
+             t->next_worker_index);
+
+  return s;
+}
+
+always_inline uword
+nat44_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_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, fq_index, from, thread_indices,
+                                        frame->n_vectors, 1);
+
+  if (n_enq < frame->n_vectors)
+    vlib_node_increment_counter (
+      vm, node->node_index, NAT44_HAIRPINNING_HANDOFF_ERROR_CONGESTION_DROP,
+      frame->n_vectors - n_enq);
+  return frame->n_vectors;
+}
+
+#endif // __included_nat44_hairpinning_h__
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index e69e24b..471d148 100644 (file)
@@ -3859,5 +3859,345 @@ class TestNAT44Out2InDPO(MethodHolder):
         self.verify_capture_in(capture, self.pg0)
 
 
+class TestNAT44EIMW(MethodHolder):
+    """ NAT44EI Test Cases (multiple workers) """
+
+    worker_config = "workers %d" % 2
+
+    max_translations = 10240
+    max_users = 10240
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNAT44EIMW, cls).setUpClass()
+        cls.vapi.cli("set log class nat level debug")
+
+        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.ipfix_src_port = 4739
+        cls.ipfix_domain_id = 1
+        cls.tcp_external_port = 80
+        cls.udp_external_port = 69
+
+        cls.create_pg_interfaces(range(10))
+        cls.interfaces = list(cls.pg_interfaces[0:4])
+
+        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.pg1.generate_remote_hosts(1)
+        cls.pg1.configure_ipv4_neighbors()
+
+        cls.overlapping_interfaces = list(list(cls.pg_interfaces[4:7]))
+        cls.vapi.ip_table_add_del(is_add=1, table={'table_id': 10})
+        cls.vapi.ip_table_add_del(is_add=1, table={'table_id': 20})
+
+        cls.pg4._local_ip4 = "172.16.255.1"
+        cls.pg4._remote_hosts[0]._ip4 = "172.16.255.2"
+        cls.pg4.set_table_ip4(10)
+        cls.pg5._local_ip4 = "172.17.255.3"
+        cls.pg5._remote_hosts[0]._ip4 = "172.17.255.4"
+        cls.pg5.set_table_ip4(10)
+        cls.pg6._local_ip4 = "172.16.255.1"
+        cls.pg6._remote_hosts[0]._ip4 = "172.16.255.2"
+        cls.pg6.set_table_ip4(20)
+        for i in cls.overlapping_interfaces:
+            i.config_ip4()
+            i.admin_up()
+            i.resolve_arp()
+
+        cls.pg7.admin_up()
+        cls.pg8.admin_up()
+
+        cls.pg9.generate_remote_hosts(2)
+        cls.pg9.config_ip4()
+        cls.vapi.sw_interface_add_del_address(
+            sw_if_index=cls.pg9.sw_if_index,
+            prefix="10.0.0.1/24")
+
+        cls.pg9.admin_up()
+        cls.pg9.resolve_arp()
+        cls.pg9._remote_hosts[1]._ip4 = cls.pg9._remote_hosts[0]._ip4
+        cls.pg4._remote_ip4 = cls.pg9._remote_hosts[0]._ip4 = "10.0.0.2"
+        cls.pg9.resolve_arp()
+
+    def setUp(self):
+        super(TestNAT44EIMW, self).setUp()
+        self.vapi.nat44_plugin_enable_disable(
+            sessions=self.max_translations,
+            users=self.max_users, enable=1)
+
+    def tearDown(self):
+        super(TestNAT44EIMW, self).tearDown()
+        if not self.vpp_dead:
+            self.vapi.nat_ipfix_enable_disable(domain_id=self.ipfix_domain_id,
+                                               src_port=self.ipfix_src_port,
+                                               enable=0)
+            self.ipfix_src_port = 4739
+            self.ipfix_domain_id = 1
+
+            self.vapi.nat44_plugin_enable_disable(enable=0)
+            self.vapi.cli("clear logging")
+
+    def test_hairpinning(self):
+        """ NAT44EI hairpinning - 1:1 NAPT """
+
+        host = self.pg0.remote_hosts[0]
+        server = self.pg0.remote_hosts[1]
+        host_in_port = 1234
+        host_out_port = 0
+        server_in_port = 5678
+        server_out_port = 8765
+        worker_1 = 1
+        worker_2 = 2
+
+        self.nat44_add_address(self.nat_addr)
+        flags = self.config_flags.NAT_IS_INSIDE
+        self.vapi.nat44_interface_add_del_feature(
+            sw_if_index=self.pg0.sw_if_index,
+            flags=flags, is_add=1)
+        self.vapi.nat44_interface_add_del_feature(
+            sw_if_index=self.pg1.sw_if_index,
+            is_add=1)
+
+        # add static mapping for server
+        self.nat44_add_static_mapping(server.ip4, self.nat_addr,
+                                      server_in_port, server_out_port,
+                                      proto=IP_PROTOS.tcp)
+
+        cnt = self.statistics.get_counter('/nat44/hairpinning')
+        # send packet from host to server
+        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+             IP(src=host.ip4, dst=self.nat_addr) /
+             TCP(sport=host_in_port, dport=server_out_port))
+        self.pg0.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, self.nat_addr)
+            self.assertEqual(ip.dst, server.ip4)
+            self.assertNotEqual(tcp.sport, host_in_port)
+            self.assertEqual(tcp.dport, server_in_port)
+            self.assert_packet_checksums_valid(p)
+            host_out_port = tcp.sport
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        after = self.statistics.get_counter('/nat44/hairpinning')
+
+        if_idx = self.pg0.sw_if_index
+        self.assertEqual(after[worker_2][if_idx] - cnt[worker_1][if_idx], 1)
+
+        # send reply from server to host
+        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+             IP(src=server.ip4, dst=self.nat_addr) /
+             TCP(sport=server_in_port, dport=host_out_port))
+        self.pg0.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, self.nat_addr)
+            self.assertEqual(ip.dst, host.ip4)
+            self.assertEqual(tcp.sport, server_out_port)
+            self.assertEqual(tcp.dport, host_in_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        after = self.statistics.get_counter('/nat44/hairpinning')
+        if_idx = self.pg0.sw_if_index
+        self.assertEqual(after[worker_1][if_idx] - cnt[worker_1][if_idx], 1)
+        self.assertEqual(after[worker_2][if_idx] - cnt[worker_2][if_idx], 2)
+
+    def test_hairpinning2(self):
+        """ NAT44EI hairpinning - 1:1 NAT"""
+
+        server1_nat_ip = "10.0.0.10"
+        server2_nat_ip = "10.0.0.11"
+        host = self.pg0.remote_hosts[0]
+        server1 = self.pg0.remote_hosts[1]
+        server2 = self.pg0.remote_hosts[2]
+        server_tcp_port = 22
+        server_udp_port = 20
+
+        self.nat44_add_address(self.nat_addr)
+        flags = self.config_flags.NAT_IS_INSIDE
+        self.vapi.nat44_interface_add_del_feature(
+            sw_if_index=self.pg0.sw_if_index,
+            flags=flags, is_add=1)
+        self.vapi.nat44_interface_add_del_feature(
+            sw_if_index=self.pg1.sw_if_index,
+            is_add=1)
+
+        # add static mapping for servers
+        self.nat44_add_static_mapping(server1.ip4, server1_nat_ip)
+        self.nat44_add_static_mapping(server2.ip4, server2_nat_ip)
+
+        # host to server1
+        pkts = []
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=host.ip4, dst=server1_nat_ip) /
+             TCP(sport=self.tcp_port_in, dport=server_tcp_port))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=host.ip4, dst=server1_nat_ip) /
+             UDP(sport=self.udp_port_in, dport=server_udp_port))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=host.ip4, dst=server1_nat_ip) /
+             ICMP(id=self.icmp_id_in, type='echo-request'))
+        pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IP].src, self.nat_addr)
+                self.assertEqual(packet[IP].dst, server1.ip4)
+                if packet.haslayer(TCP):
+                    self.assertNotEqual(packet[TCP].sport, self.tcp_port_in)
+                    self.assertEqual(packet[TCP].dport, server_tcp_port)
+                    self.tcp_port_out = packet[TCP].sport
+                    self.assert_packet_checksums_valid(packet)
+                elif packet.haslayer(UDP):
+                    self.assertNotEqual(packet[UDP].sport, self.udp_port_in)
+                    self.assertEqual(packet[UDP].dport, server_udp_port)
+                    self.udp_port_out = packet[UDP].sport
+                else:
+                    self.assertNotEqual(packet[ICMP].id, self.icmp_id_in)
+                    self.icmp_id_out = packet[ICMP].id
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
+        # server1 to host
+        pkts = []
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server1.ip4, dst=self.nat_addr) /
+             TCP(sport=server_tcp_port, dport=self.tcp_port_out))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server1.ip4, dst=self.nat_addr) /
+             UDP(sport=server_udp_port, dport=self.udp_port_out))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server1.ip4, dst=self.nat_addr) /
+             ICMP(id=self.icmp_id_out, type='echo-reply'))
+        pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IP].src, server1_nat_ip)
+                self.assertEqual(packet[IP].dst, host.ip4)
+                if packet.haslayer(TCP):
+                    self.assertEqual(packet[TCP].dport, self.tcp_port_in)
+                    self.assertEqual(packet[TCP].sport, server_tcp_port)
+                    self.assert_packet_checksums_valid(packet)
+                elif packet.haslayer(UDP):
+                    self.assertEqual(packet[UDP].dport, self.udp_port_in)
+                    self.assertEqual(packet[UDP].sport, server_udp_port)
+                else:
+                    self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
+        # server2 to server1
+        pkts = []
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server2.ip4, dst=server1_nat_ip) /
+             TCP(sport=self.tcp_port_in, dport=server_tcp_port))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server2.ip4, dst=server1_nat_ip) /
+             UDP(sport=self.udp_port_in, dport=server_udp_port))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server2.ip4, dst=server1_nat_ip) /
+             ICMP(id=self.icmp_id_in, type='echo-request'))
+        pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IP].src, server2_nat_ip)
+                self.assertEqual(packet[IP].dst, server1.ip4)
+                if packet.haslayer(TCP):
+                    self.assertEqual(packet[TCP].sport, self.tcp_port_in)
+                    self.assertEqual(packet[TCP].dport, server_tcp_port)
+                    self.tcp_port_out = packet[TCP].sport
+                    self.assert_packet_checksums_valid(packet)
+                elif packet.haslayer(UDP):
+                    self.assertEqual(packet[UDP].sport, self.udp_port_in)
+                    self.assertEqual(packet[UDP].dport, server_udp_port)
+                    self.udp_port_out = packet[UDP].sport
+                else:
+                    self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+                    self.icmp_id_out = packet[ICMP].id
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
+        # server1 to server2
+        pkts = []
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server1.ip4, dst=server2_nat_ip) /
+             TCP(sport=server_tcp_port, dport=self.tcp_port_out))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server1.ip4, dst=server2_nat_ip) /
+             UDP(sport=server_udp_port, dport=self.udp_port_out))
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=server1.ip4, dst=server2_nat_ip) /
+             ICMP(id=self.icmp_id_out, type='echo-reply'))
+        pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IP].src, server1_nat_ip)
+                self.assertEqual(packet[IP].dst, server2.ip4)
+                if packet.haslayer(TCP):
+                    self.assertEqual(packet[TCP].dport, self.tcp_port_in)
+                    self.assertEqual(packet[TCP].sport, server_tcp_port)
+                    self.assert_packet_checksums_valid(packet)
+                elif packet.haslayer(UDP):
+                    self.assertEqual(packet[UDP].dport, self.udp_port_in)
+                    self.assertEqual(packet[UDP].sport, server_udp_port)
+                else:
+                    self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
index aae9996..88637e1 100644 (file)
@@ -378,6 +378,7 @@ typedef struct
     struct
     {
       u32 flags;
+      u32 required_thread_index;
     } snat;
 
     u32 unused[6];
index a88a69a..1a29d14 100644 (file)
@@ -272,6 +272,8 @@ def handle_failed_suite(logger, last_test_temp_dir, vpp_pid):
             except Exception as e:
                 logger.exception("Unexpected error running `file' utility "
                                  "on core-file")
+            logger.error("gdb %s %s" %
+                         (os.getenv('VPP_BIN', 'vpp'), core_path))
 
     if vpp_pid:
         # Copy api post mortem