xref: /openbmc/linux/tools/net/ynl/lib/ynl.py (revision 6a65f015)
137d9df22SJakub Kicinski# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
24e4480e8SJakub Kicinski
37c2435efSDonald Hunterfrom collections import namedtuple
44e4480e8SJakub Kicinskiimport functools
54e4480e8SJakub Kicinskiimport os
64e4480e8SJakub Kicinskiimport random
74e4480e8SJakub Kicinskiimport socket
84e4480e8SJakub Kicinskiimport struct
97c2435efSDonald Hunterfrom struct import Struct
104e4480e8SJakub Kicinskiimport yaml
11d8eea68dSDonald Hunterimport ipaddress
12d8eea68dSDonald Hunterimport uuid
134e4480e8SJakub Kicinski
1430a5c6c8SJakub Kicinskifrom .nlspec import SpecFamily
1530a5c6c8SJakub Kicinski
164e4480e8SJakub Kicinski#
174e4480e8SJakub Kicinski# Generic Netlink code which should really be in some library, but I can't quickly find one.
184e4480e8SJakub Kicinski#
194e4480e8SJakub Kicinski
204e4480e8SJakub Kicinski
214e4480e8SJakub Kicinskiclass Netlink:
224e4480e8SJakub Kicinski    # Netlink socket
234e4480e8SJakub Kicinski    SOL_NETLINK = 270
244e4480e8SJakub Kicinski
254e4480e8SJakub Kicinski    NETLINK_ADD_MEMBERSHIP = 1
264e4480e8SJakub Kicinski    NETLINK_CAP_ACK = 10
274e4480e8SJakub Kicinski    NETLINK_EXT_ACK = 11
28e46dd903SDonald Hunter    NETLINK_GET_STRICT_CHK = 12
294e4480e8SJakub Kicinski
304e4480e8SJakub Kicinski    # Netlink message
314e4480e8SJakub Kicinski    NLMSG_ERROR = 2
324e4480e8SJakub Kicinski    NLMSG_DONE = 3
334e4480e8SJakub Kicinski
344e4480e8SJakub Kicinski    NLM_F_REQUEST = 1
354e4480e8SJakub Kicinski    NLM_F_ACK = 4
364e4480e8SJakub Kicinski    NLM_F_ROOT = 0x100
374e4480e8SJakub Kicinski    NLM_F_MATCH = 0x200
381768d8a7SDonald Hunter
391768d8a7SDonald Hunter    NLM_F_REPLACE = 0x100
401768d8a7SDonald Hunter    NLM_F_EXCL = 0x200
411768d8a7SDonald Hunter    NLM_F_CREATE = 0x400
424e4480e8SJakub Kicinski    NLM_F_APPEND = 0x800
434e4480e8SJakub Kicinski
444e4480e8SJakub Kicinski    NLM_F_CAPPED = 0x100
454e4480e8SJakub Kicinski    NLM_F_ACK_TLVS = 0x200
464e4480e8SJakub Kicinski
474e4480e8SJakub Kicinski    NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
484e4480e8SJakub Kicinski
494e4480e8SJakub Kicinski    NLA_F_NESTED = 0x8000
504e4480e8SJakub Kicinski    NLA_F_NET_BYTEORDER = 0x4000
514e4480e8SJakub Kicinski
524e4480e8SJakub Kicinski    NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER
534e4480e8SJakub Kicinski
544e4480e8SJakub Kicinski    # Genetlink defines
554e4480e8SJakub Kicinski    NETLINK_GENERIC = 16
564e4480e8SJakub Kicinski
574e4480e8SJakub Kicinski    GENL_ID_CTRL = 0x10
584e4480e8SJakub Kicinski
594e4480e8SJakub Kicinski    # nlctrl
604e4480e8SJakub Kicinski    CTRL_CMD_GETFAMILY = 3
614e4480e8SJakub Kicinski
624e4480e8SJakub Kicinski    CTRL_ATTR_FAMILY_ID = 1
634e4480e8SJakub Kicinski    CTRL_ATTR_FAMILY_NAME = 2
644e4480e8SJakub Kicinski    CTRL_ATTR_MAXATTR = 5
654e4480e8SJakub Kicinski    CTRL_ATTR_MCAST_GROUPS = 7
664e4480e8SJakub Kicinski
674e4480e8SJakub Kicinski    CTRL_ATTR_MCAST_GRP_NAME = 1
684e4480e8SJakub Kicinski    CTRL_ATTR_MCAST_GRP_ID = 2
694e4480e8SJakub Kicinski
704e4480e8SJakub Kicinski    # Extack types
714e4480e8SJakub Kicinski    NLMSGERR_ATTR_MSG = 1
724e4480e8SJakub Kicinski    NLMSGERR_ATTR_OFFS = 2
734e4480e8SJakub Kicinski    NLMSGERR_ATTR_COOKIE = 3
744e4480e8SJakub Kicinski    NLMSGERR_ATTR_POLICY = 4
754e4480e8SJakub Kicinski    NLMSGERR_ATTR_MISS_TYPE = 5
764e4480e8SJakub Kicinski    NLMSGERR_ATTR_MISS_NEST = 6
774e4480e8SJakub Kicinski
784e4480e8SJakub Kicinski
7948993e22SStanislav Fomichevclass NlError(Exception):
8048993e22SStanislav Fomichev  def __init__(self, nl_msg):
8148993e22SStanislav Fomichev    self.nl_msg = nl_msg
8248993e22SStanislav Fomichev
8348993e22SStanislav Fomichev  def __str__(self):
8448993e22SStanislav Fomichev    return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}"
8548993e22SStanislav Fomichev
8648993e22SStanislav Fomichev
874e4480e8SJakub Kicinskiclass NlAttr:
887c2435efSDonald Hunter    ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little'])
897c2435efSDonald Hunter    type_formats = {
907c2435efSDonald Hunter        'u8' : ScalarFormat(Struct('B'), Struct("B"),  Struct("B")),
917c2435efSDonald Hunter        's8' : ScalarFormat(Struct('b'), Struct("b"),  Struct("b")),
927c2435efSDonald Hunter        'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")),
937c2435efSDonald Hunter        's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")),
947c2435efSDonald Hunter        'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")),
957c2435efSDonald Hunter        's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")),
967c2435efSDonald Hunter        'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")),
977c2435efSDonald Hunter        's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q"))
987c2435efSDonald Hunter    }
99b423c3c8SDonald Hunter
1004e4480e8SJakub Kicinski    def __init__(self, raw, offset):
1014e4480e8SJakub Kicinski        self._len, self._type = struct.unpack("HH", raw[offset:offset + 4])
1024e4480e8SJakub Kicinski        self.type = self._type & ~Netlink.NLA_TYPE_MASK
1034e4480e8SJakub Kicinski        self.payload_len = self._len
1044e4480e8SJakub Kicinski        self.full_len = (self.payload_len + 3) & ~3
1054e4480e8SJakub Kicinski        self.raw = raw[offset + 4:offset + self.payload_len]
1064e4480e8SJakub Kicinski
1077c2435efSDonald Hunter    @classmethod
1087c2435efSDonald Hunter    def get_format(cls, attr_type, byte_order=None):
1097c2435efSDonald Hunter        format = cls.type_formats[attr_type]
1109f7cc57fSStanislav Fomichev        if byte_order:
1117c2435efSDonald Hunter            return format.big if byte_order == "big-endian" \
1127c2435efSDonald Hunter                else format.little
1137c2435efSDonald Hunter        return format.native
1149f7cc57fSStanislav Fomichev
115d8eea68dSDonald Hunter    @classmethod
116d8eea68dSDonald Hunter    def formatted_string(cls, raw, display_hint):
117d8eea68dSDonald Hunter        if display_hint == 'mac':
118d8eea68dSDonald Hunter            formatted = ':'.join('%02x' % b for b in raw)
119d8eea68dSDonald Hunter        elif display_hint == 'hex':
120d8eea68dSDonald Hunter            formatted = bytes.hex(raw, ' ')
121d8eea68dSDonald Hunter        elif display_hint in [ 'ipv4', 'ipv6' ]:
122d8eea68dSDonald Hunter            formatted = format(ipaddress.ip_address(raw))
123d8eea68dSDonald Hunter        elif display_hint == 'uuid':
124d8eea68dSDonald Hunter            formatted = str(uuid.UUID(bytes=raw))
125d8eea68dSDonald Hunter        else:
126d8eea68dSDonald Hunter            formatted = raw
127d8eea68dSDonald Hunter        return formatted
128d8eea68dSDonald Hunter
1297c2435efSDonald Hunter    def as_scalar(self, attr_type, byte_order=None):
1307c2435efSDonald Hunter        format = self.get_format(attr_type, byte_order)
1317c2435efSDonald Hunter        return format.unpack(self.raw)[0]
1324e4480e8SJakub Kicinski
1334e4480e8SJakub Kicinski    def as_strz(self):
1344e4480e8SJakub Kicinski        return self.raw.decode('ascii')[:-1]
1354e4480e8SJakub Kicinski
1364e4480e8SJakub Kicinski    def as_bin(self):
1374e4480e8SJakub Kicinski        return self.raw
1384e4480e8SJakub Kicinski
139b423c3c8SDonald Hunter    def as_c_array(self, type):
1407c2435efSDonald Hunter        format = self.get_format(type)
1417c2435efSDonald Hunter        return [ x[0] for x in format.iter_unpack(self.raw) ]
142b423c3c8SDonald Hunter
14326071913SDonald Hunter    def as_struct(self, members):
14426071913SDonald Hunter        value = dict()
14526071913SDonald Hunter        offset = 0
14626071913SDonald Hunter        for m in members:
14726071913SDonald Hunter            # TODO: handle non-scalar members
148d8eea68dSDonald Hunter            if m.type == 'binary':
149d8eea68dSDonald Hunter                decoded = self.raw[offset:offset+m['len']]
150d8eea68dSDonald Hunter                offset += m['len']
151d8eea68dSDonald Hunter            elif m.type in NlAttr.type_formats:
152bddd2e56SDonald Hunter                format = self.get_format(m.type, m.byte_order)
153d8eea68dSDonald Hunter                [ decoded ] = format.unpack_from(self.raw, offset)
1547c2435efSDonald Hunter                offset += format.size
155d8eea68dSDonald Hunter            if m.display_hint:
156d8eea68dSDonald Hunter                decoded = self.formatted_string(decoded, m.display_hint)
157d8eea68dSDonald Hunter            value[m.name] = decoded
15826071913SDonald Hunter        return value
15926071913SDonald Hunter
1604e4480e8SJakub Kicinski    def __repr__(self):
1614e4480e8SJakub Kicinski        return f"[type:{self.type} len:{self._len}] {self.raw}"
1624e4480e8SJakub Kicinski
1634e4480e8SJakub Kicinski
1644e4480e8SJakub Kicinskiclass NlAttrs:
1654e4480e8SJakub Kicinski    def __init__(self, msg):
1664e4480e8SJakub Kicinski        self.attrs = []
1674e4480e8SJakub Kicinski
1684e4480e8SJakub Kicinski        offset = 0
1694e4480e8SJakub Kicinski        while offset < len(msg):
1704e4480e8SJakub Kicinski            attr = NlAttr(msg, offset)
1714e4480e8SJakub Kicinski            offset += attr.full_len
1724e4480e8SJakub Kicinski            self.attrs.append(attr)
1734e4480e8SJakub Kicinski
1744e4480e8SJakub Kicinski    def __iter__(self):
1754e4480e8SJakub Kicinski        yield from self.attrs
1764e4480e8SJakub Kicinski
1774e4480e8SJakub Kicinski    def __repr__(self):
1784e4480e8SJakub Kicinski        msg = ''
1794e4480e8SJakub Kicinski        for a in self.attrs:
1804e4480e8SJakub Kicinski            if msg:
1814e4480e8SJakub Kicinski                msg += '\n'
1824e4480e8SJakub Kicinski            msg += repr(a)
1834e4480e8SJakub Kicinski        return msg
1844e4480e8SJakub Kicinski
1854e4480e8SJakub Kicinski
1864e4480e8SJakub Kicinskiclass NlMsg:
1874e4480e8SJakub Kicinski    def __init__(self, msg, offset, attr_space=None):
1884e4480e8SJakub Kicinski        self.hdr = msg[offset:offset + 16]
1894e4480e8SJakub Kicinski
1904e4480e8SJakub Kicinski        self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \
1914e4480e8SJakub Kicinski            struct.unpack("IHHII", self.hdr)
1924e4480e8SJakub Kicinski
1934e4480e8SJakub Kicinski        self.raw = msg[offset + 16:offset + self.nl_len]
1944e4480e8SJakub Kicinski
1954e4480e8SJakub Kicinski        self.error = 0
1964e4480e8SJakub Kicinski        self.done = 0
1974e4480e8SJakub Kicinski
1984e4480e8SJakub Kicinski        extack_off = None
1994e4480e8SJakub Kicinski        if self.nl_type == Netlink.NLMSG_ERROR:
2004e4480e8SJakub Kicinski            self.error = struct.unpack("i", self.raw[0:4])[0]
2014e4480e8SJakub Kicinski            self.done = 1
2024e4480e8SJakub Kicinski            extack_off = 20
2034e4480e8SJakub Kicinski        elif self.nl_type == Netlink.NLMSG_DONE:
2046a65f015SJakub Kicinski            self.error = struct.unpack("i", self.raw[0:4])[0]
2054e4480e8SJakub Kicinski            self.done = 1
2064e4480e8SJakub Kicinski            extack_off = 4
2074e4480e8SJakub Kicinski
2084e4480e8SJakub Kicinski        self.extack = None
2094e4480e8SJakub Kicinski        if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off:
2104e4480e8SJakub Kicinski            self.extack = dict()
2114e4480e8SJakub Kicinski            extack_attrs = NlAttrs(self.raw[extack_off:])
2124e4480e8SJakub Kicinski            for extack in extack_attrs:
2134e4480e8SJakub Kicinski                if extack.type == Netlink.NLMSGERR_ATTR_MSG:
2144e4480e8SJakub Kicinski                    self.extack['msg'] = extack.as_strz()
2154e4480e8SJakub Kicinski                elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE:
2167c2435efSDonald Hunter                    self.extack['miss-type'] = extack.as_scalar('u32')
2174e4480e8SJakub Kicinski                elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST:
2187c2435efSDonald Hunter                    self.extack['miss-nest'] = extack.as_scalar('u32')
2194e4480e8SJakub Kicinski                elif extack.type == Netlink.NLMSGERR_ATTR_OFFS:
2207c2435efSDonald Hunter                    self.extack['bad-attr-offs'] = extack.as_scalar('u32')
2214e4480e8SJakub Kicinski                else:
2224e4480e8SJakub Kicinski                    if 'unknown' not in self.extack:
2234e4480e8SJakub Kicinski                        self.extack['unknown'] = []
2244e4480e8SJakub Kicinski                    self.extack['unknown'].append(extack)
2254e4480e8SJakub Kicinski
2264e4480e8SJakub Kicinski            if attr_space:
2274e4480e8SJakub Kicinski                # We don't have the ability to parse nests yet, so only do global
2284e4480e8SJakub Kicinski                if 'miss-type' in self.extack and 'miss-nest' not in self.extack:
2294e4480e8SJakub Kicinski                    miss_type = self.extack['miss-type']
23030a5c6c8SJakub Kicinski                    if miss_type in attr_space.attrs_by_val:
23130a5c6c8SJakub Kicinski                        spec = attr_space.attrs_by_val[miss_type]
2324e4480e8SJakub Kicinski                        desc = spec['name']
2334e4480e8SJakub Kicinski                        if 'doc' in spec:
2344e4480e8SJakub Kicinski                            desc += f" ({spec['doc']})"
2354e4480e8SJakub Kicinski                        self.extack['miss-type'] = desc
2364e4480e8SJakub Kicinski
237e46dd903SDonald Hunter    def cmd(self):
238e46dd903SDonald Hunter        return self.nl_type
239e46dd903SDonald Hunter
2404e4480e8SJakub Kicinski    def __repr__(self):
2414e4480e8SJakub Kicinski        msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n"
2424e4480e8SJakub Kicinski        if self.error:
2434e4480e8SJakub Kicinski            msg += '\terror: ' + str(self.error)
2444e4480e8SJakub Kicinski        if self.extack:
2454e4480e8SJakub Kicinski            msg += '\textack: ' + repr(self.extack)
2464e4480e8SJakub Kicinski        return msg
2474e4480e8SJakub Kicinski
2484e4480e8SJakub Kicinski
2494e4480e8SJakub Kicinskiclass NlMsgs:
2504e4480e8SJakub Kicinski    def __init__(self, data, attr_space=None):
2514e4480e8SJakub Kicinski        self.msgs = []
2524e4480e8SJakub Kicinski
2534e4480e8SJakub Kicinski        offset = 0
2544e4480e8SJakub Kicinski        while offset < len(data):
2554e4480e8SJakub Kicinski            msg = NlMsg(data, offset, attr_space=attr_space)
2564e4480e8SJakub Kicinski            offset += msg.nl_len
2574e4480e8SJakub Kicinski            self.msgs.append(msg)
2584e4480e8SJakub Kicinski
2594e4480e8SJakub Kicinski    def __iter__(self):
2604e4480e8SJakub Kicinski        yield from self.msgs
2614e4480e8SJakub Kicinski
2624e4480e8SJakub Kicinski
2634e4480e8SJakub Kicinskigenl_family_name_to_id = None
2644e4480e8SJakub Kicinski
2654e4480e8SJakub Kicinski
2664e4480e8SJakub Kicinskidef _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None):
2674e4480e8SJakub Kicinski    # we prepend length in _genl_msg_finalize()
2684e4480e8SJakub Kicinski    if seq is None:
2694e4480e8SJakub Kicinski        seq = random.randint(1, 1024)
2704e4480e8SJakub Kicinski    nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
271758d29fbSDonald Hunter    genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0)
2724e4480e8SJakub Kicinski    return nlmsg + genlmsg
2734e4480e8SJakub Kicinski
2744e4480e8SJakub Kicinski
2754e4480e8SJakub Kicinskidef _genl_msg_finalize(msg):
2764e4480e8SJakub Kicinski    return struct.pack("I", len(msg) + 4) + msg
2774e4480e8SJakub Kicinski
2784e4480e8SJakub Kicinski
2794e4480e8SJakub Kicinskidef _genl_load_families():
2804e4480e8SJakub Kicinski    with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock:
2814e4480e8SJakub Kicinski        sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
2824e4480e8SJakub Kicinski
2834e4480e8SJakub Kicinski        msg = _genl_msg(Netlink.GENL_ID_CTRL,
2844e4480e8SJakub Kicinski                        Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP,
2854e4480e8SJakub Kicinski                        Netlink.CTRL_CMD_GETFAMILY, 1)
2864e4480e8SJakub Kicinski        msg = _genl_msg_finalize(msg)
2874e4480e8SJakub Kicinski
2884e4480e8SJakub Kicinski        sock.send(msg, 0)
2894e4480e8SJakub Kicinski
2904e4480e8SJakub Kicinski        global genl_family_name_to_id
2914e4480e8SJakub Kicinski        genl_family_name_to_id = dict()
2924e4480e8SJakub Kicinski
2934e4480e8SJakub Kicinski        while True:
2944e4480e8SJakub Kicinski            reply = sock.recv(128 * 1024)
2954e4480e8SJakub Kicinski            nms = NlMsgs(reply)
2964e4480e8SJakub Kicinski            for nl_msg in nms:
2974e4480e8SJakub Kicinski                if nl_msg.error:
2984e4480e8SJakub Kicinski                    print("Netlink error:", nl_msg.error)
2994e4480e8SJakub Kicinski                    return
3004e4480e8SJakub Kicinski                if nl_msg.done:
3014e4480e8SJakub Kicinski                    return
3024e4480e8SJakub Kicinski
3034e4480e8SJakub Kicinski                gm = GenlMsg(nl_msg)
3044e4480e8SJakub Kicinski                fam = dict()
305fb0a06d4SDonald Hunter                for attr in NlAttrs(gm.raw):
3064e4480e8SJakub Kicinski                    if attr.type == Netlink.CTRL_ATTR_FAMILY_ID:
3077c2435efSDonald Hunter                        fam['id'] = attr.as_scalar('u16')
3084e4480e8SJakub Kicinski                    elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME:
3094e4480e8SJakub Kicinski                        fam['name'] = attr.as_strz()
3104e4480e8SJakub Kicinski                    elif attr.type == Netlink.CTRL_ATTR_MAXATTR:
3117c2435efSDonald Hunter                        fam['maxattr'] = attr.as_scalar('u32')
3124e4480e8SJakub Kicinski                    elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS:
3134e4480e8SJakub Kicinski                        fam['mcast'] = dict()
3144e4480e8SJakub Kicinski                        for entry in NlAttrs(attr.raw):
3154e4480e8SJakub Kicinski                            mcast_name = None
3164e4480e8SJakub Kicinski                            mcast_id = None
3174e4480e8SJakub Kicinski                            for entry_attr in NlAttrs(entry.raw):
3184e4480e8SJakub Kicinski                                if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME:
3194e4480e8SJakub Kicinski                                    mcast_name = entry_attr.as_strz()
3204e4480e8SJakub Kicinski                                elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID:
3217c2435efSDonald Hunter                                    mcast_id = entry_attr.as_scalar('u32')
3224e4480e8SJakub Kicinski                            if mcast_name and mcast_id is not None:
3234e4480e8SJakub Kicinski                                fam['mcast'][mcast_name] = mcast_id
3244e4480e8SJakub Kicinski                if 'name' in fam and 'id' in fam:
3254e4480e8SJakub Kicinski                    genl_family_name_to_id[fam['name']] = fam
3264e4480e8SJakub Kicinski
3274e4480e8SJakub Kicinski
3284e4480e8SJakub Kicinskiclass GenlMsg:
329fb0a06d4SDonald Hunter    def __init__(self, nl_msg):
3304e4480e8SJakub Kicinski        self.nl = nl_msg
331fb0a06d4SDonald Hunter        self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0)
332fb0a06d4SDonald Hunter        self.raw = nl_msg.raw[4:]
3334e4480e8SJakub Kicinski
334e46dd903SDonald Hunter    def cmd(self):
335e46dd903SDonald Hunter        return self.genl_cmd
336e46dd903SDonald Hunter
3374e4480e8SJakub Kicinski    def __repr__(self):
3384e4480e8SJakub Kicinski        msg = repr(self.nl)
3394e4480e8SJakub Kicinski        msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n"
3404e4480e8SJakub Kicinski        for a in self.raw_attrs:
3414e4480e8SJakub Kicinski            msg += '\t\t' + repr(a) + '\n'
3424e4480e8SJakub Kicinski        return msg
3434e4480e8SJakub Kicinski
3444e4480e8SJakub Kicinski
345e46dd903SDonald Hunterclass NetlinkProtocol:
346e46dd903SDonald Hunter    def __init__(self, family_name, proto_num):
3474e4480e8SJakub Kicinski        self.family_name = family_name
348e46dd903SDonald Hunter        self.proto_num = proto_num
349e46dd903SDonald Hunter
350e46dd903SDonald Hunter    def _message(self, nl_type, nl_flags, seq=None):
351e46dd903SDonald Hunter        if seq is None:
352e46dd903SDonald Hunter            seq = random.randint(1, 1024)
353e46dd903SDonald Hunter        nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
354e46dd903SDonald Hunter        return nlmsg
355e46dd903SDonald Hunter
356e46dd903SDonald Hunter    def message(self, flags, command, version, seq=None):
357e46dd903SDonald Hunter        return self._message(command, flags, seq)
358e46dd903SDonald Hunter
359e46dd903SDonald Hunter    def _decode(self, nl_msg):
360e46dd903SDonald Hunter        return nl_msg
361e46dd903SDonald Hunter
362e46dd903SDonald Hunter    def decode(self, ynl, nl_msg):
363e46dd903SDonald Hunter        msg = self._decode(nl_msg)
364e46dd903SDonald Hunter        fixed_header_size = 0
365e46dd903SDonald Hunter        if ynl:
366e46dd903SDonald Hunter            op = ynl.rsp_by_value[msg.cmd()]
367e46dd903SDonald Hunter            fixed_header_size = ynl._fixed_header_size(op)
368e46dd903SDonald Hunter        msg.raw_attrs = NlAttrs(msg.raw[fixed_header_size:])
369e46dd903SDonald Hunter        return msg
370e46dd903SDonald Hunter
371e46dd903SDonald Hunter    def get_mcast_id(self, mcast_name, mcast_groups):
372e46dd903SDonald Hunter        if mcast_name not in mcast_groups:
373e46dd903SDonald Hunter            raise Exception(f'Multicast group "{mcast_name}" not present in the spec')
374e46dd903SDonald Hunter        return mcast_groups[mcast_name].value
375e46dd903SDonald Hunter
376e46dd903SDonald Hunter
377e46dd903SDonald Hunterclass GenlProtocol(NetlinkProtocol):
378e46dd903SDonald Hunter    def __init__(self, family_name):
379e46dd903SDonald Hunter        super().__init__(family_name, Netlink.NETLINK_GENERIC)
3804e4480e8SJakub Kicinski
3814e4480e8SJakub Kicinski        global genl_family_name_to_id
3824e4480e8SJakub Kicinski        if genl_family_name_to_id is None:
3834e4480e8SJakub Kicinski            _genl_load_families()
3844e4480e8SJakub Kicinski
3854e4480e8SJakub Kicinski        self.genl_family = genl_family_name_to_id[family_name]
3864e4480e8SJakub Kicinski        self.family_id = genl_family_name_to_id[family_name]['id']
3874e4480e8SJakub Kicinski
388e46dd903SDonald Hunter    def message(self, flags, command, version, seq=None):
389e46dd903SDonald Hunter        nlmsg = self._message(self.family_id, flags, seq)
390e46dd903SDonald Hunter        genlmsg = struct.pack("BBH", command, version, 0)
391e46dd903SDonald Hunter        return nlmsg + genlmsg
392e46dd903SDonald Hunter
393e46dd903SDonald Hunter    def _decode(self, nl_msg):
394e46dd903SDonald Hunter        return GenlMsg(nl_msg)
395e46dd903SDonald Hunter
396e46dd903SDonald Hunter    def get_mcast_id(self, mcast_name, mcast_groups):
397e46dd903SDonald Hunter        if mcast_name not in self.genl_family['mcast']:
398e46dd903SDonald Hunter            raise Exception(f'Multicast group "{mcast_name}" not present in the family')
399e46dd903SDonald Hunter        return self.genl_family['mcast'][mcast_name]
400e46dd903SDonald Hunter
4014e4480e8SJakub Kicinski
4024e4480e8SJakub Kicinski#
4034e4480e8SJakub Kicinski# YNL implementation details.
4044e4480e8SJakub Kicinski#
4054e4480e8SJakub Kicinski
4064e4480e8SJakub Kicinski
40730a5c6c8SJakub Kicinskiclass YnlFamily(SpecFamily):
4084e4480e8SJakub Kicinski    def __init__(self, def_path, schema=None):
40930a5c6c8SJakub Kicinski        super().__init__(def_path, schema)
41030a5c6c8SJakub Kicinski
4114e4480e8SJakub Kicinski        self.include_raw = False
4124e4480e8SJakub Kicinski
413e46dd903SDonald Hunter        try:
414e46dd903SDonald Hunter            if self.proto == "netlink-raw":
415e46dd903SDonald Hunter                self.nlproto = NetlinkProtocol(self.yaml['name'],
416e46dd903SDonald Hunter                                               self.yaml['protonum'])
417e46dd903SDonald Hunter            else:
418e46dd903SDonald Hunter                self.nlproto = GenlProtocol(self.yaml['name'])
419e46dd903SDonald Hunter        except KeyError:
420e46dd903SDonald Hunter            raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel")
421e46dd903SDonald Hunter
422e46dd903SDonald Hunter        self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num)
4234e4480e8SJakub Kicinski        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
4244e4480e8SJakub Kicinski        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1)
425e46dd903SDonald Hunter        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1)
4264e4480e8SJakub Kicinski
4274e4480e8SJakub Kicinski        self.async_msg_ids = set()
4284e4480e8SJakub Kicinski        self.async_msg_queue = []
4294e4480e8SJakub Kicinski
43030a5c6c8SJakub Kicinski        for msg in self.msgs.values():
43130a5c6c8SJakub Kicinski            if msg.is_async:
432fd0616d3SJakub Kicinski                self.async_msg_ids.add(msg.rsp_value)
4334e4480e8SJakub Kicinski
43430a5c6c8SJakub Kicinski        for op_name, op in self.ops.items():
43530a5c6c8SJakub Kicinski            bound_f = functools.partial(self._op, op_name)
43630a5c6c8SJakub Kicinski            setattr(self, op.ident_name, bound_f)
4374e4480e8SJakub Kicinski
4384e4480e8SJakub Kicinski
4394e4480e8SJakub Kicinski    def ntf_subscribe(self, mcast_name):
440e46dd903SDonald Hunter        mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups)
4414e4480e8SJakub Kicinski        self.sock.bind((0, 0))
4424e4480e8SJakub Kicinski        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP,
443e46dd903SDonald Hunter                             mcast_id)
4444e4480e8SJakub Kicinski
4454e4480e8SJakub Kicinski    def _add_attr(self, space, name, value):
4467582113cSJakub Kicinski        try:
44730a5c6c8SJakub Kicinski            attr = self.attr_sets[space][name]
4487582113cSJakub Kicinski        except KeyError:
4497582113cSJakub Kicinski            raise Exception(f"Space '{space}' has no attribute '{name}'")
45030a5c6c8SJakub Kicinski        nl_type = attr.value
4514e4480e8SJakub Kicinski        if attr["type"] == 'nest':
4524e4480e8SJakub Kicinski            nl_type |= Netlink.NLA_F_NESTED
4534e4480e8SJakub Kicinski            attr_payload = b''
4544e4480e8SJakub Kicinski            for subname, subvalue in value.items():
4554e4480e8SJakub Kicinski                attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue)
45619b64b48SJakub Kicinski        elif attr["type"] == 'flag':
45719b64b48SJakub Kicinski            attr_payload = b''
4584e4480e8SJakub Kicinski        elif attr["type"] == 'string':
4594e4480e8SJakub Kicinski            attr_payload = str(value).encode('ascii') + b'\x00'
4604e4480e8SJakub Kicinski        elif attr["type"] == 'binary':
461649bde90SJakub Kicinski            if isinstance(value, bytes):
462649bde90SJakub Kicinski                attr_payload = value
463649bde90SJakub Kicinski            elif isinstance(value, str):
464d8eea68dSDonald Hunter                attr_payload = bytes.fromhex(value)
465649bde90SJakub Kicinski            else:
466649bde90SJakub Kicinski                raise Exception(f'Unknown type for binary attribute, value: {value}')
4677c2435efSDonald Hunter        elif attr['type'] in NlAttr.type_formats:
4687c2435efSDonald Hunter            format = NlAttr.get_format(attr['type'], attr.byte_order)
4697c2435efSDonald Hunter            attr_payload = format.pack(int(value))
4704e4480e8SJakub Kicinski        else:
4714e4480e8SJakub Kicinski            raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}')
4724e4480e8SJakub Kicinski
4734e4480e8SJakub Kicinski        pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4)
4744e4480e8SJakub Kicinski        return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad
4754e4480e8SJakub Kicinski
476df15c15eSArkadiusz Kubalewski    def _decode_enum(self, raw, attr_spec):
477c311aaa7SJakub Kicinski        enum = self.consts[attr_spec['enum']]
4784e4480e8SJakub Kicinski        if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']:
479d7ddf5f4SArkadiusz Kubalewski            i = 0
4804e4480e8SJakub Kicinski            value = set()
4814e4480e8SJakub Kicinski            while raw:
4824e4480e8SJakub Kicinski                if raw & 1:
483c311aaa7SJakub Kicinski                    value.add(enum.entries_by_val[i].name)
4844e4480e8SJakub Kicinski                raw >>= 1
4854e4480e8SJakub Kicinski                i += 1
4864e4480e8SJakub Kicinski        else:
487d7ddf5f4SArkadiusz Kubalewski            value = enum.entries_by_val[raw].name
488df15c15eSArkadiusz Kubalewski        return value
4894e4480e8SJakub Kicinski
490b423c3c8SDonald Hunter    def _decode_binary(self, attr, attr_spec):
49126071913SDonald Hunter        if attr_spec.struct_name:
492313a7a80SDonald Hunter            members = self.consts[attr_spec.struct_name]
493313a7a80SDonald Hunter            decoded = attr.as_struct(members)
494313a7a80SDonald Hunter            for m in members:
495313a7a80SDonald Hunter                if m.enum:
496df15c15eSArkadiusz Kubalewski                    decoded[m.name] = self._decode_enum(decoded[m.name], m)
49726071913SDonald Hunter        elif attr_spec.sub_type:
498b423c3c8SDonald Hunter            decoded = attr.as_c_array(attr_spec.sub_type)
499b423c3c8SDonald Hunter        else:
500b423c3c8SDonald Hunter            decoded = attr.as_bin()
501d8eea68dSDonald Hunter            if attr_spec.display_hint:
502d8eea68dSDonald Hunter                decoded = NlAttr.formatted_string(decoded, attr_spec.display_hint)
503b423c3c8SDonald Hunter        return decoded
504b423c3c8SDonald Hunter
5050493e56dSDonald Hunter    def _decode_array_nest(self, attr, attr_spec):
5060493e56dSDonald Hunter        decoded = []
5070493e56dSDonald Hunter        offset = 0
5080493e56dSDonald Hunter        while offset < len(attr.raw):
5090493e56dSDonald Hunter            item = NlAttr(attr.raw, offset)
5100493e56dSDonald Hunter            offset += item.full_len
5110493e56dSDonald Hunter
5120493e56dSDonald Hunter            subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes'])
5130493e56dSDonald Hunter            decoded.append({ item.type: subattrs })
5140493e56dSDonald Hunter        return decoded
5150493e56dSDonald Hunter
5164e4480e8SJakub Kicinski    def _decode(self, attrs, space):
51730a5c6c8SJakub Kicinski        attr_space = self.attr_sets[space]
5184e4480e8SJakub Kicinski        rsp = dict()
5194e4480e8SJakub Kicinski        for attr in attrs:
5207582113cSJakub Kicinski            try:
52130a5c6c8SJakub Kicinski                attr_spec = attr_space.attrs_by_val[attr.type]
5227582113cSJakub Kicinski            except KeyError:
5237582113cSJakub Kicinski                raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'")
5244e4480e8SJakub Kicinski            if attr_spec["type"] == 'nest':
5254e4480e8SJakub Kicinski                subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'])
52690256f3fSJakub Kicinski                decoded = subdict
5274e4480e8SJakub Kicinski            elif attr_spec["type"] == 'string':
52890256f3fSJakub Kicinski                decoded = attr.as_strz()
5294e4480e8SJakub Kicinski            elif attr_spec["type"] == 'binary':
530b423c3c8SDonald Hunter                decoded = self._decode_binary(attr, attr_spec)
53119b64b48SJakub Kicinski            elif attr_spec["type"] == 'flag':
53290256f3fSJakub Kicinski                decoded = True
5337c2435efSDonald Hunter            elif attr_spec["type"] in NlAttr.type_formats:
5347c2435efSDonald Hunter                decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order)
5350493e56dSDonald Hunter            elif attr_spec["type"] == 'array-nest':
5360493e56dSDonald Hunter                decoded = self._decode_array_nest(attr, attr_spec)
5374e4480e8SJakub Kicinski            else:
5387c2435efSDonald Hunter                raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}')
5394e4480e8SJakub Kicinski
540df15c15eSArkadiusz Kubalewski            if 'enum' in attr_spec:
541df15c15eSArkadiusz Kubalewski                decoded = self._decode_enum(decoded, attr_spec)
542df15c15eSArkadiusz Kubalewski
54390256f3fSJakub Kicinski            if not attr_spec.is_multi:
54490256f3fSJakub Kicinski                rsp[attr_spec['name']] = decoded
54590256f3fSJakub Kicinski            elif attr_spec.name in rsp:
54690256f3fSJakub Kicinski                rsp[attr_spec.name].append(decoded)
54790256f3fSJakub Kicinski            else:
54890256f3fSJakub Kicinski                rsp[attr_spec.name] = [decoded]
54990256f3fSJakub Kicinski
5504e4480e8SJakub Kicinski        return rsp
5514e4480e8SJakub Kicinski
5524cd2796fSJakub Kicinski    def _decode_extack_path(self, attrs, attr_set, offset, target):
5534cd2796fSJakub Kicinski        for attr in attrs:
5547582113cSJakub Kicinski            try:
5554cd2796fSJakub Kicinski                attr_spec = attr_set.attrs_by_val[attr.type]
5567582113cSJakub Kicinski            except KeyError:
5577582113cSJakub Kicinski                raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'")
5584cd2796fSJakub Kicinski            if offset > target:
5594cd2796fSJakub Kicinski                break
5604cd2796fSJakub Kicinski            if offset == target:
5614cd2796fSJakub Kicinski                return '.' + attr_spec.name
5624cd2796fSJakub Kicinski
5634cd2796fSJakub Kicinski            if offset + attr.full_len <= target:
5644cd2796fSJakub Kicinski                offset += attr.full_len
5654cd2796fSJakub Kicinski                continue
5664cd2796fSJakub Kicinski            if attr_spec['type'] != 'nest':
5674cd2796fSJakub Kicinski                raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack")
5684cd2796fSJakub Kicinski            offset += 4
5694cd2796fSJakub Kicinski            subpath = self._decode_extack_path(NlAttrs(attr.raw),
5704cd2796fSJakub Kicinski                                               self.attr_sets[attr_spec['nested-attributes']],
5714cd2796fSJakub Kicinski                                               offset, target)
5724cd2796fSJakub Kicinski            if subpath is None:
5734cd2796fSJakub Kicinski                return None
5744cd2796fSJakub Kicinski            return '.' + attr_spec.name + subpath
5754cd2796fSJakub Kicinski
5764cd2796fSJakub Kicinski        return None
5774cd2796fSJakub Kicinski
578fb0a06d4SDonald Hunter    def _decode_extack(self, request, op, extack):
5794cd2796fSJakub Kicinski        if 'bad-attr-offs' not in extack:
5804cd2796fSJakub Kicinski            return
5814cd2796fSJakub Kicinski
582e46dd903SDonald Hunter        msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set))
583e46dd903SDonald Hunter        offset = 20 + self._fixed_header_size(op)
584e46dd903SDonald Hunter        path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset,
585fb0a06d4SDonald Hunter                                        extack['bad-attr-offs'])
5864cd2796fSJakub Kicinski        if path:
5874cd2796fSJakub Kicinski            del extack['bad-attr-offs']
5884cd2796fSJakub Kicinski            extack['bad-attr'] = path
5894cd2796fSJakub Kicinski
590fb0a06d4SDonald Hunter    def _fixed_header_size(self, op):
591fb0a06d4SDonald Hunter        if op.fixed_header:
592fb0a06d4SDonald Hunter            fixed_header_members = self.consts[op.fixed_header].members
593fb0a06d4SDonald Hunter            size = 0
594fb0a06d4SDonald Hunter            for m in fixed_header_members:
595fb0a06d4SDonald Hunter                format = NlAttr.get_format(m.type, m.byte_order)
596fb0a06d4SDonald Hunter                size += format.size
597fb0a06d4SDonald Hunter            return size
598fb0a06d4SDonald Hunter        else:
599fb0a06d4SDonald Hunter            return 0
600fb0a06d4SDonald Hunter
601fb0a06d4SDonald Hunter    def _decode_fixed_header(self, msg, name):
602fb0a06d4SDonald Hunter        fixed_header_members = self.consts[name].members
603fb0a06d4SDonald Hunter        fixed_header_attrs = dict()
604fb0a06d4SDonald Hunter        offset = 0
605fb0a06d4SDonald Hunter        for m in fixed_header_members:
606fb0a06d4SDonald Hunter            format = NlAttr.get_format(m.type, m.byte_order)
607fb0a06d4SDonald Hunter            [ value ] = format.unpack_from(msg.raw, offset)
608fb0a06d4SDonald Hunter            offset += format.size
609fb0a06d4SDonald Hunter            if m.enum:
610fb0a06d4SDonald Hunter                value = self._decode_enum(value, m)
611fb0a06d4SDonald Hunter            fixed_header_attrs[m.name] = value
612fb0a06d4SDonald Hunter        return fixed_header_attrs
613fb0a06d4SDonald Hunter
614e46dd903SDonald Hunter    def handle_ntf(self, decoded):
6154e4480e8SJakub Kicinski        msg = dict()
6164e4480e8SJakub Kicinski        if self.include_raw:
617e46dd903SDonald Hunter            msg['raw'] = decoded
618e46dd903SDonald Hunter        op = self.rsp_by_value[decoded.cmd()]
619e46dd903SDonald Hunter        attrs = self._decode(decoded.raw_attrs, op.attr_set.name)
620e46dd903SDonald Hunter        if op.fixed_header:
621e46dd903SDonald Hunter            attrs.update(self._decode_fixed_header(decoded, op.fixed_header))
622e46dd903SDonald Hunter
6234e4480e8SJakub Kicinski        msg['name'] = op['name']
624e46dd903SDonald Hunter        msg['msg'] = attrs
6254e4480e8SJakub Kicinski        self.async_msg_queue.append(msg)
6264e4480e8SJakub Kicinski
6274e4480e8SJakub Kicinski    def check_ntf(self):
6284e4480e8SJakub Kicinski        while True:
6294e4480e8SJakub Kicinski            try:
6304e4480e8SJakub Kicinski                reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT)
6314e4480e8SJakub Kicinski            except BlockingIOError:
6324e4480e8SJakub Kicinski                return
6334e4480e8SJakub Kicinski
6344e4480e8SJakub Kicinski            nms = NlMsgs(reply)
6354e4480e8SJakub Kicinski            for nl_msg in nms:
6364e4480e8SJakub Kicinski                if nl_msg.error:
6374e4480e8SJakub Kicinski                    print("Netlink error in ntf!?", os.strerror(-nl_msg.error))
6384e4480e8SJakub Kicinski                    print(nl_msg)
6394e4480e8SJakub Kicinski                    continue
6404e4480e8SJakub Kicinski                if nl_msg.done:
6414e4480e8SJakub Kicinski                    print("Netlink done while checking for ntf!?")
6424e4480e8SJakub Kicinski                    continue
6434e4480e8SJakub Kicinski
644e46dd903SDonald Hunter                decoded = self.nlproto.decode(self, nl_msg)
645e46dd903SDonald Hunter                if decoded.cmd() not in self.async_msg_ids:
646e46dd903SDonald Hunter                    print("Unexpected msg id done while checking for ntf", decoded)
6474e4480e8SJakub Kicinski                    continue
6484e4480e8SJakub Kicinski
649e46dd903SDonald Hunter                self.handle_ntf(decoded)
6504e4480e8SJakub Kicinski
651f3d07b02SStanislav Fomichev    def operation_do_attributes(self, name):
652f3d07b02SStanislav Fomichev      """
653f3d07b02SStanislav Fomichev      For a given operation name, find and return a supported
654f3d07b02SStanislav Fomichev      set of attributes (as a dict).
655f3d07b02SStanislav Fomichev      """
656f3d07b02SStanislav Fomichev      op = self.find_operation(name)
657f3d07b02SStanislav Fomichev      if not op:
658f3d07b02SStanislav Fomichev        return None
659f3d07b02SStanislav Fomichev
660f3d07b02SStanislav Fomichev      return op['do']['request']['attributes'].copy()
661f3d07b02SStanislav Fomichev
6621768d8a7SDonald Hunter    def _op(self, method, vals, flags, dump=False):
66330a5c6c8SJakub Kicinski        op = self.ops[method]
6644e4480e8SJakub Kicinski
6654e4480e8SJakub Kicinski        nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK
6661768d8a7SDonald Hunter        for flag in flags or []:
6671768d8a7SDonald Hunter            nl_flags |= flag
6684e4480e8SJakub Kicinski        if dump:
6694e4480e8SJakub Kicinski            nl_flags |= Netlink.NLM_F_DUMP
6704e4480e8SJakub Kicinski
6714e4480e8SJakub Kicinski        req_seq = random.randint(1024, 65535)
672e46dd903SDonald Hunter        msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq)
673f036d936SDonald Hunter        fixed_header_members = []
674f036d936SDonald Hunter        if op.fixed_header:
675f036d936SDonald Hunter            fixed_header_members = self.consts[op.fixed_header].members
676f036d936SDonald Hunter            for m in fixed_header_members:
6775ac18889SDonald Hunter                value = vals.pop(m.name) if m.name in vals else 0
678bddd2e56SDonald Hunter                format = NlAttr.get_format(m.type, m.byte_order)
6797c2435efSDonald Hunter                msg += format.pack(value)
6804e4480e8SJakub Kicinski        for name, value in vals.items():
68130a5c6c8SJakub Kicinski            msg += self._add_attr(op.attr_set.name, name, value)
6824e4480e8SJakub Kicinski        msg = _genl_msg_finalize(msg)
6834e4480e8SJakub Kicinski
6844e4480e8SJakub Kicinski        self.sock.send(msg, 0)
6854e4480e8SJakub Kicinski
6864e4480e8SJakub Kicinski        done = False
6874e4480e8SJakub Kicinski        rsp = []
6884e4480e8SJakub Kicinski        while not done:
6894e4480e8SJakub Kicinski            reply = self.sock.recv(128 * 1024)
69030a5c6c8SJakub Kicinski            nms = NlMsgs(reply, attr_space=op.attr_set)
6914e4480e8SJakub Kicinski            for nl_msg in nms:
6924cd2796fSJakub Kicinski                if nl_msg.extack:
693fb0a06d4SDonald Hunter                    self._decode_extack(msg, op, nl_msg.extack)
6944cd2796fSJakub Kicinski
6954e4480e8SJakub Kicinski                if nl_msg.error:
69648993e22SStanislav Fomichev                    raise NlError(nl_msg)
6974e4480e8SJakub Kicinski                if nl_msg.done:
6984cd2796fSJakub Kicinski                    if nl_msg.extack:
6994cd2796fSJakub Kicinski                        print("Netlink warning:")
7004cd2796fSJakub Kicinski                        print(nl_msg)
7014e4480e8SJakub Kicinski                    done = True
7024e4480e8SJakub Kicinski                    break
7034e4480e8SJakub Kicinski
704e46dd903SDonald Hunter                decoded = self.nlproto.decode(self, nl_msg)
705e46dd903SDonald Hunter
7064e4480e8SJakub Kicinski                # Check if this is a reply to our request
707e46dd903SDonald Hunter                if nl_msg.nl_seq != req_seq or decoded.cmd() != op.rsp_value:
708e46dd903SDonald Hunter                    if decoded.cmd() in self.async_msg_ids:
709e46dd903SDonald Hunter                        self.handle_ntf(decoded)
7104e4480e8SJakub Kicinski                        continue
7114e4480e8SJakub Kicinski                    else:
712e46dd903SDonald Hunter                        print('Unexpected message: ' + repr(decoded))
7134e4480e8SJakub Kicinski                        continue
7144e4480e8SJakub Kicinski
715e46dd903SDonald Hunter                rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)
716fb0a06d4SDonald Hunter                if op.fixed_header:
717e46dd903SDonald Hunter                    rsp_msg.update(self._decode_fixed_header(decoded, op.fixed_header))
718081e8df6SJakub Kicinski                rsp.append(rsp_msg)
7194e4480e8SJakub Kicinski
7204e4480e8SJakub Kicinski        if not rsp:
7214e4480e8SJakub Kicinski            return None
7224e4480e8SJakub Kicinski        if not dump and len(rsp) == 1:
7234e4480e8SJakub Kicinski            return rsp[0]
7244e4480e8SJakub Kicinski        return rsp
7258dfec0a8SJakub Kicinski
7261768d8a7SDonald Hunter    def do(self, method, vals, flags):
7271768d8a7SDonald Hunter        return self._op(method, vals, flags)
7288dfec0a8SJakub Kicinski
7298dfec0a8SJakub Kicinski    def dump(self, method, vals):
7301768d8a7SDonald Hunter        return self._op(method, vals, [], dump=True)
731