NAT44: make 1:1NAT for DHCP addressed interface persistent
[vpp.git] / src / plugins / nat / nat.c
index 95004da..e3f7fba 100644 (file)
@@ -24,6 +24,7 @@
 #include <nat/nat_ipfix_logging.h>
 #include <nat/nat_det.h>
 #include <nat/nat64.h>
+#include <nat/nat66.h>
 #include <nat/dslite.h>
 #include <nat/nat_reass.h>
 #include <vnet/fib/fib_table.h>
@@ -38,79 +39,79 @@ snat_main_t snat_main;
 VNET_FEATURE_INIT (ip4_snat_in2out, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-in2out",
-  .runs_before = VNET_FEATURES ("nat44-out2in"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_out2in, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_nat_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-classify",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_det_in2out, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-in2out",
-  .runs_before = VNET_FEATURES ("nat44-det-out2in"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_det_out2in, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-out2in",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_nat_det_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-det-classify",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_in2out_worker_handoff, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-in2out-worker-handoff",
-  .runs_before = VNET_FEATURES ("nat44-out2in-worker-handoff"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_out2in_worker_handoff, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in-worker-handoff",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_nat_handoff_classify, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-handoff-classify",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_in2out_fast, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-in2out-fast",
-  .runs_before = VNET_FEATURES ("nat44-out2in-fast"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_out2in_fast, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-out2in-fast",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_hairpin_dst, static) = {
   .arc_name = "ip4-unicast",
   .node_name = "nat44-hairpin-dst",
-  .runs_before = VNET_FEATURES ("ip4-lookup"),
+  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
 };
 
 /* Hook up output features */
 VNET_FEATURE_INIT (ip4_snat_in2out_output, static) = {
   .arc_name = "ip4-output",
   .node_name = "nat44-in2out-output",
-  .runs_before = VNET_FEATURES ("interface-output"),
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_in2out_output_worker_handoff, static) = {
   .arc_name = "ip4-output",
   .node_name = "nat44-in2out-output-worker-handoff",
-  .runs_before = VNET_FEATURES ("interface-output"),
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
 VNET_FEATURE_INIT (ip4_snat_hairpin_src, static) = {
   .arc_name = "ip4-output",
   .node_name = "nat44-hairpin-src",
-  .runs_before = VNET_FEATURES ("interface-output"),
+  .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"),
 };
 
 /* Hook up ip4-local features */
@@ -674,30 +675,79 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
   snat_interface_t *interface;
   int i;
   snat_main_per_thread_data_t *tsm;
+  snat_user_key_t u_key;
+  snat_user_t *u;
+  dlist_elt_t * head, * elt;
+  u32 elt_index, head_index;
+  u32 ses_index;
+  u64 user_index;
+  snat_session_t * s;
 
   /* If the external address is a specific interface address */
   if (sw_if_index != ~0)
     {
       ip4_address_t * first_int_addr;
+      snat_static_map_resolve_t *rp, *rp_match = 0;
+
+      for (i = 0; i < vec_len (sm->to_resolve); i++)
+        {
+          rp = sm->to_resolve + i;
+          if (rp->sw_if_index != sw_if_index &&
+              rp->l_addr.as_u32 != l_addr.as_u32 &&
+              rp->vrf_id != vrf_id && rp->addr_only != addr_only)
+            continue;
+
+          if (!addr_only)
+            {
+              if (rp->l_port != l_port && rp->e_port != e_port && rp->proto != proto)
+                continue;
+            }
+
+          rp_match = rp;
+          break;
+        }
 
       /* Might be already set... */
       first_int_addr = ip4_interface_first_address
         (sm->ip4_main, sw_if_index, 0 /* just want the address*/);
 
-      /* DHCP resolution required? */
-      if (first_int_addr == 0)
+      if (is_add)
         {
-          snat_add_static_mapping_when_resolved
-            (sm, l_addr, l_port, sw_if_index, e_port, vrf_id, proto,
-             addr_only,  is_add, tag);
-          return 0;
+          if (rp_match)
+            return VNET_API_ERROR_VALUE_EXIST;
+
+          /* DHCP resolution required? */
+          if (first_int_addr == 0)
+            {
+              snat_add_static_mapping_when_resolved
+                (sm, l_addr, l_port, sw_if_index, e_port, vrf_id, proto,
+                 addr_only,  is_add, tag);
+              return 0;
+            }
+            else
+            {
+              e_addr.as_u32 = first_int_addr->as_u32;
+              /* Identity mapping? */
+              if (l_addr.as_u32 == 0)
+                l_addr.as_u32 = e_addr.as_u32;
+            }
         }
-        else
+      else
         {
-          e_addr.as_u32 = first_int_addr->as_u32;
-          /* Identity mapping? */
-          if (l_addr.as_u32 == 0)
-            l_addr.as_u32 = e_addr.as_u32;
+          if (!rp_match)
+            return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+          vec_del1 (sm->to_resolve, i);
+
+          if (first_int_addr)
+            {
+              e_addr.as_u32 = first_int_addr->as_u32;
+              /* Identity mapping? */
+              if (l_addr.as_u32 == 0)
+                l_addr.as_u32 = e_addr.as_u32;
+            }
+          else
+            return 0;
         }
     }
 
@@ -767,7 +817,7 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
                 }
             }
           /* External address must be allocated */
-          if (!a)
+          if (!a && (l_addr.as_u32 != e_addr.as_u32))
             return VNET_API_ERROR_NO_SUCH_ENTRY;
         }
 
@@ -831,6 +881,51 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
             clib_warning ("out2in key add failed");
         }
 
+      /* Delete dynamic sessions matching local address (+ local port) */
+      if (!(sm->static_mapping_only))
+        {
+          u_key.addr = m->local_addr;
+          u_key.fib_index = m->fib_index;
+          kv.key = u_key.as_u64;
+          if (!clib_bihash_search_8_8 (&tsm->user_hash, &kv, &value))
+            {
+              user_index = value.value;
+              u = pool_elt_at_index (tsm->users, user_index);
+              if (u->nsessions)
+                {
+                  head_index = u->sessions_per_user_list_head_index;
+                  head = pool_elt_at_index (tsm->list_pool, head_index);
+                  elt_index = head->next;
+                  elt = pool_elt_at_index (tsm->list_pool, elt_index);
+                  ses_index = elt->value;
+                  while (ses_index != ~0)
+                    {
+                      s =  pool_elt_at_index (tsm->sessions, ses_index);
+                      elt = pool_elt_at_index (tsm->list_pool, elt->next);
+                      ses_index = elt->value;
+
+                      if (snat_is_session_static (s))
+                        continue;
+
+                      if (!addr_only)
+                        {
+                          if ((s->out2in.addr.as_u32 != e_addr.as_u32) &&
+                              (clib_net_to_host_u16 (s->out2in.port) != e_port))
+                            continue;
+                        }
+
+                      nat_free_session_data (sm, s, tsm - sm->per_thread_data);
+                      clib_dlist_remove (tsm->list_pool, s->per_user_index);
+                      pool_put_index (tsm->list_pool, s->per_user_index);
+                      pool_put (tsm->sessions, s);
+                      u->nsessions--;
+
+                      if (!addr_only)
+                        break;
+                    }
+                }
+            }
+        }
     }
   else
     {
@@ -906,14 +1001,6 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
       if (!(sm->static_mapping_only) ||
           (sm->static_mapping_only && sm->static_mapping_connection_tracking))
         {
-          snat_user_key_t u_key;
-          snat_user_t *u;
-          dlist_elt_t * head, * elt;
-          u32 elt_index, head_index;
-          u32 ses_index;
-          u64 user_index;
-          snat_session_t * s;
-
           u_key.addr = m->local_addr;
           u_key.fib_index = m->fib_index;
           kv.key = u_key.as_u64;
@@ -988,6 +1075,33 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
   return 0;
 }
 
+static int lb_local_exists (nat44_lb_addr_port_t * local,
+                            ip4_address_t * e_addr, u16 e_port)
+{
+  snat_main_t *sm = &snat_main;
+  snat_static_mapping_t *m;
+  nat44_lb_addr_port_t *ap;
+
+  /* *INDENT-OFF* */
+  pool_foreach (m, sm->static_mappings,
+  ({
+      if (vec_len(m->locals))
+        {
+          if (m->external_port == e_port && m->external_addr.as_u32 == e_addr->as_u32)
+            continue;
+
+          vec_foreach (ap, m->locals)
+          {
+            if (ap->port == local->port && ap->addr.as_u32 == local->addr.as_u32)
+              return 1;
+          }
+        }
+  }));
+  /* *INDENT-ON* */
+
+  return 0;
+}
+
 int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                                      snat_protocol_t proto, u32 vrf_id,
                                      nat44_lb_addr_port_t *locals, u8 is_add,
@@ -1208,12 +1322,15 @@ int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                 }
             }
 
