7.3 KiB
ARP及ICMP协议实现报告
本文档详细记录了在一个C语言网络协议栈项目中,从零开始实现地址解析协议(ARP)和Internet控制报文协议(ICMP)的完整过程。项目基于一个为Windows环境设计的初始模板,整个过程不仅包括了协议逻辑的实现,还涉及了代码的Linux兼容性改造和整体架构的模块化重构。
1. 初始代码分析
项目开始于一个名为start的目录,其中包含一个简化的网络协议栈框架。
1.1. 项目结构勘探
首先,我们分析了start目录下的源代码,特别是xnet_tiny/src子目录。我们识别出以下几个核心文件:
xnet_tiny.h: 定义了协议栈的基本数据结构,如以太网帧头xether_hdr_t和网络数据包xnet_packet_t。xnet_tiny.c: 包含了协议栈的核心驱动逻辑,如xnet_init()(初始化)和xnet_poll()(轮询)。app.c: 项目的主入口,调用xnet_init()和xnet_poll()来启动和运行协议栈。xnet_app/port_pcap.c: 负责与pcap库交互,进行底层网络数据包的收发。
1.2. 关键实现点定位
在xnet_tiny.c中,我们发现了ethernet_in函数,它负责处理接收到的以太网数据帧。函数内部的switch语句根据以太网帧头的协议类型字段来分发数据包:
switch (swap_order16(hdr->protocol)) {
case XNET_PROTOCOL_ARP:
// 此处为空,是ARP实现入口
break;
case XNET_PROTOCOL_IP: {
// 此处为空,是IP及ICMP实现入口
break;
}
}
这两个case分支是空的,这正是我们需要插入ARP和IP/ICMP处理逻辑的地方。
2. ARP协议实现
为了保持代码的模块化,我们将ARP相关的逻辑实现在单独的文件中。
2.1. 创建ARP模块
我们创建了以下两个文件:
xarp.h: 定义ARP协议包结构xarp_packet_t和ARP缓存条目xarp_entry_t,并声明了外部接口xarp_init()和xarp_in()。xarp.c: 实现ARP的具体逻辑。
2.2. 实现ARP核心逻辑
在xarp.c中,我们完成了以下工作:
- ARP缓存(ARP Table): 定义了一个静态数组
arp_table[]作为ARP缓存,用于存储IP地址到MAC地址的映射。 xarp_init(): 此函数在协议栈初始化时被调用,负责清空ARP缓存中的所有条目。xarp_in(): 这是处理ARP报文的核心。- 当接收到一个ARP请求报文时,检查其请求的目标IP地址是否为本机IP。如果是,则构建一个ARP响应报文,填入本机的MAC地址,然后通过
ethernet_out_to()函数发送出去。 - 无论是请求还是响应,都会从报文中提取发送方的IP和MAC地址,并更新到ARP缓存中(
update_entry函数)。
- 当接收到一个ARP请求报文时,检查其请求的目标IP地址是否为本机IP。如果是,则构建一个ARP响应报文,填入本机的MAC地址,然后通过
xarp_resolve(): 为了让上层协议(如IP)能够通过IP地址查询MAC地址,我们添加了此函数。它会首先查询ARP缓存。如果找到匹配的条目,则返回对应的MAC地址;如果未找到,则调用send_arp_request()广播一个ARP请求,并返回NULL,由上层决定是否重试。
2.3. 集成到协议栈
我们将ARP模块集成到主协议栈中:
- 在
xnet_tiny.c中包含了xarp.h。 - 在
xnet_init()函数的末尾调用xarp_init()。 - 在
ethernet_in()的switch语句中,为XNET_PROTOCOL_ARP添加了对xarp_in(packet)的调用。
为了让xarp.c能够调用xnet_tiny.c中的ethernet_out_to等函数,我们将这些函数的static关键字移除,并在xnet_tiny.h中添加了它们的声明,使其成为公共接口。
3. ICMP及IP层实现
与ARP类似,我们为ICMP和IP创建了独立的模块。
3.1. 创建ICMP和IP模块
xicmp.h: 定义了IP报头xip_hdr_t和ICMP报文结构xicmp_packet_t。xicmp.c: 实现了xicmp_in()函数,用于处理传入的ICMP报文。xip.h/xip.c: 创建了一个简单的IP输出模块,核心是xip_out()函数,用于发送IP包。
3.2. 实现ICMP Echo Reply
xicmp_in()是ICMP实现的核心,其工作流程如下:
- IP报头解析: 首先,剥离以太网头部,解析IP报头。验证版本号、头部长度和头部校验和。
- 目标IP检查: 确认该IP包的目标地址是本机。
- ICMP报文处理:
- 剥离IP头部,解析ICMP报文。
- 检查ICMP类型是否为Echo Request(Ping请求)。
- 如果是,则准备一个Echo Reply(Ping应答)报文。大部分数据(如ID、序列号和数据负载)可以直接从请求报文中复制。
- 将ICMP类型设置为
ICMP_TYPE_ECHO_REPLY,然后重新计算ICMP校验和。
- 构建IP响应包:
- 构建IP头部,源和目的IP地址与收到的请求包相反。
- 重新计算IP头部校验和。
- 发送响应: 调用
xip_out()函数将响应包发送出去。xip_out内部会使用xarp_resolve来查找下一跳的MAC地址。
3.3. 集成到协议栈
- 在
xnet_tiny.c中包含了xicmp.h。 - 在
ethernet_in()的switch语句中,为XNET_PROTOCOL_IP添加了对xicmp_in(packet)的调用。 - 在
xicmp.c中,使用新创建的xip_out()函数来发送响应包,而不是直接调用底层的ethernet_out_to(),实现了更好的分层。
4. Linux兼容性改造与重构
原始代码是为Windows设计的,为了在Linux上运行,我们进行了以下关键修改:
- 修正
pcap_device.c:- 该文件包含了大量
#if defined(WIN32)的条件编译块,用于加载npcap动态库。 - 我们删除了所有Windows特定的代码(如
#pragma comment,GetSystemDirectory,MessageBox等),只保留了#else分支下的标准pcap实现,使其在Linux下能够直接编译通过。
- 该文件包含了大量
- 修正
port_pcap.c:- 原始代码中的
my_mac_addr数组长度为8字节,这是错误的。我们将其修正为标准的6字节以太网MAC地址。 - 将硬编码的pcap设备IP地址从
192.168.254.1修改为192.168.254.2,与我们协议栈配置的IP保持一致。
- 原始代码中的
- IP地址集中管理:
- 最初,本机IP地址在
xarp.c和xicmp.c中都有硬编码定义。 - 我们将其移至
xnet_tiny.c中定义,并在xnet_tiny.h中通过extern声明,实现了全局统一配置。
- 最初,本机IP地址在
5. 编译与测试
完成编码后,即可进行编译测试:
- 安装依赖: 确保Linux系统上已安装
libpcap的开发库(如sudo apt install libpcap-dev)。 - 编译:
- 进入
start/build目录。 - 运行
cmake ..来生成Makefile。 - 运行
make进行编译。
- 进入
- 运行与测试:
- 编译成功后,会在
build目录下生成可执行文件xnet.exe。 - 使用
sudo ./xnet.exe运行(需要root权限以访问网络接口)。 - 在另一台处于同一局域网的主机上,执行
ping 192.168.254.2,即可测试ICMP Echo功能。同时,可以通过tcpdump或Wireshark抓包观察ARP请求和响应过程。
- 编译成功后,会在
6. 总结
通过以上步骤,我们成功地在一个基础网络框架上,完整地实现了ARP和ICMP Echo功能。整个过程不仅加深了对网络协议底层工作原理的理解,也锻炼了在现有代码基础上进行功能扩展、重构和跨平台移植的能力。最终的代码结构清晰,模块化程度高,易于理解和进一步扩展。