L2 over MPLS
[vpp.git] / src / plugins / snat / snat.c
index 97eb061..f196b5c 100644 (file)
@@ -72,6 +72,29 @@ VNET_FEATURE_INIT (ip4_snat_out2in_fast, static) = {
   .node_name = "snat-out2in-fast",
   .runs_before = VNET_FEATURES ("ip4-lookup"),
 };
+VNET_FEATURE_INIT (ip4_snat_hairpin_dst, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "snat-hairpin-dst",
+  .runs_before = VNET_FEATURES ("ip4-lookup"),
+};
+
+/* Hook up output features */
+VNET_FEATURE_INIT (ip4_snat_in2out_output, static) = {
+  .arc_name = "ip4-output",
+  .node_name = "snat-in2out-output",
+  .runs_before = VNET_FEATURES ("interface-output"),
+};
+VNET_FEATURE_INIT (ip4_snat_in2out_output_worker_handoff, static) = {
+  .arc_name = "ip4-output",
+  .node_name = "snat-in2out-output-worker-handoff",
+  .runs_before = VNET_FEATURES ("interface-output"),
+};
+VNET_FEATURE_INIT (ip4_snat_hairpin_src, static) = {
+  .arc_name = "ip4-output",
+  .node_name = "snat-hairpin-src",
+  .runs_before = VNET_FEATURES ("interface-output"),
+};
+
 
 /* *INDENT-OFF* */
 VLIB_PLUGIN_REGISTER () = {
@@ -112,7 +135,7 @@ snat_add_del_addr_to_fib (ip4_address_t * addr, u8 p_len, u32 sw_if_index,
                                     (FIB_ENTRY_FLAG_CONNECTED |
                                      FIB_ENTRY_FLAG_LOCAL |
                                      FIB_ENTRY_FLAG_EXCLUSIVE),
-                                    FIB_PROTOCOL_IP4,
+                                    DPO_PROTO_IP4,
                                     NULL,
                                     sw_if_index,
                                     ~0,
@@ -142,7 +165,11 @@ void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id)
 
   vec_add2 (sm->addresses, ap, 1);
   ap->addr = *addr;
-  ap->fib_index = ip4_fib_index_from_table_id(vrf_id);
+  if (vrf_id != ~0)
+    ap->fib_index =
+      fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, vrf_id);
+  else
+    ap->fib_index = ~0;
 #define _(N, i, n, s) \
   clib_bitmap_alloc (ap->busy_##n##_port_bitmap, 65535);
   foreach_snat_protocol
@@ -157,6 +184,14 @@ void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id)
     snat_add_del_addr_to_fib(addr, 32, i->sw_if_index, 1);
     break;
   }));
+  pool_foreach (i, sm->output_feature_interfaces,
+  ({
+    if (i->is_inside)
+      continue;
+
+    snat_add_del_addr_to_fib(addr, 32, i->sw_if_index, 1);
+    break;
+  }));
 }
 
 static int is_snat_address_used_in_static_mapping (snat_main_t *sm,
@@ -542,6 +577,14 @@ delete:
     snat_add_del_addr_to_fib(&e_addr, 32, interface->sw_if_index, is_add);
     break;
   }));
+  pool_foreach (interface, sm->output_feature_interfaces,
+  ({
+    if (interface->is_inside)
+      continue;
+
+    snat_add_del_addr_to_fib(&e_addr, 32, interface->sw_if_index, is_add);
+    break;
+  }));
 
   return 0;
 }
@@ -592,6 +635,9 @@ int snat_del_address (snat_main_t *sm, ip4_address_t addr, u8 delete_sm)
         }
     }
 
+  if (a->fib_index != ~0)
+    fib_table_unlock(a->fib_index, FIB_PROTOCOL_IP4);
+
   /* Delete sessions using address */
   if (a->busy_tcp_ports || a->busy_udp_ports || a->busy_icmp_ports)
     {
@@ -668,6 +714,14 @@ int snat_del_address (snat_main_t *sm, ip4_address_t addr, u8 delete_sm)
     snat_add_del_addr_to_fib(&addr, 32, interface->sw_if_index, 0);
     break;
   }));
