137d9df22SJakub 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 70*48993e22SStanislav Fomichevclass NlError(Exception): 71*48993e22SStanislav Fomichev def __init__(self, nl_msg): 72*48993e22SStanislav Fomichev self.nl_msg = nl_msg 73*48993e22SStanislav Fomichev 74*48993e22SStanislav Fomichev def __str__(self): 75*48993e22SStanislav Fomichev return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}" 76*48993e22SStanislav Fomichev 77*48993e22SStanislav Fomichev 784e4480e8SJakub Kicinskiclass NlAttr: 79b423c3c8SDonald Hunter type_formats = { 'u8' : ('B', 1), 's8' : ('b', 1), 80b423c3c8SDonald Hunter 'u16': ('H', 2), 's16': ('h', 2), 81b423c3c8SDonald Hunter 'u32': ('I', 4), 's32': ('i', 4), 82b423c3c8SDonald Hunter 'u64': ('Q', 8), 's64': ('q', 8) } 83b423c3c8SDonald Hunter 844e4480e8SJakub Kicinski def __init__(self, raw, offset): 854e4480e8SJakub Kicinski self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) 864e4480e8SJakub Kicinski self.type = self._type & ~Netlink.NLA_TYPE_MASK 874e4480e8SJakub Kicinski self.payload_len = self._len 884e4480e8SJakub Kicinski self.full_len = (self.payload_len + 3) & ~3 894e4480e8SJakub Kicinski self.raw = raw[offset + 4:offset + self.payload_len] 904e4480e8SJakub Kicinski 919f7cc57fSStanislav Fomichev def format_byte_order(byte_order): 929f7cc57fSStanislav Fomichev if byte_order: 939f7cc57fSStanislav Fomichev return ">" if byte_order == "big-endian" else "<" 949f7cc57fSStanislav Fomichev return "" 959f7cc57fSStanislav Fomichev 9619b64b48SJakub Kicinski def as_u8(self): 9719b64b48SJakub Kicinski return struct.unpack("B", self.raw)[0] 9819b64b48SJakub Kicinski 999f7cc57fSStanislav Fomichev def as_u16(self, byte_order=None): 1009f7cc57fSStanislav Fomichev endian = NlAttr.format_byte_order(byte_order) 1019f7cc57fSStanislav Fomichev return struct.unpack(f"{endian}H", self.raw)[0] 1024e4480e8SJakub Kicinski 1039f7cc57fSStanislav Fomichev def as_u32(self, byte_order=None): 1049f7cc57fSStanislav Fomichev endian = NlAttr.format_byte_order(byte_order) 1059f7cc57fSStanislav Fomichev return struct.unpack(f"{endian}I", self.raw)[0] 1064e4480e8SJakub Kicinski 1079f7cc57fSStanislav Fomichev def as_u64(self, byte_order=None): 1089f7cc57fSStanislav Fomichev endian = NlAttr.format_byte_order(byte_order) 1099f7cc57fSStanislav Fomichev return struct.unpack(f"{endian}Q", self.raw)[0] 1104e4480e8SJakub Kicinski 1114e4480e8SJakub Kicinski def as_strz(self): 1124e4480e8SJakub Kicinski return self.raw.decode('ascii')[:-1] 1134e4480e8SJakub Kicinski 1144e4480e8SJakub Kicinski def as_bin(self): 1154e4480e8SJakub Kicinski return self.raw 1164e4480e8SJakub Kicinski 117b423c3c8SDonald Hunter def as_c_array(self, type): 118b423c3c8SDonald Hunter format, _ = self.type_formats[type] 119b423c3c8SDonald Hunter return list({ x[0] for x in struct.iter_unpack(format, self.raw) }) 120b423c3c8SDonald Hunter 12126071913SDonald Hunter def as_struct(self, members): 12226071913SDonald Hunter value = dict() 12326071913SDonald Hunter offset = 0 12426071913SDonald Hunter for m in members: 12526071913SDonald Hunter # TODO: handle non-scalar members 12626071913SDonald Hunter format, size = self.type_formats[m.type] 12726071913SDonald Hunter decoded = struct.unpack_from(format, self.raw, offset) 12826071913SDonald Hunter offset += size 12926071913SDonald Hunter value[m.name] = decoded[0] 13026071913SDonald Hunter return value 13126071913SDonald Hunter 1324e4480e8SJakub Kicinski def __repr__(self): 1334e4480e8SJakub Kicinski return f"[type:{self.type} len:{self._len}] {self.raw}" 1344e4480e8SJakub Kicinski 1354e4480e8SJakub Kicinski 1364e4480e8SJakub Kicinskiclass NlAttrs: 1374e4480e8SJakub Kicinski def __init__(self, msg): 1384e4480e8SJakub Kicinski self.attrs = [] 1394e4480e8SJakub Kicinski 1404e4480e8SJakub Kicinski offset = 0 1414e4480e8SJakub Kicinski while offset < len(msg): 1424e4480e8SJakub Kicinski attr = NlAttr(msg, offset) 1434e4480e8SJakub Kicinski offset += attr.full_len 1444e4480e8SJakub Kicinski self.attrs.append(attr) 1454e4480e8SJakub Kicinski 1464e4480e8SJakub Kicinski def __iter__(self): 1474e4480e8SJakub Kicinski yield from self.attrs 1484e4480e8SJakub Kicinski 1494e4480e8SJakub Kicinski def __repr__(self): 1504e4480e8SJakub Kicinski msg = '' 1514e4480e8SJakub Kicinski for a in self.attrs: 1524e4480e8SJakub Kicinski if msg: 1534e4480e8SJakub Kicinski msg += '\n' 1544e4480e8SJakub Kicinski msg += repr(a) 1554e4480e8SJakub Kicinski return msg 1564e4480e8SJakub Kicinski 1574e4480e8SJakub Kicinski 1584e4480e8SJakub Kicinskiclass NlMsg: 1594e4480e8SJakub Kicinski def __init__(self, msg, offset, attr_space=None): 1604e4480e8SJakub Kicinski self.hdr = msg[offset:offset + 16] 1614e4480e8SJakub Kicinski 1624e4480e8SJakub Kicinski self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ 1634e4480e8SJakub Kicinski struct.unpack("IHHII", self.hdr) 1644e4480e8SJakub Kicinski 1654e4480e8SJakub Kicinski self.raw = msg[offset + 16:offset + self.nl_len] 1664e4480e8SJakub Kicinski 1674e4480e8SJakub Kicinski self.error = 0 1684e4480e8SJakub Kicinski self.done = 0 1694e4480e8SJakub Kicinski 1704e4480e8SJakub Kicinski extack_off = None 1714e4480e8SJakub Kicinski if self.nl_type == Netlink.NLMSG_ERROR: 1724e4480e8SJakub Kicinski self.error = struct.unpack("i", self.raw[0:4])[0] 1734e4480e8SJakub Kicinski self.done = 1 1744e4480e8SJakub Kicinski extack_off = 20 1754e4480e8SJakub Kicinski elif self.nl_type == Netlink.NLMSG_DONE: 1764e4480e8SJakub Kicinski self.done = 1 1774e4480e8SJakub Kicinski extack_off = 4 1784e4480e8SJakub Kicinski 1794e4480e8SJakub Kicinski self.extack = None 1804e4480e8SJakub Kicinski if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: 1814e4480e8SJakub Kicinski self.extack = dict() 1824e4480e8SJakub Kicinski extack_attrs = NlAttrs(self.raw[extack_off:]) 1834e4480e8SJakub Kicinski for extack in extack_attrs: 1844e4480e8SJakub Kicinski if extack.type == Netlink.NLMSGERR_ATTR_MSG: 1854e4480e8SJakub Kicinski self.extack['msg'] = extack.as_strz() 1864e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: 1874e4480e8SJakub Kicinski self.extack['miss-type'] = extack.as_u32() 1884e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: 1894e4480e8SJakub Kicinski self.extack['miss-nest'] = extack.as_u32() 1904e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 1914e4480e8SJakub Kicinski self.extack['bad-attr-offs'] = extack.as_u32() 1924e4480e8SJakub Kicinski else: 1934e4480e8SJakub Kicinski if 'unknown' not in self.extack: 1944e4480e8SJakub Kicinski self.extack['unknown'] = [] 1954e4480e8SJakub Kicinski self.extack['unknown'].append(extack) 1964e4480e8SJakub Kicinski 1974e4480e8SJakub Kicinski if attr_space: 1984e4480e8SJakub Kicinski # We don't have the ability to parse nests yet, so only do global 1994e4480e8SJakub Kicinski if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 2004e4480e8SJakub Kicinski miss_type = self.extack['miss-type'] 20130a5c6c8SJakub Kicinski if miss_type in attr_space.attrs_by_val: 20230a5c6c8SJakub Kicinski spec = attr_space.attrs_by_val[miss_type] 2034e4480e8SJakub Kicinski desc = spec['name'] 2044e4480e8SJakub Kicinski if 'doc' in spec: 2054e4480e8SJakub Kicinski desc += f" ({spec['doc']})" 2064e4480e8SJakub Kicinski self.extack['miss-type'] = desc 2074e4480e8SJakub Kicinski 2084e4480e8SJakub Kicinski def __repr__(self): 2094e4480e8SJakub Kicinski msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n" 2104e4480e8SJakub Kicinski if self.error: 2114e4480e8SJakub Kicinski msg += '\terror: ' + str(self.error) 2124e4480e8SJakub Kicinski if self.extack: 2134e4480e8SJakub Kicinski msg += '\textack: ' + repr(self.extack) 2144e4480e8SJakub Kicinski return msg 2154e4480e8SJakub Kicinski 2164e4480e8SJakub Kicinski 2174e4480e8SJakub Kicinskiclass NlMsgs: 2184e4480e8SJakub Kicinski def __init__(self, data, attr_space=None): 2194e4480e8SJakub Kicinski self.msgs = [] 2204e4480e8SJakub Kicinski 2214e4480e8SJakub Kicinski offset = 0 2224e4480e8SJakub Kicinski while offset < len(data): 2234e4480e8SJakub Kicinski msg = NlMsg(data, offset, attr_space=attr_space) 2244e4480e8SJakub Kicinski offset += msg.nl_len 2254e4480e8SJakub Kicinski self.msgs.append(msg) 2264e4480e8SJakub Kicinski 2274e4480e8SJakub Kicinski def __iter__(self): 2284e4480e8SJakub Kicinski yield from self.msgs 2294e4480e8SJakub Kicinski 2304e4480e8SJakub Kicinski 2314e4480e8SJakub Kicinskigenl_family_name_to_id = None 2324e4480e8SJakub Kicinski 2334e4480e8SJakub Kicinski 2344e4480e8SJakub Kicinskidef _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): 2354e4480e8SJakub Kicinski # we prepend length in _genl_msg_finalize() 2364e4480e8SJakub Kicinski if seq is None: 2374e4480e8SJakub Kicinski seq = random.randint(1, 1024) 2384e4480e8SJakub Kicinski nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 239758d29fbSDonald Hunter genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0) 2404e4480e8SJakub Kicinski return nlmsg + genlmsg 2414e4480e8SJakub Kicinski 2424e4480e8SJakub Kicinski 2434e4480e8SJakub Kicinskidef _genl_msg_finalize(msg): 2444e4480e8SJakub Kicinski return struct.pack("I", len(msg) + 4) + msg 2454e4480e8SJakub Kicinski 2464e4480e8SJakub Kicinski 2474e4480e8SJakub Kicinskidef _genl_load_families(): 2484e4480e8SJakub Kicinski with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 2494e4480e8SJakub Kicinski sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 2504e4480e8SJakub Kicinski 2514e4480e8SJakub Kicinski msg = _genl_msg(Netlink.GENL_ID_CTRL, 2524e4480e8SJakub Kicinski Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 2534e4480e8SJakub Kicinski Netlink.CTRL_CMD_GETFAMILY, 1) 2544e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 2554e4480e8SJakub Kicinski 2564e4480e8SJakub Kicinski sock.send(msg, 0) 2574e4480e8SJakub Kicinski 2584e4480e8SJakub Kicinski global genl_family_name_to_id 2594e4480e8SJakub Kicinski genl_family_name_to_id = dict() 2604e4480e8SJakub Kicinski 2614e4480e8SJakub Kicinski while True: 2624e4480e8SJakub Kicinski reply = sock.recv(128 * 1024) 2634e4480e8SJakub Kicinski nms = NlMsgs(reply) 2644e4480e8SJakub Kicinski for nl_msg in nms: 2654e4480e8SJakub Kicinski if nl_msg.error: 2664e4480e8SJakub Kicinski print("Netlink error:", nl_msg.error) 2674e4480e8SJakub Kicinski return 2684e4480e8SJakub Kicinski if nl_msg.done: 2694e4480e8SJakub Kicinski return 2704e4480e8SJakub Kicinski 2714e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 2724e4480e8SJakub Kicinski fam = dict() 2734e4480e8SJakub Kicinski for attr in gm.raw_attrs: 2744e4480e8SJakub Kicinski if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: 2754e4480e8SJakub Kicinski fam['id'] = attr.as_u16() 2764e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: 2774e4480e8SJakub Kicinski fam['name'] = attr.as_strz() 2784e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MAXATTR: 2794e4480e8SJakub Kicinski fam['maxattr'] = attr.as_u32() 2804e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: 2814e4480e8SJakub Kicinski fam['mcast'] = dict() 2824e4480e8SJakub Kicinski for entry in NlAttrs(attr.raw): 2834e4480e8SJakub Kicinski mcast_name = None 2844e4480e8SJakub Kicinski mcast_id = None 2854e4480e8SJakub Kicinski for entry_attr in NlAttrs(entry.raw): 2864e4480e8SJakub Kicinski if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: 2874e4480e8SJakub Kicinski mcast_name = entry_attr.as_strz() 2884e4480e8SJakub Kicinski elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: 2894e4480e8SJakub Kicinski mcast_id = entry_attr.as_u32() 2904e4480e8SJakub Kicinski if mcast_name and mcast_id is not None: 2914e4480e8SJakub Kicinski fam['mcast'][mcast_name] = mcast_id 2924e4480e8SJakub Kicinski if 'name' in fam and 'id' in fam: 2934e4480e8SJakub Kicinski genl_family_name_to_id[fam['name']] = fam 2944e4480e8SJakub Kicinski 2954e4480e8SJakub Kicinski 2964e4480e8SJakub Kicinskiclass GenlMsg: 297f036d936SDonald Hunter def __init__(self, nl_msg, fixed_header_members=[]): 2984e4480e8SJakub Kicinski self.nl = nl_msg 2994e4480e8SJakub Kicinski 3004e4480e8SJakub Kicinski self.hdr = nl_msg.raw[0:4] 301f036d936SDonald Hunter offset = 4 3024e4480e8SJakub Kicinski 303758d29fbSDonald Hunter self.genl_cmd, self.genl_version, _ = struct.unpack("BBH", self.hdr) 3044e4480e8SJakub Kicinski 305f036d936SDonald Hunter self.fixed_header_attrs = dict() 306f036d936SDonald Hunter for m in fixed_header_members: 307f036d936SDonald Hunter format, size = NlAttr.type_formats[m.type] 308f036d936SDonald Hunter decoded = struct.unpack_from(format, nl_msg.raw, offset) 309f036d936SDonald Hunter offset += size 310f036d936SDonald Hunter self.fixed_header_attrs[m.name] = decoded[0] 311f036d936SDonald Hunter 312f036d936SDonald Hunter self.raw = nl_msg.raw[offset:] 3134e4480e8SJakub Kicinski self.raw_attrs = NlAttrs(self.raw) 3144e4480e8SJakub Kicinski 3154e4480e8SJakub Kicinski def __repr__(self): 3164e4480e8SJakub Kicinski msg = repr(self.nl) 3174e4480e8SJakub Kicinski msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" 3184e4480e8SJakub Kicinski for a in self.raw_attrs: 3194e4480e8SJakub Kicinski msg += '\t\t' + repr(a) + '\n' 3204e4480e8SJakub Kicinski return msg 3214e4480e8SJakub Kicinski 3224e4480e8SJakub Kicinski 3234e4480e8SJakub Kicinskiclass GenlFamily: 3244e4480e8SJakub Kicinski def __init__(self, family_name): 3254e4480e8SJakub Kicinski self.family_name = family_name 3264e4480e8SJakub Kicinski 3274e4480e8SJakub Kicinski global genl_family_name_to_id 3284e4480e8SJakub Kicinski if genl_family_name_to_id is None: 3294e4480e8SJakub Kicinski _genl_load_families() 3304e4480e8SJakub Kicinski 3314e4480e8SJakub Kicinski self.genl_family = genl_family_name_to_id[family_name] 3324e4480e8SJakub Kicinski self.family_id = genl_family_name_to_id[family_name]['id'] 3334e4480e8SJakub Kicinski 3344e4480e8SJakub Kicinski 3354e4480e8SJakub Kicinski# 3364e4480e8SJakub Kicinski# YNL implementation details. 3374e4480e8SJakub Kicinski# 3384e4480e8SJakub Kicinski 3394e4480e8SJakub Kicinski 34030a5c6c8SJakub Kicinskiclass YnlFamily(SpecFamily): 3414e4480e8SJakub Kicinski def __init__(self, def_path, schema=None): 34230a5c6c8SJakub Kicinski super().__init__(def_path, schema) 34330a5c6c8SJakub Kicinski 3444e4480e8SJakub Kicinski self.include_raw = False 3454e4480e8SJakub Kicinski 3464e4480e8SJakub Kicinski self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) 3474e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 3484e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 3494e4480e8SJakub Kicinski 3504e4480e8SJakub Kicinski self.async_msg_ids = set() 3514e4480e8SJakub Kicinski self.async_msg_queue = [] 3524e4480e8SJakub Kicinski 35330a5c6c8SJakub Kicinski for msg in self.msgs.values(): 35430a5c6c8SJakub Kicinski if msg.is_async: 355fd0616d3SJakub Kicinski self.async_msg_ids.add(msg.rsp_value) 3564e4480e8SJakub Kicinski 35730a5c6c8SJakub Kicinski for op_name, op in self.ops.items(): 35830a5c6c8SJakub Kicinski bound_f = functools.partial(self._op, op_name) 35930a5c6c8SJakub Kicinski setattr(self, op.ident_name, bound_f) 3604e4480e8SJakub Kicinski 3614e4480e8SJakub Kicinski self.family = GenlFamily(self.yaml['name']) 3624e4480e8SJakub Kicinski 3634e4480e8SJakub Kicinski def ntf_subscribe(self, mcast_name): 3644e4480e8SJakub Kicinski if mcast_name not in self.family.genl_family['mcast']: 3654e4480e8SJakub Kicinski raise Exception(f'Multicast group "{mcast_name}" not present in the family') 3664e4480e8SJakub Kicinski 3674e4480e8SJakub Kicinski self.sock.bind((0, 0)) 3684e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 3694e4480e8SJakub Kicinski self.family.genl_family['mcast'][mcast_name]) 3704e4480e8SJakub Kicinski 3714e4480e8SJakub Kicinski def _add_attr(self, space, name, value): 37230a5c6c8SJakub Kicinski attr = self.attr_sets[space][name] 37330a5c6c8SJakub Kicinski nl_type = attr.value 3744e4480e8SJakub Kicinski if attr["type"] == 'nest': 3754e4480e8SJakub Kicinski nl_type |= Netlink.NLA_F_NESTED 3764e4480e8SJakub Kicinski attr_payload = b'' 3774e4480e8SJakub Kicinski for subname, subvalue in value.items(): 3784e4480e8SJakub Kicinski attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) 37919b64b48SJakub Kicinski elif attr["type"] == 'flag': 38019b64b48SJakub Kicinski attr_payload = b'' 3818da3a559SJiri Pirko elif attr["type"] == 'u8': 3828da3a559SJiri Pirko attr_payload = struct.pack("B", int(value)) 383dd3a7d58SMichal Michalik elif attr["type"] == 'u16': 3849f7cc57fSStanislav Fomichev endian = NlAttr.format_byte_order(attr.byte_order) 3859f7cc57fSStanislav Fomichev attr_payload = struct.pack(f"{endian}H", int(value)) 3864e4480e8SJakub Kicinski elif attr["type"] == 'u32': 3879f7cc57fSStanislav Fomichev endian = NlAttr.format_byte_order(attr.byte_order) 3889f7cc57fSStanislav Fomichev attr_payload = struct.pack(f"{endian}I", int(value)) 389dd3a7d58SMichal Michalik elif attr["type"] == 'u64': 3909f7cc57fSStanislav Fomichev endian = NlAttr.format_byte_order(attr.byte_order) 3919f7cc57fSStanislav Fomichev attr_payload = struct.pack(f"{endian}Q", int(value)) 3924e4480e8SJakub Kicinski elif attr["type"] == 'string': 3934e4480e8SJakub Kicinski attr_payload = str(value).encode('ascii') + b'\x00' 3944e4480e8SJakub Kicinski elif attr["type"] == 'binary': 3954e4480e8SJakub Kicinski attr_payload = value 3964e4480e8SJakub Kicinski else: 3974e4480e8SJakub Kicinski raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') 3984e4480e8SJakub Kicinski 3994e4480e8SJakub Kicinski pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 4004e4480e8SJakub Kicinski return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 4014e4480e8SJakub Kicinski 4024e4480e8SJakub Kicinski def _decode_enum(self, rsp, attr_spec): 4034e4480e8SJakub Kicinski raw = rsp[attr_spec['name']] 404c311aaa7SJakub Kicinski enum = self.consts[attr_spec['enum']] 4054e4480e8SJakub Kicinski i = attr_spec.get('value-start', 0) 4064e4480e8SJakub Kicinski if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']: 4074e4480e8SJakub Kicinski value = set() 4084e4480e8SJakub Kicinski while raw: 4094e4480e8SJakub Kicinski if raw & 1: 410c311aaa7SJakub Kicinski value.add(enum.entries_by_val[i].name) 4114e4480e8SJakub Kicinski raw >>= 1 4124e4480e8SJakub Kicinski i += 1 4134e4480e8SJakub Kicinski else: 414758d29fbSDonald Hunter value = enum.entries_by_val[raw - i].name 4154e4480e8SJakub Kicinski rsp[attr_spec['name']] = value 4164e4480e8SJakub Kicinski 417b423c3c8SDonald Hunter def _decode_binary(self, attr, attr_spec): 41826071913SDonald Hunter if attr_spec.struct_name: 41926071913SDonald Hunter decoded = attr.as_struct(self.consts[attr_spec.struct_name]) 42026071913SDonald Hunter elif attr_spec.sub_type: 421b423c3c8SDonald Hunter decoded = attr.as_c_array(attr_spec.sub_type) 422b423c3c8SDonald Hunter else: 423b423c3c8SDonald Hunter decoded = attr.as_bin() 424b423c3c8SDonald Hunter return decoded 425b423c3c8SDonald Hunter 4264e4480e8SJakub Kicinski def _decode(self, attrs, space): 42730a5c6c8SJakub Kicinski attr_space = self.attr_sets[space] 4284e4480e8SJakub Kicinski rsp = dict() 4294e4480e8SJakub Kicinski for attr in attrs: 43030a5c6c8SJakub Kicinski attr_spec = attr_space.attrs_by_val[attr.type] 4314e4480e8SJakub Kicinski if attr_spec["type"] == 'nest': 4324e4480e8SJakub Kicinski subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) 43390256f3fSJakub Kicinski decoded = subdict 43419b64b48SJakub Kicinski elif attr_spec['type'] == 'u8': 43590256f3fSJakub Kicinski decoded = attr.as_u8() 436dd3a7d58SMichal Michalik elif attr_spec['type'] == 'u16': 4379f7cc57fSStanislav Fomichev decoded = attr.as_u16(attr_spec.byte_order) 4384e4480e8SJakub Kicinski elif attr_spec['type'] == 'u32': 4399f7cc57fSStanislav Fomichev decoded = attr.as_u32(attr_spec.byte_order) 4404e4480e8SJakub Kicinski elif attr_spec['type'] == 'u64': 4419f7cc57fSStanislav Fomichev decoded = attr.as_u64(attr_spec.byte_order) 4424e4480e8SJakub Kicinski elif attr_spec["type"] == 'string': 44390256f3fSJakub Kicinski decoded = attr.as_strz() 4444e4480e8SJakub Kicinski elif attr_spec["type"] == 'binary': 445b423c3c8SDonald Hunter decoded = self._decode_binary(attr, attr_spec) 44619b64b48SJakub Kicinski elif attr_spec["type"] == 'flag': 44790256f3fSJakub Kicinski decoded = True 4484e4480e8SJakub Kicinski else: 4494e4480e8SJakub Kicinski raise Exception(f'Unknown {attr.type} {attr_spec["name"]} {attr_spec["type"]}') 4504e4480e8SJakub Kicinski 45190256f3fSJakub Kicinski if not attr_spec.is_multi: 45290256f3fSJakub Kicinski rsp[attr_spec['name']] = decoded 45390256f3fSJakub Kicinski elif attr_spec.name in rsp: 45490256f3fSJakub Kicinski rsp[attr_spec.name].append(decoded) 45590256f3fSJakub Kicinski else: 45690256f3fSJakub Kicinski rsp[attr_spec.name] = [decoded] 45790256f3fSJakub Kicinski 4584e4480e8SJakub Kicinski if 'enum' in attr_spec: 4594e4480e8SJakub Kicinski self._decode_enum(rsp, attr_spec) 4604e4480e8SJakub Kicinski return rsp 4614e4480e8SJakub Kicinski 4624cd2796fSJakub Kicinski def _decode_extack_path(self, attrs, attr_set, offset, target): 4634cd2796fSJakub Kicinski for attr in attrs: 4644cd2796fSJakub Kicinski attr_spec = attr_set.attrs_by_val[attr.type] 4654cd2796fSJakub Kicinski if offset > target: 4664cd2796fSJakub Kicinski break 4674cd2796fSJakub Kicinski if offset == target: 4684cd2796fSJakub Kicinski return '.' + attr_spec.name 4694cd2796fSJakub Kicinski 4704cd2796fSJakub Kicinski if offset + attr.full_len <= target: 4714cd2796fSJakub Kicinski offset += attr.full_len 4724cd2796fSJakub Kicinski continue 4734cd2796fSJakub Kicinski if attr_spec['type'] != 'nest': 4744cd2796fSJakub Kicinski raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 4754cd2796fSJakub Kicinski offset += 4 4764cd2796fSJakub Kicinski subpath = self._decode_extack_path(NlAttrs(attr.raw), 4774cd2796fSJakub Kicinski self.attr_sets[attr_spec['nested-attributes']], 4784cd2796fSJakub Kicinski offset, target) 4794cd2796fSJakub Kicinski if subpath is None: 4804cd2796fSJakub Kicinski return None 4814cd2796fSJakub Kicinski return '.' + attr_spec.name + subpath 4824cd2796fSJakub Kicinski 4834cd2796fSJakub Kicinski return None 4844cd2796fSJakub Kicinski 4854cd2796fSJakub Kicinski def _decode_extack(self, request, attr_space, extack): 4864cd2796fSJakub Kicinski if 'bad-attr-offs' not in extack: 4874cd2796fSJakub Kicinski return 4884cd2796fSJakub Kicinski 4894cd2796fSJakub Kicinski genl_req = GenlMsg(NlMsg(request, 0, attr_space=attr_space)) 4904cd2796fSJakub Kicinski path = self._decode_extack_path(genl_req.raw_attrs, attr_space, 4914cd2796fSJakub Kicinski 20, extack['bad-attr-offs']) 4924cd2796fSJakub Kicinski if path: 4934cd2796fSJakub Kicinski del extack['bad-attr-offs'] 4944cd2796fSJakub Kicinski extack['bad-attr'] = path 4954cd2796fSJakub Kicinski 4964e4480e8SJakub Kicinski def handle_ntf(self, nl_msg, genl_msg): 4974e4480e8SJakub Kicinski msg = dict() 4984e4480e8SJakub Kicinski if self.include_raw: 4994e4480e8SJakub Kicinski msg['nlmsg'] = nl_msg 5004e4480e8SJakub Kicinski msg['genlmsg'] = genl_msg 501fd0616d3SJakub Kicinski op = self.rsp_by_value[genl_msg.genl_cmd] 5024e4480e8SJakub Kicinski msg['name'] = op['name'] 50330a5c6c8SJakub Kicinski msg['msg'] = self._decode(genl_msg.raw_attrs, op.attr_set.name) 5044e4480e8SJakub Kicinski self.async_msg_queue.append(msg) 5054e4480e8SJakub Kicinski 5064e4480e8SJakub Kicinski def check_ntf(self): 5074e4480e8SJakub Kicinski while True: 5084e4480e8SJakub Kicinski try: 5094e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT) 5104e4480e8SJakub Kicinski except BlockingIOError: 5114e4480e8SJakub Kicinski return 5124e4480e8SJakub Kicinski 5134e4480e8SJakub Kicinski nms = NlMsgs(reply) 5144e4480e8SJakub Kicinski for nl_msg in nms: 5154e4480e8SJakub Kicinski if nl_msg.error: 5164e4480e8SJakub Kicinski print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 5174e4480e8SJakub Kicinski print(nl_msg) 5184e4480e8SJakub Kicinski continue 5194e4480e8SJakub Kicinski if nl_msg.done: 5204e4480e8SJakub Kicinski print("Netlink done while checking for ntf!?") 5214e4480e8SJakub Kicinski continue 5224e4480e8SJakub Kicinski 5234e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 5244e4480e8SJakub Kicinski if gm.genl_cmd not in self.async_msg_ids: 5254e4480e8SJakub Kicinski print("Unexpected msg id done while checking for ntf", gm) 5264e4480e8SJakub Kicinski continue 5274e4480e8SJakub Kicinski 5284e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 5294e4480e8SJakub Kicinski 5304e4480e8SJakub Kicinski def _op(self, method, vals, dump=False): 53130a5c6c8SJakub Kicinski op = self.ops[method] 5324e4480e8SJakub Kicinski 5334e4480e8SJakub Kicinski nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 5344e4480e8SJakub Kicinski if dump: 5354e4480e8SJakub Kicinski nl_flags |= Netlink.NLM_F_DUMP 5364e4480e8SJakub Kicinski 5374e4480e8SJakub Kicinski req_seq = random.randint(1024, 65535) 538fd0616d3SJakub Kicinski msg = _genl_msg(self.family.family_id, nl_flags, op.req_value, 1, req_seq) 539f036d936SDonald Hunter fixed_header_members = [] 540f036d936SDonald Hunter if op.fixed_header: 541f036d936SDonald Hunter fixed_header_members = self.consts[op.fixed_header].members 542f036d936SDonald Hunter for m in fixed_header_members: 543f036d936SDonald Hunter value = vals.pop(m.name) 544f036d936SDonald Hunter format, _ = NlAttr.type_formats[m.type] 545f036d936SDonald Hunter msg += struct.pack(format, value) 5464e4480e8SJakub Kicinski for name, value in vals.items(): 54730a5c6c8SJakub Kicinski msg += self._add_attr(op.attr_set.name, name, value) 5484e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 5494e4480e8SJakub Kicinski 5504e4480e8SJakub Kicinski self.sock.send(msg, 0) 5514e4480e8SJakub Kicinski 5524e4480e8SJakub Kicinski done = False 5534e4480e8SJakub Kicinski rsp = [] 5544e4480e8SJakub Kicinski while not done: 5554e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024) 55630a5c6c8SJakub Kicinski nms = NlMsgs(reply, attr_space=op.attr_set) 5574e4480e8SJakub Kicinski for nl_msg in nms: 5584cd2796fSJakub Kicinski if nl_msg.extack: 5594cd2796fSJakub Kicinski self._decode_extack(msg, op.attr_set, nl_msg.extack) 5604cd2796fSJakub Kicinski 5614e4480e8SJakub Kicinski if nl_msg.error: 562*48993e22SStanislav Fomichev raise NlError(nl_msg) 5634e4480e8SJakub Kicinski if nl_msg.done: 5644cd2796fSJakub Kicinski if nl_msg.extack: 5654cd2796fSJakub Kicinski print("Netlink warning:") 5664cd2796fSJakub Kicinski print(nl_msg) 5674e4480e8SJakub Kicinski done = True 5684e4480e8SJakub Kicinski break 5694e4480e8SJakub Kicinski 570f036d936SDonald Hunter gm = GenlMsg(nl_msg, fixed_header_members) 5714e4480e8SJakub Kicinski # Check if this is a reply to our request 572fd0616d3SJakub Kicinski if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.rsp_value: 5734e4480e8SJakub Kicinski if gm.genl_cmd in self.async_msg_ids: 5744e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 5754e4480e8SJakub Kicinski continue 5764e4480e8SJakub Kicinski else: 5774e4480e8SJakub Kicinski print('Unexpected message: ' + repr(gm)) 5784e4480e8SJakub Kicinski continue 5794e4480e8SJakub Kicinski 580f036d936SDonald Hunter rsp.append(self._decode(gm.raw_attrs, op.attr_set.name) 581f036d936SDonald Hunter | gm.fixed_header_attrs) 5824e4480e8SJakub Kicinski 5834e4480e8SJakub Kicinski if not rsp: 5844e4480e8SJakub Kicinski return None 5854e4480e8SJakub Kicinski if not dump and len(rsp) == 1: 5864e4480e8SJakub Kicinski return rsp[0] 5874e4480e8SJakub Kicinski return rsp 5888dfec0a8SJakub Kicinski 5898dfec0a8SJakub Kicinski def do(self, method, vals): 5908dfec0a8SJakub Kicinski return self._op(method, vals) 5918dfec0a8SJakub Kicinski 5928dfec0a8SJakub Kicinski def dump(self, method, vals): 5938dfec0a8SJakub Kicinski return self._op(method, vals, dump=True) 594