137d9df22SJakub Kicinski# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 24e4480e8SJakub Kicinski 37c2435efSDonald Hunterfrom collections import namedtuple 44e4480e8SJakub Kicinskiimport functools 54e4480e8SJakub Kicinskiimport os 64e4480e8SJakub Kicinskiimport random 74e4480e8SJakub Kicinskiimport socket 84e4480e8SJakub Kicinskiimport struct 97c2435efSDonald Hunterfrom struct import Struct 104e4480e8SJakub Kicinskiimport yaml 11d8eea68dSDonald Hunterimport ipaddress 12d8eea68dSDonald Hunterimport uuid 134e4480e8SJakub Kicinski 1430a5c6c8SJakub Kicinskifrom .nlspec import SpecFamily 1530a5c6c8SJakub Kicinski 164e4480e8SJakub Kicinski# 174e4480e8SJakub Kicinski# Generic Netlink code which should really be in some library, but I can't quickly find one. 184e4480e8SJakub Kicinski# 194e4480e8SJakub Kicinski 204e4480e8SJakub Kicinski 214e4480e8SJakub Kicinskiclass Netlink: 224e4480e8SJakub Kicinski # Netlink socket 234e4480e8SJakub Kicinski SOL_NETLINK = 270 244e4480e8SJakub Kicinski 254e4480e8SJakub Kicinski NETLINK_ADD_MEMBERSHIP = 1 264e4480e8SJakub Kicinski NETLINK_CAP_ACK = 10 274e4480e8SJakub Kicinski NETLINK_EXT_ACK = 11 284e4480e8SJakub Kicinski 294e4480e8SJakub Kicinski # Netlink message 304e4480e8SJakub Kicinski NLMSG_ERROR = 2 314e4480e8SJakub Kicinski NLMSG_DONE = 3 324e4480e8SJakub Kicinski 334e4480e8SJakub Kicinski NLM_F_REQUEST = 1 344e4480e8SJakub Kicinski NLM_F_ACK = 4 354e4480e8SJakub Kicinski NLM_F_ROOT = 0x100 364e4480e8SJakub Kicinski NLM_F_MATCH = 0x200 374e4480e8SJakub Kicinski NLM_F_APPEND = 0x800 384e4480e8SJakub Kicinski 394e4480e8SJakub Kicinski NLM_F_CAPPED = 0x100 404e4480e8SJakub Kicinski NLM_F_ACK_TLVS = 0x200 414e4480e8SJakub Kicinski 424e4480e8SJakub Kicinski NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH 434e4480e8SJakub Kicinski 444e4480e8SJakub Kicinski NLA_F_NESTED = 0x8000 454e4480e8SJakub Kicinski NLA_F_NET_BYTEORDER = 0x4000 464e4480e8SJakub Kicinski 474e4480e8SJakub Kicinski NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER 484e4480e8SJakub Kicinski 494e4480e8SJakub Kicinski # Genetlink defines 504e4480e8SJakub Kicinski NETLINK_GENERIC = 16 514e4480e8SJakub Kicinski 524e4480e8SJakub Kicinski GENL_ID_CTRL = 0x10 534e4480e8SJakub Kicinski 544e4480e8SJakub Kicinski # nlctrl 554e4480e8SJakub Kicinski CTRL_CMD_GETFAMILY = 3 564e4480e8SJakub Kicinski 574e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_ID = 1 584e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_NAME = 2 594e4480e8SJakub Kicinski CTRL_ATTR_MAXATTR = 5 604e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GROUPS = 7 614e4480e8SJakub Kicinski 624e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_NAME = 1 634e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_ID = 2 644e4480e8SJakub Kicinski 654e4480e8SJakub Kicinski # Extack types 664e4480e8SJakub Kicinski NLMSGERR_ATTR_MSG = 1 674e4480e8SJakub Kicinski NLMSGERR_ATTR_OFFS = 2 684e4480e8SJakub Kicinski NLMSGERR_ATTR_COOKIE = 3 694e4480e8SJakub Kicinski NLMSGERR_ATTR_POLICY = 4 704e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_TYPE = 5 714e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_NEST = 6 724e4480e8SJakub Kicinski 734e4480e8SJakub Kicinski 7448993e22SStanislav Fomichevclass NlError(Exception): 7548993e22SStanislav Fomichev def __init__(self, nl_msg): 7648993e22SStanislav Fomichev self.nl_msg = nl_msg 7748993e22SStanislav Fomichev 7848993e22SStanislav Fomichev def __str__(self): 7948993e22SStanislav Fomichev return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}" 8048993e22SStanislav Fomichev 8148993e22SStanislav Fomichev 824e4480e8SJakub Kicinskiclass NlAttr: 837c2435efSDonald Hunter ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) 847c2435efSDonald Hunter type_formats = { 857c2435efSDonald Hunter 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), 867c2435efSDonald Hunter 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), 877c2435efSDonald Hunter 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")), 887c2435efSDonald Hunter 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")), 897c2435efSDonald Hunter 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")), 907c2435efSDonald Hunter 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")), 917c2435efSDonald Hunter 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")), 927c2435efSDonald Hunter 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q")) 937c2435efSDonald Hunter } 94b423c3c8SDonald Hunter 954e4480e8SJakub Kicinski def __init__(self, raw, offset): 964e4480e8SJakub Kicinski self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) 974e4480e8SJakub Kicinski self.type = self._type & ~Netlink.NLA_TYPE_MASK 984e4480e8SJakub Kicinski self.payload_len = self._len 994e4480e8SJakub Kicinski self.full_len = (self.payload_len + 3) & ~3 1004e4480e8SJakub Kicinski self.raw = raw[offset + 4:offset + self.payload_len] 1014e4480e8SJakub Kicinski 1027c2435efSDonald Hunter @classmethod 1037c2435efSDonald Hunter def get_format(cls, attr_type, byte_order=None): 1047c2435efSDonald Hunter format = cls.type_formats[attr_type] 1059f7cc57fSStanislav Fomichev if byte_order: 1067c2435efSDonald Hunter return format.big if byte_order == "big-endian" \ 1077c2435efSDonald Hunter else format.little 1087c2435efSDonald Hunter return format.native 1099f7cc57fSStanislav Fomichev 110d8eea68dSDonald Hunter @classmethod 111d8eea68dSDonald Hunter def formatted_string(cls, raw, display_hint): 112d8eea68dSDonald Hunter if display_hint == 'mac': 113d8eea68dSDonald Hunter formatted = ':'.join('%02x' % b for b in raw) 114d8eea68dSDonald Hunter elif display_hint == 'hex': 115d8eea68dSDonald Hunter formatted = bytes.hex(raw, ' ') 116d8eea68dSDonald Hunter elif display_hint in [ 'ipv4', 'ipv6' ]: 117d8eea68dSDonald Hunter formatted = format(ipaddress.ip_address(raw)) 118d8eea68dSDonald Hunter elif display_hint == 'uuid': 119d8eea68dSDonald Hunter formatted = str(uuid.UUID(bytes=raw)) 120d8eea68dSDonald Hunter else: 121d8eea68dSDonald Hunter formatted = raw 122d8eea68dSDonald Hunter return formatted 123d8eea68dSDonald Hunter 1247c2435efSDonald Hunter def as_scalar(self, attr_type, byte_order=None): 1257c2435efSDonald Hunter format = self.get_format(attr_type, byte_order) 1267c2435efSDonald Hunter return format.unpack(self.raw)[0] 1274e4480e8SJakub Kicinski 1284e4480e8SJakub Kicinski def as_strz(self): 1294e4480e8SJakub Kicinski return self.raw.decode('ascii')[:-1] 1304e4480e8SJakub Kicinski 1314e4480e8SJakub Kicinski def as_bin(self): 1324e4480e8SJakub Kicinski return self.raw 1334e4480e8SJakub Kicinski 134b423c3c8SDonald Hunter def as_c_array(self, type): 1357c2435efSDonald Hunter format = self.get_format(type) 1367c2435efSDonald Hunter return [ x[0] for x in format.iter_unpack(self.raw) ] 137b423c3c8SDonald Hunter 13826071913SDonald Hunter def as_struct(self, members): 13926071913SDonald Hunter value = dict() 14026071913SDonald Hunter offset = 0 14126071913SDonald Hunter for m in members: 14226071913SDonald Hunter # TODO: handle non-scalar members 143d8eea68dSDonald Hunter if m.type == 'binary': 144d8eea68dSDonald Hunter decoded = self.raw[offset:offset+m['len']] 145d8eea68dSDonald Hunter offset += m['len'] 146d8eea68dSDonald Hunter elif m.type in NlAttr.type_formats: 147bddd2e56SDonald Hunter format = self.get_format(m.type, m.byte_order) 148d8eea68dSDonald Hunter [ decoded ] = format.unpack_from(self.raw, offset) 1497c2435efSDonald Hunter offset += format.size 150d8eea68dSDonald Hunter if m.display_hint: 151d8eea68dSDonald Hunter decoded = self.formatted_string(decoded, m.display_hint) 152d8eea68dSDonald Hunter value[m.name] = decoded 15326071913SDonald Hunter return value 15426071913SDonald Hunter 1554e4480e8SJakub Kicinski def __repr__(self): 1564e4480e8SJakub Kicinski return f"[type:{self.type} len:{self._len}] {self.raw}" 1574e4480e8SJakub Kicinski 1584e4480e8SJakub Kicinski 1594e4480e8SJakub Kicinskiclass NlAttrs: 1604e4480e8SJakub Kicinski def __init__(self, msg): 1614e4480e8SJakub Kicinski self.attrs = [] 1624e4480e8SJakub Kicinski 1634e4480e8SJakub Kicinski offset = 0 1644e4480e8SJakub Kicinski while offset < len(msg): 1654e4480e8SJakub Kicinski attr = NlAttr(msg, offset) 1664e4480e8SJakub Kicinski offset += attr.full_len 1674e4480e8SJakub Kicinski self.attrs.append(attr) 1684e4480e8SJakub Kicinski 1694e4480e8SJakub Kicinski def __iter__(self): 1704e4480e8SJakub Kicinski yield from self.attrs 1714e4480e8SJakub Kicinski 1724e4480e8SJakub Kicinski def __repr__(self): 1734e4480e8SJakub Kicinski msg = '' 1744e4480e8SJakub Kicinski for a in self.attrs: 1754e4480e8SJakub Kicinski if msg: 1764e4480e8SJakub Kicinski msg += '\n' 1774e4480e8SJakub Kicinski msg += repr(a) 1784e4480e8SJakub Kicinski return msg 1794e4480e8SJakub Kicinski 1804e4480e8SJakub Kicinski 1814e4480e8SJakub Kicinskiclass NlMsg: 1824e4480e8SJakub Kicinski def __init__(self, msg, offset, attr_space=None): 1834e4480e8SJakub Kicinski self.hdr = msg[offset:offset + 16] 1844e4480e8SJakub Kicinski 1854e4480e8SJakub Kicinski self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ 1864e4480e8SJakub Kicinski struct.unpack("IHHII", self.hdr) 1874e4480e8SJakub Kicinski 1884e4480e8SJakub Kicinski self.raw = msg[offset + 16:offset + self.nl_len] 1894e4480e8SJakub Kicinski 1904e4480e8SJakub Kicinski self.error = 0 1914e4480e8SJakub Kicinski self.done = 0 1924e4480e8SJakub Kicinski 1934e4480e8SJakub Kicinski extack_off = None 1944e4480e8SJakub Kicinski if self.nl_type == Netlink.NLMSG_ERROR: 1954e4480e8SJakub Kicinski self.error = struct.unpack("i", self.raw[0:4])[0] 1964e4480e8SJakub Kicinski self.done = 1 1974e4480e8SJakub Kicinski extack_off = 20 1984e4480e8SJakub Kicinski elif self.nl_type == Netlink.NLMSG_DONE: 1994e4480e8SJakub Kicinski self.done = 1 2004e4480e8SJakub Kicinski extack_off = 4 2014e4480e8SJakub Kicinski 2024e4480e8SJakub Kicinski self.extack = None 2034e4480e8SJakub Kicinski if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: 2044e4480e8SJakub Kicinski self.extack = dict() 2054e4480e8SJakub Kicinski extack_attrs = NlAttrs(self.raw[extack_off:]) 2064e4480e8SJakub Kicinski for extack in extack_attrs: 2074e4480e8SJakub Kicinski if extack.type == Netlink.NLMSGERR_ATTR_MSG: 2084e4480e8SJakub Kicinski self.extack['msg'] = extack.as_strz() 2094e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: 2107c2435efSDonald Hunter self.extack['miss-type'] = extack.as_scalar('u32') 2114e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: 2127c2435efSDonald Hunter self.extack['miss-nest'] = extack.as_scalar('u32') 2134e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 2147c2435efSDonald Hunter self.extack['bad-attr-offs'] = extack.as_scalar('u32') 2154e4480e8SJakub Kicinski else: 2164e4480e8SJakub Kicinski if 'unknown' not in self.extack: 2174e4480e8SJakub Kicinski self.extack['unknown'] = [] 2184e4480e8SJakub Kicinski self.extack['unknown'].append(extack) 2194e4480e8SJakub Kicinski 2204e4480e8SJakub Kicinski if attr_space: 2214e4480e8SJakub Kicinski # We don't have the ability to parse nests yet, so only do global 2224e4480e8SJakub Kicinski if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 2234e4480e8SJakub Kicinski miss_type = self.extack['miss-type'] 22430a5c6c8SJakub Kicinski if miss_type in attr_space.attrs_by_val: 22530a5c6c8SJakub Kicinski spec = attr_space.attrs_by_val[miss_type] 2264e4480e8SJakub Kicinski desc = spec['name'] 2274e4480e8SJakub Kicinski if 'doc' in spec: 2284e4480e8SJakub Kicinski desc += f" ({spec['doc']})" 2294e4480e8SJakub Kicinski self.extack['miss-type'] = desc 2304e4480e8SJakub Kicinski 2314e4480e8SJakub Kicinski def __repr__(self): 2324e4480e8SJakub Kicinski msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n" 2334e4480e8SJakub Kicinski if self.error: 2344e4480e8SJakub Kicinski msg += '\terror: ' + str(self.error) 2354e4480e8SJakub Kicinski if self.extack: 2364e4480e8SJakub Kicinski msg += '\textack: ' + repr(self.extack) 2374e4480e8SJakub Kicinski return msg 2384e4480e8SJakub Kicinski 2394e4480e8SJakub Kicinski 2404e4480e8SJakub Kicinskiclass NlMsgs: 2414e4480e8SJakub Kicinski def __init__(self, data, attr_space=None): 2424e4480e8SJakub Kicinski self.msgs = [] 2434e4480e8SJakub Kicinski 2444e4480e8SJakub Kicinski offset = 0 2454e4480e8SJakub Kicinski while offset < len(data): 2464e4480e8SJakub Kicinski msg = NlMsg(data, offset, attr_space=attr_space) 2474e4480e8SJakub Kicinski offset += msg.nl_len 2484e4480e8SJakub Kicinski self.msgs.append(msg) 2494e4480e8SJakub Kicinski 2504e4480e8SJakub Kicinski def __iter__(self): 2514e4480e8SJakub Kicinski yield from self.msgs 2524e4480e8SJakub Kicinski 2534e4480e8SJakub Kicinski 2544e4480e8SJakub Kicinskigenl_family_name_to_id = None 2554e4480e8SJakub Kicinski 2564e4480e8SJakub Kicinski 2574e4480e8SJakub Kicinskidef _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): 2584e4480e8SJakub Kicinski # we prepend length in _genl_msg_finalize() 2594e4480e8SJakub Kicinski if seq is None: 2604e4480e8SJakub Kicinski seq = random.randint(1, 1024) 2614e4480e8SJakub Kicinski nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 262758d29fbSDonald Hunter genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0) 2634e4480e8SJakub Kicinski return nlmsg + genlmsg 2644e4480e8SJakub Kicinski 2654e4480e8SJakub Kicinski 2664e4480e8SJakub Kicinskidef _genl_msg_finalize(msg): 2674e4480e8SJakub Kicinski return struct.pack("I", len(msg) + 4) + msg 2684e4480e8SJakub Kicinski 2694e4480e8SJakub Kicinski 2704e4480e8SJakub Kicinskidef _genl_load_families(): 2714e4480e8SJakub Kicinski with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 2724e4480e8SJakub Kicinski sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 2734e4480e8SJakub Kicinski 2744e4480e8SJakub Kicinski msg = _genl_msg(Netlink.GENL_ID_CTRL, 2754e4480e8SJakub Kicinski Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 2764e4480e8SJakub Kicinski Netlink.CTRL_CMD_GETFAMILY, 1) 2774e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 2784e4480e8SJakub Kicinski 2794e4480e8SJakub Kicinski sock.send(msg, 0) 2804e4480e8SJakub Kicinski 2814e4480e8SJakub Kicinski global genl_family_name_to_id 2824e4480e8SJakub Kicinski genl_family_name_to_id = dict() 2834e4480e8SJakub Kicinski 2844e4480e8SJakub Kicinski while True: 2854e4480e8SJakub Kicinski reply = sock.recv(128 * 1024) 2864e4480e8SJakub Kicinski nms = NlMsgs(reply) 2874e4480e8SJakub Kicinski for nl_msg in nms: 2884e4480e8SJakub Kicinski if nl_msg.error: 2894e4480e8SJakub Kicinski print("Netlink error:", nl_msg.error) 2904e4480e8SJakub Kicinski return 2914e4480e8SJakub Kicinski if nl_msg.done: 2924e4480e8SJakub Kicinski return 2934e4480e8SJakub Kicinski 2944e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 2954e4480e8SJakub Kicinski fam = dict() 2964e4480e8SJakub Kicinski for attr in gm.raw_attrs: 2974e4480e8SJakub Kicinski if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: 2987c2435efSDonald Hunter fam['id'] = attr.as_scalar('u16') 2994e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: 3004e4480e8SJakub Kicinski fam['name'] = attr.as_strz() 3014e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MAXATTR: 3027c2435efSDonald Hunter fam['maxattr'] = attr.as_scalar('u32') 3034e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: 3044e4480e8SJakub Kicinski fam['mcast'] = dict() 3054e4480e8SJakub Kicinski for entry in NlAttrs(attr.raw): 3064e4480e8SJakub Kicinski mcast_name = None 3074e4480e8SJakub Kicinski mcast_id = None 3084e4480e8SJakub Kicinski for entry_attr in NlAttrs(entry.raw): 3094e4480e8SJakub Kicinski if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: 3104e4480e8SJakub Kicinski mcast_name = entry_attr.as_strz() 3114e4480e8SJakub Kicinski elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: 3127c2435efSDonald Hunter mcast_id = entry_attr.as_scalar('u32') 3134e4480e8SJakub Kicinski if mcast_name and mcast_id is not None: 3144e4480e8SJakub Kicinski fam['mcast'][mcast_name] = mcast_id 3154e4480e8SJakub Kicinski if 'name' in fam and 'id' in fam: 3164e4480e8SJakub Kicinski genl_family_name_to_id[fam['name']] = fam 3174e4480e8SJakub Kicinski 3184e4480e8SJakub Kicinski 3194e4480e8SJakub Kicinskiclass GenlMsg: 320f036d936SDonald Hunter def __init__(self, nl_msg, fixed_header_members=[]): 3214e4480e8SJakub Kicinski self.nl = nl_msg 3224e4480e8SJakub Kicinski 3234e4480e8SJakub Kicinski self.hdr = nl_msg.raw[0:4] 324f036d936SDonald Hunter offset = 4 3254e4480e8SJakub Kicinski 326758d29fbSDonald Hunter self.genl_cmd, self.genl_version, _ = struct.unpack("BBH", self.hdr) 3274e4480e8SJakub Kicinski 328f036d936SDonald Hunter self.fixed_header_attrs = dict() 329f036d936SDonald Hunter for m in fixed_header_members: 330bddd2e56SDonald Hunter format = NlAttr.get_format(m.type, m.byte_order) 3317c2435efSDonald Hunter decoded = format.unpack_from(nl_msg.raw, offset) 3327c2435efSDonald Hunter offset += format.size 333f036d936SDonald Hunter self.fixed_header_attrs[m.name] = decoded[0] 334f036d936SDonald Hunter 335f036d936SDonald Hunter self.raw = nl_msg.raw[offset:] 3364e4480e8SJakub Kicinski self.raw_attrs = NlAttrs(self.raw) 3374e4480e8SJakub Kicinski 3384e4480e8SJakub Kicinski def __repr__(self): 3394e4480e8SJakub Kicinski msg = repr(self.nl) 3404e4480e8SJakub Kicinski msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" 3414e4480e8SJakub Kicinski for a in self.raw_attrs: 3424e4480e8SJakub Kicinski msg += '\t\t' + repr(a) + '\n' 3434e4480e8SJakub Kicinski return msg 3444e4480e8SJakub Kicinski 3454e4480e8SJakub Kicinski 3464e4480e8SJakub Kicinskiclass GenlFamily: 3474e4480e8SJakub Kicinski def __init__(self, family_name): 3484e4480e8SJakub Kicinski self.family_name = family_name 3494e4480e8SJakub Kicinski 3504e4480e8SJakub Kicinski global genl_family_name_to_id 3514e4480e8SJakub Kicinski if genl_family_name_to_id is None: 3524e4480e8SJakub Kicinski _genl_load_families() 3534e4480e8SJakub Kicinski 3544e4480e8SJakub Kicinski self.genl_family = genl_family_name_to_id[family_name] 3554e4480e8SJakub Kicinski self.family_id = genl_family_name_to_id[family_name]['id'] 3564e4480e8SJakub Kicinski 3574e4480e8SJakub Kicinski 3584e4480e8SJakub Kicinski# 3594e4480e8SJakub Kicinski# YNL implementation details. 3604e4480e8SJakub Kicinski# 3614e4480e8SJakub Kicinski 3624e4480e8SJakub Kicinski 36330a5c6c8SJakub Kicinskiclass YnlFamily(SpecFamily): 3644e4480e8SJakub Kicinski def __init__(self, def_path, schema=None): 36530a5c6c8SJakub Kicinski super().__init__(def_path, schema) 36630a5c6c8SJakub Kicinski 3674e4480e8SJakub Kicinski self.include_raw = False 3684e4480e8SJakub Kicinski 3694e4480e8SJakub Kicinski self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) 3704e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 3714e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 3724e4480e8SJakub Kicinski 3734e4480e8SJakub Kicinski self.async_msg_ids = set() 3744e4480e8SJakub Kicinski self.async_msg_queue = [] 3754e4480e8SJakub Kicinski 37630a5c6c8SJakub Kicinski for msg in self.msgs.values(): 37730a5c6c8SJakub Kicinski if msg.is_async: 378fd0616d3SJakub Kicinski self.async_msg_ids.add(msg.rsp_value) 3794e4480e8SJakub Kicinski 38030a5c6c8SJakub Kicinski for op_name, op in self.ops.items(): 38130a5c6c8SJakub Kicinski bound_f = functools.partial(self._op, op_name) 38230a5c6c8SJakub Kicinski setattr(self, op.ident_name, bound_f) 3834e4480e8SJakub Kicinski 384ebe3bdc4SJakub Kicinski try: 3854e4480e8SJakub Kicinski self.family = GenlFamily(self.yaml['name']) 386ebe3bdc4SJakub Kicinski except KeyError: 387ebe3bdc4SJakub Kicinski raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel") 3884e4480e8SJakub Kicinski 3894e4480e8SJakub Kicinski def ntf_subscribe(self, mcast_name): 3904e4480e8SJakub Kicinski if mcast_name not in self.family.genl_family['mcast']: 3914e4480e8SJakub Kicinski raise Exception(f'Multicast group "{mcast_name}" not present in the family') 3924e4480e8SJakub Kicinski 3934e4480e8SJakub Kicinski self.sock.bind((0, 0)) 3944e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 3954e4480e8SJakub Kicinski self.family.genl_family['mcast'][mcast_name]) 3964e4480e8SJakub Kicinski 3974e4480e8SJakub Kicinski def _add_attr(self, space, name, value): 39830a5c6c8SJakub Kicinski attr = self.attr_sets[space][name] 39930a5c6c8SJakub Kicinski nl_type = attr.value 4004e4480e8SJakub Kicinski if attr["type"] == 'nest': 4014e4480e8SJakub Kicinski nl_type |= Netlink.NLA_F_NESTED 4024e4480e8SJakub Kicinski attr_payload = b'' 4034e4480e8SJakub Kicinski for subname, subvalue in value.items(): 4044e4480e8SJakub Kicinski attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) 40519b64b48SJakub Kicinski elif attr["type"] == 'flag': 40619b64b48SJakub Kicinski attr_payload = b'' 4074e4480e8SJakub Kicinski elif attr["type"] == 'string': 4084e4480e8SJakub Kicinski attr_payload = str(value).encode('ascii') + b'\x00' 4094e4480e8SJakub Kicinski elif attr["type"] == 'binary': 410d8eea68dSDonald Hunter attr_payload = bytes.fromhex(value) 4117c2435efSDonald Hunter elif attr['type'] in NlAttr.type_formats: 4127c2435efSDonald Hunter format = NlAttr.get_format(attr['type'], attr.byte_order) 4137c2435efSDonald Hunter attr_payload = format.pack(int(value)) 4144e4480e8SJakub Kicinski else: 4154e4480e8SJakub Kicinski raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') 4164e4480e8SJakub Kicinski 4174e4480e8SJakub Kicinski pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 4184e4480e8SJakub Kicinski return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 4194e4480e8SJakub Kicinski 420*df15c15eSArkadiusz Kubalewski def _decode_enum(self, raw, attr_spec): 421c311aaa7SJakub Kicinski enum = self.consts[attr_spec['enum']] 4224e4480e8SJakub Kicinski if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']: 423d7ddf5f4SArkadiusz Kubalewski i = 0 4244e4480e8SJakub Kicinski value = set() 4254e4480e8SJakub Kicinski while raw: 4264e4480e8SJakub Kicinski if raw & 1: 427c311aaa7SJakub Kicinski value.add(enum.entries_by_val[i].name) 4284e4480e8SJakub Kicinski raw >>= 1 4294e4480e8SJakub Kicinski i += 1 4304e4480e8SJakub Kicinski else: 431d7ddf5f4SArkadiusz Kubalewski value = enum.entries_by_val[raw].name 432*df15c15eSArkadiusz Kubalewski return value 4334e4480e8SJakub Kicinski 434b423c3c8SDonald Hunter def _decode_binary(self, attr, attr_spec): 43526071913SDonald Hunter if attr_spec.struct_name: 436313a7a80SDonald Hunter members = self.consts[attr_spec.struct_name] 437313a7a80SDonald Hunter decoded = attr.as_struct(members) 438313a7a80SDonald Hunter for m in members: 439313a7a80SDonald Hunter if m.enum: 440*df15c15eSArkadiusz Kubalewski decoded[m.name] = self._decode_enum(decoded[m.name], m) 44126071913SDonald Hunter elif attr_spec.sub_type: 442b423c3c8SDonald Hunter decoded = attr.as_c_array(attr_spec.sub_type) 443b423c3c8SDonald Hunter else: 444b423c3c8SDonald Hunter decoded = attr.as_bin() 445d8eea68dSDonald Hunter if attr_spec.display_hint: 446d8eea68dSDonald Hunter decoded = NlAttr.formatted_string(decoded, attr_spec.display_hint) 447b423c3c8SDonald Hunter return decoded 448b423c3c8SDonald Hunter 4494e4480e8SJakub Kicinski def _decode(self, attrs, space): 45030a5c6c8SJakub Kicinski attr_space = self.attr_sets[space] 4514e4480e8SJakub Kicinski rsp = dict() 4524e4480e8SJakub Kicinski for attr in attrs: 45330a5c6c8SJakub Kicinski attr_spec = attr_space.attrs_by_val[attr.type] 4544e4480e8SJakub Kicinski if attr_spec["type"] == 'nest': 4554e4480e8SJakub Kicinski subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) 45690256f3fSJakub Kicinski decoded = subdict 4574e4480e8SJakub Kicinski elif attr_spec["type"] == 'string': 45890256f3fSJakub Kicinski decoded = attr.as_strz() 4594e4480e8SJakub Kicinski elif attr_spec["type"] == 'binary': 460b423c3c8SDonald Hunter decoded = self._decode_binary(attr, attr_spec) 46119b64b48SJakub Kicinski elif attr_spec["type"] == 'flag': 46290256f3fSJakub Kicinski decoded = True 4637c2435efSDonald Hunter elif attr_spec["type"] in NlAttr.type_formats: 4647c2435efSDonald Hunter decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) 4654e4480e8SJakub Kicinski else: 4667c2435efSDonald Hunter raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') 4674e4480e8SJakub Kicinski 468*df15c15eSArkadiusz Kubalewski if 'enum' in attr_spec: 469*df15c15eSArkadiusz Kubalewski decoded = self._decode_enum(decoded, attr_spec) 470*df15c15eSArkadiusz Kubalewski 47190256f3fSJakub Kicinski if not attr_spec.is_multi: 47290256f3fSJakub Kicinski rsp[attr_spec['name']] = decoded 47390256f3fSJakub Kicinski elif attr_spec.name in rsp: 47490256f3fSJakub Kicinski rsp[attr_spec.name].append(decoded) 47590256f3fSJakub Kicinski else: 47690256f3fSJakub Kicinski rsp[attr_spec.name] = [decoded] 47790256f3fSJakub Kicinski 4784e4480e8SJakub Kicinski return rsp 4794e4480e8SJakub Kicinski 4804cd2796fSJakub Kicinski def _decode_extack_path(self, attrs, attr_set, offset, target): 4814cd2796fSJakub Kicinski for attr in attrs: 4824cd2796fSJakub Kicinski attr_spec = attr_set.attrs_by_val[attr.type] 4834cd2796fSJakub Kicinski if offset > target: 4844cd2796fSJakub Kicinski break 4854cd2796fSJakub Kicinski if offset == target: 4864cd2796fSJakub Kicinski return '.' + attr_spec.name 4874cd2796fSJakub Kicinski 4884cd2796fSJakub Kicinski if offset + attr.full_len <= target: 4894cd2796fSJakub Kicinski offset += attr.full_len 4904cd2796fSJakub Kicinski continue 4914cd2796fSJakub Kicinski if attr_spec['type'] != 'nest': 4924cd2796fSJakub Kicinski raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 4934cd2796fSJakub Kicinski offset += 4 4944cd2796fSJakub Kicinski subpath = self._decode_extack_path(NlAttrs(attr.raw), 4954cd2796fSJakub Kicinski self.attr_sets[attr_spec['nested-attributes']], 4964cd2796fSJakub Kicinski offset, target) 4974cd2796fSJakub Kicinski if subpath is None: 4984cd2796fSJakub Kicinski return None 4994cd2796fSJakub Kicinski return '.' + attr_spec.name + subpath 5004cd2796fSJakub Kicinski 5014cd2796fSJakub Kicinski return None 5024cd2796fSJakub Kicinski 5034cd2796fSJakub Kicinski def _decode_extack(self, request, attr_space, extack): 5044cd2796fSJakub Kicinski if 'bad-attr-offs' not in extack: 5054cd2796fSJakub Kicinski return 5064cd2796fSJakub Kicinski 5074cd2796fSJakub Kicinski genl_req = GenlMsg(NlMsg(request, 0, attr_space=attr_space)) 5084cd2796fSJakub Kicinski path = self._decode_extack_path(genl_req.raw_attrs, attr_space, 5094cd2796fSJakub Kicinski 20, extack['bad-attr-offs']) 5104cd2796fSJakub Kicinski if path: 5114cd2796fSJakub Kicinski del extack['bad-attr-offs'] 5124cd2796fSJakub Kicinski extack['bad-attr'] = path 5134cd2796fSJakub Kicinski 5144e4480e8SJakub Kicinski def handle_ntf(self, nl_msg, genl_msg): 5154e4480e8SJakub Kicinski msg = dict() 5164e4480e8SJakub Kicinski if self.include_raw: 5174e4480e8SJakub Kicinski msg['nlmsg'] = nl_msg 5184e4480e8SJakub Kicinski msg['genlmsg'] = genl_msg 519fd0616d3SJakub Kicinski op = self.rsp_by_value[genl_msg.genl_cmd] 5204e4480e8SJakub Kicinski msg['name'] = op['name'] 52130a5c6c8SJakub Kicinski msg['msg'] = self._decode(genl_msg.raw_attrs, op.attr_set.name) 5224e4480e8SJakub Kicinski self.async_msg_queue.append(msg) 5234e4480e8SJakub Kicinski 5244e4480e8SJakub Kicinski def check_ntf(self): 5254e4480e8SJakub Kicinski while True: 5264e4480e8SJakub Kicinski try: 5274e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT) 5284e4480e8SJakub Kicinski except BlockingIOError: 5294e4480e8SJakub Kicinski return 5304e4480e8SJakub Kicinski 5314e4480e8SJakub Kicinski nms = NlMsgs(reply) 5324e4480e8SJakub Kicinski for nl_msg in nms: 5334e4480e8SJakub Kicinski if nl_msg.error: 5344e4480e8SJakub Kicinski print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 5354e4480e8SJakub Kicinski print(nl_msg) 5364e4480e8SJakub Kicinski continue 5374e4480e8SJakub Kicinski if nl_msg.done: 5384e4480e8SJakub Kicinski print("Netlink done while checking for ntf!?") 5394e4480e8SJakub Kicinski continue 5404e4480e8SJakub Kicinski 5414e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 5424e4480e8SJakub Kicinski if gm.genl_cmd not in self.async_msg_ids: 5434e4480e8SJakub Kicinski print("Unexpected msg id done while checking for ntf", gm) 5444e4480e8SJakub Kicinski continue 5454e4480e8SJakub Kicinski 5464e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 5474e4480e8SJakub Kicinski 548f3d07b02SStanislav Fomichev def operation_do_attributes(self, name): 549f3d07b02SStanislav Fomichev """ 550f3d07b02SStanislav Fomichev For a given operation name, find and return a supported 551f3d07b02SStanislav Fomichev set of attributes (as a dict). 552f3d07b02SStanislav Fomichev """ 553f3d07b02SStanislav Fomichev op = self.find_operation(name) 554f3d07b02SStanislav Fomichev if not op: 555f3d07b02SStanislav Fomichev return None 556f3d07b02SStanislav Fomichev 557f3d07b02SStanislav Fomichev return op['do']['request']['attributes'].copy() 558f3d07b02SStanislav Fomichev 5594e4480e8SJakub Kicinski def _op(self, method, vals, dump=False): 56030a5c6c8SJakub Kicinski op = self.ops[method] 5614e4480e8SJakub Kicinski 5624e4480e8SJakub Kicinski nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 5634e4480e8SJakub Kicinski if dump: 5644e4480e8SJakub Kicinski nl_flags |= Netlink.NLM_F_DUMP 5654e4480e8SJakub Kicinski 5664e4480e8SJakub Kicinski req_seq = random.randint(1024, 65535) 567fd0616d3SJakub Kicinski msg = _genl_msg(self.family.family_id, nl_flags, op.req_value, 1, req_seq) 568f036d936SDonald Hunter fixed_header_members = [] 569f036d936SDonald Hunter if op.fixed_header: 570f036d936SDonald Hunter fixed_header_members = self.consts[op.fixed_header].members 571f036d936SDonald Hunter for m in fixed_header_members: 5725ac18889SDonald Hunter value = vals.pop(m.name) if m.name in vals else 0 573bddd2e56SDonald Hunter format = NlAttr.get_format(m.type, m.byte_order) 5747c2435efSDonald Hunter msg += format.pack(value) 5754e4480e8SJakub Kicinski for name, value in vals.items(): 57630a5c6c8SJakub Kicinski msg += self._add_attr(op.attr_set.name, name, value) 5774e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 5784e4480e8SJakub Kicinski 5794e4480e8SJakub Kicinski self.sock.send(msg, 0) 5804e4480e8SJakub Kicinski 5814e4480e8SJakub Kicinski done = False 5824e4480e8SJakub Kicinski rsp = [] 5834e4480e8SJakub Kicinski while not done: 5844e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024) 58530a5c6c8SJakub Kicinski nms = NlMsgs(reply, attr_space=op.attr_set) 5864e4480e8SJakub Kicinski for nl_msg in nms: 5874cd2796fSJakub Kicinski if nl_msg.extack: 5884cd2796fSJakub Kicinski self._decode_extack(msg, op.attr_set, nl_msg.extack) 5894cd2796fSJakub Kicinski 5904e4480e8SJakub Kicinski if nl_msg.error: 59148993e22SStanislav Fomichev raise NlError(nl_msg) 5924e4480e8SJakub Kicinski if nl_msg.done: 5934cd2796fSJakub Kicinski if nl_msg.extack: 5944cd2796fSJakub Kicinski print("Netlink warning:") 5954cd2796fSJakub Kicinski print(nl_msg) 5964e4480e8SJakub Kicinski done = True 5974e4480e8SJakub Kicinski break 5984e4480e8SJakub Kicinski 599f036d936SDonald Hunter gm = GenlMsg(nl_msg, fixed_header_members) 6004e4480e8SJakub Kicinski # Check if this is a reply to our request 601fd0616d3SJakub Kicinski if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.rsp_value: 6024e4480e8SJakub Kicinski if gm.genl_cmd in self.async_msg_ids: 6034e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 6044e4480e8SJakub Kicinski continue 6054e4480e8SJakub Kicinski else: 6064e4480e8SJakub Kicinski print('Unexpected message: ' + repr(gm)) 6074e4480e8SJakub Kicinski continue 6084e4480e8SJakub Kicinski 609081e8df6SJakub Kicinski rsp_msg = self._decode(gm.raw_attrs, op.attr_set.name) 610081e8df6SJakub Kicinski rsp_msg.update(gm.fixed_header_attrs) 611081e8df6SJakub Kicinski rsp.append(rsp_msg) 6124e4480e8SJakub Kicinski 6134e4480e8SJakub Kicinski if not rsp: 6144e4480e8SJakub Kicinski return None 6154e4480e8SJakub Kicinski if not dump and len(rsp) == 1: 6164e4480e8SJakub Kicinski return rsp[0] 6174e4480e8SJakub Kicinski return rsp 6188dfec0a8SJakub Kicinski 6198dfec0a8SJakub Kicinski def do(self, method, vals): 6208dfec0a8SJakub Kicinski return self._op(method, vals) 6218dfec0a8SJakub Kicinski 6228dfec0a8SJakub Kicinski def dump(self, method, vals): 6238dfec0a8SJakub Kicinski return self._op(method, vals, dump=True) 624