一、什么是ICMP
ICMP是Internet控制报文协议(Internet Control Message Protocol)的缩写,是TCP/IP协议栈中的一个重要协议。
ICMP用于传递有关通信状态、错误和网络拥塞等信息。它主要是为了帮助网络管理员诊断和解决网络问题。
二、ICMP协议结构
ICMP报文是放在IP数据报的数据部分中,它通常包括ICMP报头和ICMP数据两部分。
ICMP报头一般包含以下字段:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Code | Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identifier | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
具体字段含义:
- Type:指示ICMP报文类型,CP1是回显请求,CP2是回显应答。
- Code:对Type进行细分,类型为1时,Code可以是0(Echo Request)或8(Echo Reply)。
- Checksum:ICMP头部和数据的16位的校验和。计算校验和时采用补码求和的方式。
- Identifier:用于标识此请求的ID值。通常是惟一的,因此可以与其他回显请求区分开来。
- Sequence Number:序列号值。可以将该字段视为用于识别每个回显请求的附加信息。
三、ICMP属于哪一层协议
ICMP作为IP层的一个可选模块,使用IP作为它的传输层协议。从这个角度来看,ICMP属于网络层协议。
ICMP主要用于网络的控制与管理,在TCP/IP的体系结构中,应当归类于网络层,但从实现方式上看,ICMP作为IP的协议扩展模块,天然属于IP层。
四、ICMP应用示例
下面是一个使用Python实现ICMP PING命令的示例:
import os, struct, socket, select, time ICMP_ECHO_REQUEST = 8 def checksum(source_string): sum = 0 count_to = (len(source_string) / 2) * 2 for count in xrange(0, count_to, 2): this_val = ord(source_string[count + 1]) * 256 + ord(source_string[count]) sum = sum + this_val sum = sum & 0xffffffff if count_to < len(source_string): sum = sum + ord(source_string[len(source_string) - 1]) sum = sum & 0xffffffff sum = (sum >> 16) + (sum & 0xffff) sum = sum + (sum >> 16) answer = ~sum answer = answer & 0xffff answer = answer >> 8 | (answer << 8 & 0xff00) return answer def receive_one_ping(icmp_socket, ID, timeout): time_left = timeout while True: started_select = time.time() ready_to_read = select.select([icmp_socket], [], [], time_left) how_long_in_select = (time.time() - started_select) if ready_to_read[0] == []: # Timeout return time_received = time.time() rec_packet, addr = icmp_socket.recvfrom(1024) icmp_header = rec_packet[20:28] type, code, checksum, packet_ID, sequence = struct.unpack( "bbHHh", icmp_header ) # Filters out the echo request itself. if type != ICMP_ECHO_REQUEST or packet_ID != ID: continue return time_received - started_select def send_one_ping(icmp_socket, dest_addr, ID): dest_addr = socket.gethostbyname(dest_addr) my_checksum = 0 header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1) data = struct.pack("d", time.time()) my_checksum = checksum(header + data) if os.name == "nt": # 注意:不同的操作系统对于SOCK_RAW的定义不同 # 而我们需要通过socket.IPPROTO_ICMP来获取其中的ICMP协议 protocol_type = socket.IPPROTO_ICMP else: protocol_type = socket.IPPROTO_ICMP header = struct.pack( "bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1 ) packet = header + data while packet: # 发送数据报 sent = icmp_socket.sendto(packet, (dest_addr, 1)) packet = packet[sent:] def ping(dest_addr, timeout=2, count=4): icmp = socket.getprotobyname("icmp") # 创建一个原始套接字 icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp) my_ID = os.getpid() & 0xFFFF for i in xrange(count): send_one_ping(icmp_socket, dest_addr, my_ID) delay = receive_one_ping(icmp_socket, my_ID, timeout) if delay is None: print("Timeout") else: print("Reply from {}: delay={:.3f}ms".format(dest_addr, delay * 1000)) time.sleep(1) icmp_socket.close() ping("www.baidu.com")