def send_one_ping(sock: socket, dest_addr: str, icmp_id: int, seq: int, size: int): """Sends one ping to the given destination. ICMP Header (bits): type (8), code (8), checksum (16), id (16), sequence (16) ICMP Payload: time (double), data ICMP Wikipedia: https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol Args: sock: Socket. dest_addr: The destination address, can be an IP address or a domain name. Ex. "192.168.1.1"/"example.com" icmp_id: ICMP packet id, usually is same as pid. seq: ICMP packet sequence, usually increases from 0 in the same process. size: The ICMP packet payload size in bytes. Note this is only for the payload part. Raises: HostUnkown: If destination address is a domain name and cannot resolved. """ try: dest_addr = socket.gethostbyname(dest_addr) # Domain name will translated into IP address, and IP address leaves unchanged. except socket.gaierror as e: print("Cannot resolve {}: Unknown host".format(dest_addr)) raise errors.HostUnknown(dest_addr) from e pseudo_checksum = 0 # Pseudo checksum is used to calculate the real checksum. icmp_header = struct.pack(ICMP_HEADER_FORMAT, IcmpType.ECHO_REQUEST, ICMP_DEFAULT_CODE, pseudo_checksum, icmp_id, seq) padding = (size - struct.calcsize(ICMP_TIME_FORMAT) - struct.calcsize(ICMP_HEADER_FORMAT)) * "Q" # Using double to store current time. icmp_payload = struct.pack(ICMP_TIME_FORMAT, time.time()) + padding.encode() real_checksum = checksum(icmp_header + icmp_payload) # Calculates the checksum on the dummy header and the icmp_payload. # Don't know why I need socket.htons() on real_checksum since ICMP_HEADER_FORMAT already in Network Bytes Order (big-endian) icmp_header = struct.pack(ICMP_HEADER_FORMAT, IcmpType.ECHO_REQUEST, ICMP_DEFAULT_CODE, socket.htons(real_checksum), icmp_id, seq) # Put real checksum into ICMP header. packet = icmp_header + icmp_payload sock.sendto(packet, (dest_addr, 0)) # addr = (ip, port). Port is 0 respectively the OS default behavior will be used.
def resolve_ip(dest_addr: str): """Resolves the domain name or returns the IP if IP Defaults to IPv4 where available, returns the ip type as string 4 or 6 Args: dest_addr: IPv4, IPv6 or hostname Raises: HostUnknown: If destination address is a domain name and cannot resolved. """ # Domain name will translated into IP address, and IP address leaves unchanged. try: ipv4_addresses = socket.getaddrinfo(dest_addr, None, family=socket.AF_INET) dest_addr = list(set(item[4][0] for item in ipv4_addresses))[0] return dest_addr, '4' except socket.gaierror: try: ipv6_addresses = socket.getaddrinfo(dest_addr, None, family=socket.AF_INET6) dest_addr = list(set(item[4][0] for item in ipv6_addresses))[0] return dest_addr, '6' except socket.gaierror as e: raise errors.HostUnknown(dest_addr) from e