srv6-ad: Adding support for L2 traffic 55/13355/5
authorFrancois Clad <fclad@cisco.com>
Wed, 4 Jul 2018 13:35:29 +0000 (15:35 +0200)
committerDamjan Marion <dmarion@me.com>
Wed, 22 Aug 2018 20:32:53 +0000 (20:32 +0000)
Change-Id: I3c14555113c2b58f3be629600051beb7f6716cfe
Signed-off-by: Francois Clad <fclad@cisco.com>
src/plugins/srv6-ad/ad.c
src/plugins/srv6-ad/ad.h
src/plugins/srv6-ad/node.c
test/test_srv6_ad.py

index ae7a6c9..64bf66d 100644 (file)
 #include <vpp/app/version.h>
 #include <srv6-ad/ad.h>
 
+#define SID_CREATE_IFACE_FEATURE_ERROR  -1
+#define SID_CREATE_INVALID_IFACE_TYPE   -3
+#define SID_CREATE_INVALID_IFACE_INDEX  -4
+#define SID_CREATE_INVALID_ADJ_INDEX    -5
+
 unsigned char function_name[] = "SRv6-AD-plugin";
 unsigned char keyword_str[] = "End.AD";
 unsigned char def_str[] =
@@ -45,16 +50,22 @@ srv6_ad_localsid_creation_fn (ip6_sr_localsid_t * localsid)
 
   /* Retrieve the adjacency corresponding to the (OIF, next_hop) */
   adj_index_t nh_adj_index = ADJ_INDEX_INVALID;
-  if (ls_mem->ip_version == DA_IP4)
-    nh_adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4,
-                                       VNET_LINK_IP4, &ls_mem->nh_addr,
-                                       ls_mem->sw_if_index_out);
-  else if (ls_mem->ip_version == DA_IP6)
-    nh_adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP6,
-                                       VNET_LINK_IP6, &ls_mem->nh_addr,
-                                       ls_mem->sw_if_index_out);
-  if (nh_adj_index == ADJ_INDEX_INVALID)
-    return -5;
+  if (ls_mem->inner_type != AD_TYPE_L2)
+    {
+      if (ls_mem->inner_type == AD_TYPE_IP4)
+       nh_adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4,
+                                           VNET_LINK_IP4, &ls_mem->nh_addr,
+                                           ls_mem->sw_if_index_out);
+      else if (ls_mem->inner_type == AD_TYPE_IP6)
+       nh_adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP6,
+                                           VNET_LINK_IP6, &ls_mem->nh_addr,
+                                           ls_mem->sw_if_index_out);
+      if (nh_adj_index == ADJ_INDEX_INVALID)
+       {
+         clib_mem_free (ls_mem);
+         return SID_CREATE_INVALID_ADJ_INDEX;
+       }
+    }
 
   ls_mem->nh_adj = nh_adj_index;
 
@@ -64,52 +75,90 @@ srv6_ad_localsid_creation_fn (ip6_sr_localsid_t * localsid)
   /* Sanitise the SW_IF_INDEX */
   if (pool_is_free_index (sm->vnet_main->interface_main.sw_interfaces,
                          ls_mem->sw_if_index_in))
-    return -3;
+    {
+      adj_unlock (ls_mem->nh_adj);
+      clib_mem_free (ls_mem);
+      return SID_CREATE_INVALID_IFACE_INDEX;
+    }
 
   vnet_sw_interface_t *sw = vnet_get_sw_interface (sm->vnet_main,
                                                   ls_mem->sw_if_index_in);
   if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE)
-    return -3;
+    {
+      adj_unlock (ls_mem->nh_adj);
+      clib_mem_free (ls_mem);
+      return SID_CREATE_INVALID_IFACE_TYPE;
+    }
 
