1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2# 3# pylint: disable=missing-class-docstring, missing-function-docstring 4# pylint: disable=too-many-branches, too-many-locals, too-many-instance-attributes 5# pylint: disable=too-many-lines 6 7""" 8YAML Netlink Library 9 10An implementation of the genetlink and raw netlink protocols. 11""" 12 13from collections import namedtuple 14from enum import Enum 15import functools 16import os 17import random 18import socket 19import struct 20from struct import Struct 21import sys 22import ipaddress 23import uuid 24import queue 25import selectors 26import time 27 28from .nlspec import SpecFamily 29 30# 31# Generic Netlink code which should really be in some library, but I can't quickly find one. 32# 33 34 35class YnlException(Exception): 36 pass 37 38 39# pylint: disable=too-few-public-methods 40class Netlink: 41 # Netlink socket 42 SOL_NETLINK = 270 43 44 NETLINK_ADD_MEMBERSHIP = 1 45 NETLINK_CAP_ACK = 10 46 NETLINK_EXT_ACK = 11 47 NETLINK_GET_STRICT_CHK = 12 48 49 # Netlink message 50 NLMSG_ERROR = 2 51 NLMSG_DONE = 3 52 53 NLM_F_REQUEST = 1 54 NLM_F_ACK = 4 55 NLM_F_ROOT = 0x100 56 NLM_F_MATCH = 0x200 57 58 NLM_F_REPLACE = 0x100 59 NLM_F_EXCL = 0x200 60 NLM_F_CREATE = 0x400 61 NLM_F_APPEND = 0x800 62 63 NLM_F_CAPPED = 0x100 64 NLM_F_ACK_TLVS = 0x200 65 66 NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH 67 68 NLA_F_NESTED = 0x8000 69 NLA_F_NET_BYTEORDER = 0x4000 70 71 NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER 72 73 # Genetlink defines 74 NETLINK_GENERIC = 16 75 76 GENL_ID_CTRL = 0x10 77 78 # nlctrl 79 CTRL_CMD_GETFAMILY = 3 80 CTRL_CMD_GETPOLICY = 10 81 82 CTRL_ATTR_FAMILY_ID = 1 83 CTRL_ATTR_FAMILY_NAME = 2 84 CTRL_ATTR_MAXATTR = 5 85 CTRL_ATTR_MCAST_GROUPS = 7 86 CTRL_ATTR_POLICY = 8 87 CTRL_ATTR_OP_POLICY = 9 88 CTRL_ATTR_OP = 10 89 90 CTRL_ATTR_MCAST_GRP_NAME = 1 91 CTRL_ATTR_MCAST_GRP_ID = 2 92 93 CTRL_ATTR_POLICY_DO = 1 94 CTRL_ATTR_POLICY_DUMP = 2 95 96 # Extack types 97 NLMSGERR_ATTR_MSG = 1 98 NLMSGERR_ATTR_OFFS = 2 99 NLMSGERR_ATTR_COOKIE = 3 100 NLMSGERR_ATTR_POLICY = 4 101 NLMSGERR_ATTR_MISS_TYPE = 5 102 NLMSGERR_ATTR_MISS_NEST = 6 103 104 # Policy types 105 NL_POLICY_TYPE_ATTR_TYPE = 1 106 NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 2 107 NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 3 108 NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 4 109 NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 5 110 NL_POLICY_TYPE_ATTR_MIN_LENGTH = 6 111 NL_POLICY_TYPE_ATTR_MAX_LENGTH = 7 112 NL_POLICY_TYPE_ATTR_POLICY_IDX = 8 113 NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 9 114 NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 10 115 NL_POLICY_TYPE_ATTR_PAD = 11 116 NL_POLICY_TYPE_ATTR_MASK = 12 117 118 AttrType = Enum('AttrType', ['flag', 'u8', 'u16', 'u32', 'u64', 119 's8', 's16', 's32', 's64', 120 'binary', 'string', 'nul-string', 121 'nested', 'nested-array', 122 'bitfield32', 'sint', 'uint']) 123 124class NlError(Exception): 125 def __init__(self, nl_msg): 126 self.nl_msg = nl_msg 127 self.error = -nl_msg.error 128 129 def __str__(self): 130 msg = "Netlink error: " 131 132 extack = self.nl_msg.extack.copy() if self.nl_msg.extack else {} 133 if 'msg' in extack: 134 msg += extack['msg'] + ': ' 135 del extack['msg'] 136 msg += os.strerror(self.error) 137 if extack: 138 msg += ' ' + str(extack) 139 return msg 140 141 142class ConfigError(Exception): 143 pass 144 145 146class NlPolicy: 147 """Kernel policy for one mode (do or dump) of one operation. 148 149 Returned by YnlFamily.get_policy(). Attributes of the policy 150 are accessible as attributes of the object. Nested policies 151 can be accessed indexing the object like a dictionary:: 152 153 pol = ynl.get_policy('page-pool-stats-get', 'do') 154 pol['info'].type # 'nested' 155 pol['info']['id'].type # 'uint' 156 pol['info']['id'].min_value # 1 157 158 Each policy entry always has a 'type' attribute (e.g. u32, string, 159 nested). Optional attributes depending on the 'type': min-value, 160 max-value, min-length, max-length, mask. 161 162 Policies can form infinite nesting loops. These loops are trimmed 163 when policy is converted to a dict with pol.to_dict(). 164 """ 165 def __init__(self, ynl, policy_idx, policy_table, attr_set, props=None): 166 self._policy_idx = policy_idx 167 self._policy_table = policy_table 168 self._ynl = ynl 169 self._props = props or {} 170 self._entries = {} 171 self._cache = {} 172 if policy_idx is not None and policy_idx in policy_table: 173 for attr_id, decoded in policy_table[policy_idx].items(): 174 if attr_set and attr_id in attr_set.attrs_by_val: 175 spec = attr_set.attrs_by_val[attr_id] 176 name = spec['name'] 177 else: 178 spec = None 179 name = f'attr-{attr_id}' 180 self._entries[name] = (spec, decoded) 181 182 def __getitem__(self, name): 183 """Descend into a nested policy by attribute name.""" 184 if name not in self._cache: 185 spec, decoded = self._entries[name] 186 props = dict(decoded) 187 child_idx = None 188 child_set = None 189 if 'policy-idx' in props: 190 child_idx = props.pop('policy-idx') 191 if spec and 'nested-attributes' in spec.yaml: 192 child_set = self._ynl.attr_sets[spec.yaml['nested-attributes']] 193 self._cache[name] = NlPolicy(self._ynl, child_idx, 194 self._policy_table, 195 child_set, props) 196 return self._cache[name] 197 198 def __getattr__(self, name): 199 """Access this policy entry's own properties (type, min-value, etc.). 200 201 Underscores in the name are converted to dashes, so that 202 pol.min_value looks up "min-value". 203 """ 204 key = name.replace('_', '-') 205 try: 206 # Hack for level-0 which we still want to have .type but we don't 207 # want type to pointlessly show up in the dict / JSON form. 208 if not self._props and name == "type": 209 return "nested" 210 return self._props[key] 211 except KeyError: 212 raise AttributeError(name) 213 214 def get(self, name, default=None): 215 """Look up a child policy entry by attribute name, with a default.""" 216 try: 217 return self[name] 218 except KeyError: 219 return default 220 221 def __contains__(self, name): 222 return name in self._entries 223 224 def __len__(self): 225 return len(self._entries) 226 227 def __iter__(self): 228 return iter(self._entries) 229 230 def keys(self): 231 """Return attribute names accepted by this policy.""" 232 return self._entries.keys() 233 234 def to_dict(self, seen=None): 235 """Convert to a plain dict, suitable for JSON serialization. 236 237 Nested NlPolicy objects are expanded recursively. Cyclic 238 references are trimmed (resolved to just {"type": "nested"}). 239 """ 240 if seen is None: 241 seen = set() 242 result = dict(self._props) 243 if self._policy_idx is not None: 244 if self._policy_idx not in seen: 245 seen = seen | {self._policy_idx} 246 children = {} 247 for name in self: 248 children[name] = self[name].to_dict(seen) 249 if self._props: 250 result['policy'] = children 251 else: 252 result = children 253 return result 254 255 def __repr__(self): 256 return repr(self.to_dict()) 257 258 259class NlAttr: 260 ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) 261 type_formats = { 262 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), 263 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), 264 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")), 265 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")), 266 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")), 267 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")), 268 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")), 269 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q")) 270 } 271 272 def __init__(self, raw, offset): 273 self._len, self._type = struct.unpack("HH", raw[offset : offset + 4]) 274 self.type = self._type & ~Netlink.NLA_TYPE_MASK 275 self.is_nest = self._type & Netlink.NLA_F_NESTED 276 self.payload_len = self._len 277 self.full_len = (self.payload_len + 3) & ~3 278 self.raw = raw[offset + 4 : offset + self.payload_len] 279 280 @classmethod 281 def get_format(cls, attr_type, byte_order=None): 282 format_ = cls.type_formats[attr_type] 283 if byte_order: 284 return format_.big if byte_order == "big-endian" \ 285 else format_.little 286 return format_.native 287 288 def as_scalar(self, attr_type, byte_order=None): 289 format_ = self.get_format(attr_type, byte_order) 290 return format_.unpack(self.raw)[0] 291 292 def as_auto_scalar(self, attr_type, byte_order=None): 293 if len(self.raw) != 4 and len(self.raw) != 8: 294 raise YnlException(f"Auto-scalar len payload be 4 or 8 bytes, got {len(self.raw)}") 295 real_type = attr_type[0] + str(len(self.raw) * 8) 296 format_ = self.get_format(real_type, byte_order) 297 return format_.unpack(self.raw)[0] 298 299 def as_strz(self): 300 return self.raw.decode('ascii')[:-1] 301 302 def as_bin(self): 303 return self.raw 304 305 def as_c_array(self, c_type): 306 format_ = self.get_format(c_type) 307 return [ x[0] for x in format_.iter_unpack(self.raw) ] 308 309 def __repr__(self): 310 return f"[type:{self.type} len:{self._len}] {self.raw}" 311 312 313class NlAttrs: 314 def __init__(self, msg, offset=0): 315 self.attrs = [] 316 317 while offset < len(msg): 318 attr = NlAttr(msg, offset) 319 offset += attr.full_len 320 self.attrs.append(attr) 321 322 def __iter__(self): 323 yield from self.attrs 324 325 def __repr__(self): 326 msg = '' 327 for a in self.attrs: 328 if msg: 329 msg += '\n' 330 msg += repr(a) 331 return msg 332 333 334class NlMsg: 335 def __init__(self, msg, offset, attr_space=None): 336 self.hdr = msg[offset : offset + 16] 337 338 self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ 339 struct.unpack("IHHII", self.hdr) 340 341 self.raw = msg[offset + 16 : offset + self.nl_len] 342 343 self.error = 0 344 self.done = 0 345 346 extack_off = None 347 if self.nl_type == Netlink.NLMSG_ERROR: 348 self.error = struct.unpack("i", self.raw[0:4])[0] 349 self.done = 1 350 extack_off = 20 351 elif self.nl_type == Netlink.NLMSG_DONE: 352 self.error = struct.unpack("i", self.raw[0:4])[0] 353 self.done = 1 354 extack_off = 4 355 356 self.extack = None 357 if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: 358 self.extack = {} 359 extack_attrs = NlAttrs(self.raw[extack_off:]) 360 for extack in extack_attrs: 361 if extack.type == Netlink.NLMSGERR_ATTR_MSG: 362 self.extack['msg'] = extack.as_strz() 363 elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: 364 self.extack['miss-type'] = extack.as_scalar('u32') 365 elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: 366 self.extack['miss-nest'] = extack.as_scalar('u32') 367 elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 368 self.extack['bad-attr-offs'] = extack.as_scalar('u32') 369 elif extack.type == Netlink.NLMSGERR_ATTR_POLICY: 370 self.extack['policy'] = _genl_decode_policy(extack.raw) 371 else: 372 if 'unknown' not in self.extack: 373 self.extack['unknown'] = [] 374 self.extack['unknown'].append(extack) 375 376 if attr_space: 377 self.annotate_extack(attr_space) 378 379 def annotate_extack(self, attr_space): 380 """ Make extack more human friendly with attribute information """ 381 382 # We don't have the ability to parse nests yet, so only do global 383 if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 384 miss_type = self.extack['miss-type'] 385 if miss_type in attr_space.attrs_by_val: 386 spec = attr_space.attrs_by_val[miss_type] 387 self.extack['miss-type'] = spec['name'] 388 if 'doc' in spec: 389 self.extack['miss-type-doc'] = spec['doc'] 390 391 def cmd(self): 392 return self.nl_type 393 394 def __repr__(self): 395 msg = (f"nl_len = {self.nl_len} ({len(self.raw)}) " 396 f"nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}") 397 if self.error: 398 msg += '\n\terror: ' + str(self.error) 399 if self.extack: 400 msg += '\n\textack: ' + repr(self.extack) 401 return msg 402 403 404# pylint: disable=too-few-public-methods 405class NlMsgs: 406 def __init__(self, data): 407 self.msgs = [] 408 409 offset = 0 410 while offset < len(data): 411 msg = NlMsg(data, offset) 412 offset += msg.nl_len 413 self.msgs.append(msg) 414 415 def __iter__(self): 416 yield from self.msgs 417 418 419def _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): 420 # we prepend length in _genl_msg_finalize() 421 if seq is None: 422 seq = random.randint(1, 1024) 423 nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 424 genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0) 425 return nlmsg + genlmsg 426 427 428def _genl_msg_finalize(msg): 429 return struct.pack("I", len(msg) + 4) + msg 430 431 432def _genl_decode_policy(raw): 433 policy = {} 434 for attr in NlAttrs(raw): 435 if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE: 436 type_ = attr.as_scalar('u32') 437 policy['type'] = Netlink.AttrType(type_).name 438 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S: 439 policy['min-value'] = attr.as_scalar('s64') 440 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S: 441 policy['max-value'] = attr.as_scalar('s64') 442 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U: 443 policy['min-value'] = attr.as_scalar('u64') 444 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U: 445 policy['max-value'] = attr.as_scalar('u64') 446 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH: 447 policy['min-length'] = attr.as_scalar('u32') 448 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH: 449 policy['max-length'] = attr.as_scalar('u32') 450 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_POLICY_IDX: 451 policy['policy-idx'] = attr.as_scalar('u32') 452 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: 453 policy['bitfield32-mask'] = attr.as_scalar('u32') 454 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK: 455 policy['mask'] = attr.as_scalar('u64') 456 return policy 457 458 459# pylint: disable=too-many-nested-blocks 460def _genl_load_families(): 461 genl_family_name_to_id = {} 462 463 with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 464 sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 465 466 msg = _genl_msg(Netlink.GENL_ID_CTRL, 467 Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 468 Netlink.CTRL_CMD_GETFAMILY, 1) 469 msg = _genl_msg_finalize(msg) 470 471 sock.send(msg, 0) 472 473 while True: 474 reply = sock.recv(128 * 1024) 475 nms = NlMsgs(reply) 476 for nl_msg in nms: 477 if nl_msg.error: 478 raise YnlException(f"Netlink error: {nl_msg.error}") 479 if nl_msg.done: 480 return genl_family_name_to_id 481 482 gm = GenlMsg(nl_msg) 483 fam = {} 484 for attr in NlAttrs(gm.raw): 485 if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: 486 fam['id'] = attr.as_scalar('u16') 487 elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: 488 fam['name'] = attr.as_strz() 489 elif attr.type == Netlink.CTRL_ATTR_MAXATTR: 490 fam['maxattr'] = attr.as_scalar('u32') 491 elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: 492 fam['mcast'] = {} 493 for entry in NlAttrs(attr.raw): 494 mcast_name = None 495 mcast_id = None 496 for entry_attr in NlAttrs(entry.raw): 497 if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: 498 mcast_name = entry_attr.as_strz() 499 elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: 500 mcast_id = entry_attr.as_scalar('u32') 501 if mcast_name and mcast_id is not None: 502 fam['mcast'][mcast_name] = mcast_id 503 if 'name' in fam and 'id' in fam: 504 genl_family_name_to_id[fam['name']] = fam 505 506 507# pylint: disable=too-many-nested-blocks 508def _genl_policy_dump(family_id, op): 509 op_policy = {} 510 policy_table = {} 511 512 with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 513 sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 514 515 msg = _genl_msg(Netlink.GENL_ID_CTRL, 516 Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 517 Netlink.CTRL_CMD_GETPOLICY, 1) 518 msg += struct.pack('HHHxx', 6, Netlink.CTRL_ATTR_FAMILY_ID, family_id) 519 msg += struct.pack('HHI', 8, Netlink.CTRL_ATTR_OP, op) 520 msg = _genl_msg_finalize(msg) 521 522 sock.send(msg, 0) 523 524 while True: 525 reply = sock.recv(128 * 1024) 526 nms = NlMsgs(reply) 527 for nl_msg in nms: 528 if nl_msg.error: 529 raise YnlException(f"Netlink error: {nl_msg.error}") 530 if nl_msg.done: 531 return op_policy, policy_table 532 533 gm = GenlMsg(nl_msg) 534 for attr in NlAttrs(gm.raw): 535 if attr.type == Netlink.CTRL_ATTR_OP_POLICY: 536 for op_attr in NlAttrs(attr.raw): 537 for method_attr in NlAttrs(op_attr.raw): 538 if method_attr.type == Netlink.CTRL_ATTR_POLICY_DO: 539 op_policy['do'] = method_attr.as_scalar('u32') 540 elif method_attr.type == Netlink.CTRL_ATTR_POLICY_DUMP: 541 op_policy['dump'] = method_attr.as_scalar('u32') 542 elif attr.type == Netlink.CTRL_ATTR_POLICY: 543 for pidx_attr in NlAttrs(attr.raw): 544 policy_idx = pidx_attr.type 545 for aid_attr in NlAttrs(pidx_attr.raw): 546 attr_id = aid_attr.type 547 decoded = _genl_decode_policy(aid_attr.raw) 548 if policy_idx not in policy_table: 549 policy_table[policy_idx] = {} 550 policy_table[policy_idx][attr_id] = decoded 551 552 553class GenlMsg: 554 def __init__(self, nl_msg): 555 self.nl = nl_msg 556 self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0) 557 self.raw = nl_msg.raw[4:] 558 self.raw_attrs = [] 559 560 def cmd(self): 561 return self.genl_cmd 562 563 def __repr__(self): 564 msg = repr(self.nl) 565 msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" 566 for a in self.raw_attrs: 567 msg += '\t\t' + repr(a) + '\n' 568 return msg 569 570 571class NetlinkProtocol: 572 def __init__(self, family_name, proto_num): 573 self.family_name = family_name 574 self.proto_num = proto_num 575 576 def _message(self, nl_type, nl_flags, seq=None): 577 if seq is None: 578 seq = random.randint(1, 1024) 579 nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 580 return nlmsg 581 582 def message(self, flags, command, _version, seq=None): 583 return self._message(command, flags, seq) 584 585 def _decode(self, nl_msg): 586 return nl_msg 587 588 def decode(self, ynl, nl_msg, op): 589 msg = self._decode(nl_msg) 590 if op is None: 591 op = ynl.rsp_by_value[msg.cmd()] 592 fixed_header_size = ynl.struct_size(op.fixed_header) 593 msg.raw_attrs = NlAttrs(msg.raw, fixed_header_size) 594 return msg 595 596 def get_mcast_id(self, mcast_name, mcast_groups): 597 if mcast_name not in mcast_groups: 598 raise YnlException(f'Multicast group "{mcast_name}" not present in the spec') 599 return mcast_groups[mcast_name].value 600 601 def msghdr_size(self): 602 return 16 603 604 605class GenlProtocol(NetlinkProtocol): 606 genl_family_name_to_id = {} 607 608 def __init__(self, family_name): 609 super().__init__(family_name, Netlink.NETLINK_GENERIC) 610 611 if not GenlProtocol.genl_family_name_to_id: 612 GenlProtocol.genl_family_name_to_id = _genl_load_families() 613 614 self.genl_family = GenlProtocol.genl_family_name_to_id[family_name] 615 self.family_id = GenlProtocol.genl_family_name_to_id[family_name]['id'] 616 617 def message(self, flags, command, version, seq=None): 618 nlmsg = self._message(self.family_id, flags, seq) 619 genlmsg = struct.pack("BBH", command, version, 0) 620 return nlmsg + genlmsg 621 622 def _decode(self, nl_msg): 623 return GenlMsg(nl_msg) 624 625 def get_mcast_id(self, mcast_name, mcast_groups): 626 if mcast_name not in self.genl_family['mcast']: 627 raise YnlException(f'Multicast group "{mcast_name}" not present in the family') 628 return self.genl_family['mcast'][mcast_name] 629 630 def msghdr_size(self): 631 return super().msghdr_size() + 4 632 633 634# pylint: disable=too-few-public-methods 635class SpaceAttrs: 636 SpecValuesPair = namedtuple('SpecValuesPair', ['spec', 'values']) 637 638 def __init__(self, attr_space, attrs, outer = None): 639 outer_scopes = outer.scopes if outer else [] 640 inner_scope = self.SpecValuesPair(attr_space, attrs) 641 self.scopes = [inner_scope] + outer_scopes 642 643 def lookup(self, name): 644 for scope in self.scopes: 645 if name in scope.spec: 646 if name in scope.values: 647 return scope.values[name] 648 spec_name = scope.spec.yaml['name'] 649 raise YnlException( 650 f"No value for '{name}' in attribute space '{spec_name}'") 651 raise YnlException(f"Attribute '{name}' not defined in any attribute-set") 652 653 654# 655# YNL implementation details. 656# 657 658 659class YnlFamily(SpecFamily): 660 """ 661 YNL family -- a Netlink interface built from a YAML spec. 662 663 Primary use of the class is to execute Netlink commands: 664 665 ynl.<op_name>(attrs, ...) 666 667 By default this will execute the <op_name> as "do", pass dump=True 668 to perform a dump operation. 669 670 ynl.<op_name> is a shorthand / convenience wrapper for the following 671 methods which take the op_name as a string: 672 673 ynl.do(op_name, attrs, flags=None) -- execute a do operation 674 ynl.dump(op_name, attrs) -- execute a dump operation 675 ynl.do_multi(ops) -- batch multiple do operations 676 677 The flags argument in ynl.do() allows passing in extra NLM_F_* flags 678 which may be necessary for old families. 679 680 Notification API: 681 682 ynl.ntf_subscribe(mcast_name) -- join a multicast group 683 ynl.check_ntf() -- drain pending notifications 684 ynl.poll_ntf(duration=None) -- yield notifications 685 686 Policy introspection allows querying validation criteria from the running 687 kernel. Allows checking whether kernel supports a given attribute or value. 688 689 ynl.get_policy(op_name, mode) -- query kernel policy for an op 690 """ 691 def __init__(self, def_path, schema=None, process_unknown=False, 692 recv_size=0): 693 super().__init__(def_path, schema) 694 695 self.include_raw = False 696 self.process_unknown = process_unknown 697 698 try: 699 if self.proto == "netlink-raw": 700 self.nlproto = NetlinkProtocol(self.yaml['name'], 701 self.yaml['protonum']) 702 else: 703 self.nlproto = GenlProtocol(self.yaml['name']) 704 except KeyError as err: 705 raise YnlException(f"Family '{self.yaml['name']}' not supported by the kernel") from err 706 707 self._recv_dbg = False 708 # Note that netlink will use conservative (min) message size for 709 # the first dump recv() on the socket, our setting will only matter 710 # from the second recv() on. 711 self._recv_size = recv_size if recv_size else 131072 712 # Netlink will always allocate at least PAGE_SIZE - sizeof(skb_shinfo) 713 # for a message, so smaller receive sizes will lead to truncation. 714 # Note that the min size for other families may be larger than 4k! 715 if self._recv_size < 4000: 716 raise ConfigError() 717 718 self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num) 719 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 720 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 721 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1) 722 723 self.async_msg_ids = set() 724 self.async_msg_queue = queue.Queue() 725 726 for msg in self.msgs.values(): 727 if msg.is_async: 728 self.async_msg_ids.add(msg.rsp_value) 729 730 for op_name, op in self.ops.items(): 731 bound_f = functools.partial(self._op, op_name) 732 setattr(self, op.ident_name, bound_f) 733 734 def close(self): 735 if self.sock is not None: 736 self.sock.close() 737 self.sock = None 738 739 def __enter__(self): 740 return self 741 742 def __exit__(self, exc_type, exc, tb): 743 self.close() 744 745 def ntf_subscribe(self, mcast_name): 746 mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups) 747 self.sock.bind((0, 0)) 748 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 749 mcast_id) 750 751 def set_recv_dbg(self, enabled): 752 self._recv_dbg = enabled 753 754 def _recv_dbg_print(self, reply, nl_msgs): 755 if not self._recv_dbg: 756 return 757 print("Recv: read", len(reply), "bytes,", 758 len(nl_msgs.msgs), "messages", file=sys.stderr) 759 for nl_msg in nl_msgs: 760 print(" ", nl_msg, file=sys.stderr) 761 762 def _encode_enum(self, attr_spec, value): 763 enum = self.consts[attr_spec['enum']] 764 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): 765 scalar = 0 766 if isinstance(value, str): 767 value = [value] 768 for single_value in value: 769 scalar += enum.entries[single_value].user_value(as_flags = True) 770 return scalar 771 return enum.entries[value].user_value() 772 773 def _get_scalar(self, attr_spec, value): 774 try: 775 return int(value) 776 except (ValueError, TypeError) as e: 777 if 'enum' in attr_spec: 778 return self._encode_enum(attr_spec, value) 779 if attr_spec.display_hint: 780 return self._from_string(value, attr_spec) 781 raise e 782 783 # pylint: disable=too-many-statements 784 def _add_attr(self, space, name, value, search_attrs): 785 try: 786 attr = self.attr_sets[space][name] 787 except KeyError as err: 788 raise YnlException(f"Space '{space}' has no attribute '{name}'") from err 789 nl_type = attr.value 790 791 if attr.is_multi and isinstance(value, list): 792 attr_payload = b'' 793 for subvalue in value: 794 attr_payload += self._add_attr(space, name, subvalue, search_attrs) 795 return attr_payload 796 797 if attr["type"] == 'nest': 798 nl_type |= Netlink.NLA_F_NESTED 799 sub_space = attr['nested-attributes'] 800 attr_payload = self._add_nest_attrs(value, sub_space, search_attrs) 801 elif attr['type'] == 'indexed-array' and attr['sub-type'] == 'nest': 802 nl_type |= Netlink.NLA_F_NESTED 803 sub_space = attr['nested-attributes'] 804 attr_payload = self._encode_indexed_array(value, sub_space, 805 search_attrs) 806 elif attr["type"] == 'flag': 807 if not value: 808 # If value is absent or false then skip attribute creation. 809 return b'' 810 attr_payload = b'' 811 elif attr["type"] == 'string': 812 attr_payload = str(value).encode('ascii') + b'\x00' 813 elif attr["type"] == 'binary': 814 if value is None: 815 attr_payload = b'' 816 elif isinstance(value, bytes): 817 attr_payload = value 818 elif isinstance(value, str): 819 if attr.display_hint: 820 attr_payload = self._from_string(value, attr) 821 else: 822 attr_payload = bytes.fromhex(value) 823 elif isinstance(value, dict) and attr.struct_name: 824 attr_payload = self._encode_struct(attr.struct_name, value) 825 elif isinstance(value, list) and attr.sub_type in NlAttr.type_formats: 826 format_ = NlAttr.get_format(attr.sub_type) 827 attr_payload = b''.join([format_.pack(x) for x in value]) 828 else: 829 raise YnlException(f'Unknown type for binary attribute, value: {value}') 830 elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar: 831 scalar = self._get_scalar(attr, value) 832 if attr.is_auto_scalar: 833 attr_type = attr["type"][0] + ('32' if scalar.bit_length() <= 32 else '64') 834 else: 835 attr_type = attr["type"] 836 format_ = NlAttr.get_format(attr_type, attr.byte_order) 837 attr_payload = format_.pack(scalar) 838 elif attr['type'] in "bitfield32": 839 scalar_value = self._get_scalar(attr, value["value"]) 840 scalar_selector = self._get_scalar(attr, value["selector"]) 841 attr_payload = struct.pack("II", scalar_value, scalar_selector) 842 elif attr['type'] == 'sub-message': 843 msg_format, _ = self._resolve_selector(attr, search_attrs) 844 attr_payload = b'' 845 if msg_format.fixed_header: 846 attr_payload += self._encode_struct(msg_format.fixed_header, value) 847 if msg_format.attr_set: 848 if msg_format.attr_set in self.attr_sets: 849 nl_type |= Netlink.NLA_F_NESTED 850 sub_attrs = SpaceAttrs(msg_format.attr_set, value, search_attrs) 851 for subname, subvalue in value.items(): 852 attr_payload += self._add_attr(msg_format.attr_set, 853 subname, subvalue, sub_attrs) 854 else: 855 raise YnlException(f"Unknown attribute-set '{msg_format.attr_set}'") 856 else: 857 raise YnlException(f'Unknown type at {space} {name} {value} {attr["type"]}') 858 859 return self._add_attr_raw(nl_type, attr_payload) 860 861 def _add_attr_raw(self, nl_type, attr_payload): 862 pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 863 return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 864 865 def _add_nest_attrs(self, value, sub_space, search_attrs): 866 sub_attrs = SpaceAttrs(self.attr_sets[sub_space], value, search_attrs) 867 attr_payload = b'' 868 for subname, subvalue in value.items(): 869 attr_payload += self._add_attr(sub_space, subname, subvalue, 870 sub_attrs) 871 return attr_payload 872 873 def _encode_indexed_array(self, vals, sub_space, search_attrs): 874 attr_payload = b'' 875 for i, val in enumerate(vals): 876 idx = i | Netlink.NLA_F_NESTED 877 val_payload = self._add_nest_attrs(val, sub_space, search_attrs) 878 attr_payload += self._add_attr_raw(idx, val_payload) 879 return attr_payload 880 881 def _get_enum_or_unknown(self, enum, raw): 882 try: 883 name = enum.entries_by_val[raw].name 884 except KeyError as error: 885 if self.process_unknown: 886 name = f"Unknown({raw})" 887 else: 888 raise error 889 return name 890 891 def _decode_enum(self, raw, attr_spec): 892 enum = self.consts[attr_spec['enum']] 893 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): 894 i = 0 895 value = set() 896 while raw: 897 if raw & 1: 898 value.add(self._get_enum_or_unknown(enum, i)) 899 raw >>= 1 900 i += 1 901 else: 902 value = self._get_enum_or_unknown(enum, raw) 903 return value 904 905 def _decode_binary(self, attr, attr_spec): 906 if attr_spec.struct_name: 907 decoded = self._decode_struct(attr.raw, attr_spec.struct_name) 908 elif attr_spec.sub_type: 909 decoded = attr.as_c_array(attr_spec.sub_type) 910 if 'enum' in attr_spec: 911 decoded = [ self._decode_enum(x, attr_spec) for x in decoded ] 912 elif attr_spec.display_hint: 913 decoded = [ self._formatted_string(x, attr_spec.display_hint) 914 for x in decoded ] 915 else: 916 decoded = attr.as_bin() 917 if attr_spec.display_hint: 918 decoded = self._formatted_string(decoded, attr_spec.display_hint) 919 return decoded 920 921 def _decode_array_attr(self, attr, attr_spec): 922 decoded = [] 923 offset = 0 924 while offset < len(attr.raw): 925 item = NlAttr(attr.raw, offset) 926 offset += item.full_len 927 928 if attr_spec["sub-type"] == 'nest': 929 subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes']) 930 decoded.append({ item.type: subattrs }) 931 elif attr_spec["sub-type"] == 'binary': 932 subattr = item.as_bin() 933 if attr_spec.display_hint: 934 subattr = self._formatted_string(subattr, attr_spec.display_hint) 935 decoded.append(subattr) 936 elif attr_spec["sub-type"] in NlAttr.type_formats: 937 subattr = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order) 938 if 'enum' in attr_spec: 939 subattr = self._decode_enum(subattr, attr_spec) 940 elif attr_spec.display_hint: 941 subattr = self._formatted_string(subattr, attr_spec.display_hint) 942 decoded.append(subattr) 943 else: 944 raise YnlException(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}') 945 return decoded 946 947 def _decode_nest_type_value(self, attr, attr_spec): 948 decoded = {} 949 value = attr 950 for name in attr_spec['type-value']: 951 value = NlAttr(value.raw, 0) 952 decoded[name] = value.type 953 subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes']) 954 decoded.update(subattrs) 955 return decoded 956 957 def _decode_unknown(self, attr): 958 if attr.is_nest: 959 return self._decode(NlAttrs(attr.raw), None) 960 return attr.as_bin() 961 962 def _rsp_add(self, rsp, name, is_multi, decoded): 963 if is_multi is None: 964 if name in rsp and not isinstance(rsp[name], list): 965 rsp[name] = [rsp[name]] 966 is_multi = True 967 else: 968 is_multi = False 969 970 if not is_multi: 971 rsp[name] = decoded 972 elif name in rsp: 973 rsp[name].append(decoded) 974 else: 975 rsp[name] = [decoded] 976 977 def _resolve_selector(self, attr_spec, search_attrs): 978 sub_msg = attr_spec.sub_message 979 if sub_msg not in self.sub_msgs: 980 raise YnlException(f"No sub-message spec named {sub_msg} for {attr_spec.name}") 981 sub_msg_spec = self.sub_msgs[sub_msg] 982 983 selector = attr_spec.selector 984 value = search_attrs.lookup(selector) 985 if value not in sub_msg_spec.formats: 986 raise YnlException(f"No message format for '{value}' in sub-message spec '{sub_msg}'") 987 988 spec = sub_msg_spec.formats[value] 989 return spec, value 990 991 def _decode_sub_msg(self, attr, attr_spec, search_attrs): 992 msg_format, _ = self._resolve_selector(attr_spec, search_attrs) 993 decoded = {} 994 offset = 0 995 if msg_format.fixed_header: 996 decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header)) 997 offset = self.struct_size(msg_format.fixed_header) 998 if msg_format.attr_set: 999 if msg_format.attr_set in self.attr_sets: 1000 subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set) 1001 decoded.update(subdict) 1002 else: 1003 raise YnlException(f"Unknown attribute-set '{msg_format.attr_set}' " 1004 f"when decoding '{attr_spec.name}'") 1005 return decoded 1006 1007 # pylint: disable=too-many-statements 1008 def _decode(self, attrs, space, outer_attrs = None): 1009 rsp = {} 1010 search_attrs = {} 1011 if space: 1012 attr_space = self.attr_sets[space] 1013 search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs) 1014 1015 for attr in attrs: 1016 try: 1017 attr_spec = attr_space.attrs_by_val[attr.type] 1018 except (KeyError, UnboundLocalError) as err: 1019 if not self.process_unknown: 1020 raise YnlException(f"Space '{space}' has no attribute " 1021 f"with value '{attr.type}'") from err 1022 attr_name = f"UnknownAttr({attr.type})" 1023 self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr)) 1024 continue 1025 1026 try: 1027 if attr_spec["type"] == 'pad': 1028 continue 1029 elif attr_spec["type"] == 'nest': 1030 subdict = self._decode(NlAttrs(attr.raw), 1031 attr_spec['nested-attributes'], 1032 search_attrs) 1033 decoded = subdict 1034 elif attr_spec["type"] == 'string': 1035 decoded = attr.as_strz() 1036 elif attr_spec["type"] == 'binary': 1037 decoded = self._decode_binary(attr, attr_spec) 1038 elif attr_spec["type"] == 'flag': 1039 decoded = True 1040 elif attr_spec.is_auto_scalar: 1041 decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order) 1042 if 'enum' in attr_spec: 1043 decoded = self._decode_enum(decoded, attr_spec) 1044 elif attr_spec["type"] in NlAttr.type_formats: 1045 decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) 1046 if 'enum' in attr_spec: 1047 decoded = self._decode_enum(decoded, attr_spec) 1048 elif attr_spec.display_hint: 1049 decoded = self._formatted_string(decoded, attr_spec.display_hint) 1050 elif attr_spec["type"] == 'indexed-array': 1051 decoded = self._decode_array_attr(attr, attr_spec) 1052 elif attr_spec["type"] == 'bitfield32': 1053 value, selector = struct.unpack("II", attr.raw) 1054 if 'enum' in attr_spec: 1055 value = self._decode_enum(value, attr_spec) 1056 selector = self._decode_enum(selector, attr_spec) 1057 decoded = {"value": value, "selector": selector} 1058 elif attr_spec["type"] == 'sub-message': 1059 decoded = self._decode_sub_msg(attr, attr_spec, search_attrs) 1060 elif attr_spec["type"] == 'nest-type-value': 1061 decoded = self._decode_nest_type_value(attr, attr_spec) 1062 else: 1063 if not self.process_unknown: 1064 raise YnlException(f'Unknown {attr_spec["type"]} ' 1065 f'with name {attr_spec["name"]}') 1066 decoded = self._decode_unknown(attr) 1067 1068 self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded) 1069 except: 1070 print(f"Error decoding '{attr_spec.name}' from '{space}'") 1071 raise 1072 1073 return rsp 1074 1075 # pylint: disable=too-many-arguments, too-many-positional-arguments 1076 def _decode_extack_path(self, attrs, attr_set, offset, target, search_attrs): 1077 for attr in attrs: 1078 try: 1079 attr_spec = attr_set.attrs_by_val[attr.type] 1080 except KeyError as err: 1081 raise YnlException( 1082 f"Space '{attr_set.name}' has no attribute with value '{attr.type}'") from err 1083 if offset > target: 1084 break 1085 if offset == target: 1086 return '.' + attr_spec.name 1087 1088 if offset + attr.full_len <= target: 1089 offset += attr.full_len 1090 continue 1091 1092 pathname = attr_spec.name 1093 if attr_spec['type'] == 'nest': 1094 sub_attrs = self.attr_sets[attr_spec['nested-attributes']] 1095 search_attrs = SpaceAttrs(sub_attrs, search_attrs.lookup(attr_spec['name'])) 1096 elif attr_spec['type'] == 'sub-message': 1097 msg_format, value = self._resolve_selector(attr_spec, search_attrs) 1098 if msg_format is None: 1099 raise YnlException(f"Can't resolve sub-message of " 1100 f"{attr_spec['name']} for extack") 1101 sub_attrs = self.attr_sets[msg_format.attr_set] 1102 pathname += f"({value})" 1103 else: 1104 raise YnlException(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 1105 offset += 4 1106 subpath = self._decode_extack_path(NlAttrs(attr.raw), sub_attrs, 1107 offset, target, search_attrs) 1108 if subpath is None: 1109 return None 1110 return '.' + pathname + subpath 1111 1112 return None 1113 1114 def _decode_extack(self, request, op, extack, vals): 1115 if 'bad-attr-offs' not in extack: 1116 return 1117 1118 msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op) 1119 offset = self.nlproto.msghdr_size() + self.struct_size(op.fixed_header) 1120 search_attrs = SpaceAttrs(op.attr_set, vals) 1121 path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, 1122 extack['bad-attr-offs'], search_attrs) 1123 if path: 1124 del extack['bad-attr-offs'] 1125 extack['bad-attr'] = path 1126 1127 def struct_size(self, name): 1128 if name: 1129 members = self.consts[name].members 1130 size = 0 1131 for m in members: 1132 if m.type in ['pad', 'binary']: 1133 if m.struct: 1134 size += self.struct_size(m.struct) 1135 else: 1136 size += m.len 1137 else: 1138 format_ = NlAttr.get_format(m.type, m.byte_order) 1139 size += format_.size 1140 return size 1141 return 0 1142 1143 def _decode_struct(self, data, name): 1144 members = self.consts[name].members 1145 attrs = {} 1146 offset = 0 1147 for m in members: 1148 value = None 1149 if m.type == 'pad': 1150 offset += m.len 1151 elif m.type == 'binary': 1152 if m.struct: 1153 len_ = self.struct_size(m.struct) 1154 value = self._decode_struct(data[offset : offset + len_], 1155 m.struct) 1156 offset += len_ 1157 else: 1158 value = data[offset : offset + m.len] 1159 offset += m.len 1160 else: 1161 format_ = NlAttr.get_format(m.type, m.byte_order) 1162 [ value ] = format_.unpack_from(data, offset) 1163 offset += format_.size 1164 if value is not None: 1165 if m.enum: 1166 value = self._decode_enum(value, m) 1167 elif m.display_hint: 1168 value = self._formatted_string(value, m.display_hint) 1169 attrs[m.name] = value 1170 return attrs 1171 1172 def _encode_struct(self, name, vals): 1173 members = self.consts[name].members 1174 attr_payload = b'' 1175 for m in members: 1176 value = vals.pop(m.name) if m.name in vals else None 1177 if m.type == 'pad': 1178 attr_payload += bytearray(m.len) 1179 elif m.type == 'binary': 1180 if m.struct: 1181 if value is None: 1182 value = {} 1183 attr_payload += self._encode_struct(m.struct, value) 1184 else: 1185 if value is None: 1186 attr_payload += bytearray(m.len) 1187 else: 1188 attr_payload += bytes.fromhex(value) 1189 else: 1190 if value is None: 1191 value = 0 1192 format_ = NlAttr.get_format(m.type, m.byte_order) 1193 attr_payload += format_.pack(value) 1194 return attr_payload 1195 1196 def _formatted_string(self, raw, display_hint): 1197 if display_hint == 'mac': 1198 formatted = ':'.join(f'{b:02x}' for b in raw) 1199 elif display_hint == 'hex': 1200 if isinstance(raw, int): 1201 formatted = hex(raw) 1202 else: 1203 formatted = bytes.hex(raw, ' ') 1204 elif display_hint in [ 'ipv4', 'ipv6', 'ipv4-or-v6' ]: 1205 formatted = format(ipaddress.ip_address(raw)) 1206 elif display_hint == 'uuid': 1207 formatted = str(uuid.UUID(bytes=raw)) 1208 else: 1209 formatted = raw 1210 return formatted 1211 1212 def _from_string(self, string, attr_spec): 1213 if attr_spec.display_hint in ['ipv4', 'ipv6', 'ipv4-or-v6']: 1214 ip = ipaddress.ip_address(string) 1215 if attr_spec['type'] == 'binary': 1216 raw = ip.packed 1217 else: 1218 raw = int(ip) 1219 elif attr_spec.display_hint == 'hex': 1220 if attr_spec['type'] == 'binary': 1221 raw = bytes.fromhex(string) 1222 else: 1223 raw = int(string, 16) 1224 elif attr_spec.display_hint == 'mac': 1225 # Parse MAC address in format "00:11:22:33:44:55" or "001122334455" 1226 if ':' in string: 1227 mac_bytes = [int(x, 16) for x in string.split(':')] 1228 else: 1229 if len(string) % 2 != 0: 1230 raise YnlException(f"Invalid MAC address format: {string}") 1231 mac_bytes = [int(string[i:i+2], 16) for i in range(0, len(string), 2)] 1232 raw = bytes(mac_bytes) 1233 else: 1234 raise YnlException(f"Display hint '{attr_spec.display_hint}' not implemented" 1235 f" when parsing '{attr_spec['name']}'") 1236 return raw 1237 1238 def handle_ntf(self, decoded): 1239 msg = {} 1240 if self.include_raw: 1241 msg['raw'] = decoded 1242 op = self.rsp_by_value[decoded.cmd()] 1243 attrs = self._decode(decoded.raw_attrs, op.attr_set.name) 1244 if op.fixed_header: 1245 attrs.update(self._decode_struct(decoded.raw, op.fixed_header)) 1246 1247 msg['name'] = op['name'] 1248 msg['msg'] = attrs 1249 self.async_msg_queue.put(msg) 1250 1251 def check_ntf(self): 1252 while True: 1253 try: 1254 reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT) 1255 except BlockingIOError: 1256 return 1257 1258 nms = NlMsgs(reply) 1259 self._recv_dbg_print(reply, nms) 1260 for nl_msg in nms: 1261 if nl_msg.error: 1262 print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 1263 print(nl_msg) 1264 continue 1265 if nl_msg.done: 1266 print("Netlink done while checking for ntf!?") 1267 continue 1268 1269 decoded = self.nlproto.decode(self, nl_msg, None) 1270 if decoded.cmd() not in self.async_msg_ids: 1271 print("Unexpected msg id while checking for ntf", decoded) 1272 continue 1273 1274 self.handle_ntf(decoded) 1275 1276 def poll_ntf(self, duration=None): 1277 start_time = time.time() 1278 selector = selectors.DefaultSelector() 1279 selector.register(self.sock, selectors.EVENT_READ) 1280 1281 while True: 1282 try: 1283 yield self.async_msg_queue.get_nowait() 1284 except queue.Empty: 1285 if duration is not None: 1286 timeout = start_time + duration - time.time() 1287 if timeout <= 0: 1288 return 1289 else: 1290 timeout = None 1291 events = selector.select(timeout) 1292 if events: 1293 self.check_ntf() 1294 1295 def operation_do_attributes(self, name): 1296 """ 1297 For a given operation name, find and return a supported 1298 set of attributes (as a dict). 1299 """ 1300 op = self.find_operation(name) 1301 if not op: 1302 return None 1303 1304 return op['do']['request']['attributes'].copy() 1305 1306 def _encode_message(self, op, vals, flags, req_seq): 1307 nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 1308 for flag in flags or []: 1309 nl_flags |= flag 1310 1311 msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq) 1312 if op.fixed_header: 1313 msg += self._encode_struct(op.fixed_header, vals) 1314 search_attrs = SpaceAttrs(op.attr_set, vals) 1315 for name, value in vals.items(): 1316 msg += self._add_attr(op.attr_set.name, name, value, search_attrs) 1317 msg = _genl_msg_finalize(msg) 1318 return msg 1319 1320 # pylint: disable=too-many-statements 1321 def _ops(self, ops): 1322 reqs_by_seq = {} 1323 req_seq = random.randint(1024, 65535) 1324 payload = b'' 1325 for (method, vals, flags) in ops: 1326 op = self.ops[method] 1327 msg = self._encode_message(op, vals, flags, req_seq) 1328 reqs_by_seq[req_seq] = (op, vals, msg, flags) 1329 payload += msg 1330 req_seq += 1 1331 1332 self.sock.send(payload, 0) 1333 1334 done = False 1335 rsp = [] 1336 op_rsp = [] 1337 while not done: 1338 reply = self.sock.recv(self._recv_size) 1339 nms = NlMsgs(reply) 1340 self._recv_dbg_print(reply, nms) 1341 for nl_msg in nms: 1342 if nl_msg.nl_seq in reqs_by_seq: 1343 (op, vals, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq] 1344 if nl_msg.extack: 1345 nl_msg.annotate_extack(op.attr_set) 1346 self._decode_extack(req_msg, op, nl_msg.extack, vals) 1347 else: 1348 op = None 1349 req_flags = [] 1350 1351 if nl_msg.error: 1352 raise NlError(nl_msg) 1353 if nl_msg.done: 1354 if nl_msg.extack: 1355 print("Netlink warning:") 1356 print(nl_msg) 1357 1358 if Netlink.NLM_F_DUMP in req_flags: 1359 rsp.append(op_rsp) 1360 elif not op_rsp: 1361 rsp.append(None) 1362 elif len(op_rsp) == 1: 1363 rsp.append(op_rsp[0]) 1364 else: 1365 rsp.append(op_rsp) 1366 op_rsp = [] 1367 1368 del reqs_by_seq[nl_msg.nl_seq] 1369 done = len(reqs_by_seq) == 0 1370 break 1371 1372 decoded = self.nlproto.decode(self, nl_msg, op) 1373 1374 # Check if this is a reply to our request 1375 if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value: 1376 if decoded.cmd() in self.async_msg_ids: 1377 self.handle_ntf(decoded) 1378 continue 1379 print('Unexpected message: ' + repr(decoded)) 1380 continue 1381 1382 rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name) 1383 if op.fixed_header: 1384 rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header)) 1385 op_rsp.append(rsp_msg) 1386 1387 return rsp 1388 1389 def _op(self, method, vals, flags=None, dump=False): 1390 req_flags = flags or [] 1391 if dump: 1392 req_flags.append(Netlink.NLM_F_DUMP) 1393 1394 ops = [(method, vals, req_flags)] 1395 return self._ops(ops)[0] 1396 1397 def do(self, method, vals, flags=None): 1398 return self._op(method, vals, flags) 1399 1400 def dump(self, method, vals): 1401 return self._op(method, vals, dump=True) 1402 1403 def do_multi(self, ops): 1404 return self._ops(ops) 1405 1406 def get_policy(self, op_name, mode): 1407 """Query running kernel for the Netlink policy of an operation. 1408 1409 Allows checking whether kernel supports a given attribute or value. 1410 This method consults the running kernel, not the YAML spec. 1411 1412 Args: 1413 op_name: operation name as it appears in the YAML spec 1414 mode: 'do' or 'dump' 1415 1416 Returns: 1417 NlPolicy acting as a read-only dict mapping attribute names 1418 to their policy properties (type, min/max, nested, etc.), 1419 or None if the operation has no policy for the given mode. 1420 Empty policy usually implies that the operation rejects 1421 all attributes. 1422 """ 1423 op = self.ops[op_name] 1424 op_policy, policy_table = _genl_policy_dump(self.nlproto.family_id, 1425 op.req_value) 1426 if mode not in op_policy: 1427 return None 1428 policy_idx = op_policy[mode] 1429 return NlPolicy(self, policy_idx, policy_table, op.attr_set) 1430