24 KiB
Executable File
ARP超时重传
在ARP协议中,如果某个ARP表项在一定时间内没有收到响应,或者表项的状态变为无效,系统会触发ARP超时重传机制。这一机制确保在网络中能够及时更新或重新获取目标设备的MAC地址。
超时重传的实现
在xnet_tiny.c中,xarp_poll函数负责定期检查ARP表项的状态,并根据需要触发重传。具体实现如下:
/
* 查询ARP表项是否超时,超时则重新请求
*/
void xarp_poll(void) {
// 检查ARP定时器是否超时
if (xnet_check_tmo(&arp_timer, XARP_TIMER_PERIOD)) {
for (arp_entry = arp_table; arp_entry < XARP_CFG_ENTRY_SIZE * sizeof(xarp_entry_t) + arp_table; arp_entry = arp_entry + sizeof(xarp_entry_t)) {
switch (arp_entry->state) {
case XARP_ENTRY_RESOLVING:
// 如果ARP表项正在解析中,检查超时计数器
if (--arp_entry->tmo == 0) {
// 如果重试次数用完,释放ARP表项
if (arp_entry->retry_cnt-- == 0) {
arp_entry->state = XARP_ENTRY_FREE;
}
else {
// 否则继续重试,发送ARP请求
xarp_make_request(&arp_entry->ipaddr);
arp_entry->state = XARP_ENTRY_RESOLVING;
arp_entry->tmo = XARP_CFG_ENTRY_PENDING_TMO;
}
}
break;
case XARP_ENTRY_OK:
// 如果ARP表项有效,检查超时计数器
if (--arp_entry->tmo == 0) {
// 超时后重新发送ARP请求
xarp_make_request(&arp_entry->ipaddr);
arp_entry->state = XARP_ENTRY_RESOLVING;
arp_entry->tmo = XARP_CFG_ENTRY_PENDING_TMO;
}
break;
}
}
}
}
超时重传的流程
- 定时检查:
xarp_poll函数会定期检查ARP表项的状态,检查周期由XARP_TIMER_PERIOD定义(默认为1秒)。 - 状态判断:
- 如果表项状态为
XARP_ENTRY_RESOLVING(正在解析中),则检查超时计数器。如果超时且重试次数用完,则释放该表项;否则,重新发送ARP请求并重置超时计数器。 - 如果表项状态为
XARP_ENTRY_OK(有效),则检查超时计数器。如果超时,则重新发送ARP请求并将表项状态设置为XARP_ENTRY_RESOLVING。
- 如果表项状态为
- 重传ARP请求:通过调用
xarp_make_request函数,重新发送ARP请求以获取目标IP地址对应的MAC地址。
超时重传的意义
ARP超时重传机制确保了在网络中即使某些设备暂时不可达,系统也能通过多次尝试获取其MAC地址。这种机制在网络环境不稳定或设备频繁上下线的情况下尤为重要,能够有效避免因ARP表项失效导致的通信中断。
实验验证
为了验证ARP超时重传机制的正确性,可以通过以下步骤进行测试:
- 启动程序:运行程序并确保ARP表项初始化完成。
- 模拟超时:在ARP表项超时后,观察是否触发了重传机制。
- 抓包分析:使用Wireshark抓包工具,查看是否在超时后重新发送了ARP请求。
通过以上步骤,可以验证ARP超时重传机制是否按预期工作。
#figure(image("ARP超时重传.png",format: "png",width: 100%,fit: "stretch"),caption: "ARP超时重传抓包结果")<figure5>
从@figure5 中可以看到,ARP请求在超时后重新发送,说明超时重传机制工作正常。
以下是 === 定义IP数据报、=== 实现IP输入 和 === 实现IP输出 部分的 Markdown 内容:
=== 定义IP数据报
IP数据报是网络层协议的核心数据结构,包含了源IP地址、目标IP地址以及上层协议类型等信息。在 `xnet_tiny.h` 中,我们定义了IP数据报的结构体:
```c
typedef struct _xip_hdr_t {
uint8_t hdr_len : 4; // 首部长, 4字节为单位
uint8_t version : 4; // 版本号
uint8_t tos; // 服务类型
uint16_t total_len; // 总长度
uint16_t id; // 标识符
uint16_t flags_fragment; // 标志与分段
uint8_t ttl; // 存活时间
uint8_t protocol; // 上层协议
uint16_t hdr_checksum; // 首部校验和
uint8_t src_ip[XNET_IPV4_ADDR_SIZE]; // 源IP
uint8_t dest_ip[XNET_IPV4_ADDR_SIZE]; // 目标IP
} xip_hdr_t;
该结构体定义了IP数据报的各个字段,包括:
hdr_len:IP头部的长度,以4字节为单位。version:IP版本号,通常为IPv4(值为4)。tos:服务类型,用于指定数据报的优先级。total_len:IP数据报的总长度。id:标识符,用于标识IP数据报。flags_fragment:标志和分段信息,用于IP分片。ttl:生存时间,用于防止数据报在网络中无限循环。protocol:上层协议类型,如ICMP、TCP或UDP。hdr_checksum:IP头部的校验和,用于检测数据报是否损坏。src_ip和dest_ip:源IP地址和目标IP地址。
=== 实现IP输入
IP输入函数 xip_in 负责处理接收到的IP数据报。在 xnet_tiny.c 中,我们实现了该函数:
void xip_in(xnet_packet_t * packet) {
xip_hdr_t* iphdr = (xip_hdr_t*)packet->data;
uint32_t total_size, header_size;
uint16_t pre_checksum;
xipaddr_t src_ip;
// 检查IP版本号是否为IPv4
if (iphdr->version != XNET_VERSION_IPV4) {
return;
}
// 检查头部长度和总长度是否符合要求
header_size = iphdr->hdr_len * 4;
total_size = swap_order16(iphdr->total_len);
if ((header_size < sizeof(xip_hdr_t)) || ((total_size < header_size) || (packet->size < total_size))) {
return;
}
// 校验头部的校验和是否正确
pre_checksum = iphdr->hdr_checksum;
iphdr->hdr_checksum = 0;
if (pre_checksum != checksum16((uint16_t*)iphdr, header_size, 0, 1)) {
return;
}
// 检查目标IP地址是否为本机IP
if (!xipaddr_is_equal_buf(&netif_ipaddr, iphdr->dest_ip)) {
return;
}
// 根据协议类型分发到不同的处理函数
xipaddr_from_buf(&src_ip, iphdr->src_ip);
switch(iphdr->protocol) {
case XNET_PROTOCOL_ICMP:
remove_header(packet, header_size);
xicmp_in(&src_ip, packet);
break;
default:
break;
}
}
该函数的主要功能是:
- 检查IP版本号是否为IPv4。
- 检查IP头部的长度和总长度是否符合要求。
- 校验IP头部的校验和是否正确。
- 检查目标IP地址是否为本机IP。
- 根据上层协议类型(如ICMP)将数据报分发到相应的处理函数。
=== 实现IP输出
IP输出函数 xip_out 负责发送IP数据报。在 xnet_tiny.c 中,我们实现了该函数:
xnet_err_t xip_out(xnet_protocol_t protocol, xipaddr_t* dest_ip, xnet_packet_t * packet) {
static uint32_t ip_packet_id = 0; // 静态变量,用于生成唯一的IP包ID
xip_hdr_t * iphdr;
add_header(packet, sizeof(xip_hdr_t)); // 添加IP头部
iphdr = (xip_hdr_t*)packet->data; // 获取IP头部指针
iphdr->version = XNET_VERSION_IPV4; // 设置IP版本号为IPv4
iphdr->hdr_len = sizeof(xip_hdr_t) / 4; // 设置IP头部长度
iphdr->tos = 0; // 设置服务类型
iphdr->total_len = swap_order16(packet->size); // 设置总长度
iphdr->id = swap_order16(ip_packet_id); // 设置包ID
iphdr->flags_fragment = 0; // 设置标志和片偏移
iphdr->ttl = XNET_IP_DEFAULT_TTL; // 设置生存时间
iphdr->protocol = protocol; // 设置上层协议类型
memcpy(iphdr->dest_ip, dest_ip->array, XNET_IPV4_ADDR_SIZE); // 设置目标IP地址
memcpy(iphdr->src_ip, netif_ipaddr.array, XNET_IPV4_ADDR_SIZE); // 设置源IP地址
iphdr->hdr_checksum = 0; // 初始化校验和字段
iphdr->hdr_checksum = checksum16((uint16_t *)iphdr, sizeof(xip_hdr_t), 0, 1); // 计算并设置校验和
ip_packet_id++; // 增加包ID
return ethernet_out(dest_ip, packet); // 通过以太网发送IP包
}
该函数的主要功能是:
- 添加IP头部到数据报中。
- 设置IP头部的各个字段,包括版本号、头部长度、总长度、包ID、生存时间、上层协议类型、源IP地址和目标IP地址。
- 计算并设置IP头部的校验和。
- 通过以太网发送IP数据报。
### 说明:
1. 代码块:使用 ```c 插入代码块,并指定语言为 C。
2. 标题:使用 `===` 表示三级标题。
3. 段落:直接编写文本内容,Markdown 会自动处理段落格式。
将此内容插入到 `main.txt` 文件的适当位置即可。
以下是 `=== 定义ICMP数据报`、`=== 实现ICMP输入` 和 `=== 实现ICMP-ping响应` 部分的 Typst 代码:
```typ
=== 定义ICMP数据报
#para[
ICMP(Internet Control Message Protocol)是网络层协议,用于在IP网络中传递控制消息。在 `xnet_tiny.h` 中,我们定义了ICMP数据报的结构体:
#raw(block: true, lang: "c")[
```c
typedef struct _xicmp_hdr_t {
uint8_t type; // 类型
uint8_t code; // 代码
uint16_t checksum; // ICMP报文的校验和
uint16_t id; // 标识符
uint16_t seq; // 序号
} xicmp_hdr_t;
]
该结构体定义了ICMP数据报的各个字段,包括:
type:ICMP消息类型,例如回显请求(8)和回显响应(0)。code:ICMP消息代码,用于进一步细分消息类型。checksum:ICMP报文的校验和,用于检测报文是否损坏。id和seq:标识符和序号,用于匹配请求和响应。
此外,我们还定义了ICMP消息类型的常量: #raw(block: true, lang: "c")[
#define XICMP_CODE_ECHO_REQUEST 8 // 回显请求
#define XICMP_CODE_ECHO_REPLY 0 // 回显响应
] ]
=== 实现ICMP输入
#para[
ICMP输入函数 xicmp_in 负责处理接收到的ICMP数据报。在 xnet_tiny.c 中,我们实现了该函数:
#raw(block: true, lang: "c")[
void xicmp_in(xipaddr_t *src_ip, xnet_packet_t * packet) {
xicmp_hdr_t* icmphdr = (xicmp_hdr_t *)packet->data; // 获取ICMP头部指针
if ((packet->size >= sizeof(xicmp_hdr_t)) && (icmphdr->type == XICMP_CODE_ECHO_REQUEST)) {
reply_icmp_request(icmphdr, src_ip, packet); // 如果是ECHO请求,发送ECHO回复
}
}
]
该函数的主要功能是:
- 检查接收到的ICMP数据报是否完整。
- 如果ICMP消息类型为回显请求(
XICMP_CODE_ECHO_REQUEST),则调用reply_icmp_request函数发送回显响应。 ]
=== 实现ICMP-ping响应
#para[
ICMP-ping响应函数 reply_icmp_request 负责生成并发送ICMP回显响应。在 xnet_tiny.c 中,我们实现了该函数:
#raw(block: true, lang: "c")[
static xnet_err_t reply_icmp_request(xicmp_hdr_t * icmp_hdr, xipaddr_t* src_ip, xnet_packet_t * packet) {
xicmp_hdr_t * replay_hdr;
xnet_packet_t * tx = xnet_alloc_for_send(packet->size);
replay_hdr = (xicmp_hdr_t *)tx->data; // 获取ICMP头部指针
replay_hdr->type = XICMP_CODE_ECHO_REPLY; // 设置ICMP类型为ECHO回复
replay_hdr->code = 0; // 设置代码为0
replay_hdr->id = icmp_hdr->id; // 复制ID
replay_hdr->seq = icmp_hdr->seq; // 复制序列号
replay_hdr->checksum = 0; // 初始化校验和字段
memcpy(((uint8_t *)replay_hdr) + sizeof(xicmp_hdr_t), ((uint8_t *)icmp_hdr) + sizeof(xicmp_hdr_t),
packet->size - sizeof(xicmp_hdr_t)); // 复制数据部分
replay_hdr->checksum = checksum16((uint16_t*)replay_hdr, tx->size, 0, 1); // 计算校验和
return xip_out(XNET_PROTOCOL_ICMP, src_ip, tx); // 发送ICMP回复包
}
]
该函数的主要功能是:
- 分配一个新的数据包用于发送ICMP回显响应。
- 设置ICMP回显响应的各个字段,包括类型、代码、ID、序列号和校验和。
- 复制原始ICMP请求的数据部分到响应中。
- 计算并设置ICMP回显响应的校验和。
- 通过IP层发送ICMP回显响应。
通过该函数,我们可以实现对ICMP-ping请求的响应,从而支持基本的网络连通性测试。 ]
### 说明:
1. 代码块:使用 `#raw(block: true, lang: "c")` 插入代码块,并指定语言为 C。
2. 标题:使用 `===` 表示三级标题。
3. 段落:使用 `#para[]` 包裹段落内容。
将此代码插入到 `main.txt` 文件的适当位置即可。
6 实验原理及方案
ARP(地址解析协议)是 TCP/IP 协议族中用于将 IP 地址解析为 MAC 地址的重要协议。IP 通信依赖于数据链路层的硬件地址(MAC 地址),而 ARP 负责动态地将网络层的 IP 地址转换为对应的数据链路层 MAC 地址,从而实现设备间的通信。ARP 协议的实现主要包括发送 ARP 请求、接收并处理 ARP 响应、更新 ARP 缓存、以及缓存超时机制。
本次实验围绕 TCP/IP 协议栈的 ARP 协议的实现这一问题展开,要求学员在理解 ARP 协议原理的基础上,深入探究 ARP 的技术细节,动手实现 ARP 协议的 Python 程序。
### 1)ARP 的初始化
在一个典型的局域网中,设备通过 IP 地址进行网络层通信,但 IP 地址并不能直接用于数据链路层传输。以太网等数据链路层协议使用 MAC 地址进行通信,因此,发送设备需要将目标 IP 地址解析为 MAC 地址才能发送数据帧。
如果该设备的 ARP 缓存中没有目标设备的 MAC 地址映射,它会广播 ARP 请求,询问网络上哪个设备持有特定的 IP 地址。ARP 请求是一个以太网层的广播包,发送到子网内所有设备,只有持有目标 IP 地址的设备才会进行响应。
ARP 初始化的过程是设备发现并解析网络中其他设备的关键步骤。ARP 请求包含源设备的 IP 地址和 MAC 地址,而目标设备通过 ARP 响应提供其对应的 MAC 地址。这个机制确保设备能够通过网络层(IP 地址)和链路层(MAC 地址)之间建立正确的映射关系。
### 2)无回报 ARP 的生成
无回报 ARP(Gratuitous ARP),又称为“主动 ARP”或“自愿 ARP”,是一种特殊的 ARP 操作。与典型的 ARP 请求不同,无回报 ARP 并不是为了解析目标设备的 MAC 地址,而是设备主动向网络发送广播 ARP 包,通常用于更新网络中的 IP-MAC 映射关系、检测 IP 地址冲突等。
无回报 ARP 是设备主动广播自身的 IP 地址和 MAC 地址,不带有显式的 ARP 请求和响应互动。其主要目的是通知网络中其他设备更新其 ARP 缓存表中的信息。这种情况下,设备并不期待其他设备回应。它是单向广播的,通常被用于下列几种情况:
- 更新网络中的 ARP 表:当设备的 MAC 地址或 IP 地址发生变动时,可以主动发送无回报 ARP,以便通知网络中其他设备更新其 ARP 缓存。
- IP 冲突检测:设备在启动时,通过发送无回报 ARP 来检测是否有其他设备占用了相同的 IP 地址。如果另一台设备使用了相同的 IP 地址,它会回应此 ARP 广播,从而帮助设备检测到 IP 冲突。
- 负载均衡器和高可用性系统:当系统切换主备设备时,备设备通常会发送无回报 ARP 来通知网络中的所有节点其 IP-MAC 映射已经改变,避免继续向已下线的设备发送数据。
无回报 ARP 的生成过程如下:
1. 生成 ARP 广播包:设备在确定需要广播自身 IP-MAC 映射时,会生成一个 ARP 广播包。该包包含设备自身的 IP 地址和 MAC 地址,并且目标硬件地址设置为全 0,因为无回报 ARP 并不是请求对方设备的 MAC 地址,而是向网络中的所有设备广播自身的信息。
2. 设置操作码为 ARP 请求:尽管无回报 ARP 是主动广播,但它在帧结构中被标记为 ARP 请求(操作码为 1),这使得网络中的其他设备会将其视为一种信息广播,用于更新 ARP 缓存。
3. 发送广播:ARP 广播包通过以太网层进行传输,目标 MAC 地址为 FF:FF:FF:FF:FF:FF,即局域网内的所有设备都可以接收到此广播。
4. 网络中的设备处理:网络中所有收到此广播的设备会检查 ARP 包中的发送方 IP 地址和 MAC 地址,并将其更新到本地的 ARP 缓存表中。这样,即使该 IP 地址之前未出现在这些设备的 ARP 表中,它们也会记录并更新新的映射。
### 3)ARP 的输入处理
ARP(地址解析协议)的输入处理指的是设备在接收到 ARP 请求或响应时,如何对该 ARP 报文进行解析和处理,并据此更新设备的 ARP 缓存,或进一步采取必要的网络行为。ARP 输入处理的核心任务是解析报文,更新 ARP 缓存,并根据报文类型采取不同的操作。
在这部分有以下步骤:
- 接收 ARP 报文:设备通过网络接口接收到 ARP 报文,无论是广播还是单播形式。这些 ARP 报文可以是 ARP 请求、ARP 响应,或者是无回报 ARP。
- 解析 ARP 报文:设备对 ARP 报文进行解析,提取其中的关键信息。
- 检查报文有效性:设备检查 ARP 报文的有效性,包括检查硬件类型是否为以太网、协议类型是否为 IPv4、操作码是否为合法的请求或响应。如果报文不符合 ARP 协议规定,设备将丢弃该报文。
- 更新 ARP 缓存:根据 ARP 报文中的信息,设备更新自己的 ARP 缓存表。设备通常会把报文中的发送方 IP 地址和发送方 MAC 地址映射记录下来,以便将来进行快速的 IP 到 MAC 地址解析。
- 据操作码进行处理:不同类型的 ARP 报文有不同的处理方式:
- 如果接收到的是 ARP 请求,设备需要检查目标 IP 地址是否与自身的 IP 地址匹配,如果匹配,则需要发送一个 ARP 响应包,告知请求设备自己的 MAC 地址。
- 如果接收到的是 ARP 响应,设备会根据响应包中的信息,更新或添加到 ARP 缓存表,并不再发送进一步的响应。
- 如果接收到的是无回报 ARP,设备会将报文中的 IP-MAC 映射记录下来,以更新其 ARP 缓存。
### 4)ARP 的超时重新请求机制
ARP(地址解析协议)的超时重新请求机制指的是设备在尝试解析某个 IP 地址到 MAC 地址时,若未能在设定的时间内收到响应,会采取的重发 ARP 请求的策略。这种机制旨在保证网络设备在通信中能够及时获取目标设备的 MAC 地址,并维持 ARP 缓存的准确性。
ARP 缓存存储的是 IP 地址与 MAC 地址之间的映射关系。在通信过程中,网络设备通常会先查询 ARP 缓存以查找目标设备的 MAC 地址。如果缓存中存在该 IP 地址的记录,设备会直接使用缓存中的 MAC 地址进行通信;如果没有找到相应记录,设备会发出 ARP 请求,广播请求目标 IP 地址对应的 MAC 地址。
如果设备在发送 ARP 请求后,未能在指定的时间内收到 ARP 响应,它会认为该 ARP 请求失败。这时,设备会重新发送 ARP 请求,通常会进行一定次数的重发,以确保能够成功解析目标设备的 MAC 地址。
步骤如下:
1. 发送 ARP 请求:当设备需要与一个目标 IP 地址进行通信时,它首先会查询 ARP 缓存表。如果缓存中没有该 IP 地址的对应条目,设备会发出一个 ARP 请求,以广播方式在本地网络中请求目标设备的 MAC 地址。
2. 等待响应:设备在发送 ARP 请求后,进入等待状态。在这个状态下,设备会等待目标设备的 ARP 响应,通常是几百毫秒到几秒的时间(具体取决于设备的实现和网络情况)。
3. 超时判断:如果在规定的时间内设备没有收到目标设备的 ARP 响应,它会触发超时事件,认为该请求失败。此时,设备进入重新请求机制,准备发出新的 ARP 请求。
4. 重发 ARP 请求:超时后,设备会重新发送 ARP 请求,再次请求目标 IP 地址的 MAC 地址。这个过程通常会重复数次,直到收到响应或者达到最大重试次数为止。设备之间的重试次数和重试间隔时间是预先设定的,例如,设备可能设置为重试 3 到 5 次,每次间隔 1 秒。
5. 处理无响应的情况:如果设备在多次重发后,依然没有收到 ARP 响应,它将放弃请求,并在上层协议(如 IP、TCP 或 UDP)层上返回错误,表明目标设备不可达。这通常会导致应用层超时或连接失败。设备可能会记录这一信息,并在稍后再尝试通信时重新发起 ARP 请求。
6. ARP 缓存条目的超时:即使设备成功解析了目标设备的 MAC 地址,ARP 缓存中的条目也并非永久有效。每个 ARP 条目都有一个生存时间(TTL),通常为几分钟到几十分钟。超过这个时间后,如果该条目未被更新,设备会将其删除。当设备再次需要该目标设备的 MAC 地址时,会重新发出 ARP 请求。
= 实验总结
== 内容总结
本次实验主要围绕ARP协议的实现展开,通过编写程序完善了TCP/IP协议栈的ARP协议部分。实验内容包括ARP的初始化、无回报ARP的生成、ARP的输入处理以及ARP的超时重新请求机制。在基础任务中,成功实现了ARP协议的核心功能,包括ARP请求与响应的构建与解析、ARP缓存表的管理等。此外,还完成了拓展任务,实现了多个ARP表项的管理以及IP层的输入输出处理。
在实验过程中,首先通过Wireshark抓包分析了ARP报文的结构,并基于此定义了ARP报文的数据结构和相关函数。接着,实现了ARP报文的发送与接收功能,包括ARP请求的生成与广播、ARP响应的处理以及ARP缓存表的更新。通过定时器机制,实现了ARP表项的超时重传功能,确保了ARP缓存的准确性和及时性。
在完成ARP协议的基础上,进一步实现了IP协议和ICMP协议。通过定义IP数据报和ICMP数据报的结构,实现了IP层的输入输出功能,并成功实现了ICMP的ping响应功能。实验结果表明,ARP、IP和ICMP协议的功能均得到了正确实现,能够有效支持网络设备之间的通信。
== 心得感悟
通过本次实验,我深刻理解了ARP协议的工作原理及其在网络通信中的重要作用。ARP协议作为TCP/IP协议栈中的重要组成部分,负责将IP地址解析为MAC地址,是网络设备之间通信的基础。通过亲手实现ARP协议的核心功能,我不仅加深了对ARP协议的理解,还掌握了网络协议栈的实现方法。
在实验过程中,我遇到了一些挑战,例如如何正确解析ARP报文、如何管理ARP缓存表以及如何处理ARP超时重传等。通过查阅资料、分析抓包数据以及反复调试代码,我逐步解决了这些问题,并成功实现了ARP协议的功能。这让我认识到,网络协议的实现不仅需要扎实的理论基础,还需要细致的调试和问题解决能力。
此外,通过实现IP和ICMP协议,我进一步了解了网络层协议的工作原理。IP协议负责数据包的传输,而ICMP协议则用于传递控制消息。通过实现ICMP的ping响应功能,我掌握了ICMP协议的基本实现方法,并理解了其在网络连通性测试中的应用。
总的来说,本次实验让我对TCP/IP协议栈有了更深入的理解,并提升了我的编程能力和网络协议分析能力。这些知识和技能对我今后学习更复杂的网络协议以及从事网络相关工作具有重要意义。