MPLS Exp-null Tests 48/3648/2
authorNeale Ranns <nranns@cisco.com>
Tue, 1 Nov 2016 10:05:08 +0000 (10:05 +0000)
committerDamjan Marion <dmarion.lists@gmail.com>
Tue, 1 Nov 2016 19:26:24 +0000 (19:26 +0000)
Add some 'make test' unit tests for MPLS explicit NULL label handling.
Fix the stacking of the MPLS load-balance result form the lookup onto the IPx lookup object.

Change-Id: I890d1221b8e3dea99bcc714ed9d0154a5f602c52
Signed-off-by: Neale Ranns <nranns@cisco.com>
test/framework.py
test/test_mpls.py [new file with mode: 0644]
test/vpp_interface.py
test/vpp_papi_provider.py
vnet/vnet/dpo/dpo.c
vnet/vnet/dpo/lookup_dpo.c
vnet/vnet/dpo/mpls_label_dpo.c
vnet/vnet/fib/fib_entry_src.c
vnet/vnet/fib/fib_test.c

index 5f75e01..8c39701 100644 (file)
@@ -113,7 +113,8 @@ class VppTestCase(unittest.TestCase):
         cls.set_debug_flags(d)
         cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
         cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
-        cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon",
+        cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon",
+                           "cli-listen localhost:5002", "}",
                            "api-segment", "{", "prefix", cls.shm_prefix, "}"]
         if cls.plugin_path is not None:
             cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
