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