1*4e4480e8SJakub Kicinski# SPDX-License-Identifier: BSD-3-Clause 2*4e4480e8SJakub Kicinski 3*4e4480e8SJakub Kicinskiimport functools 4*4e4480e8SJakub Kicinskiimport jsonschema 5*4e4480e8SJakub Kicinskiimport os 6*4e4480e8SJakub Kicinskiimport random 7*4e4480e8SJakub Kicinskiimport socket 8*4e4480e8SJakub Kicinskiimport struct 9*4e4480e8SJakub Kicinskiimport yaml 10*4e4480e8SJakub Kicinski 11*4e4480e8SJakub Kicinski# 12*4e4480e8SJakub Kicinski# Generic Netlink code which should really be in some library, but I can't quickly find one. 13*4e4480e8SJakub Kicinski# 14*4e4480e8SJakub Kicinski 15*4e4480e8SJakub Kicinski 16*4e4480e8SJakub Kicinskiclass Netlink: 17*4e4480e8SJakub Kicinski # Netlink socket 18*4e4480e8SJakub Kicinski SOL_NETLINK = 270 19*4e4480e8SJakub Kicinski 20*4e4480e8SJakub Kicinski NETLINK_ADD_MEMBERSHIP = 1 21*4e4480e8SJakub Kicinski NETLINK_CAP_ACK = 10 22*4e4480e8SJakub Kicinski NETLINK_EXT_ACK = 11 23*4e4480e8SJakub Kicinski 24*4e4480e8SJakub Kicinski # Netlink message 25*4e4480e8SJakub Kicinski NLMSG_ERROR = 2 26*4e4480e8SJakub Kicinski NLMSG_DONE = 3 27*4e4480e8SJakub Kicinski 28*4e4480e8SJakub Kicinski NLM_F_REQUEST = 1 29*4e4480e8SJakub Kicinski NLM_F_ACK = 4 30*4e4480e8SJakub Kicinski NLM_F_ROOT = 0x100 31*4e4480e8SJakub Kicinski NLM_F_MATCH = 0x200 32*4e4480e8SJakub Kicinski NLM_F_APPEND = 0x800 33*4e4480e8SJakub Kicinski 34*4e4480e8SJakub Kicinski NLM_F_CAPPED = 0x100 35*4e4480e8SJakub Kicinski NLM_F_ACK_TLVS = 0x200 36*4e4480e8SJakub Kicinski 37*4e4480e8SJakub Kicinski NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH 38*4e4480e8SJakub Kicinski 39*4e4480e8SJakub Kicinski NLA_F_NESTED = 0x8000 40*4e4480e8SJakub Kicinski NLA_F_NET_BYTEORDER = 0x4000 41*4e4480e8SJakub Kicinski 42*4e4480e8SJakub Kicinski NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER 43*4e4480e8SJakub Kicinski 44*4e4480e8SJakub Kicinski # Genetlink defines 45*4e4480e8SJakub Kicinski NETLINK_GENERIC = 16 46*4e4480e8SJakub Kicinski 47*4e4480e8SJakub Kicinski GENL_ID_CTRL = 0x10 48*4e4480e8SJakub Kicinski 49*4e4480e8SJakub Kicinski # nlctrl 50*4e4480e8SJakub Kicinski CTRL_CMD_GETFAMILY = 3 51*4e4480e8SJakub Kicinski 52*4e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_ID = 1 53*4e4480e8SJakub Kicinski CTRL_ATTR_FAMILY_NAME = 2 54*4e4480e8SJakub Kicinski CTRL_ATTR_MAXATTR = 5 55*4e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GROUPS = 7 56*4e4480e8SJakub Kicinski 57*4e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_NAME = 1 58*4e4480e8SJakub Kicinski CTRL_ATTR_MCAST_GRP_ID = 2 59*4e4480e8SJakub Kicinski 60*4e4480e8SJakub Kicinski # Extack types 61*4e4480e8SJakub Kicinski NLMSGERR_ATTR_MSG = 1 62*4e4480e8SJakub Kicinski NLMSGERR_ATTR_OFFS = 2 63*4e4480e8SJakub Kicinski NLMSGERR_ATTR_COOKIE = 3 64*4e4480e8SJakub Kicinski NLMSGERR_ATTR_POLICY = 4 65*4e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_TYPE = 5 66*4e4480e8SJakub Kicinski NLMSGERR_ATTR_MISS_NEST = 6 67*4e4480e8SJakub Kicinski 68*4e4480e8SJakub Kicinski 69*4e4480e8SJakub Kicinskiclass NlAttr: 70*4e4480e8SJakub Kicinski def __init__(self, raw, offset): 71*4e4480e8SJakub Kicinski self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) 72*4e4480e8SJakub Kicinski self.type = self._type & ~Netlink.NLA_TYPE_MASK 73*4e4480e8SJakub Kicinski self.payload_len = self._len 74*4e4480e8SJakub Kicinski self.full_len = (self.payload_len + 3) & ~3 75*4e4480e8SJakub Kicinski self.raw = raw[offset + 4:offset + self.payload_len] 76*4e4480e8SJakub Kicinski 77*4e4480e8SJakub Kicinski def as_u16(self): 78*4e4480e8SJakub Kicinski return struct.unpack("H", self.raw)[0] 79*4e4480e8SJakub Kicinski 80*4e4480e8SJakub Kicinski def as_u32(self): 81*4e4480e8SJakub Kicinski return struct.unpack("I", self.raw)[0] 82*4e4480e8SJakub Kicinski 83*4e4480e8SJakub Kicinski def as_u64(self): 84*4e4480e8SJakub Kicinski return struct.unpack("Q", self.raw)[0] 85*4e4480e8SJakub Kicinski 86*4e4480e8SJakub Kicinski def as_strz(self): 87*4e4480e8SJakub Kicinski return self.raw.decode('ascii')[:-1] 88*4e4480e8SJakub Kicinski 89*4e4480e8SJakub Kicinski def as_bin(self): 90*4e4480e8SJakub Kicinski return self.raw 91*4e4480e8SJakub Kicinski 92*4e4480e8SJakub Kicinski def __repr__(self): 93*4e4480e8SJakub Kicinski return f"[type:{self.type} len:{self._len}] {self.raw}" 94*4e4480e8SJakub Kicinski 95*4e4480e8SJakub Kicinski 96*4e4480e8SJakub Kicinskiclass NlAttrs: 97*4e4480e8SJakub Kicinski def __init__(self, msg): 98*4e4480e8SJakub Kicinski self.attrs = [] 99*4e4480e8SJakub Kicinski 100*4e4480e8SJakub Kicinski offset = 0 101*4e4480e8SJakub Kicinski while offset < len(msg): 102*4e4480e8SJakub Kicinski attr = NlAttr(msg, offset) 103*4e4480e8SJakub Kicinski offset += attr.full_len 104*4e4480e8SJakub Kicinski self.attrs.append(attr) 105*4e4480e8SJakub Kicinski 106*4e4480e8SJakub Kicinski def __iter__(self): 107*4e4480e8SJakub Kicinski yield from self.attrs 108*4e4480e8SJakub Kicinski 109*4e4480e8SJakub Kicinski def __repr__(self): 110*4e4480e8SJakub Kicinski msg = '' 111*4e4480e8SJakub Kicinski for a in self.attrs: 112*4e4480e8SJakub Kicinski if msg: 113*4e4480e8SJakub Kicinski msg += '\n' 114*4e4480e8SJakub Kicinski msg += repr(a) 115*4e4480e8SJakub Kicinski return msg 116*4e4480e8SJakub Kicinski 117*4e4480e8SJakub Kicinski 118*4e4480e8SJakub Kicinskiclass NlMsg: 119*4e4480e8SJakub Kicinski def __init__(self, msg, offset, attr_space=None): 120*4e4480e8SJakub Kicinski self.hdr = msg[offset:offset + 16] 121*4e4480e8SJakub Kicinski 122*4e4480e8SJakub Kicinski self.nl_len, self.nl_type, self.nl_flags, self.nl_seq, self.nl_portid = \ 123*4e4480e8SJakub Kicinski struct.unpack("IHHII", self.hdr) 124*4e4480e8SJakub Kicinski 125*4e4480e8SJakub Kicinski self.raw = msg[offset + 16:offset + self.nl_len] 126*4e4480e8SJakub Kicinski 127*4e4480e8SJakub Kicinski self.error = 0 128*4e4480e8SJakub Kicinski self.done = 0 129*4e4480e8SJakub Kicinski 130*4e4480e8SJakub Kicinski extack_off = None 131*4e4480e8SJakub Kicinski if self.nl_type == Netlink.NLMSG_ERROR: 132*4e4480e8SJakub Kicinski self.error = struct.unpack("i", self.raw[0:4])[0] 133*4e4480e8SJakub Kicinski self.done = 1 134*4e4480e8SJakub Kicinski extack_off = 20 135*4e4480e8SJakub Kicinski elif self.nl_type == Netlink.NLMSG_DONE: 136*4e4480e8SJakub Kicinski self.done = 1 137*4e4480e8SJakub Kicinski extack_off = 4 138*4e4480e8SJakub Kicinski 139*4e4480e8SJakub Kicinski self.extack = None 140*4e4480e8SJakub Kicinski if self.nl_flags & Netlink.NLM_F_ACK_TLVS and extack_off: 141*4e4480e8SJakub Kicinski self.extack = dict() 142*4e4480e8SJakub Kicinski extack_attrs = NlAttrs(self.raw[extack_off:]) 143*4e4480e8SJakub Kicinski for extack in extack_attrs: 144*4e4480e8SJakub Kicinski if extack.type == Netlink.NLMSGERR_ATTR_MSG: 145*4e4480e8SJakub Kicinski self.extack['msg'] = extack.as_strz() 146*4e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: 147*4e4480e8SJakub Kicinski self.extack['miss-type'] = extack.as_u32() 148*4e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: 149*4e4480e8SJakub Kicinski self.extack['miss-nest'] = extack.as_u32() 150*4e4480e8SJakub Kicinski elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: 151*4e4480e8SJakub Kicinski self.extack['bad-attr-offs'] = extack.as_u32() 152*4e4480e8SJakub Kicinski else: 153*4e4480e8SJakub Kicinski if 'unknown' not in self.extack: 154*4e4480e8SJakub Kicinski self.extack['unknown'] = [] 155*4e4480e8SJakub Kicinski self.extack['unknown'].append(extack) 156*4e4480e8SJakub Kicinski 157*4e4480e8SJakub Kicinski if attr_space: 158*4e4480e8SJakub Kicinski # We don't have the ability to parse nests yet, so only do global 159*4e4480e8SJakub Kicinski if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 160*4e4480e8SJakub Kicinski miss_type = self.extack['miss-type'] 161*4e4480e8SJakub Kicinski if len(attr_space.attr_list) > miss_type: 162*4e4480e8SJakub Kicinski spec = attr_space.attr_list[miss_type] 163*4e4480e8SJakub Kicinski desc = spec['name'] 164*4e4480e8SJakub Kicinski if 'doc' in spec: 165*4e4480e8SJakub Kicinski desc += f" ({spec['doc']})" 166*4e4480e8SJakub Kicinski self.extack['miss-type'] = desc 167*4e4480e8SJakub Kicinski 168*4e4480e8SJakub Kicinski def __repr__(self): 169*4e4480e8SJakub Kicinski msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n" 170*4e4480e8SJakub Kicinski if self.error: 171*4e4480e8SJakub Kicinski msg += '\terror: ' + str(self.error) 172*4e4480e8SJakub Kicinski if self.extack: 173*4e4480e8SJakub Kicinski msg += '\textack: ' + repr(self.extack) 174*4e4480e8SJakub Kicinski return msg 175*4e4480e8SJakub Kicinski 176*4e4480e8SJakub Kicinski 177*4e4480e8SJakub Kicinskiclass NlMsgs: 178*4e4480e8SJakub Kicinski def __init__(self, data, attr_space=None): 179*4e4480e8SJakub Kicinski self.msgs = [] 180*4e4480e8SJakub Kicinski 181*4e4480e8SJakub Kicinski offset = 0 182*4e4480e8SJakub Kicinski while offset < len(data): 183*4e4480e8SJakub Kicinski msg = NlMsg(data, offset, attr_space=attr_space) 184*4e4480e8SJakub Kicinski offset += msg.nl_len 185*4e4480e8SJakub Kicinski self.msgs.append(msg) 186*4e4480e8SJakub Kicinski 187*4e4480e8SJakub Kicinski def __iter__(self): 188*4e4480e8SJakub Kicinski yield from self.msgs 189*4e4480e8SJakub Kicinski 190*4e4480e8SJakub Kicinski 191*4e4480e8SJakub Kicinskigenl_family_name_to_id = None 192*4e4480e8SJakub Kicinski 193*4e4480e8SJakub Kicinski 194*4e4480e8SJakub Kicinskidef _genl_msg(nl_type, nl_flags, genl_cmd, genl_version, seq=None): 195*4e4480e8SJakub Kicinski # we prepend length in _genl_msg_finalize() 196*4e4480e8SJakub Kicinski if seq is None: 197*4e4480e8SJakub Kicinski seq = random.randint(1, 1024) 198*4e4480e8SJakub Kicinski nlmsg = struct.pack("HHII", nl_type, nl_flags, seq, 0) 199*4e4480e8SJakub Kicinski genlmsg = struct.pack("bbH", genl_cmd, genl_version, 0) 200*4e4480e8SJakub Kicinski return nlmsg + genlmsg 201*4e4480e8SJakub Kicinski 202*4e4480e8SJakub Kicinski 203*4e4480e8SJakub Kicinskidef _genl_msg_finalize(msg): 204*4e4480e8SJakub Kicinski return struct.pack("I", len(msg) + 4) + msg 205*4e4480e8SJakub Kicinski 206*4e4480e8SJakub Kicinski 207*4e4480e8SJakub Kicinskidef _genl_load_families(): 208*4e4480e8SJakub Kicinski with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) as sock: 209*4e4480e8SJakub Kicinski sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 210*4e4480e8SJakub Kicinski 211*4e4480e8SJakub Kicinski msg = _genl_msg(Netlink.GENL_ID_CTRL, 212*4e4480e8SJakub Kicinski Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK | Netlink.NLM_F_DUMP, 213*4e4480e8SJakub Kicinski Netlink.CTRL_CMD_GETFAMILY, 1) 214*4e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 215*4e4480e8SJakub Kicinski 216*4e4480e8SJakub Kicinski sock.send(msg, 0) 217*4e4480e8SJakub Kicinski 218*4e4480e8SJakub Kicinski global genl_family_name_to_id 219*4e4480e8SJakub Kicinski genl_family_name_to_id = dict() 220*4e4480e8SJakub Kicinski 221*4e4480e8SJakub Kicinski while True: 222*4e4480e8SJakub Kicinski reply = sock.recv(128 * 1024) 223*4e4480e8SJakub Kicinski nms = NlMsgs(reply) 224*4e4480e8SJakub Kicinski for nl_msg in nms: 225*4e4480e8SJakub Kicinski if nl_msg.error: 226*4e4480e8SJakub Kicinski print("Netlink error:", nl_msg.error) 227*4e4480e8SJakub Kicinski return 228*4e4480e8SJakub Kicinski if nl_msg.done: 229*4e4480e8SJakub Kicinski return 230*4e4480e8SJakub Kicinski 231*4e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 232*4e4480e8SJakub Kicinski fam = dict() 233*4e4480e8SJakub Kicinski for attr in gm.raw_attrs: 234*4e4480e8SJakub Kicinski if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: 235*4e4480e8SJakub Kicinski fam['id'] = attr.as_u16() 236*4e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: 237*4e4480e8SJakub Kicinski fam['name'] = attr.as_strz() 238*4e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MAXATTR: 239*4e4480e8SJakub Kicinski fam['maxattr'] = attr.as_u32() 240*4e4480e8SJakub Kicinski elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: 241*4e4480e8SJakub Kicinski fam['mcast'] = dict() 242*4e4480e8SJakub Kicinski for entry in NlAttrs(attr.raw): 243*4e4480e8SJakub Kicinski mcast_name = None 244*4e4480e8SJakub Kicinski mcast_id = None 245*4e4480e8SJakub Kicinski for entry_attr in NlAttrs(entry.raw): 246*4e4480e8SJakub Kicinski if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: 247*4e4480e8SJakub Kicinski mcast_name = entry_attr.as_strz() 248*4e4480e8SJakub Kicinski elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: 249*4e4480e8SJakub Kicinski mcast_id = entry_attr.as_u32() 250*4e4480e8SJakub Kicinski if mcast_name and mcast_id is not None: 251*4e4480e8SJakub Kicinski fam['mcast'][mcast_name] = mcast_id 252*4e4480e8SJakub Kicinski if 'name' in fam and 'id' in fam: 253*4e4480e8SJakub Kicinski genl_family_name_to_id[fam['name']] = fam 254*4e4480e8SJakub Kicinski 255*4e4480e8SJakub Kicinski 256*4e4480e8SJakub Kicinskiclass GenlMsg: 257*4e4480e8SJakub Kicinski def __init__(self, nl_msg): 258*4e4480e8SJakub Kicinski self.nl = nl_msg 259*4e4480e8SJakub Kicinski 260*4e4480e8SJakub Kicinski self.hdr = nl_msg.raw[0:4] 261*4e4480e8SJakub Kicinski self.raw = nl_msg.raw[4:] 262*4e4480e8SJakub Kicinski 263*4e4480e8SJakub Kicinski self.genl_cmd, self.genl_version, _ = struct.unpack("bbH", self.hdr) 264*4e4480e8SJakub Kicinski 265*4e4480e8SJakub Kicinski self.raw_attrs = NlAttrs(self.raw) 266*4e4480e8SJakub Kicinski 267*4e4480e8SJakub Kicinski def __repr__(self): 268*4e4480e8SJakub Kicinski msg = repr(self.nl) 269*4e4480e8SJakub Kicinski msg += f"\tgenl_cmd = {self.genl_cmd} genl_ver = {self.genl_version}\n" 270*4e4480e8SJakub Kicinski for a in self.raw_attrs: 271*4e4480e8SJakub Kicinski msg += '\t\t' + repr(a) + '\n' 272*4e4480e8SJakub Kicinski return msg 273*4e4480e8SJakub Kicinski 274*4e4480e8SJakub Kicinski 275*4e4480e8SJakub Kicinskiclass GenlFamily: 276*4e4480e8SJakub Kicinski def __init__(self, family_name): 277*4e4480e8SJakub Kicinski self.family_name = family_name 278*4e4480e8SJakub Kicinski 279*4e4480e8SJakub Kicinski global genl_family_name_to_id 280*4e4480e8SJakub Kicinski if genl_family_name_to_id is None: 281*4e4480e8SJakub Kicinski _genl_load_families() 282*4e4480e8SJakub Kicinski 283*4e4480e8SJakub Kicinski self.genl_family = genl_family_name_to_id[family_name] 284*4e4480e8SJakub Kicinski self.family_id = genl_family_name_to_id[family_name]['id'] 285*4e4480e8SJakub Kicinski 286*4e4480e8SJakub Kicinski 287*4e4480e8SJakub Kicinski# 288*4e4480e8SJakub Kicinski# YNL implementation details. 289*4e4480e8SJakub Kicinski# 290*4e4480e8SJakub Kicinski 291*4e4480e8SJakub Kicinski 292*4e4480e8SJakub Kicinskiclass YnlAttrSpace: 293*4e4480e8SJakub Kicinski def __init__(self, family, yaml): 294*4e4480e8SJakub Kicinski self.yaml = yaml 295*4e4480e8SJakub Kicinski 296*4e4480e8SJakub Kicinski self.attrs = dict() 297*4e4480e8SJakub Kicinski self.name = self.yaml['name'] 298*4e4480e8SJakub Kicinski self.subspace_of = self.yaml['subset-of'] if 'subspace-of' in self.yaml else None 299*4e4480e8SJakub Kicinski 300*4e4480e8SJakub Kicinski val = 0 301*4e4480e8SJakub Kicinski max_val = 0 302*4e4480e8SJakub Kicinski for elem in self.yaml['attributes']: 303*4e4480e8SJakub Kicinski if 'value' in elem: 304*4e4480e8SJakub Kicinski val = elem['value'] 305*4e4480e8SJakub Kicinski else: 306*4e4480e8SJakub Kicinski elem['value'] = val 307*4e4480e8SJakub Kicinski if val > max_val: 308*4e4480e8SJakub Kicinski max_val = val 309*4e4480e8SJakub Kicinski val += 1 310*4e4480e8SJakub Kicinski 311*4e4480e8SJakub Kicinski self.attrs[elem['name']] = elem 312*4e4480e8SJakub Kicinski 313*4e4480e8SJakub Kicinski self.attr_list = [None] * (max_val + 1) 314*4e4480e8SJakub Kicinski for elem in self.yaml['attributes']: 315*4e4480e8SJakub Kicinski self.attr_list[elem['value']] = elem 316*4e4480e8SJakub Kicinski 317*4e4480e8SJakub Kicinski def __getitem__(self, key): 318*4e4480e8SJakub Kicinski return self.attrs[key] 319*4e4480e8SJakub Kicinski 320*4e4480e8SJakub Kicinski def __contains__(self, key): 321*4e4480e8SJakub Kicinski return key in self.yaml 322*4e4480e8SJakub Kicinski 323*4e4480e8SJakub Kicinski def __iter__(self): 324*4e4480e8SJakub Kicinski yield from self.attrs 325*4e4480e8SJakub Kicinski 326*4e4480e8SJakub Kicinski def items(self): 327*4e4480e8SJakub Kicinski return self.attrs.items() 328*4e4480e8SJakub Kicinski 329*4e4480e8SJakub Kicinski 330*4e4480e8SJakub Kicinskiclass YnlFamily: 331*4e4480e8SJakub Kicinski def __init__(self, def_path, schema=None): 332*4e4480e8SJakub Kicinski self.include_raw = False 333*4e4480e8SJakub Kicinski 334*4e4480e8SJakub Kicinski with open(def_path, "r") as stream: 335*4e4480e8SJakub Kicinski self.yaml = yaml.safe_load(stream) 336*4e4480e8SJakub Kicinski 337*4e4480e8SJakub Kicinski if schema: 338*4e4480e8SJakub Kicinski with open(schema, "r") as stream: 339*4e4480e8SJakub Kicinski schema = yaml.safe_load(stream) 340*4e4480e8SJakub Kicinski 341*4e4480e8SJakub Kicinski jsonschema.validate(self.yaml, schema) 342*4e4480e8SJakub Kicinski 343*4e4480e8SJakub Kicinski self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) 344*4e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 345*4e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 346*4e4480e8SJakub Kicinski 347*4e4480e8SJakub Kicinski self._ops = dict() 348*4e4480e8SJakub Kicinski self._spaces = dict() 349*4e4480e8SJakub Kicinski self._types = dict() 350*4e4480e8SJakub Kicinski 351*4e4480e8SJakub Kicinski for elem in self.yaml['attribute-sets']: 352*4e4480e8SJakub Kicinski self._spaces[elem['name']] = YnlAttrSpace(self, elem) 353*4e4480e8SJakub Kicinski 354*4e4480e8SJakub Kicinski for elem in self.yaml['definitions']: 355*4e4480e8SJakub Kicinski self._types[elem['name']] = elem 356*4e4480e8SJakub Kicinski 357*4e4480e8SJakub Kicinski async_separation = 'async-prefix' in self.yaml['operations'] 358*4e4480e8SJakub Kicinski self.async_msg_ids = set() 359*4e4480e8SJakub Kicinski self.async_msg_queue = [] 360*4e4480e8SJakub Kicinski val = 0 361*4e4480e8SJakub Kicinski max_val = 0 362*4e4480e8SJakub Kicinski for elem in self.yaml['operations']['list']: 363*4e4480e8SJakub Kicinski if not (async_separation and ('notify' in elem or 'event' in elem)): 364*4e4480e8SJakub Kicinski if 'value' in elem: 365*4e4480e8SJakub Kicinski val = elem['value'] 366*4e4480e8SJakub Kicinski else: 367*4e4480e8SJakub Kicinski elem['value'] = val 368*4e4480e8SJakub Kicinski val += 1 369*4e4480e8SJakub Kicinski max_val = max(val, max_val) 370*4e4480e8SJakub Kicinski 371*4e4480e8SJakub Kicinski if 'notify' in elem or 'event' in elem: 372*4e4480e8SJakub Kicinski self.async_msg_ids.add(elem['value']) 373*4e4480e8SJakub Kicinski 374*4e4480e8SJakub Kicinski self._ops[elem['name']] = elem 375*4e4480e8SJakub Kicinski 376*4e4480e8SJakub Kicinski op_name = elem['name'].replace('-', '_') 377*4e4480e8SJakub Kicinski 378*4e4480e8SJakub Kicinski bound_f = functools.partial(self._op, elem['name']) 379*4e4480e8SJakub Kicinski setattr(self, op_name, bound_f) 380*4e4480e8SJakub Kicinski 381*4e4480e8SJakub Kicinski self._op_array = [None] * max_val 382*4e4480e8SJakub Kicinski for _, op in self._ops.items(): 383*4e4480e8SJakub Kicinski self._op_array[op['value']] = op 384*4e4480e8SJakub Kicinski if 'notify' in op: 385*4e4480e8SJakub Kicinski op['attribute-set'] = self._ops[op['notify']]['attribute-set'] 386*4e4480e8SJakub Kicinski 387*4e4480e8SJakub Kicinski self.family = GenlFamily(self.yaml['name']) 388*4e4480e8SJakub Kicinski 389*4e4480e8SJakub Kicinski def ntf_subscribe(self, mcast_name): 390*4e4480e8SJakub Kicinski if mcast_name not in self.family.genl_family['mcast']: 391*4e4480e8SJakub Kicinski raise Exception(f'Multicast group "{mcast_name}" not present in the family') 392*4e4480e8SJakub Kicinski 393*4e4480e8SJakub Kicinski self.sock.bind((0, 0)) 394*4e4480e8SJakub Kicinski self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, 395*4e4480e8SJakub Kicinski self.family.genl_family['mcast'][mcast_name]) 396*4e4480e8SJakub Kicinski 397*4e4480e8SJakub Kicinski def _add_attr(self, space, name, value): 398*4e4480e8SJakub Kicinski attr = self._spaces[space][name] 399*4e4480e8SJakub Kicinski nl_type = attr['value'] 400*4e4480e8SJakub Kicinski if attr["type"] == 'nest': 401*4e4480e8SJakub Kicinski nl_type |= Netlink.NLA_F_NESTED 402*4e4480e8SJakub Kicinski attr_payload = b'' 403*4e4480e8SJakub Kicinski for subname, subvalue in value.items(): 404*4e4480e8SJakub Kicinski attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) 405*4e4480e8SJakub Kicinski elif attr["type"] == 'u32': 406*4e4480e8SJakub Kicinski attr_payload = struct.pack("I", int(value)) 407*4e4480e8SJakub Kicinski elif attr["type"] == 'string': 408*4e4480e8SJakub Kicinski attr_payload = str(value).encode('ascii') + b'\x00' 409*4e4480e8SJakub Kicinski elif attr["type"] == 'binary': 410*4e4480e8SJakub Kicinski attr_payload = value 411*4e4480e8SJakub Kicinski else: 412*4e4480e8SJakub Kicinski raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') 413*4e4480e8SJakub Kicinski 414*4e4480e8SJakub Kicinski pad = b'\x00' * ((4 - len(attr_payload) % 4) % 4) 415*4e4480e8SJakub Kicinski return struct.pack('HH', len(attr_payload) + 4, nl_type) + attr_payload + pad 416*4e4480e8SJakub Kicinski 417*4e4480e8SJakub Kicinski def _decode_enum(self, rsp, attr_spec): 418*4e4480e8SJakub Kicinski raw = rsp[attr_spec['name']] 419*4e4480e8SJakub Kicinski enum = self._types[attr_spec['enum']] 420*4e4480e8SJakub Kicinski i = attr_spec.get('value-start', 0) 421*4e4480e8SJakub Kicinski if 'enum-as-flags' in attr_spec and attr_spec['enum-as-flags']: 422*4e4480e8SJakub Kicinski value = set() 423*4e4480e8SJakub Kicinski while raw: 424*4e4480e8SJakub Kicinski if raw & 1: 425*4e4480e8SJakub Kicinski value.add(enum['entries'][i]) 426*4e4480e8SJakub Kicinski raw >>= 1 427*4e4480e8SJakub Kicinski i += 1 428*4e4480e8SJakub Kicinski else: 429*4e4480e8SJakub Kicinski value = enum['entries'][raw - i] 430*4e4480e8SJakub Kicinski rsp[attr_spec['name']] = value 431*4e4480e8SJakub Kicinski 432*4e4480e8SJakub Kicinski def _decode(self, attrs, space): 433*4e4480e8SJakub Kicinski attr_space = self._spaces[space] 434*4e4480e8SJakub Kicinski rsp = dict() 435*4e4480e8SJakub Kicinski for attr in attrs: 436*4e4480e8SJakub Kicinski attr_spec = attr_space.attr_list[attr.type] 437*4e4480e8SJakub Kicinski if attr_spec["type"] == 'nest': 438*4e4480e8SJakub Kicinski subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) 439*4e4480e8SJakub Kicinski rsp[attr_spec['name']] = subdict 440*4e4480e8SJakub Kicinski elif attr_spec['type'] == 'u32': 441*4e4480e8SJakub Kicinski rsp[attr_spec['name']] = attr.as_u32() 442*4e4480e8SJakub Kicinski elif attr_spec['type'] == 'u64': 443*4e4480e8SJakub Kicinski rsp[attr_spec['name']] = attr.as_u64() 444*4e4480e8SJakub Kicinski elif attr_spec["type"] == 'string': 445*4e4480e8SJakub Kicinski rsp[attr_spec['name']] = attr.as_strz() 446*4e4480e8SJakub Kicinski elif attr_spec["type"] == 'binary': 447*4e4480e8SJakub Kicinski rsp[attr_spec['name']] = attr.as_bin() 448*4e4480e8SJakub Kicinski else: 449*4e4480e8SJakub Kicinski raise Exception(f'Unknown {attr.type} {attr_spec["name"]} {attr_spec["type"]}') 450*4e4480e8SJakub Kicinski 451*4e4480e8SJakub Kicinski if 'enum' in attr_spec: 452*4e4480e8SJakub Kicinski self._decode_enum(rsp, attr_spec) 453*4e4480e8SJakub Kicinski return rsp 454*4e4480e8SJakub Kicinski 455*4e4480e8SJakub Kicinski def handle_ntf(self, nl_msg, genl_msg): 456*4e4480e8SJakub Kicinski msg = dict() 457*4e4480e8SJakub Kicinski if self.include_raw: 458*4e4480e8SJakub Kicinski msg['nlmsg'] = nl_msg 459*4e4480e8SJakub Kicinski msg['genlmsg'] = genl_msg 460*4e4480e8SJakub Kicinski op = self._op_array[genl_msg.genl_cmd] 461*4e4480e8SJakub Kicinski msg['name'] = op['name'] 462*4e4480e8SJakub Kicinski msg['msg'] = self._decode(genl_msg.raw_attrs, op['attribute-set']) 463*4e4480e8SJakub Kicinski self.async_msg_queue.append(msg) 464*4e4480e8SJakub Kicinski 465*4e4480e8SJakub Kicinski def check_ntf(self): 466*4e4480e8SJakub Kicinski while True: 467*4e4480e8SJakub Kicinski try: 468*4e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT) 469*4e4480e8SJakub Kicinski except BlockingIOError: 470*4e4480e8SJakub Kicinski return 471*4e4480e8SJakub Kicinski 472*4e4480e8SJakub Kicinski nms = NlMsgs(reply) 473*4e4480e8SJakub Kicinski for nl_msg in nms: 474*4e4480e8SJakub Kicinski if nl_msg.error: 475*4e4480e8SJakub Kicinski print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) 476*4e4480e8SJakub Kicinski print(nl_msg) 477*4e4480e8SJakub Kicinski continue 478*4e4480e8SJakub Kicinski if nl_msg.done: 479*4e4480e8SJakub Kicinski print("Netlink done while checking for ntf!?") 480*4e4480e8SJakub Kicinski continue 481*4e4480e8SJakub Kicinski 482*4e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 483*4e4480e8SJakub Kicinski if gm.genl_cmd not in self.async_msg_ids: 484*4e4480e8SJakub Kicinski print("Unexpected msg id done while checking for ntf", gm) 485*4e4480e8SJakub Kicinski continue 486*4e4480e8SJakub Kicinski 487*4e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 488*4e4480e8SJakub Kicinski 489*4e4480e8SJakub Kicinski def _op(self, method, vals, dump=False): 490*4e4480e8SJakub Kicinski op = self._ops[method] 491*4e4480e8SJakub Kicinski 492*4e4480e8SJakub Kicinski nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 493*4e4480e8SJakub Kicinski if dump: 494*4e4480e8SJakub Kicinski nl_flags |= Netlink.NLM_F_DUMP 495*4e4480e8SJakub Kicinski 496*4e4480e8SJakub Kicinski req_seq = random.randint(1024, 65535) 497*4e4480e8SJakub Kicinski msg = _genl_msg(self.family.family_id, nl_flags, op['value'], 1, req_seq) 498*4e4480e8SJakub Kicinski for name, value in vals.items(): 499*4e4480e8SJakub Kicinski msg += self._add_attr(op['attribute-set'], name, value) 500*4e4480e8SJakub Kicinski msg = _genl_msg_finalize(msg) 501*4e4480e8SJakub Kicinski 502*4e4480e8SJakub Kicinski self.sock.send(msg, 0) 503*4e4480e8SJakub Kicinski 504*4e4480e8SJakub Kicinski done = False 505*4e4480e8SJakub Kicinski rsp = [] 506*4e4480e8SJakub Kicinski while not done: 507*4e4480e8SJakub Kicinski reply = self.sock.recv(128 * 1024) 508*4e4480e8SJakub Kicinski nms = NlMsgs(reply, attr_space=self._spaces[op['attribute-set']]) 509*4e4480e8SJakub Kicinski for nl_msg in nms: 510*4e4480e8SJakub Kicinski if nl_msg.error: 511*4e4480e8SJakub Kicinski print("Netlink error:", os.strerror(-nl_msg.error)) 512*4e4480e8SJakub Kicinski print(nl_msg) 513*4e4480e8SJakub Kicinski return 514*4e4480e8SJakub Kicinski if nl_msg.done: 515*4e4480e8SJakub Kicinski done = True 516*4e4480e8SJakub Kicinski break 517*4e4480e8SJakub Kicinski 518*4e4480e8SJakub Kicinski gm = GenlMsg(nl_msg) 519*4e4480e8SJakub Kicinski # Check if this is a reply to our request 520*4e4480e8SJakub Kicinski if nl_msg.nl_seq != req_seq or gm.genl_cmd != op['value']: 521*4e4480e8SJakub Kicinski if gm.genl_cmd in self.async_msg_ids: 522*4e4480e8SJakub Kicinski self.handle_ntf(nl_msg, gm) 523*4e4480e8SJakub Kicinski continue 524*4e4480e8SJakub Kicinski else: 525*4e4480e8SJakub Kicinski print('Unexpected message: ' + repr(gm)) 526*4e4480e8SJakub Kicinski continue 527*4e4480e8SJakub Kicinski 528*4e4480e8SJakub Kicinski rsp.append(self._decode(gm.raw_attrs, op['attribute-set'])) 529*4e4480e8SJakub Kicinski 530*4e4480e8SJakub Kicinski if not rsp: 531*4e4480e8SJakub Kicinski return None 532*4e4480e8SJakub Kicinski if not dump and len(rsp) == 1: 533*4e4480e8SJakub Kicinski return rsp[0] 534*4e4480e8SJakub Kicinski return rsp 535