1# SPDX-License-Identifier: GPL-2.0 2 3import errno 4import json 5import os 6import random 7import re 8import time 9from .utils import cmd, ip 10 11 12class NetdevSim: 13 """ 14 Class for netdevsim netdevice and its attributes. 15 """ 16 17 def __init__(self, nsimdev, port_index, ifname, ns=None): 18 # In case udev renamed the netdev to according to new schema, 19 # check if the name matches the port_index. 20 nsimnamere = re.compile(r"eni\d+np(\d+)") 21 match = nsimnamere.match(ifname) 22 if match and int(match.groups()[0]) != port_index + 1: 23 raise Exception("netdevice name mismatches the expected one") 24 25 self.ifname = ifname 26 self.nsimdev = nsimdev 27 self.port_index = port_index 28 self.ns = ns 29 self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index) 30 ret = ip("-j link show dev %s" % ifname, ns=ns) 31 self.dev = json.loads(ret.stdout)[0] 32 self.ifindex = self.dev["ifindex"] 33 34 def dfs_write(self, path, val): 35 self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val) 36 37 38class NetdevSimDev: 39 """ 40 Class for netdevsim bus device and its attributes. 41 """ 42 @staticmethod 43 def ctrl_write(path, val): 44 fullpath = os.path.join("/sys/bus/netdevsim/", path) 45 with open(fullpath, "w") as f: 46 f.write(val) 47 48 def dfs_write(self, path, val): 49 fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path) 50 with open(fullpath, "w") as f: 51 f.write(val) 52 53 def __init__(self, port_count=1, queue_count=1, ns=None): 54 # nsim will spawn in init_net, we'll set to actual ns once we switch it there 55 self.ns = None 56 57 if not os.path.exists("/sys/bus/netdevsim"): 58 cmd("modprobe netdevsim") 59 60 addr = random.randrange(1 << 15) 61 while True: 62 try: 63 self.ctrl_write("new_device", "%u %u %u" % (addr, port_count, queue_count)) 64 except OSError as e: 65 if e.errno == errno.ENOSPC: 66 addr = random.randrange(1 << 15) 67 continue 68 raise e 69 break 70 self.addr = addr 71 72 # As probe of netdevsim device might happen from a workqueue, 73 # so wait here until all netdevs appear. 74 self.wait_for_netdevs(port_count) 75 76 if ns: 77 cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}") 78 self.ns = ns 79 80 cmd("udevadm settle", ns=self.ns) 81 ifnames = self.get_ifnames() 82 83 self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr 84 85 self.nsims = [] 86 for port_index in range(port_count): 87 self.nsims.append(self._make_port(port_index, ifnames[port_index])) 88 89 self.removed = False 90 91 def __enter__(self): 92 return self 93 94 def __exit__(self, ex_type, ex_value, ex_tb): 95 """ 96 __exit__ gets called at the end of a "with" block. 97 """ 98 self.remove() 99 100 def _make_port(self, port_index, ifname): 101 return NetdevSim(self, port_index, ifname, self.ns) 102 103 def get_ifnames(self): 104 ifnames = [] 105 listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/", 106 ns=self.ns).stdout.split() 107 for ifname in listdir: 108 ifnames.append(ifname) 109 ifnames.sort() 110 return ifnames 111 112 def wait_for_netdevs(self, port_count): 113 timeout = 5 114 timeout_start = time.time() 115 116 while True: 117 try: 118 ifnames = self.get_ifnames() 119 except FileNotFoundError as e: 120 ifnames = [] 121 if len(ifnames) == port_count: 122 break 123 if time.time() < timeout_start + timeout: 124 continue 125 raise Exception("netdevices did not appear within timeout") 126 127 def remove(self): 128 if not self.removed: 129 self.ctrl_write("del_device", "%u" % (self.addr, )) 130 self.removed = True 131 132 def remove_nsim(self, nsim): 133 self.nsims.remove(nsim) 134 self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ), 135 "%u" % (nsim.port_index, )) 136