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 Primary use of the class is to execute Netlink commands: 665 666 ynl.<op_name>(attrs, ...) 667 668 By default this will execute the <op_name> as "do", pass dump=True 669 to perform a dump operation. 670 671 ynl.<op_name> is a shorthand / convenience wrapper for the following 672 methods which take the op_name as a string: 673 674 ynl.do(op_name, attrs, flags=None) -- execute a do operation 675 ynl.dump(op_name, attrs) -- execute a dump operation 676 ynl.do_multi(ops) -- batch multiple do operations 677 678 The flags argument in ynl.do() allows passing in extra NLM_F_* flags 679 which may be necessary for old families. 680 681 Notification API: 682 683 ynl.ntf_subscribe(mcast_name) -- join a multicast group 684 ynl.ntf_listen_all_nsid() -- listen on all netns 685 ynl.ntf_bind(addr=(0, 0)) -- bind socket for unicast notifications 686 ynl.check_ntf() -- drain pending notifications 687 ynl.poll_ntf(duration=None) -- yield notifications 688 689 Policy introspection allows querying validation criteria from the running 690 kernel. Allows checking whether kernel supports a given attribute or value. 691 692 ynl.get_policy(op_name, mode) -- query kernel policy for an op 693 """ 694 def __init__(self, def_path, schema=None, process_unknown=False, 695 recv_size=0): 696 super().__init__(def_path, schema) 697 698 self.include_raw = False 699 self.process_unknown = process_unknown 700 701 try: 702 if self.proto == "netlink-raw": 703 self.nlproto = NetlinkProtocol(self.yaml['name'], 704 self.yaml['protonum']) 705 else: 706 self.nlproto = GenlProtocol(self.yaml['name']) 707 except KeyError as err: 708 raise YnlException(f"Family '{self.yaml['name']}' not supported by the kernel") from err 709 710 self._recv_dbg = False 711 # Note that netlink will use conservative (min) message size for 712 # the first dump recv() on the socket, our setting will only matter 713 # from the second recv() on. 714 self._recv_size = recv_size if recv_size else 131072 715 # Netlink will always allocate at least PAGE_SIZE - sizeof(skb_shinfo) 716 # for a message, so smaller receive sizes will lead to truncation. 717 # Note that the min size for other families may be larger than 4k! 718 if self._recv_size < 4000: 719 raise ConfigError() 720 721 self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num) 722 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 723 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 724 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1) 725 726 self.async_msg_ids = set() 727 self.async_msg_queue = queue.Queue() 728 729 for msg in self.msgs.values(): 730 if msg.is_async: 731 self.async_msg_ids.add(msg.rsp_value) 732 733 for op_name, op in self.ops.items(): 734 bound_f = functools.partial(self._op, op_name) 735 setattr(self, op.ident_name, bound_f) 736 737 def close(self): 738 if self.sock is not None: 739 self.sock.close() 740 self.sock = None 741 742 def __enter__(self): 743 return self 744 745 def __exit__(self, exc_type, exc, tb): 746 self.close() 747 748 def ntf_subscribe(self, mcast_name): 749 mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups) 750 self.sock.bind((0, 0)) 751 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 752 mcast_id) 753 754 def ntf_listen_all_nsid(self): 755 """Enable NETLINK_LISTEN_ALL_NSID to receive notifications from all 756 namespaces that have an nsid mapped in the current one.""" 757 self.sock.setsockopt(Netlink.SOL_NETLINK, 758 Netlink.NETLINK_LISTEN_ALL_NSID, 1) 759 760 @staticmethod 761 def _decode_nsid(ancdata): 762 for cmsg_level, cmsg_type, cmsg_data in ancdata: 763 if (cmsg_level == Netlink.SOL_NETLINK and 764 cmsg_type == Netlink.NETLINK_LISTEN_ALL_NSID): 765 nsid = struct.unpack('i', cmsg_data)[0] 766 if nsid >= 0: 767 return nsid 768 return None 769 return None 770 771 def ntf_bind(self, addr=(0, 0)): 772 """Bind socket for receiving unicast notifications.""" 773 self.sock.bind(addr) 774 775 def set_recv_dbg(self, enabled): 776 self._recv_dbg = enabled 777 778 def _recv_dbg_print(self, reply, nl_msgs): 779 if not self._recv_dbg: 780 return 781 print("Recv: read", len(reply), "bytes,", 782 len(nl_msgs.msgs), "messages", file=sys.stderr) 783 for nl_msg in nl_msgs: 784 print(" ", nl_msg, file=sys.stderr) 785 786 def _encode_enum(self, attr_spec, value): 787 enum = self.consts[attr_spec['enum']] 788 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): 789 scalar = 0 790 if isinstance(value, str): 791 value = [value] 792 for single_value in value: 793 scalar += enum.entries[single_value].user_value(as_flags = True) 794 return scalar 795 return enum.entries[value].user_value() 796 797 def _get_scalar(self, attr_spec, value): 798 try: 799 return int(value) 800 except (ValueError, TypeError) as e: 801 if 'enum' in attr_spec: 802 return self._encode_enum(attr_spec, value) 803 if attr_spec.display_hint: 804 return self._from_string(value, attr_spec) 805 raise e 806 807 # pylint: disable=too-many-statements 808 def _add_attr(self, space, name, value, search_attrs): 809 try: 810 attr = self.attr_sets[space][name] 811 except KeyError as err: 812 raise YnlException(f"Space '{space}' has no attribute '{name}'") from err 813 nl_type = attr.value 814 815 if attr.is_multi and isinstance(value, list): 816 attr_payload = b'' 817 for subvalue in value: 818 attr_payload += self._add_attr(space, name, subvalue, search_attrs) 819 return attr_payload 820 821 if attr["type"] == 'nest': 822 nl_type |= Netlink.NLA_F_NESTED 823 sub_space = attr['nested-attributes'] 824 attr_payload = self._add_nest_attrs(value, sub_space, search_attrs) 825 elif attr['type'] == 'indexed-array' and attr['sub-type'] == 'nest': 826 nl_type |= Netlink.NLA_F_NESTED 827 sub_space = attr['nested-attributes'] 828 attr_payload = self._encode_indexed_array(value, sub_space, 829 search_attrs) 830 elif attr["type"] == 'flag': 831 if not value: 832 # If value is absent or false then skip attribute creation. 833 return b'' 834 attr_payload = b'' 835 elif attr["type"] == 'string': 836 attr_payload = str(value).encode('ascii') + b'\x00' 837 elif attr["type"] == 'binary': 838 if value is None: 839 attr_payload = b'' 840 elif isinstance(value, bytes): 841 attr_payload = value 842 elif isinstance(value, str): 843 if attr.display_hint: 844 attr_payload = self._from_string(value, attr) 845 else: 846 attr_payload = bytes.fromhex(value) 847 elif isinstance(value, dict) and attr.struct_name: 848 attr_payload = self._encode_struct(attr.struct_name, value) 849 elif isinstance(value, list) and attr.sub_type in NlAttr.type_formats: 850 format_ = NlAttr.get_format(attr.sub_type) 851 attr_payload = b''.join([format_.pack(x) for x in value]) 852 else: 853 raise YnlException(f'Unknown type for binary attribute, value: {value}') 854 elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar: 855 scalar = self._get_scalar(attr, value) 856 if attr.is_auto_scalar: 857 attr_type = attr["type"][0] + ('32' if scalar.bit_length() <= 32 else '64') 858 else: 859 attr_type = attr["type"] 860 format_ = NlAttr.get_format(attr_type, attr.byte_order) 861 attr_payload = format_.pack(scalar) 862 elif attr['type'] in "bitfield32": 863 scalar_value = self._get_scalar(attr, value["value"]) 864 scalar_selector = self._get_scalar(attr, value["selector"]) 865 attr_payload = struct.pack("II", scalar_value, scalar_selector) 866 elif attr['type'] == 'sub-message': 867 msg_format, _ = self._resolve_selector(attr, search_attrs) 868 attr_payload = b'' 869 if msg_format.fixed_header: 870 attr_payload += self._encode_struct(msg_format.fixed_header, value) 871 if msg_format.attr_set: 872 if msg_format.attr_set in self.attr_sets: 873 nl_type |= Netlink.NLA_F_NESTED 874 sub_attrs = SpaceAttrs(msg_format.attr_set, value, search_attrs) 875 for subname, subvalue in value.items(): 876 attr_payload += self._add_attr(msg_format.attr_set, 877 subname, subvalue, sub_attrs) 878 else: 879 raise YnlException(f"Unknown attribute-set '{msg_format.attr_set}'") 880 else: 881 raise YnlException(f'Unknown type at {space} {name} {value} {attr["type"]}') 882 883 return self._add_attr_raw(nl_type, attr_payload) 884 885 def _add_attr_raw(self, nl_type, attr_payload): 886 pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 887 return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 888 889 def _add_nest_attrs(self, value, sub_space, search_attrs): 890 sub_attrs = SpaceAttrs(self.attr_sets[sub_space], value, search_attrs) 891 attr_payload = b'' 892 for subname, subvalue in value.items(): 893 attr_payload += self._add_attr(sub_space, subname, subvalue, 894 sub_attrs) 895 return attr_payload 896 897 def _encode_indexed_array(self, vals, sub_space, search_attrs): 898 attr_payload = b'' 899 for i, val in enumerate(vals): 900 idx = i | Netlink.NLA_F_NESTED 901 val_payload = self._add_nest_attrs(val, sub_space, search_attrs) 902 attr_payload += self._add_attr_raw(idx, val_payload) 903 return attr_payload 904 905 def _get_enum_or_unknown(self, enum, raw): 906 try: 907 name = enum.entries_by_val[raw].name 908 except KeyError as error: 909 if self.process_unknown: 910 name = f"Unknown({raw})" 911 else: 912 raise error 913 return name 914 915 def _decode_enum(self, raw, attr_spec): 916 enum = self.consts[attr_spec['enum']] 917 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): 918 i = 0 919 value = set() 920 while raw: 921 if raw & 1: 922 value.add(self._get_enum_or_unknown(enum, i)) 923 raw >>= 1 924 i += 1 925 else: 926 value = self._get_enum_or_unknown(enum, raw) 927 return value 928 929 def _decode_binary(self, attr, attr_spec): 930 if attr_spec.struct_name: 931 decoded = self._decode_struct(attr.raw, attr_spec.struct_name) 932 elif attr_spec.sub_type: 933 decoded = attr.as_c_array(attr_spec.sub_type) 934 if 'enum' in attr_spec: 935 decoded = [ self._decode_enum(x, attr_spec) for x in decoded ] 936 elif attr_spec.display_hint: 937 decoded = [ self._formatted_string(x, attr_spec.display_hint) 938 for x in decoded ] 939 else: 940 decoded = attr.as_bin() 941 if attr_spec.display_hint: 942 decoded = self._formatted_string(decoded, attr_spec.display_hint) 943 return decoded 944 945 def _decode_array_attr(self, attr, attr_spec): 946 decoded = [] 947 offset = 0 948 while offset < len(attr.raw): 949 item = NlAttr(attr.raw, offset) 950 offset += item.full_len 951 952 if attr_spec["sub-type"] == 'nest': 953 subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes']) 954 decoded.append({ item.type: subattrs }) 955 elif attr_spec["sub-type"] == 'binary': 956 subattr = item.as_bin() 957 if attr_spec.display_hint: 958 subattr = self._formatted_string(subattr, attr_spec.display_hint) 959 decoded.append(subattr) 960 elif attr_spec["sub-type"] in NlAttr.type_formats: 961 subattr = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order) 962 if 'enum' in attr_spec: 963 subattr = self._decode_enum(subattr, attr_spec) 964 elif attr_spec.display_hint: 965 subattr = self._formatted_string(subattr, attr_spec.display_hint) 966 decoded.append(subattr) 967 else: 968 raise YnlException(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}') 969 return decoded 970 971 def _decode_nest_type_value(self, attr, attr_spec): 972 decoded = {} 973 value = attr 974 for name in attr_spec['type-value']: 975 value = NlAttr(value.raw, 0) 976 decoded[name] = value.type 977 subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes']) 978 decoded.update(subattrs) 979 return decoded 980 981 def _decode_unknown(self, attr): 982 if attr.is_nest: 983 return self._decode(NlAttrs(attr.raw), None) 984 return attr.as_bin() 985 986 def _rsp_add(self, rsp, name, is_multi, decoded): 987 if is_multi is None: 988 if name in rsp and not isinstance(rsp[name], list): 989 rsp[name] = [rsp[name]] 990 is_multi = True 991 else: 992 is_multi = False 993 994 if not is_multi: 995 rsp[name] = decoded 996 elif name in rsp: 997 rsp[name].append(decoded) 998 else: 999 rsp[name] = [decoded] 1000 1001 def _resolve_selector(self, attr_spec, search_attrs): 1002 sub_msg = attr_spec.sub_message 1003 if sub_msg not in self.sub_msgs: 1004 raise YnlException(f"No sub-message spec named {sub_msg} for {attr_spec.name}") 1005 sub_msg_spec = self.sub_msgs[sub_msg] 1006 1007 selector = attr_spec.selector 1008 value = search_attrs.lookup(selector) 1009 if value not in sub_msg_spec.formats: 1010 raise YnlException(f"No message format for '{value}' in sub-message spec '{sub_msg}'") 1011 1012 spec = sub_msg_spec.formats[value] 1013 return spec, value 1014 1015 def _decode_sub_msg(self, attr, attr_spec, search_attrs): 1016 msg_format, _ = self._resolve_selector(attr_spec, search_attrs) 1017 decoded = {} 1018 offset = 0 1019 if msg_format.fixed_header: 1020 decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header)) 1021 offset = self.struct_size(msg_format.fixed_header) 1022 if msg_format.attr_set: 1023 if msg_format.attr_set in self.attr_sets: 1024 subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set) 1025 decoded.update(subdict) 1026 else: 1027 raise YnlException(f"Unknown attribute-set '{msg_format.attr_set}' " 1028 f"when decoding '{attr_spec.name}'") 1029 return decoded 1030 1031 # pylint: disable=too-many-statements 1032 def _decode(self, attrs, space, outer_attrs = None): 1033 rsp = {} 1034 search_attrs = {} 1035 if space: 1036 attr_space = self.attr_sets[space] 1037 search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs) 1038 1039 for attr in attrs: 1040 try: 1041 attr_spec = attr_space.attrs_by_val[attr.type] 1042 except (KeyError, UnboundLocalError) as err: 1043 if not self.process_unknown: 1044 raise YnlException(f"Space '{space}' has no attribute " 1045 f"with value '{attr.type}'") from err 1046 attr_name = f"UnknownAttr({attr.type})" 1047 self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr)) 1048 continue 1049 1050 try: 1051 if attr_spec["type"] == 'pad': 1052 continue 1053 elif attr_spec["type"] == 'nest': 1054 subdict = self._decode(NlAttrs(attr.raw), 1055 attr_spec['nested-attributes'], 1056 search_attrs) 1057 decoded = subdict 1058 elif attr_spec["type"] == 'string': 1059 decoded = attr.as_strz() 1060 elif attr_spec["type"] == 'binary': 1061 decoded = self._decode_binary(attr, attr_spec) 1062 elif attr_spec["type"] == 'flag': 1063 decoded = True 1064 elif attr_spec.is_auto_scalar: 1065 decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order) 1066 if 'enum' in attr_spec: 1067 decoded = self._decode_enum(decoded, attr_spec) 1068 elif attr_spec["type"] in NlAttr.type_formats: 1069 decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) 1070 if 'enum' in attr_spec: 1071 decoded = self._decode_enum(decoded, attr_spec) 1072 elif attr_spec.display_hint: 1073 decoded = self._formatted_string(decoded, attr_spec.display_hint) 1074 elif attr_spec["type"] == 'indexed-array': 1075 decoded = self._decode_array_attr(attr, attr_spec) 1076 elif attr_spec["type"] == 'bitfield32': 1077 value, selector = struct.unpack("II", attr.raw) 1078 if 'enum' in attr_spec: 1079 value = self._decode_enum(value, attr_spec) 1080 selector = self._decode_enum(selector, attr_spec) 1081 decoded = {"value": value, "selector": selector} 1082 elif attr_spec["type"] == 'sub-message': 1083 decoded = self._decode_sub_msg(attr, attr_spec, search_attrs) 1084 elif attr_spec["type"] == 'nest-type-value': 1085 decoded = self._decode_nest_type_value(attr, attr_spec) 1086 else: 1087 if not self.process_unknown: 1088 raise YnlException(f'Unknown {attr_spec["type"]} ' 1089 f'with name {attr_spec["name"]}') 1090 decoded = self._decode_unknown(attr) 1091 1092 self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded) 1093 except: 1094 print(f"Error decoding '{attr_spec.name}' from '{space}'") 1095 raise 1096 1097 return rsp 1098 1099 # pylint: disable=too-many-arguments, too-many-positional-arguments 1100 def _decode_extack_path(self, attrs, attr_set, offset, target, search_attrs): 1101 for attr in attrs: 1102 try: 1103 attr_spec = attr_set.attrs_by_val[attr.type] 1104 except KeyError as err: 1105 raise YnlException( 1106 f"Space '{attr_set.name}' has no attribute with value '{attr.type}'") from err 1107 if offset > target: 1108 break 1109 if offset == target: 1110 return '.' + attr_spec.name 1111 1112 if offset + attr.full_len <= target: 1113 offset += attr.full_len 1114 continue 1115 1116 pathname = attr_spec.name 1117 if attr_spec['type'] == 'nest': 1118 sub_attrs = self.attr_sets[attr_spec['nested-attributes']] 1119 search_attrs = SpaceAttrs(sub_attrs, search_attrs.lookup(attr_spec['name'])) 1120 elif attr_spec['type'] == 'sub-message': 1121 msg_format, value = self._resolve_selector(attr_spec, search_attrs) 1122 if msg_format is None: 1123 raise YnlException(f"Can't resolve sub-message of " 1124 f"{attr_spec['name']} for extack") 1125 sub_attrs = self.attr_sets[msg_format.attr_set] 1126 pathname += f"({value})" 1127 else: 1128 raise YnlException(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 1129 offset += 4 1130 subpath = self._decode_extack_path(NlAttrs(attr.raw), sub_attrs, 1131 offset, target, search_attrs) 1132 if subpath is None: 1133 return None 1134 return '.' + pathname + subpath 1135 1136 return None 1137 1138 def _decode_extack(self, request, op, extack, vals): 1139 if 'bad-attr-offs' not in extack: 1140 return 1141 1142 msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op) 1143 offset = self.nlproto.msghdr_size() + self.struct_size(op.fixed_header) 1144 search_attrs = SpaceAttrs(op.attr_set, vals) 1145 path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, 1146 extack['bad-attr-offs'], search_attrs) 1147 if path: 1148 del extack['bad-attr-offs'] 1149 extack['bad-attr'] = path 1150 1151 def struct_size(self, name): 1152 if name: 1153 members = self.consts[name].members 1154 size = 0 1155 for m in members: 1156 if m.type in ['pad', 'binary']: 1157 if m.struct: 1158 size += self.struct_size(m.struct) 1159 else: 1160 size += m.len 1161 else: 1162 format_ = NlAttr.get_format(m.type, m.byte_order) 1163 size += format_.size 1164 return size 1165 return 0 1166 1167 def _decode_struct(self, data, name): 1168 members = self.consts[name].members 1169 attrs = {} 1170 offset = 0 1171 for m in members: 1172 value = None 1173 if m.type == 'pad': 1174 offset += m.len 1175 elif m.type == 'binary': 1176 if m.struct: 1177 len_ = self.struct_size(m.struct) 1178 value = self._decode_struct(data[offset : offset + len_], 1179 m.struct) 1180 offset += len_ 1181 else: 1182 value = data[offset : offset + m.len] 1183 offset += m.len 1184 else: 1185 format_ = NlAttr.get_format(m.type, m.byte_order) 1186 [ value ] = format_.unpack_from(data, offset) 1187 offset += format_.size 1188 if value is not None: 1189 if m.enum: 1190 value = self._decode_enum(value, m) 1191 elif m.display_hint: 1192 value = self._formatted_string(value, m.display_hint) 1193 attrs[m.name] = value 1194 return attrs 1195 1196 def _encode_struct(self, name, vals): 1197 members = self.consts[name].members 1198 attr_payload = b'' 1199 for m in members: 1200 value = vals.pop(m.name) if m.name in vals else None 1201 if m.type == 'pad': 1202 attr_payload += bytearray(m.len) 1203 elif m.type == 'binary': 1204 if m.struct: 1205 if value is None: 1206 value = {} 1207 attr_payload += self._encode_struct(m.struct, value) 1208 else: 1209 if value is None: 1210 attr_payload += bytearray(m.len) 1211 else: 1212 attr_payload += bytes.fromhex(value) 1213 else: 1214 if value is None: 1215 value = 0 1216 format_ = NlAttr.get_format(m.type, m.byte_order) 1217 attr_payload += format_.pack(value) 1218 return attr_payload 1219 1220 def _formatted_string(self, raw, display_hint): 1221 if display_hint == 'mac': 1222 formatted = ':'.join(f'{b:02x}' for b in raw) 1223 elif display_hint == 'hex': 1224 if isinstance(raw, int): 1225 formatted = hex(raw) 1226 else: 1227 formatted = bytes.hex(raw, ' ') 1228 elif display_hint in [ 'ipv4', 'ipv6', 'ipv4-or-v6' ]: 1229 formatted = format(ipaddress.ip_address(raw)) 1230 elif display_hint == 'uuid': 1231 formatted = str(uuid.UUID(bytes=raw)) 1232 else: 1233 formatted = raw 1234 return formatted 1235 1236 def _from_string(self, string, attr_spec): 1237 if attr_spec.display_hint in ['ipv4', 'ipv6', 'ipv4-or-v6']: 1238 ip = ipaddress.ip_address(string) 1239 if attr_spec['type'] == 'binary': 1240 raw = ip.packed 1241 else: 1242 raw = int(ip) 1243 elif attr_spec.display_hint == 'hex': 1244 if attr_spec['type'] == 'binary': 1245 raw = bytes.fromhex(string) 1246 else: 1247 raw = int(string, 16) 1248 elif attr_spec.display_hint == 'mac': 1249 # Parse MAC address in format "00:11:22:33:44:55" or "001122334455" 1250 if ':' in string: 1251 mac_bytes = [int(x, 16) for x in string.split(':')] 1252 else: 1253 if len(string) % 2 != 0: 1254 raise YnlException(f"Invalid MAC address format: {string}") 1255 mac_bytes = [int(string[i:i+2], 16) for i in range(0, len(string), 2)] 1256 raw = bytes(mac_bytes) 1257 else: 1258 raise YnlException(f"Display hint '{attr_spec.display_hint}' not implemented" 1259 f" when parsing '{attr_spec['name']}'") 1260 return raw 1261 1262 def handle_ntf(self, decoded, nsid=None): 1263 msg = {} 1264 if self.include_raw: 1265 msg['raw'] = decoded 1266 op = self.rsp_by_value[decoded.cmd()] 1267 attrs = self._decode(decoded.raw_attrs, op.attr_set.name) 1268 if op.fixed_header: 1269 attrs.update(self._decode_struct(decoded.raw, op.fixed_header)) 1270 1271 msg['name'] = op['name'] 1272 msg['msg'] = attrs 1273 if nsid is not None: 1274 msg['nsid'] = nsid 1275 self.async_msg_queue.put(msg) 1276 1277 def _recvmsg(self, flags=0): 1278 reply, ancdata, _, _ = self.sock.recvmsg(self._recv_size, 4096, flags) 1279 return reply, ancdata 1280 1281 def check_ntf(self): 1282 while True: 1283 try: 1284 reply, ancdata = self._recvmsg(socket.MSG_DONTWAIT) 1285 except BlockingIOError: 1286 return 1287 1288 nsid = self._decode_nsid(ancdata) 1289 nms = NlMsgs(reply) 1290 self._recv_dbg_print(reply, nms) 1291 for nl_msg in nms: 1292 if nl_msg.error: 1293 print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 1294 print(nl_msg) 1295 continue 1296 if nl_msg.done: 1297 print("Netlink done while checking for ntf!?") 1298 continue 1299 1300 decoded = self.nlproto.decode(self, nl_msg, None) 1301 if decoded.cmd() not in self.async_msg_ids: 1302 print("Unexpected msg id while checking for ntf", decoded) 1303 continue 1304 1305 self.handle_ntf(decoded, nsid) 1306 1307 def poll_ntf(self, duration=None): 1308 start_time = time.time() 1309 selector = selectors.DefaultSelector() 1310 selector.register(self.sock, selectors.EVENT_READ) 1311 1312 while True: 1313 try: 1314 yield self.async_msg_queue.get_nowait() 1315 except queue.Empty: 1316 if duration is not None: 1317 timeout = start_time + duration - time.time() 1318 if timeout <= 0: 1319 return 1320 else: 1321 timeout = None 1322 events = selector.select(timeout) 1323 if events: 1324 self.check_ntf() 1325 1326 def operation_do_attributes(self, name): 1327 """ 1328 For a given operation name, find and return a supported 1329 set of attributes (as a dict). 1330 """ 1331 op = self.find_operation(name) 1332 if not op: 1333 return None 1334 1335 return op['do']['request']['attributes'].copy() 1336 1337 def _encode_message(self, op, vals, flags, req_seq): 1338 nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 1339 for flag in flags or []: 1340 nl_flags |= flag 1341 1342 msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq) 1343 if op.fixed_header: 1344 msg += self._encode_struct(op.fixed_header, vals) 1345 search_attrs = SpaceAttrs(op.attr_set, vals) 1346 for name, value in vals.items(): 1347 msg += self._add_attr(op.attr_set.name, name, value, search_attrs) 1348 msg = _genl_msg_finalize(msg) 1349 return msg 1350 1351 # pylint: disable=too-many-statements 1352 def _ops(self, ops): 1353 reqs_by_seq = {} 1354 req_seq = random.randint(1024, 65535) 1355 payload = b'' 1356 for (method, vals, flags) in ops: 1357 op = self.ops[method] 1358 msg = self._encode_message(op, vals, flags, req_seq) 1359 reqs_by_seq[req_seq] = (op, vals, msg, flags) 1360 payload += msg 1361 req_seq += 1 1362 1363 self.sock.send(payload, 0) 1364 1365 done = False 1366 rsp = [] 1367 op_rsp = [] 1368 while not done: 1369 reply, ancdata = self._recvmsg() 1370 nsid = self._decode_nsid(ancdata) 1371 nms = NlMsgs(reply) 1372 self._recv_dbg_print(reply, nms) 1373 for nl_msg in nms: 1374 if nl_msg.nl_seq in reqs_by_seq: 1375 (op, vals, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq] 1376 if nl_msg.extack: 1377 nl_msg.annotate_extack(op.attr_set) 1378 self._decode_extack(req_msg, op, nl_msg.extack, vals) 1379 else: 1380 op = None 1381 req_flags = [] 1382 1383 if nl_msg.error: 1384 raise NlError(nl_msg) 1385 if nl_msg.done: 1386 if nl_msg.extack: 1387 print("Netlink warning:") 1388 print(nl_msg) 1389 1390 if Netlink.NLM_F_DUMP in req_flags: 1391 rsp.append(op_rsp) 1392 elif not op_rsp: 1393 rsp.append(None) 1394 elif len(op_rsp) == 1: 1395 rsp.append(op_rsp[0]) 1396 else: 1397 rsp.append(op_rsp) 1398 op_rsp = [] 1399 1400 del reqs_by_seq[nl_msg.nl_seq] 1401 done = len(reqs_by_seq) == 0 1402 break 1403 1404 decoded = self.nlproto.decode(self, nl_msg, op) 1405 1406 # Check if this is a reply to our request 1407 if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value: 1408 if decoded.cmd() in self.async_msg_ids: 1409 self.handle_ntf(decoded, nsid) 1410 continue 1411 print('Unexpected message: ' + repr(decoded)) 1412 continue 1413 1414 rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name) 1415 if op.fixed_header: 1416 rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header)) 1417 op_rsp.append(rsp_msg) 1418 1419 return rsp 1420 1421 def _op(self, method, vals, flags=None, dump=False): 1422 req_flags = flags or [] 1423 if dump: 1424 req_flags.append(Netlink.NLM_F_DUMP) 1425 1426 ops = [(method, vals, req_flags)] 1427 return self._ops(ops)[0] 1428 1429 def do(self, method, vals, flags=None): 1430 return self._op(method, vals, flags) 1431 1432 def dump(self, method, vals): 1433 return self._op(method, vals, dump=True) 1434 1435 def do_multi(self, ops): 1436 return self._ops(ops) 1437 1438 def get_policy(self, op_name, mode): 1439 """Query running kernel for the Netlink policy of an operation. 1440 1441 Allows checking whether kernel supports a given attribute or value. 1442 This method consults the running kernel, not the YAML spec. 1443 1444 Args: 1445 op_name: operation name as it appears in the YAML spec 1446 mode: 'do' or 'dump' 1447 1448 Returns: 1449 NlPolicy acting as a read-only dict mapping attribute names 1450 to their policy properties (type, min/max, nested, etc.), 1451 or None if the operation has no policy for the given mode. 1452 Empty policy usually implies that the operation rejects 1453 all attributes. 1454 """ 1455 op = self.ops[op_name] 1456 op_policy, policy_table = _genl_policy_dump(self.nlproto.family_id, 1457 op.req_value) 1458 if mode not in op_policy: 1459 return None 1460 policy_idx = op_policy[mode] 1461 return NlPolicy(self, policy_idx, policy_table, op.attr_set) 1462