xref: /linux/tools/net/ynl/lib/ynl.py (revision 484f0a071f8d482649eaf025dee7b76a7202fec9)
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.done = 1
205            extack_off = 4
206
207        self.extack = None
208        if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off:
209            self.extack = dict()
210            extack_attrs = NlAttrs(self.raw[extack_off:])
211            for extack in extack_attrs:
212                if extack.type == Netlink.NLMSGERR_ATTR_MSG:
213                    self.extack['msg'] = extack.as_strz()
214                elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE:
215                    self.extack['miss-type'] = extack.as_scalar('u32')
216                elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST:
217                    self.extack['miss-nest'] = extack.as_scalar('u32')
218                elif extack.type == Netlink.NLMSGERR_ATTR_OFFS:
219                    self.extack['bad-attr-offs'] = extack.as_scalar('u32')
220                else:
221                    if 'unknown' not in self.extack:
222                        self.extack['unknown'] = []
223                    self.extack['unknown'].append(extack)
224
225            if attr_space:
226                # We don't have the ability to parse nests yet, so only do global
227                if 'miss-type' in self.extack and 'miss-nest' not in self.extack:
228                    miss_type = self.extack['miss-type']
229                    if miss_type in attr_space.attrs_by_val:
230                        spec = attr_space.attrs_by_val[miss_type]
231                        desc = spec['name']
232                        if 'doc' in spec:
233                            desc += f" ({spec['doc']})"
234                        self.extack['miss-type'] = desc
235
236    def cmd(self):
237        return self.nl_type
238
239    def __repr__(self):
240        msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n"
241        if self.error:
242            msg += '\terror: ' + str(self.error)
243        if self.extack:
244            msg += '\textack: ' + repr(self.extack)
245        return msg
246
247
248class NlMsgs:
249    def __init__(self, data, attr_space=None):
250        self.msgs = []
251
252        offset = 0
253        while offset < len(data):
254            msg = NlMsg(data, offset, attr_space=attr_space)
255            offset += msg.nl_len
256            self.msgs.append(msg)
257
258    def __iter__(self):
259        yield from self.msgs
260
261
262genl_family_name_to_id = None
263
264
265def _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None):
266    # we prepend length in _genl_msg_finalize()
267    if seq is None:
268        seq = random.randint(1, 1024)
269    nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
270    genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0)
271    return nlmsg + genlmsg
272
273
274def _genl_msg_finalize(msg):
275    return struct.pack("I", len(msg) + 4) + msg
276
277
278def _genl_load_families():
279    with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock:
280        sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
281
282        msg = _genl_msg(Netlink.GENL_ID_CTRL,
283                        Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP,
284                        Netlink.CTRL_CMD_GETFAMILY, 1)
285        msg = _genl_msg_finalize(msg)
286
287        sock.send(msg, 0)
288
289        global genl_family_name_to_id
290        genl_family_name_to_id = dict()
291
292        while True:
293            reply = sock.recv(128 * 1024)
294            nms = NlMsgs(reply)
295            for nl_msg in nms:
296                if nl_msg.error:
297                    print("Netlink error:", nl_msg.error)
298                    return
299                if nl_msg.done:
300                    return
301
302                gm = GenlMsg(nl_msg)
303                fam = dict()
304                for attr in NlAttrs(gm.raw):
305                    if attr.type == Netlink.CTRL_ATTR_FAMILY_ID:
306                        fam['id'] = attr.as_scalar('u16')
307                    elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME:
308                        fam['name'] = attr.as_strz()
309                    elif attr.type == Netlink.CTRL_ATTR_MAXATTR:
310                        fam['maxattr'] = attr.as_scalar('u32')
311                    elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS:
312                        fam['mcast'] = dict()
313                        for entry in NlAttrs(attr.raw):
314                            mcast_name = None
315                            mcast_id = None
316                            for entry_attr in NlAttrs(entry.raw):
317                                if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME:
318                                    mcast_name = entry_attr.as_strz()
319                                elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID:
320                                    mcast_id = entry_attr.as_scalar('u32')
321                            if mcast_name and mcast_id is not None:
322                                fam['mcast'][mcast_name] = mcast_id
323                if 'name' in fam and 'id' in fam:
324                    genl_family_name_to_id[fam['name']] = fam
325
326
327class GenlMsg:
328    def __init__(self, nl_msg):
329        self.nl = nl_msg
330        self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0)
331        self.raw = nl_msg.raw[4:]
332
333    def cmd(self):
334        return self.genl_cmd
335
336    def __repr__(self):
337        msg = repr(self.nl)
338        msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n"
339        for a in self.raw_attrs:
340            msg += '\t\t' + repr(a) + '\n'
341        return msg
342
343
344class NetlinkProtocol:
345    def __init__(self, family_name, proto_num):
346        self.family_name = family_name
347        self.proto_num = proto_num
348
349    def _message(self, nl_type, nl_flags, seq=None):
350        if seq is None:
351            seq = random.randint(1, 1024)
352        nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
353        return nlmsg
354
355    def message(self, flags, command, version, seq=None):
356        return self._message(command, flags, seq)
357
358    def _decode(self, nl_msg):
359        return nl_msg
360
361    def decode(self, ynl, nl_msg):
362        msg = self._decode(nl_msg)
363        fixed_header_size = 0
364        if ynl:
365            op = ynl.rsp_by_value[msg.cmd()]
366            fixed_header_size = ynl._fixed_header_size(op)
367        msg.raw_attrs = NlAttrs(msg.raw[fixed_header_size:])
368        return msg
369
370    def get_mcast_id(self, mcast_name, mcast_groups):
371        if mcast_name not in mcast_groups:
372            raise Exception(f'Multicast group "{mcast_name}" not present in the spec')
373        return mcast_groups[mcast_name].value
374
375
376class GenlProtocol(NetlinkProtocol):
377    def __init__(self, family_name):
378        super().__init__(family_name, Netlink.NETLINK_GENERIC)
379
380        global genl_family_name_to_id
381        if genl_family_name_to_id is None:
382            _genl_load_families()
383
384        self.genl_family = genl_family_name_to_id[family_name]
385        self.family_id = genl_family_name_to_id[family_name]['id']
386
387    def message(self, flags, command, version, seq=None):
388        nlmsg = self._message(self.family_id, flags, seq)
389        genlmsg = struct.pack("BBH", command, version, 0)
390        return nlmsg + genlmsg
391
392    def _decode(self, nl_msg):
393        return GenlMsg(nl_msg)
394
395    def get_mcast_id(self, mcast_name, mcast_groups):
396        if mcast_name not in self.genl_family['mcast']:
397            raise Exception(f'Multicast group "{mcast_name}" not present in the family')
398        return self.genl_family['mcast'][mcast_name]
399
400
401#
402# YNL implementation details.
403#
404
405
406class YnlFamily(SpecFamily):
407    def __init__(self, def_path, schema=None):
408        super().__init__(def_path, schema)
409
410        self.include_raw = False
411
412        try:
413            if self.proto == "netlink-raw":
414                self.nlproto = NetlinkProtocol(self.yaml['name'],
415                                               self.yaml['protonum'])
416            else:
417                self.nlproto = GenlProtocol(self.yaml['name'])
418        except KeyError:
419            raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel")
420
421        self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num)
422        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
423        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1)
424        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1)
425
426        self.async_msg_ids = set()
427        self.async_msg_queue = []
428
429        for msg in self.msgs.values():
430            if msg.is_async:
431                self.async_msg_ids.add(msg.rsp_value)
432
433        for op_name, op in self.ops.items():
434            bound_f = functools.partial(self._op, op_name)
435            setattr(self, op.ident_name, bound_f)
436
437
438    def ntf_subscribe(self, mcast_name):
439        mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups)
440        self.sock.bind((0, 0))
441        self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP,
442                             mcast_id)
443
444    def _add_attr(self, space, name, value):
445        try:
446            attr = self.attr_sets[space][name]
447        except KeyError:
448            raise Exception(f"Space '{space}' has no attribute '{name}'")
449        nl_type = attr.value
450        if attr["type"] == 'nest':
451            nl_type |= Netlink.NLA_F_NESTED
452            attr_payload = b''
453            for subname, subvalue in value.items():
454                attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue)
455        elif attr["type"] == 'flag':
456            attr_payload = b''
457        elif attr["type"] == 'string':
458            attr_payload = str(value).encode('ascii') + b'\x00'
459        elif attr["type"] == 'binary':
460            if isinstance(value, bytes):
461                attr_payload = value
462            elif isinstance(value, str):
463                attr_payload = bytes.fromhex(value)
464            else:
465                raise Exception(f'Unknown type for binary attribute, value: {value}')
466        elif attr['type'] in NlAttr.type_formats:
467            format = NlAttr.get_format(attr['type'], attr.byte_order)
468            attr_payload = format.pack(int(value))
469        else:
470            raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}')
471
472        pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4)
473        return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad
474
475    def _decode_enum(self, raw, attr_spec):
476        enum = self.consts[attr_spec['enum']]
477        if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']:
478            i = 0
479            value = set()
480            while raw:
481                if raw & 1:
482                    value.add(enum.entries_by_val[i].name)
483                raw >>= 1
484                i += 1
485        else:
486            value = enum.entries_by_val[raw].name
487        return value
488
489    def _decode_binary(self, attr, attr_spec):
490        if attr_spec.struct_name:
491            members = self.consts[attr_spec.struct_name]
492            decoded = attr.as_struct(members)
493            for m in members:
494                if m.enum:
495                    decoded[m.name] = self._decode_enum(decoded[m.name], m)
496        elif attr_spec.sub_type:
497            decoded = attr.as_c_array(attr_spec.sub_type)
498        else:
499            decoded = attr.as_bin()
500            if attr_spec.display_hint:
501                decoded = NlAttr.formatted_string(decoded, attr_spec.display_hint)
502        return decoded
503
504    def _decode_array_nest(self, attr, attr_spec):
505        decoded = []
506        offset = 0
507        while offset < len(attr.raw):
508            item = NlAttr(attr.raw, offset)
509            offset += item.full_len
510
511            subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes'])
512            decoded.append({ item.type: subattrs })
513        return decoded
514
515    def _decode(self, attrs, space):
516        attr_space = self.attr_sets[space]
517        rsp = dict()
518        for attr in attrs:
519            try:
520                attr_spec = attr_space.attrs_by_val[attr.type]
521            except KeyError:
522                raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'")
523            if attr_spec["type"] == 'nest':
524                subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'])
525                decoded = subdict
526            elif attr_spec["type"] == 'string':
527                decoded = attr.as_strz()
528            elif attr_spec["type"] == 'binary':
529                decoded = self._decode_binary(attr, attr_spec)
530            elif attr_spec["type"] == 'flag':
531                decoded = True
532            elif attr_spec["type"] in NlAttr.type_formats:
533                decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order)
534            elif attr_spec["type"] == 'array-nest':
535                decoded = self._decode_array_nest(attr, attr_spec)
536            else:
537                raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}')
538
539            if 'enum' in attr_spec:
540                decoded = self._decode_enum(decoded, attr_spec)
541
542            if not attr_spec.is_multi:
543                rsp[attr_spec['name']] = decoded
544            elif attr_spec.name in rsp:
545                rsp[attr_spec.name].append(decoded)
546            else:
547                rsp[attr_spec.name] = [decoded]
548
549        return rsp
550
551    def _decode_extack_path(self, attrs, attr_set, offset, target):
552        for attr in attrs:
553            try:
554                attr_spec = attr_set.attrs_by_val[attr.type]
555            except KeyError:
556                raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'")
557            if offset > target:
558                break
559            if offset == target:
560                return '.' + attr_spec.name
561
562            if offset + attr.full_len <= target:
563                offset += attr.full_len
564                continue
565            if attr_spec['type'] != 'nest':
566                raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack")
567            offset += 4
568            subpath = self._decode_extack_path(NlAttrs(attr.raw),
569                                               self.attr_sets[attr_spec['nested-attributes']],
570                                               offset, target)
571            if subpath is None:
572                return None
573            return '.' + attr_spec.name + subpath
574
575        return None
576
577    def _decode_extack(self, request, op, extack):
578        if 'bad-attr-offs' not in extack:
579            return
580
581        msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set))
582        offset = 20 + self._fixed_header_size(op)
583        path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset,
584                                        extack['bad-attr-offs'])
585        if path:
586            del extack['bad-attr-offs']
587            extack['bad-attr'] = path
588
589    def _fixed_header_size(self, op):
590        if op.fixed_header:
591            fixed_header_members = self.consts[op.fixed_header].members
592            size = 0
593            for m in fixed_header_members:
594                format = NlAttr.get_format(m.type, m.byte_order)
595                size += format.size
596            return size
597        else:
598            return 0
599
600    def _decode_fixed_header(self, msg, name):
601        fixed_header_members = self.consts[name].members
602        fixed_header_attrs = dict()
603        offset = 0
604        for m in fixed_header_members:
605            format = NlAttr.get_format(m.type, m.byte_order)
606            [ value ] = format.unpack_from(msg.raw, offset)
607            offset += format.size
608            if m.enum:
609                value = self._decode_enum(value, m)
610            fixed_header_attrs[m.name] = value
611        return fixed_header_attrs
612
613    def handle_ntf(self, decoded):
614        msg = dict()
615        if self.include_raw:
616            msg['raw'] = decoded
617        op = self.rsp_by_value[decoded.cmd()]
618        attrs = self._decode(decoded.raw_attrs, op.attr_set.name)
619        if op.fixed_header:
620            attrs.update(self._decode_fixed_header(decoded, op.fixed_header))
621
622        msg['name'] = op['name']
623        msg['msg'] = attrs
624        self.async_msg_queue.append(msg)
625
626    def check_ntf(self):
627        while True:
628            try:
629                reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT)
630            except BlockingIOError:
631                return
632
633            nms = NlMsgs(reply)
634            for nl_msg in nms:
635                if nl_msg.error:
636                    print("Netlink error in ntf!?", os.strerror(-nl_msg.error))
637                    print(nl_msg)
638                    continue
639                if nl_msg.done:
640                    print("Netlink done while checking for ntf!?")
641                    continue
642
643                decoded = self.nlproto.decode(self, nl_msg)
644                if decoded.cmd() not in self.async_msg_ids:
645                    print("Unexpected msg id done while checking for ntf", decoded)
646                    continue
647
648                self.handle_ntf(decoded)
649
650    def operation_do_attributes(self, name):
651      """
652      For a given operation name, find and return a supported
653      set of attributes (as a dict).
654      """
655      op = self.find_operation(name)
656      if not op:
657        return None
658
659      return op['do']['request']['attributes'].copy()
660
661    def _op(self, method, vals, flags, dump=False):
662        op = self.ops[method]
663
664        nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK
665        for flag in flags or []:
666            nl_flags |= flag
667        if dump:
668            nl_flags |= Netlink.NLM_F_DUMP
669
670        req_seq = random.randint(1024, 65535)
671        msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq)
672        fixed_header_members = []
673        if op.fixed_header:
674            fixed_header_members = self.consts[op.fixed_header].members
675            for m in fixed_header_members:
676                value = vals.pop(m.name) if m.name in vals else 0
677                format = NlAttr.get_format(m.type, m.byte_order)
678                msg += format.pack(value)
679        for name, value in vals.items():
680            msg += self._add_attr(op.attr_set.name, name, value)
681        msg = _genl_msg_finalize(msg)
682
683        self.sock.send(msg, 0)
684
685        done = False
686        rsp = []
687        while not done:
688            reply = self.sock.recv(128 * 1024)
689            nms = NlMsgs(reply, attr_space=op.attr_set)
690            for nl_msg in nms:
691                if nl_msg.extack:
692                    self._decode_extack(msg, op, nl_msg.extack)
693
694                if nl_msg.error:
695                    raise NlError(nl_msg)
696                if nl_msg.done:
697                    if nl_msg.extack:
698                        print("Netlink warning:")
699                        print(nl_msg)
700                    done = True
701                    break
702
703                decoded = self.nlproto.decode(self, nl_msg)
704
705                # Check if this is a reply to our request
706                if nl_msg.nl_seq != req_seq or decoded.cmd() != op.rsp_value:
707                    if decoded.cmd() in self.async_msg_ids:
708                        self.handle_ntf(decoded)
709                        continue
710                    else:
711                        print('Unexpected message: ' + repr(decoded))
712                        continue
713
714                rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)
715                if op.fixed_header:
716                    rsp_msg.update(self._decode_fixed_header(decoded, op.fixed_header))
717                rsp.append(rsp_msg)
718
719        if not rsp:
720            return None
721        if not dump and len(rsp) == 1:
722            return rsp[0]
723        return rsp
724
725    def do(self, method, vals, flags):
726        return self._op(method, vals, flags)
727
728    def dump(self, method, vals):
729        return self._op(method, vals, [], dump=True)
730