import ipaddress
import os
import socket
import struct
import sys
import threading
import time


# сканируем подсеть
SUBNET = '192.168.31.0/24'
# волшебная строка, которую мы будем искать в ICMP-ответах
# MESSAGE = 'PYTHONRULES!'
MESSAGE = 'asdfaskdflajdskfjalsjdf'

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]


# Добавляет в UDP-датаграммы наше волшебное сообщение
def udp_sender():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender:
        for i in range(10):
            sender.sendto(bytes(MESSAGE, 'utf8'), ('77.88.44.242', 5555))
            print('send icmp')
            # sender.sendto(bytes(MESSAGE, 'utf8'), ('8.8.8.8', 65212))
            time.sleep(0.3)
        # for ip in ipaddress.ip_network(SUBNET).hosts():
            # print(bytes(MESSAGE, 'utf8'), (str(ip), 65212))
            # sender.sendto(bytes(MESSAGE, 'utf8'), (str(ip), 65212))


def icmp_sender():
    icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
    icmp_packet = struct.pack('!BBHHH', 8, 0, 0, 0, 1) + b'pingdata'

    crc = 0
    for i in range(0, len(icmp_packet), 2):
        crc += (icmp_packet[i] << 8) + icmp_packet[i + 1]

        


class Scanner:
    def __init__(self, host):
        self.host = host
        if os.name == 'nt':
            socket_protocol = socket.IPPROTO_IP
        else:
            socket_protocol = socket.IPPROTO_ICMP
        
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
        self.socket.bind((host, 0))
        self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

        if os.name == 'nt':
            self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)



    def sniff(self):
        hosts_up = set([f'{str(self.host)} *'])
        try:
            while True:
                # читаем пакет
                raw_buffer = self.socket.recvfrom(65535)[0]
                # создаем IP-заголовок из первых 20 байт
                ip_header = IP(raw_buffer[0:20])
                # нас интересует ICMP
                # print(raw_buffer)
                print(ip_header.src_address, ip_header.dst_address)
                # print(ip_header.src_address)
                if ip_header.protocol == 'ICMP':
                    print('ICMP')
                    offset = ip_header.ihl * 4
                    buf = raw_buffer[offset:offset + 8]
                    # print(raw_buffer)
                    icmp_header = ICMP(buf)
                    # ищем тип и код 3
                    print(icmp_header.code, icmp_header.type)
                    if icmp_header.code == 3 and icmp_header.type == 3:
                        print('!')
                        if ipaddress.ip_address(ip_header.src_address) in ipaddress.IPv4Network(SUBNET):
                            # проверяем содержит ли буфер наше волшебное сообщение
                            print(raw_buffer[len(raw_buffer) - len(MESSAGE):])
                            if raw_buffer[len(raw_buffer) - len(MESSAGE):] == bytes(MESSAGE, 'utf8'):
                                tgt = str(ip_header.src_address)
                                print(ip_header.src_address, ip_header.dst_address)
                                if tgt != self.host and tgt not in hosts_up:
                                    hosts_up.add(str(ip_header.dst_address))
                                    print(f'Host Up: {tgt}')
        # обрабатываем Ctrl+C
        except KeyboardInterrupt:
            # если мы в Windows, выключаем неизбирательный режим
            if os.name == 'nt':
                self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
            
            print('\nUser interrupted')
            
            if hosts_up:
                print(f'\n\nSummary: Hosts up on {SUBNET}')
                for host in sorted(hosts_up):
                    print(f'{host}')
                print('')
                sys.exit()



if __name__ == '__main__':
    if len(sys.argv) == 2:
        host = sys.argv[1]
        s = Scanner(host)
        # time.sleep(1)
        t = threading.Thread(target=udp_sender)
        t.start()
        s.sniff()
            


