From 6197cb730e1571ca69859489c0ae7ea90a5c5fd4 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Thu, 3 Jun 2021 14:43:21 +0000 Subject: [PATCH] pg: A Tunnel mode variant of a pg interface Type: feature this allows VPP to simulate linux tun devices. Signed-off-by: Neale Ranns Change-Id: I3adf38b49a254804370f78edd5d275d192fd00a6 --- src/vnet/pg/cli.c | 34 ++++++++++++++--- src/vnet/pg/pg.api | 22 +++++++++++ src/vnet/pg/pg.h | 14 +++++-- src/vnet/pg/pg_api.c | 35 ++++++++++++----- src/vnet/pg/stream.c | 46 +++++++++++++++++++---- test/framework.py | 30 ++++++++++++++- test/test_pg.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ test/vpp_pg_interface.py | 5 ++- 8 files changed, 256 insertions(+), 28 deletions(-) create mode 100644 test/test_pg.py diff --git a/src/vnet/pg/cli.c b/src/vnet/pg/cli.c index 9da0f8d9981..e57e72573f3 100644 --- a/src/vnet/pg/cli.c +++ b/src/vnet/pg/cli.c @@ -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); diff --git a/src/vnet/pg/pg.api b/src/vnet/pg/pg.api index 3a44f1d87a7..3630e0c2f0d 100644 --- a/src/vnet/pg/pg.api +++ b/src/vnet/pg/pg.api @@ -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 diff --git a/src/vnet/pg/pg.h b/src/vnet/pg/pg.h index da5af25b22e..ffa2f8a82f4 100644 --- a/src/vnet/pg/pg.h +++ b/src/vnet/pg/pg.h @@ -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) diff --git a/src/vnet/pg/pg_api.c b/src/vnet/pg/pg_api.c index 554e8ea31c1..b3eb315e807 100644 --- a/src/vnet/pg/pg_api.c +++ b/src/vnet/pg/pg_api.c @@ -40,12 +40,13 @@ #include - -#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) diff --git a/src/vnet/pg/stream.c b/src/vnet/pg/stream.c index 0ce640bf13f..686627b8d9a 100644 --- a/src/vnet/pg/stream.c +++ b/src/vnet/pg/stream.c @@ -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) { diff --git a/test/framework.py b/test/framework.py index c2a2fc68430..2afec7a4f0f 100755 --- a/test/framework.py +++ b/test/framework.py @@ -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 index 00000000000..76b7fd7f1d9 --- /dev/null +++ b/test/test_pg.py @@ -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) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index 3bcde9319a8..567b81de7c0 100755 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -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 -- 2.16.6