1042 lines · python
1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2 3from collections import namedtuple4from enum import Enum5import functools6import os7import random8import socket9import struct10from struct import Struct11import sys12import yaml13import ipaddress14import uuid15 16from .nlspec import SpecFamily17 18#19# Generic Netlink code which should really be in some library, but I can't quickly find one.20#21 22 23class Netlink:24 # Netlink socket25 SOL_NETLINK = 27026 27 NETLINK_ADD_MEMBERSHIP = 128 NETLINK_CAP_ACK = 1029 NETLINK_EXT_ACK = 1130 NETLINK_GET_STRICT_CHK = 1231 32 # Netlink message33 NLMSG_ERROR = 234 NLMSG_DONE = 335 36 NLM_F_REQUEST = 137 NLM_F_ACK = 438 NLM_F_ROOT = 0x10039 NLM_F_MATCH = 0x20040 41 NLM_F_REPLACE = 0x10042 NLM_F_EXCL = 0x20043 NLM_F_CREATE = 0x40044 NLM_F_APPEND = 0x80045 46 NLM_F_CAPPED = 0x10047 NLM_F_ACK_TLVS = 0x20048 49 NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH50 51 NLA_F_NESTED = 0x800052 NLA_F_NET_BYTEORDER = 0x400053 54 NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER55 56 # Genetlink defines57 NETLINK_GENERIC = 1658 59 GENL_ID_CTRL = 0x1060 61 # nlctrl62 CTRL_CMD_GETFAMILY = 363 64 CTRL_ATTR_FAMILY_ID = 165 CTRL_ATTR_FAMILY_NAME = 266 CTRL_ATTR_MAXATTR = 567 CTRL_ATTR_MCAST_GROUPS = 768 69 CTRL_ATTR_MCAST_GRP_NAME = 170 CTRL_ATTR_MCAST_GRP_ID = 271 72 # Extack types73 NLMSGERR_ATTR_MSG = 174 NLMSGERR_ATTR_OFFS = 275 NLMSGERR_ATTR_COOKIE = 376 NLMSGERR_ATTR_POLICY = 477 NLMSGERR_ATTR_MISS_TYPE = 578 NLMSGERR_ATTR_MISS_NEST = 679 80 # Policy types81 NL_POLICY_TYPE_ATTR_TYPE = 182 NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 283 NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 384 NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 485 NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 586 NL_POLICY_TYPE_ATTR_MIN_LENGTH = 687 NL_POLICY_TYPE_ATTR_MAX_LENGTH = 788 NL_POLICY_TYPE_ATTR_POLICY_IDX = 889 NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 990 NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 1091 NL_POLICY_TYPE_ATTR_PAD = 1192 NL_POLICY_TYPE_ATTR_MASK = 1293 94 AttrType = Enum('AttrType', ['flag', 'u8', 'u16', 'u32', 'u64',95 's8', 's16', 's32', 's64',96 'binary', 'string', 'nul-string',97 'nested', 'nested-array',98 'bitfield32', 'sint', 'uint'])99 100class NlError(Exception):101 def __init__(self, nl_msg):102 self.nl_msg = nl_msg103 self.error = -nl_msg.error104 105 def __str__(self):106 return f"Netlink error: {os.strerror(self.error)}\n{self.nl_msg}"107 108 109class ConfigError(Exception):110 pass111 112 113class NlAttr:114 ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little'])115 type_formats = {116 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")),117 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")),118 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")),119 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")),120 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")),121 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")),122 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")),123 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q"))124 }125 126 def __init__(self, raw, offset):127 self._len, self._type = struct.unpack("HH", raw[offset : offset + 4])128 self.type = self._type & ~Netlink.NLA_TYPE_MASK129 self.is_nest = self._type & Netlink.NLA_F_NESTED130 self.payload_len = self._len131 self.full_len = (self.payload_len + 3) & ~3132 self.raw = raw[offset + 4 : offset + self.payload_len]133 134 @classmethod135 def get_format(cls, attr_type, byte_order=None):136 format = cls.type_formats[attr_type]137 if byte_order:138 return format.big if byte_order == "big-endian" \139 else format.little140 return format.native141 142 def as_scalar(self, attr_type, byte_order=None):143 format = self.get_format(attr_type, byte_order)144 return format.unpack(self.raw)[0]145 146 def as_auto_scalar(self, attr_type, byte_order=None):147 if len(self.raw) != 4 and len(self.raw) != 8:148 raise Exception(f"Auto-scalar len payload be 4 or 8 bytes, got {len(self.raw)}")149 real_type = attr_type[0] + str(len(self.raw) * 8)150 format = self.get_format(real_type, byte_order)151 return format.unpack(self.raw)[0]152 153 def as_strz(self):154 return self.raw.decode('ascii')[:-1]155 156 def as_bin(self):157 return self.raw158 159 def as_c_array(self, type):160 format = self.get_format(type)161 return [ x[0] for x in format.iter_unpack(self.raw) ]162 163 def __repr__(self):164 return f"[type:{self.type} len:{self._len}] {self.raw}"165 166 167class NlAttrs:168 def __init__(self, msg, offset=0):169 self.attrs = []170 171 while offset < len(msg):172 attr = NlAttr(msg, offset)173 offset += attr.full_len174 self.attrs.append(attr)175 176 def __iter__(self):177 yield from self.attrs178 179 def __repr__(self):180 msg = ''181 for a in self.attrs:182 if msg:183 msg += '\n'184 msg += repr(a)185 return msg186 187 188class NlMsg:189 def __init__(self, msg, offset, attr_space=None):190 self.hdr = msg[offset : offset + 16]191 192 self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \193 struct.unpack("IHHII", self.hdr)194 195 self.raw = msg[offset + 16 : offset + self.nl_len]196 197 self.error = 0198 self.done = 0199 200 extack_off = None201 if self.nl_type == Netlink.NLMSG_ERROR:202 self.error = struct.unpack("i", self.raw[0:4])[0]203 self.done = 1204 extack_off = 20205 elif self.nl_type == Netlink.NLMSG_DONE:206 self.error = struct.unpack("i", self.raw[0:4])[0]207 self.done = 1208 extack_off = 4209 210 self.extack = None211 if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off:212 self.extack = dict()213 extack_attrs = NlAttrs(self.raw[extack_off:])214 for extack in extack_attrs:215 if extack.type == Netlink.NLMSGERR_ATTR_MSG:216 self.extack['msg'] = extack.as_strz()217 elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE:218 self.extack['miss-type'] = extack.as_scalar('u32')219 elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST:220 self.extack['miss-nest'] = extack.as_scalar('u32')221 elif extack.type == Netlink.NLMSGERR_ATTR_OFFS:222 self.extack['bad-attr-offs'] = extack.as_scalar('u32')223 elif extack.type == Netlink.NLMSGERR_ATTR_POLICY:224 self.extack['policy'] = self._decode_policy(extack.raw)225 else:226 if 'unknown' not in self.extack:227 self.extack['unknown'] = []228 self.extack['unknown'].append(extack)229 230 if attr_space:231 # We don't have the ability to parse nests yet, so only do global232 if 'miss-type' in self.extack and 'miss-nest' not in self.extack:233 miss_type = self.extack['miss-type']234 if miss_type in attr_space.attrs_by_val:235 spec = attr_space.attrs_by_val[miss_type]236 self.extack['miss-type'] = spec['name']237 if 'doc' in spec:238 self.extack['miss-type-doc'] = spec['doc']239 240 def _decode_policy(self, raw):241 policy = {}242 for attr in NlAttrs(raw):243 if attr.type == Netlink.NL_POLICY_TYPE_ATTR_TYPE:244 type = attr.as_scalar('u32')245 policy['type'] = Netlink.AttrType(type).name246 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_S:247 policy['min-value'] = attr.as_scalar('s64')248 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_S:249 policy['max-value'] = attr.as_scalar('s64')250 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_VALUE_U:251 policy['min-value'] = attr.as_scalar('u64')252 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_VALUE_U:253 policy['max-value'] = attr.as_scalar('u64')254 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MIN_LENGTH:255 policy['min-length'] = attr.as_scalar('u32')256 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MAX_LENGTH:257 policy['max-length'] = attr.as_scalar('u32')258 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_BITFIELD32_MASK:259 policy['bitfield32-mask'] = attr.as_scalar('u32')260 elif attr.type == Netlink.NL_POLICY_TYPE_ATTR_MASK:261 policy['mask'] = attr.as_scalar('u64')262 return policy263 264 def cmd(self):265 return self.nl_type266 267 def __repr__(self):268 msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}"269 if self.error:270 msg += '\n\terror: ' + str(self.error)271 if self.extack:272 msg += '\n\textack: ' + repr(self.extack)273 return msg274 275 276class NlMsgs:277 def __init__(self, data, attr_space=None):278 self.msgs = []279 280 offset = 0281 while offset < len(data):282 msg = NlMsg(data, offset, attr_space=attr_space)283 offset += msg.nl_len284 self.msgs.append(msg)285 286 def __iter__(self):287 yield from self.msgs288 289 290genl_family_name_to_id = None291 292 293def _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None):294 # we prepend length in _genl_msg_finalize()295 if seq is None:296 seq = random.randint(1, 1024)297 nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)298 genlmsg = struct.pack("BBH", genl_cmd, genl_version, 0)299 return nlmsg + genlmsg300 301 302def _genl_msg_finalize(msg):303 return struct.pack("I", len(msg) + 4) + msg304 305 306def _genl_load_families():307 with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock:308 sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)309 310 msg = _genl_msg(Netlink.GENL_ID_CTRL,311 Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP,312 Netlink.CTRL_CMD_GETFAMILY, 1)313 msg = _genl_msg_finalize(msg)314 315 sock.send(msg, 0)316 317 global genl_family_name_to_id318 genl_family_name_to_id = dict()319 320 while True:321 reply = sock.recv(128 * 1024)322 nms = NlMsgs(reply)323 for nl_msg in nms:324 if nl_msg.error:325 print("Netlink error:", nl_msg.error)326 return327 if nl_msg.done:328 return329 330 gm = GenlMsg(nl_msg)331 fam = dict()332 for attr in NlAttrs(gm.raw):333 if attr.type == Netlink.CTRL_ATTR_FAMILY_ID:334 fam['id'] = attr.as_scalar('u16')335 elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME:336 fam['name'] = attr.as_strz()337 elif attr.type == Netlink.CTRL_ATTR_MAXATTR:338 fam['maxattr'] = attr.as_scalar('u32')339 elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS:340 fam['mcast'] = dict()341 for entry in NlAttrs(attr.raw):342 mcast_name = None343 mcast_id = None344 for entry_attr in NlAttrs(entry.raw):345 if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME:346 mcast_name = entry_attr.as_strz()347 elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID:348 mcast_id = entry_attr.as_scalar('u32')349 if mcast_name and mcast_id is not None:350 fam['mcast'][mcast_name] = mcast_id351 if 'name' in fam and 'id' in fam:352 genl_family_name_to_id[fam['name']] = fam353 354 355class GenlMsg:356 def __init__(self, nl_msg):357 self.nl = nl_msg358 self.genl_cmd, self.genl_version, _ = struct.unpack_from("BBH", nl_msg.raw, 0)359 self.raw = nl_msg.raw[4:]360 361 def cmd(self):362 return self.genl_cmd363 364 def __repr__(self):365 msg = repr(self.nl)366 msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n"367 for a in self.raw_attrs:368 msg += '\t\t' + repr(a) + '\n'369 return msg370 371 372class NetlinkProtocol:373 def __init__(self, family_name, proto_num):374 self.family_name = family_name375 self.proto_num = proto_num376 377 def _message(self, nl_type, nl_flags, seq=None):378 if seq is None:379 seq = random.randint(1, 1024)380 nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0)381 return nlmsg382 383 def message(self, flags, command, version, seq=None):384 return self._message(command, flags, seq)385 386 def _decode(self, nl_msg):387 return nl_msg388 389 def decode(self, ynl, nl_msg, op):390 msg = self._decode(nl_msg)391 if op is None:392 op = ynl.rsp_by_value[msg.cmd()]393 fixed_header_size = ynl._struct_size(op.fixed_header)394 msg.raw_attrs = NlAttrs(msg.raw, fixed_header_size)395 return msg396 397 def get_mcast_id(self, mcast_name, mcast_groups):398 if mcast_name not in mcast_groups:399 raise Exception(f'Multicast group "{mcast_name}" not present in the spec')400 return mcast_groups[mcast_name].value401 402 def msghdr_size(self):403 return 16404 405 406class GenlProtocol(NetlinkProtocol):407 def __init__(self, family_name):408 super().__init__(family_name, Netlink.NETLINK_GENERIC)409 410 global genl_family_name_to_id411 if genl_family_name_to_id is None:412 _genl_load_families()413 414 self.genl_family = genl_family_name_to_id[family_name]415 self.family_id = genl_family_name_to_id[family_name]['id']416 417 def message(self, flags, command, version, seq=None):418 nlmsg = self._message(self.family_id, flags, seq)419 genlmsg = struct.pack("BBH", command, version, 0)420 return nlmsg + genlmsg421 422 def _decode(self, nl_msg):423 return GenlMsg(nl_msg)424 425 def get_mcast_id(self, mcast_name, mcast_groups):426 if mcast_name not in self.genl_family['mcast']:427 raise Exception(f'Multicast group "{mcast_name}" not present in the family')428 return self.genl_family['mcast'][mcast_name]429 430 def msghdr_size(self):431 return super().msghdr_size() + 4432 433 434class SpaceAttrs:435 SpecValuesPair = namedtuple('SpecValuesPair', ['spec', 'values'])436 437 def __init__(self, attr_space, attrs, outer = None):438 outer_scopes = outer.scopes if outer else []439 inner_scope = self.SpecValuesPair(attr_space, attrs)440 self.scopes = [inner_scope] + outer_scopes441 442 def lookup(self, name):443 for scope in self.scopes:444 if name in scope.spec:445 if name in scope.values:446 return scope.values[name]447 spec_name = scope.spec.yaml['name']448 raise Exception(449 f"No value for '{name}' in attribute space '{spec_name}'")450 raise Exception(f"Attribute '{name}' not defined in any attribute-set")451 452 453#454# YNL implementation details.455#456 457 458class YnlFamily(SpecFamily):459 def __init__(self, def_path, schema=None, process_unknown=False,460 recv_size=0):461 super().__init__(def_path, schema)462 463 self.include_raw = False464 self.process_unknown = process_unknown465 466 try:467 if self.proto == "netlink-raw":468 self.nlproto = NetlinkProtocol(self.yaml['name'],469 self.yaml['protonum'])470 else:471 self.nlproto = GenlProtocol(self.yaml['name'])472 except KeyError:473 raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel")474 475 self._recv_dbg = False476 # Note that netlink will use conservative (min) message size for477 # the first dump recv() on the socket, our setting will only matter478 # from the second recv() on.479 self._recv_size = recv_size if recv_size else 131072480 # Netlink will always allocate at least PAGE_SIZE - sizeof(skb_shinfo)481 # for a message, so smaller receive sizes will lead to truncation.482 # Note that the min size for other families may be larger than 4k!483 if self._recv_size < 4000:484 raise ConfigError()485 486 self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num)487 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)488 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1)489 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_GET_STRICT_CHK, 1)490 491 self.async_msg_ids = set()492 self.async_msg_queue = []493 494 for msg in self.msgs.values():495 if msg.is_async:496 self.async_msg_ids.add(msg.rsp_value)497 498 for op_name, op in self.ops.items():499 bound_f = functools.partial(self._op, op_name)500 setattr(self, op.ident_name, bound_f)501 502 503 def ntf_subscribe(self, mcast_name):504 mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups)505 self.sock.bind((0, 0))506 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP,507 mcast_id)508 509 def set_recv_dbg(self, enabled):510 self._recv_dbg = enabled511 512 def _recv_dbg_print(self, reply, nl_msgs):513 if not self._recv_dbg:514 return515 print("Recv: read", len(reply), "bytes,",516 len(nl_msgs.msgs), "messages", file=sys.stderr)517 for nl_msg in nl_msgs:518 print(" ", nl_msg, file=sys.stderr)519 520 def _encode_enum(self, attr_spec, value):521 enum = self.consts[attr_spec['enum']]522 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False):523 scalar = 0524 if isinstance(value, str):525 value = [value]526 for single_value in value:527 scalar += enum.entries[single_value].user_value(as_flags = True)528 return scalar529 else:530 return enum.entries[value].user_value()531 532 def _get_scalar(self, attr_spec, value):533 try:534 return int(value)535 except (ValueError, TypeError) as e:536 if 'enum' not in attr_spec:537 raise e538 return self._encode_enum(attr_spec, value)539 540 def _add_attr(self, space, name, value, search_attrs):541 try:542 attr = self.attr_sets[space][name]543 except KeyError:544 raise Exception(f"Space '{space}' has no attribute '{name}'")545 nl_type = attr.value546 547 if attr.is_multi and isinstance(value, list):548 attr_payload = b''549 for subvalue in value:550 attr_payload += self._add_attr(space, name, subvalue, search_attrs)551 return attr_payload552 553 if attr["type"] == 'nest':554 nl_type |= Netlink.NLA_F_NESTED555 attr_payload = b''556 sub_attrs = SpaceAttrs(self.attr_sets[space], value, search_attrs)557 for subname, subvalue in value.items():558 attr_payload += self._add_attr(attr['nested-attributes'],559 subname, subvalue, sub_attrs)560 elif attr["type"] == 'flag':561 if not value:562 # If value is absent or false then skip attribute creation.563 return b''564 attr_payload = b''565 elif attr["type"] == 'string':566 attr_payload = str(value).encode('ascii') + b'\x00'567 elif attr["type"] == 'binary':568 if isinstance(value, bytes):569 attr_payload = value570 elif isinstance(value, str):571 attr_payload = bytes.fromhex(value)572 elif isinstance(value, dict) and attr.struct_name:573 attr_payload = self._encode_struct(attr.struct_name, value)574 else:575 raise Exception(f'Unknown type for binary attribute, value: {value}')576 elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar:577 scalar = self._get_scalar(attr, value)578 if attr.is_auto_scalar:579 attr_type = attr["type"][0] + ('32' if scalar.bit_length() <= 32 else '64')580 else:581 attr_type = attr["type"]582 format = NlAttr.get_format(attr_type, attr.byte_order)583 attr_payload = format.pack(scalar)584 elif attr['type'] in "bitfield32":585 scalar_value = self._get_scalar(attr, value["value"])586 scalar_selector = self._get_scalar(attr, value["selector"])587 attr_payload = struct.pack("II", scalar_value, scalar_selector)588 elif attr['type'] == 'sub-message':589 msg_format = self._resolve_selector(attr, search_attrs)590 attr_payload = b''591 if msg_format.fixed_header:592 attr_payload += self._encode_struct(msg_format.fixed_header, value)593 if msg_format.attr_set:594 if msg_format.attr_set in self.attr_sets:595 nl_type |= Netlink.NLA_F_NESTED596 sub_attrs = SpaceAttrs(msg_format.attr_set, value, search_attrs)597 for subname, subvalue in value.items():598 attr_payload += self._add_attr(msg_format.attr_set,599 subname, subvalue, sub_attrs)600 else:601 raise Exception(f"Unknown attribute-set '{msg_format.attr_set}'")602 else:603 raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}')604 605 pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4)606 return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad607 608 def _decode_enum(self, raw, attr_spec):609 enum = self.consts[attr_spec['enum']]610 if enum.type == 'flags' or attr_spec.get('enum-as-flags', False):611 i = 0612 value = set()613 while raw:614 if raw & 1:615 value.add(enum.entries_by_val[i].name)616 raw >>= 1617 i += 1618 else:619 value = enum.entries_by_val[raw].name620 return value621 622 def _decode_binary(self, attr, attr_spec):623 if attr_spec.struct_name:624 decoded = self._decode_struct(attr.raw, attr_spec.struct_name)625 elif attr_spec.sub_type:626 decoded = attr.as_c_array(attr_spec.sub_type)627 else:628 decoded = attr.as_bin()629 if attr_spec.display_hint:630 decoded = self._formatted_string(decoded, attr_spec.display_hint)631 return decoded632 633 def _decode_array_attr(self, attr, attr_spec):634 decoded = []635 offset = 0636 while offset < len(attr.raw):637 item = NlAttr(attr.raw, offset)638 offset += item.full_len639 640 if attr_spec["sub-type"] == 'nest':641 subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes'])642 decoded.append({ item.type: subattrs })643 elif attr_spec["sub-type"] == 'binary':644 subattrs = item.as_bin()645 if attr_spec.display_hint:646 subattrs = self._formatted_string(subattrs, attr_spec.display_hint)647 decoded.append(subattrs)648 elif attr_spec["sub-type"] in NlAttr.type_formats:649 subattrs = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order)650 if attr_spec.display_hint:651 subattrs = self._formatted_string(subattrs, attr_spec.display_hint)652 decoded.append(subattrs)653 else:654 raise Exception(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}')655 return decoded656 657 def _decode_nest_type_value(self, attr, attr_spec):658 decoded = {}659 value = attr660 for name in attr_spec['type-value']:661 value = NlAttr(value.raw, 0)662 decoded[name] = value.type663 subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes'])664 decoded.update(subattrs)665 return decoded666 667 def _decode_unknown(self, attr):668 if attr.is_nest:669 return self._decode(NlAttrs(attr.raw), None)670 else:671 return attr.as_bin()672 673 def _rsp_add(self, rsp, name, is_multi, decoded):674 if is_multi == None:675 if name in rsp and type(rsp[name]) is not list:676 rsp[name] = [rsp[name]]677 is_multi = True678 else:679 is_multi = False680 681 if not is_multi:682 rsp[name] = decoded683 elif name in rsp:684 rsp[name].append(decoded)685 else:686 rsp[name] = [decoded]687 688 def _resolve_selector(self, attr_spec, search_attrs):689 sub_msg = attr_spec.sub_message690 if sub_msg not in self.sub_msgs:691 raise Exception(f"No sub-message spec named {sub_msg} for {attr_spec.name}")692 sub_msg_spec = self.sub_msgs[sub_msg]693 694 selector = attr_spec.selector695 value = search_attrs.lookup(selector)696 if value not in sub_msg_spec.formats:697 raise Exception(f"No message format for '{value}' in sub-message spec '{sub_msg}'")698 699 spec = sub_msg_spec.formats[value]700 return spec701 702 def _decode_sub_msg(self, attr, attr_spec, search_attrs):703 msg_format = self._resolve_selector(attr_spec, search_attrs)704 decoded = {}705 offset = 0706 if msg_format.fixed_header:707 decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header));708 offset = self._struct_size(msg_format.fixed_header)709 if msg_format.attr_set:710 if msg_format.attr_set in self.attr_sets:711 subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set)712 decoded.update(subdict)713 else:714 raise Exception(f"Unknown attribute-set '{attr_space}' when decoding '{attr_spec.name}'")715 return decoded716 717 def _decode(self, attrs, space, outer_attrs = None):718 rsp = dict()719 if space:720 attr_space = self.attr_sets[space]721 search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs)722 723 for attr in attrs:724 try:725 attr_spec = attr_space.attrs_by_val[attr.type]726 except (KeyError, UnboundLocalError):727 if not self.process_unknown:728 raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'")729 attr_name = f"UnknownAttr({attr.type})"730 self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr))731 continue732 733 if attr_spec["type"] == 'nest':734 subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'], search_attrs)735 decoded = subdict736 elif attr_spec["type"] == 'string':737 decoded = attr.as_strz()738 elif attr_spec["type"] == 'binary':739 decoded = self._decode_binary(attr, attr_spec)740 elif attr_spec["type"] == 'flag':741 decoded = True742 elif attr_spec.is_auto_scalar:743 decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order)744 elif attr_spec["type"] in NlAttr.type_formats:745 decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order)746 if 'enum' in attr_spec:747 decoded = self._decode_enum(decoded, attr_spec)748 elif attr_spec.display_hint:749 decoded = self._formatted_string(decoded, attr_spec.display_hint)750 elif attr_spec["type"] == 'indexed-array':751 decoded = self._decode_array_attr(attr, attr_spec)752 elif attr_spec["type"] == 'bitfield32':753 value, selector = struct.unpack("II", attr.raw)754 if 'enum' in attr_spec:755 value = self._decode_enum(value, attr_spec)756 selector = self._decode_enum(selector, attr_spec)757 decoded = {"value": value, "selector": selector}758 elif attr_spec["type"] == 'sub-message':759 decoded = self._decode_sub_msg(attr, attr_spec, search_attrs)760 elif attr_spec["type"] == 'nest-type-value':761 decoded = self._decode_nest_type_value(attr, attr_spec)762 else:763 if not self.process_unknown:764 raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}')765 decoded = self._decode_unknown(attr)766 767 self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded)768 769 return rsp770 771 def _decode_extack_path(self, attrs, attr_set, offset, target):772 for attr in attrs:773 try:774 attr_spec = attr_set.attrs_by_val[attr.type]775 except KeyError:776 raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'")777 if offset > target:778 break779 if offset == target:780 return '.' + attr_spec.name781 782 if offset + attr.full_len <= target:783 offset += attr.full_len784 continue785 if attr_spec['type'] != 'nest':786 raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack")787 offset += 4788 subpath = self._decode_extack_path(NlAttrs(attr.raw),789 self.attr_sets[attr_spec['nested-attributes']],790 offset, target)791 if subpath is None:792 return None793 return '.' + attr_spec.name + subpath794 795 return None796 797 def _decode_extack(self, request, op, extack):798 if 'bad-attr-offs' not in extack:799 return800 801 msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op)802 offset = self.nlproto.msghdr_size() + self._struct_size(op.fixed_header)803 path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset,804 extack['bad-attr-offs'])805 if path:806 del extack['bad-attr-offs']807 extack['bad-attr'] = path808 809 def _struct_size(self, name):810 if name:811 members = self.consts[name].members812 size = 0813 for m in members:814 if m.type in ['pad', 'binary']:815 if m.struct:816 size += self._struct_size(m.struct)817 else:818 size += m.len819 else:820 format = NlAttr.get_format(m.type, m.byte_order)821 size += format.size822 return size823 else:824 return 0825 826 def _decode_struct(self, data, name):827 members = self.consts[name].members828 attrs = dict()829 offset = 0830 for m in members:831 value = None832 if m.type == 'pad':833 offset += m.len834 elif m.type == 'binary':835 if m.struct:836 len = self._struct_size(m.struct)837 value = self._decode_struct(data[offset : offset + len],838 m.struct)839 offset += len840 else:841 value = data[offset : offset + m.len]842 offset += m.len843 else:844 format = NlAttr.get_format(m.type, m.byte_order)845 [ value ] = format.unpack_from(data, offset)846 offset += format.size847 if value is not None:848 if m.enum:849 value = self._decode_enum(value, m)850 elif m.display_hint:851 value = self._formatted_string(value, m.display_hint)852 attrs[m.name] = value853 return attrs854 855 def _encode_struct(self, name, vals):856 members = self.consts[name].members857 attr_payload = b''858 for m in members:859 value = vals.pop(m.name) if m.name in vals else None860 if m.type == 'pad':861 attr_payload += bytearray(m.len)862 elif m.type == 'binary':863 if m.struct:864 if value is None:865 value = dict()866 attr_payload += self._encode_struct(m.struct, value)867 else:868 if value is None:869 attr_payload += bytearray(m.len)870 else:871 attr_payload += bytes.fromhex(value)872 else:873 if value is None:874 value = 0875 format = NlAttr.get_format(m.type, m.byte_order)876 attr_payload += format.pack(value)877 return attr_payload878 879 def _formatted_string(self, raw, display_hint):880 if display_hint == 'mac':881 formatted = ':'.join('%02x' % b for b in raw)882 elif display_hint == 'hex':883 if isinstance(raw, int):884 formatted = hex(raw)885 else:886 formatted = bytes.hex(raw, ' ')887 elif display_hint in [ 'ipv4', 'ipv6' ]:888 formatted = format(ipaddress.ip_address(raw))889 elif display_hint == 'uuid':890 formatted = str(uuid.UUID(bytes=raw))891 else:892 formatted = raw893 return formatted894 895 def handle_ntf(self, decoded):896 msg = dict()897 if self.include_raw:898 msg['raw'] = decoded899 op = self.rsp_by_value[decoded.cmd()]900 attrs = self._decode(decoded.raw_attrs, op.attr_set.name)901 if op.fixed_header:902 attrs.update(self._decode_struct(decoded.raw, op.fixed_header))903 904 msg['name'] = op['name']905 msg['msg'] = attrs906 self.async_msg_queue.append(msg)907 908 def check_ntf(self):909 while True:910 try:911 reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT)912 except BlockingIOError:913 return914 915 nms = NlMsgs(reply)916 self._recv_dbg_print(reply, nms)917 for nl_msg in nms:918 if nl_msg.error:919 print("Netlink error in ntf!?", os.strerror(-nl_msg.error))920 print(nl_msg)921 continue922 if nl_msg.done:923 print("Netlink done while checking for ntf!?")924 continue925 926 decoded = self.nlproto.decode(self, nl_msg, None)927 if decoded.cmd() not in self.async_msg_ids:928 print("Unexpected msg id done while checking for ntf", decoded)929 continue930 931 self.handle_ntf(decoded)932 933 def operation_do_attributes(self, name):934 """935 For a given operation name, find and return a supported936 set of attributes (as a dict).937 """938 op = self.find_operation(name)939 if not op:940 return None941 942 return op['do']['request']['attributes'].copy()943 944 def _encode_message(self, op, vals, flags, req_seq):945 nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK946 for flag in flags or []:947 nl_flags |= flag948 949 msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq)950 if op.fixed_header:951 msg += self._encode_struct(op.fixed_header, vals)952 search_attrs = SpaceAttrs(op.attr_set, vals)953 for name, value in vals.items():954 msg += self._add_attr(op.attr_set.name, name, value, search_attrs)955 msg = _genl_msg_finalize(msg)956 return msg957 958 def _ops(self, ops):959 reqs_by_seq = {}960 req_seq = random.randint(1024, 65535)961 payload = b''962 for (method, vals, flags) in ops:963 op = self.ops[method]964 msg = self._encode_message(op, vals, flags, req_seq)965 reqs_by_seq[req_seq] = (op, msg, flags)966 payload += msg967 req_seq += 1968 969 self.sock.send(payload, 0)970 971 done = False972 rsp = []973 op_rsp = []974 while not done:975 reply = self.sock.recv(self._recv_size)976 nms = NlMsgs(reply, attr_space=op.attr_set)977 self._recv_dbg_print(reply, nms)978 for nl_msg in nms:979 if nl_msg.nl_seq in reqs_by_seq:980 (op, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq]981 if nl_msg.extack:982 self._decode_extack(req_msg, op, nl_msg.extack)983 else:984 op = None985 req_flags = []986 987 if nl_msg.error:988 raise NlError(nl_msg)989 if nl_msg.done:990 if nl_msg.extack:991 print("Netlink warning:")992 print(nl_msg)993 994 if Netlink.NLM_F_DUMP in req_flags:995 rsp.append(op_rsp)996 elif not op_rsp:997 rsp.append(None)998 elif len(op_rsp) == 1:999 rsp.append(op_rsp[0])1000 else:1001 rsp.append(op_rsp)1002 op_rsp = []1003 1004 del reqs_by_seq[nl_msg.nl_seq]1005 done = len(reqs_by_seq) == 01006 break1007 1008 decoded = self.nlproto.decode(self, nl_msg, op)1009 1010 # Check if this is a reply to our request1011 if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value:1012 if decoded.cmd() in self.async_msg_ids:1013 self.handle_ntf(decoded)1014 continue1015 else:1016 print('Unexpected message: ' + repr(decoded))1017 continue1018 1019 rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)1020 if op.fixed_header:1021 rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header))1022 op_rsp.append(rsp_msg)1023 1024 return rsp1025 1026 def _op(self, method, vals, flags=None, dump=False):1027 req_flags = flags or []1028 if dump:1029 req_flags.append(Netlink.NLM_F_DUMP)1030 1031 ops = [(method, vals, req_flags)]1032 return self._ops(ops)[0]1033 1034 def do(self, method, vals, flags=None):1035 return self._op(method, vals, flags)1036 1037 def dump(self, method, vals):1038 return self._op(method, vals, dump=True)1039 1040 def do_multi(self, ops):1041 return self._ops(ops)1042