summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/drivers/net/lib/py/env.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/drivers/net/lib/py/env.py')
-rw-r--r--tools/testing/selftests/drivers/net/lib/py/env.py275
1 files changed, 250 insertions, 25 deletions
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index ad5ff645183a..24ce122abd9c 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -1,18 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
+import ipaddress
import os
import time
+import json
from pathlib import Path
from lib.py import KsftSkipEx, KsftXfailEx
-from lib.py import ksft_setup
+from lib.py import ksft_setup, wait_file
from lib.py import cmd, ethtool, ip, CmdExitFailure
from lib.py import NetNS, NetdevSimDev
from .remote import Remote
+from . import bpftool, RtnlFamily, Netlink
class NetDrvEnvBase:
"""
- Base class for a NIC / host envirnoments
+ Base class for a NIC / host environments
Attributes:
test_dir: Path to the source directory of the test
@@ -25,6 +28,9 @@ class NetDrvEnvBase:
self.env = self._load_env_file()
+ # Following attrs must be set be inheriting classes
+ self.dev = None
+
def _load_env_file(self):
env = os.environ.copy()
@@ -48,6 +54,22 @@ class NetDrvEnvBase:
env[pair[0]] = pair[1]
return ksft_setup(env)
+ def __del__(self):
+ pass
+
+ def __enter__(self):
+ ip(f"link set dev {self.dev['ifname']} up")
+ wait_file(f"/sys/class/net/{self.dev['ifname']}/carrier",
+ lambda x: x.strip() == "1")
+
+ return self
+
+ def __exit__(self, ex_type, ex_value, ex_tb):
+ """
+ __exit__ gets called at the end of a "with" block.
+ """
+ self.__del__()
+
class NetDrvEnv(NetDrvEnvBase):
"""
@@ -72,17 +94,6 @@ class NetDrvEnv(NetDrvEnvBase):
self.ifname = self.dev['ifname']
self.ifindex = self.dev['ifindex']
- def __enter__(self):
- ip(f"link set dev {self.dev['ifname']} up")
-
- return self
-
- def __exit__(self, ex_type, ex_value, ex_tb):
- """
- __exit__ gets called at the end of a "with" block.
- """
- self.__del__()
-
def __del__(self):
if self._ns:
self._ns.remove()
@@ -160,6 +171,9 @@ class NetDrvEpEnv(NetDrvEnvBase):
# resolve remote interface name
self.remote_ifname = self.resolve_remote_ifc()
+ self.remote_dev = ip("-d link show dev " + self.remote_ifname,
+ host=self.remote, json=True)[0]
+ self.remote_ifindex = self.remote_dev['ifindex']
self._required_cmd = {}
@@ -219,15 +233,6 @@ class NetDrvEpEnv(NetDrvEnvBase):
raise Exception("Can't resolve remote interface name, multiple interfaces match")
return v6[0]["ifname"] if v6 else v4[0]["ifname"]
- def __enter__(self):
- return self
-
- def __exit__(self, ex_type, ex_value, ex_tb):
- """
- __exit__ gets called at the end of a "with" block.
- """
- self.__del__()
-
def __del__(self):
if self._ns:
self._ns.remove()
@@ -246,6 +251,22 @@ class NetDrvEpEnv(NetDrvEnvBase):
if not self.addr_v[ipver] or not self.remote_addr_v[ipver]:
raise KsftSkipEx(f"Test requires IPv{ipver} connectivity")
+ def require_nsim(self, nsim_test=True):
+ """Require or exclude netdevsim for this test"""
+ if nsim_test and self._ns is None:
+ raise KsftXfailEx("Test only works on netdevsim")
+ if nsim_test is False and self._ns is not None:
+ raise KsftXfailEx("Test does not work on netdevsim")
+
+ def get_local_nsim_dev(self):
+ """Returns the local netdevsim device or None.
+ Using this method is discouraged, as it makes tests nsim-specific.
+ Standard interfaces available on all HW should ideally be used.
+ This method is intended for the few cases where nsim-specific
+ assertions need to be verified which cannot be verified otherwise.
+ """
+ return self._ns
+
def _require_cmd(self, comm, key, host=None):
cached = self._required_cmd.get(comm, {})
if cached.get(key) is None:
@@ -259,7 +280,7 @@ class NetDrvEpEnv(NetDrvEnvBase):
if not self._require_cmd(comm, "local"):
raise KsftSkipEx("Test requires command: " + comm)
if remote:
- if not self._require_cmd(comm, "remote"):
+ if not self._require_cmd(comm, "remote", host=self.remote):
raise KsftSkipEx("Test requires (remote) command: " + comm)
def wait_hw_stats_settle(self):
@@ -276,7 +297,211 @@ class NetDrvEpEnv(NetDrvEnvBase):
if "Operation not supported" not in e.cmd.stderr:
raise
- self._stats_settle_time = 0.025 + \
- data.get('stats-block-usecs', 0) / 1000 / 1000
+ self._stats_settle_time = \
+ 1.25 * data.get('stats-block-usecs', 20000) / 1000 / 1000
time.sleep(self._stats_settle_time)
+
+
+class NetDrvContEnv(NetDrvEpEnv):
+ """
+ Class for an environment with a netkit pair setup for forwarding traffic
+ between the physical interface and a network namespace.
+ NETIF = "eth0"
+ LOCAL_V6 = "2001:db8:1::1"
+ REMOTE_V6 = "2001:db8:1::2"
+ LOCAL_PREFIX_V6 = "2001:db8:2::0/64"
+
+ +-----------------------------+ +------------------------------+
+ dst | INIT NS | | TEST NS |
+ 2001: | +---------------+ | | |
+ db8:2::2| | NETIF | | bpf | |
+ +---|>| 2001:db8:1::1 | |redirect| +-------------------------+ |
+ | | | |-----------|--------|>| Netkit | |
+ | | +---------------+ | _peer | | nk_guest | |
+ | | +-------------+ Netkit pair | | | fe80::2/64 | |
+ | | | Netkit |.............|........|>| 2001:db8:2::2/64 | |
+ | | | nk_host | | | +-------------------------+ |
+ | | | fe80::1/64 | | | |
+ | | +-------------+ | | route: |
+ | | | | default |
+ | | route: | | via fe80::1 dev nk_guest |
+ | | 2001:db8:2::2/128 | +------------------------------+
+ | | via fe80::2 dev nk_host |
+ | +-----------------------------+
+ |
+ | +---------------+
+ | | REMOTE |
+ +---| 2001:db8:1::2 |
+ +---------------+
+ """
+
+ def __init__(self, src_path, rxqueues=1, **kwargs):
+ self.netns = None
+ self._nk_host_ifname = None
+ self._nk_guest_ifname = None
+ self._tc_clsact_added = False
+ self._tc_attached = False
+ self._bpf_prog_pref = None
+ self._bpf_prog_id = None
+ self._init_ns_attached = False
+ self._old_fwd = None
+ self._old_accept_ra = None
+
+ super().__init__(src_path, **kwargs)
+
+ self.require_ipver("6")
+ local_prefix = self.env.get("LOCAL_PREFIX_V6")
+ if not local_prefix:
+ raise KsftSkipEx("LOCAL_PREFIX_V6 required")
+
+ net = ipaddress.IPv6Network(local_prefix, strict=False)
+ self.ipv6_prefix = str(net.network_address)
+ self.nk_host_ipv6 = f"{self.ipv6_prefix}2:1"
+ self.nk_guest_ipv6 = f"{self.ipv6_prefix}2:2"
+
+ local_v6 = ipaddress.IPv6Address(self.addr_v["6"])
+ if local_v6 in net:
+ raise KsftSkipEx("LOCAL_V6 must not fall within LOCAL_PREFIX_V6")
+
+ rtnl = RtnlFamily()
+ rtnl.newlink(
+ {
+ "linkinfo": {
+ "kind": "netkit",
+ "data": {
+ "mode": "l2",
+ "policy": "forward",
+ "peer-policy": "forward",
+ },
+ },
+ "num-rx-queues": rxqueues,
+ },
+ flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
+ )
+
+ all_links = ip("-d link show", json=True)
+ netkit_links = [link for link in all_links
+ if link.get('linkinfo', {}).get('info_kind') == 'netkit'
+ and 'UP' not in link.get('flags', [])]
+
+ if len(netkit_links) != 2:
+ raise KsftSkipEx("Failed to create netkit pair")
+
+ netkit_links.sort(key=lambda x: x['ifindex'])
+ self._nk_host_ifname = netkit_links[1]['ifname']
+ self._nk_guest_ifname = netkit_links[0]['ifname']
+ self.nk_host_ifindex = netkit_links[1]['ifindex']
+ self.nk_guest_ifindex = netkit_links[0]['ifindex']
+
+ self._setup_ns()
+ self._attach_bpf()
+
+ def __del__(self):
+ if self._tc_attached:
+ cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
+ self._tc_attached = False
+
+ if self._tc_clsact_added:
+ cmd(f"tc qdisc del dev {self.ifname} clsact")
+ self._tc_clsact_added = False
+
+ if self._nk_host_ifname:
+ cmd(f"ip link del dev {self._nk_host_ifname}")
+ self._nk_host_ifname = None
+ self._nk_guest_ifname = None
+
+ if self._init_ns_attached:
+ cmd("ip netns del init", fail=False)
+ self._init_ns_attached = False
+
+ if self.netns:
+ del self.netns
+ self.netns = None
+
+ if self._old_fwd is not None:
+ with open("/proc/sys/net/ipv6/conf/all/forwarding", "w",
+ encoding="utf-8") as f:
+ f.write(self._old_fwd)
+ self._old_fwd = None
+ if self._old_accept_ra is not None:
+ with open("/proc/sys/net/ipv6/conf/all/accept_ra", "w",
+ encoding="utf-8") as f:
+ f.write(self._old_accept_ra)
+ self._old_accept_ra = None
+
+ super().__del__()
+
+ def _setup_ns(self):
+ fwd_path = "/proc/sys/net/ipv6/conf/all/forwarding"
+ ra_path = "/proc/sys/net/ipv6/conf/all/accept_ra"
+ with open(fwd_path, encoding="utf-8") as f:
+ self._old_fwd = f.read().strip()
+ with open(ra_path, encoding="utf-8") as f:
+ self._old_accept_ra = f.read().strip()
+ with open(fwd_path, "w", encoding="utf-8") as f:
+ f.write("1")
+ with open(ra_path, "w", encoding="utf-8") as f:
+ f.write("2")
+
+ self.netns = NetNS()
+ cmd("ip netns attach init 1")
+ self._init_ns_attached = True
+ ip("netns set init 0", ns=self.netns)
+ ip(f"link set dev {self._nk_guest_ifname} netns {self.netns.name}")
+ ip(f"link set dev {self._nk_host_ifname} up")
+ ip(f"-6 addr add fe80::1/64 dev {self._nk_host_ifname} nodad")
+ ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self._nk_host_ifname}")
+
+ ip("link set lo up", ns=self.netns)
+ ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns)
+ ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns)
+ ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
+ ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)
+
+ def _tc_ensure_clsact(self):
+ qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
+ for q in qdisc:
+ if q['kind'] == 'clsact':
+ return
+ cmd(f"tc qdisc add dev {self.ifname} clsact")
+ self._tc_clsact_added = True
+
+ def _get_bpf_prog_ids(self):
+ filters = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout)
+ for bpf in filters:
+ if 'options' not in bpf:
+ continue
+ if bpf['options']['bpf_name'].startswith('nk_forward.bpf'):
+ return (bpf['pref'], bpf['options']['prog']['id'])
+ raise Exception("Failed to get BPF prog ID")
+
+ def _attach_bpf(self):
+ bpf_obj = self.test_dir / "nk_forward.bpf.o"
+ if not bpf_obj.exists():
+ raise KsftSkipEx("BPF prog not found")
+
+ self._tc_ensure_clsact()
+ cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj}"
+ " sec tc/ingress direct-action")
+ self._tc_attached = True
+
+ (self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids()
+ prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True)
+ map_ids = prog_info.get("map_ids", [])
+
+ bss_map_id = None
+ for map_id in map_ids:
+ map_info = bpftool(f"map show id {map_id}", json=True)
+ if map_info.get("name").endswith("bss"):
+ bss_map_id = map_id
+
+ if bss_map_id is None:
+ raise Exception("Failed to find .bss map")
+
+ ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix)
+ ipv6_bytes = ipv6_addr.packed
+ ifindex_bytes = self.nk_host_ifindex.to_bytes(4, byteorder='little')
+ value = ipv6_bytes + ifindex_bytes
+ value_hex = ' '.join(f'{b:02x}' for b in value)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")