-          m_key.port = clib_host_to_net_u16 (local->port);
-          kv.key = m_key.as_u64;
-          if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 0))
+          if (!lb_local_exists(local, &e_addr, e_port))
             {
-              clib_warning ("in2out key del failed");
-              return VNET_API_ERROR_UNSPECIFIED;
+              m_key.port = clib_host_to_net_u16 (local->port);
+              kv.key = m_key.as_u64;
+              if (clib_bihash_add_del_8_8(&tsm->in2out, &kv, 0))
+                {
+                  clib_warning ("in2out key del failed");
+                  return VNET_API_ERROR_UNSPECIFIED;
+                }
             }
           /* Delete sessions */
           u_key.addr = local->addr;
@@ -1383,6 +1500,12 @@ int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
   if (sm->out2in_dpo && !is_inside)
     return VNET_API_ERROR_UNSUPPORTED;
 
+  pool_foreach (i, sm->output_feature_interfaces,
+  ({
+    if (i->sw_if_index == sw_if_index)
+      return VNET_API_ERROR_VALUE_EXIST;
+  }));
+
   if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking))
     feature_name = is_inside ?  "nat44-in2out-fast" : "nat44-out2in-fast";
   else
@@ -1534,6 +1657,12 @@ int snat_interface_add_del_output_feature (u32 sw_if_index,
       (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)))
     return VNET_API_ERROR_UNSUPPORTED;
 
