cnat: Add support for SNat ICMP 88/28788/4
authorNathan Skrzypczak <nathan.skrzypczak@gmail.com>
Thu, 10 Sep 2020 15:44:41 +0000 (17:44 +0200)
committerDave Barach <openvpp@barachs.net>
Fri, 25 Sep 2020 19:55:39 +0000 (19:55 +0000)
Type: feature

snat supports :
* echo request/reply by allocating an identifier
when translating echo requests
* icmp errors in the same manner as dnat

Change-Id: I684e983b0181f95c5eace5a984d40084e5625fa4
Signed-off-by: Nathan Skrzypczak <nathan.skrzypczak@gmail.com>
src/plugins/cnat/cnat_node.h
src/plugins/cnat/cnat_node_snat.c
src/plugins/cnat/cnat_node_vip.c
src/plugins/cnat/cnat_session.c
src/plugins/cnat/cnat_types.c
src/plugins/cnat/cnat_types.h
src/plugins/cnat/test/test_cnat.py

index a396796..2e3b0e0 100644 (file)
@@ -42,6 +42,30 @@ icmp_type_is_error_message (u8 icmp_type)
   return 0;
 }
 
+static_always_inline u8
+icmp_type_is_echo (u8 icmp_type)
+{
+  switch (icmp_type)
+    {
+    case ICMP4_echo_request:
+    case ICMP4_echo_reply:
+      return 1;
+    }
+  return 0;
+}
+
+static_always_inline u8
+icmp6_type_is_echo (u8 icmp_type)
+{
+  switch (icmp_type)
+    {
+    case ICMP6_echo_request:
+    case ICMP6_echo_reply:
+      return 1;
+    }
+  return 0;
+}
+
 static_always_inline u8
 icmp6_type_is_error_message (u8 icmp_type)
 {
@@ -170,20 +194,39 @@ cnat_tcp_update_session_lifetime (tcp_header_t * tcp, u32 index)
 }
 
 static_always_inline void
-cnat_translation_icmp4 (ip4_header_t * outer_ip4, udp_header_t * outer_udp,
-                       ip4_address_t outer_new_addr[VLIB_N_DIR],
-                       u16 outer_new_port[VLIB_N_DIR], u8 snat_outer_ip)
+cnat_translation_icmp4_echo (ip4_header_t * ip4, icmp46_header_t * icmp,
+                            ip4_address_t new_addr[VLIB_N_DIR],
+                            u16 new_port[VLIB_N_DIR])
+{
+  ip_csum_t sum;
+  u16 old_port;
+  cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1);
+
+  cnat_ip4_translate_l3 (ip4, new_addr);
+  old_port = echo->identifier;
+  echo->identifier = new_port[VLIB_RX];
+
+  sum = icmp->checksum;
+  sum = ip_csum_update (sum, old_port, new_port[VLIB_RX],
+                       ip4_header_t /* cheat */ ,
+                       length /* changed member */ );
+
+  icmp->checksum = ip_csum_fold (sum);
+}
+
+static_always_inline void
+cnat_translation_icmp4_error (ip4_header_t * outer_ip4,
+                             icmp46_header_t * icmp,
+                             ip4_address_t outer_new_addr[VLIB_N_DIR],
+                             u16 outer_new_port[VLIB_N_DIR],
+                             u8 snat_outer_ip)
 {
-  icmp46_header_t *icmp = (icmp46_header_t *) outer_udp;
   ip4_address_t new_addr[VLIB_N_DIR];
   ip4_address_t old_addr[VLIB_N_DIR];
   u16 new_port[VLIB_N_DIR];
   u16 old_port[VLIB_N_DIR];
   ip_csum_t sum, old_ip_sum, inner_l4_sum, inner_l4_old_sum;
 
-  if (!icmp_type_is_error_message (icmp->type))
-    return;
-
   ip4_header_t *ip4 = (ip4_header_t *) (icmp + 2);
   udp_header_t *udp = (udp_header_t *) (ip4 + 1);
   tcp_header_t *tcp = (tcp_header_t *) udp;
@@ -287,10 +330,18 @@ cnat_translation_ip4 (const cnat_session_t * session,
     }
   else if (ip4->protocol == IP_PROTOCOL_ICMP)
     {
-      /* SNAT only if src_addr was translated */
-      u8 snat_outer_ip =
-       (ip4->src_address.as_u32 == session->key.cs_ip[VLIB_RX].ip4.as_u32);
-      cnat_translation_icmp4 (ip4, udp, new_addr, new_port, snat_outer_ip);
+      icmp46_header_t *icmp = (icmp46_header_t *) udp;
+      if (icmp_type_is_error_message (icmp->type))
+       {
+         /* SNAT only if src_addr was translated */
+         u8 snat_outer_ip =
+           (ip4->src_address.as_u32 ==
+            session->key.cs_ip[VLIB_RX].ip4.as_u32);
+         cnat_translation_icmp4_error (ip4, icmp, new_addr, new_port,
+                                       snat_outer_ip);
+       }
+      else if (icmp_type_is_echo (icmp->type))
+       cnat_translation_icmp4_echo (ip4, icmp, new_addr, new_port);
     }
 }
 
