LwIP下ICMP协议浅析
目录1 ICMP协议1.1 工作机制1.2 ICMP 的实际应用场景2 ICMP协议在嵌入式系统中的应用2.1 差错报告报文 (Error Reporting)2.2 询问/查询报文 (Query Messages)2.3 LwIP上ICMP功能具体实现1 ICMP协议ICMPInternet Control Message Protocol互联网控制消息协议是TCP/IP协议族中一个非常重要的网络层子协议。如果把 IP 协议比作负责寄送包裹的“快递员”那么 ICMP就是快递公司的“异常反馈与客服系统”。IP协议只负责尽最大努力去传递数据包但它本身并不保证送达也不会在出错时主动告诉你。而ICMP的核心作用就是在数据包传输过程中出现差错或需要传递网络状态时在主机和路由器之间传递控制消息。1.1 工作机制ICMP报文并不是直接交给下层数据链路层的而是会被封装在IP数据报内部作为IP数据包的数据部分进行传递。每一个ICMP报文都包含三个关键字段用来标识具体的情况a. 类型 (Type)表示大类的差错或查询类型。b. 代码 (Code)对类型进行更细致的划分例如“不可达”是因为网络不通、主机关闭还是端口未监听。c. 校验和 (Checksum)用于检验报文在传输过程中是否损坏。在实际应用中ICMP的功能主要分为两大类差错报告报文和查询报文。1.2 ICMP 的实际应用场景除了底层的自动报错机制ICMP在日常网络管理中有着非常直观的应用1Ping命令网络连通性测试通过发送ICMP回送请求来测试本地主机是否能与另一台主机交换数据是排查网络故障的第一步。2Traceroute/Tracert命令路由追踪利用ICMP的“超时”机制。该命令故意发送一系列TTL值逐渐递增1, 2, 3…的数据包迫使路径上的每一个路由器在TTL耗尽时返回超时报文。通过收集这些报文就能完整描绘出数据包从源到目的地所经过的每一跳路径。2 ICMP协议在嵌入式系统中的应用LwIP作为一个面向嵌入式系统的轻量级协议栈并没有实现ICMP的所有标准类型而是聚焦于最核心的差错报告和网络诊断功能标准的 ICMP 包含重定向、源站抑制、时间戳等报文但在 LwIP 中通常为了节省资源而不做处理或直接忽略。2.1 差错报告报文 (Error Reporting)当IP数据报在网络传输中出现问题如无法到达目的地时路由器或目标主机会通过ICMP 向源主机发送差错报文。LwIP主要实现了以下两种最常用的差错报文1目的不可达 (Destination Unreachable, Type 3)a触发场景当数据包找不到对应的路由、目标主机关闭、或者目标端口没有程序监听时触发。LwIP 的实现细节LwIP会根据具体错误返回不同的代码Code例如 端口不可达UDP 包发到了一个未绑定的端口、协议不可达IP头部指定的上层协议不存在或 需要分片但设置了不分片 等。2超时 (Time Exceeded, Type 11)触发场景最常见的是 IP 头部的TTL生存时间字段减为 0。每经过一个路由器TTL会减 1当它为 0 时路由器会丢弃该包并回传超时报文。这常用于traceroute路由追踪工具。底层原理当LwIP触发这些差错时会调用内部的icmp_dest_unreach() 或icmp_time_exceeded() 函数。这些函数会将引起差错的原始 IP 首部 原始数据包的前8字节数据封装在新的ICMP报文中发回给源主机以便源主机判断是哪个应用或连接出了问题。2.2 询问/查询报文 (Query Messages)1回送请求与应答 (Echo Request/Reply, Type 8 / Type 0)这是大家最熟悉的Ping命令的核心。源主机发送Type 8的请求目标主机收到后回复Type 0的应答。如果能收到应答说明网络链路是通的。LwIP的处理逻辑LwIP 内部会自动处理接收到的Echo请求并构造一个Echo应答包原路返回无需用户编写额外的业务代码。2.3 LwIP上ICMP功能具体实现在STM32等嵌入式设备上利用LwIP实现Ping功能即主动向外发送ICMP请求是非常常见的需求常用于设备上线后的网络自检或与上位机的心跳保活。LwIP默认提供了底层的ICMP处理能力但没有直接提供一个现成的“Ping应用程序”。我们需要利用LwIP的Raw API原始 API 来自己实现一个简单的Ping客户端。步骤如下1创建 Raw PCB使用raw_new(IP_PROTO_ICMP) 创建一个专门用于ICMP协议的协议控制块。2绑定接收回调注册一个回调函数当接收到ICMP应答包Type 0时由该函数进行处理比如点亮 LED或打印RTT往返时间。3构造并发送请求手动填充ICMP首部设置 Type8, Code0, 标识符和序列号计算校验和然后通过raw_sendto()发送出去。简版代码如下#includelwip/raw.h#includelwip/icmp.h#includelwip/inet_chksum.hstaticstructraw_pcb*ping_pcbNULL;staticu16_tping_seq_num0;// 接收 ICMP 应答的回调函数staticu8_tping_recv(void*arg,structraw_pcb*pcb,structpbuf*p,constip_addr_t*addr){structicmp_echo_hdr*iecho(structicmp_echo_hdr*)p-payload;// 检查是否是回显应答(Type 0)且标识符匹配if(iecho-typeICMP_ERntohs(iecho-id)PING_ID){printf(收到来自 %s 的应答! 序列号: %d\n,ipaddr_ntoa(addr),ntohs(iecho-seqno));}return1;// 表示已处理该包}// 初始化并发送一次 Pingvoiddevice_ping(ip_addr_t*target_ip){structpbuf*p;structicmp_echo_hdr*iecho;if(!ping_pcb){ping_pcbraw_new(IP_PROTO_ICMP);raw_recv(ping_pcb,ping_recv,NULL);// 绑定接收回调}// 分配内存ICMP 首部 少量测试数据ppbuf_alloc(PBUF_IP,sizeof(structicmp_echo_hdr)4,PBUF_RAM);if(p!NULL){iecho(structicmp_echo_hdr*)p-payload;// 填充 ICMP 首部ICMPH_TYPE_SET(iecho,ICMP_ECHO);// Type 8ICMPH_CODE_SET(iecho,0);// Code 0iecho-chksum0;iecho-idhtons(PING_ID);// 自定义 IDiecho-seqnohtons(ping_seq_num);// 计算校验和iecho-chksuminet_chksum(iecho,p-tot_len);// 发送数据包raw_sendto(ping_pcb,p,target_ip);pbuf_free(p);}}