会当击水三千里,自信人生二百年。

1、以太网控制器在接收数据包的时候会检查数据包的MAC地址是否合法
① 广播地址 ② 本地网卡的MAC地址
如果是非法数据包,压根都不会进入协议栈,直接在硬件层面就丢包了
2、为什么需要设置ARP重传机制?为何要给每个ARP表项设置一个定时器?
arp_entry记录的是网络当中主机IP地址与MAC地址的映射关系,但是由于网卡绑定的IP地址是动态的。如果对方的机器下线,此时DHCP服务器可能会回收这个IP地址进而分配给别的网卡,这时候我们本机保存的arp表项当中维护的映射关系就出错了,需要重新进行修正。
总结:记录IP地址与MAC地址的映射关系是为了提高转换速率,而设置定时器,甚至是删除部分表项则是为了适应网络的动态变化。由于网络是不可靠的,所以我们需要在自己的协议栈当中添加ARP的超时重传机制。
3、协议栈实现超时重传机制
设置一个定时器,到时间了就重新向表项中的主机发送ARP请求。
除此之外,还需要设置另外一个超时值,因为我们重新发送ARP请求之后,还需要判断一下在指定的时间内有没有收到响应,从而避免的等待时间过长的情况的出现。如果超过这个时间还没有收到响应的话,我们就会再发送一个请求,直到对方给了回复或者超过最大请求次数,此时就可以将该表项删除。
代码实现部分
1)在port_pcap.c中添加时间的头文件:#include<time.h>
并且添加xsys_get_time函数:clock()表示的是程序从启动到调用clock()的时间
而clock() / CLOCKS_PER_SEC表示的是从程序启动到调用经过多少秒
cpp
const xnet_time_t xsys_get_time(void) {
//clock()表示的是从程序启动到调用这个函数的时间
//clock()/CLOCKS_PER_SEC表示的是从程序启动到调用经过多少秒
return clock() / CLOCKS_PER_SEC;
}
2)在xnet_tiny.h中添加xnet_time_t的定义
cpp
typedef uint32_t xnet_time_t; //时间类型,返回当前系统跑了多少s
const xnet_time_t xsys_get_time(void);
在xnet_tiny.h中添加xarp_poll的定义
cpp
void xarp_poll(void);
然后在xnet_poll函数中调用xarp_poll
cpp
void xnet_poll(void)
{
ethernet_poll();
xarp_poll();
}
3)在xnet_tiny.h中添加:
① arp表项的超时时间;
② arp表项挂起时最大的查询(重试)次数;
③ arp表项挂起时的超时时间(查一次最多等多久);
cpp
#define XARP_CFG_ENTRY_OK_TMO (5) //arp表项的超时时间设置为5s
#define XARP_CFG_MAX_RETRIES (4) //arp表项挂起时最大的查询次数
#define XARP_CFG_ENTRY_PENDING_TMO (1) //arp表项挂起时的超时时间(查一次最多等多久)
在xnet_tiny.h中添加arp表项的几个状态:
cpp
#define XARP_ENTRY_FREE 0 //arp表项空闲
#define XNET_ENT_OK 1 //arp表项解析成功
#define XARP_ENTRY_RESOLVING 2 //arp表项正在解析
注:OK表示ARP表项的IP地址与MAC地址之间的转换关系是正确的,RESOLVING表示需要确认/解析映射关系是否正确
4)接下来实现xarp_poll函数:
协议栈每隔1s扫描一次整个ARP表中的表项(当然本项目只设置了一个arp表项)
每个正常状态的arp表项如果被扫描五次,则需要被设置为RESOLVING状态
在解析状态如果连续5次没有请求成功,就会被回收表项
cpp
void xarp_poll(void)
{
if (xnet_check_tmo(&arp_timer, XARP_TIMER_PERIOD))
{
switch (arp_entry.state)
{
case XARP_ENTRY_OK:
if (--arp_entry.tmo == 0)
{
xarp_make_request(&arp_entry.ipaddr);
arp_entry.state = XARP_ENTRY_RESOLVING;
arp_entry.tmo = XARP_CFG_ENTRY_PENDING_TMO;
}
break;
case XARP_ENTRY_RESOLVING:
if (--arp_entry.tmo == 0)
{
if (arp_entry.retry_cnt-- == 0)
arp_entry.state = XARP_ENTRY_FREE;
else
{
xarp_make_request(&arp_entry.ipaddr);
arp_entry.state = XARP_ENTRY_RESOLVING;
arp_entry.tmo = XARP_CFG_ENTRY_PENDING_TMO;
}
}
break;
}
}
}
同时在update_arp_entry函数当中也需要添加对于arp表项其他字段的初始化
cpp
static void update_arp_entry(uint8_t* src_ip, uint8_t* mac_addr)
{
memcpy(arp_entry.ipaddr.array, src_ip, XNET_IPV4_ADDR_SIZE);
memcpy(arp_entry.macaddr, mac_addr, XNET_MAC_ADDR_SIZE);
arp_entry.state = XARP_ENTRY_OK;
arp_entry.tmo = XARP_CFG_ENTRY_OK_TMO;
arp_entry.retry_cnt = XARP_CFG_MAX_RETRIES;
}
5)定义一个宏XARP_TIME_PERIOD 来设置arp表的扫描时间,即多久调用一次xarp_poll
cpp
#define XARP_TIME_PERIOD 1 //arp扫描周期(1s扫描一次)
6)实现xnet_check_tmo函数,用来检查arp表项是否超时,同时通过指针可以更新timer的值
特别地:在这里指定sec=0表示用于获取系统当前运行的秒数;非0的时候才进行arp的超时检查
cpp
//检查是否超时,同时通过指针可以更新timer的值
int xnet_check_tmo(xnet_time_t* timer, uint32_t sec) {
xnet_time_t cur = xsys_get_time();
if (sec == 0) { //0特殊地,用于获取当前的时间
*timer = cur;
return 0;
}
else if (cur - *timer >= sec) { //非0检查超时
*timer = cur; //当超时的时候,才更新时间
return 1;
}
return 0;
}
下面让我们一起进入愉快的调试环节吧🤷♀️:
①首先检查一下计时器是否正常工作
能够正常进入,得到的时间单位是秒,没问题

②使用虚拟机ping 192.168.254.2,观察5s之后能否进入XARP_ENTRY_OK的处理函数

③5s超时之后,紧接着就会进入XARP_ENTRY_RESOLVING状态

④把网卡禁用观察能否进入清除ARP表项的分支
发现程序在进入5次RESOLVING状态之后还未进入OK状态,则清空了该ARP表项(置为FREE状态),后续将不会进入任何case当中

如果上面的三个断点你都能进入的话,那么恭喜你完成了本章的所有内容的🎉🎉
至此我们已经实现了arp超时重传的所有功能!!!
下一节课我们会继续带领大家实现IP协议的相关功能,期待的话多多关注吧 🤞💕💕