]>
Commit | Line | Data |
---|---|---|
37d9df22 | 1 | # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
e4b48ed4 | 2 | |
7c2435ef | 3 | from collections import namedtuple |
bd3ce405 | 4 | from enum import Enum |
e4b48ed4 | 5 | import functools |
e4b48ed4 JK |
6 | import os |
7 | import random | |
8 | import socket | |
9 | import struct | |
7c2435ef | 10 | from struct import Struct |
a6a41521 | 11 | import sys |
e4b48ed4 | 12 | import yaml |
d8eea68d DH |
13 | import ipaddress |
14 | import uuid | |
1bf70e6c DH |
15 | import queue |
16 | import time | |
e4b48ed4 | 17 | |
30a5c6c8 JK |
18 | from .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 | ||
25 | class 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 |
102 | class 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 |
111 | class ConfigError(Exception): |
112 | pass | |
113 | ||
114 | ||
e4b48ed4 | 115 | class 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 | ||
169 | class 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 | ||
190 | class 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 | ||
278 | class 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 | ||
292 | genl_family_name_to_id = None | |
293 | ||
294 | ||
295 | def _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 | ||
304 | def _genl_msg_finalize(msg): | |
305 | return struct.pack("I", len(msg) + 4) + msg | |
306 | ||
307 | ||
308 | def _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 | ||
357 | class 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 |
374 | class 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 | |
408 | class 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 | |
436 | class 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 | 460 | class 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) |