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