tcp/ip学习之8: ICMP协议请求和响应
ICMP(Internet Control Message Protocol)的请求和响应机制主要通过特定类型的ICMP消息来实现,其中最常见的是“回显请求”(Echo Request)和“回显应答”(Echo Reply)消息。这种机制通常用于网络诊断工具(如ping命令)来检测网络连接状态、测量延迟等。以下是ICMP请求和响应的详细过程:
1. 回显请求(Echo Request)与回显应答(Echo Reply)
1.1 基本概念
回显请求(Echo Request):由发送方主机生成并发送给目标主机的消息,用于请求目标主机返回一个响应。
回显应答(Echo Reply):目标主机收到回显请求后,生成并返回给发送方主机的消息,表示目标主机已经收到请求。
1.2 消息格式
ICMP回显请求和回显应答消息的格式如下:
| 字段名称 | 长度(字节) | 描述 |
|---|---|---|
| 类型(Type) | 1 | 回显请求为8,回显应答为0 |
| 代码(Code) | 1 | 通常为0 |
| 校验和(Checksum) | 2 | 用于检验消息的完整性和正确性 |
| 标识符(Identifier) | 2 | 用于区分不同的请求,通常由发送方设置 |
| 序列号(Sequence Number) | 2 | 用于区分同一请求中的不同数据包,通常由发送方设置 |
| 数据(Data) | 可变 | 用于携带用户数据或测试数据 |
1.3 工作过程
- 发送方主机生成回显请求消息:
- 发送方主机创建一个ICMP回显请求消息,设置类型为8(Echo Request),代码为0。
- 设置标识符和序列号,通常由操作系统或应用程序生成,用于区分不同的请求。
- 填充数据部分,可以是任意数据,通常用于携带测试信息。
- 计算校验和,确保消息的完整性。
- 将ICMP消息封装到IP数据报中,设置目标地址为目标主机的IP地址,然后发送出去。
- 目标主机接收并处理回显请求消息:
- 目标主机收到IP数据报后,解析出ICMP消息。
- 检查消息的类型、代码和校验和,确认消息的合法性。
- 如果消息合法,目标主机生成一个ICMP回显应答消息:
- 设置类型为0(Echo Reply),代码为0。
- 使用收到的回显请求消息中的标识符和序列号,确保响应与请求匹配。
- 将数据部分的内容从回显请求消息中复制到回显应答消息中。
- 计算校验和,确保消息的完整性。
- 将ICMP回显应答消息封装到IP数据报中,设置目标地址为发送方主机的IP地址,然后发送回去。-
- 发送方主机接收并处理回显应答消息:
- 发送方主机收到IP数据报后,解析出ICMP回显应答消息。
- 检查消息的类型、代码和校验和,确认消息的合法性。
- 检查标识符和序列号,确保该响应是针对之前发送的请求的。
如果响应合法,发送方主机可以确认目标主机是可达的,并可以根据发送和接收的时间差计算往返时间(RTT)。
2. 示例:使用ping命令
ping命令是基于ICMP回显请求和回显应答机制的一个典型应用。以下是使用ping命令的过程:
2.1 用户执行ping命令
用户在终端或命令行界面输入以下命令:
ping <目标主机IP地址>
ping 192.168.1.2
2.2 发送ICMP回显请求
用户的主机生成一个ICMP回显请求消息,设置目标地址为192.168.1.1。
操作系统将该消息封装到IP数据报中并发送出去。
2.3 目标主机响应
目标主机(192.168.1.1)收到ICMP回显请求消息后,生成并返回一个ICMP回显应答消息。
目标主机将回显应答消息封装到IP数据报中,设置目标地址为发送方主机的IP地址,然后发送回去。
2.4 发送方主机接收响应
发送方主机收到ICMP回显应答消息后,确认目标主机是可达的。
计算往返时间(RTT),并显示在终端上,例如:
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
3. ICMP请求和响应的其他类型
除了回显请求和回显应答,ICMP还支持其他类型的请求和响应消息,例如:
- 时间戳请求(Timestamp Request)和时间戳应答(Timestamp Reply):用于获取目标主机的当前时间。
- 地址掩码请求(Address Mask Request)和地址掩码应答(Address Mask Reply):用于获取目标主机的子网掩码信息。
这些消息的工作原理与回显请求和回显应答类似,都是通过发送请求消息并等待目标主机的响应来实现特定功能。
4. 注意事项
- 网络延迟和丢包:ICMP消息在网络中传输可能会受到延迟或丢包的影响。如果发送方主机在一定时间内没有收到响应,可能会认为目标主机不可达,但实际上可能是网络问题导致消息丢失。
- 防火墙和安全策略:某些网络环境中的防火墙或安全策略可能会阻止ICMP消息的传输。例如,某些企业网络可能会禁用ICMP回显请求和回显应答消息,以防止外部攻击者利用这些消息进行网络扫描。
- ICMP泛洪攻击:ICMP消息可能会被恶意利用,例如ICMP泛洪攻击(ICMP Flood Attack)。攻击者向目标主机发送大量的ICMP回显请求消息,导致目标主机忙于处理这些请求,从而耗尽资源或导致网络拥塞。
总之,ICMP的请求和响应机制是网络通信中的一个重要工具,通过合理使用可以有效检测网络状态和优化网络配置,但同时也需要注意其可能带来的安全问题。
- 编写代码
import socket
import os
import struct
import time
import select
# ICMP类型:回显请求
ICMP_ECHO_REQUEST = 8
def checksum(data):
"""计算校验和"""
n = len(data)
m = n % 2
sum = 0
for i in range(0, n - m, 2):
sum += (data[i]) + ((data[i + 1]) << 8)
if m:
sum += (data[-1])
sum = (sum >> 16) + (sum & 0xffff)
sum += (sum >> 16)
answer = ~sum & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
def send_one_ping(my_socket, id, sequence, dest_addr):
"""发送一次Ping数据包"""
my_checksum = 0
header = struct.pack("!bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id, sequence)
data = struct.pack("!d", time.time())
my_checksum = checksum(header + data)
header = struct.pack("!bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id, sequence)
packet = header + data
my_socket.sendto(packet, (dest_addr, 1))
def receive_one_ping(my_socket, id, sequence, dest_addr, timeout):
"""接收一次Ping的返回消息"""
time_left = timeout
while True:
started_select = time.time()
what_ready = select.select([my_socket], [], [], time_left)
how_long_in_select = (time.time() - started_select)
if not what_ready[0]:
return None
time_received = time.time()
rec_packet, addr = my_socket.recvfrom(1024)
header = rec_packet[20:28]
type, code, checksum, packet_id, seq = struct.unpack("!bbHHh", header)
if type == 0 and packet_id == id:
byte_in_double = struct.calcsize("d")
time_sent = struct.unpack("d", rec_packet[28:28 + byte_in_double])[0]
delay = time_received - time_sent
ttl = struct.unpack("!b", rec_packet[8:9])[0]
return delay, ttl
time_left -= how_long_in_select
if time_left <= 0:
return None
def do_one_ping(dest_addr, id, sequence, timeout):
"""向指定地址发送Ping消息"""
icmp = socket.getprotobyname("icmp")
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
send_one_ping(my_socket, id, sequence, dest_addr)
delay = receive_one_ping(my_socket, id, sequence, dest_addr, timeout)
my_socket.close()
return delay
def ping(host, timeout=1):
"""主函数Ping"""
dest = socket.gethostbyname(host)
print(f"Pinging {dest} using Python:")
print()
my_id = os.getpid() & 0xFFFF
for i in range(4):
delay = do_one_ping(dest, my_id, i, timeout)
if delay:
print(f"Reply from {dest}: bytes=32 seq={i} ttl={int(delay[1])} time={delay[0] * 1000:.2f}ms")
else:
print("Request timed out.")
# 示例用法
if __name__ == "__main__":
ping("192.168.1.2")
运行上面的代码:(运行两次)


浙公网安备 33010602011771号