@@ -358,11 +409,52 @@ cnat_ip6_translate_l4 (ip6_header_t * ip6, udp_header_t * udp,
 }
 
 static_always_inline void
-cnat_translation_icmp6 (ip6_header_t * outer_ip6, udp_header_t * outer_udp,
-                       ip6_address_t outer_new_addr[VLIB_N_DIR],
-                       u16 outer_new_port[VLIB_N_DIR], u8 snat_outer_ip)
+cnat_translation_icmp6_echo (ip6_header_t * ip6, icmp46_header_t * icmp,
+                            ip6_address_t new_addr[VLIB_N_DIR],
+                            u16 new_port[VLIB_N_DIR])
+{
+  cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1);
+  ip6_address_t old_addr[VLIB_N_DIR];
+  ip_csum_t sum;
+  u16 old_port;
+  old_port = echo->identifier;
+  ip6_address_copy (&old_addr[VLIB_TX], &ip6->dst_address);
+  ip6_address_copy (&old_addr[VLIB_RX], &ip6->src_address);
+
+  sum = icmp->checksum;
+
+  cnat_ip6_translate_l3 (ip6, new_addr);
+  if (has_ip6_address (&new_addr[VLIB_TX]))
+    {
+      sum = ip_csum_add_even (sum, new_addr[VLIB_TX].as_u64[0]);
+      sum = ip_csum_add_even (sum, new_addr[VLIB_TX].as_u64[1]);
+      sum = ip_csum_sub_even (sum, old_addr[VLIB_TX].as_u64[0]);
+      sum = ip_csum_sub_even (sum, old_addr[VLIB_TX].as_u64[1]);
+    }
+
+  if (has_ip6_address (&new_addr[VLIB_RX]))
+    {
+      sum = ip_csum_add_even (sum, new_addr[VLIB_RX].as_u64[0]);
+      sum = ip_csum_add_even (sum, new_addr[VLIB_RX].as_u64[1]);
+      sum = ip_csum_sub_even (sum, old_addr[VLIB_RX].as_u64[0]);
+      sum = ip_csum_sub_even (sum, old_addr[VLIB_RX].as_u64[1]);
+    }
+
+  echo->identifier = new_port[VLIB_RX];
+  sum = ip_csum_update (sum, old_port, new_port[VLIB_RX],
+                       ip4_header_t /* cheat */ ,
+                       length /* changed member */ );
+
+  icmp->checksum = ip_csum_fold (sum);
+}
+
+static_always_inline void
+cnat_translation_icmp6_error (ip6_header_t * outer_ip6,
+                             icmp46_header_t * icmp,
+                             ip6_address_t outer_new_addr[VLIB_N_DIR],
+                             u16 outer_new_port[VLIB_N_DIR],
+                             u8 snat_outer_ip)
 {
-  icmp46_header_t *icmp = (icmp46_header_t *) outer_udp;
   ip6_address_t new_addr[VLIB_N_DIR];
   ip6_address_t old_addr[VLIB_N_DIR];
   ip6_address_t outer_old_addr[VLIB_N_DIR];
@@ -411,6 +503,7 @@ cnat_translation_icmp6 (ip6_header_t * outer_ip6, udp_header_t * outer_udp,
       sum = ip_csum_sub_even (sum, outer_old_addr[VLIB_RX].as_u64[1]);
     }
 
+  /* Translate inner TCP / UDP */
   if (ip6->protocol == IP_PROTOCOL_TCP)
     {
       inner_l4_old_sum = inner_l4_sum = tcp->checksum;
@@ -494,10 +587,18 @@ cnat_translation_ip6 (const cnat_session_t * session,
     }
   else if (ip6->protocol == IP_PROTOCOL_ICMP6)
     {
-      /* SNAT only if src_addr was translated */
-      u8 snat_outer_ip = cmp_ip6_address (&ip6->src_address,
-                                         &session->key.cs_ip[VLIB_RX].ip6);
-      cnat_translation_icmp6 (ip6, udp, new_addr, new_port, snat_outer_ip);
+      icmp46_header_t *icmp = (icmp46_header_t *) udp;
+      if (icmp6_type_is_error_message (icmp->type))
+       {
+         /* SNAT only if src_addr was translated */
+         u8 snat_outer_ip = cmp_ip6_address (&ip6->src_address,
+                                             &session->key.
+                                             cs_ip[VLIB_RX].ip6);
+         cnat_translation_icmp6_error (ip6, icmp, new_addr, new_port,
+                                       snat_outer_ip);
+       }
+      else if (icmp6_type_is_echo (icmp->type))
+       cnat_translation_icmp6_echo (ip6, icmp, new_addr, new_port);
     }
 }
 
@@ -517,18 +618,32 @@ cnat_session_make_key (vlib_buffer_t * b, ip_address_family_t af,
       if (PREDICT_FALSE (ip4->protocol == IP_PROTOCOL_ICMP))
        {
          icmp46_header_t *icmp = (icmp46_header_t *) (ip4 + 1);
-         if (!icmp_type_is_error_message (icmp->type))
+         if (icmp_type_is_error_message (icmp->type))
+           {
+             ip4 = (ip4_header_t *) (icmp + 2);        /* Use inner packet */
+             udp = (udp_header_t *) (ip4 + 1);
+             /* Swap dst & src for search as ICMP payload is reversed */
+             ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX],
+                                   &ip4->dst_address);
+             ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX],
+                                   &ip4->src_address);
+             session->key.cs_proto = ip4->protocol;
+             session->key.cs_port[VLIB_TX] = udp->src_port;
+             session->key.cs_port[VLIB_RX] = udp->dst_port;
+           }
+         else if (icmp_type_is_echo (icmp->type))
+           {
+             cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1);
+             ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX],
+                                   &ip4->dst_address);
+             ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX],
+                                   &ip4->src_address);
+             session->key.cs_proto = ip4->protocol;
+             session->key.cs_port[VLIB_TX] = echo->identifier;
+             session->key.cs_port[VLIB_RX] = echo->identifier;
+           }
+         else
            goto error;
