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