aboutsummaryrefslogtreecommitdiffstats
path: root/test/test_vm_vpp_interfaces.py
diff options
context:
space:
mode:
authorNaveen Joy <najoy@cisco.com>2022-10-04 14:22:05 -0700
committerDave Wallace <dwallacelf@gmail.com>2022-12-13 01:43:01 +0000
commite416893a597959509c7f667c140c271c0bb78c14 (patch)
treec294b99cfe1890415c8a14968caf358182b6a4ca /test/test_vm_vpp_interfaces.py
parentd3ccb0c2fba3e3f8b2eca7b2e1a1fe2877eb993b (diff)
tests: tapv2, tunv2 and af_packet interface tests for vpp
Tests gso/gro-coalesce features on tapv2, tunv2 and af_packet interfaces to ensure that packet transmission is enabled correctly for various MTU sizes and interface combinations in bridged and routed topologies for IPv4 and IPv6. Interface tests are dynamically generated at run time from the config file vm_test_config.py. Type: test Change-Id: I5f9d8cc80d20b4e34011fc8a87e35659bd9613bc Signed-off-by: Naveen Joy <najoy@cisco.com>
Diffstat (limited to 'test/test_vm_vpp_interfaces.py')
-rw-r--r--test/test_vm_vpp_interfaces.py579
1 files changed, 579 insertions, 0 deletions
diff --git a/test/test_vm_vpp_interfaces.py b/test/test_vm_vpp_interfaces.py
new file mode 100644
index 00000000000..7d902c5a1a8
--- /dev/null
+++ b/test/test_vm_vpp_interfaces.py
@@ -0,0 +1,579 @@
+#!/usr/bin/env python3
+import unittest
+from ipaddress import ip_address, ip_interface
+from vpp_qemu_utils import (
+ create_namespace,
+ delete_namespace,
+ create_host_interface,
+ delete_host_interfaces,
+ set_interface_mtu,
+ disable_interface_gso,
+ add_namespace_route,
+)
+from vpp_iperf import start_iperf, stop_iperf
+from framework import VppTestCase, VppTestRunner
+from config import config
+from vpp_papi import VppEnum
+import time
+import sys
+from vm_test_config import test_config
+
+#
+# Tests for:
+# - tapv2, tunv2 & af_packet_v2 & v3 interfaces.
+# - reads test config from the file vm_test_config.py
+# - Uses iPerf to send TCP/IP streams to VPP
+# - VPP ingress interface runs the iperf client
+# - VPP egress interface runs the iperf server
+# - Runs tests specified in the vm_test_config module and verifies that:
+# - TCP over IPv4 and IPv6 is enabled correctly for Bridged and Routed topologies.
+# sending jumbo frames (9000/9001 MTUs) with GSO/GRO is enabled correctly.
+# sending VPP buffer-sized frames(2048 MTU) with GSO/GRO is enabled correctly.
+# sending standard frames (1500 MTU) with GSO/GRO is enabled correctly.
+# sending smaller frames (512 MTU) with GSO/GRO is enabled correctly for IPv4
+# sending odd sized frames (9001, 2049 MTU) with GSO/GRO is enabled correctly.
+#
+
+
+def filter_tests(test):
+ """Filter test IDs to include only those selected to run."""
+ selection = test_config["tests_to_run"]
+ if not selection or selection == " ":
+ return True
+ else:
+ test_ids_to_run = []
+ for test_id in selection.split(","):
+ if "-" in test_id.strip():
+ start, end = map(int, test_id.split("-"))
+ test_ids_to_run.extend(list(range(start, end + 1)))
+ elif test_id.strip():
+ test_ids_to_run.append(int(test_id))
+ return test["id"] in test_ids_to_run
+
+
+# Test Config variables
+client_namespace = test_config["client_namespace"]
+server_namespace = test_config["server_namespace"]
+tests = filter(filter_tests, test_config["tests"])
+af_packet_config = test_config["af_packet"]
+layer2 = test_config["L2"]
+layer3 = test_config["L3"]
+
+
+def create_test(test_name, test, ip_version, mtu):
+ """Create and return a unittest method for a test."""
+
+ @unittest.skipUnless(config.extended, "part of extended tests")
+ def test_func(self):
+ self.logger.debug(f"Starting unittest:{test_name}")
+ self.setUpTestToplogy(test=test, ip_version=ip_version)
+ result = self.set_interfaces_mtu(
+ mtu=mtu,
+ ip_version=ip_version,
+ vpp_interfaces=self.vpp_interfaces,
+ linux_interfaces=self.linux_interfaces,
+ )
+ # Start the Iperf server in dual stack mode & run iperf client
+ if result is True:
+ start_iperf(ip_version=6, server_only=True, logger=self.logger)
+ self.assertTrue(
+ start_iperf(
+ ip_version=ip_version,
+ server_ipv4_address=self.server_ip4_address,
+ server_ipv6_address=self.server_ip6_address,
+ client_only=True,
+ duration=2,
+ logger=self.logger,
+ )
+ )
+ else:
+ print(
+ f"Skipping test:{test_name} as mtu:{mtu} is "
+ f"invalid for TCP/IPv{ip_version}"
+ )
+
+ test_func.__name__ = test_name
+ return test_func
+
+
+def generate_vpp_interface_tests():
+ """Generate unittests for testing vpp interfaces."""
+ for test in tests:
+ for ip_version in test_config["ip_versions"]:
+ for mtu in test_config["mtus"]:
+ test_name = (
+ f"test_id_{test['id']}_"
+ + f"client_{test['client_if_type']}"
+ + f"_v{test['client_if_version']}_"
+ + f"gso_{test.get('client_if_gso', 0)}_"
+ + f"gro_{test.get('client_if_gro', 0)}_"
+ + f"to_server_{test['server_if_type']}"
+ + f"_v{test['server_if_version']}_"
+ + f"gso_{test.get('server_if_gso', 0)}_"
+ + f"gro_{test.get('server_if_gro', 0)}_"
+ + f"mtu_{mtu}_mode_{test['x_connect_mode']}_"
+ + f"tcp_ipv{ip_version}"
+ )
+ test_func = create_test(
+ test_name=test_name, test=test, ip_version=ip_version, mtu=mtu
+ )
+ setattr(TestVPPInterfacesQemu, test_name, test_func)
+
+
+class TestVPPInterfacesQemu(VppTestCase):
+ """Test VPP interfaces inside a QEMU VM for IPv4/v6.
+
+ Test Setup:
+ Linux_ns1--iperfClient--host-int1--vpp-af_packet-int1--VPP-BD
+ --vppaf_packet_int2--host-int2--iperfServer--Linux_ns2
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestVPPInterfacesQemu, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestVPPInterfacesQemu, cls).tearDownClass()
+
+ def setUpTestToplogy(self, test, ip_version):
+ """Setup the test topology.
+
+ 1. Create Linux Namespaces for iPerf Client & Server.
+ 2. Create VPP iPerf client and server virtual interfaces.
+ 3. Enable desired vif features such as GSO & GRO.
+ 3. Cross-Connect interfaces in VPP using L2 or L3.
+ """
+ super(TestVPPInterfacesQemu, self).setUp()
+ client_if_type = test["client_if_type"]
+ server_if_type = test["server_if_type"]
+ client_if_version = test["client_if_version"]
+ server_if_version = test["server_if_version"]
+ x_connect_mode = test["x_connect_mode"]
+ # server ip4/ip6 addresses required by iperf client
+ server_ip4_prefix = (
+ layer2["server_ip4_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["server_ip4_prefix"]
+ )
+ server_ip6_prefix = (
+ layer2["server_ip6_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["server_ip6_prefix"]
+ )
+ self.server_ip4_address = str(ip_interface(server_ip4_prefix).ip)
+ self.server_ip6_address = str(ip_interface(server_ip6_prefix).ip)
+ # next-hop IP address on VPP for routing from client & server namespaces
+ vpp_client_prefix = (
+ layer3["vpp_client_ip4_prefix"]
+ if ip_version == 4
+ else layer3["vpp_client_ip6_prefix"]
+ )
+ vpp_client_nexthop = str(ip_interface(vpp_client_prefix).ip)
+ vpp_server_prefix = (
+ layer3["vpp_server_ip4_prefix"]
+ if ip_version == 4
+ else layer3["vpp_server_ip6_prefix"]
+ )
+ vpp_server_nexthop = str(ip_interface(vpp_server_prefix).ip)
+ create_namespace([client_namespace, server_namespace])
+ self.vpp_interfaces = []
+ self.linux_interfaces = []
+ enable_client_if_gso = test.get("client_if_gso", 0)
+ enable_server_if_gso = test.get("server_if_gso", 0)
+ enable_client_if_gro = test.get("client_if_gro", 0)
+ enable_server_if_gro = test.get("server_if_gro", 0)
+ ## Handle client interface types
+ if client_if_type == "af_packet":
+ create_host_interface(
+ af_packet_config["iprf_client_interface_on_linux"],
+ af_packet_config["iprf_client_interface_on_vpp"],
+ client_namespace,
+ layer2["client_ip4_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["client_ip4_prefix"],
+ layer2["client_ip6_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["client_ip6_prefix"],
+ )
+ self.ingress_if_idx = self.create_af_packet(
+ version=client_if_version,
+ host_if_name=af_packet_config["iprf_client_interface_on_vpp"],
+ enable_gso=enable_client_if_gso,
+ )
+ self.vpp_interfaces.append(self.ingress_if_idx)
+ self.linux_interfaces.append(
+ ["", af_packet_config["iprf_client_interface_on_vpp"]]
+ )
+ self.linux_interfaces.append(
+ [client_namespace, af_packet_config["iprf_client_interface_on_linux"]]
+ )
+ if enable_client_if_gso == 0:
+ disable_interface_gso(
+ "", af_packet_config["iprf_client_interface_on_vpp"]
+ )
+ disable_interface_gso(
+ client_namespace, af_packet_config["iprf_client_interface_on_linux"]
+ )
+ elif client_if_type == "tap" or client_if_type == "tun":
+ self.ingress_if_idx = self.create_tap_tun(
+ id=101,
+ host_namespace=client_namespace,
+ ip_version=ip_version,
+ host_ip4_prefix=layer2["client_ip4_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["client_ip4_prefix"],
+ host_ip6_prefix=layer2["client_ip6_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["client_ip6_prefix"],
+ host_ip4_gw=vpp_client_nexthop
+ if x_connect_mode == "L3" and ip_version == 4
+ else None,
+ host_ip6_gw=vpp_client_nexthop
+ if x_connect_mode == "L3" and ip_version == 6
+ else None,
+ int_type=client_if_type,
+ host_if_name=f"{client_if_type}0",
+ enable_gso=enable_client_if_gso,
+ enable_gro=enable_client_if_gro,
+ )
+ self.vpp_interfaces.append(self.ingress_if_idx)
+ self.linux_interfaces.append([client_namespace, f"{client_if_type}0"])
+ # Seeing TCP timeouts if tx=on & rx=on Linux tap & tun interfaces
+ disable_interface_gso(client_namespace, f"{client_if_type}0")
+ else:
+ print(
+ f"Unsupported client interface type: {client_if_type} "
+ f"for test - ID={test['id']}"
+ )
+ sys.exit(1)
+
+ if server_if_type == "af_packet":
+ create_host_interface(
+ af_packet_config["iprf_server_interface_on_linux"],
+ af_packet_config["iprf_server_interface_on_vpp"],
+ server_namespace,
+ server_ip4_prefix,
+ server_ip6_prefix,
+ )
+ self.egress_if_idx = self.create_af_packet(
+ version=server_if_version,
+ host_if_name=af_packet_config["iprf_server_interface_on_vpp"],
+ enable_gso=enable_server_if_gso,
+ )
+ self.vpp_interfaces.append(self.egress_if_idx)
+ self.linux_interfaces.append(
+ ["", af_packet_config["iprf_server_interface_on_vpp"]]
+ )
+ self.linux_interfaces.append(
+ [server_namespace, af_packet_config["iprf_server_interface_on_linux"]]
+ )
+ if enable_server_if_gso == 0:
+ disable_interface_gso(
+ "", af_packet_config["iprf_server_interface_on_vpp"]
+ )
+ disable_interface_gso(
+ server_namespace, af_packet_config["iprf_server_interface_on_linux"]
+ )
+ elif server_if_type == "tap" or server_if_type == "tun":
+ self.egress_if_idx = self.create_tap_tun(
+ id=102,
+ host_namespace=server_namespace,
+ ip_version=ip_version,
+ host_ip4_prefix=layer2["server_ip4_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["server_ip4_prefix"],
+ host_ip6_prefix=layer2["server_ip6_prefix"]
+ if x_connect_mode == "L2"
+ else layer3["server_ip6_prefix"],
+ int_type=server_if_type,
+ host_if_name=f"{server_if_type}0",
+ enable_gso=enable_server_if_gso,
+ enable_gro=enable_server_if_gro,
+ )
+ self.vpp_interfaces.append(self.egress_if_idx)
+ self.linux_interfaces.append([server_namespace, f"{server_if_type}0"])
+ # Seeing TCP timeouts if tx=on & rx=on Linux tap & tun interfaces
+ disable_interface_gso(server_namespace, f"{server_if_type}0")
+ else:
+ print(
+ f"Unsupported server interface type: {server_if_type} "
+ f"for test - ID={test['id']}"
+ )
+ sys.exit(1)
+
+ if x_connect_mode == "L2":
+ self.l2_connect_interfaces(1, self.ingress_if_idx, self.egress_if_idx)
+ elif x_connect_mode == "L3":
+ # L3 connect client & server side
+ vrf_id = layer3["ip4_vrf"] if ip_version == 4 else layer3["ip6_vrf"]
+ self.l3_connect_interfaces(
+ ip_version,
+ vrf_id,
+ (self.ingress_if_idx, vpp_client_prefix),
+ (self.egress_if_idx, vpp_server_prefix),
+ )
+ # Setup namespace routing
+ if ip_version == 4:
+ add_namespace_route(client_namespace, "0.0.0.0/0", vpp_client_nexthop)
+ add_namespace_route(server_namespace, "0.0.0.0/0", vpp_server_nexthop)
+ else:
+ add_namespace_route(client_namespace, "::/0", vpp_client_nexthop)
+ add_namespace_route(server_namespace, "::/0", vpp_server_nexthop)
+ # Wait for Linux IPv6 stack to become ready
+ if ip_version == 6:
+ time.sleep(2)
+
+ def tearDown(self):
+ try:
+ self.vapi.tap_delete_v2(self.ingress_if_idx)
+ except Exception:
+ pass
+ try:
+ self.vapi.tap_delete_v2(self.egress_if_idx)
+ except Exception:
+ pass
+ try:
+ for interface in self.vapi.af_packet_dump():
+ if (
+ interface.host_if_name
+ == af_packet_config["iprf_client_interface_on_vpp"]
+ ):
+ self.vapi.af_packet_delete(
+ af_packet_config["iprf_client_interface_on_vpp"]
+ )
+ elif (
+ interface.host_if_name
+ == af_packet_config["iprf_server_interface_on_vpp"]
+ ):
+ self.vapi.af_packet_delete(
+ af_packet_config["iprf_server_interface_on_vpp"]
+ )
+ except Exception:
+ pass
+ try:
+ delete_host_interfaces(
+ af_packet_config["iprf_client_interface_on_linux"],
+ af_packet_config["iprf_server_interface_on_linux"],
+ af_packet_config["iprf_client_interface_on_vpp"],
+ af_packet_config["iprf_server_interface_on_vpp"],
+ )
+ except Exception:
+ pass
+ try:
+ self.vapi.ip_table_add_del(is_add=0, table={"table_id": layer3["ip4_vrf"]})
+ except Exception:
+ pass
+ try:
+ self.vapi.ip_table_add_del(is_add=0, table={"table_id": layer3["ip6_vrf"]})
+ except Exception:
+ pass
+ try:
+ delete_namespace(
+ [
+ client_namespace,
+ server_namespace,
+ ]
+ )
+ except Exception:
+ pass
+ try:
+ stop_iperf()
+ except Exception:
+ pass
+
+ def create_af_packet(self, version, host_if_name, enable_gso=0):
+ """Create an af_packetv3 interface in VPP.
+
+ Parameters:
+ version -- 2 for af_packet_create_v2
+ -- 3 for af_packet_create_v3
+ host_if_name -- host interface name
+ enable_gso -- Enable GSO on the interface when True
+ """
+ af_packet_mode = VppEnum.vl_api_af_packet_mode_t
+ af_packet_interface_mode = af_packet_mode.AF_PACKET_API_MODE_ETHERNET
+ af_packet_flags = VppEnum.vl_api_af_packet_flags_t
+ af_packet_interface_flags = af_packet_flags.AF_PACKET_API_FLAG_QDISC_BYPASS
+ if enable_gso:
+ af_packet_interface_flags = (
+ af_packet_interface_flags | af_packet_flags.AF_PACKET_API_FLAG_CKSUM_GSO
+ )
+ if version == 2:
+ af_packet_interface_flags = (
+ af_packet_interface_flags | af_packet_flags.AF_PACKET_API_FLAG_VERSION_2
+ )
+ api_args = {
+ "use_random_hw_addr": True,
+ "host_if_name": host_if_name,
+ "flags": af_packet_interface_flags,
+ }
+ api_args["mode"] = af_packet_interface_mode
+ result = self.vapi.af_packet_create_v3(**api_args)
+ sw_if_index = result.sw_if_index
+ # Enable software GSO chunking when interface doesn't support GSO offload
+ if enable_gso == 0:
+ self.vapi.feature_gso_enable_disable(
+ sw_if_index=sw_if_index, enable_disable=1
+ )
+ else:
+ self.vapi.feature_gso_enable_disable(
+ sw_if_index=sw_if_index, enable_disable=0
+ )
+ self.vapi.sw_interface_set_flags(sw_if_index=sw_if_index, flags=1)
+ return sw_if_index
+
+ def create_tap_tun(
+ self,
+ id,
+ host_namespace,
+ ip_version,
+ host_ip4_prefix=None,
+ host_ip6_prefix=None,
+ host_ip4_gw=None,
+ host_ip6_gw=None,
+ int_type="tap",
+ host_if_name=None,
+ enable_gso=0,
+ enable_gro=0,
+ ):
+ """Create a tapv2 or tunv2 interface in VPP and attach to host.
+
+ Parameters:
+ id -- interface ID
+ host_namespace -- host namespace to attach the tap/tun interface to
+ ip_version -- 4 or 6
+ host_ip4_prefix -- ipv4 host interface address in CIDR notation
+ if ip_version=4
+ host_ip6_prefix -- ipv6 host interface address in CIDR notation
+ if ip_version=6
+ host_ip4_gw -- host IPv4 default gateway IP Address
+ host_ip6_gw -- host IPv6 default gateway IP address
+ int_type -- "tap" for tapv2 & "tun" for tunv2 interface
+ host_if_name -- host side interface name
+ enable_gso -- enable GSO
+ enable_gro -- enable GSO/GRO-Coalesce
+ """
+ TapFlags = VppEnum.vl_api_tap_flags_t
+ tap_flags = 0
+ if int_type == "tun":
+ tap_flags = TapFlags.TAP_API_FLAG_TUN
+ if enable_gro:
+ tap_flags = tap_flags | (
+ TapFlags.TAP_API_FLAG_GSO | TapFlags.TAP_API_FLAG_GRO_COALESCE
+ )
+ elif enable_gso:
+ tap_flags = tap_flags | TapFlags.TAP_API_FLAG_GSO
+ elif int_type == "tap":
+ if enable_gro:
+ tap_flags = (
+ TapFlags.TAP_API_FLAG_GSO | TapFlags.TAP_API_FLAG_GRO_COALESCE
+ )
+ elif enable_gso:
+ tap_flags = TapFlags.TAP_API_FLAG_GSO
+
+ api_args = {
+ "id": id,
+ "host_namespace_set": True,
+ "host_namespace": host_namespace,
+ "host_if_name_set": False,
+ "host_bridge_set": False,
+ "host_mac_addr_set": False,
+ }
+ if tap_flags != 0:
+ api_args["tap_flags"] = tap_flags
+ if ip_version == 4:
+ api_args["host_ip4_prefix"] = ip_interface(host_ip4_prefix)
+ api_args["host_ip4_prefix_set"] = True
+ if host_ip4_gw:
+ api_args["host_ip4_gw"] = ip_address(host_ip4_gw)
+ api_args["host_ip4_gw_set"] = True
+ if ip_version == 6:
+ api_args["host_ip6_prefix"] = ip_interface(host_ip6_prefix)
+ api_args["host_ip6_prefix_set"] = True
+ if host_ip6_gw:
+ api_args["host_ip6_gw"] = ip_address(host_ip6_gw)
+ api_args["host_ip6_gw_set"] = True
+ if host_if_name:
+ api_args["host_if_name"] = host_if_name
+ api_args["host_if_name_set"] = True
+
+ result = self.vapi.tap_create_v2(**api_args)
+ sw_if_index = result.sw_if_index
+ # Enable software GSO chunking when interface doesn't support GSO offload and
+ # GRO coalesce
+ if enable_gso == 0 and enable_gro == 0:
+ self.vapi.feature_gso_enable_disable(
+ sw_if_index=sw_if_index, enable_disable=1
+ )
+ else:
+ self.vapi.feature_gso_enable_disable(
+ sw_if_index=sw_if_index, enable_disable=0
+ )
+ # Admin up
+ self.vapi.sw_interface_set_flags(sw_if_index=sw_if_index, flags=1)
+ return sw_if_index
+
+ def dump_bridge_domain_details(self, bd_id):
+ return self.vapi.bridge_domain_dump(bd_id=bd_id)
+
+ def l2_connect_interfaces(self, bridge_id, *sw_if_idxs):
+ for if_idx in sw_if_idxs:
+ self.vapi.sw_interface_set_l2_bridge(
+ rx_sw_if_index=if_idx, bd_id=bridge_id, shg=0, port_type=0, enable=True
+ )
+
+ def l3_connect_interfaces(self, ip_version, vrf_id, *if_idx_ip_prefixes):
+ """Setup routing for (if_idx, ip_prefix) inside VPP.
+
+ arguments:
+ if_idx_ip_prefixes -- sequence of (if_idx, ip_prefix) tuples
+ ip_version -- 4 or 6
+ vrf_id -- vrf_id
+ """
+ is_ipv6 = 0 if ip_version == 4 else 1
+ self.vapi.ip_table_add_del(
+ is_add=1, table={"table_id": vrf_id, "is_ip6": is_ipv6}
+ )
+ for sw_if_index, ip_prefix in if_idx_ip_prefixes:
+ self.vapi.sw_interface_set_table(
+ sw_if_index=sw_if_index, is_ipv6=is_ipv6, vrf_id=vrf_id
+ )
+ self.vapi.sw_interface_add_del_address(
+ sw_if_index=sw_if_index, is_add=1, prefix=ip_interface(ip_prefix)
+ )
+
+ def set_interfaces_mtu(self, mtu, ip_version, **kwargs):
+ """Set MTUs on VPP and Linux interfaces.
+
+ arguments --
+ mtu -- mtu value
+ ip_version - 4 or 6
+ kwargs['vpp_interfaces'] -- list of vpp interface if indexes
+ kwargs['linux_interfaces'] -- list of tuples (namespace, interface_names)
+ return True if mtu is set, else False
+ """
+ vpp_interfaces = kwargs.get("vpp_interfaces")
+ linux_interfaces = kwargs.get("linux_interfaces")
+ # IPv6 on Linux requires an MTU value >=1280
+ if (ip_version == 6 and mtu >= 1280) or ip_version == 4:
+ for sw_if_idx in vpp_interfaces:
+ self.vapi.sw_interface_set_mtu(
+ sw_if_index=sw_if_idx, mtu=[mtu, 0, 0, 0]
+ )
+ for namespace, interface_name in linux_interfaces:
+ set_interface_mtu(
+ namespace=namespace,
+ interface=interface_name,
+ mtu=mtu,
+ logger=self.logger,
+ )
+ return True
+ else:
+ return False
+
+
+generate_vpp_interface_tests()
+
+if __name__ == "__main__":
+ unittest.main(testRunner=VppTestRunner)