class ArpPacketParser(PacketParserInterface): def __init__(self, config: ConfigurationData): self.config = config self.ip_utils = IpAddrUtils() self.mac_utils = MacAddressUtils() def extract_data(self, packet: ARP) -> Munch: data = Munch() try: data.arp_request_src = packet.op data.arp_src_mac = self.mac_utils.convert_hexadecimal_mac_to_readable_mac( packet.sha) data.arp_src_ip = self.ip_utils.inet_to_str(packet.spa) data.arp_dst_mac = self.mac_utils.convert_hexadecimal_mac_to_readable_mac( packet.tha) data.arp_dst_ip = self.ip_utils.inet_to_str(packet.tpa) if self.config.use_numeric_values is True: data.arp_src_mac = self.mac_utils.mac_to_int(data.arp_src_mac) data.arp_dst_mac = self.mac_utils.mac_to_int(data.arp_dst_mac) data.arc_src_ip = self.ip_utils.ip_to_int(data.arp_src_ip) data.arc_dst_ip = self.ip_utils.ip_to_int(data.arp_src_ip) except BaseException as ex: logging.warning('Unable to extract ARP from `%s`. Error: `%s`', type(packet), ex) raise ex return data
class IgmpPacketParser(PacketParserInterface): # Use Scipy: https://github.com/secdev/scapy/blob/master/scapy/contrib/igmp.py def __init__( self, config: ConfigurationData, ): self.config = config self.ip_addr_utils = IpAddrUtils() @staticmethod def load_igmp_packet_from_ip_packet(ip_packet: IP) -> IGMP: try: return IGMP(ip_packet.data) except BaseException as ex: logging.warning( 'Can not extract IGMP packet data from IP Packet. Error: `%s`', ex) raise ex def extract_data(self, packet: IGMP) -> Munch: data = Munch() if packet is None: return data data.igmp_type = packet.type data.igmp_addr = self.ip_addr_utils.inet_to_str(packet.group) if self.config.use_numeric_values is True: data.igmp_addr = self.ip_addr_utils.ip_to_int(data.igmp_addr) return data
class Ip6PacketParser(PacketParserInterface): def __init__(self, config: ConfigurationData): self.config = config self.ip_utils = IpAddrUtils() def extract_data(self, packet: IP6) -> Munch: data = Munch() try: data.src_ip, data.dst_ip = self.extract_src_dest_ip(packet) data.ip_proto = packet.p data.ip_payload_size = len(packet.data) if packet.all_extension_headers: data.ip6_nxt_hdr = self.config.FieldDelimiter.join([ str(header.nxt) for header in packet.all_extension_headers ]) except BaseException as ex: logging.warning('Unable to extract IP6 from `%s`. Error: `%s`', type(packet), ex) raise ex return data def extract_src_dest_ip(self, ip_packet: IP) -> Tuple: src_ip = self.ip_utils.inet_to_str(ip_packet.src) dst_ip = self.ip_utils.inet_to_str(ip_packet.dst) if self.config.use_numeric_values is True: return self.ip_utils.ip_to_int(src_ip), self.ip_utils.ip_to_int( dst_ip) return src_ip, dst_ip
class NtpPacketParser(PacketParserInterface): def __init__(self, config: ConfigurationData): self.config = config self.ip_utils = IpAddrUtils() @staticmethod def load_ntp_packet_from_ip_packet(ip_packet: IP) -> Optional[NTP]: try: udp_packet = UDP(ip_packet.data) return NtpPacketParser.load_ntp_packet_from_udp_packet(udp_packet) except BaseException as ex: logging.warning( 'Can not extract NTP packet from UDP packet. Error: `%s`', ex) raise ex @staticmethod def load_ntp_packet_from_udp_packet(udp_packet: UDP) -> Optional[NTP]: try: return NTP(udp_packet.data) except dpkt.dpkt.NeedData: logging.warning( 'Not enough data to extract NTP packet from UDP packet') except BaseException as ex: logging.warning( 'Can not extract NTP packet from UDP packet. Error: `%s`', ex) raise ex def extract_data(self, packet: NTP) -> Munch: data = Munch() try: data.ntp_mode = packet.mode data.ntp_interval = packet.interval data.ntp_stratum = packet.stratum data.ntp_reference_id = self.resolve_ntp_reference(packet) except BaseException as ex: logging.warning('Unable to extract NTP from `%s`. Error: `%s`', type(packet), ex) raise ex return data def resolve_ntp_reference(self, packet: NTP) -> Union[int, str]: reference_id = self.ip_utils.inet_to_str(packet.id) if reference_id is None: # Could not parse NTP REFID, probably it is a string return packet.id if reference_id == '0.0.0.0': # REFID is NULL but dpkt considers it as b'\x00\x00\x00\x00' return '' if self.config.use_numeric_values is True: return self.ip_utils.ip_to_int(reference_id) return reference_id
class DnsPacketParser(PacketParserInterface): def __init__(self, config: ConfigurationData): self.config = config self.ip_utils = IpAddrUtils() @staticmethod def load_dns_packet_from_ip_packet(ip_packet: IP) -> Optional[DNS]: try: udp_packet = UDP(ip_packet.data) return DnsPacketParser.load_dns_packet_from_udp_packet(udp_packet) except BaseException as ex: logging.warning('Can not extract DNS packet from UDP packet. Error: `%s`', ex) raise ex @staticmethod def load_dns_packet_from_udp_packet(udp_packet: UDP) -> Optional[DNS]: try: return DNS(udp_packet.data) except Exception as ex: logging.warning('Can not extract DNS packet from UDP packet. Error: `%s`', ex) raise ex def extract_data(self, packet: DNS) -> Munch: data = Munch() try: data.dns_type = packet.qr data.dns_op = packet.op data.dns_rcode = packet.rcode if data.dns_type == dpkt.dns.DNS_Q: # This is a DNS query data.update(self.extract_data_from_dns_query(packet)) elif data.dns_type == dpkt.dns.DNS_R: # This is a DNS response data.update(self.extract_data_from_dns_response(packet)) except BaseException as ex: logging.warning('Unable to extract DNS from `%s`. Error: `%s`', type(packet), ex) raise ex return data def extract_data_from_dns_query(self, dns_packet: DNS) -> Munch: data = Munch() try: if len(dns_packet.qd) > 1: if self.config.use_numeric_values is True: data.dns.query_multiple_domains = 1 else: data.dns_query_multiple_domains = True data.dns_query_domain = self.config.FieldDelimiter.join([q.name for q in dns_packet.qd]) data.dns_query_type = self.config.FieldDelimiter.join([str(q.type) for q in dns_packet.qd]) data.dns_query_cls = self.config.FieldDelimiter.join([str(q.cls) for q in dns_packet.qd]) except BaseException as ex: logging.warning('Unable to extract DNS from `%s`. Error: `%s`', type(dns_packet), ex) raise ex return data def extract_data_from_dns_response(self, dns_packet: DNS) -> Munch: data = Munch() # Process and get responses based on record types listed in # http://en.wikipedia.org/wiki/List_of_DNS_record_types dns_ans_ip_list = [] dns_ans_name_list = [] dns_ans_ttl = [] try: for answer in dns_packet.an: data.dns_ans_type = answer.type if answer.type == dpkt.dns.DNS_CNAME: data.dns_ans_cname = answer.name data.dns_ans_cname_ttl = answer.ttl elif answer.type == dpkt.dns.DNS_A or answer.type == dpkt.dns.DNS_AAAA: if hasattr(answer, 'ip'): dns_ans_ip_list.append(self.ip_utils.inet_to_str(answer.ip)) dns_ans_name_list.append(answer.name) dns_ans_ttl.append(answer.ttl) # TODO: Handle other types of dns answers: # Ref: https://engineering-notebook.readthedocs.io/en/latest/engineering/dpkt.html#dns-answer data.dns_ans_name = self.config.FieldDelimiter.join(dns_ans_name_list) # We are using only max value because in experience ttl is same even if there is separate ttl for each IP # address in DNS response if dns_ans_ttl: data.dns_ans_ttl = max(dns_ans_ttl) else: data.dns_ans_ttl = None if self.config.use_numeric_values is True: dns_ans_ip_list = map(self.ip_utils.ip_to_int, dns_ans_ip_list) data.dns_ans_ip = self.config.FieldDelimiter.join([str(ip) for ip in dns_ans_ip_list]) except Exception as ex: logging.error('Unable to process dns answers packet. Error: `%s`', ex) raise ex return data
class IpPacketParser(PacketParserInterface): def __init__(self, config: ConfigurationData, static_data: StaticData = None): self.config = config self.ip_utils = IpAddrUtils() self.static_data = static_data or StaticData() @staticmethod def load_ip_packet_from_ethernet_frame( packet_data: bytes) -> Union[IP, IP6]: if isinstance(packet_data, (IP, IP6)): return packet_data # Packet data is bytes because it is a fragmented packet. try: return IP(packet_data) except dpkt.dpkt.UnpackError: return IP6( packet_data) # When IPv6 packet is encapsulated in IPv4 packet except BaseException as ex: logging.error( 'Can not parse Ethernet frame as IPv4 or IPv6 packet. Error: `%s`', ex) raise ex def extract_data(self, packet: IP) -> Munch: data = Munch() try: data.src_ip, data.dst_ip = self.extract_src_dest_ip(packet) data.ip_proto = self.get_ip_proto_name(packet.p) data.ip_payload_size = len(packet.data) data.ip_ttl = packet.ttl data.ip_tos = packet.tos data.ip_opts = self.parse_ip_options(packet.opts) or '' data.ip_do_not_fragment = bool(packet.off & dpkt.ip.IP_DF) data.ip_more_fragment = bool(packet.off & dpkt.ip.IP_MF) if self.config.use_numeric_values: data.ip_do_not_fragment = 1 if data.ip_do_not_fragment is True else 0 data.ip_more_fragment = 1 if data.ip_more_fragment is True else 0 except BaseException as ex: logging.warning('Unable to extract IP4 from `%s`. Error: `%s`', type(packet), ex) raise ex return data # pylint: disable=duplicate-code def extract_src_dest_ip(self, ip_packet: IP) -> Tuple: src_ip = self.ip_utils.inet_to_str(ip_packet.src) dst_ip = self.ip_utils.inet_to_str(ip_packet.dst) if self.config.use_numeric_values is True: return self.ip_utils.ip_to_int(src_ip), self.ip_utils.ip_to_int( dst_ip) return src_ip, dst_ip def parse_ip_options(self, ip_options: bytes) -> Union[int, str]: # Split 4 bytes \x94\x04\x00\x00 to single bytes [94, 04, 00, 00] options = binascii.hexlify(ip_options).decode('utf-8') if not options: return '' options = [options[i:i + 2] for i in range(0, len(options), 2)] # first byte gives information of IP options if self.config.use_numeric_values is True: return hex_to_integer(options[0]) hex_option = '0x' + options[0] return self.static_data.ip_options_data.get( hex_option, {}).get('abbrv') or hex_option def get_ip_proto_name(self, proto_num: int) -> Union[int, str]: if self.config.use_numeric_values is True: return proto_num try: proto_key = str(proto_num) except ValueError: return proto_num proto_name = '' if proto_key in self.static_data.ip_protocol_data: proto_name = self.static_data.ip_protocol_data.get( proto_key, {}).get('keyword', '') return proto_name or proto_key