当前位置: 首页 > article >正文

内核ICMP协议分析

一、发送、接收ICMPv4消息

        ICMP主要作用是发送有关网络层错误和控制消息的机制,让你能够通过发送ICMP消息来获取有关通信环境中问题的反馈。由于ICMP协议是TCP/IP协议栈的核心组成部分,用于传递网络层的控制信息,是网络通信的底层基础已被深度集成到内核代码中,因此不能构建为内核模块动态加载和卸载。

        ICMP也可以被用来发起各种安全攻击。比如,Smurf攻击是一种拒绝服务攻击,它使用ip广播地址,一广播方式向计算机网络中发送大量的ICMP数据包,并将受害者的ip地址用作源地址,因此每个收到ICMP请求的主机都会回复一个echo包,进而淹没源ip主机。

1.ICMPv4

        ICMPv4消息分为两类:错误消息和信息消息(查询消息)。ICMPv4被用于ping和traceroute等诊断工具。

一、内容详细讲解

1. ping 工具
  • 原理:属于用户空间程序(iputils 包),通过原始套接字发送 ICMP_ECHO 消息,目标主机收到后返回 ICMP_REPLY 消息,以此检测网络连通性。
  • 核心功能:验证主机是否可达,测量网络延迟。
2. traceroute 工具
  • 原理
    • 初始探测(TTL=1)

      • traceroute 首先发送一个 TTL(生存时间)为 1 的数据包(默认用 UDP 协议)。当数据包到达第一跳路由器时,路由器会将 TTL 减 1,此时 TTL 变为 0,路由器无法继续转发该数据包,会向源主机返回 ICMP_TIME_EXCEED 消息(超时消息)。通过这个消息,traceroute 就记录下了第一跳路由的信息。
    • 逐跳探测(TTL 递增)

      • 每次收到 ICMP_TIME_EXCEED 或 ICMP_DEST_UNREACH(目标不可达,如最终目标端口不可达)消息后,traceroute 会将新发送数据包的 TTL 加 1。例如,第二次发送 TTL=2 的数据包,此时数据包会经过第一跳路由,再到达第二跳路由,第二跳路由处理后 TTL 变 0,返回 ICMP_TIME_EXCEED,从而记录第二跳路由。重复此过程,直到数据包到达目标主机。
    • 目标响应阶段

      • 当 TTL 足够大,数据包最终到达目标主机。若目标主机发现 UDP 端口不可达(因 traceroute 默认用高位随机 UDP 端口,目标通常无对应服务),会返回 ICMP_DEST_UNREACH 消息,此时 traceroute 确认已到达目标,停止探测,并基于收集的超时消息生成完整的路由路径清单。
  • 核心功能:追踪数据包从源到目标主机的路由路径,展示经过的路由器。

二、案例演示

1. ping 案例

场景:检测百度服务器(www.baidu.com)是否连通。
命令

ping www.baidu.com  

输出示例

PING www.a.shifen.com (39.156.69.79) 56(84) bytes of data.  
64 bytes from 39.156.69.79: icmp_seq=1 ttl=128 time=34.5 ms  
64 bytes from 39.156.69.79: icmp_seq=2 ttl=128 time=32.1 ms  
# 按Ctrl+C停止,显示统计信息  
--- www.a.shifen.com ping statistics ---  
2 packets transmitted, 2 received, 0% packet loss, time 1001ms  
rtt min/avg/max/mdev = 32.104/33.302/34.500/1.198 ms  

解释:发送 ICMP_ECHO 包,接收 ICMP_REPLY,显示延迟(time)和丢包率等。

2. traceroute 案例

场景:追踪到百度服务器的路由路径。
命令(Linux/macOS):

traceroute www.baidu.com  

输出示例

traceroute to www.a.shifen.com (39.156.69.79), 64 hops max, 52 byte packets  
 1  router.local  1.234 ms  0.987 ms  0.876 ms  
 2  192.168.1.1  5.678 ms  4.321 ms  3.456 ms  
 3  *  *  *  
 4  202.97.XXX.XXX  34.567 ms  32.123 ms  31.456 ms  
# 最终显示目标主机响应  
64  39.156.69.79  45.678 ms  