+  pool_foreach (interface, sm->output_feature_interfaces,
+  ({
+    if (interface->is_inside)
+      continue;
+
+    snat_add_del_addr_to_fib(&addr, 32, interface->sw_if_index, 0);
+    break;
+  }));
 
   return 0;
 }
@@ -746,10 +800,95 @@ fib:
   return 0;
 }
 
+int snat_interface_add_del_output_feature (u32 sw_if_index,
+                                           u8 is_inside,
+                                           int is_del)
+{
+  snat_main_t *sm = &snat_main;
+  snat_interface_t *i;
+  snat_address_t * ap;
+  snat_static_mapping_t * m;
+
+  if (sm->deterministic ||
+      (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)))
+    return VNET_API_ERROR_UNSUPPORTED;
+
+  if (is_inside)
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "snat-hairpin-dst",
+                                   sw_if_index, !is_del, 0, 0);
+      vnet_feature_enable_disable ("ip4-output", "snat-hairpin-src",
+                                   sw_if_index, !is_del, 0, 0);
+      goto fq;
+    }
+
+  if (sm->num_workers > 1)
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "snat-out2in-worker-handoff",
+                                   sw_if_index, !is_del, 0, 0);
+      vnet_feature_enable_disable ("ip4-output",
+                                   "snat-in2out-output-worker-handoff",
+                                   sw_if_index, !is_del, 0, 0);
+    }
+  else
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "snat-out2in", sw_if_index,
+                                   !is_del, 0, 0);
+      vnet_feature_enable_disable ("ip4-output", "snat-in2out-output",
+                                   sw_if_index, !is_del, 0, 0);
+    }
+
+fq:
+  if (sm->fq_in2out_output_index == ~0 && sm->num_workers > 1)
+    sm->fq_in2out_output_index =
+      vlib_frame_queue_main_init (sm->in2out_output_node_index, 0);
+
+  if (sm->fq_out2in_index == ~0 && sm->num_workers > 1)
+    sm->fq_out2in_index = vlib_frame_queue_main_init (sm->out2in_node_index, 0);
+
+  pool_foreach (i, sm->output_feature_interfaces,
+  ({
+    if (i->sw_if_index == sw_if_index)
+      {
+        if (is_del)
+          pool_put (sm->output_feature_interfaces, i);
+        else
+          return VNET_API_ERROR_VALUE_EXIST;
+
+        goto fib;
+      }
+  }));
+
+  if (is_del)
+    return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+  pool_get (sm->output_feature_interfaces, i);
+  i->sw_if_index = sw_if_index;
+  i->is_inside = is_inside;
+
+  /* Add/delete external addresses to FIB */
+fib:
+  if (is_inside)
+    return 0;
+
+  vec_foreach (ap, sm->addresses)
+    snat_add_del_addr_to_fib(&ap->addr, 32, sw_if_index, !is_del);
+
+  pool_foreach (m, sm->static_mappings,
+  ({
+    if (!(m->addr_only))
+      continue;
+
+    snat_add_del_addr_to_fib(&m->external_addr, 32, sw_if_index, !is_del);
+  }));
+
+  return 0;
+}
+
 int snat_set_workers (uword * bitmap)
 {
   snat_main_t *sm = &snat_main;
-  int i;
+  int i, j = 0;
 
   if (sm->num_workers < 2)
     return VNET_API_ERROR_FEATURE_DISABLED;
@@ -761,8 +900,13 @@ int snat_set_workers (uword * bitmap)
   clib_bitmap_foreach (i, bitmap,
     ({
       vec_add1(sm->workers, i);
+      sm->per_thread_data[i].snat_thread_index = j;
+      j++;
     }));
 
+  sm->port_per_thread = (0xffff - 1024) / _vec_len (sm->workers);
+  sm->num_snat_thread = _vec_len (sm->workers);
+
   return 0;
 }
 
@@ -797,7 +941,9 @@ static clib_error_t * snat_init (vlib_main_t * vm)
   sm->first_worker_index = 0;
   sm->next_worker = 0;
   sm->num_workers = 0;