-  int ret = -1;
-  if (ls_mem->ip_version == DA_IP4)
+  if (ls_mem->inner_type == AD_TYPE_L2)
     {
-      ret = vnet_feature_enable_disable ("ip4-unicast", "srv6-ad4-rewrite",
-                                        ls_mem->sw_if_index_in, 1, 0, 0);
+      /* Enable End.AD2 rewrite node for this interface */
+      int ret =
+       vnet_feature_enable_disable ("device-input", "srv6-ad2-rewrite",
+                                    ls_mem->sw_if_index_in, 1, 0, 0);
       if (ret != 0)
-       return -1;
+       {
+         clib_mem_free (ls_mem);
+         return SID_CREATE_IFACE_FEATURE_ERROR;
+       }
+
+      /* Set interface in promiscuous mode */
+      vnet_main_t *vnm = vnet_get_main ();
+      ethernet_set_flags (vnm, ls_mem->sw_if_index_in,
+                         ETHERNET_INTERFACE_FLAG_ACCEPT_ALL);
 
-      /* FIB API calls - Recursive route through the BindingSID */
-      if (ls_mem->sw_if_index_in < vec_len (sm->sw_iface_localsid4))
+      /* Associate local SID index to this interface (resize vector if needed) */
+      if (ls_mem->sw_if_index_in >= vec_len (sm->sw_iface_localsid2))
        {
-         sm->sw_iface_localsid4[ls_mem->sw_if_index_in] = localsid_index;
+         vec_resize (sm->sw_iface_localsid2,
+                     (pool_len (sm->vnet_main->interface_main.sw_interfaces)
+                      - vec_len (sm->sw_iface_localsid2)));
        }
-      else
+      sm->sw_iface_localsid2[ls_mem->sw_if_index_in] = localsid_index;
+    }
+  else if (ls_mem->inner_type == AD_TYPE_IP4)
+    {
+      /* Enable End.AD4 rewrite node for this interface */
+      int ret =
+       vnet_feature_enable_disable ("ip4-unicast", "srv6-ad4-rewrite",
+                                    ls_mem->sw_if_index_in, 1, 0, 0);
+      if (ret != 0)
+       {
+         adj_unlock (ls_mem->nh_adj);
+         clib_mem_free (ls_mem);
+         return SID_CREATE_IFACE_FEATURE_ERROR;
+       }
+
+      /* Associate local SID index to this interface (resize vector if needed) */
+      if (ls_mem->sw_if_index_in >= vec_len (sm->sw_iface_localsid4))
        {
          vec_resize (sm->sw_iface_localsid4,
                      (pool_len (sm->vnet_main->interface_main.sw_interfaces)
                       - vec_len (sm->sw_iface_localsid4)));
-         sm->sw_iface_localsid4[ls_mem->sw_if_index_in] = localsid_index;
        }
+      sm->sw_iface_localsid4[ls_mem->sw_if_index_in] = localsid_index;
     }
-  else if (ls_mem->ip_version == DA_IP6)
+  else if (ls_mem->inner_type == AD_TYPE_IP6)
     {
-      ret = vnet_feature_enable_disable ("ip6-unicast", "srv6-ad6-rewrite",
-                                        ls_mem->sw_if_index_in, 1, 0, 0);
+      /* Enable End.AD6 rewrite node for this interface */
+      int ret =
+       vnet_feature_enable_disable ("ip6-unicast", "srv6-ad6-rewrite",
+                                    ls_mem->sw_if_index_in, 1, 0, 0);
       if (ret != 0)
-       return -1;
-
-      if (ls_mem->sw_if_index_in < vec_len (sm->sw_iface_localsid6))
        {
-         sm->sw_iface_localsid6[ls_mem->sw_if_index_in] = localsid_index;
+         adj_unlock (ls_mem->nh_adj);
+         clib_mem_free (ls_mem);
+         return SID_CREATE_IFACE_FEATURE_ERROR;
        }
-      else
+
+      /* Associate local SID index to this interface (resize vector if needed) */
+      if (ls_mem->sw_if_index_in >= vec_len (sm->sw_iface_localsid6))
        {
          vec_resize (sm->sw_iface_localsid6,
                      (pool_len (sm->vnet_main->interface_main.sw_interfaces)
                       - vec_len (sm->sw_iface_localsid6)));
-         sm->sw_iface_localsid6[ls_mem->sw_if_index_in] = localsid_index;
        }
+      sm->sw_iface_localsid6[ls_mem->sw_if_index_in] = localsid_index;
     }
 
   ls_mem->rw_len = 0;
@@ -135,38 +184,56 @@ srv6_ad_localsid_removal_fn (ip6_sr_localsid_t * localsid)
   srv6_ad_main_t *sm = &srv6_ad_main;
   srv6_ad_localsid_t *ls_mem = localsid->plugin_mem;
 
