1*37d9df22SJakub Kicinski# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 24e4480e8SJakub Kicinski 34e4480e8SJakub Kicinskiimport functools 44e4480e8SJakub Kicinskiimport os 54e4480e8SJakub Kicinskiimport random 64e4480e8SJakub Kicinskiimport socket 74e4480e8SJakub Kicinskiimport struct 84e4480e8SJakub Kicinskiimport yaml 94e4480e8SJakub Kicinski 1030a5c6c8SJakub Kicinskifrom .nlspec import SpecFamily 1130a5c6c8SJakub Kicinski 124e4480e8SJakub Kicinski# 134e4480e8SJakub Kicinski# Generic Netlink code which should really be in some library, but I can't quickly find one. 144e4480e8SJakub Kicinski# 154e4480e8SJakub Kicinski 164e4480e8SJakub Kicinski 174e4480e8SJakub Kicinskiclass Netlink: 184e4480e8SJakub Kicinski # Netlink socket 194e4480e8SJakub Kicinski SOL_NETLINK = 270 204e4480e8SJakub Kicinski 214e4480e8SJakub Kicinski NETLINK_ADD_MEMBERSHIP = 1 224e4480e8SJakub Kicinski NETLINK_CAP_ACK = 10 234e4480e8SJakub Kicinski NETLINK_EXT_ACK = 11 244e4480e8SJakub Kicinski 254e4480e8SJakub Kicinski # Netlink message 264e4480e8SJakub Kicinski NLMSG_ERROR = 2 274e4480e8SJakub Kicinski NLMSG_DONE = 3 284e4480e8SJakub Kicinski 294e4480e8SJakub Kicinski NLM_F_REQUEST = 1 304e4480e8SJakub Kicinski NLM_F_ACK = 4 314e4480e8SJakub Kicinski NLM_F_ROOT = 0x100 324e4480e8SJakub Kicinski NLM_F_MATCH = 0x200 334e4480e8SJakub Kicinski NLM_F_APPEND = 0x800 344e4480e8SJakub Kicinski 354e4480e8SJakub Kicinski NLM_F_CAPPED = 0x100 364e4480e8SJakub Kicinski NLM_F_ACK_TLVS = 0x200 374e4480e8SJakub Kicinski 384e4480e8SJakub Kicinski NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH 394e4480e8SJakub Kicinski 404e4480e8SJakub Kicinski NLA_F_NESTED = 0x8000 414e4480e8SJakub Kicinski NLA_F_NET_BYTEORDER = 0x4000 424e4480e8SJakub Kicinski 434e4480e8SJakub Kicinski NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER 444e4480e8SJakub Kicinski 454e4480e8SJakub Kicinski # Genetlink defines 464e4480e8SJakub Kicinski NETLINK_GENERIC = 16 474e4480e8SJakub Kicinski 484e4480e8SJakub Kicinski GENL_ID_CTRL = 0x10 494e4480e8SJakub Kicinski 504e4480e8SJakub Kicinski # nlctrl 514e4480e8SJakub Kicinski CTRL_CMD_GETFAMILY = 3 524e4480e8SJakub Kicinski 534e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_ID = 1 544e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_NAME = 2 554e4480e8SJakub Kicinski CTRL_ATTR_MAXATTR = 5 564e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GROUPS = 7 574e4480e8SJakub Kicinski 584e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_NAME = 1 594e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_ID = 2 604e4480e8SJakub Kicinski 614e4480e8SJakub Kicinski # Extack types 624e4480e8SJakub Kicinski NLMSGERR_ATTR_MSG = 1 634e4480e8SJakub Kicinski NLMSGERR_ATTR_OFFS = 2 644e4480e8SJakub Kicinski NLMSGERR_ATTR_COOKIE = 3 654e4480e8SJakub Kicinski NLMSGERR_ATTR_POLICY = 4 664e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_TYPE = 5 674e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_NEST = 6 684e4480e8SJakub Kicinski 694e4480e8SJakub Kicinski 704e4480e8SJakub Kicinskiclass NlAttr: 714e4480e8SJakub Kicinski def __init__(self, raw, offset): 724e4480e8SJakub Kicinski self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) 734e4480e8SJakub Kicinski self.type = self._type & ~Netlink.NLA_TYPE_MASK 744e4480e8SJakub Kicinski self.payload_len = self._len 754e4480e8SJakub Kicinski self.full_len = (self.payload_len + 3) & ~3 764e4480e8SJakub Kicinski self.raw = raw[offset + 4:offset + self.payload_len] 774e4480e8SJakub Kicinski 7819b64b48SJakub Kicinski def as_u8(self): 7919b64b48SJakub Kicinski return struct.unpack("B", self.raw)[0] 8019b64b48SJakub Kicinski 814e4480e8SJakub Kicinski def as_u16(self): 824e4480e8SJakub Kicinski return struct.unpack("H", self.raw)[0] 834e4480e8SJakub Kicinski 844e4480e8SJakub Kicinski def as_u32(self): 854e4480e8SJakub Kicinski return struct.unpack("I", self.raw)[0] 864e4480e8SJakub Kicinski 874e4480e8SJakub Kicinski def as_u64(self): 884e4480e8SJakub Kicinski return struct.unpack("Q", self.raw)[0] 894e4480e8SJakub Kicinski 904e4480e8SJakub Kicinski def as_strz(self): 914e4480e8SJakub Kicinski return self.raw.decode('ascii')[:-1] 924e4480e8SJakub Kicinski 934e4480e8SJakub Kicinski def as_bin(self): 944e4480e8SJakub Kicinski return self.raw 954e4480e8SJakub Kicinski 964e4480e8SJakub Kicinski def __repr__(self): 974e4480e8SJakub Kicinski return f"[type:{self.type} len:{self._len}] {self.raw}" 984e4480e8SJakub Kicinski 994e4480e8SJakub Kicinski 1004e4480e8SJakub Kicinskiclass NlAttrs: 1014e4480e8SJakub Kicinski def __init__(self, msg): 1024e4480e8SJakub Kicinski self.attrs = [] 1034e4480e8SJakub Kicinski 1044e4480e8SJakub Kicinski offset = 0 1054e4480e8SJakub Kicinski while offset < len(msg): 1064e4480e8SJakub Kicinski attr = NlAttr(msg, offset) 1074e4480e8SJakub Kicinski offset += attr.full_len 1084e4480e8SJakub Kicinski self.attrs.append(attr) 1094e4480e8SJakub Kicinski 1104e4480e8SJakub Kicinski def __iter__(self): 1114e4480e8SJakub Kicinski yield from self.attrs 1124e4480e8SJakub Kicinski 1134e4480e8SJakub Kicinski def __repr__(self): 1144e4480e8SJakub Kicinski msg = '' 1154e4480e8SJakub Kicinski for a in self.attrs: 1164e4480e8SJakub Kicinski if msg: 1174e4480e8SJakub Kicinski msg += '\n' 1184e4480e8SJakub Kicinski msg += repr(a) 1194e4480e8SJakub Kicinski return msg 1204e4480e8SJakub Kicinski 1214e4480e8SJakub Kicinski 1224e4480e8SJakub Kicinskiclass NlMsg: 1234e4480e8SJakub Kicinski def __init__(self, msg, offset, attr_space=None): 1244e4480e8SJakub Kicinski self.hdr = msg[offset:offset + 16] 1254e4480e8SJakub Kicinski 1264e4480e8SJakub Kicinski self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ 1274e4480e8SJakub Kicinski struct.unpack("IHHII", self.hdr) 1284e4480e8SJakub Kicinski 1294e4480e8SJakub Kicinski self.raw = msg[offset + 16:offset + self.nl_len] 1304e4480e8SJakub Kicinski 1314e4480e8SJakub Kicinski self.error = 0 1324e4480e8SJakub Kicinski self.done = 0 1334e4480e8SJakub Kicinski 1344e4480e8SJakub Kicinski extack_off = None 1354e4480e8SJakub Kicinski if self.nl_type == Netlink.NLMSG_ERROR: 1364e4480e8SJakub Kicinski self.error = struct.unpack("i", self.raw[0:4])[0] 1374e4480e8SJakub Kicinski self.done = 1 1384e4480e8SJakub Kicinski extack_off = 20 1394e4480e8SJakub Kicinski elif self.nl_type == Netlink.NLMSG_DONE: 1404e4480e8SJakub Kicinski self.done = 1 1414e4480e8SJakub Kicinski extack_off = 4 1424e4480e8SJakub Kicinski 1434e4480e8SJakub Kicinski self.extack = None 1444e4480e8SJakub Kicinski if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: 1454e4480e8SJakub Kicinski self.extack = dict() 1464e4480e8SJakub Kicinski extack_attrs = NlAttrs(self.raw[extack_off:]) 1474e4480e8SJakub Kicinski for extack in extack_attrs: 1484e4480e8SJakub Kicinski if extack.type == Netlink.NLMSGERR_ATTR_MSG: 1494e4480e8SJakub Kicinski self.extack['msg'] = extack.as_strz() 1504e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: 1514e4480e8SJakub Kicinski self.extack['miss-type'] = extack.as_u32() 1524e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: 1534e4480e8SJakub Kicinski self.extack['miss-nest'] = extack.as_u32() 1544e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 1554e4480e8SJakub Kicinski self.extack['bad-attr-offs'] = extack.as_u32() 1564e4480e8SJakub Kicinski else: 1574e4480e8SJakub Kicinski if 'unknown' not in self.extack: 1584e4480e8SJakub Kicinski self.extack['unknown'] = [] 1594e4480e8SJakub Kicinski self.extack['unknown'].append(extack) 1604e4480e8SJakub Kicinski 1614e4480e8SJakub Kicinski if attr_space: 1624e4480e8SJakub Kicinski # We don't have the ability to parse nests yet, so only do global 1634e4480e8SJakub Kicinski if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 1644e4480e8SJakub Kicinski miss_type = self.extack['miss-type'] 16530a5c6c8SJakub Kicinski if miss_type in attr_space.attrs_by_val: 16630a5c6c8SJakub Kicinski spec = attr_space.attrs_by_val[miss_type] 1674e4480e8SJakub Kicinski desc = spec['name'] 1684e4480e8SJakub Kicinski if 'doc' in spec: 1694e4480e8SJakub Kicinski desc += f" ({spec['doc']})" 1704e4480e8SJakub Kicinski self.extack['miss-type'] = desc 1714e4480e8SJakub Kicinski 1724e4480e8SJakub Kicinski def __repr__(self): 1734e4480e8SJakub Kicinski msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n" 1744e4480e8SJakub Kicinski if self.error: 1754e4480e8SJakub Kicinski msg += '\terror: ' + str(self.error) 1764e4480e8SJakub Kicinski if self.extack: 1774e4480e8SJakub Kicinski msg += '\textack: ' + repr(self.extack) 1784e4480e8SJakub Kicinski return msg 1794e4480e8SJakub Kicinski 1804e4480e8SJakub Kicinski 1814e4480e8SJakub Kicinskiclass NlMsgs: 1824e4480e8SJakub Kicinski def __init__(self, data, attr_space=None): 1834e4480e8SJakub Kicinski self.msgs = [] 1844e4480e8SJakub Kicinski 1854e4480e8SJakub Kicinski offset = 0 1864e4480e8SJakub Kicinski while offset < len(data): 1874e4480e8SJakub Kicinski msg = NlMsg(data, offset, attr_space=attr_space) 1884e4480e8SJakub Kicinski offset += msg.nl_len 1894e4480e8SJakub Kicinski self.msgs.append(msg) 1904e4480e8SJakub Kicinski 1914e4480e8SJakub Kicinski def __iter__(self): 1924e4480e8SJakub Kicinski yield from self.msgs 1934e4480e8SJakub Kicinski 1944e4480e8SJakub Kicinski 1954e4480e8SJakub Kicinskigenl_family_name_to_id = None 1964e4480e8SJakub Kicinski 1974e4480e8SJakub Kicinski 1984e4480e8SJakub Kicinskidef _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): 1994e4480e8SJakub Kicinski # we prepend length in _genl_msg_finalize() 2004e4480e8SJakub Kicinski if seq is None: 2014e4480e8SJakub Kicinski seq = random.randint(1, 1024) 2024e4480e8SJakub Kicinski nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 2034e4480e8SJakub Kicinski genlmsg = struct.pack("bbH", genl_cmd, genl_version, 0) 2044e4480e8SJakub Kicinski return nlmsg + genlmsg 2054e4480e8SJakub Kicinski 2064e4480e8SJakub Kicinski 2074e4480e8SJakub Kicinskidef _genl_msg_finalize(msg): 2084e4480e8SJakub Kicinski return struct.pack("I", len(msg) + 4) + msg 2094e4480e8SJakub Kicinski 2104e4480e8SJakub Kicinski 2114e4480e8SJakub Kicinskidef _genl_load_families(): 2124e4480e8SJakub Kicinski with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 2134e4480e8SJakub Kicinski sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 2144e4480e8SJakub Kicinski 2154e4480e8SJakub Kicinski msg = _genl_msg(Netlink.GENL_ID_CTRL, 2164e4480e8SJakub Kicinski Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 2174e4480e8SJakub Kicinski Netlink.CTRL_CMD_GETFAMILY, 1) 2184e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 2194e4480e8SJakub Kicinski 2204e4480e8SJakub Kicinski sock.send(msg, 0) 2214e4480e8SJakub Kicinski 2224e4480e8SJakub Kicinski global genl_family_name_to_id 2234e4480e8SJakub Kicinski genl_family_name_to_id = dict() 2244e4480e8SJakub Kicinski 2254e4480e8SJakub Kicinski while True: 2264e4480e8SJakub Kicinski reply = sock.recv(128 * 1024) 2274e4480e8SJakub Kicinski nms = NlMsgs(reply) 2284e4480e8SJakub Kicinski for nl_msg in nms: 2294e4480e8SJakub Kicinski if nl_msg.error: 2304e4480e8SJakub Kicinski print("Netlink error:", nl_msg.error) 2314e4480e8SJakub Kicinski return 2324e4480e8SJakub Kicinski if nl_msg.done: 2334e4480e8SJakub Kicinski return 2344e4480e8SJakub Kicinski 2354e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 2364e4480e8SJakub Kicinski fam = dict() 2374e4480e8SJakub Kicinski for attr in gm.raw_attrs: 2384e4480e8SJakub Kicinski if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: 2394e4480e8SJakub Kicinski fam['id'] = attr.as_u16() 2404e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: 2414e4480e8SJakub Kicinski fam['name'] = attr.as_strz() 2424e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MAXATTR: 2434e4480e8SJakub Kicinski fam['maxattr'] = attr.as_u32() 2444e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: 2454e4480e8SJakub Kicinski fam['mcast'] = dict() 2464e4480e8SJakub Kicinski for entry in NlAttrs(attr.raw): 2474e4480e8SJakub Kicinski mcast_name = None 2484e4480e8SJakub Kicinski mcast_id = None 2494e4480e8SJakub Kicinski for entry_attr in NlAttrs(entry.raw): 2504e4480e8SJakub Kicinski if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: 2514e4480e8SJakub Kicinski mcast_name = entry_attr.as_strz() 2524e4480e8SJakub Kicinski elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: 2534e4480e8SJakub Kicinski mcast_id = entry_attr.as_u32() 2544e4480e8SJakub Kicinski if mcast_name and mcast_id is not None: 2554e4480e8SJakub Kicinski fam['mcast'][mcast_name] = mcast_id 2564e4480e8SJakub Kicinski if 'name' in fam and 'id' in fam: 2574e4480e8SJakub Kicinski genl_family_name_to_id[fam['name']] = fam 2584e4480e8SJakub Kicinski 2594e4480e8SJakub Kicinski 2604e4480e8SJakub Kicinskiclass GenlMsg: 2614e4480e8SJakub Kicinski def __init__(self, nl_msg): 2624e4480e8SJakub Kicinski self.nl = nl_msg 2634e4480e8SJakub Kicinski 2644e4480e8SJakub Kicinski self.hdr = nl_msg.raw[0:4] 2654e4480e8SJakub Kicinski self.raw = nl_msg.raw[4:] 2664e4480e8SJakub Kicinski 2674e4480e8SJakub Kicinski self.genl_cmd, self.genl_version, _ = struct.unpack("bbH", self.hdr) 2684e4480e8SJakub Kicinski 2694e4480e8SJakub Kicinski self.raw_attrs = NlAttrs(self.raw) 2704e4480e8SJakub Kicinski 2714e4480e8SJakub Kicinski def __repr__(self): 2724e4480e8SJakub Kicinski msg = repr(self.nl) 2734e4480e8SJakub Kicinski msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" 2744e4480e8SJakub Kicinski for a in self.raw_attrs: 2754e4480e8SJakub Kicinski msg += '\t\t' + repr(a) + '\n' 2764e4480e8SJakub Kicinski return msg 2774e4480e8SJakub Kicinski 2784e4480e8SJakub Kicinski 2794e4480e8SJakub Kicinskiclass GenlFamily: 2804e4480e8SJakub Kicinski def __init__(self, family_name): 2814e4480e8SJakub Kicinski self.family_name = family_name 2824e4480e8SJakub Kicinski 2834e4480e8SJakub Kicinski global genl_family_name_to_id 2844e4480e8SJakub Kicinski if genl_family_name_to_id is None: 2854e4480e8SJakub Kicinski _genl_load_families() 2864e4480e8SJakub Kicinski 2874e4480e8SJakub Kicinski self.genl_family = genl_family_name_to_id[family_name] 2884e4480e8SJakub Kicinski self.family_id = genl_family_name_to_id[family_name]['id'] 2894e4480e8SJakub Kicinski 2904e4480e8SJakub Kicinski 2914e4480e8SJakub Kicinski# 2924e4480e8SJakub Kicinski# YNL implementation details. 2934e4480e8SJakub Kicinski# 2944e4480e8SJakub Kicinski 2954e4480e8SJakub Kicinski 29630a5c6c8SJakub Kicinskiclass YnlFamily(SpecFamily): 2974e4480e8SJakub Kicinski def __init__(self, def_path, schema=None): 29830a5c6c8SJakub Kicinski super().__init__(def_path, schema) 29930a5c6c8SJakub Kicinski 3004e4480e8SJakub Kicinski self.include_raw = False 3014e4480e8SJakub Kicinski 3024e4480e8SJakub Kicinski self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) 3034e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 3044e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 3054e4480e8SJakub Kicinski 3064e4480e8SJakub Kicinski self._types = dict() 3074e4480e8SJakub Kicinski 30819b64b48SJakub Kicinski for elem in self.yaml.get('definitions', []): 3094e4480e8SJakub Kicinski self._types[elem['name']] = elem 3104e4480e8SJakub Kicinski 3114e4480e8SJakub Kicinski self.async_msg_ids = set() 3124e4480e8SJakub Kicinski self.async_msg_queue = [] 3134e4480e8SJakub Kicinski 31430a5c6c8SJakub Kicinski for msg in self.msgs.values(): 31530a5c6c8SJakub Kicinski if msg.is_async: 316fd0616d3SJakub Kicinski self.async_msg_ids.add(msg.rsp_value) 3174e4480e8SJakub Kicinski 31830a5c6c8SJakub Kicinski for op_name, op in self.ops.items(): 31930a5c6c8SJakub Kicinski bound_f = functools.partial(self._op, op_name) 32030a5c6c8SJakub Kicinski setattr(self, op.ident_name, bound_f) 3214e4480e8SJakub Kicinski 3224e4480e8SJakub Kicinski self.family = GenlFamily(self.yaml['name']) 3234e4480e8SJakub Kicinski 3244e4480e8SJakub Kicinski def ntf_subscribe(self, mcast_name): 3254e4480e8SJakub Kicinski if mcast_name not in self.family.genl_family['mcast']: 3264e4480e8SJakub Kicinski raise Exception(f'Multicast group "{mcast_name}" not present in the family') 3274e4480e8SJakub Kicinski 3284e4480e8SJakub Kicinski self.sock.bind((0, 0)) 3294e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 3304e4480e8SJakub Kicinski self.family.genl_family['mcast'][mcast_name]) 3314e4480e8SJakub Kicinski 3324e4480e8SJakub Kicinski def _add_attr(self, space, name, value): 33330a5c6c8SJakub Kicinski attr = self.attr_sets[space][name] 33430a5c6c8SJakub Kicinski nl_type = attr.value 3354e4480e8SJakub Kicinski if attr["type"] == 'nest': 3364e4480e8SJakub Kicinski nl_type |= Netlink.NLA_F_NESTED 3374e4480e8SJakub Kicinski attr_payload = b'' 3384e4480e8SJakub Kicinski for subname, subvalue in value.items(): 3394e4480e8SJakub Kicinski attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) 34019b64b48SJakub Kicinski elif attr["type"] == 'flag': 34119b64b48SJakub Kicinski attr_payload = b'' 3424e4480e8SJakub Kicinski elif attr["type"] == 'u32': 3434e4480e8SJakub Kicinski attr_payload = struct.pack("I", int(value)) 3444e4480e8SJakub Kicinski elif attr["type"] == 'string': 3454e4480e8SJakub Kicinski attr_payload = str(value).encode('ascii') + b'\x00' 3464e4480e8SJakub Kicinski elif attr["type"] == 'binary': 3474e4480e8SJakub Kicinski attr_payload = value 3484e4480e8SJakub Kicinski else: 3494e4480e8SJakub Kicinski raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') 3504e4480e8SJakub Kicinski 3514e4480e8SJakub Kicinski pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 3524e4480e8SJakub Kicinski return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 3534e4480e8SJakub Kicinski 3544e4480e8SJakub Kicinski def _decode_enum(self, rsp, attr_spec): 3554e4480e8SJakub Kicinski raw = rsp[attr_spec['name']] 3564e4480e8SJakub Kicinski enum = self._types[attr_spec['enum']] 3574e4480e8SJakub Kicinski i = attr_spec.get('value-start', 0) 3584e4480e8SJakub Kicinski if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']: 3594e4480e8SJakub Kicinski value = set() 3604e4480e8SJakub Kicinski while raw: 3614e4480e8SJakub Kicinski if raw & 1: 3624e4480e8SJakub Kicinski value.add(enum['entries'][i]) 3634e4480e8SJakub Kicinski raw >>= 1 3644e4480e8SJakub Kicinski i += 1 3654e4480e8SJakub Kicinski else: 3664e4480e8SJakub Kicinski value = enum['entries'][raw - i] 3674e4480e8SJakub Kicinski rsp[attr_spec['name']] = value 3684e4480e8SJakub Kicinski 3694e4480e8SJakub Kicinski def _decode(self, attrs, space): 37030a5c6c8SJakub Kicinski attr_space = self.attr_sets[space] 3714e4480e8SJakub Kicinski rsp = dict() 3724e4480e8SJakub Kicinski for attr in attrs: 37330a5c6c8SJakub Kicinski attr_spec = attr_space.attrs_by_val[attr.type] 3744e4480e8SJakub Kicinski if attr_spec["type"] == 'nest': 3754e4480e8SJakub Kicinski subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) 37690256f3fSJakub Kicinski decoded = subdict 37719b64b48SJakub Kicinski elif attr_spec['type'] == 'u8': 37890256f3fSJakub Kicinski decoded = attr.as_u8() 3794e4480e8SJakub Kicinski elif attr_spec['type'] == 'u32': 38090256f3fSJakub Kicinski decoded = attr.as_u32() 3814e4480e8SJakub Kicinski elif attr_spec['type'] == 'u64': 38290256f3fSJakub Kicinski decoded = attr.as_u64() 3834e4480e8SJakub Kicinski elif attr_spec["type"] == 'string': 38490256f3fSJakub Kicinski decoded = attr.as_strz() 3854e4480e8SJakub Kicinski elif attr_spec["type"] == 'binary': 38690256f3fSJakub Kicinski decoded = attr.as_bin() 38719b64b48SJakub Kicinski elif attr_spec["type"] == 'flag': 38890256f3fSJakub Kicinski decoded = True 3894e4480e8SJakub Kicinski else: 3904e4480e8SJakub Kicinski raise Exception(f'Unknown {attr.type} {attr_spec["name"]} {attr_spec["type"]}') 3914e4480e8SJakub Kicinski 39290256f3fSJakub Kicinski if not attr_spec.is_multi: 39390256f3fSJakub Kicinski rsp[attr_spec['name']] = decoded 39490256f3fSJakub Kicinski elif attr_spec.name in rsp: 39590256f3fSJakub Kicinski rsp[attr_spec.name].append(decoded) 39690256f3fSJakub Kicinski else: 39790256f3fSJakub Kicinski rsp[attr_spec.name] = [decoded] 39890256f3fSJakub Kicinski 3994e4480e8SJakub Kicinski if 'enum' in attr_spec: 4004e4480e8SJakub Kicinski self._decode_enum(rsp, attr_spec) 4014e4480e8SJakub Kicinski return rsp 4024e4480e8SJakub Kicinski 4034cd2796fSJakub Kicinski def _decode_extack_path(self, attrs, attr_set, offset, target): 4044cd2796fSJakub Kicinski for attr in attrs: 4054cd2796fSJakub Kicinski attr_spec = attr_set.attrs_by_val[attr.type] 4064cd2796fSJakub Kicinski if offset > target: 4074cd2796fSJakub Kicinski break 4084cd2796fSJakub Kicinski if offset == target: 4094cd2796fSJakub Kicinski return '.' + attr_spec.name 4104cd2796fSJakub Kicinski 4114cd2796fSJakub Kicinski if offset + attr.full_len <= target: 4124cd2796fSJakub Kicinski offset += attr.full_len 4134cd2796fSJakub Kicinski continue 4144cd2796fSJakub Kicinski if attr_spec['type'] != 'nest': 4154cd2796fSJakub Kicinski raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 4164cd2796fSJakub Kicinski offset += 4 4174cd2796fSJakub Kicinski subpath = self._decode_extack_path(NlAttrs(attr.raw), 4184cd2796fSJakub Kicinski self.attr_sets[attr_spec['nested-attributes']], 4194cd2796fSJakub Kicinski offset, target) 4204cd2796fSJakub Kicinski if subpath is None: 4214cd2796fSJakub Kicinski return None 4224cd2796fSJakub Kicinski return '.' + attr_spec.name + subpath 4234cd2796fSJakub Kicinski 4244cd2796fSJakub Kicinski return None 4254cd2796fSJakub Kicinski 4264cd2796fSJakub Kicinski def _decode_extack(self, request, attr_space, extack): 4274cd2796fSJakub Kicinski if 'bad-attr-offs' not in extack: 4284cd2796fSJakub Kicinski return 4294cd2796fSJakub Kicinski 4304cd2796fSJakub Kicinski genl_req = GenlMsg(NlMsg(request, 0, attr_space=attr_space)) 4314cd2796fSJakub Kicinski path = self._decode_extack_path(genl_req.raw_attrs, attr_space, 4324cd2796fSJakub Kicinski 20, extack['bad-attr-offs']) 4334cd2796fSJakub Kicinski if path: 4344cd2796fSJakub Kicinski del extack['bad-attr-offs'] 4354cd2796fSJakub Kicinski extack['bad-attr'] = path 4364cd2796fSJakub Kicinski 4374e4480e8SJakub Kicinski def handle_ntf(self, nl_msg, genl_msg): 4384e4480e8SJakub Kicinski msg = dict() 4394e4480e8SJakub Kicinski if self.include_raw: 4404e4480e8SJakub Kicinski msg['nlmsg'] = nl_msg 4414e4480e8SJakub Kicinski msg['genlmsg'] = genl_msg 442fd0616d3SJakub Kicinski op = self.rsp_by_value[genl_msg.genl_cmd] 4434e4480e8SJakub Kicinski msg['name'] = op['name'] 44430a5c6c8SJakub Kicinski msg['msg'] = self._decode(genl_msg.raw_attrs, op.attr_set.name) 4454e4480e8SJakub Kicinski self.async_msg_queue.append(msg) 4464e4480e8SJakub Kicinski 4474e4480e8SJakub Kicinski def check_ntf(self): 4484e4480e8SJakub Kicinski while True: 4494e4480e8SJakub Kicinski try: 4504e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT) 4514e4480e8SJakub Kicinski except BlockingIOError: 4524e4480e8SJakub Kicinski return 4534e4480e8SJakub Kicinski 4544e4480e8SJakub Kicinski nms = NlMsgs(reply) 4554e4480e8SJakub Kicinski for nl_msg in nms: 4564e4480e8SJakub Kicinski if nl_msg.error: 4574e4480e8SJakub Kicinski print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 4584e4480e8SJakub Kicinski print(nl_msg) 4594e4480e8SJakub Kicinski continue 4604e4480e8SJakub Kicinski if nl_msg.done: 4614e4480e8SJakub Kicinski print("Netlink done while checking for ntf!?") 4624e4480e8SJakub Kicinski continue 4634e4480e8SJakub Kicinski 4644e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 4654e4480e8SJakub Kicinski if gm.genl_cmd not in self.async_msg_ids: 4664e4480e8SJakub Kicinski print("Unexpected msg id done while checking for ntf", gm) 4674e4480e8SJakub Kicinski continue 4684e4480e8SJakub Kicinski 4694e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 4704e4480e8SJakub Kicinski 4714e4480e8SJakub Kicinski def _op(self, method, vals, dump=False): 47230a5c6c8SJakub Kicinski op = self.ops[method] 4734e4480e8SJakub Kicinski 4744e4480e8SJakub Kicinski nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 4754e4480e8SJakub Kicinski if dump: 4764e4480e8SJakub Kicinski nl_flags |= Netlink.NLM_F_DUMP 4774e4480e8SJakub Kicinski 4784e4480e8SJakub Kicinski req_seq = random.randint(1024, 65535) 479fd0616d3SJakub Kicinski msg = _genl_msg(self.family.family_id, nl_flags, op.req_value, 1, req_seq) 4804e4480e8SJakub Kicinski for name, value in vals.items(): 48130a5c6c8SJakub Kicinski msg += self._add_attr(op.attr_set.name, name, value) 4824e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 4834e4480e8SJakub Kicinski 4844e4480e8SJakub Kicinski self.sock.send(msg, 0) 4854e4480e8SJakub Kicinski 4864e4480e8SJakub Kicinski done = False 4874e4480e8SJakub Kicinski rsp = [] 4884e4480e8SJakub Kicinski while not done: 4894e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024) 49030a5c6c8SJakub Kicinski nms = NlMsgs(reply, attr_space=op.attr_set) 4914e4480e8SJakub Kicinski for nl_msg in nms: 4924cd2796fSJakub Kicinski if nl_msg.extack: 4934cd2796fSJakub Kicinski self._decode_extack(msg, op.attr_set, nl_msg.extack) 4944cd2796fSJakub Kicinski 4954e4480e8SJakub Kicinski if nl_msg.error: 4964e4480e8SJakub Kicinski print("Netlink error:", os.strerror(-nl_msg.error)) 4974e4480e8SJakub Kicinski print(nl_msg) 4984e4480e8SJakub Kicinski return 4994e4480e8SJakub Kicinski if nl_msg.done: 5004cd2796fSJakub Kicinski if nl_msg.extack: 5014cd2796fSJakub Kicinski print("Netlink warning:") 5024cd2796fSJakub Kicinski print(nl_msg) 5034e4480e8SJakub Kicinski done = True 5044e4480e8SJakub Kicinski break 5054e4480e8SJakub Kicinski 5064e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 5074e4480e8SJakub Kicinski # Check if this is a reply to our request 508fd0616d3SJakub Kicinski if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.rsp_value: 5094e4480e8SJakub Kicinski if gm.genl_cmd in self.async_msg_ids: 5104e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 5114e4480e8SJakub Kicinski continue 5124e4480e8SJakub Kicinski else: 5134e4480e8SJakub Kicinski print('Unexpected message: ' + repr(gm)) 5144e4480e8SJakub Kicinski continue 5154e4480e8SJakub Kicinski 51630a5c6c8SJakub Kicinski rsp.append(self._decode(gm.raw_attrs, op.attr_set.name)) 5174e4480e8SJakub Kicinski 5184e4480e8SJakub Kicinski if not rsp: 5194e4480e8SJakub Kicinski return None 5204e4480e8SJakub Kicinski if not dump and len(rsp) == 1: 5214e4480e8SJakub Kicinski return rsp[0] 5224e4480e8SJakub Kicinski return rsp 5238dfec0a8SJakub Kicinski 5248dfec0a8SJakub Kicinski def do(self, method, vals): 5258dfec0a8SJakub Kicinski return self._op(method, vals) 5268dfec0a8SJakub Kicinski 5278dfec0a8SJakub Kicinski def dump(self, method, vals): 5288dfec0a8SJakub Kicinski return self._op(method, vals, dump=True) 529