+  pool_foreach (i, sm->interfaces,
+  ({
+    if (i->sw_if_index == sw_if_index)
+      return VNET_API_ERROR_VALUE_EXIST;
+  }));
+
   if (is_inside)
     {
       vnet_feature_enable_disable ("ip4-unicast", "nat44-hairpin-dst",
@@ -1735,6 +1864,8 @@ static clib_error_t * snat_init (vlib_main_t * vm)
 
   dslite_init(vm);
 
+  nat66_init();
+
   /* Init virtual fragmenentation reassembly */
   return nat_reass_init(vm);
 }
@@ -2560,16 +2691,14 @@ u8 * format_snat_static_map_to_resolve (u8 * s, va_list * args)
   if (m->addr_only)
       s = format (s, "local %U external %U vrf %d",
                   format_ip4_address, &m->l_addr,
-                  format_vnet_sw_interface_name, vnm,
-                  vnet_get_sw_interface (vnm, m->sw_if_index),
+                  format_vnet_sw_if_index_name, vnm, m->sw_if_index,
                   m->vrf_id);
   else
       s = format (s, "%U local %U:%d external %U:%d vrf %d",
                   format_snat_protocol, m->proto,
                   format_ip4_address, &m->l_addr, m->l_port,
-                  format_vnet_sw_interface_name, vnm,
-                  vnet_get_sw_interface (vnm, m->sw_if_index), m->e_port,
-                  m->vrf_id);
+                  format_vnet_sw_if_index_name, vnm, m->sw_if_index,
+                  m->e_port, m->vrf_id);
 
   return s;
 }
@@ -2614,7 +2743,6 @@ snat_ip4_add_del_interface_address_cb (ip4_main_t * im,
 {
   snat_main_t *sm = &snat_main;
   snat_static_map_resolve_t *rp;
-  u32 *indices_to_delete = 0;
   ip4_address_t l_addr;
   int i, j;
   int rv;
@@ -2672,18 +2800,8 @@ match:
               if (rv)
                 clib_warning ("snat_add_static_mapping returned %d",
                               rv);
-              vec_free (rp->tag);
-              vec_add1 (indices_to_delete, j);
             }
         }
-      /* If we resolved any of the outstanding static mappings */
-      if (vec_len(indices_to_delete))
-        {
-          /* Delete them */
-          for (j = vec_len(indices_to_delete)-1; j >= 0; j--)
-            vec_delete(sm->to_resolve, 1, j);
-          vec_free(indices_to_delete);
-        }
       return;
     }
   else
@@ -2775,7 +2893,7 @@ nat44_del_session (snat_main_t *sm, ip4_address_t *addr, u16 port,
   snat_user_t *u;
 
   ip.dst_address.as_u32 = ip.src_address.as_u32 = addr->as_u32;
-  if (sm->num_workers)
+  if (sm->num_workers > 1)
     tsm =
       vec_elt_at_index (sm->per_thread_data,
                        sm->worker_in2out_cb (&ip, fib_index));