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