diff --git a/test/test_mpls.py b/test/test_mpls.py
new file mode 100644 (file)
index 0000000..45af470
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+
+import unittest
+import socket
+from logging import *
+
+from framework import VppTestCase, VppTestRunner
+from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q, ARP
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP
+from scapy.contrib.mpls import MPLS
+
+class TestMPLS(VppTestCase):
+    """ MPLS Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestMPLS, cls).setUpClass()
+
+    def setUp(self):
+        super(TestMPLS, self).setUp()
+
+        # create 2 pg interfaces
+        self.create_pg_interfaces(range(3))
+
+        # setup both interfaces
+        # assign them different tables.
+        table_id = 0
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.set_table_ip4(table_id)
+            i.set_table_ip6(table_id)
+            i.config_ip4()
+            i.config_ip6()
+            i.enable_mpls()
+            i.resolve_arp()
+            i.resolve_ndp()
+            table_id += 1
+
+    def tearDown(self):
+        super(TestMPLS, self).tearDown()
+
+    def create_stream_ip4(self, src_if, mpls_label, mpls_ttl):
+        pkts = []
+        for i in range(0, 257):
+            info = self.create_packet_info(src_if.sw_if_index,
+                                           src_if.sw_if_index)
+            payload = self.info_to_payload(info)
+            p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+                 MPLS(label=mpls_label, ttl=mpls_ttl) /
+                 IP(src=src_if.remote_ip4, dst=src_if.remote_ip4) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(payload))
+            info.data = p.copy()
+            pkts.append(p)
+        return pkts
+
+    def create_stream_ip6(self, src_if, mpls_label, mpls_ttl):
+        pkts = []
+        for i in range(0, 257):
+            info = self.create_packet_info(src_if.sw_if_index,
+                                           src_if.sw_if_index)
+            payload = self.info_to_payload(info)
+            p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+                 MPLS(label=mpls_label, ttl=mpls_ttl) /
+                 IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(payload))
+            info.data = p.copy()
+            pkts.append(p)
+        return pkts
+
+    def verify_capture_ip4(self, src_if, capture, sent):
+        try:
+            self.assertEqual(len(capture), len(sent))
+
+            for i in range(len(capture)):
+                tx = sent[i]
+                rx = capture[i]
+
+                # the rx'd packet has the MPLS label popped
+                eth = rx[Ether];
+                self.assertEqual(eth.type, 0x800);
+
+                tx_ip = tx[IP]
+                rx_ip = rx[IP]
+
+                self.assertEqual(rx_ip.src, tx_ip.src)
+                self.assertEqual(rx_ip.dst, tx_ip.dst)
+                # IP processing post pop has decremented the TTL
+                self.assertEqual(rx_ip.ttl+1, tx_ip.ttl)
+
+        except:
+            raise;
+
+    def verify_capture_ip6(self, src_if, capture, sent):
+        try:
+            self.assertEqual(len(capture), len(sent))
+
+            for i in range(len(capture)):
+                tx = sent[i]
+                rx = capture[i]
+
+                # the rx'd packet has the MPLS label popped
+                eth = rx[Ether];
+                self.assertEqual(eth.type, 0x86DD);
+
+                tx_ip = tx[IPv6]
+                rx_ip = rx[IPv6]
+
+                self.assertEqual(rx_ip.src, tx_ip.src)
+                self.assertEqual(rx_ip.dst, tx_ip.dst)
+                # IP processing post pop has decremented the TTL
+                self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
+
+        except:
+            raise;
+
+
+    def test_v4_exp_null(self):
+        """ MPLS V4 Explicit NULL test """
+
+        #
+        # The first test case has an MPLS TTL of 0
+        # all packet should be dropped
+        #
+        tx = self.create_stream_ip4(self.pg0, 0, 0)
+        self.pg0.add_stream(tx)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg0.get_capture()
+
+        try:
+            self.assertEqual(0, len(rx));
+        except:
+            error("MPLS TTL=0 packets forwarded")
+            error(packet.show())
+            raise
+
+        #
+        # a stream with a non-zero MPLS TTL
+        # PG0 is in the default table
+        #
+        self.vapi.cli("clear trace")
+        tx = self.create_stream_ip4(self.pg0, 0, 2)
+        self.pg0.add_stream(tx)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg0.get_capture()
+        self.verify_capture_ip4(self.pg0, rx, tx)
+
+        #
+        # a stream with a non-zero MPLS TTL
+        # PG1 is in table 1
+        # we are ensuring the post-pop lookup occurs in the VRF table
+        #
+        self.vapi.cli("clear trace")
+        tx = self.create_stream_ip4(self.pg1, 0, 2)
+        self.pg1.add_stream(tx)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture()
+        self.verify_capture_ip4(self.pg0, rx, tx)
+
+    def test_v6_exp_null(self):
+        """ MPLS V6 Explicit NULL test """
+
+        #
+        # a stream with a non-zero MPLS TTL
+        # PG0 is in the default table
+        #
+        self.vapi.cli("clear trace")
+        tx = self.create_stream_ip6(self.pg0, 2, 2)
+        self.pg0.add_stream(tx)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg0.get_capture()
+        self.verify_capture_ip6(self.pg0, rx, tx)
+
+        #
+        # a stream with a non-zero MPLS TTL
+        # PG1 is in table 1
+        # we are ensuring the post-pop lookup occurs in the VRF table
+        #
+        self.vapi.cli("clear trace")
+        tx = self.create_stream_ip6(self.pg1, 2, 2)
+        self.pg1.add_stream(tx)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture()
+        self.verify_capture_ip6(self.pg0, rx, tx)
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
index c596ab5..509ab95 100644 (file)
@@ -131,6 +131,18 @@ class VppInterface(object):
         self.test.vapi.sw_interface_add_del_address(
             self.sw_if_index, addr, addr_len, is_ipv6=1)
 
+    def set_table_ip4(self, table_id):
+        """Set the interface in a IPv4 Table.
+        Must be called before configuring IP4 addresses"""
+        self.test.vapi.sw_interface_set_table(
+            self.sw_if_index, 0, table_id)
+
+    def set_table_ip6(self, table_id):
+        """Set the interface in a IPv6 Table.
+        Must be called before configuring IP6 addresses"""
+        self.test.vapi.sw_interface_set_table(
+            self.sw_if_index, 1, table_id)
+
     def disable_ipv6_ra(self):
         """Configure IPv6 RA suppress on the VPP interface"""
         self.test.vapi.sw_interface_ra_suppress(self.sw_if_index)
@@ -238,3 +250,8 @@ class VppInterface(object):
                 self.sub_if.append(sub_if)
             else:
                 self.sub_if = sub_if
+
+    def enable_mpls(self):
+        """Enable MPLS on the VPP interface"""
+        self.test.vapi.sw_interface_enable_disable_mpls(
+            self.sw_if_index)
index 261a0f4..f0eb410 100644 (file)
@@ -106,6 +106,18 @@ class VppPapiProvider(object):
             args = (0, b'')
         return self.api(vpp_papi.sw_interface_dump, args)
 
+    def sw_interface_set_table(self, sw_if_index, is_ipv6, table_id):
+        """
+          Set the IPvX Table-id for the Interface
+
+        :param sw_if_index:
+        :param is_ipv6:
+        :param table_id:
+
+        """
+        return self.api(vpp_papi.sw_interface_set_table,
+                        (sw_if_index, is_ipv6, table_id))
+
     def sw_interface_add_del_address(self, sw_if_index, addr, addr_len,
                                      is_ipv6=0, is_add=1, del_all=0):
         """
