1fee65b7eSAlexander V. Chernikov#!/usr/local/bin/python3 2fee65b7eSAlexander V. Chernikovimport struct 3fee65b7eSAlexander V. Chernikovfrom ctypes import sizeof 4*fc2538cbSAlexander V. Chernikovfrom enum import Enum 5fee65b7eSAlexander V. Chernikovfrom typing import List 6*fc2538cbSAlexander 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 10*fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmAckFlags 11*fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmNewFlags 12*fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.base_headers import NlmGetFlags 13*fc2538cbSAlexander 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 19*fc2538cbSAlexander V. Chernikovfrom atf_python.sys.netlink.utils import get_bitmask_str 20*fc2538cbSAlexander V. Chernikov 21*fc2538cbSAlexander V. Chernikov 22*fc2538cbSAlexander V. Chernikovclass NlMsgCategory(Enum): 23*fc2538cbSAlexander V. Chernikov UNKNOWN = 0 24*fc2538cbSAlexander V. Chernikov GET = 1 25*fc2538cbSAlexander V. Chernikov NEW = 2 26*fc2538cbSAlexander V. Chernikov DELETE = 3 27*fc2538cbSAlexander V. Chernikov ACK = 4 28*fc2538cbSAlexander V. Chernikov 29*fc2538cbSAlexander V. Chernikov 30*fc2538cbSAlexander V. Chernikovclass NlMsgProps(NamedTuple): 31*fc2538cbSAlexander V. Chernikov msg: Enum 32*fc2538cbSAlexander 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 83*fc2538cbSAlexander V. Chernikov @property 84*fc2538cbSAlexander V. Chernikov def msg_name(self): 85*fc2538cbSAlexander V. Chernikov return "msg#{}".format(self._get_msg_type()) 86*fc2538cbSAlexander V. Chernikov 87*fc2538cbSAlexander V. Chernikov def _get_nl_category(self): 88*fc2538cbSAlexander V. Chernikov if self.is_reply(self.nl_hdr): 89*fc2538cbSAlexander V. Chernikov return NlMsgCategory.ACK 90*fc2538cbSAlexander V. Chernikov return NlMsgCategory.UNKNOWN 91*fc2538cbSAlexander V. Chernikov 92*fc2538cbSAlexander V. Chernikov def get_nlm_flags_str(self): 93*fc2538cbSAlexander V. Chernikov category = self._get_nl_category() 94*fc2538cbSAlexander V. Chernikov flags = self.nl_hdr.nlmsg_flags 95*fc2538cbSAlexander V. Chernikov 96*fc2538cbSAlexander V. Chernikov if category == NlMsgCategory.UNKNOWN: 97*fc2538cbSAlexander V. Chernikov return self.helper.get_bitmask_str(NlmBaseFlags, flags) 98*fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.GET: 99*fc2538cbSAlexander V. Chernikov flags_enum = NlmGetFlags 100*fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.NEW: 101*fc2538cbSAlexander V. Chernikov flags_enum = NlmNewFlags 102*fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.DELETE: 103*fc2538cbSAlexander V. Chernikov flags_enum = NlmDeleteFlags 104*fc2538cbSAlexander V. Chernikov elif category == NlMsgCategory.ACK: 105*fc2538cbSAlexander V. Chernikov flags_enum = NlmAckFlags 106*fc2538cbSAlexander V. Chernikov return get_bitmask_str([NlmBaseFlags, flags_enum], flags) 107*fc2538cbSAlexander V. Chernikov 108*fc2538cbSAlexander 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 110*fc2538cbSAlexander 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, 115*fc2538cbSAlexander V. Chernikov self.msg_name, 116*fc2538cbSAlexander 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): 137*fc2538cbSAlexander 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 197fee65b7eSAlexander V. Chernikov def parse_attrs(self, data: bytes, attr_map): 198fee65b7eSAlexander V. Chernikov ret = [] 199fee65b7eSAlexander V. Chernikov off = 0 200fee65b7eSAlexander V. Chernikov while len(data) - off >= 4: 201fee65b7eSAlexander V. Chernikov nla_len, raw_nla_type = struct.unpack("@HH", data[off:off + 4]) 202fee65b7eSAlexander V. Chernikov if nla_len + off > len(data): 203fee65b7eSAlexander V. Chernikov raise ValueError( 204fee65b7eSAlexander V. Chernikov "attr length {} > than the remaining length {}".format( 205fee65b7eSAlexander V. Chernikov nla_len, len(data) - off 206fee65b7eSAlexander V. Chernikov ) 207fee65b7eSAlexander V. Chernikov ) 208fee65b7eSAlexander V. Chernikov nla_type = raw_nla_type & 0x3F 209fee65b7eSAlexander V. Chernikov if nla_type in attr_map: 210fee65b7eSAlexander V. Chernikov v = attr_map[nla_type] 211fee65b7eSAlexander V. Chernikov val = v["ad"].cls.from_bytes(data[off:off + nla_len], v["ad"].val) 212fee65b7eSAlexander V. Chernikov if "child" in v: 213fee65b7eSAlexander V. Chernikov # nested 214fee65b7eSAlexander V. Chernikov attrs, _ = self.parse_attrs( 215fee65b7eSAlexander V. Chernikov data[off + 4:off + nla_len], v["child"] 216fee65b7eSAlexander V. Chernikov ) 217fee65b7eSAlexander V. Chernikov val = NlAttrNested(v["ad"].val, attrs) 218fee65b7eSAlexander V. Chernikov else: 219fee65b7eSAlexander V. Chernikov # unknown attribute 220fee65b7eSAlexander V. Chernikov val = NlAttr(raw_nla_type, data[off + 4:off + nla_len]) 221fee65b7eSAlexander V. Chernikov ret.append(val) 222fee65b7eSAlexander V. Chernikov off += align4(nla_len) 223fee65b7eSAlexander V. Chernikov return ret, off 224fee65b7eSAlexander V. Chernikov 225fee65b7eSAlexander V. Chernikov def parse_nla_list(self, data: bytes) -> List[NlAttr]: 226fee65b7eSAlexander V. Chernikov return self.parse_attrs(data, self.nl_attrs_map) 227fee65b7eSAlexander V. Chernikov 228fee65b7eSAlexander V. Chernikov def __bytes__(self): 229fee65b7eSAlexander V. Chernikov ret = bytes() 230fee65b7eSAlexander V. Chernikov for nla in self.nla_list: 231fee65b7eSAlexander V. Chernikov ret += bytes(nla) 232fee65b7eSAlexander V. Chernikov ret = bytes(self.base_hdr) + ret 233fee65b7eSAlexander V. Chernikov self.nl_hdr.nlmsg_len = len(ret) + sizeof(Nlmsghdr) 234fee65b7eSAlexander V. Chernikov return bytes(self.nl_hdr) + ret 235fee65b7eSAlexander V. Chernikov 236*fc2538cbSAlexander V. Chernikov def _get_msg_type(self): 237*fc2538cbSAlexander V. Chernikov return self.nl_hdr.nlmsg_type 238*fc2538cbSAlexander V. Chernikov 239*fc2538cbSAlexander V. Chernikov @property 240*fc2538cbSAlexander V. Chernikov def msg_props(self): 241*fc2538cbSAlexander V. Chernikov msg_type = self._get_msg_type() 242*fc2538cbSAlexander V. Chernikov for msg_props in self.messages: 243*fc2538cbSAlexander V. Chernikov if msg_props.msg.value == msg_type: 244*fc2538cbSAlexander V. Chernikov return msg_props 245*fc2538cbSAlexander V. Chernikov return None 246*fc2538cbSAlexander V. Chernikov 247*fc2538cbSAlexander V. Chernikov @property 248*fc2538cbSAlexander V. Chernikov def msg_name(self): 249*fc2538cbSAlexander V. Chernikov msg_props = self.msg_props 250*fc2538cbSAlexander V. Chernikov if msg_props is not None: 251*fc2538cbSAlexander V. Chernikov return msg_props.msg.name 252*fc2538cbSAlexander V. Chernikov return super().msg_name 253*fc2538cbSAlexander V. Chernikov 254fee65b7eSAlexander V. Chernikov def print_base_header(self, hdr, prepend=""): 255fee65b7eSAlexander V. Chernikov pass 256fee65b7eSAlexander V. Chernikov 257fee65b7eSAlexander V. Chernikov def print_message(self): 258*fc2538cbSAlexander V. Chernikov self.print_nl_header() 259fee65b7eSAlexander V. Chernikov self.print_base_header(self.base_hdr, " ") 260fee65b7eSAlexander V. Chernikov for nla in self.nla_list: 261fee65b7eSAlexander V. Chernikov nla.print_attr(" ") 262