Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'selftests-net-add-netkit-container-env-and-test'

David Wei says:

====================
selftests/net: add netkit container env and test

Add a new Python selftest env NetDrvContEnv that sets up a pair of
netkit netdevs, with one inside of a netns, and a bpf prog that forwards
skbs from NETIF to the netkit inside the netns.

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 |
+---------------+

I will use this series for queue leasing selftests. Include a basic ping
test in this series as demonstration.
====================

Link: https://patch.msgid.link/20260305181803.2912736-1-dw@davidwei.uk
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+347 -12
+38
tools/testing/selftests/drivers/net/README.rst
··· 66 66 67 67 Local and remote endpoint IP addresses. 68 68 69 + LOCAL_PREFIX_V6 70 + ~~~~~~~~~~~~~~~ 71 + 72 + Local IP prefix/subnet which can be used to allocate extra IP addresses (for 73 + network name spaces behind macvlan, veth, netkit devices). DUT must be 74 + reachable using these addresses from the endpoint. 75 + 76 + LOCAL_PREFIX_V6 must NOT match LOCAL_V6. 77 + 78 + Example: 79 + NETIF = "eth0" 80 + LOCAL_V6 = "2001:db8:1::1" 81 + REMOTE_V6 = "2001:db8:1::2" 82 + LOCAL_PREFIX_V6 = "2001:db8:2::0/64" 83 + 84 + +-----------------------------+ +------------------------------+ 85 + dst | INIT NS | | TEST NS | 86 + 2001: | +---------------+ | | | 87 + db8:2::2| | NETIF | | bpf | | 88 + +---|>| 2001:db8:1::1 | |redirect| +-------------------------+ | 89 + | | | |-----------|--------|>| Netkit | | 90 + | | +---------------+ | _peer | | nk_guest | | 91 + | | +-------------+ Netkit pair | | | fe80::2/64 | | 92 + | | | Netkit |.............|........|>| 2001:db8:2::2/64 | | 93 + | | | nk_host | | | +-------------------------+ | 94 + | | | fe80::1/64 | | | | 95 + | | +-------------+ | | route: | 96 + | | | | default | 97 + | | route: | | via fe80::1 dev nk_guest | 98 + | | 2001:db8:2::2/128 | +------------------------------+ 99 + | | via fe80::2 dev nk_host | 100 + | +-----------------------------+ 101 + | 102 + | +---------------+ 103 + | | REMOTE | 104 + +---| 2001:db8:1::2 | 105 + +---------------+ 106 + 69 107 REMOTE_TYPE 70 108 ~~~~~~~~~~~ 71 109
+1
tools/testing/selftests/drivers/net/hw/Makefile
··· 32 32 irq.py \ 33 33 loopback.sh \ 34 34 nic_timestamp.py \ 35 + nk_netns.py \ 35 36 pp_alloc_fail.py \ 36 37 rss_api.py \ 37 38 rss_ctx.py \
+3
tools/testing/selftests/drivers/net/hw/config
··· 1 + CONFIG_BPF_SYSCALL=y 1 2 CONFIG_FAIL_FUNCTION=y 2 3 CONFIG_FAULT_INJECTION=y 3 4 CONFIG_FAULT_INJECTION_DEBUG_FS=y ··· 6 5 CONFIG_IO_URING=y 7 6 CONFIG_IPV6=y 8 7 CONFIG_IPV6_GRE=y 8 + CONFIG_NET_CLS_BPF=y 9 9 CONFIG_NET_IPGRE=y 10 10 CONFIG_NET_IPGRE_DEMUX=y 11 + CONFIG_NETKIT=y 11 12 CONFIG_UDMABUF=y 12 13 CONFIG_VXLAN=y
+4 -3
tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
··· 3 3 """ 4 4 Driver test environment (hardware-only tests). 5 5 NetDrvEnv and NetDrvEpEnv are the main environment classes. 6 + NetDrvContEnv extends NetDrvEpEnv with netkit container support. 6 7 Former is for local host only tests, latter creates / connects 7 8 to a remote endpoint. See NIPA wiki for more information about 8 9 running and writing driver tests. ··· 31 30 from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ 32 31 ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none 33 32 from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner 34 - from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv 33 + from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv 35 34 36 35 __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", 37 36 "EthtoolFamily", "NetdevFamily", "NetshaperFamily", ··· 46 45 "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", 47 46 "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", 48 47 "ksft_not_none", "ksft_not_none", 49 - "NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote", 50 - "Iperf3Runner"] 48 + "NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic", 49 + "Remote", "Iperf3Runner"] 51 50 except ModuleNotFoundError as e: 52 51 print("Failed importing `net` library from kernel sources") 53 52 print(str(e))
+49
tools/testing/selftests/drivers/net/hw/nk_forward.bpf.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <linux/bpf.h> 3 + #include <linux/pkt_cls.h> 4 + #include <linux/if_ether.h> 5 + #include <linux/ipv6.h> 6 + #include <linux/in6.h> 7 + #include <bpf/bpf_endian.h> 8 + #include <bpf/bpf_helpers.h> 9 + 10 + #define TC_ACT_OK 0 11 + #define ETH_P_IPV6 0x86DD 12 + 13 + #define ctx_ptr(field) ((void *)(long)(field)) 14 + 15 + #define v6_p64_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \ 16 + a.s6_addr32[1] == b.s6_addr32[1]) 17 + 18 + volatile __u32 netkit_ifindex; 19 + volatile __u8 ipv6_prefix[16]; 20 + 21 + SEC("tc/ingress") 22 + int tc_redirect_peer(struct __sk_buff *skb) 23 + { 24 + void *data_end = ctx_ptr(skb->data_end); 25 + void *data = ctx_ptr(skb->data); 26 + struct in6_addr *peer_addr; 27 + struct ipv6hdr *ip6h; 28 + struct ethhdr *eth; 29 + 30 + peer_addr = (struct in6_addr *)ipv6_prefix; 31 + 32 + if (skb->protocol != bpf_htons(ETH_P_IPV6)) 33 + return TC_ACT_OK; 34 + 35 + eth = data; 36 + if ((void *)(eth + 1) > data_end) 37 + return TC_ACT_OK; 38 + 39 + ip6h = data + sizeof(struct ethhdr); 40 + if ((void *)(ip6h + 1) > data_end) 41 + return TC_ACT_OK; 42 + 43 + if (!v6_p64_equal(ip6h->daddr, (*peer_addr))) 44 + return TC_ACT_OK; 45 + 46 + return bpf_redirect_peer(netkit_ifindex, 0); 47 + } 48 + 49 + char __license[] SEC("license") = "GPL";
+29
tools/testing/selftests/drivers/net/hw/nk_netns.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + """ 5 + Test exercising NetDrvContEnv() itself, a NetDrvContEnv() selftest. 6 + """ 7 + 8 + from lib.py import ksft_run, ksft_exit 9 + from lib.py import NetDrvContEnv 10 + from lib.py import cmd 11 + 12 + 13 + def test_ping(cfg) -> None: 14 + """ Run ping between the container and the remote system. """ 15 + cfg.require_ipver("6") 16 + 17 + cmd(f"ping -c 1 -W5 {cfg.nk_guest_ipv6}", host=cfg.remote) 18 + cmd(f"ping -c 1 -W5 {cfg.remote_addr_v['6']}", ns=cfg.netns) 19 + 20 + 21 + def main() -> None: 22 + """ Ksft boiler plate main """ 23 + with NetDrvContEnv(__file__) as cfg: 24 + ksft_run([test_ping], args=(cfg,)) 25 + ksft_exit() 26 + 27 + 28 + if __name__ == "__main__": 29 + main()
+6 -5
tools/testing/selftests/drivers/net/lib/py/__init__.py
··· 3 3 """ 4 4 Driver test environment. 5 5 NetDrvEnv and NetDrvEpEnv are the main environment classes. 6 + NetDrvContEnv extends NetDrvEpEnv with netkit container support. 6 7 Former is for local host only tests, latter creates / connects 7 8 to a remote endpoint. See NIPA wiki for more information about 8 9 running and writing driver tests. ··· 20 19 # Import one by one to avoid pylint false positives 21 20 from net.lib.py import NetNS, NetNSEnter, NetdevSimDev 22 21 from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \ 23 - NlError, RtnlFamily, DevlinkFamily, PSPFamily 22 + NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink 24 23 from net.lib.py import CmdExitFailure 25 24 from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ 26 25 fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file ··· 32 31 33 32 __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", 34 33 "EthtoolFamily", "NetdevFamily", "NetshaperFamily", 35 - "NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", 34 + "NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink", 36 35 "CmdExitFailure", 37 36 "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", 38 37 "fd_read_timeout", "ip", "rand_port", "rand_ports", ··· 44 43 "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", 45 44 "ksft_not_none", "ksft_not_none"] 46 45 47 - from .env import NetDrvEnv, NetDrvEpEnv 46 + from .env import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv 48 47 from .load import GenerateTraffic, Iperf3Runner 49 48 from .remote import Remote 50 49 51 - __all__ += ["NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote", 52 - "Iperf3Runner"] 50 + __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic", 51 + "Remote", "Iperf3Runner"] 53 52 except ModuleNotFoundError as e: 54 53 print("Failed importing `net` library from kernel sources") 55 54 print(str(e))
+207
tools/testing/selftests/drivers/net/lib/py/env.py
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 3 + import ipaddress 3 4 import os 4 5 import time 6 + import json 5 7 from pathlib import Path 6 8 from lib.py import KsftSkipEx, KsftXfailEx 7 9 from lib.py import ksft_setup, wait_file 8 10 from lib.py import cmd, ethtool, ip, CmdExitFailure 9 11 from lib.py import NetNS, NetdevSimDev 10 12 from .remote import Remote 13 + from . import bpftool, RtnlFamily, Netlink 11 14 12 15 13 16 class NetDrvEnvBase: ··· 292 289 data.get('stats-block-usecs', 0) / 1000 / 1000 293 290 294 291 time.sleep(self._stats_settle_time) 292 + 293 + 294 + class NetDrvContEnv(NetDrvEpEnv): 295 + """ 296 + Class for an environment with a netkit pair setup for forwarding traffic 297 + between the physical interface and a network namespace. 298 + NETIF = "eth0" 299 + LOCAL_V6 = "2001:db8:1::1" 300 + REMOTE_V6 = "2001:db8:1::2" 301 + LOCAL_PREFIX_V6 = "2001:db8:2::0/64" 302 + 303 + +-----------------------------+ +------------------------------+ 304 + dst | INIT NS | | TEST NS | 305 + 2001: | +---------------+ | | | 306 + db8:2::2| | NETIF | | bpf | | 307 + +---|>| 2001:db8:1::1 | |redirect| +-------------------------+ | 308 + | | | |-----------|--------|>| Netkit | | 309 + | | +---------------+ | _peer | | nk_guest | | 310 + | | +-------------+ Netkit pair | | | fe80::2/64 | | 311 + | | | Netkit |.............|........|>| 2001:db8:2::2/64 | | 312 + | | | nk_host | | | +-------------------------+ | 313 + | | | fe80::1/64 | | | | 314 + | | +-------------+ | | route: | 315 + | | | | default | 316 + | | route: | | via fe80::1 dev nk_guest | 317 + | | 2001:db8:2::2/128 | +------------------------------+ 318 + | | via fe80::2 dev nk_host | 319 + | +-----------------------------+ 320 + | 321 + | +---------------+ 322 + | | REMOTE | 323 + +---| 2001:db8:1::2 | 324 + +---------------+ 325 + """ 326 + 327 + def __init__(self, src_path, rxqueues=1, **kwargs): 328 + self.netns = None 329 + self._nk_host_ifname = None 330 + self._nk_guest_ifname = None 331 + self._tc_clsact_added = False 332 + self._tc_attached = False 333 + self._bpf_prog_pref = None 334 + self._bpf_prog_id = None 335 + self._init_ns_attached = False 336 + self._old_fwd = None 337 + self._old_accept_ra = None 338 + 339 + super().__init__(src_path, **kwargs) 340 + 341 + self.require_ipver("6") 342 + local_prefix = self.env.get("LOCAL_PREFIX_V6") 343 + if not local_prefix: 344 + raise KsftSkipEx("LOCAL_PREFIX_V6 required") 345 + 346 + net = ipaddress.IPv6Network(local_prefix, strict=False) 347 + self.ipv6_prefix = str(net.network_address) 348 + self.nk_host_ipv6 = f"{self.ipv6_prefix}2:1" 349 + self.nk_guest_ipv6 = f"{self.ipv6_prefix}2:2" 350 + 351 + local_v6 = ipaddress.IPv6Address(self.addr_v["6"]) 352 + if local_v6 in net: 353 + raise KsftSkipEx("LOCAL_V6 must not fall within LOCAL_PREFIX_V6") 354 + 355 + rtnl = RtnlFamily() 356 + rtnl.newlink( 357 + { 358 + "linkinfo": { 359 + "kind": "netkit", 360 + "data": { 361 + "mode": "l2", 362 + "policy": "forward", 363 + "peer-policy": "forward", 364 + }, 365 + }, 366 + "num-rx-queues": rxqueues, 367 + }, 368 + flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL], 369 + ) 370 + 371 + all_links = ip("-d link show", json=True) 372 + netkit_links = [link for link in all_links 373 + if link.get('linkinfo', {}).get('info_kind') == 'netkit' 374 + and 'UP' not in link.get('flags', [])] 375 + 376 + if len(netkit_links) != 2: 377 + raise KsftSkipEx("Failed to create netkit pair") 378 + 379 + netkit_links.sort(key=lambda x: x['ifindex']) 380 + self._nk_host_ifname = netkit_links[1]['ifname'] 381 + self._nk_guest_ifname = netkit_links[0]['ifname'] 382 + self.nk_host_ifindex = netkit_links[1]['ifindex'] 383 + self.nk_guest_ifindex = netkit_links[0]['ifindex'] 384 + 385 + self._setup_ns() 386 + self._attach_bpf() 387 + 388 + def __del__(self): 389 + if self._tc_attached: 390 + cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}") 391 + self._tc_attached = False 392 + 393 + if self._tc_clsact_added: 394 + cmd(f"tc qdisc del dev {self.ifname} clsact") 395 + self._tc_clsact_added = False 396 + 397 + if self._nk_host_ifname: 398 + cmd(f"ip link del dev {self._nk_host_ifname}") 399 + self._nk_host_ifname = None 400 + self._nk_guest_ifname = None 401 + 402 + if self._init_ns_attached: 403 + cmd("ip netns del init", fail=False) 404 + self._init_ns_attached = False 405 + 406 + if self.netns: 407 + del self.netns 408 + self.netns = None 409 + 410 + if self._old_fwd is not None: 411 + with open("/proc/sys/net/ipv6/conf/all/forwarding", "w", 412 + encoding="utf-8") as f: 413 + f.write(self._old_fwd) 414 + self._old_fwd = None 415 + if self._old_accept_ra is not None: 416 + with open("/proc/sys/net/ipv6/conf/all/accept_ra", "w", 417 + encoding="utf-8") as f: 418 + f.write(self._old_accept_ra) 419 + self._old_accept_ra = None 420 + 421 + super().__del__() 422 + 423 + def _setup_ns(self): 424 + fwd_path = "/proc/sys/net/ipv6/conf/all/forwarding" 425 + ra_path = "/proc/sys/net/ipv6/conf/all/accept_ra" 426 + with open(fwd_path, encoding="utf-8") as f: 427 + self._old_fwd = f.read().strip() 428 + with open(ra_path, encoding="utf-8") as f: 429 + self._old_accept_ra = f.read().strip() 430 + with open(fwd_path, "w", encoding="utf-8") as f: 431 + f.write("1") 432 + with open(ra_path, "w", encoding="utf-8") as f: 433 + f.write("2") 434 + 435 + self.netns = NetNS() 436 + cmd("ip netns attach init 1") 437 + self._init_ns_attached = True 438 + ip("netns set init 0", ns=self.netns) 439 + ip(f"link set dev {self._nk_guest_ifname} netns {self.netns.name}") 440 + ip(f"link set dev {self._nk_host_ifname} up") 441 + ip(f"-6 addr add fe80::1/64 dev {self._nk_host_ifname} nodad") 442 + ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self._nk_host_ifname}") 443 + 444 + ip("link set lo up", ns=self.netns) 445 + ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns) 446 + ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns) 447 + ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns) 448 + ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns) 449 + 450 + def _tc_ensure_clsact(self): 451 + qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout) 452 + for q in qdisc: 453 + if q['kind'] == 'clsact': 454 + return 455 + cmd(f"tc qdisc add dev {self.ifname} clsact") 456 + self._tc_clsact_added = True 457 + 458 + def _get_bpf_prog_ids(self): 459 + filters = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout) 460 + for bpf in filters: 461 + if 'options' not in bpf: 462 + continue 463 + if bpf['options']['bpf_name'].startswith('nk_forward.bpf'): 464 + return (bpf['pref'], bpf['options']['prog']['id']) 465 + raise Exception("Failed to get BPF prog ID") 466 + 467 + def _attach_bpf(self): 468 + bpf_obj = self.test_dir / "nk_forward.bpf.o" 469 + if not bpf_obj.exists(): 470 + raise KsftSkipEx("BPF prog not found") 471 + 472 + self._tc_ensure_clsact() 473 + cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj}" 474 + " sec tc/ingress direct-action") 475 + self._tc_attached = True 476 + 477 + (self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids() 478 + prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True) 479 + map_ids = prog_info.get("map_ids", []) 480 + 481 + bss_map_id = None 482 + for map_id in map_ids: 483 + map_info = bpftool(f"map show id {map_id}", json=True) 484 + if map_info.get("name").endswith("bss"): 485 + bss_map_id = map_id 486 + 487 + if bss_map_id is None: 488 + raise Exception("Failed to find .bss map") 489 + 490 + ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix) 491 + ipv6_bytes = ipv6_addr.packed 492 + ifindex_bytes = self.nk_host_ifindex.to_bytes(4, byteorder='little') 493 + value = ipv6_bytes + ifindex_bytes 494 + value_hex = ' '.join(f'{b:02x}' for b in value) 495 + bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+2 -2
tools/testing/selftests/net/lib/py/__init__.py
··· 16 16 bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \ 17 17 wait_file, tool 18 18 from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily 19 - from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily 19 + from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink 20 20 21 21 __all__ = ["KSRC", 22 22 "KsftFailEx", "KsftSkipEx", "KsftXfailEx", "ksft_pr", "ksft_eq", ··· 31 31 "NetdevSim", "NetdevSimDev", 32 32 "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", 33 33 "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily", 34 - "RtnlAddrFamily"] 34 + "RtnlAddrFamily", "Netlink"]
+8 -2
tools/testing/selftests/net/lib/py/ynl.py
··· 13 13 SPEC_PATH = KSFT_DIR / "net/lib/specs" 14 14 15 15 sys.path.append(tools_full_path.as_posix()) 16 - from net.lib.ynl.pyynl.lib import YnlFamily, NlError 16 + from net.lib.ynl.pyynl.lib import YnlFamily, NlError, Netlink 17 17 else: 18 18 # Running in tree 19 19 tools_full_path = KSRC / "tools" 20 20 SPEC_PATH = KSRC / "Documentation/netlink/specs" 21 21 22 22 sys.path.append(tools_full_path.as_posix()) 23 - from net.ynl.pyynl.lib import YnlFamily, NlError 23 + from net.ynl.pyynl.lib import YnlFamily, NlError, Netlink 24 24 except ModuleNotFoundError as e: 25 25 ksft_pr("Failed importing `ynl` library from kernel sources") 26 26 ksft_pr(str(e)) 27 27 ktap_result(True, comment="SKIP") 28 28 sys.exit(4) 29 + 30 + __all__ = [ 31 + "NlError", "Netlink", "YnlFamily", "SPEC_PATH", 32 + "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", 33 + "NetdevFamily", "NetshaperFamily", "DevlinkFamily", "PSPFamily", 34 + ] 29 35 30 36 # 31 37 # Wrapper classes, loading the right specs