@@ -121,6 +133,17 @@ class VppPapiProvider(object):
         return self.api(vpp_papi.sw_interface_add_del_address,
                         (sw_if_index, is_add, is_ipv6, del_all, addr_len, addr))
 
+    def sw_interface_enable_disable_mpls(self, sw_if_index,
+                                         is_enable=1):
+        """
+        Enable/Disable MPLS on the interface
+        :param sw_if_index:
+        :param is_enable:  (Default value = 1)
+
+        """
+        return self.api(vpp_papi.sw_interface_set_mpls_enable,
+                        (sw_if_index, is_enable))
+
     def sw_interface_ra_suppress(self, sw_if_index):
         suppress = 1
         managed = 0
index efee6d6..3542b3f 100644 (file)
@@ -65,15 +65,18 @@ static const char* const * const ** dpo_nodes;
 /**
  * @brief Vector of edge indicies from parent DPO nodes to child
  *
- * dpo_edges[child_type][child_proto][parent_type] = edge_index
+ * dpo_edges[child_type][child_proto][parent_type][parent_proto] = edge_index
  *
  * This array is derived at init time from the dpo_nodes above. Note that
  * the third dimension in dpo_nodes is lost, hence, the edge index from each
  * node MUST be the same.
+ * Including both the child and parent protocol is required to support the
+ * case where it changes as the grapth is traversed, most notablly when an
+ * MPLS label is popped.
  *
  * Note that this array is child type specific, not child instance specific.
  */
-static u32 ***dpo_edges;
+static u32 ****dpo_edges;
 
 /**
  * @brief The DPO type value that can be assigend to the next dynamic
@@ -269,13 +272,15 @@ dpo_get_next_node (dpo_type_t child_type,
 
     vec_validate(dpo_edges, child_type);
     vec_validate(dpo_edges[child_type], child_proto);
-    vec_validate_init_empty(dpo_edges[child_type][child_proto],
-                            parent_dpo->dpoi_type, ~0);
+    vec_validate(dpo_edges[child_type][child_proto], parent_type);
+    vec_validate_init_empty(
+        dpo_edges[child_type][child_proto][parent_type],
+        parent_proto, ~0);
 
     /*
      * if the edge index has not yet been created for this node to node transistion
      */
-    if (~0 == dpo_edges[child_type][child_proto][parent_type])
+    if (~0 == dpo_edges[child_type][child_proto][parent_type][parent_proto])
     {
         vlib_node_t *parent_node, *child_node;
         vlib_main_t *vm;
@@ -288,45 +293,45 @@ dpo_get_next_node (dpo_type_t child_type,
         ASSERT(NULL != dpo_nodes[parent_type]);
         ASSERT(NULL != dpo_nodes[parent_type][parent_proto]);
 
-        pp = 0;
+        cc = 0;
 
         /*
          * create a graph arc from each of the parent's registered node types,
          * to each of the childs.
          */
-        while (NULL != dpo_nodes[child_type][child_proto][pp])
+        while (NULL != dpo_nodes[child_type][child_proto][cc])
         {
-            parent_node =
+            child_node =
                 vlib_get_node_by_name(vm,
-                                      (u8*) dpo_nodes[child_type][child_proto][pp]);
+                                      (u8*) dpo_nodes[child_type][child_proto][cc]);
 
-            cc = 0;
+            pp = 0;
 
-            while (NULL != dpo_nodes[parent_type][child_proto][cc])
+            while (NULL != dpo_nodes[parent_type][parent_proto][pp])
             {
-                child_node =
+                parent_node =
                     vlib_get_node_by_name(vm,
-                                          (u8*) dpo_nodes[parent_type][parent_proto][cc]);
+                                          (u8*) dpo_nodes[parent_type][parent_proto][pp]);
 
                 edge = vlib_node_add_next(vm,
-                                          parent_node->index,
-                                          child_node->index);
+                                          child_node->index,
+                                          parent_node->index);
 
-                if (~0 == dpo_edges[child_type][child_proto][parent_type])
+                if (~0 == dpo_edges[child_type][child_proto][parent_type][parent_proto])
                 {
-                    dpo_edges[child_type][child_proto][parent_type] = edge;
+                    dpo_edges[child_type][child_proto][parent_type][parent_proto] = edge;
                 }
                 else
                 {
-                    ASSERT(dpo_edges[child_type][child_proto][parent_type] == edge);
+                    ASSERT(dpo_edges[child_type][child_proto][parent_type][parent_proto] == edge);
                 }
-                cc++;
+                pp++;
             }