-         ip4 = (ip4_header_t *) (icmp + 2);    /* Use inner packet */
-         udp = (udp_header_t *) (ip4 + 1);
-         /* Swap dst & src for search as ICMP payload is reversed */
-         ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX],
-                               &ip4->dst_address);
-         ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX],
-                               &ip4->src_address);
-         session->key.cs_proto = ip4->protocol;
-         session->key.cs_port[VLIB_TX] = udp->src_port;
-         session->key.cs_port[VLIB_RX] = udp->dst_port;
        }
       else
        {
@@ -550,18 +665,32 @@ cnat_session_make_key (vlib_buffer_t * b, ip_address_family_t af,
       if (PREDICT_FALSE (ip6->protocol == IP_PROTOCOL_ICMP6))
        {
          icmp46_header_t *icmp = (icmp46_header_t *) (ip6 + 1);
-         if (!icmp6_type_is_error_message (icmp->type))
+         if (icmp6_type_is_error_message (icmp->type))
+           {
+             ip6 = (ip6_header_t *) (icmp + 2);        /* Use inner packet */
+             udp = (udp_header_t *) (ip6 + 1);
+             /* Swap dst & src for search as ICMP payload is reversed */
+             ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX],
+                                   &ip6->dst_address);
+             ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX],
+                                   &ip6->src_address);
+             session->key.cs_proto = ip6->protocol;
+             session->key.cs_port[VLIB_TX] = udp->src_port;
+             session->key.cs_port[VLIB_RX] = udp->dst_port;
+           }
+         else if (icmp6_type_is_echo (icmp->type))
+           {
+             cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1);
+             ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX],
+                                   &ip6->dst_address);
+             ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX],
+                                   &ip6->src_address);
+             session->key.cs_proto = ip6->protocol;
+             session->key.cs_port[VLIB_TX] = echo->identifier;
+             session->key.cs_port[VLIB_RX] = echo->identifier;
+           }
+         else
            goto error;
