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 28*e46dd903SDonald Hunter NETLINK_GET_STRICT_CHK = 12 294e4480e8SJakub Kicinski 304e4480e8SJakub Kicinski # Netlink message 314e4480e8SJakub Kicinski NLMSG_ERROR = 2 324e4480e8SJakub Kicinski NLMSG_DONE = 3 334e4480e8SJakub Kicinski 344e4480e8SJakub Kicinski NLM_F_REQUEST = 1 354e4480e8SJakub Kicinski NLM_F_ACK = 4 364e4480e8SJakub Kicinski NLM_F_ROOT = 0x100 374e4480e8SJakub Kicinski NLM_F_MATCH = 0x200 384e4480e8SJakub Kicinski NLM_F_APPEND = 0x800 394e4480e8SJakub Kicinski 404e4480e8SJakub Kicinski NLM_F_CAPPED = 0x100 414e4480e8SJakub Kicinski NLM_F_ACK_TLVS = 0x200 424e4480e8SJakub Kicinski 434e4480e8SJakub Kicinski NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH 444e4480e8SJakub Kicinski 454e4480e8SJakub Kicinski NLA_F_NESTED = 0x8000 464e4480e8SJakub Kicinski NLA_F_NET_BYTEORDER = 0x4000 474e4480e8SJakub Kicinski 484e4480e8SJakub Kicinski NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER 494e4480e8SJakub Kicinski 504e4480e8SJakub Kicinski # Genetlink defines 514e4480e8SJakub Kicinski NETLINK_GENERIC = 16 524e4480e8SJakub Kicinski 534e4480e8SJakub Kicinski GENL_ID_CTRL = 0x10 544e4480e8SJakub Kicinski 554e4480e8SJakub Kicinski # nlctrl 564e4480e8SJakub Kicinski CTRL_CMD_GETFAMILY = 3 574e4480e8SJakub Kicinski 584e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_ID = 1 594e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_NAME = 2 604e4480e8SJakub Kicinski CTRL_ATTR_MAXATTR = 5 614e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GROUPS = 7 624e4480e8SJakub Kicinski 634e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_NAME = 1 644e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_ID = 2 654e4480e8SJakub Kicinski 664e4480e8SJakub Kicinski # Extack types 674e4480e8SJakub Kicinski NLMSGERR_ATTR_MSG = 1 684e4480e8SJakub Kicinski NLMSGERR_ATTR_OFFS = 2 694e4480e8SJakub Kicinski NLMSGERR_ATTR_COOKIE = 3 704e4480e8SJakub Kicinski NLMSGERR_ATTR_POLICY = 4 714e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_TYPE = 5 724e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_NEST = 6 734e4480e8SJakub Kicinski 744e4480e8SJakub Kicinski 7548993e22SStanislav Fomichevclass NlError(Exception): 7648993e22SStanislav Fomichev def __init__(self, nl_msg): 7748993e22SStanislav Fomichev self.nl_msg = nl_msg 7848993e22SStanislav Fomichev 7948993e22SStanislav Fomichev def __str__(self): 8048993e22SStanislav Fomichev return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}" 8148993e22SStanislav Fomichev 8248993e22SStanislav Fomichev 834e4480e8SJakub Kicinskiclass NlAttr: 847c2435efSDonald Hunter ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) 857c2435efSDonald Hunter type_formats = { 867c2435efSDonald Hunter 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), 877c2435efSDonald Hunter 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), 887c2435efSDonald Hunter 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")), 897c2435efSDonald Hunter 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")), 907c2435efSDonald Hunter 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")), 917c2435efSDonald Hunter 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")), 927c2435efSDonald Hunter 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")), 937c2435efSDonald Hunter 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q")) 947c2435efSDonald Hunter } 95b423c3c8SDonald Hunter 964e4480e8SJakub Kicinski def __init__(self, raw, offset): 974e4480e8SJakub Kicinski self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) 984e4480e8SJakub Kicinski self.type = self._type & ~Netlink.NLA_TYPE_MASK 994e4480e8SJakub Kicinski self.payload_len = self._len 1004e4480e8SJakub Kicinski self.full_len = (self.payload_len + 3) & ~3 1014e4480e8SJakub Kicinski self.raw = raw[offset + 4:offset + self.payload_len] 1024e4480e8SJakub Kicinski 1037c2435efSDonald Hunter @classmethod 1047c2435efSDonald Hunter def get_format(cls, attr_type, byte_order=None): 1057c2435efSDonald Hunter format = cls.type_formats[attr_type] 1069f7cc57fSStanislav Fomichev if byte_order: 1077c2435efSDonald Hunter return format.big if byte_order == "big-endian" \ 1087c2435efSDonald Hunter else format.little 1097c2435efSDonald Hunter return format.native 1109f7cc57fSStanislav Fomichev 111d8eea68dSDonald Hunter @classmethod 112d8eea68dSDonald Hunter def formatted_string(cls, raw, display_hint): 113d8eea68dSDonald Hunter if display_hint == 'mac': 114d8eea68dSDonald Hunter formatted = ':'.join('%02x' % b for b in raw) 115d8eea68dSDonald Hunter elif display_hint == 'hex': 116d8eea68dSDonald Hunter formatted = bytes.hex(raw, ' ') 117d8eea68dSDonald Hunter elif display_hint in [ 'ipv4', 'ipv6' ]: 118d8eea68dSDonald Hunter formatted = format(ipaddress.ip_address(raw)) 119d8eea68dSDonald Hunter elif display_hint == 'uuid': 120d8eea68dSDonald Hunter formatted = str(uuid.UUID(bytes=raw)) 121d8eea68dSDonald Hunter else: 122d8eea68dSDonald Hunter formatted = raw 123d8eea68dSDonald Hunter return formatted 124d8eea68dSDonald Hunter 1257c2435efSDonald Hunter def as_scalar(self, attr_type, byte_order=None): 1267c2435efSDonald Hunter format = self.get_format(attr_type, byte_order) 1277c2435efSDonald Hunter return format.unpack(self.raw)[0] 1284e4480e8SJakub Kicinski 1294e4480e8SJakub Kicinski def as_strz(self): 1304e4480e8SJakub Kicinski return self.raw.decode('ascii')[:-1] 1314e4480e8SJakub Kicinski 1324e4480e8SJakub Kicinski def as_bin(self): 1334e4480e8SJakub Kicinski return self.raw 1344e4480e8SJakub Kicinski 135b423c3c8SDonald Hunter def as_c_array(self, type): 1367c2435efSDonald Hunter format = self.get_format(type) 1377c2435efSDonald Hunter return [ x[0] for x in format.iter_unpack(self.raw) ] 138b423c3c8SDonald Hunter 13926071913SDonald Hunter def as_struct(self, members): 14026071913SDonald Hunter value = dict() 14126071913SDonald Hunter offset = 0 14226071913SDonald Hunter for m in members: 14326071913SDonald Hunter # TODO: handle non-scalar members 144d8eea68dSDonald Hunter if m.type == 'binary': 145d8eea68dSDonald Hunter decoded = self.raw[offset:offset+m['len']] 146d8eea68dSDonald Hunter offset += m['len'] 147d8eea68dSDonald Hunter elif m.type in NlAttr.type_formats: 148bddd2e56SDonald Hunter format = self.get_format(m.type, m.byte_order) 149d8eea68dSDonald Hunter [ decoded ] = format.unpack_from(self.raw, offset) 1507c2435efSDonald Hunter offset += format.size 151d8eea68dSDonald Hunter if m.display_hint: 152d8eea68dSDonald Hunter decoded = self.formatted_string(decoded, m.display_hint) 153d8eea68dSDonald Hunter value[m.name] = decoded 15426071913SDonald Hunter return value 15526071913SDonald Hunter 1564e4480e8SJakub Kicinski def __repr__(self): 1574e4480e8SJakub Kicinski return f"[type:{self.type} len:{self._len}] {self.raw}" 1584e4480e8SJakub Kicinski 1594e4480e8SJakub Kicinski 1604e4480e8SJakub Kicinskiclass NlAttrs: 1614e4480e8SJakub Kicinski def __init__(self, msg): 1624e4480e8SJakub Kicinski self.attrs = [] 1634e4480e8SJakub Kicinski 1644e4480e8SJakub Kicinski offset = 0 1654e4480e8SJakub Kicinski while offset < len(msg): 1664e4480e8SJakub Kicinski attr = NlAttr(msg, offset) 1674e4480e8SJakub Kicinski offset += attr.full_len 1684e4480e8SJakub Kicinski self.attrs.append(attr) 1694e4480e8SJakub Kicinski 1704e4480e8SJakub Kicinski def __iter__(self): 1714e4480e8SJakub Kicinski yield from self.attrs 1724e4480e8SJakub Kicinski 1734e4480e8SJakub Kicinski def __repr__(self): 1744e4480e8SJakub Kicinski msg = '' 1754e4480e8SJakub Kicinski for a in self.attrs: 1764e4480e8SJakub Kicinski if msg: 1774e4480e8SJakub Kicinski msg += '\n' 1784e4480e8SJakub Kicinski msg += repr(a) 1794e4480e8SJakub Kicinski return msg 1804e4480e8SJakub Kicinski 1814e4480e8SJakub Kicinski 1824e4480e8SJakub Kicinskiclass NlMsg: 1834e4480e8SJakub Kicinski def __init__(self, msg, offset, attr_space=None): 1844e4480e8SJakub Kicinski self.hdr = msg[offset:offset + 16] 1854e4480e8SJakub Kicinski 1864e4480e8SJakub Kicinski self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ 1874e4480e8SJakub Kicinski struct.unpack("IHHII", self.hdr) 1884e4480e8SJakub Kicinski 1894e4480e8SJakub Kicinski self.raw = msg[offset + 16:offset + self.nl_len] 1904e4480e8SJakub Kicinski 1914e4480e8SJakub Kicinski self.error = 0 1924e4480e8SJakub Kicinski self.done = 0 1934e4480e8SJakub Kicinski 1944e4480e8SJakub Kicinski extack_off = None 1954e4480e8SJakub Kicinski if self.nl_type == Netlink.NLMSG_ERROR: 1964e4480e8SJakub Kicinski self.error = struct.unpack("i", self.raw[0:4])[0] 1974e4480e8SJakub Kicinski self.done = 1 1984e4480e8SJakub Kicinski extack_off = 20 1994e4480e8SJakub Kicinski elif self.nl_type == Netlink.NLMSG_DONE: 2004e4480e8SJakub Kicinski self.done = 1 2014e4480e8SJakub Kicinski extack_off = 4 2024e4480e8SJakub Kicinski 2034e4480e8SJakub Kicinski self.extack = None 2044e4480e8SJakub Kicinski if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: 2054e4480e8SJakub Kicinski self.extack = dict() 2064e4480e8SJakub Kicinski extack_attrs = NlAttrs(self.raw[extack_off:]) 2074e4480e8SJakub Kicinski for extack in extack_attrs: 2084e4480e8SJakub Kicinski if extack.type == Netlink.NLMSGERR_ATTR_MSG: 2094e4480e8SJakub Kicinski self.extack['msg'] = extack.as_strz() 2104e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: 2117c2435efSDonald Hunter self.extack['miss-type'] = extack.as_scalar('u32') 2124e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: 2137c2435efSDonald Hunter self.extack['miss-nest'] = extack.as_scalar('u32') 2144e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 2157c2435efSDonald Hunter self.extack['bad-attr-offs'] = extack.as_scalar('u32') 2164e4480e8SJakub Kicinski else: 2174e4480e8SJakub Kicinski if 'unknown' not in self.extack: 2184e4480e8SJakub Kicinski self.extack['unknown'] = [] 2194e4480e8SJakub Kicinski self.extack['unknown'].append(extack) 2204e4480e8SJakub Kicinski 2214e4480e8SJakub Kicinski if attr_space: 2224e4480e8SJakub Kicinski # We don't have the ability to parse nests yet, so only do global 2234e4480e8SJakub Kicinski if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 2244e4480e8SJakub Kicinski miss_type = self.extack['miss-type'] 22530a5c6c8SJakub Kicinski if miss_type in attr_space.attrs_by_val: 22630a5c6c8SJakub Kicinski spec = attr_space.attrs_by_val[miss_type] 2274e4480e8SJakub Kicinski desc = spec['name'] 2284e4480e8SJakub Kicinski if 'doc' in spec: 2294e4480e8SJakub Kicinski desc += f" ({spec['doc']})" 2304e4480e8SJakub Kicinski self.extack['miss-type'] = desc 2314e4480e8SJakub Kicinski 232*e46dd903SDonald Hunter def cmd(self): 233*e46dd903SDonald Hunter return self.nl_type 234*e46dd903SDonald Hunter 2354e4480e8SJakub Kicinski def __repr__(self): 2364e4480e8SJakub Kicinski msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n" 2374e4480e8SJakub Kicinski if self.error: 2384e4480e8SJakub Kicinski msg += '\terror: ' + str(self.error) 2394e4480e8SJakub Kicinski if self.extack: 2404e4480e8SJakub Kicinski msg += '\textack: ' + repr(self.extack) 2414e4480e8SJakub Kicinski return msg 2424e4480e8SJakub Kicinski 2434e4480e8SJakub Kicinski 2444e4480e8SJakub Kicinskiclass NlMsgs: 2454e4480e8SJakub Kicinski def __init__(self, data, attr_space=None): 2464e4480e8SJakub Kicinski self.msgs = [] 2474e4480e8SJakub Kicinski 2484e4480e8SJakub Kicinski offset = 0 2494e4480e8SJakub Kicinski while offset < len(data): 2504e4480e8SJakub Kicinski msg = NlMsg(data, offset, attr_space=attr_space) 2514e4480e8SJakub Kicinski offset += msg.nl_len 2524e4480e8SJakub Kicinski self.msgs.append(msg) 2534e4480e8SJakub Kicinski 2544e4480e8SJakub Kicinski def __iter__(self): 2554e4480e8SJakub Kicinski yield from self.msgs 2564e4480e8SJakub Kicinski 2574e4480e8SJakub Kicinski 2584e4480e8SJakub Kicinskigenl_family_name_to_id = None 2594e4480e8SJakub Kicinski 2604e4480e8SJakub Kicinski 2614e4480e8SJakub Kicinskidef _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): 2624e4480e8SJakub Kicinski # we prepend length in _genl_msg_finalize() 2634e4480e8SJakub Kicinski if seq is None: 2644e4480e8SJakub Kicinski seq = random.randint(1, 1024) 2654e4480e8SJakub Kicinski nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 266758d29fbSDonald Hunter genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0) 2674e4480e8SJakub Kicinski return nlmsg + genlmsg 2684e4480e8SJakub Kicinski 2694e4480e8SJakub Kicinski 2704e4480e8SJakub Kicinskidef _genl_msg_finalize(msg): 2714e4480e8SJakub Kicinski return struct.pack("I", len(msg) + 4) + msg 2724e4480e8SJakub Kicinski 2734e4480e8SJakub Kicinski 2744e4480e8SJakub Kicinskidef _genl_load_families(): 2754e4480e8SJakub Kicinski with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 2764e4480e8SJakub Kicinski sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 2774e4480e8SJakub Kicinski 2784e4480e8SJakub Kicinski msg = _genl_msg(Netlink.GENL_ID_CTRL, 2794e4480e8SJakub Kicinski Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 2804e4480e8SJakub Kicinski Netlink.CTRL_CMD_GETFAMILY, 1) 2814e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 2824e4480e8SJakub Kicinski 2834e4480e8SJakub Kicinski sock.send(msg, 0) 2844e4480e8SJakub Kicinski 2854e4480e8SJakub Kicinski global genl_family_name_to_id 2864e4480e8SJakub Kicinski genl_family_name_to_id = dict() 2874e4480e8SJakub Kicinski 2884e4480e8SJakub Kicinski while True: 2894e4480e8SJakub Kicinski reply = sock.recv(128 * 1024) 2904e4480e8SJakub Kicinski nms = NlMsgs(reply) 2914e4480e8SJakub Kicinski for nl_msg in nms: 2924e4480e8SJakub Kicinski if nl_msg.error: 2934e4480e8SJakub Kicinski print("Netlink error:", nl_msg.error) 2944e4480e8SJakub Kicinski return 2954e4480e8SJakub Kicinski if nl_msg.done: 2964e4480e8SJakub Kicinski return 2974e4480e8SJakub Kicinski 2984e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 2994e4480e8SJakub Kicinski fam = dict() 300fb0a06d4SDonald Hunter for attr in NlAttrs(gm.raw): 3014e4480e8SJakub Kicinski if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: 3027c2435efSDonald Hunter fam['id'] = attr.as_scalar('u16') 3034e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: 3044e4480e8SJakub Kicinski fam['name'] = attr.as_strz() 3054e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MAXATTR: 3067c2435efSDonald Hunter fam['maxattr'] = attr.as_scalar('u32') 3074e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: 3084e4480e8SJakub Kicinski fam['mcast'] = dict() 3094e4480e8SJakub Kicinski for entry in NlAttrs(attr.raw): 3104e4480e8SJakub Kicinski mcast_name = None 3114e4480e8SJakub Kicinski mcast_id = None 3124e4480e8SJakub Kicinski for entry_attr in NlAttrs(entry.raw): 3134e4480e8SJakub Kicinski if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: 3144e4480e8SJakub Kicinski mcast_name = entry_attr.as_strz() 3154e4480e8SJakub Kicinski elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: 3167c2435efSDonald Hunter mcast_id = entry_attr.as_scalar('u32') 3174e4480e8SJakub Kicinski if mcast_name and mcast_id is not None: 3184e4480e8SJakub Kicinski fam['mcast'][mcast_name] = mcast_id 3194e4480e8SJakub Kicinski if 'name' in fam and 'id' in fam: 3204e4480e8SJakub Kicinski genl_family_name_to_id[fam['name']] = fam 3214e4480e8SJakub Kicinski 3224e4480e8SJakub Kicinski 3234e4480e8SJakub Kicinskiclass GenlMsg: 324fb0a06d4SDonald Hunter def __init__(self, nl_msg): 3254e4480e8SJakub Kicinski self.nl = nl_msg 326fb0a06d4SDonald Hunter self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0) 327fb0a06d4SDonald Hunter self.raw = nl_msg.raw[4:] 3284e4480e8SJakub Kicinski 329*e46dd903SDonald Hunter def cmd(self): 330*e46dd903SDonald Hunter return self.genl_cmd 331*e46dd903SDonald Hunter 3324e4480e8SJakub Kicinski def __repr__(self): 3334e4480e8SJakub Kicinski msg = repr(self.nl) 3344e4480e8SJakub Kicinski msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" 3354e4480e8SJakub Kicinski for a in self.raw_attrs: 3364e4480e8SJakub Kicinski msg += '\t\t' + repr(a) + '\n' 3374e4480e8SJakub Kicinski return msg 3384e4480e8SJakub Kicinski 3394e4480e8SJakub Kicinski 340*e46dd903SDonald Hunterclass NetlinkProtocol: 341*e46dd903SDonald Hunter def __init__(self, family_name, proto_num): 3424e4480e8SJakub Kicinski self.family_name = family_name 343*e46dd903SDonald Hunter self.proto_num = proto_num 344*e46dd903SDonald Hunter 345*e46dd903SDonald Hunter def _message(self, nl_type, nl_flags, seq=None): 346*e46dd903SDonald Hunter if seq is None: 347*e46dd903SDonald Hunter seq = random.randint(1, 1024) 348*e46dd903SDonald Hunter nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 349*e46dd903SDonald Hunter return nlmsg 350*e46dd903SDonald Hunter 351*e46dd903SDonald Hunter def message(self, flags, command, version, seq=None): 352*e46dd903SDonald Hunter return self._message(command, flags, seq) 353*e46dd903SDonald Hunter 354*e46dd903SDonald Hunter def _decode(self, nl_msg): 355*e46dd903SDonald Hunter return nl_msg 356*e46dd903SDonald Hunter 357*e46dd903SDonald Hunter def decode(self, ynl, nl_msg): 358*e46dd903SDonald Hunter msg = self._decode(nl_msg) 359*e46dd903SDonald Hunter fixed_header_size = 0 360*e46dd903SDonald Hunter if ynl: 361*e46dd903SDonald Hunter op = ynl.rsp_by_value[msg.cmd()] 362*e46dd903SDonald Hunter fixed_header_size = ynl._fixed_header_size(op) 363*e46dd903SDonald Hunter msg.raw_attrs = NlAttrs(msg.raw[fixed_header_size:]) 364*e46dd903SDonald Hunter return msg 365*e46dd903SDonald Hunter 366*e46dd903SDonald Hunter def get_mcast_id(self, mcast_name, mcast_groups): 367*e46dd903SDonald Hunter if mcast_name not in mcast_groups: 368*e46dd903SDonald Hunter raise Exception(f'Multicast group "{mcast_name}" not present in the spec') 369*e46dd903SDonald Hunter return mcast_groups[mcast_name].value 370*e46dd903SDonald Hunter 371*e46dd903SDonald Hunter 372*e46dd903SDonald Hunterclass GenlProtocol(NetlinkProtocol): 373*e46dd903SDonald Hunter def __init__(self, family_name): 374*e46dd903SDonald Hunter super().__init__(family_name, Netlink.NETLINK_GENERIC) 3754e4480e8SJakub Kicinski 3764e4480e8SJakub Kicinski global genl_family_name_to_id 3774e4480e8SJakub Kicinski if genl_family_name_to_id is None: 3784e4480e8SJakub Kicinski _genl_load_families() 3794e4480e8SJakub Kicinski 3804e4480e8SJakub Kicinski self.genl_family = genl_family_name_to_id[family_name] 3814e4480e8SJakub Kicinski self.family_id = genl_family_name_to_id[family_name]['id'] 3824e4480e8SJakub Kicinski 383*e46dd903SDonald Hunter def message(self, flags, command, version, seq=None): 384*e46dd903SDonald Hunter nlmsg = self._message(self.family_id, flags, seq) 385*e46dd903SDonald Hunter genlmsg = struct.pack("BBH", command, version, 0) 386*e46dd903SDonald Hunter return nlmsg + genlmsg 387*e46dd903SDonald Hunter 388*e46dd903SDonald Hunter def _decode(self, nl_msg): 389*e46dd903SDonald Hunter return GenlMsg(nl_msg) 390*e46dd903SDonald Hunter 391*e46dd903SDonald Hunter def get_mcast_id(self, mcast_name, mcast_groups): 392*e46dd903SDonald Hunter if mcast_name not in self.genl_family['mcast']: 393*e46dd903SDonald Hunter raise Exception(f'Multicast group "{mcast_name}" not present in the family') 394*e46dd903SDonald Hunter return self.genl_family['mcast'][mcast_name] 395*e46dd903SDonald Hunter 3964e4480e8SJakub Kicinski 3974e4480e8SJakub Kicinski# 3984e4480e8SJakub Kicinski# YNL implementation details. 3994e4480e8SJakub Kicinski# 4004e4480e8SJakub Kicinski 4014e4480e8SJakub Kicinski 40230a5c6c8SJakub Kicinskiclass YnlFamily(SpecFamily): 4034e4480e8SJakub Kicinski def __init__(self, def_path, schema=None): 40430a5c6c8SJakub Kicinski super().__init__(def_path, schema) 40530a5c6c8SJakub Kicinski 4064e4480e8SJakub Kicinski self.include_raw = False 4074e4480e8SJakub Kicinski 408*e46dd903SDonald Hunter try: 409*e46dd903SDonald Hunter if self.proto == "netlink-raw": 410*e46dd903SDonald Hunter self.nlproto = NetlinkProtocol(self.yaml['name'], 411*e46dd903SDonald Hunter self.yaml['protonum']) 412*e46dd903SDonald Hunter else: 413*e46dd903SDonald Hunter self.nlproto = GenlProtocol(self.yaml['name']) 414*e46dd903SDonald Hunter except KeyError: 415*e46dd903SDonald Hunter raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel") 416*e46dd903SDonald Hunter 417*e46dd903SDonald Hunter self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num) 4184e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 4194e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 420*e46dd903SDonald Hunter self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1) 4214e4480e8SJakub Kicinski 4224e4480e8SJakub Kicinski self.async_msg_ids = set() 4234e4480e8SJakub Kicinski self.async_msg_queue = [] 4244e4480e8SJakub Kicinski 42530a5c6c8SJakub Kicinski for msg in self.msgs.values(): 42630a5c6c8SJakub Kicinski if msg.is_async: 427fd0616d3SJakub Kicinski self.async_msg_ids.add(msg.rsp_value) 4284e4480e8SJakub Kicinski 42930a5c6c8SJakub Kicinski for op_name, op in self.ops.items(): 43030a5c6c8SJakub Kicinski bound_f = functools.partial(self._op, op_name) 43130a5c6c8SJakub Kicinski setattr(self, op.ident_name, bound_f) 4324e4480e8SJakub Kicinski 4334e4480e8SJakub Kicinski 4344e4480e8SJakub Kicinski def ntf_subscribe(self, mcast_name): 435*e46dd903SDonald Hunter mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups) 4364e4480e8SJakub Kicinski self.sock.bind((0, 0)) 4374e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 438*e46dd903SDonald Hunter mcast_id) 4394e4480e8SJakub Kicinski 4404e4480e8SJakub Kicinski def _add_attr(self, space, name, value): 4417582113cSJakub Kicinski try: 44230a5c6c8SJakub Kicinski attr = self.attr_sets[space][name] 4437582113cSJakub Kicinski except KeyError: 4447582113cSJakub Kicinski raise Exception(f"Space '{space}' has no attribute '{name}'") 44530a5c6c8SJakub Kicinski nl_type = attr.value 4464e4480e8SJakub Kicinski if attr["type"] == 'nest': 4474e4480e8SJakub Kicinski nl_type |= Netlink.NLA_F_NESTED 4484e4480e8SJakub Kicinski attr_payload = b'' 4494e4480e8SJakub Kicinski for subname, subvalue in value.items(): 4504e4480e8SJakub Kicinski attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) 45119b64b48SJakub Kicinski elif attr["type"] == 'flag': 45219b64b48SJakub Kicinski attr_payload = b'' 4534e4480e8SJakub Kicinski elif attr["type"] == 'string': 4544e4480e8SJakub Kicinski attr_payload = str(value).encode('ascii') + b'\x00' 4554e4480e8SJakub Kicinski elif attr["type"] == 'binary': 456649bde90SJakub Kicinski if isinstance(value, bytes): 457649bde90SJakub Kicinski attr_payload = value 458649bde90SJakub Kicinski elif isinstance(value, str): 459d8eea68dSDonald Hunter attr_payload = bytes.fromhex(value) 460649bde90SJakub Kicinski else: 461649bde90SJakub Kicinski raise Exception(f'Unknown type for binary attribute, value: {value}') 4627c2435efSDonald Hunter elif attr['type'] in NlAttr.type_formats: 4637c2435efSDonald Hunter format = NlAttr.get_format(attr['type'], attr.byte_order) 4647c2435efSDonald Hunter attr_payload = format.pack(int(value)) 4654e4480e8SJakub Kicinski else: 4664e4480e8SJakub Kicinski raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') 4674e4480e8SJakub Kicinski 4684e4480e8SJakub Kicinski pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 4694e4480e8SJakub Kicinski return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 4704e4480e8SJakub Kicinski 471df15c15eSArkadiusz Kubalewski def _decode_enum(self, raw, attr_spec): 472c311aaa7SJakub Kicinski enum = self.consts[attr_spec['enum']] 4734e4480e8SJakub Kicinski if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']: 474d7ddf5f4SArkadiusz Kubalewski i = 0 4754e4480e8SJakub Kicinski value = set() 4764e4480e8SJakub Kicinski while raw: 4774e4480e8SJakub Kicinski if raw & 1: 478c311aaa7SJakub Kicinski value.add(enum.entries_by_val[i].name) 4794e4480e8SJakub Kicinski raw >>= 1 4804e4480e8SJakub Kicinski i += 1 4814e4480e8SJakub Kicinski else: 482d7ddf5f4SArkadiusz Kubalewski value = enum.entries_by_val[raw].name 483df15c15eSArkadiusz Kubalewski return value 4844e4480e8SJakub Kicinski 485b423c3c8SDonald Hunter def _decode_binary(self, attr, attr_spec): 48626071913SDonald Hunter if attr_spec.struct_name: 487313a7a80SDonald Hunter members = self.consts[attr_spec.struct_name] 488313a7a80SDonald Hunter decoded = attr.as_struct(members) 489313a7a80SDonald Hunter for m in members: 490313a7a80SDonald Hunter if m.enum: 491df15c15eSArkadiusz Kubalewski decoded[m.name] = self._decode_enum(decoded[m.name], m) 49226071913SDonald Hunter elif attr_spec.sub_type: 493b423c3c8SDonald Hunter decoded = attr.as_c_array(attr_spec.sub_type) 494b423c3c8SDonald Hunter else: 495b423c3c8SDonald Hunter decoded = attr.as_bin() 496d8eea68dSDonald Hunter if attr_spec.display_hint: 497d8eea68dSDonald Hunter decoded = NlAttr.formatted_string(decoded, attr_spec.display_hint) 498b423c3c8SDonald Hunter return decoded 499b423c3c8SDonald Hunter 5004e4480e8SJakub Kicinski def _decode(self, attrs, space): 50130a5c6c8SJakub Kicinski attr_space = self.attr_sets[space] 5024e4480e8SJakub Kicinski rsp = dict() 5034e4480e8SJakub Kicinski for attr in attrs: 5047582113cSJakub Kicinski try: 50530a5c6c8SJakub Kicinski attr_spec = attr_space.attrs_by_val[attr.type] 5067582113cSJakub Kicinski except KeyError: 5077582113cSJakub Kicinski raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'") 5084e4480e8SJakub Kicinski if attr_spec["type"] == 'nest': 5094e4480e8SJakub Kicinski subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) 51090256f3fSJakub Kicinski decoded = subdict 5114e4480e8SJakub Kicinski elif attr_spec["type"] == 'string': 51290256f3fSJakub Kicinski decoded = attr.as_strz() 5134e4480e8SJakub Kicinski elif attr_spec["type"] == 'binary': 514b423c3c8SDonald Hunter decoded = self._decode_binary(attr, attr_spec) 51519b64b48SJakub Kicinski elif attr_spec["type"] == 'flag': 51690256f3fSJakub Kicinski decoded = True 5177c2435efSDonald Hunter elif attr_spec["type"] in NlAttr.type_formats: 5187c2435efSDonald Hunter decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) 5194e4480e8SJakub Kicinski else: 5207c2435efSDonald Hunter raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') 5214e4480e8SJakub Kicinski 522df15c15eSArkadiusz Kubalewski if 'enum' in attr_spec: 523df15c15eSArkadiusz Kubalewski decoded = self._decode_enum(decoded, attr_spec) 524df15c15eSArkadiusz Kubalewski 52590256f3fSJakub Kicinski if not attr_spec.is_multi: 52690256f3fSJakub Kicinski rsp[attr_spec['name']] = decoded 52790256f3fSJakub Kicinski elif attr_spec.name in rsp: 52890256f3fSJakub Kicinski rsp[attr_spec.name].append(decoded) 52990256f3fSJakub Kicinski else: 53090256f3fSJakub Kicinski rsp[attr_spec.name] = [decoded] 53190256f3fSJakub Kicinski 5324e4480e8SJakub Kicinski return rsp 5334e4480e8SJakub Kicinski 5344cd2796fSJakub Kicinski def _decode_extack_path(self, attrs, attr_set, offset, target): 5354cd2796fSJakub Kicinski for attr in attrs: 5367582113cSJakub Kicinski try: 5374cd2796fSJakub Kicinski attr_spec = attr_set.attrs_by_val[attr.type] 5387582113cSJakub Kicinski except KeyError: 5397582113cSJakub Kicinski raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'") 5404cd2796fSJakub Kicinski if offset > target: 5414cd2796fSJakub Kicinski break 5424cd2796fSJakub Kicinski if offset == target: 5434cd2796fSJakub Kicinski return '.' + attr_spec.name 5444cd2796fSJakub Kicinski 5454cd2796fSJakub Kicinski if offset + attr.full_len <= target: 5464cd2796fSJakub Kicinski offset += attr.full_len 5474cd2796fSJakub Kicinski continue 5484cd2796fSJakub Kicinski if attr_spec['type'] != 'nest': 5494cd2796fSJakub Kicinski raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 5504cd2796fSJakub Kicinski offset += 4 5514cd2796fSJakub Kicinski subpath = self._decode_extack_path(NlAttrs(attr.raw), 5524cd2796fSJakub Kicinski self.attr_sets[attr_spec['nested-attributes']], 5534cd2796fSJakub Kicinski offset, target) 5544cd2796fSJakub Kicinski if subpath is None: 5554cd2796fSJakub Kicinski return None 5564cd2796fSJakub Kicinski return '.' + attr_spec.name + subpath 5574cd2796fSJakub Kicinski 5584cd2796fSJakub Kicinski return None 5594cd2796fSJakub Kicinski 560fb0a06d4SDonald Hunter def _decode_extack(self, request, op, extack): 5614cd2796fSJakub Kicinski if 'bad-attr-offs' not in extack: 5624cd2796fSJakub Kicinski return 5634cd2796fSJakub Kicinski 564*e46dd903SDonald Hunter msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set)) 565*e46dd903SDonald Hunter offset = 20 + self._fixed_header_size(op) 566*e46dd903SDonald Hunter path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, 567fb0a06d4SDonald Hunter extack['bad-attr-offs']) 5684cd2796fSJakub Kicinski if path: 5694cd2796fSJakub Kicinski del extack['bad-attr-offs'] 5704cd2796fSJakub Kicinski extack['bad-attr'] = path 5714cd2796fSJakub Kicinski 572fb0a06d4SDonald Hunter def _fixed_header_size(self, op): 573fb0a06d4SDonald Hunter if op.fixed_header: 574fb0a06d4SDonald Hunter fixed_header_members = self.consts[op.fixed_header].members 575fb0a06d4SDonald Hunter size = 0 576fb0a06d4SDonald Hunter for m in fixed_header_members: 577fb0a06d4SDonald Hunter format = NlAttr.get_format(m.type, m.byte_order) 578fb0a06d4SDonald Hunter size += format.size 579fb0a06d4SDonald Hunter return size 580fb0a06d4SDonald Hunter else: 581fb0a06d4SDonald Hunter return 0 582fb0a06d4SDonald Hunter 583fb0a06d4SDonald Hunter def _decode_fixed_header(self, msg, name): 584fb0a06d4SDonald Hunter fixed_header_members = self.consts[name].members 585fb0a06d4SDonald Hunter fixed_header_attrs = dict() 586fb0a06d4SDonald Hunter offset = 0 587fb0a06d4SDonald Hunter for m in fixed_header_members: 588fb0a06d4SDonald Hunter format = NlAttr.get_format(m.type, m.byte_order) 589fb0a06d4SDonald Hunter [ value ] = format.unpack_from(msg.raw, offset) 590fb0a06d4SDonald Hunter offset += format.size 591fb0a06d4SDonald Hunter if m.enum: 592fb0a06d4SDonald Hunter value = self._decode_enum(value, m) 593fb0a06d4SDonald Hunter fixed_header_attrs[m.name] = value 594fb0a06d4SDonald Hunter return fixed_header_attrs 595fb0a06d4SDonald Hunter 596*e46dd903SDonald Hunter def handle_ntf(self, decoded): 5974e4480e8SJakub Kicinski msg = dict() 5984e4480e8SJakub Kicinski if self.include_raw: 599*e46dd903SDonald Hunter msg['raw'] = decoded 600*e46dd903SDonald Hunter op = self.rsp_by_value[decoded.cmd()] 601*e46dd903SDonald Hunter attrs = self._decode(decoded.raw_attrs, op.attr_set.name) 602*e46dd903SDonald Hunter if op.fixed_header: 603*e46dd903SDonald Hunter attrs.update(self._decode_fixed_header(decoded, op.fixed_header)) 604*e46dd903SDonald Hunter 6054e4480e8SJakub Kicinski msg['name'] = op['name'] 606*e46dd903SDonald Hunter msg['msg'] = attrs 6074e4480e8SJakub Kicinski self.async_msg_queue.append(msg) 6084e4480e8SJakub Kicinski 6094e4480e8SJakub Kicinski def check_ntf(self): 6104e4480e8SJakub Kicinski while True: 6114e4480e8SJakub Kicinski try: 6124e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT) 6134e4480e8SJakub Kicinski except BlockingIOError: 6144e4480e8SJakub Kicinski return 6154e4480e8SJakub Kicinski 6164e4480e8SJakub Kicinski nms = NlMsgs(reply) 6174e4480e8SJakub Kicinski for nl_msg in nms: 6184e4480e8SJakub Kicinski if nl_msg.error: 6194e4480e8SJakub Kicinski print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 6204e4480e8SJakub Kicinski print(nl_msg) 6214e4480e8SJakub Kicinski continue 6224e4480e8SJakub Kicinski if nl_msg.done: 6234e4480e8SJakub Kicinski print("Netlink done while checking for ntf!?") 6244e4480e8SJakub Kicinski continue 6254e4480e8SJakub Kicinski 626*e46dd903SDonald Hunter decoded = self.nlproto.decode(self, nl_msg) 627*e46dd903SDonald Hunter if decoded.cmd() not in self.async_msg_ids: 628*e46dd903SDonald Hunter print("Unexpected msg id done while checking for ntf", decoded) 6294e4480e8SJakub Kicinski continue 6304e4480e8SJakub Kicinski 631*e46dd903SDonald Hunter self.handle_ntf(decoded) 6324e4480e8SJakub Kicinski 633f3d07b02SStanislav Fomichev def operation_do_attributes(self, name): 634f3d07b02SStanislav Fomichev """ 635f3d07b02SStanislav Fomichev For a given operation name, find and return a supported 636f3d07b02SStanislav Fomichev set of attributes (as a dict). 637f3d07b02SStanislav Fomichev """ 638f3d07b02SStanislav Fomichev op = self.find_operation(name) 639f3d07b02SStanislav Fomichev if not op: 640f3d07b02SStanislav Fomichev return None 641f3d07b02SStanislav Fomichev 642f3d07b02SStanislav Fomichev return op['do']['request']['attributes'].copy() 643f3d07b02SStanislav Fomichev 6444e4480e8SJakub Kicinski def _op(self, method, vals, dump=False): 64530a5c6c8SJakub Kicinski op = self.ops[method] 6464e4480e8SJakub Kicinski 6474e4480e8SJakub Kicinski nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 6484e4480e8SJakub Kicinski if dump: 6494e4480e8SJakub Kicinski nl_flags |= Netlink.NLM_F_DUMP 6504e4480e8SJakub Kicinski 6514e4480e8SJakub Kicinski req_seq = random.randint(1024, 65535) 652*e46dd903SDonald Hunter msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq) 653f036d936SDonald Hunter fixed_header_members = [] 654f036d936SDonald Hunter if op.fixed_header: 655f036d936SDonald Hunter fixed_header_members = self.consts[op.fixed_header].members 656f036d936SDonald Hunter for m in fixed_header_members: 6575ac18889SDonald Hunter value = vals.pop(m.name) if m.name in vals else 0 658bddd2e56SDonald Hunter format = NlAttr.get_format(m.type, m.byte_order) 6597c2435efSDonald Hunter msg += format.pack(value) 6604e4480e8SJakub Kicinski for name, value in vals.items(): 66130a5c6c8SJakub Kicinski msg += self._add_attr(op.attr_set.name, name, value) 6624e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 6634e4480e8SJakub Kicinski 6644e4480e8SJakub Kicinski self.sock.send(msg, 0) 6654e4480e8SJakub Kicinski 6664e4480e8SJakub Kicinski done = False 6674e4480e8SJakub Kicinski rsp = [] 6684e4480e8SJakub Kicinski while not done: 6694e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024) 67030a5c6c8SJakub Kicinski nms = NlMsgs(reply, attr_space=op.attr_set) 6714e4480e8SJakub Kicinski for nl_msg in nms: 6724cd2796fSJakub Kicinski if nl_msg.extack: 673fb0a06d4SDonald Hunter self._decode_extack(msg, op, nl_msg.extack) 6744cd2796fSJakub Kicinski 6754e4480e8SJakub Kicinski if nl_msg.error: 67648993e22SStanislav Fomichev raise NlError(nl_msg) 6774e4480e8SJakub Kicinski if nl_msg.done: 6784cd2796fSJakub Kicinski if nl_msg.extack: 6794cd2796fSJakub Kicinski print("Netlink warning:") 6804cd2796fSJakub Kicinski print(nl_msg) 6814e4480e8SJakub Kicinski done = True 6824e4480e8SJakub Kicinski break 6834e4480e8SJakub Kicinski 684*e46dd903SDonald Hunter decoded = self.nlproto.decode(self, nl_msg) 685*e46dd903SDonald Hunter 6864e4480e8SJakub Kicinski # Check if this is a reply to our request 687*e46dd903SDonald Hunter if nl_msg.nl_seq != req_seq or decoded.cmd() != op.rsp_value: 688*e46dd903SDonald Hunter if decoded.cmd() in self.async_msg_ids: 689*e46dd903SDonald Hunter self.handle_ntf(decoded) 6904e4480e8SJakub Kicinski continue 6914e4480e8SJakub Kicinski else: 692*e46dd903SDonald Hunter print('Unexpected message: ' + repr(decoded)) 6934e4480e8SJakub Kicinski continue 6944e4480e8SJakub Kicinski 695*e46dd903SDonald Hunter rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name) 696fb0a06d4SDonald Hunter if op.fixed_header: 697*e46dd903SDonald Hunter rsp_msg.update(self._decode_fixed_header(decoded, op.fixed_header)) 698081e8df6SJakub Kicinski rsp.append(rsp_msg) 6994e4480e8SJakub Kicinski 7004e4480e8SJakub Kicinski if not rsp: 7014e4480e8SJakub Kicinski return None 7024e4480e8SJakub Kicinski if not dump and len(rsp) == 1: 7034e4480e8SJakub Kicinski return rsp[0] 7044e4480e8SJakub Kicinski return rsp 7058dfec0a8SJakub Kicinski 7068dfec0a8SJakub Kicinski def do(self, method, vals): 7078dfec0a8SJakub Kicinski return self._op(method, vals) 7088dfec0a8SJakub Kicinski 7098dfec0a8SJakub Kicinski def dump(self, method, vals): 7108dfec0a8SJakub Kicinski return self._op(method, vals, dump=True) 711