def receive_one_ping(sock: socket, icmp_id: int, seq: int, timeout: int) -> float or None: """Receives the ping from the socket. IP Header (bits): version (8), type of service (8), length (16), id (16), flags (16), time to live (8), protocol (8), checksum (16), source ip (32), destination ip (32). ICMP Packet (bytes): IP Header (20), ICMP Header (8), ICMP Payload (*). Ping Wikipedia: https://en.wikipedia.org/wiki/Ping_(networking_utility) ToS (Type of Service) in IP header for ICMP is 0. Protocol in IP header for ICMP is 1. Args: sock: The same socket used for send the ping. icmp_id: ICMP packet id. Sent packet id should be identical with received packet id. seq: ICMP packet sequence. Sent packet sequence should be identical with received packet sequence. timeout: Timeout in seconds. Returns: The delay in seconds or None on timeout. Raises: TimeToLiveExpired: If the Time-To-Live in IP Header is not large enough for destination. TimeExceeded: If time exceeded but Time-To-Live does not expired. """ ip_header_slice = slice(0, struct.calcsize(IP_HEADER_FORMAT)) # [0:20] icmp_header_slice = slice(ip_header_slice.stop, ip_header_slice.stop + struct.calcsize(ICMP_HEADER_FORMAT)) # [20:28] ip_header_keys = ('version', 'tos', 'len', 'id', 'flags', 'ttl', 'protocol', 'checksum', 'src_addr', 'dest_addr') icmp_header_keys = ('type', 'code', 'checksum', 'id', 'seq') while True: selected = select.select([sock], [], [], timeout) if selected[0] == []: # Timeout raise errors.Timeout(timeout) time_recv = time.time() recv_data, addr = sock.recvfrom(1024) ip_header_raw, icmp_header_raw, icmp_payload_raw = recv_data[ip_header_slice], recv_data[icmp_header_slice], recv_data[icmp_header_slice.stop:] ip_header = dict(zip(ip_header_keys, struct.unpack(IP_HEADER_FORMAT, ip_header_raw))) _debug("IP HEADER:", ip_header) icmp_header = dict(zip(icmp_header_keys, struct.unpack(ICMP_HEADER_FORMAT, icmp_header_raw))) _debug("ICMP HEADER:", icmp_header) if icmp_header['type'] == IcmpType.TIME_EXCEEDED: # TIME_EXCEEDED has no icmp_id and icmp_seq. Usually they are 0. if icmp_header['code'] == IcmpTimeExceededCode.TTL_EXPIRED: raise errors.TimeToLiveExpired() # Some router does not report TTL expired and then timeout shows. raise errors.TimeExceeded() if icmp_header['id'] == icmp_id and icmp_header['seq'] == seq: # ECHO_REPLY should match the if icmp_header['type'] == IcmpType.ECHO_REQUEST: # filters out the ECHO_REQUEST itself. _debug("ECHO_REQUEST filtered out.") continue if icmp_header['type'] == IcmpType.ECHO_REPLY: time_sent = struct.unpack(ICMP_TIME_FORMAT, icmp_payload_raw[0:struct.calcsize(ICMP_TIME_FORMAT)])[0] return time_recv - time_sent
def receive_one_ping(sock: socket, icmp_id: int, seq: int, timeout: int) -> float or None: """Receives the ping from the socket. IP Header (bits): version (8), type of service (8), length (16), id (16), flags (16), time to live (8), protocol (8), checksum (16), source ip (32), destination ip (32). ICMP Packet (bytes): IP Header (20), ICMP Header (8), ICMP Payload (*). Ping Wikipedia: https://en.wikipedia.org/wiki/Ping_(networking_utility) ToS (Type of Service) in IP header for ICMP is 0. Protocol in IP header for ICMP is 1. Args: sock: The same socket used for send the ping. icmp_id: ICMP packet id. Sent packet id should be identical with received packet id. seq: ICMP packet sequence. Sent packet sequence should be identical with received packet sequence. timeout: Timeout in seconds. Returns: The delay in seconds or None on timeout. Raises: TimeToLiveExpired: If the Time-To-Live in IP Header is not large enough for destination. TimeExceeded: If time exceeded but Time-To-Live does not expired. DestinationHostUnreachable: If the destination host is unreachable. DestinationUnreachable: If the destination is unreachable. """ ip_header_slice = slice(0, struct.calcsize(IP_HEADER_FORMAT)) # [0:20] icmp_header_slice = slice(ip_header_slice.stop, ip_header_slice.stop + struct.calcsize(ICMP_HEADER_FORMAT)) # [20:28] timeout_time = time.time() + timeout # Exactly time when timeout. _debug("Timeout time:", time.ctime(timeout_time)) while True: timeout_left = timeout_time - time.time( ) # How many seconds left until timeout. timeout_left = timeout_left if timeout_left > 0 else 0 # Timeout must be non-negative _debug("Timeout left: {:.2f}s".format(timeout_left)) selected = select.select( [ sock, ], [], [], timeout_left) # Wait until sock is ready to read or time is out. if selected[0] == []: # Timeout raise errors.Timeout(timeout) time_recv = time.time() recv_data, addr = sock.recvfrom(1024) ip_header_raw, icmp_header_raw, icmp_payload_raw = recv_data[ ip_header_slice], recv_data[icmp_header_slice], recv_data[ icmp_header_slice.stop:] ip_header = read_ip_header(ip_header_raw) _debug("Received IP Header:", ip_header) icmp_header = read_icmp_header(icmp_header_raw) _debug("Received ICMP Header:", icmp_header) _debug("Received ICMP Payload:", icmp_payload_raw) if icmp_header['id'] and icmp_header[ 'id'] != icmp_id: # ECHO_REPLY should match the ID field. _debug("ICMP ID dismatch. Packet filtered out.") continue if icmp_header[ 'type'] == IcmpType.TIME_EXCEEDED: # TIME_EXCEEDED has no icmp_id and icmp_seq. Usually they are 0. if icmp_header['code'] == IcmpTimeExceededCode.TTL_EXPIRED: raise errors.TimeToLiveExpired( ) # Some router does not report TTL expired and then timeout shows. raise errors.TimeExceeded() if icmp_header[ 'type'] == IcmpType.DESTINATION_UNREACHABLE: # DESTINATION_UNREACHABLE has no icmp_id and icmp_seq. Usually they are 0. if icmp_header[ 'code'] == IcmpDestinationUnreachableCode.DESTINATION_HOST_UNREACHABLE: raise errors.DestinationHostUnreachable() raise errors.DestinationUnreachable() if icmp_header['id'] and icmp_header[ 'seq'] == seq: # ECHO_REPLY should match the SEQ field. if icmp_header[ 'type'] == IcmpType.ECHO_REQUEST: # filters out the ECHO_REQUEST itself. _debug("ECHO_REQUEST received. Packet filtered out.") continue if icmp_header['type'] == IcmpType.ECHO_REPLY: time_sent = struct.unpack( ICMP_TIME_FORMAT, icmp_payload_raw[0:struct.calcsize(ICMP_TIME_FORMAT)])[0] return time_recv - time_sent _debug("Uncatched ICMP Packet:", icmp_header)