Files
NE_YuR/network/implementation_report.md
2025-11-17 22:22:52 +08:00

7.3 KiB
Raw Blame History

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中,我们完成了以下工作:

  1. ARP缓存ARP Table: 定义了一个静态数组arp_table[]作为ARP缓存用于存储IP地址到MAC地址的映射。
  2. xarp_init(): 此函数在协议栈初始化时被调用负责清空ARP缓存中的所有条目。
  3. xarp_in(): 这是处理ARP报文的核心。
    • 当接收到一个ARP请求报文时检查其请求的目标IP地址是否为本机IP。如果是则构建一个ARP响应报文填入本机的MAC地址然后通过ethernet_out_to()函数发送出去。
    • 无论是请求还是响应都会从报文中提取发送方的IP和MAC地址并更新到ARP缓存中update_entry函数)。
  4. xarp_resolve(): 为了让上层协议如IP能够通过IP地址查询MAC地址我们添加了此函数。它会首先查询ARP缓存。如果找到匹配的条目则返回对应的MAC地址如果未找到则调用send_arp_request()广播一个ARP请求并返回NULL,由上层决定是否重试。

2.3. 集成到协议栈

我们将ARP模块集成到主协议栈中

  1. xnet_tiny.c中包含了xarp.h
  2. xnet_init()函数的末尾调用xarp_init()
  3. 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实现的核心其工作流程如下

  1. IP报头解析: 首先剥离以太网头部解析IP报头。验证版本号、头部长度和头部校验和。
  2. 目标IP检查: 确认该IP包的目标地址是本机。
  3. ICMP报文处理:
    • 剥离IP头部解析ICMP报文。
    • 检查ICMP类型是否为Echo RequestPing请求
    • 如果是,则准备一个Echo ReplyPing应答报文。大部分数据如ID、序列号和数据负载可以直接从请求报文中复制。
    • 将ICMP类型设置为ICMP_TYPE_ECHO_REPLY然后重新计算ICMP校验和。
  4. 构建IP响应包:
    • 构建IP头部源和目的IP地址与收到的请求包相反。
    • 重新计算IP头部校验和。
  5. 发送响应: 调用xip_out()函数将响应包发送出去。xip_out内部会使用xarp_resolve来查找下一跳的MAC地址。

3.3. 集成到协议栈

  1. xnet_tiny.c中包含了xicmp.h
  2. ethernet_in()switch语句中,为XNET_PROTOCOL_IP添加了对xicmp_in(packet)的调用。
  3. xicmp.c中,使用新创建的xip_out()函数来发送响应包,而不是直接调用底层的ethernet_out_to(),实现了更好的分层。

4. Linux兼容性改造与重构

原始代码是为Windows设计的为了在Linux上运行我们进行了以下关键修改

  1. 修正pcap_device.c:
    • 该文件包含了大量#if defined(WIN32)的条件编译块,用于加载npcap动态库。
    • 我们删除了所有Windows特定的代码#pragma comment, GetSystemDirectory, MessageBox等),只保留了#else分支下的标准pcap实现使其在Linux下能够直接编译通过。
  2. 修正port_pcap.c:
    • 原始代码中的my_mac_addr数组长度为8字节这是错误的。我们将其修正为标准的6字节以太网MAC地址。
    • 将硬编码的pcap设备IP地址从192.168.254.1修改为192.168.254.2与我们协议栈配置的IP保持一致。
  3. IP地址集中管理:
    • 最初本机IP地址在xarp.cxicmp.c中都有硬编码定义。
    • 我们将其移至xnet_tiny.c中定义,并在xnet_tiny.h中通过extern声明,实现了全局统一配置。

5. 编译与测试

完成编码后,即可进行编译测试:

  1. 安装依赖: 确保Linux系统上已安装libpcap的开发库(如sudo apt install libpcap-dev)。
  2. 编译:
    • 进入start/build目录。
    • 运行cmake ..来生成Makefile。
    • 运行make进行编译。
  3. 运行与测试:
    • 编译成功后,会在build目录下生成可执行文件xnet.exe
    • 使用sudo ./xnet.exe运行需要root权限以访问网络接口
    • 在另一台处于同一局域网的主机上,执行ping 192.168.254.2即可测试ICMP Echo功能。同时可以通过tcpdumpWireshark抓包观察ARP请求和响应过程。

6. 总结

通过以上步骤我们成功地在一个基础网络框架上完整地实现了ARP和ICMP Echo功能。整个过程不仅加深了对网络协议底层工作原理的理解也锻炼了在现有代码基础上进行功能扩展、重构和跨平台移植的能力。最终的代码结构清晰模块化程度高易于理解和进一步扩展。