network arplab finished

This commit is contained in:
2025-11-17 22:22:52 +08:00
parent ecb6ef0a65
commit 8d9c3ed7f6
411 changed files with 42460 additions and 1 deletions

View File

@ -0,0 +1,130 @@
# 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`语句根据以太网帧头的协议类型字段来分发数据包:
```c
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 Request**Ping请求
- 如果是,则准备一个**Echo Reply**Ping应答报文。大部分数据如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.c``xicmp.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功能。同时可以通过`tcpdump``Wireshark`抓包观察ARP请求和响应过程。
## 6. 总结
通过以上步骤我们成功地在一个基础网络框架上完整地实现了ARP和ICMP Echo功能。整个过程不仅加深了对网络协议底层工作原理的理解也锻炼了在现有代码基础上进行功能扩展、重构和跨平台移植的能力。最终的代码结构清晰模块化程度高易于理解和进一步扩展。