xref: /openbmc/linux/tools/net/ynl/lib/ynl.py (revision a10c3d5f)
1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
3from collections import namedtuple
4import functools
5import os
6import random
7import socket
8import struct
9from struct import Struct
10import yaml
11import ipaddress
12import uuid
13
14from .nlspec import SpecFamily
15
16#
17# Generic Netlink code which should really be in some library, but I can't quickly find one.
18#
19
20
21class Netlink:
22    # Netlink socket
23    SOL_NETLINK = 270
24
25    NETLINK_ADD_MEMBERSHIP = 1
26    NETLINK_CAP_ACK = 10
27    NETLINK_EXT_ACK = 11
28    NETLINK_GET_STRICT_CHK = 12
29
30    # Netlink message
31    NLMSG_ERROR = 2
32    NLMSG_DONE = 3
33
34    NLM_F_REQUEST = 1
35    NLM_F_ACK = 4
36    NLM_F_ROOT = 0x100
37    NLM_F_MATCH = 0x200
38
39    NLM_F_REPLACE = 0x100
40    NLM_F_EXCL = 0x200
41    NLM_F_CREATE = 0x400
42    NLM_F_APPEND = 0x800
43
44    NLM_F_CAPPED = 0x100
45    NLM_F_ACK_TLVS = 0x200
46
47    NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
48
49    NLA_F_NESTED = 0x8000
50    NLA_F_NET_BYTEORDER = 0x4000
51
52    NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER
53
54    # Genetlink defines
55    NETLINK_GENERIC = 16
56
57    GENL_ID_CTRL = 0x10
58
59    # nlctrl
60    CTRL_CMD_GETFAMILY = 3
61
62    CTRL_ATTR_FAMILY_ID = 1
63    CTRL_ATTR_FAMILY_NAME = 2
64    CTRL_ATTR_MAXATTR = 5
65    CTRL_ATTR_MCAST_GROUPS = 7
66
67    CTRL_ATTR_MCAST_GRP_NAME = 1
68    CTRL_ATTR_MCAST_GRP_ID = 2
69
70    # Extack types
71    NLMSGERR_ATTR_MSG = 1
72    NLMSGERR_ATTR_OFFS = 2
73    NLMSGERR_ATTR_COOKIE = 3
74    NLMSGERR_ATTR_POLICY = 4
75    NLMSGERR_ATTR_MISS_TYPE = 5
76    NLMSGERR_ATTR_MISS_NEST = 6
77
78
79class NlError(Exception):
80  def __init__(self, nl_msg):
81    self.nl_msg = nl_msg
82
83  def __str__(self):
84    return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}"
85
86
87class NlAttr:
88    ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little'])
89    type_formats = {
90        'u8' : ScalarFormat(Struct('B'), Struct("B"),  Struct("B")),
91        's8' : ScalarFormat(Struct('b'), Struct("b"),  Struct("b")),
92        'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")),
93        's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")),
94        'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")),
95        's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")),
96        'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")),
97        's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q"))
98    }
99
100    def __init__(self, raw, offset):
101        self._len, self._type = struct.unpack("HH", raw[offset:offset + 4])
102        self.type = self._type & ~Netlink.NLA_TYPE_MASK
103        self.payload_len = self._len
104        self.full_len = (self.payload_len + 3) & ~3
105        self.raw = raw[offset + 4:offset + self.payload_len]
106
107    @classmethod
108    def get_format(cls, attr_type, byte_order=None):
109        format = cls.type_formats[attr_type]
110        if byte_order:
111            return format.big if byte_order == "big-endian" \
112                else format.little
113        return format.native
114
115    @classmethod
116    def formatted_string(cls, raw, display_hint):
117        if display_hint == 'mac':
118            formatted = ':'.join('%02x' % b for b in raw)
119        elif display_hint == 'hex':
120            formatted = bytes.hex(raw, ' ')
121        elif display_hint in [ 'ipv4', 'ipv6' ]:
122            formatted = format(ipaddress.ip_address(raw))
123        elif display_hint == 'uuid':
124            formatted = str(uuid.UUID(bytes=raw))
125        else:
126            formatted = raw
127        return formatted
128
129    def as_scalar(self, attr_type, byte_order=None):
130        format = self.get_format(attr_type, byte_order)
131        return format.unpack(self.raw)[0]
132
133    def as_strz(self):
134        return self.raw.decode('ascii')[:-1]
135
136    def as_bin(self):
137        return self.raw
138
139    def as_c_array(self, type):
140        format = self.get_format(type)
141        return [ x[0] for x in format.iter_unpack(self.raw) ]
142
143    def as_struct(self, members):
144        value = dict()
145        offset = 0
146        for m in members:
147            # TODO: handle non-scalar members
148            if m.type == 'binary':
149                decoded = self.raw[offset:offset+m['len']]
150                offset += m['len']
151            elif m.type in NlAttr.type_formats:
152                format = self.get_format(m.type, m.byte_order)
153                [ decoded ] = format.unpack_from(self.raw, offset)
154                offset += format.size
155            if m.display_hint:
156                decoded = self.formatted_string(decoded, m.display_hint)
157            value[m.name] = decoded
158        return value
159
160    def __repr__(self):
161        return f"[type:{self.type} len:{self._len}] {self.raw}"
162
163
164class NlAttrs:
165    def __init__(self, msg):
166        self.attrs = []
167
168        offset = 0
169        while offset < len(msg):
170            attr = NlAttr(msg, offset)
171            offset += attr.full_len
172            self.attrs.append(attr)
173
174    def __iter__(self):
175        yield from self.attrs
176
177    def __repr__(self):
178        msg = ''
179        for a in self.attrs:
180            if msg:
181                msg += '\n'
182            msg += repr(a)
183        return msg
184
185
186class NlMsg:
187    def __init__(self, msg, offset, attr_space=None):
188        self.hdr = msg[offset:offset + 16]
189
190        self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \
191            struct.unpack("IHHII", self.hdr)
192
193        self.raw = msg[offset + 16:offset + self.nl_len]
194
195        self.error = 0
196        self.done = 0
197
198        extack_off = None
199        if self.nl_type == Netlink.NLMSG_ERROR:
200            self.error = struct.unpack("i", self.raw[0:4])[0]
201            self.done = 1
202            extack_off = 20
203        elif self.nl_type == Netlink.NLMSG_DONE:
204            self.error = struct.unpack("i", self.raw[0:4])[0]
205            self.done = 1
206            extack_off = 4
207
208        self.extack = None
209        if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off:
210            self.extack = dict()
211            extack_attrs = NlAttrs(self.raw[extack_off:])
212            for extack in extack_attrs:
213                if extack.type == Netlink.NLMSGERR_ATTR_MSG:
214                    self.extack['msg'] = extack.as_strz()
215                elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE:
216                    self.extack['miss-type'] = extack.as_scalar('u32')
217                elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST:
218                    self.extack['miss-nest'] = extack.as_scalar('u32')
219                elif extack.type == Netlink.NLMSGERR_ATTR_OFFS:
220                    self.extack['bad-attr-offs'] = extack.as_scalar('u32')
221                else:
222                    if 'unknown' not in self.extack:
223                        self.extack['unknown'] = []
224                    self.extack['unknown'].append(extack)
225
226            if attr_space:
227                # We don't have the ability to parse nests yet, so only do global
228                if 'miss-type' in self.extack and 'miss-nest' not in self.extack:
229                    miss_type = self.extack['miss-type']
230                    if miss_type in attr_space.attrs_by_val:
231                        spec = attr_space.attrs_by_val[miss_type]
232                        desc = spec['name']
233                        if 'doc' in spec:
234                            desc += f" ({spec['doc']})"
235                        self.extack['miss-type'] = desc
236
237    def cmd(self):
238        return self.nl_type
239
240    def __repr__(self):
241        msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n"
242        if self.error:
243            msg += '\terror: ' + str(self.error)
244        if self.extack:
245            msg += '\textack: ' + repr(self.extack)
246        return msg
247
248
249class NlMsgs:
250    def __init__(self, data, attr_space=None):
251        self.msgs = []
252
253        offset = 0
254        while offset < len(data):
255            msg = NlMsg(data, offset, attr_space=attr_space)
256            offset += msg.nl_len
257            self.msgs.append(msg)
258
259    def __iter__(self):
260        yield from self.msgs
261
262
263genl_family_name_to_id = None
264
265
266def _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None):
267    # we prepend length in _genl_msg_finalize()
268    if seq is None:
269        seq = random.randint(1, 1024)
270    nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
271    genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0)
272    return nlmsg + genlmsg
273
274
275def _genl_msg_finalize(msg):
276    return struct.pack("I", len(msg) + 4) + msg
277
278
279def _genl_load_families():
280    with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock:
281        sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
282
283        msg = _genl_msg(Netlink.GENL_ID_CTRL,
284                        Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP,
285                        Netlink.CTRL_CMD_GETFAMILY, 1)
286        msg = _genl_msg_finalize(msg)
287
288        sock.send(msg, 0)
289
290        global genl_family_name_to_id
291        genl_family_name_to_id = dict()
292
293        while True:
294            reply = sock.recv(128 * 1024)
295            nms = NlMsgs(reply)
296            for nl_msg in nms:
297                if nl_msg.error:
298                    print("Netlink error:", nl_msg.error)
299                    return
300                if nl_msg.done:
301                    return
302
303                gm = GenlMsg(nl_msg)
304                fam = dict()
305                for attr in NlAttrs(gm.raw):
306                    if attr.type == Netlink.CTRL_ATTR_FAMILY_ID:
307                        fam['id'] = attr.as_scalar('u16')
308                    elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME:
309                        fam['name'] = attr.as_strz()
310                    elif attr.type == Netlink.CTRL_ATTR_MAXATTR:
311                        fam['maxattr'] = attr.as_scalar('u32')
312                    elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS:
313                        fam['mcast'] = dict()
314                        for entry in NlAttrs(attr.raw):
315                            mcast_name = None
316                            mcast_id = None
317                            for entry_attr in NlAttrs(entry.raw):
318                                if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME:
319                                    mcast_name = entry_attr.as_strz()
320                                elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID:
321                                    mcast_id = entry_attr.as_scalar('u32')
322                            if mcast_name and mcast_id is not None:
323                                fam['mcast'][mcast_name] = mcast_id
324                if 'name' in fam and 'id' in fam:
325                    genl_family_name_to_id[fam['name']] = fam
326
327
328class GenlMsg:
329    def __init__(self, nl_msg):
330        self.nl = nl_msg
331        self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0)
332        self.raw = nl_msg.raw[4:]
333
334    def cmd(self):
335        return self.genl_cmd
336
337    def __repr__(self):
338        msg = repr(self.nl)
339        msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n"
340        for a in self.raw_attrs:
341            msg += '\t\t' + repr(a) + '\n'
342        return msg
343
344
345class NetlinkProtocol:
346    def __init__(self, family_name, proto_num):
347        self.family_name = family_name
348        self.proto_num = proto_num
349
350    def _message(self, nl_type, nl_flags, seq=None):
351        if seq is None:
352            seq = random.randint(1, 1024)
353        nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
354        return nlmsg
355
356    def message(self, flags, command, version, seq=None):
357        return self._message(command, flags, seq)
358
359    def _decode(self, nl_msg):
360        return nl_msg
361
362    def decode(self, ynl, nl_msg):
363        msg = self._decode(nl_msg)
364        fixed_header_size = 0
365        if ynl:
366            op = ynl.rsp_by_value[msg.cmd()]
367            fixed_header_size = ynl._fixed_header_size(op)
368        msg.raw_attrs = NlAttrs(msg.raw[fixed_header_size:])
369        return msg
370
371    def get_mcast_id(self, mcast_name, mcast_groups):
372        if mcast_name not in mcast_groups:
373            raise Exception(f'Multicast group "{mcast_name}" not present in the spec')
374        return mcast_groups[mcast_name].value
375
376
377class GenlProtocol(NetlinkProtocol):
378    def __init__(self, family_name):
379        super().__init__(family_name, Netlink.NETLINK_GENERIC)
380
381        global genl_family_name_to_id
382        if genl_family_name_to_id is None:
383            _genl_load_families()
384
385        self.genl_family = genl_family_name_to_id[family_name]
386        self.family_id = genl_family_name_to_id[family_name]['id']
387
388    def message(self, flags, command, version, seq=None):
389        nlmsg = self._message(self.family_id, flags, seq)
390        genlmsg = struct.pack("BBH", command, version, 0)
391        return nlmsg + genlmsg
392
393    def _decode(self, nl_msg):
394        return GenlMsg(nl_msg)
395
396    def get_mcast_id(self, mcast_name, mcast_groups):
397        if mcast_name not in self.genl_family['mcast']:
398            raise Exception(f'Multicast group "{mcast_name}" not present in the family')
399        return self.genl_family['mcast'][mcast_name]
400
401
402#
403# YNL implementation details.
404#
405
406
407class YnlFamily(SpecFamily):
408    def __init__(self, def_path, schema=None):
409        super().__init__(def_path, schema)
410
411        self.include_raw = False
412
413        try:
414            if self.proto == "netlink-raw":
415                self.nlproto = NetlinkProtocol(self.yaml['name'],
416                                               self.yaml['protonum'])
417            else:
418                self.nlproto = GenlProtocol(self.yaml['name'])
419        except KeyError:
420            raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel")
421
422        self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num)
423        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
424        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1)
425        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1)
426
427        self.async_msg_ids = set()
428        self.async_msg_queue = []
429
430        for msg in self.msgs.values():
431            if msg.is_async:
432                self.async_msg_ids.add(msg.rsp_value)
433
434        for op_name, op in self.ops.items():
435            bound_f = functools.partial(self._op, op_name)
436            setattr(self, op.ident_name, bound_f)
437
438
439    def ntf_subscribe(self, mcast_name):
440        mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups)
441        self.sock.bind((0, 0))
442        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP,
443                             mcast_id)
444
445    def _add_attr(self, space, name, value):
446        try:
447            attr = self.attr_sets[space][name]
448        except KeyError:
449            raise Exception(f"Space '{space}' has no attribute '{name}'")
450        nl_type = attr.value
451        if attr["type"] == 'nest':
452            nl_type |= Netlink.NLA_F_NESTED
453            attr_payload = b''
454            for subname, subvalue in value.items():
455                attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue)
456        elif attr["type"] == 'flag':
457            attr_payload = b''
458        elif attr["type"] == 'string':
459            attr_payload = str(value).encode('ascii') + b'\x00'
460        elif attr["type"] == 'binary':
461            if isinstance(value, bytes):
462                attr_payload = value
463            elif isinstance(value, str):
464                attr_payload = bytes.fromhex(value)
465            else:
466                raise Exception(f'Unknown type for binary attribute, value: {value}')
467        elif attr['type'] in NlAttr.type_formats:
468            format = NlAttr.get_format(attr['type'], attr.byte_order)
469            attr_payload = format.pack(int(value))
470        else:
471            raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}')
472
473        pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4)
474        return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad
475
476    def _decode_enum(self, raw, attr_spec):
477        enum = self.consts[attr_spec['enum']]
478        if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']:
479            i = 0
480            value = set()
481            while raw:
482                if raw & 1:
483                    value.add(enum.entries_by_val[i].name)
484                raw >>= 1
485                i += 1
486        else:
487            value = enum.entries_by_val[raw].name
488        return value
489
490    def _decode_binary(self, attr, attr_spec):
491        if attr_spec.struct_name:
492            members = self.consts[attr_spec.struct_name]
493            decoded = attr.as_struct(members)
494            for m in members:
495                if m.enum:
496                    decoded[m.name] = self._decode_enum(decoded[m.name], m)
497        elif attr_spec.sub_type:
498            decoded = attr.as_c_array(attr_spec.sub_type)
499        else:
500            decoded = attr.as_bin()
501            if attr_spec.display_hint:
502                decoded = NlAttr.formatted_string(decoded, attr_spec.display_hint)
503        return decoded
504
505    def _decode_array_nest(self, attr, attr_spec):
506        decoded = []
507        offset = 0
508        while offset < len(attr.raw):
509            item = NlAttr(attr.raw, offset)
510            offset += item.full_len
511
512            subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes'])
513            decoded.append({ item.type: subattrs })
514        return decoded
515
516    def _decode(self, attrs, space):
517        attr_space = self.attr_sets[space]
518        rsp = dict()
519        for attr in attrs:
520            try:
521                attr_spec = attr_space.attrs_by_val[attr.type]
522            except KeyError:
523                raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'")
524            if attr_spec["type"] == 'nest':
525                subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'])
526                decoded = subdict
527            elif attr_spec["type"] == 'string':
528                decoded = attr.as_strz()
529            elif attr_spec["type"] == 'binary':
530                decoded = self._decode_binary(attr, attr_spec)
531            elif attr_spec["type"] == 'flag':
532                decoded = True
533            elif attr_spec["type"] in NlAttr.type_formats:
534                decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order)
535            elif attr_spec["type"] == 'array-nest':
536                decoded = self._decode_array_nest(attr, attr_spec)
537            else:
538                raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}')
539
540            if 'enum' in attr_spec:
541                decoded = self._decode_enum(decoded, attr_spec)
542
543            if not attr_spec.is_multi:
544                rsp[attr_spec['name']] = decoded
545            elif attr_spec.name in rsp:
546                rsp[attr_spec.name].append(decoded)
547            else:
548                rsp[attr_spec.name] = [decoded]
549
550        return rsp
551
552    def _decode_extack_path(self, attrs, attr_set, offset, target):
553        for attr in attrs:
554            try:
555                attr_spec = attr_set.attrs_by_val[attr.type]
556            except KeyError:
557                raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'")
558            if offset > target:
559                break
560            if offset == target:
561                return '.' + attr_spec.name
562
563            if offset + attr.full_len <= target:
564                offset += attr.full_len
565                continue
566            if attr_spec['type'] != 'nest':
567                raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack")
568            offset += 4
569            subpath = self._decode_extack_path(NlAttrs(attr.raw),
570                                               self.attr_sets[attr_spec['nested-attributes']],
571                                               offset, target)
572            if subpath is None:
573                return None
574            return '.' + attr_spec.name + subpath
575
576        return None
577
578    def _decode_extack(self, request, op, extack):
579        if 'bad-attr-offs' not in extack:
580            return
581
582        msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set))
583        offset = 20 + self._fixed_header_size(op)
584        path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset,
585                                        extack['bad-attr-offs'])
586        if path:
587            del extack['bad-attr-offs']
588            extack['bad-attr'] = path
589
590    def _fixed_header_size(self, op):
591        if op.fixed_header:
592            fixed_header_members = self.consts[op.fixed_header].members
593            size = 0
594            for m in fixed_header_members:
595                format = NlAttr.get_format(m.type, m.byte_order)
596                size += format.size
597            return size
598        else:
599            return 0
600
601    def _decode_fixed_header(self, msg, name):
602        fixed_header_members = self.consts[name].members
603        fixed_header_attrs = dict()
604        offset = 0
605        for m in fixed_header_members:
606            format = NlAttr.get_format(m.type, m.byte_order)
607            [ value ] = format.unpack_from(msg.raw, offset)
608            offset += format.size
609            if m.enum:
610                value = self._decode_enum(value, m)
611            fixed_header_attrs[m.name] = value
612        return fixed_header_attrs
613
614    def handle_ntf(self, decoded):
615        msg = dict()
616        if self.include_raw:
617            msg['raw'] = decoded
618        op = self.rsp_by_value[decoded.cmd()]
619        attrs = self._decode(decoded.raw_attrs, op.attr_set.name)
620        if op.fixed_header:
621            attrs.update(self._decode_fixed_header(decoded, op.fixed_header))
622
623        msg['name'] = op['name']
624        msg['msg'] = attrs
625        self.async_msg_queue.append(msg)
626
627    def check_ntf(self):
628        while True:
629            try:
630                reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT)
631            except BlockingIOError:
632                return
633
634            nms = NlMsgs(reply)
635            for nl_msg in nms:
636                if nl_msg.error:
637                    print("Netlink error in ntf!?", os.strerror(-nl_msg.error))
638                    print(nl_msg)
639                    continue
640                if nl_msg.done:
641                    print("Netlink done while checking for ntf!?")
642                    continue
643
644                decoded = self.nlproto.decode(self, nl_msg)
645                if decoded.cmd() not in self.async_msg_ids:
646                    print("Unexpected msg id done while checking for ntf", decoded)
647                    continue
648
649                self.handle_ntf(decoded)
650
651    def operation_do_attributes(self, name):
652      """
653      For a given operation name, find and return a supported
654      set of attributes (as a dict).
655      """
656      op = self.find_operation(name)
657      if not op:
658        return None
659
660      return op['do']['request']['attributes'].copy()
661
662    def _op(self, method, vals, flags, dump=False):
663        op = self.ops[method]
664
665        nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK
666        for flag in flags or []:
667            nl_flags |= flag
668        if dump:
669            nl_flags |= Netlink.NLM_F_DUMP
670
671        req_seq = random.randint(1024, 65535)
672        msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq)
673        fixed_header_members = []
674        if op.fixed_header:
675            fixed_header_members = self.consts[op.fixed_header].members
676            for m in fixed_header_members:
677                value = vals.pop(m.name) if m.name in vals else 0
678                format = NlAttr.get_format(m.type, m.byte_order)
679                msg += format.pack(value)
680        for name, value in vals.items():
681            msg += self._add_attr(op.attr_set.name, name, value)
682        msg = _genl_msg_finalize(msg)
683
684        self.sock.send(msg, 0)
685
686        done = False
687        rsp = []
688        while not done:
689            reply = self.sock.recv(128 * 1024)
690            nms = NlMsgs(reply, attr_space=op.attr_set)
691            for nl_msg in nms:
692                if nl_msg.extack:
693                    self._decode_extack(msg, op, nl_msg.extack)
694
695                if nl_msg.error:
696                    raise NlError(nl_msg)
697                if nl_msg.done:
698                    if nl_msg.extack:
699                        print("Netlink warning:")
700                        print(nl_msg)
701                    done = True
702                    break
703
704                decoded = self.nlproto.decode(self, nl_msg)
705
706                # Check if this is a reply to our request
707                if nl_msg.nl_seq != req_seq or decoded.cmd() != op.rsp_value:
708                    if decoded.cmd() in self.async_msg_ids:
709                        self.handle_ntf(decoded)
710                        continue
711                    else:
712                        print('Unexpected message: ' + repr(decoded))
713                        continue
714
715                rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)
716                if op.fixed_header:
717                    rsp_msg.update(self._decode_fixed_header(decoded, op.fixed_header))
718                rsp.append(rsp_msg)
719
720        if not rsp:
721            return None
722        if not dump and len(rsp) == 1:
723            return rsp[0]
724        return rsp
725
726    def do(self, method, vals, flags):
727        return self._op(method, vals, flags)
728
729    def dump(self, method, vals):
730        return self._op(method, vals, [], dump=True)
731