]> Git Repo - linux.git/blame - tools/net/ynl/lib/ynl.py
tools/net/ynl: improve async notification handling
[linux.git] / tools / net / ynl / lib / ynl.py
CommitLineData
37d9df22 1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
e4b48ed4 2
7c2435ef 3from collections import namedtuple
bd3ce405 4from enum import Enum
e4b48ed4 5import functools
e4b48ed4
JK
6import os
7import random
8import socket
9import struct
7c2435ef 10from struct import Struct
a6a41521 11import sys
e4b48ed4 12import yaml
d8eea68d
DH
13import ipaddress
14import uuid
1bf70e6c
DH
15import queue
16import time
e4b48ed4 17
30a5c6c8
JK
18from .nlspec import SpecFamily
19
e4b48ed4
JK
20#
21# Generic Netlink code which should really be in some library, but I can't quickly find one.
22#
23
24
25class Netlink:
26 # Netlink socket
27 SOL_NETLINK = 270
28
29 NETLINK_ADD_MEMBERSHIP = 1
30 NETLINK_CAP_ACK = 10
31 NETLINK_EXT_ACK = 11
e46dd903 32 NETLINK_GET_STRICT_CHK = 12
e4b48ed4
JK
33
34 # Netlink message
35 NLMSG_ERROR = 2
36 NLMSG_DONE = 3
37
38 NLM_F_REQUEST = 1
39 NLM_F_ACK = 4
40 NLM_F_ROOT = 0x100
41 NLM_F_MATCH = 0x200
1768d8a7
DH
42
43 NLM_F_REPLACE = 0x100
44 NLM_F_EXCL = 0x200
45 NLM_F_CREATE = 0x400
e4b48ed4
JK
46 NLM_F_APPEND = 0x800
47
48 NLM_F_CAPPED = 0x100
49 NLM_F_ACK_TLVS = 0x200
50
51 NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
52
53 NLA_F_NESTED = 0x8000
54 NLA_F_NET_BYTEORDER = 0x4000
55
56 NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER
57
58 # Genetlink defines
59 NETLINK_GENERIC = 16
60
61 GENL_ID_CTRL = 0x10
62
63 # nlctrl
64 CTRL_CMD_GETFAMILY = 3
65
66 CTRL_ATTR_FAMILY_ID = 1
67 CTRL_ATTR_FAMILY_NAME = 2
68 CTRL_ATTR_MAXATTR = 5
69 CTRL_ATTR_MCAST_GROUPS = 7
70
71 CTRL_ATTR_MCAST_GRP_NAME = 1
72 CTRL_ATTR_MCAST_GRP_ID = 2
73
74 # Extack types
75 NLMSGERR_ATTR_MSG = 1
76 NLMSGERR_ATTR_OFFS = 2
77 NLMSGERR_ATTR_COOKIE = 3
78 NLMSGERR_ATTR_POLICY = 4
79 NLMSGERR_ATTR_MISS_TYPE = 5
80 NLMSGERR_ATTR_MISS_NEST = 6
81
bd3ce405
DH
82 # Policy types
83 NL_POLICY_TYPE_ATTR_TYPE = 1
84 NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 2
85 NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 3
86 NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 4
87 NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 5
88 NL_POLICY_TYPE_ATTR_MIN_LENGTH = 6
89 NL_POLICY_TYPE_ATTR_MAX_LENGTH = 7
90 NL_POLICY_TYPE_ATTR_POLICY_IDX = 8
91 NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 9
92 NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 10
93 NL_POLICY_TYPE_ATTR_PAD = 11
94 NL_POLICY_TYPE_ATTR_MASK = 12
95
96 AttrType = Enum('AttrType', ['flag', 'u8', 'u16', 'u32', 'u64',
97 's8', 's16', 's32', 's64',
98 'binary', 'string', 'nul-string',
99 'nested', 'nested-array',
100 'bitfield32', 'sint', 'uint'])
e4b48ed4 101
48993e22
SF
102class NlError(Exception):
103 def __init__(self, nl_msg):
104 self.nl_msg = nl_msg
b269d2b4 105 self.error = -nl_msg.error
48993e22
SF
106
107 def __str__(self):
b269d2b4 108 return f"Netlink error: {os.strerror(self.error)}\n{self.nl_msg}"
48993e22
SF
109
110
7c93a887
JK
111class ConfigError(Exception):
112 pass
113
114
e4b48ed4 115class NlAttr:
7c2435ef
DH
116 ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little'])
117 type_formats = {
118 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")),
119 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")),
120 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")),
121 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")),
122 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")),
123 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")),
124 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")),
125 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q"))
126 }
b423c3c8 127
e4b48ed4 128 def __init__(self, raw, offset):
62691b80 129 self._len, self._type = struct.unpack("HH", raw[offset : offset + 4])
e4b48ed4 130 self.type = self._type & ~Netlink.NLA_TYPE_MASK
d96e48a3 131 self.is_nest = self._type & Netlink.NLA_F_NESTED
e4b48ed4
JK
132 self.payload_len = self._len
133 self.full_len = (self.payload_len + 3) & ~3
62691b80 134 self.raw = raw[offset + 4 : offset + self.payload_len]
e4b48ed4 135
7c2435ef
DH
136 @classmethod
137 def get_format(cls, attr_type, byte_order=None):
138 format = cls.type_formats[attr_type]
9f7cc57f 139 if byte_order:
7c2435ef
DH
140 return format.big if byte_order == "big-endian" \
141 else format.little
142 return format.native
9f7cc57f 143
7c2435ef
DH
144 def as_scalar(self, attr_type, byte_order=None):
145 format = self.get_format(attr_type, byte_order)
146 return format.unpack(self.raw)[0]
e4b48ed4 147
7d4caf54
JK
148 def as_auto_scalar(self, attr_type, byte_order=None):
149 if len(self.raw) != 4 and len(self.raw) != 8:
150 raise Exception(f"Auto-scalar len payload be 4 or 8 bytes, got {len(self.raw)}")
151 real_type = attr_type[0] + str(len(self.raw) * 8)
152 format = self.get_format(real_type, byte_order)
153 return format.unpack(self.raw)[0]
154
e4b48ed4
JK
155 def as_strz(self):
156 return self.raw.decode('ascii')[:-1]
157
158 def as_bin(self):
159 return self.raw
160
b423c3c8 161 def as_c_array(self, type):
7c2435ef
DH
162 format = self.get_format(type)
163 return [ x[0] for x in format.iter_unpack(self.raw) ]
b423c3c8 164
e4b48ed4
JK
165 def __repr__(self):
166 return f"[type:{self.type} len:{self._len}] {self.raw}"
167
168
169class NlAttrs:
1769e2be 170 def __init__(self, msg, offset=0):
e4b48ed4
JK
171 self.attrs = []
172
e4b48ed4
JK
173 while offset < len(msg):
174 attr = NlAttr(msg, offset)
175 offset += attr.full_len
176 self.attrs.append(attr)
177
178 def __iter__(self):
179 yield from self.attrs
180
181 def __repr__(self):
182 msg = ''
183 for a in self.attrs:
184 if msg:
185 msg += '\n'
186 msg += repr(a)
187 return msg
188
189
190class NlMsg:
191 def __init__(self, msg, offset, attr_space=None):
62691b80 192 self.hdr = msg[offset : offset + 16]
e4b48ed4
JK
193
194 self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \
195 struct.unpack("IHHII", self.hdr)
196
62691b80 197 self.raw = msg[offset + 16 : offset + self.nl_len]
e4b48ed4
JK
198
199 self.error = 0
200 self.done = 0
201
202 extack_off = None
203 if self.nl_type == Netlink.NLMSG_ERROR:
204 self.error = struct.unpack("i", self.raw[0:4])[0]
205 self.done = 1
206 extack_off = 20
207 elif self.nl_type == Netlink.NLMSG_DONE:
a44f2eb1 208 self.error = struct.unpack("i", self.raw[0:4])[0]
e4b48ed4
JK
209 self.done = 1
210 extack_off = 4
211
212 self.extack = None
213 if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off:
214 self.extack = dict()
215 extack_attrs = NlAttrs(self.raw[extack_off:])
216 for extack in extack_attrs:
217 if extack.type == Netlink.NLMSGERR_ATTR_MSG:
218 self.extack['msg'] = extack.as_strz()
219 elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE:
7c2435ef 220 self.extack['miss-type'] = extack.as_scalar('u32')
e4b48ed4 221 elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST:
7c2435ef 222 self.extack['miss-nest'] = extack.as_scalar('u32')
e4b48ed4 223 elif extack.type == Netlink.NLMSGERR_ATTR_OFFS:
7c2435ef 224 self.extack['bad-attr-offs'] = extack.as_scalar('u32')
bd3ce405
DH
225 elif extack.type == Netlink.NLMSGERR_ATTR_POLICY:
226 self.extack['policy'] = self._decode_policy(extack.raw)
e4b48ed4
JK
227 else:
228 if 'unknown' not in self.extack:
229 self.extack['unknown'] = []
230 self.extack['unknown'].append(extack)
231
232 if attr_space:
233 # We don't have the ability to parse nests yet, so only do global
234 if 'miss-type' in self.extack and 'miss-nest' not in self.extack:
235 miss_type = self.extack['miss-type']
30a5c6c8
JK
236 if miss_type in attr_space.attrs_by_val:
237 spec = attr_space.attrs_by_val[miss_type]
5c4c0edc 238 self.extack['miss-type'] = spec['name']
e4b48ed4 239 if 'doc' in spec:
5c4c0edc 240 self.extack['miss-type-doc'] = spec['doc']
e4b48ed4 241
bd3ce405
DH
242 def _decode_policy(self, raw):
243 policy = {}
244 for attr in NlAttrs(raw):
245 if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE:
246 type = attr.as_scalar('u32')
247 policy['type'] = Netlink.AttrType(type).name
248 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S:
249 policy['min-value'] = attr.as_scalar('s64')
250 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S:
251 policy['max-value'] = attr.as_scalar('s64')
252 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U:
253 policy['min-value'] = attr.as_scalar('u64')
254 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U:
255 policy['max-value'] = attr.as_scalar('u64')
256 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH:
257 policy['min-length'] = attr.as_scalar('u32')
258 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH:
259 policy['max-length'] = attr.as_scalar('u32')
260 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK:
261 policy['bitfield32-mask'] = attr.as_scalar('u32')
262 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK:
263 policy['mask'] = attr.as_scalar('u64')
264 return policy
265
e46dd903
DH
266 def cmd(self):
267 return self.nl_type
268
e4b48ed4 269 def __repr__(self):
7df7231d 270 msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}"
e4b48ed4 271 if self.error:
7df7231d 272 msg += '\n\terror: ' + str(self.error)
e4b48ed4 273 if self.extack:
7df7231d 274 msg += '\n\textack: ' + repr(self.extack)
e4b48ed4
JK
275 return msg
276
277
278class NlMsgs:
279 def __init__(self, data, attr_space=None):
280 self.msgs = []
281
282 offset = 0
283 while offset < len(data):
284 msg = NlMsg(data, offset, attr_space=attr_space)
285 offset += msg.nl_len
286 self.msgs.append(msg)
287
288 def __iter__(self):
289 yield from self.msgs
290
291
292genl_family_name_to_id = None
293
294
295def _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None):
296 # we prepend length in _genl_msg_finalize()
297 if seq is None:
298 seq = random.randint(1, 1024)
299 nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
758d29fb 300 genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0)
e4b48ed4
JK
301 return nlmsg + genlmsg
302
303
304def _genl_msg_finalize(msg):
305 return struct.pack("I", len(msg) + 4) + msg
306
307
308def _genl_load_families():
309 with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock:
310 sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
311
312 msg = _genl_msg(Netlink.GENL_ID_CTRL,
313 Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP,
314 Netlink.CTRL_CMD_GETFAMILY, 1)
315 msg = _genl_msg_finalize(msg)
316
317 sock.send(msg, 0)
318
319 global genl_family_name_to_id
320 genl_family_name_to_id = dict()
321
322 while True:
323 reply = sock.recv(128 * 1024)
324 nms = NlMsgs(reply)
325 for nl_msg in nms:
326 if nl_msg.error:
327 print("Netlink error:", nl_msg.error)
328 return
329 if nl_msg.done:
330 return
331
332 gm = GenlMsg(nl_msg)
333 fam = dict()
fb0a06d4 334 for attr in NlAttrs(gm.raw):
e4b48ed4 335 if attr.type == Netlink.CTRL_ATTR_FAMILY_ID:
7c2435ef 336 fam['id'] = attr.as_scalar('u16')
e4b48ed4
JK
337 elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME:
338 fam['name'] = attr.as_strz()
339 elif attr.type == Netlink.CTRL_ATTR_MAXATTR:
7c2435ef 340 fam['maxattr'] = attr.as_scalar('u32')
e4b48ed4
JK
341 elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS:
342 fam['mcast'] = dict()
343 for entry in NlAttrs(attr.raw):
344 mcast_name = None
345 mcast_id = None
346 for entry_attr in NlAttrs(entry.raw):
347 if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME:
348 mcast_name = entry_attr.as_strz()
349 elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID:
7c2435ef 350 mcast_id = entry_attr.as_scalar('u32')
e4b48ed4
JK
351 if mcast_name and mcast_id is not None:
352 fam['mcast'][mcast_name] = mcast_id
353 if 'name' in fam and 'id' in fam:
354 genl_family_name_to_id[fam['name']] = fam
355
356
357class GenlMsg:
fb0a06d4 358 def __init__(self, nl_msg):
e4b48ed4 359 self.nl = nl_msg
fb0a06d4
DH
360 self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0)
361 self.raw = nl_msg.raw[4:]
e4b48ed4 362
e46dd903
DH
363 def cmd(self):
364 return self.genl_cmd
365
e4b48ed4
JK
366 def __repr__(self):
367 msg = repr(self.nl)
368 msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n"
369 for a in self.raw_attrs:
370 msg += '\t\t' + repr(a) + '\n'
371 return msg
372
373
e46dd903
DH
374class NetlinkProtocol:
375 def __init__(self, family_name, proto_num):
e4b48ed4 376 self.family_name = family_name
e46dd903
DH
377 self.proto_num = proto_num
378
379 def _message(self, nl_type, nl_flags, seq=None):
380 if seq is None:
381 seq = random.randint(1, 1024)
382 nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)
383 return nlmsg
384
385 def message(self, flags, command, version, seq=None):
386 return self._message(command, flags, seq)
387
388 def _decode(self, nl_msg):
389 return nl_msg
390
0a966d60 391 def decode(self, ynl, nl_msg, op):
e46dd903 392 msg = self._decode(nl_msg)
6fda63c4
AK
393 if op is None:
394 op = ynl.rsp_by_value[msg.cmd()]
0a966d60 395 fixed_header_size = ynl._struct_size(op.fixed_header)
1769e2be 396 msg.raw_attrs = NlAttrs(msg.raw, fixed_header_size)
e46dd903
DH
397 return msg
398
399 def get_mcast_id(self, mcast_name, mcast_groups):
400 if mcast_name not in mcast_groups:
401 raise Exception(f'Multicast group "{mcast_name}" not present in the spec')
402 return mcast_groups[mcast_name].value
403
cecbc52c
DH
404 def msghdr_size(self):
405 return 16
406
e46dd903
DH
407
408class GenlProtocol(NetlinkProtocol):
409 def __init__(self, family_name):
410 super().__init__(family_name, Netlink.NETLINK_GENERIC)
e4b48ed4
JK
411
412 global genl_family_name_to_id
413 if genl_family_name_to_id is None:
414 _genl_load_families()
415
416 self.genl_family = genl_family_name_to_id[family_name]
417 self.family_id = genl_family_name_to_id[family_name]['id']
418
e46dd903
DH
419 def message(self, flags, command, version, seq=None):
420 nlmsg = self._message(self.family_id, flags, seq)
421 genlmsg = struct.pack("BBH", command, version, 0)
422 return nlmsg + genlmsg
423
424 def _decode(self, nl_msg):
425 return GenlMsg(nl_msg)
426
427 def get_mcast_id(self, mcast_name, mcast_groups):
428 if mcast_name not in self.genl_family['mcast']:
429 raise Exception(f'Multicast group "{mcast_name}" not present in the family')
430 return self.genl_family['mcast'][mcast_name]
431
cecbc52c
DH
432 def msghdr_size(self):
433 return super().msghdr_size() + 4
e4b48ed4 434
bf8b8323
DH
435
436class SpaceAttrs:
437 SpecValuesPair = namedtuple('SpecValuesPair', ['spec', 'values'])
438
439 def __init__(self, attr_space, attrs, outer = None):
440 outer_scopes = outer.scopes if outer else []
441 inner_scope = self.SpecValuesPair(attr_space, attrs)
442 self.scopes = [inner_scope] + outer_scopes
443
444 def lookup(self, name):
445 for scope in self.scopes:
446 if name in scope.spec:
447 if name in scope.values:
448 return scope.values[name]
449 spec_name = scope.spec.yaml['name']
450 raise Exception(
451 f"No value for '{name}' in attribute space '{spec_name}'")
452 raise Exception(f"Attribute '{name}' not defined in any attribute-set")
453
454
e4b48ed4
JK
455#
456# YNL implementation details.
457#
458
459
30a5c6c8 460class YnlFamily(SpecFamily):
7c93a887
JK
461 def __init__(self, def_path, schema=None, process_unknown=False,
462 recv_size=0):
30a5c6c8 463 super().__init__(def_path, schema)
e4b48ed4 464
30a5c6c8 465 self.include_raw = False
d96e48a3 466 self.process_unknown = process_unknown
e4b48ed4 467
e46dd903
DH
468 try:
469 if self.proto == "netlink-raw":
470 self.nlproto = NetlinkProtocol(self.yaml['name'],
471 self.yaml['protonum'])
472 else:
473 self.nlproto = GenlProtocol(self.yaml['name'])
474 except KeyError:
475 raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel")
476
a6a41521 477 self._recv_dbg = False
7c93a887
JK
478 # Note that netlink will use conservative (min) message size for
479 # the first dump recv() on the socket, our setting will only matter
480 # from the second recv() on.
481 self._recv_size = recv_size if recv_size else 131072
482 # Netlink will always allocate at least PAGE_SIZE - sizeof(skb_shinfo)
483 # for a message, so smaller receive sizes will lead to truncation.
484 # Note that the min size for other families may be larger than 4k!
485 if self._recv_size < 4000:
486 raise ConfigError()
487
e46dd903 488 self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num)
e4b48ed4
JK
489 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
490 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1)
e46dd903 491 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1)
e4b48ed4 492
e4b48ed4 493 self.async_msg_ids = set()
1bf70e6c 494 self.async_msg_queue = queue.Queue()
e4b48ed4 495
30a5c6c8
JK
496 for msg in self.msgs.values():
497 if msg.is_async:
fd0616d3 498 self.async_msg_ids.add(msg.rsp_value)
e4b48ed4 499
30a5c6c8
JK
500 for op_name, op in self.ops.items():
501 bound_f = functools.partial(self._op, op_name)
502 setattr(self, op.ident_name, bound_f)
e4b48ed4 503
e4b48ed4
JK
504
505 def ntf_subscribe(self, mcast_name):
e46dd903 506 mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups)
e4b48ed4
JK
507 self.sock.bind((0, 0))
508 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP,
e46dd903 509 mcast_id)
e4b48ed4 510
a6a41521
JK
511 def set_recv_dbg(self, enabled):
512 self._recv_dbg = enabled
513
514 def _recv_dbg_print(self, reply, nl_msgs):
515 if not self._recv_dbg:
516 return
517 print("Recv: read", len(reply), "bytes,",
518 len(nl_msgs.msgs), "messages", file=sys.stderr)
519 for nl_msg in nl_msgs:
520 print(" ", nl_msg, file=sys.stderr)
521
e8a6c515
JP
522 def _encode_enum(self, attr_spec, value):
523 enum = self.consts[attr_spec['enum']]
524 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False):
525 scalar = 0
526 if isinstance(value, str):
527 value = [value]
528 for single_value in value:
529 scalar += enum.entries[single_value].user_value(as_flags = True)
530 return scalar
531 else:
532 return enum.entries[value].user_value()
533
534 def _get_scalar(self, attr_spec, value):
535 try:
536 return int(value)
537 except (ValueError, TypeError) as e:
538 if 'enum' not in attr_spec:
539 raise e
a0d94296 540 return self._encode_enum(attr_spec, value)
e8a6c515 541
ab463c43 542 def _add_attr(self, space, name, value, search_attrs):
7582113c
JK
543 try:
544 attr = self.attr_sets[space][name]
545 except KeyError:
546 raise Exception(f"Space '{space}' has no attribute '{name}'")
30a5c6c8 547 nl_type = attr.value
b9bcfc3b
AM
548
549 if attr.is_multi and isinstance(value, list):
550 attr_payload = b''
551 for subvalue in value:
552 attr_payload += self._add_attr(space, name, subvalue, search_attrs)
553 return attr_payload
554
e4b48ed4
JK
555 if attr["type"] == 'nest':
556 nl_type |= Netlink.NLA_F_NESTED
557 attr_payload = b''
ab463c43 558 sub_attrs = SpaceAttrs(self.attr_sets[space], value, search_attrs)
e4b48ed4 559 for subname, subvalue in value.items():
ab463c43
DH
560 attr_payload += self._add_attr(attr['nested-attributes'],
561 subname, subvalue, sub_attrs)
19b64b48 562 elif attr["type"] == 'flag':
ac95b1fc
JP
563 if not value:
564 # If value is absent or false then skip attribute creation.
565 return b''
19b64b48 566 attr_payload = b''
e4b48ed4
JK
567 elif attr["type"] == 'string':
568 attr_payload = str(value).encode('ascii') + b'\x00'
569 elif attr["type"] == 'binary':
649bde90
JK
570 if isinstance(value, bytes):
571 attr_payload = value
572 elif isinstance(value, str):
573 attr_payload = bytes.fromhex(value)
ab463c43
DH
574 elif isinstance(value, dict) and attr.struct_name:
575 attr_payload = self._encode_struct(attr.struct_name, value)
649bde90
JK
576 else:
577 raise Exception(f'Unknown type for binary attribute, value: {value}')
ffe10a45 578 elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar:
e8a6c515 579 scalar = self._get_scalar(attr, value)
ffe10a45
JP
580 if attr.is_auto_scalar:
581 attr_type = attr["type"][0] + ('32' if scalar.bit_length() <= 32 else '64')
582 else:
583 attr_type = attr["type"]
584 format = NlAttr.get_format(attr_type, attr.byte_order)
585 attr_payload = format.pack(scalar)
4e2846fd 586 elif attr['type'] in "bitfield32":
e8a6c515
JP
587 scalar_value = self._get_scalar(attr, value["value"])
588 scalar_selector = self._get_scalar(attr, value["selector"])
589 attr_payload = struct.pack("II", scalar_value, scalar_selector)
ab463c43
DH
590 elif attr['type'] == 'sub-message':
591 msg_format = self._resolve_selector(attr, search_attrs)
592 attr_payload = b''
593 if msg_format.fixed_header:
594 attr_payload += self._encode_struct(msg_format.fixed_header, value)
595 if msg_format.attr_set:
596 if msg_format.attr_set in self.attr_sets:
597 nl_type |= Netlink.NLA_F_NESTED
598 sub_attrs = SpaceAttrs(msg_format.attr_set, value, search_attrs)
599 for subname, subvalue in value.items():
600 attr_payload += self._add_attr(msg_format.attr_set,
601 subname, subvalue, sub_attrs)
602 else:
603 raise Exception(f"Unknown attribute-set '{msg_format.attr_set}'")
e4b48ed4
JK
604 else:
605 raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}')
606
607 pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4)
608 return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad
609
df15c15e 610 def _decode_enum(self, raw, attr_spec):
c311aaa7 611 enum = self.consts[attr_spec['enum']]
9fea94d3 612 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False):
d7ddf5f4 613 i = 0
e4b48ed4
JK
614 value = set()
615 while raw:
616 if raw & 1:
c311aaa7 617 value.add(enum.entries_by_val[i].name)
e4b48ed4
JK
618 raw >>= 1
619 i += 1
620 else:
d7ddf5f4 621 value = enum.entries_by_val[raw].name
df15c15e 622 return value
e4b48ed4 623
b423c3c8 624 def _decode_binary(self, attr, attr_spec):
26071913 625 if attr_spec.struct_name:
e45fee0f 626 decoded = self._decode_struct(attr.raw, attr_spec.struct_name)
26071913 627 elif attr_spec.sub_type:
b423c3c8
DH
628 decoded = attr.as_c_array(attr_spec.sub_type)
629 else:
630 decoded = attr.as_bin()
d8eea68d 631 if attr_spec.display_hint:
971c3eea 632 decoded = self._formatted_string(decoded, attr_spec.display_hint)
b423c3c8
DH
633 return decoded
634
aa6485d8 635 def _decode_array_attr(self, attr, attr_spec):
0493e56d
DH
636 decoded = []
637 offset = 0
638 while offset < len(attr.raw):
639 item = NlAttr(attr.raw, offset)
640 offset += item.full_len
641
aa6485d8
HL
642 if attr_spec["sub-type"] == 'nest':
643 subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes'])
644 decoded.append({ item.type: subattrs })
a7408b56
HL
645 elif attr_spec["sub-type"] == 'binary':
646 subattrs = item.as_bin()
647 if attr_spec.display_hint:
648 subattrs = self._formatted_string(subattrs, attr_spec.display_hint)
649 decoded.append(subattrs)
650 elif attr_spec["sub-type"] in NlAttr.type_formats:
651 subattrs = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order)
652 if attr_spec.display_hint:
653 subattrs = self._formatted_string(subattrs, attr_spec.display_hint)
654 decoded.append(subattrs)
aa6485d8
HL
655 else:
656 raise Exception(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}')
0493e56d
DH
657 return decoded
658
b6e6a76d
DH
659 def _decode_nest_type_value(self, attr, attr_spec):
660 decoded = {}
661 value = attr
662 for name in attr_spec['type-value']:
663 value = NlAttr(value.raw, 0)
664 decoded[name] = value.type
665 subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes'])
666 decoded.update(subattrs)
667 return decoded
668
d96e48a3
JP
669 def _decode_unknown(self, attr):
670 if attr.is_nest:
671 return self._decode(NlAttrs(attr.raw), None)
672 else:
673 return attr.as_bin()
674
675 def _rsp_add(self, rsp, name, is_multi, decoded):
676 if is_multi == None:
677 if name in rsp and type(rsp[name]) is not list:
678 rsp[name] = [rsp[name]]
679 is_multi = True
680 else:
681 is_multi = False
682
683 if not is_multi:
684 rsp[name] = decoded
685 elif name in rsp:
686 rsp[name].append(decoded)
687 else:
688 rsp[name] = [decoded]
689
bf8b8323 690 def _resolve_selector(self, attr_spec, search_attrs):
1769e2be
DH
691 sub_msg = attr_spec.sub_message
692 if sub_msg not in self.sub_msgs:
693 raise Exception(f"No sub-message spec named {sub_msg} for {attr_spec.name}")
694 sub_msg_spec = self.sub_msgs[sub_msg]
695
696 selector = attr_spec.selector
bf8b8323 697 value = search_attrs.lookup(selector)
1769e2be
DH
698 if value not in sub_msg_spec.formats:
699 raise Exception(f"No message format for '{value}' in sub-message spec '{sub_msg}'")
700
701 spec = sub_msg_spec.formats[value]
702 return spec
703
bf8b8323
DH
704 def _decode_sub_msg(self, attr, attr_spec, search_attrs):
705 msg_format = self._resolve_selector(attr_spec, search_attrs)
1769e2be
DH
706 decoded = {}
707 offset = 0
708 if msg_format.fixed_header:
e45fee0f 709 decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header));
886365cf 710 offset = self._struct_size(msg_format.fixed_header)
1769e2be
DH
711 if msg_format.attr_set:
712 if msg_format.attr_set in self.attr_sets:
713 subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set)
714 decoded.update(subdict)
715 else:
716 raise Exception(f"Unknown attribute-set '{attr_space}' when decoding '{attr_spec.name}'")
717 return decoded
718
bf8b8323 719 def _decode(self, attrs, space, outer_attrs = None):
d0bcc15c 720 rsp = dict()
d96e48a3
JP
721 if space:
722 attr_space = self.attr_sets[space]
d0bcc15c 723 search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs)
bf8b8323 724
e4b48ed4 725 for attr in attrs:
7582113c
JK
726 try:
727 attr_spec = attr_space.attrs_by_val[attr.type]
d96e48a3
JP
728 except (KeyError, UnboundLocalError):
729 if not self.process_unknown:
730 raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'")
731 attr_name = f"UnknownAttr({attr.type})"
732 self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr))
733 continue
734
e4b48ed4 735 if attr_spec["type"] == 'nest':
bf8b8323 736 subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'], search_attrs)
90256f3f 737 decoded = subdict
e4b48ed4 738 elif attr_spec["type"] == 'string':
90256f3f 739 decoded = attr.as_strz()
e4b48ed4 740 elif attr_spec["type"] == 'binary':
b423c3c8 741 decoded = self._decode_binary(attr, attr_spec)
19b64b48 742 elif attr_spec["type"] == 'flag':
90256f3f 743 decoded = True
7d4caf54
JK
744 elif attr_spec.is_auto_scalar:
745 decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order)
7c2435ef
DH
746 elif attr_spec["type"] in NlAttr.type_formats:
747 decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order)
4e2846fd
JP
748 if 'enum' in attr_spec:
749 decoded = self._decode_enum(decoded, attr_spec)
2a901623
JK
750 elif attr_spec.display_hint:
751 decoded = self._formatted_string(decoded, attr_spec.display_hint)
aa6485d8
HL
752 elif attr_spec["type"] == 'indexed-array':
753 decoded = self._decode_array_attr(attr, attr_spec)
4e2846fd
JP
754 elif attr_spec["type"] == 'bitfield32':
755 value, selector = struct.unpack("II", attr.raw)
756 if 'enum' in attr_spec:
757 value = self._decode_enum(value, attr_spec)
758 selector = self._decode_enum(selector, attr_spec)
759 decoded = {"value": value, "selector": selector}
1769e2be 760 elif attr_spec["type"] == 'sub-message':
ab463c43 761 decoded = self._decode_sub_msg(attr, attr_spec, search_attrs)
b6e6a76d
DH
762 elif attr_spec["type"] == 'nest-type-value':
763 decoded = self._decode_nest_type_value(attr, attr_spec)
e4b48ed4 764 else:
d96e48a3
JP
765 if not self.process_unknown:
766 raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}')
767 decoded = self._decode_unknown(attr)
e4b48ed4 768
d96e48a3 769 self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded)
90256f3f 770
e4b48ed4
JK
771 return rsp
772
4cd2796f
JK
773 def _decode_extack_path(self, attrs, attr_set, offset, target):
774 for attr in attrs:
7582113c
JK
775 try:
776 attr_spec = attr_set.attrs_by_val[attr.type]
777 except KeyError:
778 raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'")
4cd2796f
JK
779 if offset > target:
780 break
781 if offset == target:
782 return '.' + attr_spec.name
783
784 if offset + attr.full_len <= target:
785 offset += attr.full_len
786 continue
787 if attr_spec['type'] != 'nest':
788 raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack")
789 offset += 4
790 subpath = self._decode_extack_path(NlAttrs(attr.raw),
791 self.attr_sets[attr_spec['nested-attributes']],
792 offset, target)
793 if subpath is None:
794 return None
795 return '.' + attr_spec.name + subpath
796
797 return None
798
fb0a06d4 799 def _decode_extack(self, request, op, extack):
4cd2796f
JK
800 if 'bad-attr-offs' not in extack:
801 return
802
0a966d60 803 msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op)
cecbc52c 804 offset = self.nlproto.msghdr_size() + self._struct_size(op.fixed_header)
e46dd903 805 path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset,
fb0a06d4 806 extack['bad-attr-offs'])
4cd2796f
JK
807 if path:
808 del extack['bad-attr-offs']
809 extack['bad-attr'] = path
810
886365cf 811 def _struct_size(self, name):
1769e2be 812 if name:
886365cf 813 members = self.consts[name].members
fb0a06d4 814 size = 0
886365cf 815 for m in members:
8b6811d9 816 if m.type in ['pad', 'binary']:
bf08f32c
DH
817 if m.struct:
818 size += self._struct_size(m.struct)
819 else:
820 size += m.len
8b6811d9
DH
821 else:
822 format = NlAttr.get_format(m.type, m.byte_order)
823 size += format.size
fb0a06d4
DH
824 return size
825 else:
826 return 0
827
e45fee0f
DH
828 def _decode_struct(self, data, name):
829 members = self.consts[name].members
830 attrs = dict()
fb0a06d4 831 offset = 0
e45fee0f 832 for m in members:
8b6811d9
DH
833 value = None
834 if m.type == 'pad':
835 offset += m.len
836 elif m.type == 'binary':
bf08f32c
DH
837 if m.struct:
838 len = self._struct_size(m.struct)
839 value = self._decode_struct(data[offset : offset + len],
840 m.struct)
841 offset += len
842 else:
843 value = data[offset : offset + m.len]
844 offset += m.len
8b6811d9
DH
845 else:
846 format = NlAttr.get_format(m.type, m.byte_order)
e45fee0f 847 [ value ] = format.unpack_from(data, offset)
8b6811d9
DH
848 offset += format.size
849 if value is not None:
850 if m.enum:
851 value = self._decode_enum(value, m)
e45fee0f 852 elif m.display_hint:
971c3eea 853 value = self._formatted_string(value, m.display_hint)
e45fee0f
DH
854 attrs[m.name] = value
855 return attrs
fb0a06d4 856
5f2823c4
DH
857 def _encode_struct(self, name, vals):
858 members = self.consts[name].members
859 attr_payload = b''
860 for m in members:
a387a921 861 value = vals.pop(m.name) if m.name in vals else None
5f2823c4
DH
862 if m.type == 'pad':
863 attr_payload += bytearray(m.len)
864 elif m.type == 'binary':
bf08f32c
DH
865 if m.struct:
866 if value is None:
867 value = dict()
868 attr_payload += self._encode_struct(m.struct, value)
a387a921 869 else:
bf08f32c
DH
870 if value is None:
871 attr_payload += bytearray(m.len)
872 else:
873 attr_payload += bytes.fromhex(value)
5f2823c4 874 else:
a387a921
DH
875 if value is None:
876 value = 0
5f2823c4
DH
877 format = NlAttr.get_format(m.type, m.byte_order)
878 attr_payload += format.pack(value)
879 return attr_payload
880
971c3eea
DH
881 def _formatted_string(self, raw, display_hint):
882 if display_hint == 'mac':
883 formatted = ':'.join('%02x' % b for b in raw)
884 elif display_hint == 'hex':
b334f5ed
HL
885 if isinstance(raw, int):
886 formatted = hex(raw)
887 else:
888 formatted = bytes.hex(raw, ' ')
971c3eea
DH
889 elif display_hint in [ 'ipv4', 'ipv6' ]:
890 formatted = format(ipaddress.ip_address(raw))
891 elif display_hint == 'uuid':
892 formatted = str(uuid.UUID(bytes=raw))
893 else:
894 formatted = raw
895 return formatted
896
e46dd903 897 def handle_ntf(self, decoded):
e4b48ed4
JK
898 msg = dict()
899 if self.include_raw:
e46dd903
DH
900 msg['raw'] = decoded
901 op = self.rsp_by_value[decoded.cmd()]
902 attrs = self._decode(decoded.raw_attrs, op.attr_set.name)
903 if op.fixed_header:
e45fee0f 904 attrs.update(self._decode_struct(decoded.raw, op.fixed_header))
e46dd903 905
e4b48ed4 906 msg['name'] = op['name']
e46dd903 907 msg['msg'] = attrs
1bf70e6c 908 self.async_msg_queue.put(msg)
e4b48ed4 909
1bf70e6c 910 def check_ntf(self, interval=0.1):
e4b48ed4
JK
911 while True:
912 try:
7c93a887 913 reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT)
1bf70e6c
DH
914 nms = NlMsgs(reply)
915 self._recv_dbg_print(reply, nms)
916 for nl_msg in nms:
917 if nl_msg.error:
918 print("Netlink error in ntf!?", os.strerror(-nl_msg.error))
919 print(nl_msg)
920 continue
921 if nl_msg.done:
922 print("Netlink done while checking for ntf!?")
923 continue
924
925 decoded = self.nlproto.decode(self, nl_msg, None)
926 if decoded.cmd() not in self.async_msg_ids:
927 print("Unexpected msg id while checking for ntf", decoded)
928 continue
929
930 self.handle_ntf(decoded)
e4b48ed4 931 except BlockingIOError:
1bf70e6c 932 pass
e4b48ed4 933
1bf70e6c
DH
934 try:
935 yield self.async_msg_queue.get_nowait()
936 except queue.Empty:
937 try:
938 time.sleep(interval)
939 except KeyboardInterrupt:
940 return
e4b48ed4 941
f3d07b02
SF
942 def operation_do_attributes(self, name):
943 """
944 For a given operation name, find and return a supported
945 set of attributes (as a dict).
946 """
947 op = self.find_operation(name)
948 if not op:
949 return None
950
951 return op['do']['request']['attributes'].copy()
952
ba8be00f 953 def _encode_message(self, op, vals, flags, req_seq):
e4b48ed4 954 nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK
1768d8a7
DH
955 for flag in flags or []:
956 nl_flags |= flag
e4b48ed4 957
e46dd903 958 msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq)
f036d936 959 if op.fixed_header:
5f2823c4 960 msg += self._encode_struct(op.fixed_header, vals)
ab463c43 961 search_attrs = SpaceAttrs(op.attr_set, vals)
e4b48ed4 962 for name, value in vals.items():
ab463c43 963 msg += self._add_attr(op.attr_set.name, name, value, search_attrs)
e4b48ed4 964 msg = _genl_msg_finalize(msg)
ba8be00f 965 return msg
e4b48ed4 966
ba8be00f
DH
967 def _ops(self, ops):
968 reqs_by_seq = {}
969 req_seq = random.randint(1024, 65535)
970 payload = b''
971 for (method, vals, flags) in ops:
972 op = self.ops[method]
973 msg = self._encode_message(op, vals, flags, req_seq)
974 reqs_by_seq[req_seq] = (op, msg, flags)
975 payload += msg
976 req_seq += 1
977
978 self.sock.send(payload, 0)
e4b48ed4
JK
979
980 done = False
981 rsp = []
ba8be00f 982 op_rsp = []
e4b48ed4 983 while not done:
7c93a887 984 reply = self.sock.recv(self._recv_size)
30a5c6c8 985 nms = NlMsgs(reply, attr_space=op.attr_set)
a6a41521 986 self._recv_dbg_print(reply, nms)
e4b48ed4 987 for nl_msg in nms:
ba8be00f
DH
988 if nl_msg.nl_seq in reqs_by_seq:
989 (op, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq]
990 if nl_msg.extack:
991 self._decode_extack(req_msg, op, nl_msg.extack)
992 else:
6fda63c4 993 op = None
ba8be00f 994 req_flags = []
4cd2796f 995
e4b48ed4 996 if nl_msg.error:
48993e22 997 raise NlError(nl_msg)
e4b48ed4 998 if nl_msg.done:
4cd2796f
JK
999 if nl_msg.extack:
1000 print("Netlink warning:")
1001 print(nl_msg)
ba8be00f
DH
1002
1003 if Netlink.NLM_F_DUMP in req_flags:
1004 rsp.append(op_rsp)
1005 elif not op_rsp:
1006 rsp.append(None)
1007 elif len(op_rsp) == 1:
1008 rsp.append(op_rsp[0])
1009 else:
1010 rsp.append(op_rsp)
1011 op_rsp = []
1012
1013 del reqs_by_seq[nl_msg.nl_seq]
1014 done = len(reqs_by_seq) == 0
e4b48ed4
JK
1015 break
1016
0a966d60 1017 decoded = self.nlproto.decode(self, nl_msg, op)
e46dd903 1018
e4b48ed4 1019 # Check if this is a reply to our request
ba8be00f 1020 if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value:
e46dd903
DH
1021 if decoded.cmd() in self.async_msg_ids:
1022 self.handle_ntf(decoded)
e4b48ed4
JK
1023 continue
1024 else:
e46dd903 1025 print('Unexpected message: ' + repr(decoded))
e4b48ed4
JK
1026 continue
1027
e46dd903 1028 rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)
fb0a06d4 1029 if op.fixed_header:
e45fee0f 1030 rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header))
ba8be00f 1031 op_rsp.append(rsp_msg)
e4b48ed4 1032
e4b48ed4 1033 return rsp
8dfec0a8 1034
ba8be00f
DH
1035 def _op(self, method, vals, flags=None, dump=False):
1036 req_flags = flags or []
1037 if dump:
1038 req_flags.append(Netlink.NLM_F_DUMP)
1039
1040 ops = [(method, vals, req_flags)]
1041 return self._ops(ops)[0]
1042
e136735f 1043 def do(self, method, vals, flags=None):
1768d8a7 1044 return self._op(method, vals, flags)
8dfec0a8
JK
1045
1046 def dump(self, method, vals):
ba8be00f
DH
1047 return self._op(method, vals, dump=True)
1048
1049 def do_multi(self, ops):
1050 return self._ops(ops)
This page took 0.333884 seconds and 4 git commands to generate.