-  int ret = -1;
-  if (ls_mem->ip_version == DA_IP4)
+  if (ls_mem->inner_type == AD_TYPE_L2)
     {
-      /* Remove hardware indirection (from sr_steering.c:137) */
-      ret = vnet_feature_enable_disable ("ip4-unicast", "srv6-ad4-rewrite",
-                                        ls_mem->sw_if_index_in, 0, 0, 0);
+      /* Disable End.AD2 rewrite node for this interface */
+      int ret =
+       vnet_feature_enable_disable ("device-input", "srv6-ad2-rewrite",
+                                    ls_mem->sw_if_index_in, 0, 0, 0);
       if (ret != 0)
        return -1;
 
-      /* Remove local SID pointer from interface table (from sr_steering.c:139) */
+      /* Disable promiscuous mode on the interface */
+      vnet_main_t *vnm = vnet_get_main ();
+      ethernet_set_flags (vnm, ls_mem->sw_if_index_in, 0);
+
+      /* Remove local SID index from interface table */
+      sm->sw_iface_localsid2[ls_mem->sw_if_index_in] = ~(u32) 0;
+    }
+  else if (ls_mem->inner_type == AD_TYPE_IP4)
+    {
+      /* Disable End.AD4 rewrite node for this interface */
+      int ret =
+       vnet_feature_enable_disable ("ip4-unicast", "srv6-ad4-rewrite",
+                                    ls_mem->sw_if_index_in, 0, 0, 0);
+      if (ret != 0)
+       return -1;
+
+      /* Remove local SID pointer from interface table */
       sm->sw_iface_localsid4[ls_mem->sw_if_index_in] = ~(u32) 0;
     }
-  else if (ls_mem->ip_version == DA_IP6)
+  else if (ls_mem->inner_type == AD_TYPE_IP6)
     {
-      /* Remove hardware indirection (from sr_steering.c:137) */
-      ret = vnet_feature_enable_disable ("ip6-unicast", "srv6-ad6-rewrite",
-                                        ls_mem->sw_if_index_in, 0, 0, 0);
+      /* Disable End.AD6 rewrite node for this interface */
+      int ret =
+       vnet_feature_enable_disable ("ip6-unicast", "srv6-ad6-rewrite",
+                                    ls_mem->sw_if_index_in, 0, 0, 0);
       if (ret != 0)
        return -1;
 
-      /* Remove local SID pointer from interface table (from sr_steering.c:139) */
+      /* Remove local SID pointer from interface table */
       sm->sw_iface_localsid6[ls_mem->sw_if_index_in] = ~(u32) 0;
     }
 
 
-  /* Unlock (OIF, NHOP) adjacency (from sr_localsid.c:103) */
+  /* Unlock (OIF, NHOP) adjacency */
   adj_unlock (ls_mem->nh_adj);
 
   /* Delete SID entry */
   pool_put (sm->sids, pool_elt_at_index (sm->sids, ls_mem->index));
 
   /* Clean up local SID memory */
+  vec_free (ls_mem->rewrite);
   clib_mem_free (localsid->plugin_mem);
 
   return 0;
@@ -186,20 +253,20 @@ format_srv6_ad_localsid (u8 * s, va_list * args)
   vnet_main_t *vnm = vnet_get_main ();
   srv6_ad_main_t *sm = &srv6_ad_main;
 