-         ip6 = (ip6_header_t *) (icmp + 2);    /* Use inner packet */
-         udp = (udp_header_t *) (ip6 + 1);
-         /* Swap dst & src for search as ICMP payload is reversed */
-         ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX],
-                               &ip6->dst_address);
-         ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX],
-                               &ip6->src_address);
-         session->key.cs_proto = ip6->protocol;
-         session->key.cs_port[VLIB_TX] = udp->src_port;
-         session->key.cs_port[VLIB_RX] = udp->dst_port;
        }
       else
        {
index aaa9e16..d6c49cf 100644 (file)
@@ -25,8 +25,9 @@ typedef enum cnat_snat_next_
 
 typedef struct cnat_snat_trace_
 {
-  u32 found;
   cnat_session_t session;
+  u32 found_session;
+  u32 created_session;
 } cnat_snat_trace_t;
 
 vlib_node_registration_t cnat_snat_ip4_node;
@@ -39,8 +40,11 @@ format_cnat_snat_trace (u8 * s, va_list * args)
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
   cnat_snat_trace_t *t = va_arg (*args, cnat_snat_trace_t *);
 
-  if (t->found)
+  if (t->found_session)
     s = format (s, "found: %U", format_cnat_session, &t->session, 1);
+  else if (t->created_session)
+    s = format (s, "created: %U\n  tr: %U",
+               format_cnat_session, &t->session, 1);
   else
     s = format (s, "not found");
   return s;
@@ -56,6 +60,7 @@ cnat_snat_inline (vlib_main_t * vm,
                  cnat_node_ctx_t * ctx, int rv, cnat_session_t * session)
 {
   cnat_main_t *cm = &cnat_main;
+  int created_session = 0;
   ip4_header_t *ip4;
   ip_protocol_t iproto;
   ip6_header_t *ip6;
@@ -126,10 +131,9 @@ cnat_snat_inline (vlib_main_t * vm,
                                &ip6->dst_address);
        }
 
-      /* Port allocation, first try to use the original port, allocate one
-         if it is already used */
-      sport = udp0->src_port;
-      rv = cnat_allocate_port (cm, &sport);
+
+      sport = 0;
+      rv = cnat_allocate_port (&sport, iproto);
       if (rv)
        {
          vlib_node_increment_counter (vm, cnat_snat_ip4_node.index,
@@ -137,13 +141,16 @@ cnat_snat_inline (vlib_main_t * vm,
          next0 = CNAT_SNAT_NEXT_DROP;
          goto trace;
        }
-
       session->value.cs_port[VLIB_RX] = sport;
-      session->value.cs_port[VLIB_TX] = udp0->dst_port;
+      session->value.cs_port[VLIB_TX] = sport;
+      if (iproto == IP_PROTOCOL_TCP || iproto == IP_PROTOCOL_UDP)
+       session->value.cs_port[VLIB_TX] = udp0->dst_port;
+
       session->value.cs_lbi = INDEX_INVALID;
       session->value.flags =
        CNAT_SESSION_FLAG_NO_CLIENT | CNAT_SESSION_FLAG_ALLOC_PORT;
 
+      created_session = 1;
       cnat_session_create (session, ctx, CNAT_SESSION_FLAG_HAS_SNAT);
     }
 
@@ -160,7 +167,9 @@ trace:
 
       t = vlib_add_trace (vm, node, b, sizeof (*t));
 
-      if (NULL != session)
+      t->found_session = !rv;
+      t->created_session = created_session;
+      if (t->found_session || t->created_session)
        clib_memcpy (&t->session, session, sizeof (t->session));
     }
   return next0;
index 10f228f..d041606 100644 (file)
@@ -70,7 +70,6 @@ cnat_vip_inline (vlib_main_t * vm,
                   cnat_node_ctx_t * ctx, int rv, cnat_session_t * session)
 {
   vlib_combined_counter_main_t *cntm = &cnat_translation_counters;
-  cnat_main_t *cm = &cnat_main;
   const cnat_translation_t *ct = NULL;
   ip4_header_t *ip4 = NULL;
   ip_protocol_t iproto;
@@ -201,7 +200,7 @@ cnat_vip_inline (vlib_main_t * vm,
               && (rsession_flags & CNAT_SESSION_FLAG_HAS_SNAT)) {
            sport = 0; /* force allocation */
            session->value.flags |= CNAT_SESSION_FLAG_ALLOC_PORT;
-           rv = cnat_allocate_port (cm, &sport);
+           rv = cnat_allocate_port (&sport, iproto);
            if (rv)
              {
                vlib_node_increment_counter (vm, cnat_vip_ip4_node.index,
index 7f95e1b..4259f42 100644 (file)
@@ -128,7 +128,7 @@ cnat_session_free (cnat_session_t * session)
   clib_bihash_kv_40_48_t *bkey = (clib_bihash_kv_40_48_t *) session;
   /* age it */
   if (session->value.flags & CNAT_SESSION_FLAG_ALLOC_PORT)
-    cnat_free_port (session->value.cs_port[VLIB_RX]);
+    cnat_free_port (session->value.cs_port[VLIB_RX], session->key.cs_proto);
   if (!(session->value.flags & CNAT_SESSION_FLAG_NO_CLIENT))
     cnat_client_free_by_ip (&session->key.cs_ip[VLIB_TX], session->key.cs_af);
   cnat_timestamp_free (session->value.cs_ts_index);
index ae485a4..9db953f 100644 (file)
@@ -80,9 +80,14 @@ cnat_types_init (vlib_main_t * vm)
                                         CNAT_FIB_SOURCE_PRIORITY,
                                         FIB_SOURCE_BH_SIMPLE);
 
+
   clib_rwlock_init (&cnat_main.ts_lock);
-  clib_spinlock_init (&cnat_main.src_ports_lock);
-  clib_bitmap_validate (cnat_main.src_ports, UINT16_MAX);
+  vec_validate (cnat_main.src_ports, CNAT_N_SPORT_PROTO);
+  for (int i = 0; i < CNAT_N_SPORT_PROTO; i++)
+    {
+      clib_spinlock_init (&cnat_main.src_ports[i].lock);
+      clib_bitmap_validate (cnat_main.src_ports[i].bmap, UINT16_MAX);
+    }
   throttle_init (&cnat_throttle, n_vlib_mains, 1e-3);
 
   return (NULL);
index ab59aaf..c9c0b70 100644 (file)
 
 #define MIN_SRC_PORT ((u16) 0xC000)
 
+typedef enum
+{
+  CNAT_SPORT_PROTO_TCP,
+  CNAT_SPORT_PROTO_UDP,
+  CNAT_SPORT_PROTO_ICMP,
+  CNAT_SPORT_PROTO_ICMP6,
+  CNAT_N_SPORT_PROTO
+} cnat_sport_proto_t;
+
 typedef struct cnat_endpoint_t_
 {
   ip_address_t ce_ip;
@@ -61,7 +70,11 @@ typedef struct cnat_endpoint_tuple_t_
   cnat_endpoint_t src_ep;
 } cnat_endpoint_tuple_t;
 
-
+typedef struct
+{
+  u16 identifier;
+  u16 sequence;
+} cnat_echo_header_t;
 
 typedef struct
 {
@@ -80,6 +93,15 @@ typedef struct
   ip6_address_t ip_masks[129];
 } cnat_snat_pfx_table_t;
 
+typedef struct cnat_src_port_allocator_
+{
+  /* Source ports bitmap for snat */
+  clib_bitmap_t *bmap;
+
+  /* Lock for src_ports access */
+  clib_spinlock_t lock;
+} cnat_src_port_allocator_t;
+
 typedef struct cnat_main_
 {
   /* Memory size of the session bihash */
@@ -113,11 +135,8 @@ typedef struct cnat_main_
   /* Lock for the timestamp pool */
   clib_rwlock_t ts_lock;
 
-  /* Source ports bitmap for snat */
-  clib_bitmap_t *src_ports;
-
-  /* Lock for src_ports access */
-  clib_spinlock_t src_ports_lock;
+  /* Per proto source ports allocator for snat */
+  cnat_src_port_allocator_t *src_ports;
 
   /* Ip4 Address to use for source NATing */
   ip4_address_t snat_ip4;
@@ -265,33 +284,59 @@ cnat_timestamp_free (u32 index)
   clib_rwlock_writer_unlock (&cnat_main.ts_lock);
 }
 
-always_inline void
-cnat_free_port (u16 port)
+always_inline cnat_src_port_allocator_t *
+cnat_get_src_port_allocator (ip_protocol_t iproto)
 {
   cnat_main_t *cm = &cnat_main;
-  clib_spinlock_lock (&cm->src_ports_lock);
-  clib_bitmap_set_no_check (cm->src_ports, port, 0);
-  clib_spinlock_unlock (&cm->src_ports_lock);
+  switch (iproto)
+    {
+    case IP_PROTOCOL_TCP:
+      return &cm->src_ports[CNAT_SPORT_PROTO_TCP];
+    case IP_PROTOCOL_UDP:
+      return &cm->src_ports[CNAT_SPORT_PROTO_UDP];
+    case IP_PROTOCOL_ICMP:
+      return &cm->src_ports[CNAT_SPORT_PROTO_ICMP];
+    case IP_PROTOCOL_ICMP6:
+      return &cm->src_ports[CNAT_SPORT_PROTO_ICMP6];
+    default:
+      return 0;
+    }
+}
+
+always_inline void
+cnat_free_port (u16 port, ip_protocol_t iproto)
+{
+  cnat_src_port_allocator_t *ca;
+  ca = cnat_get_src_port_allocator (iproto);
+  if (!ca)
+    return;
+  clib_spinlock_lock (&ca->lock);
+  clib_bitmap_set_no_check (ca->bmap, port, 0);
+  clib_spinlock_unlock (&ca->lock);
 }
 
 always_inline int
-cnat_allocate_port (cnat_main_t * cm, u16 * port)
+cnat_allocate_port (u16 * port, ip_protocol_t iproto)
 {
   *port = clib_net_to_host_u16 (*port);
   if (*port == 0)
     *port = MIN_SRC_PORT;
-  clib_spinlock_lock (&cm->src_ports_lock);
-  if (clib_bitmap_get_no_check (cm->src_ports, *port))
+  cnat_src_port_allocator_t *ca;
+  ca = cnat_get_src_port_allocator (iproto);
+  if (!ca)
+    return -1;
+  clib_spinlock_lock (&ca->lock);
+  if (clib_bitmap_get_no_check (ca->bmap, *port))
     {
-      *port = clib_bitmap_next_clear (cm->src_ports, *port);
+      *port = clib_bitmap_next_clear (ca->bmap, *port);
       if (PREDICT_FALSE (*port >= UINT16_MAX))
-       *port = clib_bitmap_next_clear (cm->src_ports, MIN_SRC_PORT);
+       *port = clib_bitmap_next_clear (ca->bmap, MIN_SRC_PORT);
       if (PREDICT_FALSE (*port >= UINT16_MAX))
        return -1;
     }
-  clib_bitmap_set_no_check (cm->src_ports, *port, 1);
+  clib_bitmap_set_no_check (ca->bmap, *port, 1);
   *port = clib_host_to_net_u16 (*port);
-  clib_spinlock_unlock (&cm->src_ports_lock);
+  clib_spinlock_unlock (&ca->lock);
   return 0;
 }
 
index 34cd8b5..3f8d33c 100644 (file)
@@ -10,6 +10,7 @@ from scapy.layers.l2 import Ether
 from scapy.layers.inet import IP, UDP, TCP, ICMP
 from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
 from scapy.layers.inet6 import IPv6, IPerror6, ICMPv6DestUnreach
+from scapy.layers.inet6 import ICMPv6EchoRequest, ICMPv6EchoReply
 
 import struct
 
@@ -123,34 +124,6 @@ class VppCNatTranslation(VppObject):
         return c[0][self.id]
 
 
-class VppCNATSourceNat(VppObject):
-
-    def __init__(self, test, address, exclude_subnets=[]):
-        self._test = test
-        self.address = address
-        self.exclude_subnets = exclude_subnets
-
-    def add_vpp_config(self):
-        a = ip_address(self.address)
-        if 4 == a.version:
-            self._test.vapi.cnat_set_snat_addresses(snat_ip4=self.address)
-        else:
-            self._test.vapi.cnat_set_snat_addresses(snat_ip6=self.address)
-        for subnet in self.exclude_subnets:
-            self.cnat_exclude_subnet(subnet, True)
-
-    def cnat_exclude_subnet(self, exclude_subnet, isAdd=True):
-        add = 1 if isAdd else 0
-        self._test.vapi.cnat_add_del_snat_prefix(
-            prefix=exclude_subnet, is_add=add)
-
-    def query_vpp_config(self):
-        return False
-
-    def remove_vpp_config(self):
-        return False
-
-
 class TestCNatTranslation(VppTestCase):
     """ CNat Translation """
     extra_vpp_punt_config = ["cnat", "{",
@@ -568,49 +541,219 @@ class TestCNatSourceNAT(VppTestCase):
             i.config_ip6()
             i.resolve_ndp()
 
+        self.pg0.configure_ipv6_neighbors()
+        self.pg0.configure_ipv4_neighbors()
+        self.pg1.generate_remote_hosts(2)
+        self.pg1.configure_ipv4_neighbors()
+        self.pg1.configure_ipv6_neighbors()
+
+        self.vapi.cli("test cnat scanner off")
+        self.vapi.cnat_set_snat_addresses(
+            snat_ip4=self.pg2.remote_hosts[0].ip4,
+            snat_ip6=self.pg2.remote_hosts[0].ip6)
+        self.vapi.feature_enable_disable(
+            enable=1,
+            arc_name="ip6-unicast",
+            feature_name="ip6-cnat-snat",
+            sw_if_index=self.pg0.sw_if_index)
+        self.vapi.feature_enable_disable(
+            enable=1,
+            arc_name="ip4-unicast",
+            feature_name="ip4-cnat-snat",
+            sw_if_index=self.pg0.sw_if_index)
+
     def tearDown(self):
+        self.vapi.cnat_session_purge()
         for i in self.pg_interfaces:
             i.unconfig_ip4()
             i.unconfig_ip6()
             i.admin_down()
         super(TestCNatSourceNAT, self).tearDown()
 
-    def cnat_set_snat_address(self, srcNatAddr, interface, isV6=False):
-        t1 = VppCNATSourceNat(self, srcNatAddr)
-        t1.add_vpp_config()
-        cnat_arc_name = "ip6-unicast" if isV6 else "ip4-unicast"
-        cnat_feature_name = "ip6-cnat-snat" if isV6 else "ip4-cnat-snat"
-        self.vapi.feature_enable_disable(
-            enable=1,
-            arc_name=cnat_arc_name,
-            feature_name=cnat_feature_name,
-            sw_if_index=interface.sw_if_index)
+    def test_snat_v6(self):
+        # """ CNat Source Nat v6 """
+        self.sourcenat_test_tcp_udp_conf(TCP, isV6=True)
+        self.sourcenat_test_tcp_udp_conf(UDP, isV6=True)
+        self.sourcenat_test_icmp_err_conf(isV6=True)
+        self.sourcenat_test_icmp_echo6_conf()
 
-        return t1
+    def test_snat_v4(self):
+        # """ CNat Source Nat v4 """
+        self.sourcenat_test_tcp_udp_conf(TCP)
+        self.sourcenat_test_tcp_udp_conf(UDP)
+        self.sourcenat_test_icmp_err_conf()
+        self.sourcenat_test_icmp_echo4_conf()
 
-    def cnat_test_sourcenat(self, srcNatAddr, l4p=TCP, isV6=False):
-        ip_v = "ip6" if isV6 else "ip4"
-        IP46 = IPv6 if isV6 else IP
-        sports = [1234, 1235, 1236]
-        dports = [6661, 6662, 6663]
+    def sourcenat_test_icmp_echo6_conf(self):
+        sports = [1234, 1235]
+        dports = [6661, 6662]
 
-        self.pg0.generate_remote_hosts(1)
-        self.pg0.configure_ipv4_neighbors()
-        self.pg0.configure_ipv6_neighbors()
-        self.pg1.generate_remote_hosts(len(sports))
-        self.pg1.configure_ipv4_neighbors()
-        self.pg1.configure_ipv6_neighbors()
+        for nbr, remote_host in enumerate(self.pg1.remote_hosts):
+            client_addr = self.pg0.remote_hosts[0].ip6
+            remote_addr = self.pg1.remote_hosts[nbr].ip6
+            src_nat_addr = self.pg2.remote_hosts[0].ip6
 
-        self.vapi.cli("test cnat scanner on")
-        t1 = self.cnat_set_snat_address(srcNatAddr, self.pg0, isV6)
+            # ping from pods to outside network
+            p1 = (
+                Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_hosts[0].mac) /
+                IPv6(src=client_addr, dst=remote_addr) /
+                ICMPv6EchoRequest(id=0xfeed) /
+                Raw())
+
+            rxs = self.send_and_expect(
+                self.pg0,
+                p1 * N_PKTS,
+                self.pg1)
+
+            for rx in rxs:
+                self.assertEqual(rx[IPv6].src, src_nat_addr)
+                self.assert_packet_checksums_valid(rx)
+
+            received_id = rx[0][ICMPv6EchoRequest].id
+            # ping reply from outside to pods
+            p2 = (
+                Ether(dst=self.pg1.local_mac,
+                      src=self.pg1.remote_hosts[nbr].mac) /
+                IPv6(src=remote_addr, dst=src_nat_addr) /
+                ICMPv6EchoReply(id=received_id))
+            rxs = self.send_and_expect(
+                self.pg1,
+                p2 * N_PKTS,
+                self.pg0)
+
+            for rx in rxs:
+                self.assert_packet_checksums_valid(rx)
+                self.assertEqual(rx[IPv6].src, remote_addr)
+                self.assertEqual(rx[ICMPv6EchoReply].id, 0xfeed)
+
+    def sourcenat_test_icmp_echo4_conf(self):
+        sports = [1234, 1235]
+        dports = [6661, 6662]
+
+        for nbr, remote_host in enumerate(self.pg1.remote_hosts):
+            IP46 = IP
+            client_addr = self.pg0.remote_hosts[0].ip4
+            remote_addr = self.pg1.remote_hosts[nbr].ip4
+            src_nat_addr = self.pg2.remote_hosts[0].ip4
+
+            # ping from pods to outside network
+            p1 = (
+                Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_hosts[0].mac) /
+                IP46(src=client_addr, dst=remote_addr) /
+                ICMP(type=8, id=0xfeed) /
+                Raw())
+
+            rxs = self.send_and_expect(
+                self.pg0,
+                p1 * N_PKTS,
+                self.pg1)
+
+            for rx in rxs:
+                self.assertEqual(rx[IP46].src, src_nat_addr)
+                self.assert_packet_checksums_valid(rx)
+
+            received_id = rx[0][ICMP].id
+            # ping reply from outside to pods
+            p2 = (
+                Ether(dst=self.pg1.local_mac,
+                      src=self.pg1.remote_hosts[nbr].mac) /
+                IP46(src=remote_addr, dst=src_nat_addr) /
+                ICMP(type=0, id=received_id))
+            rxs = self.send_and_expect(
+                self.pg1,
+                p2 * N_PKTS,
+                self.pg0)
+
+            for rx in rxs:
+                self.assert_packet_checksums_valid(rx)
+                self.assertEqual(rx[IP46].src, remote_addr)
+                self.assertEqual(rx[ICMP].id, 0xfeed)
+
+    def sourcenat_test_icmp_err_conf(self, isV6=False):
+        sports = [1234, 1235]
+        dports = [6661, 6662]
 
         for nbr, remote_host in enumerate(self.pg1.remote_hosts):
             if isV6:
+                IP46 = IPv6
                 client_addr = self.pg0.remote_hosts[0].ip6
                 remote_addr = self.pg1.remote_hosts[nbr].ip6
+                src_nat_addr = self.pg2.remote_hosts[0].ip6
+                ICMP46 = ICMPv6DestUnreach
+                ICMPelem = ICMPv6DestUnreach(code=1)
+                IP46error = IPerror6
             else:
+                IP46 = IP
                 client_addr = self.pg0.remote_hosts[0].ip4
                 remote_addr = self.pg1.remote_hosts[nbr].ip4
+                src_nat_addr = self.pg2.remote_hosts[0].ip4
+                IP46error = IPerror
+                ICMP46 = ICMP
+                ICMPelem = ICMP(type=11)
+
+            # from pods to outside network
+            p1 = (
+                Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_hosts[0].mac) /
+                IP46(src=client_addr, dst=remote_addr) /
+                TCP(sport=sports[nbr], dport=dports[nbr]) /
+                Raw())
+
+            rxs = self.send_and_expect(
+                self.pg0,
+                p1 * N_PKTS,
+                self.pg1)
+            for rx in rxs:
+                self.assert_packet_checksums_valid(rx)
+                self.assertEqual(rx[IP46].dst, remote_addr)
+                self.assertEqual(rx[TCP].dport, dports[nbr])
+                self.assertEqual(rx[IP46].src, src_nat_addr)
+                sport = rx[TCP].sport
+
+            InnerIP = rxs[0][IP46]
+            # from outside to pods, ICMP error
+            p2 = (
+                Ether(dst=self.pg1.local_mac,
+                      src=self.pg1.remote_hosts[nbr].mac) /
+                IP46(src=remote_addr, dst=src_nat_addr) /
+                ICMPelem / InnerIP)
+
+            rxs = self.send_and_expect(
+                self.pg1,
+                p2 * N_PKTS,
+                self.pg0)
+
+            for rx in rxs:
+                self.assert_packet_checksums_valid(rx)
+                self.assertEqual(rx[IP46].src, remote_addr)
+                self.assertEqual(rx[ICMP46][IP46error].src, client_addr)
+                self.assertEqual(rx[ICMP46][IP46error].dst, remote_addr)
+                self.assertEqual(rx[ICMP46][IP46error]
+                                 [TCPerror].sport, sports[nbr])
+                self.assertEqual(rx[ICMP46][IP46error]
+                                 [TCPerror].dport, dports[nbr])
+
+    def sourcenat_test_tcp_udp_conf(self, l4p, isV6=False):
+        sports = [1234, 1235]
+        dports = [6661, 6662]
+
+        for nbr, remote_host in enumerate(self.pg1.remote_hosts):
+            if isV6:
+                IP46 = IPv6
+                client_addr = self.pg0.remote_hosts[0].ip6
+                remote_addr = self.pg1.remote_hosts[nbr].ip6
+                src_nat_addr = self.pg2.remote_hosts[0].ip6
+                exclude_prefix = ip_network(
+                    "%s/100" % remote_addr, strict=False)
+            else:
+                IP46 = IP
+                client_addr = self.pg0.remote_hosts[0].ip4
+                remote_addr = self.pg1.remote_hosts[nbr].ip4
+                src_nat_addr = self.pg2.remote_hosts[0].ip4
+                exclude_prefix = ip_network(
+                    "%s/16" % remote_addr, strict=False)
             # from pods to outside network
             p1 = (
                 Ether(dst=self.pg0.local_mac,
@@ -627,14 +770,14 @@ class TestCNatSourceNAT(VppTestCase):
                 self.assert_packet_checksums_valid(rx)
                 self.assertEqual(rx[IP46].dst, remote_addr)
                 self.assertEqual(rx[l4p].dport, dports[nbr])
-                self.assertEqual(rx[IP46].src, srcNatAddr)
+                self.assertEqual(rx[IP46].src, src_nat_addr)
                 sport = rx[l4p].sport
 
             # from outside to pods
             p2 = (
                 Ether(dst=self.pg1.local_mac,
                       src=self.pg1.remote_hosts[nbr].mac) /
-                IP46(src=remote_addr, dst=srcNatAddr) /
+                IP46(src=remote_addr, dst=src_nat_addr) /
                 l4p(sport=dports[nbr], dport=sport) /
                 Raw())
 
@@ -651,11 +794,7 @@ class TestCNatSourceNAT(VppTestCase):
                 self.assertEqual(rx[IP46].src, remote_addr)
 
             # add remote host to exclude list
-            subnet_mask = 100 if isV6 else 16
-            subnet = "%s/%d" % (remote_addr, subnet_mask)
-            exclude_subnet = ip_network(subnet, strict=False)
-
-            t1.cnat_exclude_subnet(exclude_subnet)
+            self.vapi.cnat_add_del_snat_prefix(prefix=exclude_prefix, is_add=1)
             self.vapi.cnat_session_purge()
 
             rxs = self.send_and_expect(
@@ -669,7 +808,7 @@ class TestCNatSourceNAT(VppTestCase):
                 self.assertEqual(rx[IP46].src, client_addr)
 
             # remove remote host from exclude list
-            t1.cnat_exclude_subnet(exclude_subnet, isAdd=False)
+            self.vapi.cnat_add_del_snat_prefix(prefix=exclude_prefix, is_add=0)
             self.vapi.cnat_session_purge()
 
             rxs = self.send_and_expect(
@@ -681,17 +820,9 @@ class TestCNatSourceNAT(VppTestCase):
                 self.assert_packet_checksums_valid(rx)
                 self.assertEqual(rx[IP46].dst, remote_addr)
                 self.assertEqual(rx[l4p].dport, dports[nbr])
-                self.assertEqual(rx[IP46].src, srcNatAddr)
+                self.assertEqual(rx[IP46].src, src_nat_addr)
 
-    def test_cnat6_sourcenat(self):
-        # """ CNat Source Nat ipv6 """
-        self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip6, TCP, True)
-        self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip6, UDP, True)
-
-    def test_cnat4_sourcenat(self):
-        # """ CNat Source Nat ipv4 """
-        self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip4, TCP)
-        self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip4, UDP)
+            self.vapi.cnat_session_purge()
 
 
 if __name__ == '__main__':