+  sm->num_snat_thread = 1;
   sm->workers = 0;
+  sm->port_per_thread = 0xffff - 1024;
   sm->fq_in2out_index = ~0;
   sm->fq_out2in_index = ~0;
   sm->udp_timeout = SNAT_UDP_TIMEOUT;
@@ -816,6 +962,8 @@ static clib_error_t * snat_init (vlib_main_t * vm)
         }
     }
 
+  vec_validate (sm->per_thread_data, tm->n_vlib_mains - 1);
+
   /* Use all available workers by default */
   if (sm->num_workers > 1)
     {
@@ -824,6 +972,10 @@ static clib_error_t * snat_init (vlib_main_t * vm)
       snat_set_workers(bitmap);
       clib_bitmap_free (bitmap);
     }
+  else
+    {
+      sm->per_thread_data[0].snat_thread_index = 0;
+    }
 
   error = snat_api_init(vm, sm);
   if (error)
@@ -942,8 +1094,16 @@ int snat_static_mapping_match (snat_main_t * sm,
   return 0;
 }
 
-int snat_alloc_outside_address_and_port (snat_main_t * sm, 
+static_always_inline u16
+snat_random_port (snat_main_t * sm, u16 min, u16 max)
+{
+  return min + random_u32 (&sm->random_seed) /
+    (random_u32_max() / (max - min + 1) + 1);
+}
+
+int snat_alloc_outside_address_and_port (snat_main_t * sm,
                                          u32 fib_index,
+                                         u32 thread_index,
                                          snat_session_key_t * k,
                                          u32 * address_indexp)
 {
@@ -960,14 +1120,13 @@ int snat_alloc_outside_address_and_port (snat_main_t * sm,
         {
 #define _(N, j, n, s) \
         case SNAT_PROTOCOL_##N: \
-          if (a->busy_##n##_ports < (65535-1024)) \
+          if (a->busy_##n##_ports < (sm->port_per_thread * sm->num_snat_thread)) \
             { \
               while (1) \
                 { \
-                  portnum = random_u32 (&sm->random_seed); \
-                  portnum &= 0xFFFF; \
-                  if (portnum < 1024) \
-                    continue; \
+                  portnum = (sm->port_per_thread * \
+                    sm->per_thread_data[thread_index].snat_thread_index) + \
+                    snat_random_port(sm, 0, sm->port_per_thread) + 1024; \
                   if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, portnum)) \
                     continue; \
                   clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, portnum, 1); \
@@ -1103,6 +1262,7 @@ snat_feature_command_fn (vlib_main_t * vm,
   u32 sw_if_index;
   u32 * inside_sw_if_indices = 0;
   u32 * outside_sw_if_indices = 0;
+  u8 is_output_feature = 0;
   int is_del = 0;
   int i;
 
@@ -1120,6 +1280,8 @@ snat_feature_command_fn (vlib_main_t * vm,
       else if (unformat (line_input, "out %U", unformat_vnet_sw_interface,
                          vnm, &sw_if_index))
         vec_add1 (outside_sw_if_indices, sw_if_index);
+      else if (unformat (line_input, "output-feature"))
+        is_output_feature = 1;
       else if (unformat (line_input, "del"))
         is_del = 1;
       else
@@ -1135,7 +1297,30 @@ snat_feature_command_fn (vlib_main_t * vm,
       for (i = 0; i < vec_len(inside_sw_if_indices); i++)
         {
           sw_if_index = inside_sw_if_indices[i];
-          snat_interface_add_del (sw_if_index, 1, is_del);
+          if (is_output_feature)
+            {
+              if (snat_interface_add_del_output_feature (sw_if_index, 1, is_del))
+                {
+                  error = clib_error_return (0, "%s %U failed",
+                                             is_del ? "del" : "add",
+                                             format_vnet_sw_interface_name, vnm,
+                                             vnet_get_sw_interface (vnm,
+                                                                    sw_if_index));
+                  goto done;
+                }
+            }
+          else
+            {
+              if (snat_interface_add_del (sw_if_index, 1, is_del))
+                {
+                  error = clib_error_return (0, "%s %U failed",
+                                             is_del ? "del" : "add",
+                                             format_vnet_sw_interface_name, vnm,
+                                             vnet_get_sw_interface (vnm,
+                                                                    sw_if_index));
+                  goto done;
+                }
+            }
         }
     }
 
@@ -1144,7 +1329,30 @@ snat_feature_command_fn (vlib_main_t * vm,
       for (i = 0; i < vec_len(outside_sw_if_indices); i++)
         {
           sw_if_index = outside_sw_if_indices[i];
-          snat_interface_add_del (sw_if_index, 0, is_del);
+          if (is_output_feature)
+            {
+              if (snat_interface_add_del_output_feature (sw_if_index, 0, is_del))
+                {
+                  error = clib_error_return (0, "%s %U failed",
+                                             is_del ? "del" : "add",
+                                             format_vnet_sw_interface_name, vnm,
+                                             vnet_get_sw_interface (vnm,
+                                                                    sw_if_index));
+                  goto done;
+                }
+            }
+          else
+            {
+              if (snat_interface_add_del (sw_if_index, 0, is_del))
+                {
+                  error = clib_error_return (0, "%s %U failed",
+                                             is_del ? "del" : "add",
+                                             format_vnet_sw_interface_name, vnm,
+                                             vnet_get_sw_interface (vnm,
+                                                                    sw_if_index));
+                  goto done;
+                }
+            }
         }
     }
 
@@ -1159,7 +1367,8 @@ done:
 VLIB_CLI_COMMAND (set_interface_snat_command, static) = {
   .path = "set interface snat",
   .function = snat_feature_command_fn,
-  .short_help = "set interface snat in <intfc> out <intfc> [del]",
+  .short_help = "set interface snat in <intfc> out <intfc> [output-feature] "
+                "[del]",
 };
 
 uword
@@ -1542,7 +1751,6 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
   u32 static_mapping_memory_size = 64<<20;
   u8 static_mapping_only = 0;
   u8 static_mapping_connection_tracking = 0;
-  vlib_thread_main_t *tm = vlib_get_thread_main ();
 
   sm->deterministic = 0;
 
@@ -1597,6 +1805,7 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
   if (sm->deterministic)
     {
       sm->in2out_node_index = snat_det_in2out_node.index;
+      sm->in2out_output_node_index = ~0;
       sm->out2in_node_index = snat_det_out2in_node.index;
       sm->icmp_match_in2out_cb = icmp_match_in2out_det;
       sm->icmp_match_out2in_cb = icmp_match_out2in_det;
@@ -1606,6 +1815,7 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
       sm->worker_in2out_cb = snat_get_worker_in2out_cb;
       sm->worker_out2in_cb = snat_get_worker_out2in_cb;
       sm->in2out_node_index = snat_in2out_node.index;
+      sm->in2out_output_node_index = snat_in2out_output_node.index;
       sm->out2in_node_index = snat_out2in_node.index;
       if (!static_mapping_only ||
           (static_mapping_only && static_mapping_connection_tracking))
@@ -1619,8 +1829,6 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
           clib_bihash_init_8_8 (&sm->worker_by_out, "worker-by-out", user_buckets,
                                 user_memory_size);
 
-          vec_validate (sm->per_thread_data, tm->n_vlib_mains - 1);
-
           clib_bihash_init_8_8 (&sm->in2out, "in2out", translation_buckets,
                                 translation_memory_size);
 
@@ -1881,6 +2089,14 @@ show_snat_command_fn (vlib_main_t * vm,
                          i->is_inside ? "in" : "out");
       }));
 
+      pool_foreach (i, sm->output_feature_interfaces,
+      ({
+        vlib_cli_output (vm, "%U output-feature %s",
+                         format_vnet_sw_interface_name, vnm,
+                         vnet_get_sw_interface (vnm, i->sw_if_index),
+                         i->is_inside ? "in" : "out");
+      }));
+
       if (vec_len (sm->auto_add_sw_if_indices))
         {
           vlib_cli_output (vm, "SNAT pool addresses interfaces:");