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