fib: Support the POP of a Psuedo Wire Control Word 41/20741/2
authorNeale Ranns <nranns@cisco.com>
Fri, 19 Jul 2019 11:44:53 +0000 (11:44 +0000)
committerDamjan Marion <dmarion@me.com>
Wed, 24 Jul 2019 14:42:27 +0000 (14:42 +0000)
Type: feature

Change-Id: Ib24547a7c4c73ceb5383d1ca8f14ec40e6a90f01
Signed-off-by: Neale Ranns <nranns@cisco.com>
13 files changed:
src/vnet/CMakeLists.txt
src/vnet/dpo/dpo.c
src/vnet/dpo/dpo.h
src/vnet/dpo/pw_cw.c [new file with mode: 0644]
src/vnet/dpo/pw_cw.h [new file with mode: 0644]
src/vnet/fib/fib_api.c
src/vnet/fib/fib_path.c
src/vnet/fib/fib_path.h
src/vnet/fib/fib_types.api
src/vnet/fib/fib_types.c
src/vnet/fib/fib_types.h
test/test_mpls.py
test/vpp_ip_route.py

index 68e806e..91a5a77 100644 (file)
@@ -1365,6 +1365,7 @@ list(APPEND VNET_SOURCES
   dpo/mpls_label_dpo.c
   dpo/l3_proxy_dpo.c
   dpo/dvr_dpo.c
+  dpo/pw_cw.c
 )
 
 list(APPEND VNET_MULTIARCH_SOURCES
index 13ae37a..d5865d1 100644 (file)
@@ -43,6 +43,7 @@
 #include <vnet/dpo/dvr_dpo.h>
 #include <vnet/dpo/l3_proxy_dpo.h>
 #include <vnet/dpo/ip6_ll_dpo.h>
+#include <vnet/dpo/pw_cw.h>
 
 /**
  * Array of char* names for the DPO types and protos
@@ -588,6 +589,7 @@ dpo_module_init (vlib_main_t * vm)
     mpls_disp_dpo_module_init();
     dvr_dpo_module_init();
     l3_proxy_dpo_module_init();
+    pw_cw_dpo_module_init();
 
     return (NULL);
 }
index 0eeca67..73ad9ad 100644 (file)
@@ -124,6 +124,7 @@ typedef enum dpo_type_t_ {
     DPO_BIER_DISP_TABLE,
     DPO_BIER_DISP_ENTRY,
     DPO_IP6_LL,
+    DPO_PW_CW,
     DPO_LAST,
 } __attribute__((packed)) dpo_type_t;
 
@@ -159,6 +160,7 @@ typedef enum dpo_type_t_ {
     [DPO_BIER_DISP_ENTRY] = "bier-disp-entry", \
     [DPO_BIER_DISP_TABLE] = "bier-disp-table", \
     [DPO_IP6_LL] = "ip6-link-local",   \
+    [DPO_PW_CW] = "PW-CW",     \
 }
 
 /**
diff --git a/src/vnet/dpo/pw_cw.c b/src/vnet/dpo/pw_cw.c
new file mode 100644 (file)
index 0000000..8c16c33
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vnet/dpo/pw_cw.h>
+#include <vnet/fib/fib_node.h>
+
+#ifndef CLIB_MARCH_VARIANT
+
+/*
+ * pool of all MPLS Label DPOs
+ */
+pw_cw_dpo_t *pw_cw_dpo_pool;
+
+static pw_cw_dpo_t *
+pw_cw_dpo_alloc (void)
+{
+    pw_cw_dpo_t *pwcw;
+
+    pool_get_aligned_zero(pw_cw_dpo_pool, pwcw, 8);
+
+    return (pwcw);
+}
+
+static index_t
+pw_cw_dpo_get_index (pw_cw_dpo_t *pwcw)
+{
+    return (pwcw - pw_cw_dpo_pool);
+}
+
+void
+pw_cw_dpo_create (const dpo_id_t *parent,
+                  dpo_id_t *dpo)
+{
+    pw_cw_dpo_t *pwcw;
+
+    pwcw = pw_cw_dpo_alloc();
+
+    /*
+     * stack this disposition object on the parent given
+     */
+    dpo_stack(DPO_PW_CW,
+              parent->dpoi_proto,
+              &pwcw->pwcw_parent,
+              parent);
+
+    /*
+     * set up the return DPO to refer to this object
+     */
+    dpo_set(dpo,
+            DPO_PW_CW,
+            parent->dpoi_proto,
+            pw_cw_dpo_get_index(pwcw));
+}
+
+u8*
+format_pw_cw_dpo (u8 *s, va_list *args)
+{
+    index_t pwcwi = va_arg (*args, index_t);
+    u32 indent = va_arg (*args, u32);
+    pw_cw_dpo_t *pwcw;
+
+    if (pool_is_free_index(pw_cw_dpo_pool, pwcwi))
+    {
+        /*
+         * the packet trace can be printed after the DPO has been deleted
+         */
+        return (format(s, "pw-cw[???,%d]:", pwcwi));
+    }
+
+    pwcw = pw_cw_dpo_get(pwcwi);
+    s = format(s, "pw-cw[%d]:", pwcwi);
+
+    s = format(s, "\n%U", format_white_space, indent);
+    s = format(s, "%U", format_dpo_id, &pwcw->pwcw_parent, indent+2);
+
+    return (s);
+}
+
+static void
+pw_cw_dpo_lock (dpo_id_t *dpo)
+{
+    pw_cw_dpo_t *pwcw;
+
+    pwcw = pw_cw_dpo_get(dpo->dpoi_index);
+
+    pwcw->pwcw_locks++;
+}
+
+static void
+pw_cw_dpo_unlock (dpo_id_t *dpo)
+{
+    pw_cw_dpo_t *pwcw;
+
+    pwcw = pw_cw_dpo_get(dpo->dpoi_index);
+
+    pwcw->pwcw_locks--;
+
+    if (0 == pwcw->pwcw_locks)
+    {
+       dpo_reset(&pwcw->pwcw_parent);
+       pool_put(pw_cw_dpo_pool, pwcw);
+    }
+}
+#endif /* CLIB_MARCH_VARIANT */
+
+/**
+ * @brief A struct to hold tracing information for the MPLS label imposition
+ * node.
+ */
+typedef struct pw_cw_trace_t_
+{
+    /**
+     * The CW popped
+     */
+    u32 cw;
+} pw_cw_trace_t;
+
+always_inline uword
+pw_cw_pop_inline (vlib_main_t * vm,
+                  vlib_node_runtime_t * node,
+                  vlib_frame_t * from_frame)
+{
+    u32 n_left_from, next_index, * from, * to_next;
+
+    from = vlib_frame_vector_args(from_frame);
+    n_left_from = from_frame->n_vectors;
+    next_index = node->cached_next_index;
+
+    while (n_left_from > 0)
+    {
+        u32 n_left_to_next;
+
+        vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
+
+        while (n_left_from >= 4 && n_left_to_next >= 2)
+        {
+            pw_cw_dpo_t *pwcw0, *pwcw1;
+            u32 bi0, pwcwi0, bi1, pwcwi1;
+            vlib_buffer_t * b0, *b1;
+            u32 next0, next1;
+
+            bi0 = to_next[0] = from[0];
+            bi1 = to_next[1] = from[1];
+
+            /* Prefetch next iteration. */
+            {
+                vlib_buffer_t * p2, * p3;
+
+                p2 = vlib_get_buffer(vm, from[2]);
+                p3 = vlib_get_buffer(vm, from[3]);
+
+                vlib_prefetch_buffer_header(p2, STORE);
+                vlib_prefetch_buffer_header(p3, STORE);
+
+                CLIB_PREFETCH(p2->data, sizeof(pw_cw_t), STORE);
+                CLIB_PREFETCH(p3->data, sizeof(pw_cw_t), STORE);
+            }
+
+            from += 2;
+            to_next += 2;
+            n_left_from -= 2;
+            n_left_to_next -= 2;
+
+            b0 = vlib_get_buffer(vm, bi0);
+            b1 = vlib_get_buffer(vm, bi1);
+
+            /* get the next parent DPO for the next pop */
+            pwcwi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
+            pwcwi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX];
+            pwcw0 = pw_cw_dpo_get(pwcwi0);
+            pwcw1 = pw_cw_dpo_get(pwcwi1);
+
+            next0 = pwcw0->pwcw_parent.dpoi_next_node;
+            next1 = pwcw1->pwcw_parent.dpoi_next_node;
+
+            vnet_buffer(b0)->ip.adj_index[VLIB_TX] = pwcw0->pwcw_parent.dpoi_index;
+            vnet_buffer(b1)->ip.adj_index[VLIB_TX] = pwcw1->pwcw_parent.dpoi_index;
+
+            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
+            {
+                pw_cw_trace_t *tr = vlib_add_trace(vm, node, b0, sizeof(*tr));
+
+                tr->cw = *((pw_cw_t*) vlib_buffer_get_current(b0));
+            }
+            if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED))
+            {
+                pw_cw_trace_t *tr = vlib_add_trace(vm, node, b1, sizeof(*tr));
+
+                tr->cw = *((pw_cw_t*) vlib_buffer_get_current(b1));
+            }
+
+            /* pop the PW CW */
+            vlib_buffer_advance (b0, sizeof(pw_cw_t));
+            vlib_buffer_advance (b1, sizeof(pw_cw_t));
+
+            vlib_validate_buffer_enqueue_x2(vm, node, next_index, to_next,
+                                            n_left_to_next,
+                                            bi0, bi1, next0, next1);
+        }
+
+        while (n_left_from > 0 && n_left_to_next > 0)
+        {
+            pw_cw_dpo_t *pwcw0;
+            vlib_buffer_t * b0;
+            u32 bi0, pwcwi0;
+            u32 next0;
+
+            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);
+
+            pwcwi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
+            pwcw0 = pw_cw_dpo_get(pwcwi0);
+            next0 = pwcw0->pwcw_parent.dpoi_next_node;
+            vnet_buffer(b0)->ip.adj_index[VLIB_TX] = pwcw0->pwcw_parent.dpoi_index;
+
+            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
+            {
+                pw_cw_trace_t *tr = vlib_add_trace(vm, node, b0, sizeof(*tr));
+
+                tr->cw = *((pw_cw_t*) vlib_buffer_get_current(b0));
+            }
+
+            vlib_buffer_advance (b0, sizeof(pw_cw_t));
+
+            vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next,
+                                            n_left_to_next, bi0, next0);
+        }
+        vlib_put_next_frame(vm, node, next_index, n_left_to_next);
+    }
+    return from_frame->n_vectors;
+}
+
+static u8 *
+format_pw_cw_trace (u8 * s, va_list * args)
+{
+    CLIB_UNUSED(vlib_main_t * vm) = va_arg(*args, vlib_main_t *);
+    CLIB_UNUSED(vlib_node_t * node) = va_arg(*args, vlib_node_t *);
+    CLIB_UNUSED(pw_cw_trace_t * t);
+
+    t = va_arg(*args, pw_cw_trace_t *);
+
+    s = format(s, "cw:0x%x", t->cw);
+
+    return (s);
+}
+
+VLIB_NODE_FN (pw_cw_node) (vlib_main_t * vm,
+                           vlib_node_runtime_t * node,
+                           vlib_frame_t * frame)
+{
+    return (pw_cw_pop_inline(vm, node, frame));
+}
+
+VLIB_REGISTER_NODE(pw_cw_node) = {
+    .name = "pw-cw-pop",
+    .vector_size = sizeof(u32),
+    .format_trace = format_pw_cw_trace,
+};
+
+#ifndef CLIB_MARCH_VARIANT
+static void
+pw_cw_dpo_mem_show (void)
+{
+    fib_show_memory_usage("PW-CW",
+                         pool_elts(pw_cw_dpo_pool),
+                         pool_len(pw_cw_dpo_pool),
+                         sizeof(pw_cw_dpo_t));
+}
+
+const static dpo_vft_t pwcw_vft = {
+    .dv_lock = pw_cw_dpo_lock,
+    .dv_unlock = pw_cw_dpo_unlock,
+    .dv_format = format_pw_cw_dpo,
+    .dv_mem_show = pw_cw_dpo_mem_show,
+};
+
+const static char* const pw_cw_proto_nodes[] =
+{
+    "pw-cw-pop",
+    NULL,
+};
+
+const static char* const * const pw_cw_nodes[DPO_PROTO_NUM] =
+{
+    [DPO_PROTO_IP4]  = pw_cw_proto_nodes,
+    [DPO_PROTO_IP6]  = pw_cw_proto_nodes,
+    [DPO_PROTO_MPLS] = pw_cw_proto_nodes,
+    [DPO_PROTO_ETHERNET] = pw_cw_proto_nodes,
+};
+
+void
+pw_cw_dpo_module_init (void)
+{
+    dpo_register(DPO_PW_CW, &pwcw_vft, pw_cw_nodes);
+}
+
+#endif /* CLIB_MARCH_VARIANT */
diff --git a/src/vnet/dpo/pw_cw.h b/src/vnet/dpo/pw_cw.h
new file mode 100644 (file)
index 0000000..96f8c72
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2019 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __PW_CW_DPO_H__
+#define __PW_CW_DPO_H__
+
+#include <vnet/vnet.h>
+#include <vnet/mpls/packet.h>
+#include <vnet/dpo/dpo.h>
+
+/**
+ * A Psuedo Wire Control Word is 4 bytes
+ */
+typedef u32 pw_cw_t;
+
+/**
+ * A representation of a Psuedo Wire Control Word pop DPO
+ */
+typedef struct pw_cw_dpo_t
+{
+    /**
+     * Next DPO in the graph
+     */
+    dpo_id_t pwcw_parent;
+
+    /**
+     * Number of locks/users of the label
+     */
+    u16 pwcw_locks;
+} pw_cw_dpo_t;
+
+/**
+ * @brief Assert that the MPLS label object is less than a cache line in size.
+ * Should this get any bigger then we will need to reconsider how many labels
+ * can be pushed in one object.
+ */
+STATIC_ASSERT_SIZEOF(pw_cw_dpo_t, 2 * sizeof(u64));
+
+/* #define STATIC_ASSERT_ALIGNOF(d, s)                                      \ */
+/*     STATIC_ASSERT (alignof (d) == sizeof(s), "Align of " #d " must be " # s " bytes") */
+
+/* STATIC_ASSERT_AIGNOF(pw_cw_dpo_t, u64); */
+
+/**
+ * @brief Create an PW CW pop
+ *
+ * @param parent The parent of the created MPLS label object
+ * @param dpo The PW CW DPO created
+ */
+extern void pw_cw_dpo_create(const dpo_id_t *paremt,
+                             dpo_id_t *dpo);
+
+extern u8* format_pw_cw_dpo(u8 *s, va_list *args);
+
+/*
+ * Encapsulation violation for fast data-path access
+ */
+extern pw_cw_dpo_t *pw_cw_dpo_pool;
+
+static inline pw_cw_dpo_t *
+pw_cw_dpo_get (index_t index)
+{
+    return (pool_elt_at_index(pw_cw_dpo_pool, index));
+}
+
+extern void pw_cw_dpo_module_init(void);
+
+#endif
index e776235..e020b26 100644 (file)
@@ -186,6 +186,8 @@ fib_api_path_decode (vl_api_fib_path_t *in,
         out->frp_flags |= FIB_ROUTE_PATH_RESOLVE_VIA_HOST;
     if (in->flags & FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED)
         out->frp_flags |= FIB_ROUTE_PATH_RESOLVE_VIA_ATTACHED;
+    if (in->flags & FIB_API_PATH_FLAG_POP_PW_CW)
+        out->frp_flags |= FIB_ROUTE_PATH_POP_PW_CW;
 
     switch (in->type)
     {
index eebba1b..ed7bc02 100644 (file)
@@ -26,6 +26,7 @@
 #include <vnet/dpo/dvr_dpo.h>
 #include <vnet/dpo/ip_null_dpo.h>
 #include <vnet/dpo/classify_dpo.h>
+#include <vnet/dpo/pw_cw.h>
 
 #include <vnet/adj/adj.h>
 #include <vnet/adj/adj_mcast.h>
@@ -1233,6 +1234,8 @@ fib_path_route_flags_to_cfg_flags (const fib_route_path_t *rpath)
 {
     fib_path_cfg_flags_t cfg_flags = FIB_PATH_CFG_FLAG_NONE;
 
+    if (rpath->frp_flags & FIB_ROUTE_PATH_POP_PW_CW)
+       cfg_flags |= FIB_PATH_CFG_FLAG_POP_PW_CW;
     if (rpath->frp_flags & FIB_ROUTE_PATH_RESOLVE_VIA_HOST)
        cfg_flags |= FIB_PATH_CFG_FLAG_RESOLVE_HOST;
     if (rpath->frp_flags & FIB_ROUTE_PATH_RESOLVE_VIA_ATTACHED)
@@ -2381,6 +2384,16 @@ fib_path_stack_mpls_disp (fib_node_index_t path_index,
     case FIB_PATH_TYPE_DVR:
         break;
     }
+
+    if (path->fp_cfg_flags & FIB_PATH_CFG_FLAG_POP_PW_CW)
+    {
+        dpo_id_t tmp = DPO_INVALID;
+
+        dpo_copy(&tmp, dpo);
+
+        pw_cw_dpo_create(&tmp, dpo);
+        dpo_reset(&tmp);
+    }
 }
 
 void
index 50aca9e..76f876d 100644 (file)
@@ -95,10 +95,14 @@ typedef enum fib_path_cfg_attribute_t_ {
      * The deag path does a source lookup
      */
     FIB_PATH_CFG_ATTRIBUTE_DEAG_SRC,
+    /**
+     * The path pops a Psuedo Wire Control Word
+     */
+    FIB_PATH_CFG_ATTRIBUTE_POP_PW_CW,
     /**
      * Marker. Add new types before this one, then update it.
      */
-    FIB_PATH_CFG_ATTRIBUTE_LAST = FIB_PATH_CFG_ATTRIBUTE_DEAG_SRC,
+    FIB_PATH_CFG_ATTRIBUTE_LAST = FIB_PATH_CFG_ATTRIBUTE_POP_PW_CW,
 } __attribute__ ((packed)) fib_path_cfg_attribute_t;
 
 /**
@@ -119,6 +123,7 @@ typedef enum fib_path_cfg_attribute_t_ {
     [FIB_PATH_CFG_ATTRIBUTE_INTF_RX] = "interface-rx", \
     [FIB_PATH_CFG_ATTRIBUTE_RPF_ID] = "rpf-id",         \
     [FIB_PATH_CFG_ATTRIBUTE_DEAG_SRC] = "deag-src",     \
+    [FIB_PATH_CFG_ATTRIBUTE_POP_PW_CW] = "pop-pw-cw",     \
 }
 
 #define FOR_EACH_FIB_PATH_CFG_ATTRIBUTE(_item) \
@@ -143,6 +148,7 @@ typedef enum fib_path_cfg_flags_t_ {
     FIB_PATH_CFG_FLAG_INTF_RX = (1 << FIB_PATH_CFG_ATTRIBUTE_INTF_RX),
     FIB_PATH_CFG_FLAG_RPF_ID = (1 << FIB_PATH_CFG_ATTRIBUTE_RPF_ID),
     FIB_PATH_CFG_FLAG_DEAG_SRC = (1 << FIB_PATH_CFG_ATTRIBUTE_DEAG_SRC),
+    FIB_PATH_CFG_FLAG_POP_PW_CW = (1 << FIB_PATH_CFG_ATTRIBUTE_POP_PW_CW),
 } __attribute__ ((packed)) fib_path_cfg_flags_t;
 
 typedef enum fib_path_format_flags_t_
index 9073192..4a5cea7 100644 (file)
@@ -44,9 +44,11 @@ enum fib_path_flags
 {
   FIB_API_PATH_FLAG_NONE = 0,
   /* the path must resolve via an attached route */
-  FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED,
+  FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1,
   /* the path must resolve via a host route */
-  FIB_API_PATH_FLAG_RESOLVE_VIA_HOST,
+  FIB_API_PATH_FLAG_RESOLVE_VIA_HOST = 2,
+  /* pop a Pseudo Wire Control Word as well */
+  FIB_API_PATH_FLAG_POP_PW_CW = 4,
 };
 
 /* \brief A description of the 'next-hop' for a path
index 4b1280f..386aece 100644 (file)
@@ -537,6 +537,10 @@ unformat_fib_route_path (unformat_input_t * input, va_list * args)
         {
             rpath->frp_flags |= FIB_ROUTE_PATH_RESOLVE_VIA_ATTACHED;
         }
+        else if (unformat (input, "pop-pw-cw"))
+        {
+            rpath->frp_flags |= FIB_ROUTE_PATH_POP_PW_CW;
+        }
         else if (unformat (input,
                            "ip4-lookup-in-table %d",
                            &rpath->frp_fib_index))
index 77b133f..980fe3d 100644 (file)
@@ -383,6 +383,11 @@ typedef enum fib_route_path_flags_t_
     FIB_ROUTE_PATH_ICMP_UNREACH = (1 << 15),
     FIB_ROUTE_PATH_ICMP_PROHIBIT = (1 << 16),
     FIB_ROUTE_PATH_CLASSIFY = (1 << 17),
+
+    /**
+     * Pop a Psuedo Wire Control Word
+     */
+    FIB_ROUTE_PATH_POP_PW_CW = (1 << 18),
 } fib_route_path_flags_t;
 
 /**
index d068bc3..b8c3576 100644 (file)
@@ -2043,89 +2043,133 @@ class TestMPLSL2(VppTestCase):
     def test_vpls(self):
         """ Virtual Private LAN Service """
         #
-        # Create an L2 MPLS tunnel
+        # Create a L2 MPLS tunnels
         #
-        mpls_tun = VppMPLSTunnelInterface(
+        mpls_tun1 = VppMPLSTunnelInterface(
             self,
             [VppRoutePath(self.pg0.remote_ip4,
                           self.pg0.sw_if_index,
                           labels=[VppMplsLabel(42)])],
             is_l2=1)
-        mpls_tun.add_vpp_config()
-        mpls_tun.admin_up()
+        mpls_tun1.add_vpp_config()
+        mpls_tun1.admin_up()
+
+        mpls_tun2 = VppMPLSTunnelInterface(
+            self,
+            [VppRoutePath(self.pg0.remote_ip4,
+                          self.pg0.sw_if_index,
+                          labels=[VppMplsLabel(43)])],
+            is_l2=1)
+        mpls_tun2.add_vpp_config()
+        mpls_tun2.admin_up()
 
         #
-        # Create a label entry to for 55 that does L2 input to the tunnel
+        # Create a label entries, 55 and 56, that do L2 input to the tunnel
+        # the latter includes a Psuedo Wire Control Word
         #
         route_55_eos = VppMplsRoute(
             self, 55, 1,
             [VppRoutePath("0.0.0.0",
-                          mpls_tun.sw_if_index,
+                          mpls_tun1.sw_if_index,
+                          type=FibPathType.FIB_PATH_TYPE_INTERFACE_RX,
+                          proto=FibPathProto.FIB_PATH_NH_PROTO_ETHERNET)],
+            eos_proto=FibPathProto.FIB_PATH_NH_PROTO_ETHERNET)
+
+        route_56_eos = VppMplsRoute(
+            self, 56, 1,
+            [VppRoutePath("0.0.0.0",
+                          mpls_tun2.sw_if_index,
                           type=FibPathType.FIB_PATH_TYPE_INTERFACE_RX,
+                          flags=FibPathFlags.FIB_PATH_FLAG_POP_PW_CW,
                           proto=FibPathProto.FIB_PATH_NH_PROTO_ETHERNET)],
             eos_proto=FibPathProto.FIB_PATH_NH_PROTO_ETHERNET)
+
+        # move me
+        route_56_eos.add_vpp_config()
         route_55_eos.add_vpp_config()
 
+        self.logger.info(self.vapi.cli("sh mpls fib 56"))
+
         #
         # add to tunnel to the customers bridge-domain
         #
         self.vapi.sw_interface_set_l2_bridge(
-            rx_sw_if_index=mpls_tun.sw_if_index, bd_id=1)
+            rx_sw_if_index=mpls_tun1.sw_if_index, bd_id=1)
+        self.vapi.sw_interface_set_l2_bridge(
+            rx_sw_if_index=mpls_tun2.sw_if_index, bd_id=1)
         self.vapi.sw_interface_set_l2_bridge(
             rx_sw_if_index=self.pg1.sw_if_index, bd_id=1)
 
         #
-        # Packet from the customer interface and from the core
-        #
-        p_cust = (Ether(dst="00:00:de:ad:ba:be",
-                        src="00:00:de:ad:be:ef") /
-                  IP(src="10.10.10.10", dst="11.11.11.11") /
-                  UDP(sport=1234, dport=1234) /
-                  Raw('\xa5' * 100))
-        p_core = (Ether(src="00:00:de:ad:ba:be",
-                        dst="00:00:de:ad:be:ef") /
-                  IP(dst="10.10.10.10", src="11.11.11.11") /
-                  UDP(sport=1234, dport=1234) /
-                  Raw('\xa5' * 100))
+        # Packet from host on the customer interface to each host
+        # reachable over the core, and vice-versa
+        #
+        p_cust1 = (Ether(dst="00:00:de:ad:ba:b1",
+                         src="00:00:de:ad:be:ef") /
+                   IP(src="10.10.10.10", dst="11.11.11.11") /
+                   UDP(sport=1234, dport=1234) /
+                   Raw('\xa5' * 100))
+        p_cust2 = (Ether(dst="00:00:de:ad:ba:b2",
+                         src="00:00:de:ad:be:ef") /
+                   IP(src="10.10.10.10", dst="11.11.11.12") /
+                   UDP(sport=1234, dport=1234) /
+                   Raw('\xa5' * 100))
+        p_core1 = (Ether(dst=self.pg0.local_mac,
+                         src=self.pg0.remote_mac) /
+                   MPLS(label=55, ttl=64) /
+                   Ether(src="00:00:de:ad:ba:b1",
+                         dst="00:00:de:ad:be:ef") /
+                   IP(dst="10.10.10.10", src="11.11.11.11") /
+                   UDP(sport=1234, dport=1234) /
+                   Raw('\xa5' * 100))
+        p_core2 = (Ether(dst=self.pg0.local_mac,
+                         src=self.pg0.remote_mac) /
+                   MPLS(label=56, ttl=64) /
+                   Raw('\x01' * 4) /  # PW CW
+                   Ether(src="00:00:de:ad:ba:b2",
+                         dst="00:00:de:ad:be:ef") /
+                   IP(dst="10.10.10.10", src="11.11.11.12") /
+                   UDP(sport=1234, dport=1234) /
+                   Raw('\xa5' * 100))
 
         #
         # The BD is learning, so send in one of each packet to learn
         #
-        p_core_encap = (Ether(dst=self.pg0.local_mac,
-                              src=self.pg0.remote_mac) /
-                        MPLS(label=55, ttl=64) /
-                        p_core)
 
-        self.pg1.add_stream(p_cust)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg0.add_stream(p_core_encap)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
+        # 2 packets due to BD flooding
+        rx = self.send_and_expect(self.pg1, p_cust1, self.pg0, n_rx=2)
+        rx = self.send_and_expect(self.pg1, p_cust2, self.pg0, n_rx=2)
 
-        # we've learnt this so expect it be be forwarded
-        rx0 = self.pg1.get_capture(1)
+        # we've learnt this so expect it be be forwarded not flooded
+        rx = self.send_and_expect(self.pg0, [p_core1], self.pg1)
+        self.assertEqual(rx[0][Ether].dst, p_cust1[Ether].src)
+        self.assertEqual(rx[0][Ether].src, p_cust1[Ether].dst)
 
-        self.assertEqual(rx0[0][Ether].dst, p_core[Ether].dst)
-        self.assertEqual(rx0[0][Ether].src, p_core[Ether].src)
+        rx = self.send_and_expect(self.pg0, [p_core2], self.pg1)
+        self.assertEqual(rx[0][Ether].dst, p_cust2[Ether].src)
+        self.assertEqual(rx[0][Ether].src, p_cust2[Ether].dst)
 
         #
-        # now a stream in each direction
+        # now a stream in each direction from each host
         #
-        self.pg1.add_stream(p_cust * NUM_PKTS)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
+        rx = self.send_and_expect(self.pg1, p_cust1 * NUM_PKTS, self.pg0)
+        self.verify_capture_tunneled_ethernet(rx, p_cust1 * NUM_PKTS,
+                                              [VppMplsLabel(42)])
 
-        rx0 = self.pg0.get_capture(NUM_PKTS)
+        rx = self.send_and_expect(self.pg1, p_cust2 * NUM_PKTS, self.pg0)
+        self.verify_capture_tunneled_ethernet(rx, p_cust2 * NUM_PKTS,
+                                              [VppMplsLabel(43)])
 
-        self.verify_capture_tunneled_ethernet(rx0, p_cust*NUM_PKTS,
-                                              [VppMplsLabel(42)])
+        rx = self.send_and_expect(self.pg0, p_core1 * NUM_PKTS, self.pg1)
+        rx = self.send_and_expect(self.pg0, p_core2 * NUM_PKTS, self.pg1)
 
         #
         # remove interfaces from customers bridge-domain
         #
         self.vapi.sw_interface_set_l2_bridge(
-            rx_sw_if_index=mpls_tun.sw_if_index, bd_id=1, enable=0)
+            rx_sw_if_index=mpls_tun1.sw_if_index, bd_id=1, enable=0)
+        self.vapi.sw_interface_set_l2_bridge(
+            rx_sw_if_index=mpls_tun2.sw_if_index, bd_id=1, enable=0)
         self.vapi.sw_interface_set_l2_bridge(
             rx_sw_if_index=self.pg1.sw_if_index, bd_id=1, enable=0)
 
index 864a39a..a70180b 100644 (file)
@@ -64,6 +64,7 @@ class FibPathFlags:
     FIB_PATH_FLAG_NONE = 0
     FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
     FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
+    FIB_PATH_FLAG_POP_PW_CW = 4
 
 
 class MplsLspMode: