pg: A Tunnel mode variant of a pg interface 64/32564/5
authorNeale Ranns <neale@graphiant.com>
Thu, 3 Jun 2021 14:43:21 +0000 (14:43 +0000)
committerOle Tr�an <otroan@employees.org>
Mon, 14 Jun 2021 13:12:34 +0000 (13:12 +0000)
Type: feature

this allows VPP to simulate linux tun devices.

Signed-off-by: Neale Ranns <neale@graphiant.com>
Change-Id: I3adf38b49a254804370f78edd5d275d192fd00a6

src/vnet/pg/cli.c
src/vnet/pg/pg.api
src/vnet/pg/pg.h
src/vnet/pg/pg_api.c
src/vnet/pg/stream.c
test/framework.py
test/test_pg.py [new file with mode: 0644]
test/vpp_pg_interface.py

index 9da0f8d..e57e725 100644 (file)
@@ -333,6 +333,23 @@ validate_stream (pg_stream_t * s)
   return 0;
 }
 
+const char *
+pg_interface_get_input_node (pg_interface_t *pi)
+{
+  switch (pi->mode)
+    {
+    case PG_MODE_ETHERNET:
+      return ("ethernet-input");
+    case PG_MODE_IP4:
+      return ("ip4-input");
+    case PG_MODE_IP6:
+      return ("ip6-input");
+    }
+
+  ASSERT (0);
+  return ("ethernet-input");
+}
+
 static clib_error_t *
 new_stream (vlib_main_t * vm,
            unformat_input_t * input, vlib_cli_command_t * cmd)
@@ -351,7 +368,7 @@ new_stream (vlib_main_t * vm,
   s.node_index = ~0;
   s.max_packet_bytes = s.min_packet_bytes = 64;
   s.buffer_bytes = vlib_buffer_get_default_data_size (vm);
-  s.if_id = 0;
+  s.if_id = ~0;
   s.n_max_frame = VLIB_FRAME_SIZE;
   pcap_file_name = 0;
 
@@ -427,8 +444,15 @@ new_stream (vlib_main_t * vm,
     {
       if (pcap_file_name != 0)
        {
-         vlib_node_t *n =
-           vlib_get_node_by_name (vm, (u8 *) "ethernet-input");
+         vlib_node_t *n;
+
+         ASSERT (s.if_id != ~0);
+
+         if (s.if_id != ~0)
+           n = vlib_get_node_by_name (vm, (u8 *) pg_interface_get_input_node (
+                                            &pg->interfaces[s.if_id]));
+         else
+           n = vlib_get_node_by_name (vm, (u8 *) "ethernet-input");
          s.node_index = n->index;
        }
       else
@@ -694,8 +718,8 @@ create_pg_if_cmd_fn (vlib_main_t * vm,
        }
     }
 
-  pg_interface_add_or_get (pg, if_id, gso_enabled, gso_size,
-                          coalesce_enabled);
+  pg_interface_add_or_get (pg, if_id, gso_enabled, gso_size, coalesce_enabled,
+                          PG_MODE_ETHERNET);
 
 done:
   unformat_free (line_input);
index 3a44f1d..3630e0c 100644 (file)
@@ -22,6 +22,13 @@ option version = "2.0.0";
 
 import "vnet/interface_types.api";
 
+enum pg_interface_mode : u8
+{
+ PG_API_MODE_ETHERNET = 0,
+ PG_API_MODE_IP4,
+ PG_API_MODE_IP6,
+};
+
 /** \brief PacketGenerator create interface request
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
@@ -37,6 +44,15 @@ define pg_create_interface
   bool gso_enabled;
   u32 gso_size;
 };
+define pg_create_interface_v2
+{
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t interface_id;
+  bool gso_enabled;
+  u32 gso_size;
+  vl_api_pg_interface_mode_t mode;
+};
 
 /** \brief PacketGenerator create interface response
     @param context - sender context, to match reply w/ request
@@ -48,6 +64,12 @@ define pg_create_interface_reply
   i32 retval;
   vl_api_interface_index_t sw_if_index;
 };
+define pg_create_interface_v2_reply
+{
+  u32 context;
+  i32 retval;
+  vl_api_interface_index_t sw_if_index;
+};
 
 /** \brief PacketGenerator interface enable/disable packet coalesce
     @param client_index - opaque cookie to identify the sender
index da5af25..ffa2f8a 100644 (file)
@@ -299,6 +299,13 @@ pg_free_edit_group (pg_stream_t * s)
   _vec_len (s->edit_groups) = i;
 }
 
+typedef enum pg_interface_mode_t_
+{
+  PG_MODE_ETHERNET,
+  PG_MODE_IP4,
+  PG_MODE_IP6,
+} pg_interface_mode_t;
+
 typedef struct
 {
   /* TX lock */
@@ -316,6 +323,7 @@ typedef struct
   u32 gso_size;
   pcap_main_t pcap_main;
   char *pcap_file_name;
+  pg_interface_mode_t mode;
 
   mac_address_t *allowed_mcast_macs;
 } pg_interface_t;
@@ -373,9 +381,9 @@ void pg_interface_enable_disable_coalesce (pg_interface_t * pi, u8 enable,
                                           u32 tx_node_index);
 
 /* Find/create free packet-generator interface index. */
-u32 pg_interface_add_or_get (pg_main_t * pg, uword stream_index,
-                            u8 gso_enabled, u32 gso_size,
-                            u8 coalesce_enabled);
+u32 pg_interface_add_or_get (pg_main_t *pg, uword stream_index, u8 gso_enabled,
+                            u32 gso_size, u8 coalesce_enabled,
+                            pg_interface_mode_t mode);
 
 always_inline pg_node_t *
 pg_get_node (uword node_index)
index 554e8ea..b3eb315 100644 (file)
 
 #include <vlibapi/api_helper_macros.h>
 
-
-#define foreach_pg_api_msg                                              \
-_(PG_CREATE_INTERFACE, pg_create_interface)                             \
-_(PG_CAPTURE, pg_capture)                                               \
-_(PG_ENABLE_DISABLE, pg_enable_disable)                                 \
-_(PG_INTERFACE_ENABLE_DISABLE_COALESCE, pg_interface_enable_disable_coalesce)
+#define foreach_pg_api_msg                                                    \
+  _ (PG_CREATE_INTERFACE, pg_create_interface)                                \
+  _ (PG_CREATE_INTERFACE_V2, pg_create_interface_v2)                          \
+  _ (PG_CAPTURE, pg_capture)                                                  \
+  _ (PG_ENABLE_DISABLE, pg_enable_disable)                                    \
+  _ (PG_INTERFACE_ENABLE_DISABLE_COALESCE,                                    \
+     pg_interface_enable_disable_coalesce)
 
 static void
 vl_api_pg_create_interface_t_handler (vl_api_pg_create_interface_t * mp)
@@ -54,9 +55,9 @@ vl_api_pg_create_interface_t_handler (vl_api_pg_create_interface_t * mp)
   int rv = 0;
 
   pg_main_t *pg = &pg_main;
-  u32 pg_if_id = pg_interface_add_or_get (pg, ntohl (mp->interface_id),
-                                         mp->gso_enabled,
-                                         ntohl (mp->gso_size), 0);
+  u32 pg_if_id =
+    pg_interface_add_or_get (pg, ntohl (mp->interface_id), mp->gso_enabled,
+                            ntohl (mp->gso_size), 0, PG_MODE_ETHERNET);
   pg_interface_t *pi = pool_elt_at_index (pg->interfaces, pg_if_id);
 
   /* *INDENT-OFF* */
@@ -67,6 +68,22 @@ vl_api_pg_create_interface_t_handler (vl_api_pg_create_interface_t * mp)
   /* *INDENT-ON* */
 }
 
+static void
+vl_api_pg_create_interface_v2_t_handler (vl_api_pg_create_interface_v2_t *mp)
+{
+  vl_api_pg_create_interface_v2_reply_t *rmp;
+  int rv = 0;
+
+  pg_main_t *pg = &pg_main;
+  u32 pg_if_id =
+    pg_interface_add_or_get (pg, ntohl (mp->interface_id), mp->gso_enabled,
+                            ntohl (mp->gso_size), 0, (u8) mp->mode);
+  pg_interface_t *pi = pool_elt_at_index (pg->interfaces, pg_if_id);
+
+  REPLY_MACRO2 (VL_API_PG_CREATE_INTERFACE_V2_REPLY,
+               ({ rmp->sw_if_index = ntohl (pi->sw_if_index); }));
+}
+
 static void
   vl_api_pg_interface_enable_disable_coalesce_t_handler
   (vl_api_pg_interface_enable_disable_coalesce_t * mp)
index 0ce640b..686627b 100644 (file)
@@ -229,9 +229,29 @@ pg_interface_enable_disable_coalesce (pg_interface_t * pi, u8 enable,
     }
 }
 
+u8 *
+format_pg_tun_tx_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 *);
+
+  s = format (s, "PG: tunnel (no-encap)");
+  return s;
+}
+
+VNET_HW_INTERFACE_CLASS (pg_tun_hw_interface_class) = {
+  .name = "PG-tun",
+  //.format_header = format_gre_header_with_length,
+  //.unformat_header = unformat_gre_header,
+  .build_rewrite = NULL,
+  //.update_adjacency = gre_update_adj,
+  .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P,
+};
+
 u32
-pg_interface_add_or_get (pg_main_t * pg, uword if_id, u8 gso_enabled,
-                        u32 gso_size, u8 coalesce_enabled)
+pg_interface_add_or_get (pg_main_t *pg, uword if_id, u8 gso_enabled,
+                        u32 gso_size, u8 coalesce_enabled,
+                        pg_interface_mode_t mode)
 {
   vnet_main_t *vnm = vnet_get_main ();
   vlib_main_t *vm = vlib_get_main ();
@@ -262,8 +282,20 @@ pg_interface_add_or_get (pg_main_t * pg, uword if_id, u8 gso_enabled,
       hw_addr[1] = 0xfe;
 
       pi->id = if_id;
-      ethernet_register_interface (vnm, pg_dev_class.index, i, hw_addr,
-                                  &pi->hw_if_index, pg_eth_flag_change);
+      pi->mode = mode;
+
+      switch (pi->mode)
+       {
+       case PG_MODE_ETHERNET:
+         ethernet_register_interface (vnm, pg_dev_class.index, i, hw_addr,
+                                      &pi->hw_if_index, pg_eth_flag_change);
+         break;
+       case PG_MODE_IP4:
+       case PG_MODE_IP6:
+         pi->hw_if_index = vnet_register_interface (
+           vnm, pg_dev_class.index, i, pg_tun_hw_interface_class.index, i);
+         break;
+       }
       hi = vnet_get_hw_interface (vnm, pi->hw_if_index);
       if (gso_enabled)
        {
@@ -510,9 +542,9 @@ pg_stream_add (pg_main_t * pg, pg_stream_t * s_init)
   }
 
   /* Find an interface to use. */
-  s->pg_if_index =
-    pg_interface_add_or_get (pg, s->if_id, 0 /* gso_enabled */ ,
-                            0 /* gso_size */ , 0 /* coalesce_enabled */ );
+  s->pg_if_index = pg_interface_add_or_get (
+    pg, s->if_id, 0 /* gso_enabled */, 0 /* gso_size */,
+    0 /* coalesce_enabled */, PG_MODE_ETHERNET);
 
   if (s->sw_if_index[VLIB_RX] == ~0)
     {
index c2a2fc6..2afec7a 100755 (executable)
@@ -32,6 +32,7 @@ from vpp_sub_interface import VppSubInterface
 from vpp_lo_interface import VppLoInterface
 from vpp_bvi_interface import VppBviInterface
 from vpp_papi_provider import VppPapiProvider
+from vpp_papi import VppEnum
 import vpp_papi
 from vpp_papi.vpp_stats import VPPStats
 from vpp_papi.vpp_transport_socket import VppTransportSocketIOError
@@ -915,7 +916,8 @@ class VppTestCase(CPUInterface, unittest.TestCase):
         cls._pcaps = []
 
     @classmethod
-    def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0):
+    def create_pg_interfaces_internal(cls, interfaces, gso=0, gso_size=0,
+                                      mode=None):
         """
         Create packet-generator interfaces.
 
@@ -925,12 +927,36 @@ class VppTestCase(CPUInterface, unittest.TestCase):
         """
         result = []
         for i in interfaces:
-            intf = VppPGInterface(cls, i, gso, gso_size)
+            intf = VppPGInterface(cls, i, gso, gso_size, mode)
             setattr(cls, intf.name, intf)
             result.append(intf)
         cls.pg_interfaces = result
         return result
 
+    @classmethod
+    def create_pg_ip4_interfaces(cls, interfaces, gso=0, gso_size=0):
+        pgmode = VppEnum.vl_api_pg_interface_mode_t
+        return cls.create_pg_interfaces_internal(interfaces, gso, gso_size,
+                                                 pgmode.PG_API_MODE_IP4)
+
+    @classmethod
+    def create_pg_ip6_interfaces(cls, interfaces, gso=0, gso_size=0):
+        pgmode = VppEnum.vl_api_pg_interface_mode_t
+        return cls.create_pg_interfaces_internal(interfaces, gso, gso_size,
+                                                 pgmode.PG_API_MODE_IP6)
+
+    @classmethod
+    def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0):
+        pgmode = VppEnum.vl_api_pg_interface_mode_t
+        return cls.create_pg_interfaces_internal(interfaces, gso, gso_size,
+                                                 pgmode.PG_API_MODE_ETHERNET)
+
+    @classmethod
+    def create_pg_ethernet_interfaces(cls, interfaces, gso=0, gso_size=0):
+        pgmode = VppEnum.vl_api_pg_interface_mode_t
+        return cls.create_pg_interfaces_internal(interfaces, gso, gso_size,
+                                                 pgmode.PG_API_MODE_ETHERNET)
+
     @classmethod
     def create_loopback_interfaces(cls, count):
         """
diff --git a/test/test_pg.py b/test/test_pg.py
new file mode 100644 (file)
index 0000000..76b7fd7
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+import unittest
+
+import scapy.compat
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
+
+from framework import VppTestCase, VppTestRunner
+
+
+class TestPgTun(VppTestCase):
+    """ PG Test Case """
+
+    def setUp(self):
+        super(TestPgTun, self).setUp()
+
+        # create 3 pg interfaces - one each ethernet, ip4-tun, ip6-tun.
+        self.create_pg_interfaces(range(0, 1))
+        self.pg_interfaces += self.create_pg_ip4_interfaces(range(1, 2))
+        self.pg_interfaces += self.create_pg_ip6_interfaces(range(2, 3))
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+
+        for i in [self.pg0, self.pg1]:
+            i.config_ip4()
+
+        for i in [self.pg0, self.pg2]:
+            i.config_ip6()
+
+        self.pg0.resolve_arp()
+        self.pg0.resolve_ndp()
+
+    def tearDown(self):
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.admin_down()
+        super(TestPgTun, self).tearDown()
+
+    def test_pg_tun(self):
+        """ IP[46] Tunnel Mode PG """
+
+        #
+        # test that we can send and receive IP encap'd packets on the
+        # tun interfaces
+        #
+        N_PKTS = 31
+
+        # v4 tun to ethernet
+        p = (IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             UDP(sport=1234, dport=1234) /
+             Raw('0' * 48))
+
+        rxs = self.send_and_expect(self.pg1, p * N_PKTS, self.pg0)
+        for rx in rxs:
+            self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+            self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
+
+        # v6 tun to ethernet
+        p = (IPv6(src=self.pg2.remote_ip6, dst=self.pg0.remote_ip6) /
+             UDP(sport=1234, dport=1234) /
+             Raw('0' * 48))
+
+        rxs = self.send_and_expect(self.pg2, p * N_PKTS, self.pg0)
+        for rx in rxs:
+            self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+            self.assertEqual(rx[IPv6].dst, self.pg0.remote_ip6)
+
+        # eth to v4 tun
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             UDP(sport=1234, dport=1234) /
+             Raw('0' * 48))
+
+        rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1)
+        for rx in rxs:
+            rx = IP(rx)
+            self.assertFalse(rx.haslayer(Ether))
+            self.assertEqual(rx[IP].dst, self.pg1.remote_ip4)
+
+        # eth to v6 tun
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=self.pg2.remote_ip6) /
+             UDP(sport=1234, dport=1234) /
+             Raw('0' * 48))
+
+        rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg2)
+        for rx in rxs:
+            rx = IPv6(rx)
+            self.assertFalse(rx.haslayer(Ether))
+            self.assertEqual(rx[IPv6].dst, self.pg2.remote_ip6)
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
index 3bcde93..567b81d 100755 (executable)
@@ -9,6 +9,7 @@ import scapy.compat
 from scapy.utils import wrpcap, rdpcap, PcapReader
 from scapy.plist import PacketList
 from vpp_interface import VppInterface
+from vpp_papi import VppEnum
 
 from scapy.layers.l2 import Ether, ARP
 from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA,\
@@ -111,11 +112,11 @@ class VppPGInterface(VppInterface):
         self._out_history_counter += 1
         return v
 
-    def __init__(self, test, pg_index, gso, gso_size):
+    def __init__(self, test, pg_index, gso, gso_size, mode):
         """ Create VPP packet-generator interface """
         super().__init__(test)
 
-        r = test.vapi.pg_create_interface(pg_index, gso, gso_size)
+        r = test.vapi.pg_create_interface_v2(pg_index, gso, gso_size, mode)
         self.set_sw_if_index(r.sw_if_index)
 
         self._in_history_counter = 0