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
Example #5
0
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