-            pp++;
+            cc++;
         }
     }
 
-    return (dpo_edges[child_type][child_proto][parent_type]);
+    return (dpo_edges[child_type][child_proto][parent_type][parent_proto]);
 }
 
 /**
index c9e0fca..b13000d 100644 (file)
@@ -685,10 +685,10 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
            if (table_from_interface)
            {
                fib_index0 =
-                   ip4_fib_table_get_index_for_sw_if_index(
+                   ip6_fib_table_get_index_for_sw_if_index(
                        vnet_buffer(b0)->sw_if_index[VLIB_RX]);
                fib_index1 =
-                   ip4_fib_table_get_index_for_sw_if_index(
+                   ip6_fib_table_get_index_for_sw_if_index(
                        vnet_buffer(b1)->sw_if_index[VLIB_RX]);
            }
            else
@@ -810,7 +810,7 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
            if (table_from_interface)
            {
                fib_index0 =
-                   ip4_fib_table_get_index_for_sw_if_index(
+                   ip6_fib_table_get_index_for_sw_if_index(
                        vnet_buffer(b0)->sw_if_index[VLIB_RX]);
            }
            else
index 532d044..48c03be 100644 (file)
@@ -82,11 +82,12 @@ format_mpls_label_dpo (u8 *s, va_list *args)
     hdr.label_exp_s_ttl =
         clib_net_to_host_u32(mld->mld_hdr.label_exp_s_ttl);
 
-    return (format(s, "mpls-label:[%d]:%U\n%U%U",
-                  index,
-                   format_mpls_header, hdr,
-                  format_white_space, indent,
-                  format_dpo_id, &mld->mld_dpo, indent+2));
+    s = format(s, "mpls-label:[%d]:", index);
+    s = format(s, "%U\n", format_mpls_header, hdr);
+    s = format(s, "%U", format_white_space, indent);
+    s = format(s, "%U", format_dpo_id, &mld->mld_dpo, indent+2);
+
+    return (s);
 }
 
 static void
index 2cbdf18..6fb2e64 100644 (file)
@@ -34,21 +34,6 @@ fib_entry_get_proto (const fib_entry_t * fib_entry)
     return (fib_entry->fe_prefix.fp_proto);
 }
 
-static dpo_proto_t
-fib_entry_get_payload_proto (const fib_entry_t * fib_entry)
-{
-    switch (fib_entry->fe_prefix.fp_proto)
-    {
-    case FIB_PROTOCOL_IP4:
-    case FIB_PROTOCOL_IP6:
-       return fib_proto_to_dpo(fib_entry->fe_prefix.fp_proto);
-    case FIB_PROTOCOL_MPLS:
-       return fib_entry->fe_prefix.fp_payload_proto;
-    }
-
-    return (fib_proto_to_dpo(fib_entry->fe_prefix.fp_proto));
-}
-
 void
 fib_entry_src_register (fib_source_t source,
                        const fib_entry_src_vft_t *vft)
@@ -344,7 +329,7 @@ fib_entry_src_mk_lb (fib_entry_t *fib_entry,
         .fct = fct,
     };
 
-    lb_proto = fib_entry_get_payload_proto(fib_entry);
+    lb_proto = fib_proto_to_dpo(fib_entry->fe_prefix.fp_proto);
 
     fib_path_list_walk(esrc->fes_pl,
                        fib_entry_src_collect_forwarding,
index 800f4e6..2194fcb 100644 (file)
@@ -4824,7 +4824,7 @@ fib_test_validate_lb_v (const load_balance_t *lb,
            FIB_TEST_LB((exp->special.adj == dpo->dpoi_index),
                        "bucket %d stacks on drop %d",
                        bucket,
-                       exp->adj.adj);
+                       exp->special.adj);
            break;
        }
     }
@@ -5489,7 +5489,7 @@ fib_test_label (void)
     fib_test_lb_bucket_t bucket_drop = {
        .type = FT_LB_SPECIAL,
        .special = {
-           .adj = 1,
+           .adj = DPO_PROTO_IP4,
        },
     };