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 工作过程
  1. 发送方主机生成回显请求消息:
  • 发送方主机创建一个ICMP回显请求消息,设置类型为8(Echo Request),代码为0。
  • 设置标识符和序列号,通常由操作系统或应用程序生成,用于区分不同的请求。
  • 填充数据部分,可以是任意数据,通常用于携带测试信息。
  • 计算校验和,确保消息的完整性。
  • 将ICMP消息封装到IP数据报中,设置目标地址为目标主机的IP地址,然后发送出去。
  1. 目标主机接收并处理回显请求消息:
  • 目标主机收到IP数据报后,解析出ICMP消息。
  • 检查消息的类型、代码和校验和,确认消息的合法性。
  • 如果消息合法,目标主机生成一个ICMP回显应答消息:
    • 设置类型为0(Echo Reply),代码为0。
    • 使用收到的回显请求消息中的标识符和序列号,确保响应与请求匹配。
    • 将数据部分的内容从回显请求消息中复制到回显应答消息中。
    • 计算校验和,确保消息的完整性。
    • 将ICMP回显应答消息封装到IP数据报中,设置目标地址为发送方主机的IP地址,然后发送回去。-
  1. 发送方主机接收并处理回显应答消息:
  • 发送方主机收到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的请求和响应机制是网络通信中的一个重要工具,通过合理使用可以有效检测网络状态和优化网络配置,但同时也需要注意其可能带来的安全问题。

  1. 编写代码
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")

运行上面的代码:(运行两次)
image

posted @ 2025-07-28 08:34  cupid8505  阅读(177)  评论(0)    收藏  举报