解释

  • 每一行显示一跳路由的 IP 和延迟,* 表示未收到 ICMP_TIME_EXCEED 消息。
  • 通过 TTL 递增和收集的 ICMP 消息,逐步构建路由路径,直到到达目标主机。

        ICMP初始化操作是在引导阶段调用inet_init()中完成,具体如下:

2.ICMP报头

         ICMPv4报头由类型(8位)、代码(8位)、校验和(16位)和32位的可变部分组成。(上图中有效载荷在ICMP报文头部之后,不属于头部信息)ICMPv4报头用结构icmphdr表示如下:

        ICMPv4数据报在不超过576字节的前提下,有效载荷应该尽可能多的包含原始数据报的内容,因为 ICMP 的核心功能之一是反馈网络传输中的错误(如目标不可达、超时等)。携带原始数据报内容,能让发送方更精准地分析问题。例如,当路由设备因数据报过大无法转发时,ICMP 会包含原始数据报头部,发送方通过头部信息可判断是协议、端口或分片等问题。

        同时将长度限制在576字节是因为RFC 791 作为 IPv4 的基础规范,规定 576 字节是一个 “最小保障值”。在早期网络环境中,许多设备(如路由器、主机)的处理能力有限,576 字节是平衡性能与功能的妥协值。无论设备性能如何,都能处理基础的 ICMP 消息(如错误报告、网络探测),避免因报文长度不兼容导致网络诊断功能失效。例如,ping 等工具依赖 ICMP,若主机不支持 576 字节,会导致基础网络检测功能无法使用。

3.ping相关

        当主机接收到一个网络数据包时,数据包会从网络层开始处理,逐步经过一系列的处理函数,最终到达应用层或者完成相应的协议处理。对于 ICMP 回显应答消息,其处理流程大致如下:

  1. 数据包首先被网络设备接收,经过网络驱动层的处理后,进入网络协议栈的 IP 层。
  2. 在 IP 层,数据包会经过一系列的处理,详细过程见:(Linux内核网络层分析-CSDN博客),最终调用 ip_local_deliver_finish 函数,该函数负责将数据包从网络层交付给相应的上层协议处理。
  3. 因为接收到的是 ICMP 回显应答消息,所以会进入 ICMP 协议的处理流程,此时会调用 ping_rcv 函数来专门处理 ICMP 回显应答。
ip_local_deliver_finish 函数

这个函数是 IP 层处理本地接收数据包的最后一步,它的主要任务是将数据包从 IP 层交付给上层协议。在处理 ICMP 数据包时,ip_local_deliver_finish 会根据数据包的协议类型(这里是 ICMP),将数据包传递给 ICMP 协议的处理函数。具体来说,它会根据 ICMP 消息的类型调用不同的处理函数,对于 ICMP 回显应答消息,后续会触发相应的处理流程,最终可能会调用 ping_rcv 函数。

ping_rcv 函数

ping_rcv 函数是专门用来处理 ICMP 回显应答消息的。当 ip_local_deliver_finish 函数将 ICMP 回显应答消息传递给 ICMP 协议处理流程后,ping_rcv 函数会被调用,它会从数据包中提取 ICMP 头部信息,记录 id 和 sequence 等关键信息,完成对 ICMP 回显应答消息的处理。

        而接下来 ping_rcv 处理完 ICMP 回显应答后,通过以下流程将数据交付给应用层:

1. 匹配目标套接字

  • 应用层(如 ping 程序)会提前创建 原始套接字(SOCK_RAW 并绑定监听。内核维护一个套接字列表,记录所有监听 ICMP 协议的套接字。
  • ping_rcv 处理 ICMP 回显应答时,会根据报文中的关键信息(如 idsequence),匹配用户空间创建的原始套接字。例如,ping 程序发送请求时会设置唯一的 id,应答返回时通过该 id 找到对应的套接字。

2. 数据从内核空间拷贝到用户空间

  • 找到匹配的套接字后,内核通过套接字的发送队列,将 sk_buff(存储 ICMP 应答数据的缓冲区)中的数据从内核空间拷贝到用户空间。
  • 这一过程依赖套接字接口实现,应用层通过调用 recvfrom 等函数,从套接字缓冲区中读取数据。

3. 应用层处理数据

  • 用户空间的 ping 程序调用 recvfrom 等函数时,会阻塞等待数据(或非阻塞检查)。一旦数据拷贝完成,函数返回,ping 程序即可解析 ICMP 应答内容(如计算延迟、统计丢包率),最终将结果展示给用户(如显示 time=XX ms)。
  • 传统方式:早期发送 ping 必须使用 原始套接字(SOCK_RAW,需用户空间程序自己构造完整的 IP 头部、ICMP 头部,开发复杂且权限要求高(需管理员权限)。
  • 新方式:引入对 ICMP 协议的套接字支持后,可通过 socket(PF_INET, SOCK_DGRAM, PROT_ICMP) 创建套接字。这种方式类似 UDP 套接字(SOCK_DGRAM),但实际基于 ICMP 协议。它简化了 ping 开发:
    • 内核自动处理部分底层细节(如 IP 头部、校验和计算),无需用户空间完全手动构造报文。
    • 降低开发门槛,无需原始套接字的复杂操作,让普通程序也能更便捷地发送 ICMP 回显请求(ping 数据包)。

 4.接收ICMPv4消息

        方法ip_local_deliver_finish()处理目的地址为当前机器的数据包。收到ICMP数据包后,此方法就将其交给注册ICMPv4协议的原始套接字。在方法icmp_rcv()中进行处理。

1. 消息接收的前提条件

  • 传输方式:针对以 广播 或 组播 方式发送的 ICMPv4 数据包。
    • 广播是向网络中所有主机发送数据;组播是向特定组内的主机发送数据,二者都是一对多的传输方式。
  • 消息类型限定:仅处理 ICMP_ECHO(回显请求,如 ping 工具发送的请求)和 ICMP_TIMESTAMP(时间戳请求,用于获取网络延迟等时间信息)这两种类型的消息。

2. 请求合法性检查

当收到符合上述条件的消息(广播 / 组播 + ICMP_ECHO/ICMP_TIMESTAMP)时,系统会 读取特定变量(如系统配置中的访问控制参数),核实当前网络环境是否允许对 “广播 / 组播回答案请求” 做出响应。

  • 这一检查是出于安全和策略考虑。例如,若允许随意响应广播 / 组播的 ICMP_ECHO 请求,可能被利用发起网络攻击(如前文提到的 Smurf 攻击),因此需通过配置判断是否放行。

3. ICMP_ECHO 消息的处理

当收到 ICMP_ECHO 消息(即 ping 工具发送的回显请求)时,系统会调用 icmp_echo() 方法进行处理。

  • 该方法的核心作用是生成 ICMP_ECHO_REPLY 应答消息,回复给发送方,完成 ping 命令的响应流程,最终让用户通过 ping 结果判断网络连通性。

5.发送ICMPv4消息

        用于发送ICMPv4消息的方法有两个:一个是方法icmp_reply(),用于发送两种ICMP请求的相应;二是方法icmp_send(),用于发送当前机器在特定条件下主动发送的ICMPv4消息。

一、icmp_reply 方法

  • 功能:专门用于发送 ICMP 请求的响应消息,属于 “被动回复”。当系统接收到特定类型的 ICMP 请求(如 ICMP_ECHO 回显请求)时,通过此方法生成对应的应答消息。
  • 典型场景
    例如,主机收到 ping 命令发送的 ICMP_ECHO_REQUEST 消息后,调用 icmp_reply 发送 ICMP_ECHO_REPLY 消息,告知发送方 “目标可达”,完成一次 ping 的响应流程。

二、icmp_send 方法

  • 功能:用于在特定条件下主动发送 ICMPv4 消息,属于 “主动触发”。当系统检测到网络层的某些事件(如传输错误、状态变化)时,通过此方法主动生成并发送 ICMP 消息。
  • 典型场景
    例如,当路由器处理数据包时发现目标不可达(如路由表无对应条目),会通过 icmp_send 发送 ICMP_DEST_UNREACH(目标不可达)消息,通知源主机传输失败。

三、二者的核心区别

对比维度icmp_replyicmp_send
触发方式被动触发:基于接收到的 ICMP 请求主动触发:基于系统内部网络事件或状态
应用场景处理请求 - 响应场景(如 ping 应答)处理网络错误报告、状态通知等场景
消息类型关联多与请求消息类型强绑定(如回复 ECHO支持更广泛的 ICMP 类型(如超时、重定向等

6.消息处理逻及错误场景分析

一、ICMPv4 消息接收处理逻辑

1. 处理范围与条件

当系统收到以 广播(发给网络中所有设备)或 组播(发给特定组内设备)方式传输的 ICMPv4 消息,且消息类型是 ICMP_ECHO(回显请求,如 ping 命令发送的请求)或 ICMP_TIMESTAMP(时间戳请求,用于获取网络时间信息)时,系统会先读取内部配置变量(如访问控制设置),判断是否允许对这类广播 / 组播请求进行回应。

  • 举例:若某企业网络配置了禁止回应广播的 ICMP_ECHO 请求,当收到外部发送的广播 ping 请求时,系统会根据配置拒绝回应,避免被利用发起攻击(如 Smurf 攻击)。
2. ICMP_ECHO 消息的处理

如果收到的是 ICMP_ECHO 消息(即 ping 命令的回显请求),系统会调用 icmp_echo() 方法处理。该方法的核心是生成 ICMP_ECHO_REPLY 应答消息,告诉发送方 “目标可达”。

  • 举例:主机 A 对主机 B 发起 ping,主机 B 收到 ICMP_ECHO 请求后,通过 icmp_echo() 组织回应消息,最后主机 A 收到应答,显示 ping 成功。

二、ICMPv4 消息发送逻辑

1. 回应 ICMP_ECHO 和 ICMP_TIMESTAMP 的发送

前面提到的发送 ICMP 消息的方法(如 icmp_reply 等),最终都会调用 icmp_push_reply() 函数,来回应 ICMP_ECHO 和 ICMP_TIMESTAMP 消息。它是生成并发送这两类应答消息的关键函数。

  • 举例:主机收到 ICMP_ECHO 请求后,通过 icmp_push_reply() 打包 ICMP_ECHO_REPLY 消息,填充正确的头部信息(如类型、校验和),然后发送给请求方。
2. icmp_send() 的广泛应用

在 IPv4 网络的多个场景(如网络过滤模块 Netfilter、数据包转发代码 ip_forward.c、隧道技术 ip_gre 等)中,若需要主动发送 ICMP 消息(如报告网络错误),会调用 icmp_send()

  • 举例:路由器转发数据包时,发现目标网络不可达,就通过 icmp_send() 发送 ICMP_DEST_UNREACH(目标不可达)消息,通知源主机传输失败。

三、ICMP_PROT_UNREACH(协议不可达)

当 IP 数据报头部的 “协议字段”(8 位,标识上层协议,如 TCP 是 6,UDP 是 17)指定的协议在接收方不存在时,接收方会向发送方发送 ICMP_DEST_UNREACH 消息,且代码为 ICMP_PROT_UNREACH(协议不可达)。

  • 举例:发送方构造了一个 IP 数据报,协议字段填了一个不存在的协议值(如 255),当接收方解析时,发现没有对应的协议处理模块,就会发送 ICMP_PROT_UNREACH 消息给发送方,告知 “你指定的协议我不支持”。

 由于ICMP主要作用是发送有关网络层错误和控制消息的机制,所以以下情况也是ICMP的功能:

1. 对于端口不可达的处理:

        当应用层程序 A 使用 UDP 套接字,向目标 IP 地址的某端口(如端口 Y)发送数据时,若目标主机上的端口 Y 没有任何 UDP 套接字监听(即没有应用程序绑定该端口),目标主机接收该 UDP 数据包后会执行以下操作:

        1.先检查数据包校验和,若校验和 正确,目标主机会通过 ICMP 协议向发送方(程序 A 所在主机)发送 ICMP_PORT_UNREACH 消息,告知 “端口不可达”;

        2.若校验和 错误,则直接丢弃数据包,不触发 ICMP 消息发送。

举例
        开发一个 UDP 客户端程序,向服务器的 9999 端口发送数据。若服务器端未启动任何监听 9999 端口的程序,服务器收到客户端的 UDP 数据包后,因无匹配的 UDP 套接字,且若校验和正确,就会触发 ICMP 发送 “端口不可达” 消息。此时,客户端所在主机可能收到该 ICMP 消息,应用层可感知此次 UDP 通信因端口问题失败。

2.对于不存在协议的处理:

        当 IPv4 报头中的协议号错误(不在协议号列表中),或内核不支持该协议(协议未注册,协议处理程序数组无对应条目),导致数据包无法处理时,接收方需向发送方回发 ICMPv4 “目的地不可达” 消息,并用代码ICMP_PROT_UNREACH标明 “协议不可达” 的错误原因。

        1.先通过xfrm4_policy_check检测 IPsec 策略(非raw模式时)。若策略检测通过,就调用icmp_send发送 ICMP 消息,明确类型为ICMP_DEST_UNREACH、代码为ICMP_PROT_UNREACH,向发送方告知协议不可达;

        2.最后通过kfree_skb释放数据包缓冲区资源。

举例:

        若开发一个网络程序 A,错误地将 IPv4 报头协议号设为无效值(如 255)并发送给目标主机。目标主机解析时发现协议号无效且自身不支持该协议,便触发此处理逻辑 —— 通过icmp_send向程序 A 所在主机发送ICMP_PROT_UNREACH消息。程序 A 所在主机接收该消息后,应用层可感知此次通信因协议号错误而失败,可能显示 “协议不可达,无法处理数据包” 之类的提示。

3.长度超过MTU

         当转发数据包时,若数据包长度超过外出链路的 MTU(最大传输单元),且 IPv4 报头中未设置分段位(IP_DF,即不允许分片),就会触发此错误。此时目标主机会丢弃数据包,并通过 ICMP 协议向发送方返回代码为 ICMP_FRAG_NEEDED 的 ICMP_DEST_UNREACH 消息,告知发送方 “数据包过大且不允许分片,无法转发”。

        举例:服务器 A 向服务器 B 发送一个较大的 UDP 数据包,中间某路由器转发时发现数据包长度超过链路 MTU,且 IP 报头设置了不分片。路由器丢弃数据包后,向服务器 A 发送 ICMP_FRAG_NEEDED 消息。若服务器 A 的应用层监听此类消息,会感知到此次传输因 “数据包过大且不能分片” 而失败。

4.严格路由选择和网关选项被设置

         转发数据包时,若数据包同时设置了严格路由选择(strict routing)和网关(gatewaying)选项,会触发此错误。目标主机会丢弃数据包,并发送代码为 ICMP_SR_FAILED 的 “目的地不可达” ICMP 消息,向发送方反馈 “因严格路由和网关选项冲突,无法转发”。

        举例:开发一个网络程序,发送数据包时错误配置了严格路由选择和网关选项。当数据包到达转发设备,设备因这两个选项冲突无法处理,便丢弃数据包并向程序所在主机发送 ICMP_SR_FAILED 消息。程序接收该消息后,可判断传输失败原因是路由选项设置错误。

7.应用层代码案例

icmp_saw.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>

// 该校验和算法通过累加数据的 16 位分组,进行折叠操作并取反,得到一个 16 位的校验和,用于检测数据在传输过程中是否发生错误
unsigned short checksum(unsigned short *buf, int len) {
    unsigned int sum = 0;
    unsigned short result;

    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

// 发送 ICMP 请求
/*
这行代码定义了一个名为 send_icmp_packet 的函数,其返回值类型为 void,即不返回任何值。该函数接收三个参数:
sockfd:一个整数类型的参数,代表用于发送数据的套接字文件描述符。通过这个套接字可以进行网络通信。
dest_addr:一个指向 struct sockaddr_in 类型的指针,此结构体包含目标主机的 IP 地址和端口号等信息,这里主要是目标主机的 IPv4 地址。
seq:一个整数类型的参数,代表 ICMP 数据包的序列号,用于标识不同的 ICMP 请求。
*/
void send_icmp_packet(int sockfd, struct sockaddr_in *dest_addr, int seq) {
    struct icmp icmp_packet;
    memset(&icmp_packet, 0, sizeof(icmp_packet));
    //icmp_packet.icmp_type = ICMP_ECHO;:将 ICMP 数据包的类型字段 icmp_type 设置为 ICMP_ECHO,这表明该数据包是一个 ICMP 回显请求(Ping 请求)。
    icmp_packet.icmp_type = ICMP_ECHO;
    //icmp_packet.icmp_code = 0;:将 ICMP 数据包的代码字段 icmp_code 设置为 0,对于 ICMP 回显请求,代码字段通常为 0。
    icmp_packet.icmp_code = 0;
    icmp_packet.icmp_id = getpid() & 0xFFFF;
    //将 ICMP 数据包的序列号字段 icmp_seq 设置为传入的参数 seq,用于标识不同的 ICMP 请求,方便后续处理响应时进行匹配。
    icmp_packet.icmp_seq = seq;
    //将 ICMP 数据包的校验和字段 icmp_cksum 初始化为 0。在计算校验和时,需要先将该字段置为 0,然后再计算整个 ICMP 数据包的校验和
    icmp_packet.icmp_cksum = 0;

    icmp_packet.icmp_cksum = checksum((unsigned short *)&icmp_packet, sizeof(icmp_packet));

    sendto(sockfd, &icmp_packet, sizeof(icmp_packet), 0, (struct sockaddr *)dest_addr, sizeof(*dest_addr));
}

// 接收 ICMP 响应
/*
参数说明:
sockfd:表示用于接收数据的套接字文件描述符。
src_addr:指向 struct sockaddr_in 结构体的指针,用于存储发送方的地址信息。
seq:ICMP 数据包的序列号,用于匹配请求和响应。
start_time:指向 struct timeval 结构体的指针,记录发送请求的时间,用于计算 RTT。
*/
int receive_icmp_packet(int sockfd, struct sockaddr_in *src_addr, int seq, struct timeval *start_time) {
    char buffer[1024];
    struct ip *ip_header;
    struct icmp *icmp_header;
    socklen_t addrlen = sizeof(*src_addr);
    struct timeval end_time;

    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);

    struct timeval timeout;
    timeout.tv_sec = 2;
    timeout.tv_usec = 0;

    int select_result = select(sockfd + 1, &readfds, NULL, NULL, &timeout);
    if (select_result == -1) {
        perror("select");
        return -1;
    } else if (select_result == 0) {
        printf("Request timed out.\n");
        return -1;
    }

    while (1) {
        ssize_t recv_len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)src_addr, &addrlen);
        if (recv_len == -1) {
            perror("recvfrom");
            return -1;
        }

        gettimeofday(&end_time, NULL);

        ip_header = (struct ip *)buffer;
        icmp_header = (struct icmp *)(buffer + (ip_header->ip_hl << 2)); // 跳过 IP 首部

        //printf("Received packet: IP src=%s, ICMP type=%d, ICMP id=%d, ICMP seq=%d\n",
               //inet_ntoa(src_addr->sin_addr), icmp_header->icmp_type, icmp_header->icmp_id, icmp_header->icmp_seq);

        if (icmp_header->icmp_type == ICMP_ECHOREPLY && icmp_header->icmp_id == (getpid() & 0xFFFF) && icmp_header->icmp_seq == seq) {
            double rtt = (end_time.tv_sec - start_time->tv_sec) * 1000.0 + (end_time.tv_usec - start_time->tv_usec) / 1000.0;
            printf("Received ICMP reply from %s in %.2f ms\n", inet_ntoa(src_addr->sin_addr), rtt);
            return 0;
        }
    }

    return -1;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <destination_ip>\n", argv[0]);
        return 1;
    }

    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    if (inet_pton(AF_INET, argv[1], &dest_addr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        return 1;
    }

    int count = 0;
    const int MAX_COUNT = 10; // 最大发送次数
    const int INTERVAL = 1;   // 发送间隔(秒)

    while (count < MAX_COUNT) {
        struct timeval start_time;
        gettimeofday(&start_time, NULL);
        send_icmp_packet(sockfd, &dest_addr, count + 1);
        receive_icmp_packet(sockfd, &dest_addr, count + 1, &start_time);
        sleep(INTERVAL);
        count++;
    }

    close(sockfd);
    return 0;
}    

         从下图不妨看出效果完美复刻:

二、发送/接收ICMPv6消息

1.ICMPv6初始化

         ICMPv6是ipv6的有机组成部分,每个节点都必须全面实现,在ipv6中,ICMPv6除了用于错误处理和诊断外,还被用于邻居发现(ND)协议以组播侦听者发现(MLD)协议。

        ICMPv6初始化是由方法icmpv6_init()和icmp_sk_init()完成。

        ICMPv6和ICMPv4基本差不多,也为每个CPU都创建一个原始ICMPv6套接字,并将它们存储在一个数组中,要访问当前sk,可以调用方法icmpv6_sk()。 

 

2.ICMPv6报头

在 ICMP(互联网控制消息协议)中,“类型” 字段用于区分报文种类,其取值通过最高位划分两大功能类别:

1. 最高位为 0(取值 0 - 127):错误消息

用于报告网络通信中的错误状态,例如:

  • ICMP_DEST_UNREACH(值 1):目标不可达(如路由不可达、端口不可达);
  • ICMP_PKT_TOOBIG(值 2):数据包过长,需分片传输;
    这类消息用于通知发送方 “通信过程中出现错误”,帮助排查网络故障。

2. 最高位为 1(取值 128 - 255):信息消息

用于实现网络测试、信息交互等功能,例如:

  • ICMP_ECHO_REQUEST(值 128):回显请求(Ping 工具发送的请求包,主动询问对端是否可达);
  • ICMP_ECHO_REPLY(值 129):回显应答(对 ECHO_REQUEST 的响应,告知发送方 “已收到请求”);
    这类消息是主动发起的交互行为,用于测试网络连通性或获取特定信息。

用户提供的代码中,ICMPV6_ECHO_REQUEST 等宏定义对应信息消息类型,而 0 - 127 范围内的类型值(如代码中未完全展示的错误消息类型)则负责反馈网络传输中的异常情况。

3.接收ICMPv6消息方法

        收到的ICMP数据包被交给方法icmpv6_rcv(),具体方法如下:

 

 4.发送ICMPv6消息

        发送ICMPv6消息的主要方法为icmpv6_send(),具体源码如下:

 5.错误处理

1. ICMPv6 跳数限制超时

触发场景:IPv6 数据包转发过程中,每台主机都会将报头的 “跳数限制计数器” 减 1。当该计数器减为 0 时,触发此错误。
主机操作

  • 调用 icmpv6_send() 函数,发送一条代码为 ICMPV6_EXC_HOPLIMIT 的 ICMPV6_TIME_EXCEED 消息,告知发送方 “数据包因跳数限制超时被丢弃”;
  • 更新统计信息(如 IPSTATS_MIB_INHDRERRORS),记录错误事件;
  • 丢弃当前数据包,终止其转发流程。

2. ICMPv6 分段重组超时

触发场景:处理 IPv6 数据包分段重组时,超过规定时间仍未完成重组(如分段丢失、重组条件不满足)。
主机操作

  • 调用 icmpv6_send() 函数,发送代码为 ICMPV6_EXC_FRAGTIME 的 ICMPV6_TIME_EXCEED 消息,向发送方报告 “分段重组超时”;
  • 丢弃与该重组相关的分段数据,释放资源,避免无效占用。

3. ICMPv6 参数问题

触发场景:解析 IPv6 扩展报头时,发现报头存在参数错误(如选项不识别、格式异常)。
主机操作

  • 调用 icmpv6_param_prob() 函数,通过 icmpv6_send() 发送代码为 ICMPV6_UNK_OPTION 的 ICMPV6_PARAMPROB 消息,告知发送方 “报头参数存在问题”;
  • 丢弃当前有参数问题的数据包,阻止错误数据继续传输;
  • 可能记录错误统计信息,辅助后续网络故障分析。


http://www.kler.cn/a/595356.html

相关文章:

  • 使用excel.EasyExcel实现导出有自定义样式模板的excel数据文件,粘贴即用!!!
  • C# 项目06-计算程序运行时间
  • mysql 对json的处理?
  • deepseek使用记录25——当反思失效了
  • AI工具如何改变编程学习?Trae IDE与Claude 3.5的实践案例
  • 使用AI一步一步实现若依(18)
  • SpringBoot整合MQTT最详细版(亲测有效)
  • 基于springboot的教师工作量管理系统(031)
  • 同旺科技USB to I2C 适配器 ---- 指令循环发送功能
  • Linux系统——keepalived安装与部署
  • Eplan许可分析
  • 嵌入式芯片与系统设计竞赛,值得参加吗?如何选题?需要学什么?怎么准备?
  • 智能照明与新能源集成的精细化能效管理实践
  • 2020年全国职业院校技能大赛改革试点赛高职组“云计算”竞赛赛卷
  • 性能优化中如何“避免链接关键请求”
  • 招聘面试季--一文顿悟,Java中字节流和字符流的区别及使用场景上的差异
  • 5G 智慧杆塔:开启智能城市新未来
  • JAVA————十五万字汇总
  • 嵌入式硬件工程师从小白到入门-PCB绘制(二)
  • 让bnpy 在 Windows 上飞起来:跨平台改造