-  if (ls_mem->ip_version == DA_IP4)
+  if (ls_mem->inner_type == AD_TYPE_IP4)
     {
       s =
-       format (s, "Next-hop:\t%U\n", format_ip4_address,
+       format (s, "Next-hop:\t%U\n\t", format_ip4_address,
                &ls_mem->nh_addr.ip4);
     }
-  else
+  else if (ls_mem->inner_type == AD_TYPE_IP6)
     {
       s =
-       format (s, "Next-hop:\t%U\n", format_ip6_address,
+       format (s, "Next-hop:\t%U\n\t", format_ip6_address,
                &ls_mem->nh_addr.ip6);
     }
 
-  s = format (s, "\tOutgoing iface:\t%U\n", format_vnet_sw_if_index_name, vnm,
+  s = format (s, "Outgoing iface:\t%U\n", format_vnet_sw_if_index_name, vnm,
              ls_mem->sw_if_index_out);
   s = format (s, "\tIncoming iface:\t%U\n", format_vnet_sw_if_index_name, vnm,
              ls_mem->sw_if_index_in);
@@ -231,55 +298,75 @@ unformat_srv6_ad_localsid (unformat_input_t * input, va_list * args)
 
   vnet_main_t *vnm = vnet_get_main ();
 
+  u8 inner_type = AD_TYPE_L2;
   ip46_address_t nh_addr;
   u32 sw_if_index_out;
   u32 sw_if_index_in;
 
-  if (unformat (input, "end.ad nh %U oif %U iif %U",
-               unformat_ip4_address, &nh_addr.ip4,
-               unformat_vnet_sw_interface, vnm, &sw_if_index_out,
-               unformat_vnet_sw_interface, vnm, &sw_if_index_in))
+  u8 params = 0;
+#define PARAM_AD_NH   (1 << 0)
+#define PARAM_AD_OIF  (1 << 1)
+#define PARAM_AD_IIF  (1 << 2)
+
+  if (!unformat (input, "end.ad"))
+    return 0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
     {
-      /* Allocate a portion of memory */
-      ls_mem = clib_mem_alloc_aligned_at_offset (sizeof *ls_mem, 0, 0, 1);
-
-      /* Set to zero the memory */
-      memset (ls_mem, 0, sizeof *ls_mem);
-
-      /* Our brand-new car is ready */
-      ls_mem->ip_version = DA_IP4;
-      clib_memcpy (&ls_mem->nh_addr.ip4, &nh_addr.ip4,
-                  sizeof (ip4_address_t));
-      ls_mem->sw_if_index_out = sw_if_index_out;
-      ls_mem->sw_if_index_in = sw_if_index_in;
-
-      /* Dont forget to add it to the localsid */
-      *plugin_mem_p = ls_mem;
-      return 1;
+      if (!(params & PARAM_AD_NH) && unformat (input, "nh %U",
+                                              unformat_ip4_address,
+                                              &nh_addr.ip4))
+       {
+         inner_type = AD_TYPE_IP4;
+         params |= PARAM_AD_NH;
+       }
+      if (!(params & PARAM_AD_NH) && unformat (input, "nh %U",
+                                              unformat_ip6_address,
+                                              &nh_addr.ip6))
+       {
+         inner_type = AD_TYPE_IP6;
+         params |= PARAM_AD_NH;
+       }
+      else if (!(params & PARAM_AD_OIF) && unformat (input, "oif %U",
+                                                    unformat_vnet_sw_interface,
+                                                    vnm, &sw_if_index_out))
+       {
+         params |= PARAM_AD_OIF;
+       }
+      else if (!(params & PARAM_AD_IIF) && unformat (input, "iif %U",
+                                                    unformat_vnet_sw_interface,
+                                                    vnm, &sw_if_index_in))
+       {
+         params |= PARAM_AD_IIF;
+       }
+      else
+       {
+         break;
+       }
     }
-  else if (unformat (input, "end.ad nh %U oif %U iif %U",
-                    unformat_ip6_address, &nh_addr.ip6,
-                    unformat_vnet_sw_interface, vnm, &sw_if_index_out,
-                    unformat_vnet_sw_interface, vnm, &sw_if_index_in))
+
+  /* Make sure that all parameters are supplied */
+  u8 params_chk = (PARAM_AD_OIF | PARAM_AD_IIF);
+  if ((params & params_chk) != params_chk)
     {
-      /* Allocate a portion of memory */
-      ls_mem = clib_mem_alloc_aligned_at_offset (sizeof *ls_mem, 0, 0, 1);
-
-      /* Set to zero the memory */
-      memset (ls_mem, 0, sizeof *ls_mem);
-
-      /* Our brand-new car is ready */
-      ls_mem->ip_version = DA_IP6;
-      clib_memcpy (&ls_mem->nh_addr.ip6, &nh_addr.ip6,
-                  sizeof (ip6_address_t));
-      ls_mem->sw_if_index_out = sw_if_index_out;
-      ls_mem->sw_if_index_in = sw_if_index_in;
-
-      /* Dont forget to add it to the localsid */
-      *plugin_mem_p = ls_mem;
-      return 1;
+      return 0;
     }
-  return 0;
+
+  /* Allocate and initialize memory block for local SID parameters */
+  ls_mem = clib_mem_alloc_aligned_at_offset (sizeof *ls_mem, 0, 0, 1);
+  memset (ls_mem, 0, sizeof *ls_mem);
+  *plugin_mem_p = ls_mem;
+
+  /* Set local SID parameters */
+  ls_mem->inner_type = inner_type;
+  if (inner_type == AD_TYPE_IP4)
+    ls_mem->nh_addr.ip4 = nh_addr.ip4;
+  else if (inner_type == AD_TYPE_IP6)
+    ls_mem->nh_addr.ip6 = nh_addr.ip6;
+  ls_mem->sw_if_index_out = sw_if_index_out;
+  ls_mem->sw_if_index_in = sw_if_index_in;
+
+  return 1;
 }
 
 /*************************/
@@ -351,6 +438,13 @@ srv6_ad_init (vlib_main_t * vm)
 }
 
 /* *INDENT-OFF* */
+VNET_FEATURE_INIT (srv6_ad2_rewrite, static) =
+{
+  .arc_name = "device-input",
+  .node_name = "srv6-ad2-rewrite",
+  .runs_before = VNET_FEATURES ("ethernet-input"),
+};
+
 VNET_FEATURE_INIT (srv6_ad4_rewrite, static) =
 {
   .arc_name = "ip4-unicast",
index 945e812..ff00f84 100644 (file)
@@ -23,8 +23,9 @@
 #include <vppinfra/error.h>
 #include <vppinfra/elog.h>
 
-#define DA_IP4 4
-#define DA_IP6 6
+#define AD_TYPE_L2     2
+#define AD_TYPE_IP4    4
+#define AD_TYPE_IP6    6
 
 /*
  * This is the memory that will be stored per each localsid
@@ -35,7 +36,7 @@ typedef struct
   ip46_address_t nh_addr;                        /**< Proxied device address */
   u32 sw_if_index_out;                                             /**< Outgoing iface to proxied dev. */
   u32 nh_adj;                                                                  /**< Adjacency index for out. iface */
-  u8 ip_version;
+  u8 inner_type;
 
   u32 sw_if_index_in;                                              /**< Incoming iface from proxied dev. */
   u32 rw_len;                /**< Number of bits to be rewritten */
@@ -55,6 +56,7 @@ typedef struct
 
   u32 srv6_localsid_behavior_id;         /**< SRv6 LocalSID behavior number */
 
+  u32 *sw_iface_localsid2;               /**< Retrieve local SID from iface */
   u32 *sw_iface_localsid4;               /**< Retrieve local SID from iface */
   u32 *sw_iface_localsid6;               /**< Retrieve local SID from iface */
 
index e6afe42..776d283 100644 (file)
@@ -92,6 +92,7 @@ typedef enum
   SRV6_AD_LOCALSID_NEXT_ERROR,
   SRV6_AD_LOCALSID_NEXT_REWRITE4,
   SRV6_AD_LOCALSID_NEXT_REWRITE6,
+  SRV6_AD_LOCALSID_NEXT_INTERFACE,
   SRV6_AD_LOCALSID_N_NEXT,
 } srv6_ad_localsid_next_t;
 
@@ -149,9 +150,10 @@ end_ad_processing (vlib_buffer_t * b0,
       next_ext_header = ip6_ext_next_header (next_ext_header);
     }
 
-  /* Make sure next header is IP */
-  if (PREDICT_FALSE
-      (next_hdr != IP_PROTOCOL_IPV6 && next_hdr != IP_PROTOCOL_IP_IN_IP))
+  /* Make sure next header is valid */
+  if (PREDICT_FALSE (next_hdr != IP_PROTOCOL_IPV6 &&
+                    next_hdr != IP_PROTOCOL_IP_IN_IP &&
+                    next_hdr != IP_PROTOCOL_IP6_NONXT))
     {
       return;
     }
@@ -170,13 +172,23 @@ end_ad_processing (vlib_buffer_t * b0,
   /* Remove IP header and extensions */
   vlib_buffer_advance (b0, total_size);
 
-  /* Set Xconnect adjacency to VNF */
-  vnet_buffer (b0)->ip.adj_index[VLIB_TX] = ls0_mem->nh_adj;
+  if (next_hdr == IP_PROTOCOL_IP6_NONXT)
+    {
+      /* Set output interface */
+      vnet_buffer (b0)->sw_if_index[VLIB_TX] = ls0_mem->sw_if_index_out;
+
+      /* Set next node to interface-output */
+      *next0 = SRV6_AD_LOCALSID_NEXT_INTERFACE;
+    }
+  else
+    {
+      /* Set Xconnect adjacency to VNF */
+      vnet_buffer (b0)->ip.adj_index[VLIB_TX] = ls0_mem->nh_adj;
 
-  if (ls0_mem->ip_version == DA_IP4)
-    *next0 = SRV6_AD_LOCALSID_NEXT_REWRITE4;
-  else if (ls0_mem->ip_version == DA_IP6)
-    *next0 = SRV6_AD_LOCALSID_NEXT_REWRITE6;
+      /* Set next node to ip-rewrite */
+      *next0 = (next_hdr == IP_PROTOCOL_IPV6) ?
+       SRV6_AD_LOCALSID_NEXT_REWRITE6 : SRV6_AD_LOCALSID_NEXT_REWRITE4;
+    }
 }
 
 /**
@@ -269,6 +281,7 @@ VLIB_REGISTER_NODE (srv6_ad_localsid_node) = {
   .next_nodes = {
     [SRV6_AD_LOCALSID_NEXT_REWRITE4] = "ip4-rewrite",
     [SRV6_AD_LOCALSID_NEXT_REWRITE6] = "ip6-rewrite",
+    [SRV6_AD_LOCALSID_NEXT_INTERFACE] = "interface-output",
     [SRV6_AD_LOCALSID_NEXT_ERROR] = "error-drop",
   },
 };
@@ -277,6 +290,140 @@ VLIB_REGISTER_NODE (srv6_ad_localsid_node) = {
 
 /******************************* Rewriting node *******************************/
 
+/**
+ * @brief Graph node for applying a SR policy into an IPv6 packet. Encapsulation
+ */
+static uword
+srv6_ad2_rewrite_fn (vlib_main_t * vm,
+                    vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  ip6_sr_main_t *srm = &sr_main;
+  srv6_ad_main_t *sm = &srv6_ad_main;
+  u32 n_left_from, next_index, *from, *to_next;
+  u32 cnt_packets = 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);
+
+      /* TODO: Dual/quad loop */
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         u32 bi0;
+         vlib_buffer_t *b0;
+         ethernet_header_t *en0;
+         ip6_header_t *ip0 = 0;
+         ip6_sr_localsid_t *ls0;
+         srv6_ad_localsid_t *ls0_mem;
+         u32 next0 = SRV6_AD_REWRITE_NEXT_LOOKUP;
+
+         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);
+         en0 = vlib_buffer_get_current (b0);
+         ls0 = pool_elt_at_index (srm->localsids,
+                                  sm->sw_iface_localsid2[vnet_buffer
+                                                         (b0)->sw_if_index
+                                                         [VLIB_RX]]);
+         ls0_mem = ls0->plugin_mem;
+
+         if (PREDICT_FALSE (ls0_mem == NULL || ls0_mem->rewrite == NULL))
+           {
+             next0 = SRV6_AD_REWRITE_NEXT_ERROR;
+             b0->error = node->errors[SRV6_AD_REWRITE_COUNTER_NO_RW];
+           }
+         else
+           {
+             ASSERT (VLIB_BUFFER_PRE_DATA_SIZE >=
+                     (ls0_mem->rw_len + b0->current_data));
+
+             clib_memcpy (((u8 *) en0) - ls0_mem->rw_len,
+                          ls0_mem->rewrite, ls0_mem->rw_len);
+             vlib_buffer_advance (b0, -(word) ls0_mem->rw_len);
+
+             ip0 = vlib_buffer_get_current (b0);
+
+             ip0->payload_length =
+               clib_host_to_net_u16 (b0->current_length -
+                                     sizeof (ip6_header_t));
+           }
+
+         if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE) &&
+             PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+           {
+             srv6_ad_rewrite_trace_t *tr =
+               vlib_add_trace (vm, node, b0, sizeof *tr);
+             tr->error = 0;
+
+             if (next0 == SRV6_AD_REWRITE_NEXT_ERROR)
+               {
+                 tr->error = 1;
+               }
+             else
+               {
+                 clib_memcpy (tr->src.as_u8, ip0->src_address.as_u8,
+                              sizeof tr->src.as_u8);
+                 clib_memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
+                              sizeof tr->dst.as_u8);
+               }
+           }
+
+         /* Increment per-SID AD rewrite counters */
+         vlib_increment_combined_counter (((next0 ==
+                                            SRV6_AD_LOCALSID_NEXT_ERROR) ?
+                                           &(sm->invalid_counters) :
+                                           &(sm->valid_counters)),
+                                          vm->thread_index, ls0_mem->index,
+                                          1, vlib_buffer_length_in_chain (vm,
+                                                                          b0));
+
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+                                          n_left_to_next, bi0, next0);
+
+         cnt_packets++;
+       }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  /* Update counters */
+  vlib_node_increment_counter (vm, srv6_ad4_rewrite_node.index,
+                              SRV6_AD_REWRITE_COUNTER_PROCESSED,
+                              cnt_packets);
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (srv6_ad2_rewrite_node) = {
+  .function = srv6_ad2_rewrite_fn,
+  .name = "srv6-ad2-rewrite",
+  .vector_size = sizeof (u32),
+  .format_trace = format_srv6_ad_rewrite_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = SRV6_AD_REWRITE_N_COUNTERS,
+  .error_strings = srv6_ad_rewrite_counter_strings,
+  .n_next_nodes = SRV6_AD_REWRITE_N_NEXT,
+  .next_nodes = {
+    [SRV6_AD_REWRITE_NEXT_LOOKUP] = "ip6-lookup",
+    [SRV6_AD_REWRITE_NEXT_ERROR] = "error-drop",
+  },
+};
+/* *INDENT-ON* */
+
+
 /**
  * @brief Graph node for applying a SR policy into an IPv6 packet. Encapsulation
  */
