1# SPDX-License-Identifier: GPL-2.0 2 3import ipaddress 4import os 5import sys 6import time 7import json 8from pathlib import Path 9from lib.py import KsftSkipEx, KsftXfailEx 10from lib.py import ksft_pr, ksft_setup, wait_file 11from lib.py import cmd, ethtool, ip, CmdExitFailure 12from lib.py import NetNS, NetdevSimDev, UserNetNS 13from .remote import Remote 14from . import bpftool, RtnlFamily, Netlink 15 16 17class NetDrvEnvBase: 18 """ 19 Base class for a NIC / host environments 20 21 Attributes: 22 test_dir: Path to the source directory of the test 23 net_lib_dir: Path to the net/lib directory 24 """ 25 def __init__(self, src_path): 26 self.src_path = Path(src_path) 27 self.test_dir = self.src_path.parent.resolve() 28 self.net_lib_dir = (Path(__file__).parent / "../../../../net/lib").resolve() 29 30 self.env = self._load_env_file() 31 32 # Following attrs must be set be inheriting classes 33 self.dev = None 34 self.ifname = None 35 36 def _load_env_file(self): 37 env = os.environ.copy() 38 39 src_dir = Path(self.src_path).parent.resolve() 40 if not (src_dir / "net.config").exists(): 41 return ksft_setup(env) 42 43 with open((src_dir / "net.config").as_posix(), 'r') as fp: 44 for line in fp.readlines(): 45 full_file = line 46 # Strip comments 47 pos = line.find("#") 48 if pos >= 0: 49 line = line[:pos] 50 line = line.strip() 51 if not line: 52 continue 53 pair = line.split('=', maxsplit=1) 54 if len(pair) != 2: 55 raise Exception("Can't parse configuration line:", full_file) 56 env[pair[0]] = pair[1] 57 return ksft_setup(env) 58 59 def __del__(self): 60 pass 61 62 def _print_dev_info(self): 63 """ 64 Show whether the test ran on real hardware or netdevsim. 65 Useful to confirm when results are shared on the mailing list. 66 """ 67 driver = "unknown" 68 try: 69 info = ethtool(f"-i {self.ifname}").stdout 70 for line in info.splitlines(): 71 if line.startswith("driver:"): 72 driver = line.split(':', 1)[1].strip() or driver 73 break 74 except (CmdExitFailure, FileNotFoundError): 75 pass 76 ksft_pr(f"Interface: {self.ifname}, driver: {driver}") 77 78 def __enter__(self): 79 ip(f"link set dev {self.dev['ifname']} up") 80 wait_file(f"/sys/class/net/{self.dev['ifname']}/carrier", 81 lambda x: x.strip() == "1") 82 83 return self 84 85 def __exit__(self, ex_type, ex_value, ex_tb): 86 """ 87 __exit__ gets called at the end of a "with" block. 88 """ 89 self.__del__() 90 91 92class NetDrvEnv(NetDrvEnvBase): 93 """ 94 Class for a single NIC / host env, with no remote end 95 """ 96 def __init__(self, src_path, nsim_test=None, **kwargs): 97 super().__init__(src_path) 98 99 self._ns = None 100 101 if 'NETIF' in self.env: 102 if nsim_test is True: 103 raise KsftXfailEx("Test only works on netdevsim") 104 105 self.dev = ip("-d link show dev " + self.env['NETIF'], json=True)[0] 106 else: 107 if nsim_test is False: 108 raise KsftXfailEx("Test does not work on netdevsim") 109 110 self._ns = NetdevSimDev(**kwargs) 111 self.dev = self._ns.nsims[0].dev 112 self.ifname = self.dev['ifname'] 113 self.ifindex = self.dev['ifindex'] 114 self._print_dev_info() 115 116 def __del__(self): 117 if self._ns: 118 self._ns.remove() 119 self._ns = None 120 121 122class NetDrvEpEnv(NetDrvEnvBase): 123 """ 124 Class for an environment with a local device and "remote endpoint" 125 which can be used to send traffic in. 126 127 For local testing it creates two network namespaces and a pair 128 of netdevsim devices. 129 """ 130 131 # Network prefixes used for local tests 132 nsim_v4_pfx = "192.0.2." 133 nsim_v6_pfx = "2001:db8::" 134 135 def __init__(self, src_path, nsim_test=None, queue_count=None): 136 super().__init__(src_path) 137 138 self._stats_settle_time = None 139 self._queue_count = queue_count 140 141 # Things we try to destroy 142 self.remote = None 143 # These are for local testing state 144 self._netns = None 145 self._ns = None 146 self._ns_peer = None 147 148 self.addr_v = { "4": None, "6": None } 149 self.remote_addr_v = { "4": None, "6": None } 150 151 if "NETIF" in self.env: 152 if nsim_test is True: 153 raise KsftXfailEx("Test only works on netdevsim") 154 self._check_env() 155 156 self.dev = ip("-d link show dev " + self.env['NETIF'], json=True)[0] 157 158 self.addr_v["4"] = self.env.get("LOCAL_V4") 159 self.addr_v["6"] = self.env.get("LOCAL_V6") 160 self.remote_addr_v["4"] = self.env.get("REMOTE_V4") 161 self.remote_addr_v["6"] = self.env.get("REMOTE_V6") 162 kind = self.env["REMOTE_TYPE"] 163 args = self.env["REMOTE_ARGS"] 164 else: 165 if nsim_test is False: 166 raise KsftXfailEx("Test does not work on netdevsim") 167 168 self.create_local() 169 170 self.dev = self._ns.nsims[0].dev 171 172 self.addr_v["4"] = self.nsim_v4_pfx + "1" 173 self.addr_v["6"] = self.nsim_v6_pfx + "1" 174 self.remote_addr_v["4"] = self.nsim_v4_pfx + "2" 175 self.remote_addr_v["6"] = self.nsim_v6_pfx + "2" 176 kind = "netns" 177 args = self._netns.name 178 179 self.remote = Remote(kind, args, src_path) 180 181 self.set_ipver("6" if self.addr_v["6"] else "4") 182 183 self.ifname = self.dev['ifname'] 184 self.ifindex = self.dev['ifindex'] 185 self._print_dev_info() 186 187 # resolve remote interface name 188 self.remote_ifname = self.resolve_remote_ifc() 189 self.remote_dev = ip("-d link show dev " + self.remote_ifname, 190 host=self.remote, json=True)[0] 191 self.remote_ifindex = self.remote_dev['ifindex'] 192 193 self._required_cmd = {} 194 195 def create_local(self): 196 nsim_kwargs = {} 197 if self._queue_count: 198 nsim_kwargs["queue_count"] = self._queue_count 199 200 self._netns = NetNS() 201 self._ns = NetdevSimDev(**nsim_kwargs) 202 self._ns_peer = NetdevSimDev(ns=self._netns, **nsim_kwargs) 203 204 with open("/proc/self/ns/net") as nsfd0, \ 205 open("/var/run/netns/" + self._netns.name) as nsfd1: 206 ifi0 = self._ns.nsims[0].ifindex 207 ifi1 = self._ns_peer.nsims[0].ifindex 208 NetdevSimDev.ctrl_write('link_device', 209 f'{nsfd0.fileno()}:{ifi0} {nsfd1.fileno()}:{ifi1}') 210 211 ip(f" addr add dev {self._ns.nsims[0].ifname} {self.nsim_v4_pfx}1/24") 212 ip(f"-6 addr add dev {self._ns.nsims[0].ifname} {self.nsim_v6_pfx}1/64 nodad") 213 ip(f" link set dev {self._ns.nsims[0].ifname} up") 214 215 ip(f" addr add dev {self._ns_peer.nsims[0].ifname} {self.nsim_v4_pfx}2/24", ns=self._netns) 216 ip(f"-6 addr add dev {self._ns_peer.nsims[0].ifname} {self.nsim_v6_pfx}2/64 nodad", ns=self._netns) 217 ip(f" link set dev {self._ns_peer.nsims[0].ifname} up", ns=self._netns) 218 219 def _check_env(self): 220 vars_needed = [ 221 ["LOCAL_V4", "LOCAL_V6"], 222 ["REMOTE_V4", "REMOTE_V6"], 223 ["REMOTE_TYPE"], 224 ["REMOTE_ARGS"] 225 ] 226 missing = [] 227 228 for choice in vars_needed: 229 for entry in choice: 230 if entry in self.env: 231 break 232 else: 233 missing.append(choice) 234 # Make sure v4 / v6 configs are symmetric 235 if ("LOCAL_V6" in self.env) != ("REMOTE_V6" in self.env): 236 missing.append(["LOCAL_V6", "REMOTE_V6"]) 237 if ("LOCAL_V4" in self.env) != ("REMOTE_V4" in self.env): 238 missing.append(["LOCAL_V4", "REMOTE_V4"]) 239 if missing: 240 raise Exception("Invalid environment, missing configuration:", missing, 241 "Please see tools/testing/selftests/drivers/net/README.rst") 242 if "LOCAL_V6" in self.env: 243 self._check_v6_env() 244 245 def _check_v6_env(self): 246 """Tests bind() to LOCAL_V6 and bounce the link, it must survive both.""" 247 ifname, addr = self.env["NETIF"], self.env["LOCAL_V6"] 248 249 def _keep_addr(scope): 250 with open(f"/proc/sys/net/ipv6/conf/{scope}/keep_addr_on_down", 251 encoding="utf-8") as fp: 252 return int(fp.read()) 253 254 # 'all' wins when non-zero, see addrconf_ifdown() 255 if (_keep_addr("all") or _keep_addr(ifname)) <= 0: 256 ksft_pr(f"WARN: net.ipv6.conf.{ifname}.keep_addr_on_down not set") 257 258 dev = ip(f"-6 address show dev {ifname} to {addr}", json=True) 259 if not (dev and dev[0]["addr_info"][0].get("nodad")): 260 ksft_pr(f"WARN: LOCAL_V6 {addr} not configured with nodad") 261 262 def resolve_remote_ifc(self): 263 v4 = v6 = None 264 if self.remote_addr_v["4"]: 265 v4 = ip("addr show to " + self.remote_addr_v["4"], json=True, host=self.remote) 266 if self.remote_addr_v["6"]: 267 v6 = ip("addr show to " + self.remote_addr_v["6"], json=True, host=self.remote) 268 if v4 and v6 and v4[0]["ifname"] != v6[0]["ifname"]: 269 raise Exception("Can't resolve remote interface name, v4 and v6 don't match") 270 if (v4 and len(v4) > 1) or (v6 and len(v6) > 1): 271 raise Exception("Can't resolve remote interface name, multiple interfaces match") 272 return v6[0]["ifname"] if v6 else v4[0]["ifname"] 273 274 def __del__(self): 275 if self._ns: 276 self._ns.remove() 277 self._ns = None 278 if self._ns_peer: 279 self._ns_peer.remove() 280 self._ns_peer = None 281 if self._netns: 282 del self._netns 283 self._netns = None 284 if self.remote: 285 del self.remote 286 self.remote = None 287 288 def require_ipver(self, ipver): 289 if not self.addr_v[ipver] or not self.remote_addr_v[ipver]: 290 raise KsftSkipEx(f"Test requires IPv{ipver} connectivity") 291 292 def set_ipver(self, ipver): 293 """ 294 Modify the IP version used by the generic address fields. 295 """ 296 if ipver == getattr(self, "addr_ipver", None): 297 return 298 299 self.require_ipver(ipver) 300 301 self.addr_ipver = ipver 302 self.addr = self.addr_v[ipver] 303 self.remote_addr = self.remote_addr_v[ipver] 304 305 # Bracketed addresses, some commands need IPv6 to be inside [] 306 self.baddr = (f"[{self.addr_v['6']}]" if ipver == "6" 307 else self.addr_v["4"]) 308 self.remote_baddr = (f"[{self.remote_addr_v['6']}]" if ipver == "6" 309 else self.remote_addr_v["4"]) 310 311 def require_nsim(self, nsim_test=True): 312 """Require or exclude netdevsim for this test""" 313 if nsim_test and self._ns is None: 314 raise KsftXfailEx("Test only works on netdevsim") 315 if nsim_test is False and self._ns is not None: 316 raise KsftXfailEx("Test does not work on netdevsim") 317 318 def get_local_nsim_dev(self): 319 """Returns the local netdevsim device or None. 320 Using this method is discouraged, as it makes tests nsim-specific. 321 Standard interfaces available on all HW should ideally be used. 322 This method is intended for the few cases where nsim-specific 323 assertions need to be verified which cannot be verified otherwise. 324 """ 325 return self._ns 326 327 def _require_cmd(self, comm, key, host=None): 328 cached = self._required_cmd.get(comm, {}) 329 if cached.get(key) is None: 330 cached[key] = cmd("command -v -- " + comm, fail=False, 331 shell=True, host=host).ret == 0 332 self._required_cmd[comm] = cached 333 return cached[key] 334 335 def require_cmd(self, comm, local=True, remote=False): 336 if local: 337 if not self._require_cmd(comm, "local"): 338 raise KsftSkipEx("Test requires command: " + comm) 339 if remote: 340 if not self._require_cmd(comm, "remote", host=self.remote): 341 raise KsftSkipEx("Test requires (remote) command: " + comm) 342 343 def wait_hw_stats_settle(self): 344 """ 345 Wait for HW stats to become consistent, some devices DMA HW stats 346 periodically so events won't be reflected until next sync. 347 Good drivers will tell us via ethtool what their sync period is. 348 """ 349 if self._stats_settle_time is None: 350 data = {} 351 try: 352 data = ethtool("-c " + self.ifname, json=True)[0] 353 except CmdExitFailure as e: 354 if "Operation not supported" not in e.cmd.stderr: 355 raise 356 357 self._stats_settle_time = \ 358 1.25 * data.get('stats-block-usecs', 20000) / 1000 / 1000 359 360 time.sleep(self._stats_settle_time) 361 362 363class NetDrvContEnv(NetDrvEpEnv): 364 """ 365 Class for an environment with a netkit pair setup for forwarding traffic 366 between the physical interface and a network namespace. 367 NETIF = "eth0" 368 LOCAL_V6 = "2001:db8:1::1" 369 REMOTE_V6 = "2001:db8:1::2" 370 LOCAL_PREFIX_V6 = "2001:db8:2::0/64" 371 372 +-----------------------------+ +------------------------------+ 373 dst | INIT NS | | TEST NS | 374 2001: | +---------------+ | | | 375 db8:2::2| | NETIF | | bpf | | 376 +---|>| 2001:db8:1::1 | |redirect| +-------------------------+ | 377 | | | |-----------|--------|>| Netkit | | 378 | | +---------------+ | _peer | | nk_guest | | 379 | | +-------------+ Netkit pair | | | fe80::2/64 | | 380 | | | Netkit |.............|........|>| 2001:db8:2::2/64 | | 381 | | | nk_host | | | +-------------------------+ | 382 | | | fe80::1/64 | | | | 383 | | +-------------+ | | route: | 384 | | | | default | 385 | | route: | | via fe80::1 dev nk_guest | 386 | | 2001:db8:2::2/128 | +------------------------------+ 387 | | via fe80::2 dev nk_host | 388 | +-----------------------------+ 389 | 390 | +---------------+ 391 | | REMOTE | 392 +---| 2001:db8:1::2 | 393 +---------------+ 394 """ 395 396 def __init__(self, src_path, rxqueues=1, primary_rx_redirect=False, 397 userns=False, **kwargs): 398 self.netns = None 399 self._userns = userns 400 self.nk_host_ifname = None 401 self.nk_guest_ifname = None 402 self._tc_clsact_added = False 403 self._tc_attached = False 404 self._primary_rx_redirect_attached = False 405 self._primary_rx_redirect_clsact_added = False 406 self._bpf_prog_pref = None 407 self._bpf_prog_id = None 408 self._init_ns_attached = False 409 self._remote_route_added = False 410 self._old_fwd = None 411 self._old_accept_ra = None 412 413 super().__init__(src_path, **kwargs) 414 415 self.require_ipver("6") 416 local_prefix = self.env.get("LOCAL_PREFIX_V6") 417 if not local_prefix: 418 raise KsftSkipEx("LOCAL_PREFIX_V6 required") 419 420 net = ipaddress.IPv6Network(local_prefix, strict=False) 421 self.ipv6_prefix = str(net.network_address) 422 self.nk_host_ipv6 = f"{self.ipv6_prefix}2:1" 423 self.nk_guest_ipv6 = f"{self.ipv6_prefix}2:2" 424 425 local_v6 = ipaddress.IPv6Address(self.addr_v["6"]) 426 if local_v6 in net: 427 raise KsftSkipEx("LOCAL_V6 must not fall within LOCAL_PREFIX_V6") 428 429 rtnl = RtnlFamily() 430 rtnl.newlink( 431 { 432 "linkinfo": { 433 "kind": "netkit", 434 "data": { 435 "mode": "l2", 436 "policy": "forward", 437 "peer-policy": "forward", 438 }, 439 }, 440 "num-rx-queues": rxqueues, 441 }, 442 flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL], 443 ) 444 445 all_links = ip("-d link show", json=True) 446 netkit_links = [link for link in all_links 447 if link.get('linkinfo', {}).get('info_kind') == 'netkit' 448 and 'UP' not in link.get('flags', [])] 449 450 if len(netkit_links) != 2: 451 raise KsftSkipEx("Failed to create netkit pair") 452 453 netkit_links.sort(key=lambda x: x['ifindex']) 454 self.nk_host_ifname = netkit_links[1]['ifname'] 455 self.nk_guest_ifname = netkit_links[0]['ifname'] 456 self.nk_host_ifindex = netkit_links[1]['ifindex'] 457 self.nk_guest_ifindex = netkit_links[0]['ifindex'] 458 459 self._setup_ns() 460 self.attach_bpf() 461 if primary_rx_redirect: 462 self._attach_primary_rx_redirect_bpf() 463 464 def __del__(self): 465 if self._primary_rx_redirect_attached: 466 cmd(f"tc filter del dev {self.nk_host_ifname} ingress", fail=False) 467 self._primary_rx_redirect_attached = False 468 469 if self._primary_rx_redirect_clsact_added: 470 cmd(f"tc qdisc del dev {self.nk_host_ifname} clsact", fail=False) 471 self._primary_rx_redirect_clsact_added = False 472 473 if self._tc_attached: 474 cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}") 475 self._tc_attached = False 476 477 if self._tc_clsact_added: 478 cmd(f"tc qdisc del dev {self.ifname} clsact") 479 self._tc_clsact_added = False 480 481 if self._remote_route_added: 482 cmd(f"ip -6 route del {self.nk_guest_ipv6}/128", 483 host=self.remote, fail=False) 484 self._remote_route_added = False 485 486 if self.nk_host_ifname: 487 cmd(f"ip link del dev {self.nk_host_ifname}") 488 self.nk_host_ifname = None 489 self.nk_guest_ifname = None 490 491 if self._init_ns_attached: 492 cmd("ip netns del init", fail=False) 493 self._init_ns_attached = False 494 495 if self.netns: 496 del self.netns 497 self.netns = None 498 499 if self._old_fwd is not None: 500 with open("/proc/sys/net/ipv6/conf/all/forwarding", "w", 501 encoding="utf-8") as f: 502 f.write(self._old_fwd) 503 self._old_fwd = None 504 if self._old_accept_ra is not None: 505 with open("/proc/sys/net/ipv6/conf/all/accept_ra", "w", 506 encoding="utf-8") as f: 507 f.write(self._old_accept_ra) 508 self._old_accept_ra = None 509 510 super().__del__() 511 512 def _setup_ns(self): 513 fwd_path = "/proc/sys/net/ipv6/conf/all/forwarding" 514 ra_path = "/proc/sys/net/ipv6/conf/all/accept_ra" 515 with open(fwd_path, encoding="utf-8") as f: 516 self._old_fwd = f.read().strip() 517 with open(ra_path, encoding="utf-8") as f: 518 self._old_accept_ra = f.read().strip() 519 with open(fwd_path, "w", encoding="utf-8") as f: 520 f.write("1") 521 with open(ra_path, "w", encoding="utf-8") as f: 522 f.write("2") 523 524 self.netns = UserNetNS() if self._userns else NetNS() 525 cmd("ip netns attach init 1") 526 self._init_ns_attached = True 527 ip("netns set init 0", ns=self.netns) 528 ip(f"link set dev {self.nk_guest_ifname} netns {self.netns.name}") 529 nk_guest_dev = ip(f"link show dev {self.nk_guest_ifname}", 530 json=True, ns=self.netns)[0] 531 self.nk_guest_ifindex = nk_guest_dev['ifindex'] 532 ip(f"link set dev {self.nk_host_ifname} up") 533 ip(f"-6 addr add fe80::1/64 dev {self.nk_host_ifname} nodad") 534 ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self.nk_host_ifname}") 535 536 ip("link set lo up", ns=self.netns) 537 ip(f"link set dev {self.nk_guest_ifname} up", ns=self.netns) 538 ip(f"-6 addr add fe80::2/64 dev {self.nk_guest_ifname}", ns=self.netns) 539 ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self.nk_guest_ifname} nodad", ns=self.netns) 540 ip(f"-6 route add default via fe80::1 dev {self.nk_guest_ifname}", ns=self.netns) 541 542 def _tc_ensure_clsact(self, ifname=None): 543 """Ensure a clsact qdisc exists on @ifname. 544 545 Returns True if this call added the qdisc, otherwise returns False. 546 """ 547 if ifname is None: 548 ifname = self.ifname 549 qdisc = json.loads(cmd(f"tc -j qdisc show dev {ifname}").stdout) 550 for q in qdisc: 551 if q['kind'] == 'clsact': 552 return False 553 cmd(f"tc qdisc add dev {ifname} clsact") 554 return True 555 556 def _get_bpf_prog_ids(self): 557 filters = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout) 558 for bpf in filters: 559 if 'options' not in bpf: 560 continue 561 if bpf['options']['bpf_name'].startswith('nk_forward.bpf'): 562 return (bpf['pref'], bpf['options']['prog']['id']) 563 raise Exception("Failed to get BPF prog ID") 564 565 def _find_bss_map_id(self, prog_id): 566 """Find the .bss map ID for a loaded BPF program.""" 567 prog_info = bpftool(f"prog show id {prog_id}", json=True) 568 for map_id in prog_info.get("map_ids", []): 569 map_info = bpftool(f"map show id {map_id}", json=True) 570 if map_info.get("name", "").endswith("bss"): 571 return map_id 572 raise Exception(f"Failed to find .bss map for prog {prog_id}") 573 574 def _find_bpf_obj(self, name): 575 bpf_obj = self.test_dir / name 576 if bpf_obj.exists(): 577 return bpf_obj 578 bpf_obj = self.test_dir / "hw" / name 579 if bpf_obj.exists(): 580 return bpf_obj 581 return None 582 583 def detach_bpf(self): 584 if self._tc_attached: 585 cmd(f"tc filter del dev {self.ifname} ingress pref " 586 f"{self._bpf_prog_pref}", fail=False) 587 self._tc_attached = False 588 589 def attach_bpf(self): 590 bpf_obj = self._find_bpf_obj("nk_forward.bpf.o") 591 if not bpf_obj: 592 raise KsftSkipEx("BPF prog nk_forward.bpf.o not found") 593 594 if self._tc_ensure_clsact(): 595 self._tc_clsact_added = True 596 cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj}" 597 " sec tc/ingress direct-action") 598 self._tc_attached = True 599 600 (self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids() 601 bss_map_id = self._find_bss_map_id(self._bpf_prog_id) 602 603 ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix) 604 ipv6_bytes = ipv6_addr.packed 605 ifindex_bytes = self.nk_host_ifindex.to_bytes(4, byteorder='little') 606 value = ipv6_bytes + ifindex_bytes 607 value_hex = ' '.join(f'{b:02x}' for b in value) 608 bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}") 609 610 def _attach_primary_rx_redirect_bpf(self): 611 """Attach BPF redirect program on the primary netkit ingress.""" 612 bpf_obj = self._find_bpf_obj("nk_primary_rx_redirect.bpf.o") 613 if not bpf_obj: 614 raise KsftSkipEx("nk_primary_rx_redirect.bpf.o not found") 615 616 if self._tc_ensure_clsact(self.nk_host_ifname): 617 self._primary_rx_redirect_clsact_added = True 618 cmd(f"tc filter add dev {self.nk_host_ifname} ingress" 619 f" bpf obj {bpf_obj} sec tc/ingress direct-action") 620 self._primary_rx_redirect_attached = True 621 622 ip(f"-6 route add {self.nk_guest_ipv6}/128 via {self.addr_v['6']}", 623 host=self.remote) 624 self._remote_route_added = True 625 626 filters = json.loads( 627 cmd(f"tc -j filter show dev {self.nk_host_ifname} ingress").stdout) 628 redirect_prog_id = None 629 for bpf in filters: 630 if 'options' not in bpf: 631 continue 632 if bpf['options']['bpf_name'].startswith('nk_primary_rx_redirect'): 633 redirect_prog_id = bpf['options']['prog']['id'] 634 break 635 if redirect_prog_id is None: 636 raise Exception("Failed to get primary RX redirect BPF prog ID") 637 638 bss_map_id = self._find_bss_map_id(redirect_prog_id) 639 phys_ifindex_bytes = self.ifindex.to_bytes(4, byteorder=sys.byteorder) 640 value_hex = ' '.join(f'{b:02x}' for b in phys_ifindex_bytes) 641 bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}") 642