524 lines
23 KiB
Plaintext
Executable File
524 lines
23 KiB
Plaintext
Executable File
#set page(header: [
|
||
#set par(spacing: 6pt)
|
||
#align(center)[#text(size: 11pt)[《计算机网络》实验报告]]
|
||
#v(-0.3em)
|
||
#line(length: 100%, stroke: (thickness: 1pt))
|
||
],)
|
||
|
||
#show heading: it => box(width: 100%)[
|
||
#v(0.50em)
|
||
#set text(font: hei)
|
||
#it.body
|
||
]
|
||
|
||
// #outline(title: "目录",depth: 3, indent: 1em)
|
||
// #outline(
|
||
// title: [图目录],
|
||
// target: figure.where(kind: image),
|
||
// )
|
||
|
||
#show heading: it => box(width: 100%)[
|
||
#v(0.50em)
|
||
#set text(font: hei)
|
||
#counter(heading).display()
|
||
// #h(0.5em)
|
||
#it.body
|
||
]
|
||
#set enum(indent: 0.5em,body-indent: 0.5em,)
|
||
#pagebreak()
|
||
|
||
// Display inline code in a small box
|
||
// that retains the correct baseline.
|
||
#show raw.where(block: false): box.with(
|
||
fill: luma(240),
|
||
inset: (x: 3pt, y: 0pt),
|
||
outset: (y: 3pt),
|
||
radius: 2pt,
|
||
)
|
||
|
||
// Display block code in a larger block
|
||
// with more padding.
|
||
#show raw.where(block: true): it => block(
|
||
text(font: ("Consolas","FangSong_GB2312"), it),
|
||
fill: luma(240),
|
||
inset: 10pt,
|
||
radius: 4pt,
|
||
width: 100%,
|
||
)
|
||
|
||
|
||
= 实验目的与要求
|
||
== 实验目的
|
||
== 实验要求
|
||
= 实验内容
|
||
= 实验原理
|
||
= 实验环境
|
||
== 实验背景
|
||
== 实验设备
|
||
#para[
|
||
#align(center)[#table(
|
||
columns: (auto, auto,auto),
|
||
rows:(2em,2em,3em),
|
||
inset: 10pt,
|
||
align: horizon+center,
|
||
table.header(
|
||
[*设备名称*], [*设备型号*], [*设备数量*]
|
||
),
|
||
"交换机", "华为S5735", "2",
|
||
"PC", "联想启天M410
|
||
Windows 10", "4",
|
||
)]
|
||
另有网线若干,控制线2条。
|
||
]
|
||
= 实验步骤
|
||
== 环境配置
|
||
=== 虚拟机网络配置
|
||
#para[
|
||
安装Windows 10虚拟机,并配置物理机和虚拟机的IP地址,使其能够互相访问:
|
||
- 物理机网卡IP地址配置为`192.168.254.1/24`;
|
||
- 虚拟机IP地址配置为`192.168.254.3/24`。
|
||
#figure(image("物理机虚拟机IP配置.png",format: "png",width: 90%,fit: "stretch"),caption: "物理机与虚拟机IP配置")
|
||
配置好之后,在两边的命令行中分别使用`ping`命令测试是否能够互相访问。同时,在物理机上开启Wireshark,以过滤条件`icmp`进行抓包,查看IP地址是否正确:
|
||
#figure(image("环境配置ping测试.png",format: "png",width: 100%,fit: "stretch"),caption: "环境配置ping通测试")<figure2>
|
||
从@figure2 中可以看到,物理机和虚拟机之间可以互相访问,且Wireshark抓包显示IP地址正确。
|
||
]
|
||
=== 使用CMake运行项目
|
||
#para[
|
||
CMake配置较为简单。首先,在开发工具中安装对应版本CMake插件。其次,在终端中进入项目根目录,在此使用```shell mkdir build```命令新建`build`文件夹并进入该文件夹。接下来,使用CMake工具生成对应的Makefile文件:```shell cmake -G"MinGW Makefiles" ..```。
|
||
然后再运行```shell make```命令编译项目,最后使用```shell xnet.exe```命令即可运行项目:
|
||
#figure(image("cmake编译运行.png",format: "png",width: 70%,fit: "stretch"),caption: "CMake配置")
|
||
其中,MinGW是一个Windows下的GNU编译器套件,可以在Windows下编译出Linux下的可执行文件。```shell cmake -G"MinGW Makefiles" ..```命令的作用是配置使用MinGW编译器。
|
||
|
||
至此,环境配置结束。
|
||
]
|
||
== 实现ARP协议
|
||
#para[
|
||
代码已经实现了最基础的以太网协议,实现了以太网帧的封装和解封装。接下来在此基础上继续实现ARP协议。
|
||
]
|
||
=== 定义相关数据结构
|
||
#para[
|
||
在`xnet_tiny.h`中定义IP地址长度以及数据结构:
|
||
```c
|
||
#define XNET_IPV4_ADDR_SIZE 4 // IP地址长度
|
||
|
||
// IP地址
|
||
typedef union _xipaddr_t {
|
||
uint8_t array[XNET_IPV4_ADDR_SIZE]; // 以数据形式存储的ip
|
||
uint32_t addr; // 32位的ip地址
|
||
}xipaddr_t;
|
||
```
|
||
该数据结构定义了IP地址的数据结构,包括了IP地址的数组形式和32位的IP地址。
|
||
|
||
然后定义MAC地址的长度,以及ARP表项的结构体:
|
||
```c
|
||
#define XNET_MAC_ADDR_SIZE 6 // MAC地址长度
|
||
|
||
// ARP表项
|
||
typedef struct _xarp_entry_t {
|
||
xipaddr_t ipaddr; // ip地址
|
||
uint8_t macaddr[XNET_MAC_ADDR_SIZE]; // mac地址
|
||
uint8_t state; // 状态位
|
||
uint16_t tmo; // 当前超时
|
||
uint8_t retry_cnt; // 当前重试次数
|
||
}xarp_entry_t;
|
||
```
|
||
该结构体定义了ARP表项的数据结构,包括了IP地址、MAC地址、状态位、超时时间和重试次数。
|
||
|
||
定义ARP表项的最大数量:
|
||
```c
|
||
#define XARP_CFG_ENTRY_SIZE 6 // ARP表大小
|
||
```
|
||
随后,在`xnet_tiny.c`中,将ARP表定义为全局变量,并定义一个表项指针,方便后续代码编写:
|
||
```c
|
||
static xarp_entry_t arp_table[XARP_CFG_ENTRY_SIZE]; // ARP表
|
||
static xarp_entry_t* arp_entry; // ARP表项指针
|
||
```
|
||
|
||
]
|
||
=== ARP表初始化
|
||
#para[
|
||
接下来编写ARP表的初始化函数。首先在`xnet_tiny.h`中定义ARP表项的第一个状态:
|
||
```c
|
||
#define XARP_ENTRY_FREE 0 // ARP表项空闲
|
||
```
|
||
然后在`xnet_tiny.c`中定义初始化函数```c void xarp_init(void)```:
|
||
```c
|
||
// ARP初始化
|
||
void xarp_init(void) {
|
||
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))
|
||
{
|
||
arp_entry->state = XARP_ENTRY_FREE; // 此处用到了上面定义的状态
|
||
}
|
||
arp_entry = arp_table;
|
||
}
|
||
```
|
||
初始化函数```c void xarp_init(void)```是一个循环。首先将前面定义的全局表项指针指向ARP表的第一个表项,循环结束条件为指针指向的表项的地址超过ARP表的最后一个表项的地址。循环会遍历ARP表中的所有表项,将表项状态初始化为`XARP_STATE_FREE`。最后,函数会将表项指针指向第一个表项,避免其他初始化过程中可能的指针越界问题。
|
||
|
||
最后,在协议栈的初始化函数中添加```c xarp_init()```:
|
||
```c
|
||
void xnet_init (void) {
|
||
ethernet_init(); // 初始化以太网
|
||
xarp_init(); // *初始化ARP
|
||
}
|
||
```
|
||
]
|
||
=== 定义ARP报文
|
||
#para[
|
||
接下来编写无回报ARP报文的相关函数,所以需要先定义ARP报文结构,以及它所用到的相关结构。
|
||
|
||
首先在`xnet_tiny.h`中定义ARP报文中的几个字段。可以靠Wireshark抓包分析来获取这些字段的值,下面是一个示例,展示通过抓包来获取```c XNET_PROTOCOL_IP = 0x0800```:
|
||
#figure(table(
|
||
columns: (auto),
|
||
rows:(auto,auto),
|
||
inset: 10pt,
|
||
align: horizon+center,
|
||
figure(image("抓一个arp分析结构.png",format: "png",fit:"stretch",width: 100%),),
|
||
figure(image("分析结构2.png",format: "png",fit:"stretch",width: 100%),),
|
||
stroke: none,
|
||
),caption: "通过抓包分析来获取字段值",kind: image)
|
||
代码编写如下:
|
||
```c
|
||
#define XARP_HW_ETHER 0x1 // 硬件类型:以太网
|
||
#define XARP_REQUEST 0x1 // Opcode:ARP请求包
|
||
#define XARP_REPLY 0x2 // Opcode:ARP响应包
|
||
|
||
typedef enum _xnet_protocol_t {
|
||
XNET_PROTOCOL_ARP = 0x0806, // ARP协议
|
||
XNET_PROTOCOL_IP = 0x0800, // IPv4协议
|
||
XNET_PROTOCOL_ICMP = 1, // ICMP协议
|
||
}xnet_protocol_t;
|
||
```
|
||
然后定义ARP报文的数据结构:
|
||
```c
|
||
typedef struct _xarp_packet_t {
|
||
uint16_t hw_type, pro_type; // 硬件类型和协议类型
|
||
uint8_t hw_len, pro_len; // 硬件地址长 + 协议地址长
|
||
uint16_t opcode; // 请求/响应
|
||
uint8_t sender_mac[XNET_MAC_ADDR_SIZE]; // 发送包硬件地址
|
||
uint8_t sender_ip[XNET_IPV4_ADDR_SIZE]; // 发送包协议地址
|
||
uint8_t target_mac[XNET_MAC_ADDR_SIZE]; // 接收方硬件地址
|
||
uint8_t target_ip[XNET_IPV4_ADDR_SIZE]; // 接收方协议地址
|
||
}xarp_packet_t;
|
||
```
|
||
然后,使用前面定义的```c union xipaddr_t```结构,在`xnet_tiny.h`和`xnet_tiny.c`中定义ARP报文发送函数需要用到的IP地址、组播MAC地址:
|
||
```c
|
||
// xnet_tiny.h
|
||
#define XNET_CFG_NETIF_IP {192, 168, 254, 2} // 本项目模拟出的网卡的IP
|
||
|
||
// xnet_tiny.c
|
||
static const xipaddr_t netif_ipaddr = XNET_CFG_NETIF_IP;
|
||
static const uint8_t ether_broadcast[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||
```
|
||
至此,定义ARP报文的数据结构结束。
|
||
]
|
||
=== ARP报文发送函数
|
||
#para[
|
||
下面编写ARP报文发送函数```c xarp_make_request(const xipaddr_t * ipaddr)```。
|
||
```c
|
||
/**
|
||
* 产生一个ARP请求,请求网络指定ip地址的机器发回一个ARP响应
|
||
* @param ipaddr 请求的IP地址
|
||
* @return 请求结果
|
||
*/
|
||
xnet_err_t xarp_make_request(const xipaddr_t * ipaddr) {
|
||
xarp_packet_t* arp_packet;
|
||
xnet_packet_t * packet = xnet_alloc_for_send(sizeof(xarp_packet_t));
|
||
|
||
arp_packet = (xarp_packet_t *)packet->data;
|
||
arp_packet->hw_type = swap_order16(XARP_HW_ETHER); // 设置硬件类型为以太网
|
||
arp_packet->pro_type = swap_order16(XNET_PROTOCOL_IP); // 设置协议类型为IP
|
||
arp_packet->hw_len = XNET_MAC_ADDR_SIZE; // 设置硬件地址长度
|
||
arp_packet->pro_len = XNET_IPV4_ADDR_SIZE; // 设置协议地址长度
|
||
arp_packet->opcode = swap_order16(XARP_REQUEST); // 设置操作码为ARP请求
|
||
// 复制发送方MAC地址
|
||
memcpy(arp_packet->sender_mac, netif_mac, XNET_MAC_ADDR_SIZE);
|
||
// 复制发送方IP地址
|
||
memcpy(arp_packet->sender_ip, netif_ipaddr.array, XNET_IPV4_ADDR_SIZE);
|
||
// 目标MAC地址清零
|
||
memset(arp_packet->target_mac, 0, XNET_MAC_ADDR_SIZE);
|
||
// 复制目标IP地址
|
||
memcpy(arp_packet->target_ip, ipaddr->array, XNET_IPV4_ADDR_SIZE);
|
||
// 通过以太网发送ARP请求
|
||
return ethernet_out_to(XNET_PROTOCOL_ARP, ether_broadcast, packet);
|
||
}
|
||
```
|
||
这个函数的主要功能是生成并发送一个ARP请求报文,以请求指定IP地址(即函数的输入```c const xipaddr_t * ipaddr```)的机器返回其MAC地址。函数的具体步骤如下:
|
||
1. 分配一个用于发送的ARP数据包```c arp_packet```,并将其数据段设置为ARP报文结构。
|
||
2. 设置ARP报文的各个字段,包括硬件类型`hw_type`、协议类型`pro_type`、硬件地址长度`hw_len`、协议地址长度`pro_len`、操作码`opcode`(设置为`XARP_REQUEST`)等。
|
||
3. 复制发送方(即本项目模拟出的网卡)的MAC地址和IP地址到ARP报文中。
|
||
4. 将目标MAC地址字段清零,并复制目标IP地址到ARP报文中。
|
||
5. 最后,通过以太网发送该ARP请求报文,返回发送结果(状态码)。
|
||
]
|
||
=== 启动时的ARP请求
|
||
#para[
|
||
在以太网协议的初始化函数```c static xnet_err_t ethernet_init(void)```中添加一个ARP请求:
|
||
```c
|
||
/**
|
||
* 以太网初始化
|
||
* @return 初始化结果
|
||
*/
|
||
static xnet_err_t ethernet_init (void) {
|
||
xnet_err_t err = xnet_driver_open(netif_mac);
|
||
if (err < 0) return err;
|
||
|
||
return xarp_make_request(&netif_ipaddr); // 发送ARP请求
|
||
}
|
||
```
|
||
这样,当协议栈初始化时,会发送一个ARP请求。
|
||
|
||
下面用Wireshark抓包来验证ARP请求是否发送成功。首先,重新编译项目;其次,开启Wireshark抓包;最后,启动程序:
|
||
#figure(image("启动ARP2.png",format: "png",width: 100%,fit: "stretch"),caption: "启动时的ARP请求")<figure3>
|
||
从@figure3 中可以看到,ARP请求发送成功,说明编写至此的代码没有问题。
|
||
]
|
||
=== ARP报文接收函数
|
||
#para[
|
||
ARP报文接收函数主要功能是处理接收到的ARP报文,包括解析报文、更新ARP表、发送ARP响应等。下面,根据这些需求编写ARP报文接收函数```c void xarp_in(xnet_packet_t * packet)```:
|
||
```c
|
||
/**
|
||
* 处理接收到的ARP包
|
||
* @param packet 输入的ARP包
|
||
*/
|
||
void xarp_in(xnet_packet_t * packet) {
|
||
// 检查包的大小是否符合ARP包的最小长度要求
|
||
if (packet->size >= sizeof(xarp_packet_t)) {
|
||
xarp_packet_t * arp_packet = (xarp_packet_t *) packet->data;
|
||
uint16_t opcode = swap_order16(arp_packet->opcode);
|
||
|
||
// 检查包的合法性,包括硬件类型、硬件地址长度、协议类型、协议地址长度和操作码
|
||
if ((swap_order16(arp_packet->hw_type) != XARP_HW_ETHER) ||
|
||
(arp_packet->hw_len != XNET_MAC_ADDR_SIZE) ||
|
||
(swap_order16(arp_packet->pro_type) != XNET_PROTOCOL_IP) ||
|
||
(arp_packet->pro_len != XNET_IPV4_ADDR_SIZE)
|
||
|| ((opcode != XARP_REQUEST) && (opcode != XARP_REPLY))) {
|
||
return;
|
||
}
|
||
|
||
// 只处理目标IP地址为自己的ARP请求或响应包
|
||
if (!xipaddr_is_equal_buf(&netif_ipaddr, arp_packet->target_ip)) {
|
||
return;
|
||
}
|
||
|
||
// 根据操作码进行处理
|
||
switch (swap_order16(arp_packet->opcode)) {
|
||
case XARP_REQUEST: // 处理ARP请求,发送ARP响应并更新ARP表项
|
||
xarp_make_response(arp_packet);
|
||
update_arp_entry(arp_packet->sender_ip, arp_packet->sender_mac);
|
||
break;
|
||
case XARP_REPLY: // 处理ARP响应,更新ARP表项
|
||
update_arp_entry(arp_packet->sender_ip, arp_packet->sender_mac);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
该函数主要功能是处理接收到的ARP包。首先进行简单的长度判断,避免后续字段读取失败造成内存错误。随后检查包的合法性,包括硬件类型、硬件地址长度、协议类型、协议地址长度和操作码。APR响应只要求机器处理目标IP地址为自己的ARP请求或响应包,所以使用```c if (!xipaddr_is_equal_buf(&netif_ipaddr, arp_packet->target_ip))```来判断。最后,根据操作码进行处理,分别处理ARP请求和ARP响应:
|
||
- ARP请求:发送ARP响应(```c xarp_make_response(...)```)并更新ARP表项(```c update_arp_entry(...)```);
|
||
- ARP响应:只需要更新ARP表项。
|
||
|
||
其中,用到的宏```c xipaddr_is_equal_buf()```函数用于比较两个IP地址是否相等,实现如下:
|
||
```c
|
||
// 比较IP地址是否相等
|
||
#define xipaddr_is_equal_buf(addr, buf) (memcmp(
|
||
(addr)->array,
|
||
(buf),
|
||
XNET_IPV4_ADDR_SIZE
|
||
)
|
||
== 0
|
||
)
|
||
```
|
||
然后,需要编写上面函数中调用的两个函数:```c xarp_make_response()```和```c update_arp_entry()```。
|
||
|
||
```c xarp_make_response()```函数主要功能是:输入一个ARP请求包,通过此包内的源信息,生成对应的ARP响应,并发送出去。具体代码如下:
|
||
```c
|
||
/**
|
||
* 生成一个ARP响应
|
||
* @param arp_packet 接收到的ARP请求包
|
||
* @return 生成结果
|
||
*/
|
||
xnet_err_t xarp_make_response(xarp_packet_t * arp_packet) {
|
||
xarp_packet_t* response_packet;
|
||
xnet_packet_t * packet = xnet_alloc_for_send(sizeof(xarp_packet_t));
|
||
|
||
response_packet = (xarp_packet_t *)packet->data;
|
||
response_packet->hw_type = swap_order16(XARP_HW_ETHER); // 设置硬件类型为以太网
|
||
response_packet->pro_type = swap_order16(XNET_PROTOCOL_IP); // 设置协议类型为IP
|
||
response_packet->hw_len = XNET_MAC_ADDR_SIZE; // 设置硬件地址长度
|
||
response_packet->pro_len = XNET_IPV4_ADDR_SIZE; // 设置协议地址长度
|
||
response_packet->opcode = swap_order16(XARP_REPLY); // 设置操作码为ARP响应
|
||
// 复制目标MAC地址
|
||
memcpy(response_packet->target_mac, arp_packet->sender_mac, XNET_MAC_ADDR_SIZE);
|
||
// 复制目标IP地址
|
||
memcpy(response_packet->target_ip, arp_packet->sender_ip, XNET_IPV4_ADDR_SIZE);
|
||
// 复制发送方MAC地址
|
||
memcpy(response_packet->sender_mac, netif_mac, XNET_MAC_ADDR_SIZE);
|
||
// 复制发送方IP地址
|
||
memcpy(response_packet->sender_ip, netif_ipaddr.array, XNET_IPV4_ADDR_SIZE);
|
||
// 通过以太网发送ARP响应
|
||
return ethernet_out_to(XNET_PROTOCOL_ARP, ether_broadcast, packet);
|
||
}
|
||
```
|
||
可以发现此函数与前面的ARP请求函数```c xarp_make_request()```非常相似,只是操作码不同,此处为`XARP_REPLY`,其他字段均从源ARP请求报文中获取,并填入对应区域。
|
||
|
||
```c update_arp_entry()```函数主要功能是更新所有ARP表项,附带一定的可视化功能。具体代码如下:
|
||
```c
|
||
/**
|
||
* 更新ARP表项
|
||
* @param src_ip 源IP地址
|
||
* @param mac_addr 对应的mac地址
|
||
*/
|
||
static void update_arp_entry(uint8_t* src_ip, uint8_t* mac_addr) {
|
||
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))
|
||
{
|
||
// 检查ARP表项是否为空或者是否与给定的源IP地址匹配且状态不是有效的
|
||
if (arp_entry->state == XARP_ENTRY_FREE ||
|
||
( arp_entry->state == XARP_ENTRY_OK
|
||
&& xipaddr_is_equal_buf(&arp_entry->ipaddr, src_ip)
|
||
))
|
||
{
|
||
// 更新ARP表项中的IP地址和MAC地址
|
||
memcpy(arp_entry->ipaddr.array, src_ip, XNET_IPV4_ADDR_SIZE);
|
||
memcpy(arp_entry->macaddr, mac_addr, 6);
|
||
printf("learned☝🤓mac addr:\n");
|
||
for (
|
||
int i = 0;
|
||
i < sizeof(mac_addr) / sizeof(mac_addr[0]);
|
||
++i)
|
||
{
|
||
printf("%02X%c",
|
||
mac_addr[i],
|
||
i < sizeof(mac_addr) / sizeof(mac_addr[0]) - 1 ? ':' : '\n'
|
||
);
|
||
}
|
||
// 设置ARP表项状态为有效
|
||
arp_entry->state = XARP_ENTRY_OK;
|
||
// 设置ARP表项的超时时间
|
||
arp_entry->tmo = XARP_CFG_ENTRY_OK_TMO;
|
||
// 设置ARP表项的重试次数
|
||
arp_entry->retry_cnt = XARP_CFG_MAX_RETRIES;
|
||
print_arp_table(); // 打印完整的ARP表
|
||
return; // 更新后退出函数
|
||
}
|
||
}
|
||
|
||
// 如果ARP表已满,采用LRU策略替换最老的表项
|
||
arp_entry = arp_table; // 重置arp_entry指向表头
|
||
xarp_entry_t* oldest_entry = NULL;
|
||
uint32_t oldest_tmo = 0xFFFFFFFF;
|
||
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))
|
||
{
|
||
if (arp_entry->tmo < oldest_tmo) {
|
||
oldest_tmo = arp_entry->tmo;
|
||
oldest_entry = arp_entry;
|
||
}
|
||
}
|
||
if (oldest_entry != NULL) {
|
||
// 更新最老的ARP表项
|
||
memcpy(oldest_entry->ipaddr.array, src_ip, XNET_IPV4_ADDR_SIZE);
|
||
memcpy(oldest_entry->macaddr, mac_addr, 6);
|
||
printf("learned☝🤓mac addr:\n");
|
||
for (int i = 0; i < sizeof(mac_addr) / sizeof(mac_addr[0]); ++i) {
|
||
printf("%02X%c", mac_addr[i], i < sizeof(mac_addr) / sizeof(mac_addr[0]) - 1 ? ':' : '\n');
|
||
}
|
||
// 设置ARP表项状态为有效
|
||
oldest_entry->state = XARP_ENTRY_OK;
|
||
// 设置ARP表项的超时时间
|
||
oldest_entry->tmo = XARP_CFG_ENTRY_OK_TMO;
|
||
// 设置ARP表项的重试次数
|
||
oldest_entry->retry_cnt = XARP_CFG_MAX_RETRIES;
|
||
print_arp_table(); // 打印完整的ARP表
|
||
}
|
||
}
|
||
```
|
||
这个函数很长。它主要功能是更新ARP表项。更新分为两种情况:
|
||
- ARP表还有空闲表项
|
||
- ARP表已满,采用LRU策略替换最老的表项
|
||
首先,函数通过遍历ARP表中的所有表项,检查表项是否为空或者是否与给定的源IP地址匹配且状态不是有效的。如果满足条件,则更新ARP表项中的IP地址和MAC地址,并设置表项状态为有效,设置超时时间和重试次数(最后,会打印完整的ARP表)。如果ARP表已满,则采用LRU策略替换最老的表项。函数会遍历ARP表,找到超时时间最小的表项,并更新该表项的IP地址和MAC地址,设置表项状态为有效,设置超时时间和重试次数,最后打印完整的ARP表。
|
||
|
||
用到的打印函数实现如下:
|
||
```c
|
||
/**
|
||
* 打印完整的ARP表
|
||
*/
|
||
void print_arp_table() {
|
||
printf("\n----ARP Table----\n");
|
||
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)) {
|
||
if (arp_entry->state != XARP_ENTRY_FREE) {
|
||
printf("IP: ");
|
||
for (int i = 0; i < XNET_IPV4_ADDR_SIZE; ++i) {
|
||
printf("%d%c",
|
||
arp_entry->ipaddr.array[i],
|
||
i < XNET_IPV4_ADDR_SIZE - 1 ? '.' : '\n'
|
||
);
|
||
}
|
||
printf("MAC: ");
|
||
for (int i = 0; i < 6; ++i) {
|
||
printf("%02X%c", arp_entry->macaddr[i], i < 5 ? ':' : '\n');
|
||
}
|
||
printf(
|
||
"State: %s\n",
|
||
arp_entry->state == XARP_ENTRY_FREE ? "FREE" :
|
||
arp_entry->state == XARP_ENTRY_RESOLVING ? "RESOLVING" : "OK"
|
||
);
|
||
}
|
||
}
|
||
printf("\n-----------------\n");
|
||
}
|
||
```
|
||
|
||
最后,需要在以太网帧接收函数中添加ARP报文的处理:
|
||
```c
|
||
/**
|
||
* 以太网数据帧输入输出
|
||
* @param packet 待处理的包
|
||
*/
|
||
static void ethernet_in (xnet_packet_t * packet) {
|
||
// 至少要比头部数据大
|
||
if (packet->size <= sizeof(xether_hdr_t)) {
|
||
return;
|
||
}
|
||
// 根据协议类型分发到不同的处理函数
|
||
xether_hdr_t* hdr = (xether_hdr_t*)packet->data;
|
||
switch (swap_order16(hdr->protocol)) {
|
||
case XNET_PROTOCOL_ARP:
|
||
// 移除以太网头部,处理ARP协议
|
||
remove_header(packet, sizeof(xether_hdr_t));
|
||
xarp_in(packet);
|
||
break;
|
||
case XNET_PROTOCOL_IP: {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
其中,主要在```c case XNET_PROTOCOL_ARP```中添加了对ARP报文的处理。
|
||
|
||
在继续之前,再次使用Wireshark检验这部分代码编写。重新编译后,按照以下流程进行检验:
|
||
- 开启Wireshark抓包;
|
||
- 运行本程序;
|
||
- 在虚拟机上ping本程序,以触发ARP请求;
|
||
- 查看Wireshark抓包结果和程序输出。
|
||
#figure(image("ARP响应.png",format: "png",width: 100%,fit: "stretch"),caption: "ARP请求响应")<figure4>
|
||
从@figure4 中可以看到,ARP响应都发送成功,程序输出中也表明学习到了虚拟机的MAC地址,说明代码编写正确。
|
||
]
|
||
=== ARP超时重传
|
||
= 实验总结
|
||
== 内容总结
|
||
|
||
== 心得感悟
|
||
|
||
#show heading: it => box(width: 100%)[
|
||
#v(0.50em)
|
||
#set text(font: hei)
|
||
// #counter(heading).display()
|
||
// #h(0.5em)
|
||
#it.body
|
||
]
|
||
#pagebreak()
|
||
#bibliography("ref.yml",full: true,title: "参考文献",style:"gb-7714-2015-numeric")
|