index 13979ba..75d9d12 100644 (file)
@@ -408,6 +408,136 @@ class TestSRv6(VppTestCase):
 
         self.logger.debug("packet verification: SUCCESS")
 
+    def test_SRv6_End_AD_L2(self):
+        """ Test SRv6 End.AD behavior with L2 traffic.
+        """
+        self.src_addr = 'a0::'
+        self.sid_list = ['a1::', 'a2::a4', 'a3::']
+        self.test_sid_index = 1
+
+        # send traffic to one destination interface
+        # source and destination interfaces are IPv6 only
+        self.setup_interfaces(ipv6=[True, False])
+
+        # configure route to next segment
+        route = VppIpRoute(self, self.sid_list[self.test_sid_index + 1], 128,
+                           [VppRoutePath(self.pg0.remote_ip6,
+                                         self.pg0.sw_if_index,
+                                         proto=DpoProto.DPO_PROTO_IP6)],
+                           is_ip6=1)
+        route.add_vpp_config()
+
+        # configure SRv6 localSID behavior
+        cli_str = "sr localsid address " + \
+                  self.sid_list[self.test_sid_index] + \
+                  " behavior end.ad" + \
+                  " oif " + self.pg1.name + \
+                  " iif " + self.pg1.name
+        self.vapi.cli(cli_str)
+
+        # log the localsids
+        self.logger.debug(self.vapi.cli("show sr localsid"))
+
+        # send one packet per packet size
+        count = len(self.pg_packet_sizes)
+
+        # prepare L2 in SRv6 headers
+        packet_header1 = self.create_packet_header_IPv6_SRH_L2(
+            srcaddr=self.src_addr,
+            sidlist=self.sid_list[::-1],
+            segleft=len(self.sid_list) - self.test_sid_index - 1,
+            vlan=0)
+
+        # generate packets (pg0->pg1)
+        pkts1 = self.create_stream(self.pg0, self.pg1, packet_header1,
+                                   self.pg_packet_sizes, count)
+
+        # send packets and verify received packets
+        self.send_and_verify_pkts(self.pg0, pkts1, self.pg1,
+                                  self.compare_rx_tx_packet_End_AD_L2_out)
+
+        # log the localsid counters
+        self.logger.info(self.vapi.cli("show sr localsid"))
+
+        # prepare L2 header for returning packets
+        packet_header2 = self.create_packet_header_L2()
+
+        # generate returning packets (pg1->pg0)
+        pkts2 = self.create_stream(self.pg1, self.pg0, packet_header2,
+                                   self.pg_packet_sizes, count)
+
+        # send packets and verify received packets
+        self.send_and_verify_pkts(self.pg1, pkts2, self.pg0,
+                                  self.compare_rx_tx_packet_End_AD_L2_in)
+
+        # log the localsid counters
+        self.logger.info(self.vapi.cli("show sr localsid"))
+
+        # remove SRv6 localSIDs
+        cli_str = "sr localsid del address " + \
+                  self.sid_list[self.test_sid_index]
+        self.vapi.cli(cli_str)
+
+        # cleanup interfaces
+        self.teardown_interfaces()
+
+    def compare_rx_tx_packet_End_AD_L2_out(self, tx_pkt, rx_pkt):
+        """ Compare input and output packet after passing End.AD with L2
+
+        :param tx_pkt: transmitted packet
+        :param rx_pkt: received packet
+        """
+
+        # get IPv4 header of rx'ed packet
+        rx_eth = rx_pkt.getlayer(Ether)
+
+        tx_ip = tx_pkt.getlayer(IPv6)
+        # we can't just get the 2nd Ether layer
+        # get the Raw content and dissect it as Ether
+        tx_eth1 = Ether(str(tx_pkt[Raw]))
+
+        # verify if rx'ed packet has no SRH
+        self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+
+        # the whole rx_eth pkt should be equal to tx_eth1
+        self.assertEqual(rx_eth, tx_eth1)
+
+        self.logger.debug("packet verification: SUCCESS")
+
+    def compare_rx_tx_packet_End_AD_L2_in(self, tx_pkt, rx_pkt):
+        """ Compare input and output packet after passing End.AD
+
+        :param tx_pkt: transmitted packet
+        :param rx_pkt: received packet
+        """
+
+        ####
+        # get first (outer) IPv6 header of rx'ed packet
+        rx_ip = rx_pkt.getlayer(IPv6)
+        # received ip.src should be equal to SR Policy source
+        self.assertEqual(rx_ip.src, self.src_addr)
+        # received ip.dst should be equal to expected sidlist next segment
+        self.assertEqual(rx_ip.dst, self.sid_list[self.test_sid_index + 1])
+
+        # rx'ed packet should have SRH
+        self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+
+        # get SRH
+        rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+        # rx'ed seglist should be equal to SID-list in reversed order
+        self.assertEqual(rx_srh.addresses, self.sid_list[::-1])
+        # segleft should be equal to previous segleft value minus 1
+        self.assertEqual(rx_srh.segleft,
+                         len(self.sid_list) - self.test_sid_index - 2)
+        # lastentry should be equal to the SID-list length minus 1
+        self.assertEqual(rx_srh.lastentry, len(self.sid_list) - 1)
+
+        # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
+        tx_ether = tx_pkt.getlayer(Ether)
+        self.assertEqual(Ether(str(rx_srh.payload)), tx_ether)
+
+        self.logger.debug("packet verification: SUCCESS")
+
     def create_stream(self, src_if, dst_if, packet_header, packet_sizes,
                       count):
         """Create SRv6 input packet stream for defined interface.
@@ -547,6 +677,50 @@ class TestSRv6(VppTestCase):
             UDP(sport=1234, dport=1234)
         return p
 
+    def create_packet_header_L2(self, vlan=0):
+        """Create packet header: L2 header
+
+        :param vlan: if vlan!=0 then add 802.1q header
+        """
+        # Note: the dst addr ('00:55:44:33:22:11') is used in
+        # the compare function compare_rx_tx_packet_T_Encaps_L2
+        # to detect presence of L2 in SRH payload
+        p = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11')
+        etype = 0x8137  # IPX
+        if vlan:
+            # add 802.1q layer
+            p /= Dot1Q(vlan=vlan, type=etype)
+        else:
+            p.type = etype
+        return p
+
+    def create_packet_header_IPv6_SRH_L2(self, srcaddr, sidlist, segleft,
+                                         vlan=0):
+        """Create packet header: L2 encapsulated in SRv6:
+        IPv6 header with SRH, L2
+
+        :param int srcaddr: IPv6 source address
+        :param list sidlist: segment list of outer IPv6 SRH
+        :param int segleft: segments-left field of outer IPv6 SRH
+        :param vlan: L2 vlan; if vlan!=0 then add 802.1q header
+
+        IPv6 source address is set to srcaddr
+        IPv6 destination address is set to sidlist[segleft]
+        """
+        eth = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11')
+        etype = 0x8137  # IPX
+        if vlan:
+            # add 802.1q layer
+            eth /= Dot1Q(vlan=vlan, type=etype)
+        else:
+            eth.type = etype
+
+        p = IPv6(src=srcaddr, dst=sidlist[segleft]) / \
+            IPv6ExtHdrSegmentRouting(addresses=sidlist,
+                                     segleft=segleft, nh=59) / \
+            eth
+        return p
+
     def get_payload_info(self, packet):
         """ Extract the payload_info from the packet
         """