1fee65b7eSAlexander V. Chernikov#!/usr/local/bin/python3 2fee65b7eSAlexander V. Chernikovimport struct 3fee65b7eSAlexander V. Chernikovfrom ctypes import sizeof 4fc2538cbSAlexander V. Chernikovfrom enum import Enum 5fee65b7eSAlexander V. Chernikovfrom typing import List 6fc2538cbSAlexander V. Chernikovfrom typing import NamedTuple 7fee65b7eSAlexander V. Chernikov 8fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.attrs import NlAttr 9fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.attrs import NlAttrNested 10fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmAckFlags 11fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmNewFlags 12fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmGetFlags 13fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmDeleteFlags 14fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmBaseFlags 15fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import Nlmsghdr 16fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlMsgType 17fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.utils import align4 18fee65b7eSAlexander V. Chernikovfrom atf_python.sys.netlink.utils import enum_or_int 19fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.utils import get_bitmask_str 20fc2538cbSAlexander V. Chernikov 21fc2538cbSAlexander V. Chernikov 22fc2538cbSAlexander V. Chernikovclass NlMsgCategory(Enum): 23fc2538cbSAlexander V. Chernikov UNKNOWN = 0 24fc2538cbSAlexander V. Chernikov GET = 1 25fc2538cbSAlexander V. Chernikov NEW = 2 26fc2538cbSAlexander V. Chernikov DELETE = 3 27fc2538cbSAlexander V. Chernikov ACK = 4 28fc2538cbSAlexander V. Chernikov 29fc2538cbSAlexander V. Chernikov 30fc2538cbSAlexander V. Chernikovclass NlMsgProps(NamedTuple): 31fc2538cbSAlexander V. Chernikov msg: Enum 32fc2538cbSAlexander V. Chernikov category: NlMsgCategory 33fee65b7eSAlexander V. Chernikov 34fee65b7eSAlexander V. Chernikov 35fee65b7eSAlexander V. Chernikovclass BaseNetlinkMessage(object): 36fee65b7eSAlexander V. Chernikov def __init__(self, helper, nlmsg_type): 37fee65b7eSAlexander V. Chernikov self.nlmsg_type = enum_or_int(nlmsg_type) 38fee65b7eSAlexander V. Chernikov self.nla_list = [] 39fee65b7eSAlexander V. Chernikov self._orig_data = None 40fee65b7eSAlexander V. Chernikov self.helper = helper 41fee65b7eSAlexander V. Chernikov self.nl_hdr = Nlmsghdr( 42fee65b7eSAlexander V. Chernikov nlmsg_type=self.nlmsg_type, nlmsg_seq=helper.get_seq(), nlmsg_pid=helper.pid 43fee65b7eSAlexander V. Chernikov ) 44fee65b7eSAlexander V. Chernikov self.base_hdr = None 45fee65b7eSAlexander V. Chernikov 46fee65b7eSAlexander V. Chernikov def set_request(self, need_ack=True): 47fee65b7eSAlexander V. Chernikov self.add_nlflags([NlmBaseFlags.NLM_F_REQUEST]) 48fee65b7eSAlexander V. Chernikov if need_ack: 49fee65b7eSAlexander V. Chernikov self.add_nlflags([NlmBaseFlags.NLM_F_ACK]) 50fee65b7eSAlexander V. Chernikov 51fee65b7eSAlexander V. Chernikov def add_nlflags(self, flags: List): 52fee65b7eSAlexander V. Chernikov int_flags = 0 53fee65b7eSAlexander V. Chernikov for flag in flags: 54fee65b7eSAlexander V. Chernikov int_flags |= enum_or_int(flag) 55fee65b7eSAlexander V. Chernikov self.nl_hdr.nlmsg_flags |= int_flags 56fee65b7eSAlexander V. Chernikov 57fee65b7eSAlexander V. Chernikov def add_nla(self, nla): 58fee65b7eSAlexander V. Chernikov self.nla_list.append(nla) 59fee65b7eSAlexander V. Chernikov 60fee65b7eSAlexander V. Chernikov def _get_nla(self, nla_list, nla_type): 61fee65b7eSAlexander V. Chernikov nla_type_raw = enum_or_int(nla_type) 62fee65b7eSAlexander V. Chernikov for nla in nla_list: 63fee65b7eSAlexander V. Chernikov if nla.nla_type == nla_type_raw: 64fee65b7eSAlexander V. Chernikov return nla 65fee65b7eSAlexander V. Chernikov return None 66fee65b7eSAlexander V. Chernikov 67fee65b7eSAlexander V. Chernikov def get_nla(self, nla_type): 68fee65b7eSAlexander V. Chernikov return self._get_nla(self.nla_list, nla_type) 69fee65b7eSAlexander V. Chernikov 70fee65b7eSAlexander V. Chernikov @staticmethod 71fee65b7eSAlexander V. Chernikov def parse_nl_header(data: bytes): 72fee65b7eSAlexander V. Chernikov if len(data) < sizeof(Nlmsghdr): 73fee65b7eSAlexander V. Chernikov raise ValueError("length less than netlink message header") 74fee65b7eSAlexander V. Chernikov return Nlmsghdr.from_buffer_copy(data), sizeof(Nlmsghdr) 75fee65b7eSAlexander V. Chernikov 76fee65b7eSAlexander V. Chernikov def is_type(self, nlmsg_type): 77fee65b7eSAlexander V. Chernikov nlmsg_type_raw = enum_or_int(nlmsg_type) 78fee65b7eSAlexander V. Chernikov return nlmsg_type_raw == self.nl_hdr.nlmsg_type 79fee65b7eSAlexander V. Chernikov 80fee65b7eSAlexander V. Chernikov def is_reply(self, hdr): 81fee65b7eSAlexander V. Chernikov return hdr.nlmsg_type == NlMsgType.NLMSG_ERROR.value 82fee65b7eSAlexander V. Chernikov 83fc2538cbSAlexander V. Chernikov @property 84fc2538cbSAlexander V. Chernikov def msg_name(self): 85fc2538cbSAlexander V. Chernikov return "msg#{}".format(self._get_msg_type()) 86fc2538cbSAlexander V. Chernikov 87fc2538cbSAlexander V. Chernikov def _get_nl_category(self): 88fc2538cbSAlexander V. Chernikov if self.is_reply(self.nl_hdr): 89fc2538cbSAlexander V. Chernikov return NlMsgCategory.ACK 90fc2538cbSAlexander V. Chernikov return NlMsgCategory.UNKNOWN 91fc2538cbSAlexander V. Chernikov 92fc2538cbSAlexander V. Chernikov def get_nlm_flags_str(self): 93fc2538cbSAlexander V. Chernikov category = self._get_nl_category() 94fc2538cbSAlexander V. Chernikov flags = self.nl_hdr.nlmsg_flags 95fc2538cbSAlexander V. Chernikov 96fc2538cbSAlexander V. Chernikov if category == NlMsgCategory.UNKNOWN: 97fc2538cbSAlexander V. Chernikov return self.helper.get_bitmask_str(NlmBaseFlags, flags) 98fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.GET: 99fc2538cbSAlexander V. Chernikov flags_enum = NlmGetFlags 100fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.NEW: 101fc2538cbSAlexander V. Chernikov flags_enum = NlmNewFlags 102fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.DELETE: 103fc2538cbSAlexander V. Chernikov flags_enum = NlmDeleteFlags 104fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.ACK: 105fc2538cbSAlexander V. Chernikov flags_enum = NlmAckFlags 106fc2538cbSAlexander V. Chernikov return get_bitmask_str([NlmBaseFlags, flags_enum], flags) 107fc2538cbSAlexander V. Chernikov 108fc2538cbSAlexander V. Chernikov def print_nl_header(self, prepend=""): 109fee65b7eSAlexander V. Chernikov # len=44, type=RTM_DELROUTE, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1641163704, pid=0 # noqa: E501 110fc2538cbSAlexander V. Chernikov hdr = self.nl_hdr 111fee65b7eSAlexander V. Chernikov print( 112fee65b7eSAlexander V. Chernikov "{}len={}, type={}, flags={}(0x{:X}), seq={}, pid={}".format( 113fee65b7eSAlexander V. Chernikov prepend, 114fee65b7eSAlexander V. Chernikov hdr.nlmsg_len, 115fc2538cbSAlexander V. Chernikov self.msg_name, 116fc2538cbSAlexander V. Chernikov self.get_nlm_flags_str(), 117fee65b7eSAlexander V. Chernikov hdr.nlmsg_flags, 118fee65b7eSAlexander V. Chernikov hdr.nlmsg_seq, 119fee65b7eSAlexander V. Chernikov hdr.nlmsg_pid, 120fee65b7eSAlexander V. Chernikov ) 121fee65b7eSAlexander V. Chernikov ) 122fee65b7eSAlexander V. Chernikov 123fee65b7eSAlexander V. Chernikov @classmethod 124fee65b7eSAlexander V. Chernikov def from_bytes(cls, helper, data): 125fee65b7eSAlexander V. Chernikov try: 126fee65b7eSAlexander V. Chernikov hdr, hdrlen = BaseNetlinkMessage.parse_nl_header(data) 127fee65b7eSAlexander V. Chernikov self = cls(helper, hdr.nlmsg_type) 128fee65b7eSAlexander V. Chernikov self._orig_data = data 129fee65b7eSAlexander V. Chernikov self.nl_hdr = hdr 130fee65b7eSAlexander V. Chernikov except ValueError as e: 131fee65b7eSAlexander V. Chernikov print("Failed to parse nl header: {}".format(e)) 132fee65b7eSAlexander V. Chernikov cls.print_as_bytes(data) 133fee65b7eSAlexander V. Chernikov raise 134fee65b7eSAlexander V. Chernikov return self 135fee65b7eSAlexander V. Chernikov 136fee65b7eSAlexander V. Chernikov def print_message(self): 137fc2538cbSAlexander V. Chernikov self.print_nl_header() 138fee65b7eSAlexander V. Chernikov 139fee65b7eSAlexander V. Chernikov @staticmethod 140fee65b7eSAlexander V. Chernikov def print_as_bytes(data: bytes, descr: str): 141fee65b7eSAlexander V. Chernikov print("===vv {} (len:{:3d}) vv===".format(descr, len(data))) 142fee65b7eSAlexander V. Chernikov off = 0 143fee65b7eSAlexander V. Chernikov step = 16 144fee65b7eSAlexander V. Chernikov while off < len(data): 145fee65b7eSAlexander V. Chernikov for i in range(step): 146fee65b7eSAlexander V. Chernikov if off + i < len(data): 147fee65b7eSAlexander V. Chernikov print(" {:02X}".format(data[off + i]), end="") 148fee65b7eSAlexander V. Chernikov print("") 149fee65b7eSAlexander V. Chernikov off += step 150fee65b7eSAlexander V. Chernikov print("--------------------") 151fee65b7eSAlexander V. Chernikov 152fee65b7eSAlexander V. Chernikov 153fee65b7eSAlexander V. Chernikovclass StdNetlinkMessage(BaseNetlinkMessage): 154fee65b7eSAlexander V. Chernikov nl_attrs_map = {} 155fee65b7eSAlexander V. Chernikov 156fee65b7eSAlexander V. Chernikov @classmethod 157fee65b7eSAlexander V. Chernikov def from_bytes(cls, helper, data): 158fee65b7eSAlexander V. Chernikov try: 159fee65b7eSAlexander V. Chernikov hdr, hdrlen = BaseNetlinkMessage.parse_nl_header(data) 160fee65b7eSAlexander V. Chernikov self = cls(helper, hdr.nlmsg_type) 161fee65b7eSAlexander V. Chernikov self._orig_data = data 162fee65b7eSAlexander V. Chernikov self.nl_hdr = hdr 163fee65b7eSAlexander V. Chernikov except ValueError as e: 164fee65b7eSAlexander V. Chernikov print("Failed to parse nl header: {}".format(e)) 165fee65b7eSAlexander V. Chernikov cls.print_as_bytes(data) 166fee65b7eSAlexander V. Chernikov raise 167fee65b7eSAlexander V. Chernikov 168fee65b7eSAlexander V. Chernikov offset = align4(hdrlen) 169fee65b7eSAlexander V. Chernikov try: 170fee65b7eSAlexander V. Chernikov base_hdr, hdrlen = self.parse_base_header(data[offset:]) 171fee65b7eSAlexander V. Chernikov self.base_hdr = base_hdr 172fee65b7eSAlexander V. Chernikov offset += align4(hdrlen) 173fee65b7eSAlexander V. Chernikov # XXX: CAP_ACK 174fee65b7eSAlexander V. Chernikov except ValueError as e: 175fee65b7eSAlexander V. Chernikov print("Failed to parse nl rt header: {}".format(e)) 176fee65b7eSAlexander V. Chernikov cls.print_as_bytes(data) 177fee65b7eSAlexander V. Chernikov raise 178fee65b7eSAlexander V. Chernikov 179fee65b7eSAlexander V. Chernikov orig_offset = offset 180fee65b7eSAlexander V. Chernikov try: 181fee65b7eSAlexander V. Chernikov nla_list, nla_len = self.parse_nla_list(data[offset:]) 182fee65b7eSAlexander V. Chernikov offset += nla_len 183fee65b7eSAlexander V. Chernikov if offset != len(data): 184fee65b7eSAlexander V. Chernikov raise ValueError( 185fee65b7eSAlexander V. Chernikov "{} bytes left at the end of the packet".format(len(data) - offset) 186fee65b7eSAlexander V. Chernikov ) # noqa: E501 187fee65b7eSAlexander V. Chernikov self.nla_list = nla_list 188fee65b7eSAlexander V. Chernikov except ValueError as e: 189fee65b7eSAlexander V. Chernikov print( 190fee65b7eSAlexander V. Chernikov "Failed to parse nla attributes at offset {}: {}".format(orig_offset, e) 191fee65b7eSAlexander V. Chernikov ) # noqa: E501 192fee65b7eSAlexander V. Chernikov cls.print_as_bytes(data, "msg dump") 193fee65b7eSAlexander V. Chernikov cls.print_as_bytes(data[orig_offset:], "failed block") 194fee65b7eSAlexander V. Chernikov raise 195fee65b7eSAlexander V. Chernikov return self 196fee65b7eSAlexander V. Chernikov 197*54b955f4SAlexander V. Chernikov def parse_child(self, data: bytes, attr_key, attr_map): 198*54b955f4SAlexander V. Chernikov attrs, _ = self.parse_attrs(data, attr_map) 199*54b955f4SAlexander V. Chernikov return NlAttrNested(attr_key, attrs) 200*54b955f4SAlexander V. Chernikov 201*54b955f4SAlexander V. Chernikov def parse_child_array(self, data: bytes, attr_key, attr_map): 202*54b955f4SAlexander V. Chernikov ret = [] 203*54b955f4SAlexander V. Chernikov off = 0 204*54b955f4SAlexander V. Chernikov while len(data) - off >= 4: 205*54b955f4SAlexander V. Chernikov nla_len, raw_nla_type = struct.unpack("@HH", data[off : off + 4]) 206*54b955f4SAlexander V. Chernikov if nla_len + off > len(data): 207*54b955f4SAlexander V. Chernikov raise ValueError( 208*54b955f4SAlexander V. Chernikov "attr length {} > than the remaining length {}".format( 209*54b955f4SAlexander V. Chernikov nla_len, len(data) - off 210*54b955f4SAlexander V. Chernikov ) 211*54b955f4SAlexander V. Chernikov ) 212*54b955f4SAlexander V. Chernikov nla_type = raw_nla_type & 0x3FFF 213*54b955f4SAlexander V. Chernikov val = self.parse_child(data[off + 4 : off + nla_len], nla_type, attr_map) 214*54b955f4SAlexander V. Chernikov ret.append(val) 215*54b955f4SAlexander V. Chernikov off += align4(nla_len) 216*54b955f4SAlexander V. Chernikov return NlAttrNested(attr_key, ret) 217*54b955f4SAlexander V. Chernikov 218fee65b7eSAlexander V. Chernikov def parse_attrs(self, data: bytes, attr_map): 219fee65b7eSAlexander V. Chernikov ret = [] 220fee65b7eSAlexander V. Chernikov off = 0 221fee65b7eSAlexander V. Chernikov while len(data) - off >= 4: 222fee65b7eSAlexander V. Chernikov nla_len, raw_nla_type = struct.unpack("@HH", data[off : off + 4]) 223fee65b7eSAlexander V. Chernikov if nla_len + off > len(data): 224fee65b7eSAlexander V. Chernikov raise ValueError( 225fee65b7eSAlexander V. Chernikov "attr length {} > than the remaining length {}".format( 226fee65b7eSAlexander V. Chernikov nla_len, len(data) - off 227fee65b7eSAlexander V. Chernikov ) 228fee65b7eSAlexander V. Chernikov ) 22904a03660SAlexander V. Chernikov nla_type = raw_nla_type & 0x3FFF 230fee65b7eSAlexander V. Chernikov if nla_type in attr_map: 231fee65b7eSAlexander V. Chernikov v = attr_map[nla_type] 232fee65b7eSAlexander V. Chernikov val = v["ad"].cls.from_bytes(data[off : off + nla_len], v["ad"].val) 233fee65b7eSAlexander V. Chernikov if "child" in v: 234fee65b7eSAlexander V. Chernikov # nested 235*54b955f4SAlexander V. Chernikov child_data = data[off + 4 : off + nla_len] 236*54b955f4SAlexander V. Chernikov if v.get("is_array", False): 237*54b955f4SAlexander V. Chernikov # Array of nested attributes 238*54b955f4SAlexander V. Chernikov val = self.parse_child_array( 239*54b955f4SAlexander V. Chernikov child_data, v["ad"].val, v["child"] 240fee65b7eSAlexander V. Chernikov ) 241*54b955f4SAlexander V. Chernikov else: 242*54b955f4SAlexander V. Chernikov val = self.parse_child(child_data, v["ad"].val, v["child"]) 243fee65b7eSAlexander V. Chernikov else: 244fee65b7eSAlexander V. Chernikov # unknown attribute 245fee65b7eSAlexander V. Chernikov val = NlAttr(raw_nla_type, data[off + 4 : off + nla_len]) 246fee65b7eSAlexander V. Chernikov ret.append(val) 247fee65b7eSAlexander V. Chernikov off += align4(nla_len) 248fee65b7eSAlexander V. Chernikov return ret, off 249fee65b7eSAlexander V. Chernikov 250fee65b7eSAlexander V. Chernikov def parse_nla_list(self, data: bytes) -> List[NlAttr]: 251fee65b7eSAlexander V. Chernikov return self.parse_attrs(data, self.nl_attrs_map) 252fee65b7eSAlexander V. Chernikov 253fee65b7eSAlexander V. Chernikov def __bytes__(self): 254fee65b7eSAlexander V. Chernikov ret = bytes() 255fee65b7eSAlexander V. Chernikov for nla in self.nla_list: 256fee65b7eSAlexander V. Chernikov ret += bytes(nla) 257fee65b7eSAlexander V. Chernikov ret = bytes(self.base_hdr) + ret 258fee65b7eSAlexander V. Chernikov self.nl_hdr.nlmsg_len = len(ret) + sizeof(Nlmsghdr) 259fee65b7eSAlexander V. Chernikov return bytes(self.nl_hdr) + ret 260fee65b7eSAlexander V. Chernikov 261fc2538cbSAlexander V. Chernikov def _get_msg_type(self): 262fc2538cbSAlexander V. Chernikov return self.nl_hdr.nlmsg_type 263fc2538cbSAlexander V. Chernikov 264fc2538cbSAlexander V. Chernikov @property 265fc2538cbSAlexander V. Chernikov def msg_props(self): 266fc2538cbSAlexander V. Chernikov msg_type = self._get_msg_type() 267fc2538cbSAlexander V. Chernikov for msg_props in self.messages: 268fc2538cbSAlexander V. Chernikov if msg_props.msg.value == msg_type: 269fc2538cbSAlexander V. Chernikov return msg_props 270fc2538cbSAlexander V. Chernikov return None 271fc2538cbSAlexander V. Chernikov 272fc2538cbSAlexander V. Chernikov @property 273fc2538cbSAlexander V. Chernikov def msg_name(self): 274fc2538cbSAlexander V. Chernikov msg_props = self.msg_props 275fc2538cbSAlexander V. Chernikov if msg_props is not None: 276fc2538cbSAlexander V. Chernikov return msg_props.msg.name 277fc2538cbSAlexander V. Chernikov return super().msg_name 278fc2538cbSAlexander V. Chernikov 279fee65b7eSAlexander V. Chernikov def print_base_header(self, hdr, prepend=""): 280fee65b7eSAlexander V. Chernikov pass 281fee65b7eSAlexander V. Chernikov 282fee65b7eSAlexander V. Chernikov def print_message(self): 283fc2538cbSAlexander V. Chernikov self.print_nl_header() 284fee65b7eSAlexander V. Chernikov self.print_base_header(self.base_hdr, " ") 285fee65b7eSAlexander V. Chernikov for nla in self.nla_list: 286fee65b7eSAlexander V. Chernikov nla.print_attr(" ") 287