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