import ipaddress
import os
import socket
import struct
import sys


class IP:
    def __init__(self, buff):
        header = struct.unpack('<BBHHHBBH4s4s', buff)
        self.ver = header[0] >> 4
        self.ihl = header[0] & 0xF

        self.tos = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol_num = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]

        # IP-адруса, понятные человек
        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # сопоставляем константы протоколов с их назначением
        self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
        try:
            self.protocol = self.protocol_map[self.protocol_num]
        except Exception as e:
            print('%s No protocol for %s' % (e, self.protocol_num))
            self.protocol = str(self.protocol_num)


class ICMP:
    def __init__(self, buff):
        header = struct.unpack('<BBHHH', buff)
        self.type = header[0]
        self.code = header[1]
        self.sum = header[2]
        self.id = header[3]
        self.seq = header[4]

    
def sniff(host):
    # создаем сырой сокет и привязываем к общедоступному интерфейсу
    if os.name == 'nt':
        socket_protocol = socket.IPPROTO_IP
    else:
        socket_protokol = socket.IPPROTO_ICMP

    sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
    sniffer.bind((host, 0))
    # делаем так, чтобы захватывался IP-заголовок
    sniffer.setsockopt(socket.IPPROTO_IP, socket.RCVALL_ON, 1)
    
    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

    try:
        while True:
            # читаем пакет
            raw_buffer = sniffer.recvfrom(65535)[0]
            # создаем IP-заголовок из первый 20 байтов
            ip_header = IP(raw_buffer[0:20])
            # нас интересует ICMP
            if ip_header.protocol == 'ICMP':
                print("Protocol: %s %s -> %s" % (ip_header.protocol,
                                                ip_header.src_address,
                                                ip_header.dst_address))
            print(f'Version: {ip_header.ver}')
            print(f'Header Length: {ip_header.ihl} TTL: {ip_header.ttl}')

            # определяем где начинается ICMP-пакет
            offset = ip_header.ihl * 4
            buf = raw_buffer[offset:offset + 8]
            # создаем структуру ICMP
            icmp_header = ICMP(buf)
            print('ICMP -> Type: %s Code: %s\n' % (icmp_header.type, icmp_header.code))

    except KeyboardInterrupt:
        # если мы в Windows, выключаем неизбирательный режим
        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
        sys.exit()


if __name__ == '__main__':
    if len(sys.argv) == 2:
        host = sys.argv[1]
        sniff(host)