LwIP 协议栈源码详解 ——TCP/IP 协议的实现(十:ARP 层流程)

来源:互联网 发布:人工智能对战星际争霸 编辑:程序博客网 时间:2024/06/03 06:42
9 ARP 层流程
       前面一节重点说了 ARP 缓存表以及如何对其进行相关操作,关于 ARP,一共想说三个
函数,前面已经讲过了两个。
       最后要讲的一个函数是 update_arp_entry,该函数用于更新 ARP 缓存表中的表项或者在
缓存表中插入一个新的表项。该函数会在收到一个 IP 数据包或 ARP 数据包后被调用。该函
数原型如下,
static err_t 
update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags) 
其中重要的两个参数 ipaddr 和 ethaddr 分别对应的 ip 地址和 mac 地址,函数利用这两个地址
去更新或插入 ARP 表项。由于这个函数代码量较小,这里就列出源码来讲解,注意这个源
码是经过我处理的,已经去掉了编译选项、源码注释、调试输出信息等非重点部分。
static err_t 
update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags) 

s8_t i; //  两个变量,不解释
u8_t k; 
i = find_entry(ipaddr, flags); //  查找或新建一个 ARP 表项,返回其索引值
if (i < 0) return (err_t)i; //  如果为不合法的索引值,则更新缓存表失败
arp_table[i].state = ETHARP_STATE_STABLE; //  否则将对应表项状态改为 stable 
arp_table[i].netif = netif; //  记录下网络接口
k = ETHARP_HWADDR_LEN; //  这一段是更新缓存表项中的 MAC 地址
while (k > 0) { 
k--; 
arp_table[i].ethaddr.addr[k] = ethaddr->addr[k]; 

arp_table[i].ctime = 0; //  生存时间值置 0 
#if ARP_QUEUEING //该 ARP 表项上有᳾发送的队列,则把这些队列发送出去
while (arp_table[i].q != NULL) { //  只要缓冲链表中海有数据则循环
struct pbuf *p; 
struct etharp_q_entry *q = arp_table[i].q; //  记录下缓冲链表表头
arp_table[i].q = q->next; //  缓冲链表表头指向下一个节点
p = q->p; //  取得记录下的缓冲链表表头指向的数据包
memp_free(MEMP_ARP_QUEUE, q); //  释放记录下的缓冲链表表头
etharp_send_ip(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr); //  发送数据包
pbuf_free(p); //  释放数据包缓存空间

#endif 
return ERR_OK; 

从源程序中可以看出,update_arp_entry 的流程如下:先通过调用 find_entry 找到对应
ipaddr 对应的表项,并设置相应的 arp 表项的成员 (主要是 state, netif, ethaddr, cttime),
最后如果定义了 ARP_QUEUEING,并且这个 arp 表项上有᳾发送的数据包的话,则把这些
数据全部发送出去。虽然比较啰嗦,但是还是我们还是根据不同的 ipaddr 经过 find_entry 执
行后,来看看 update_arp_entry 运行的几种不同情况。
        首先可以肯定的是, update_arp_entry 的两个参数 ipaddr 和 ethaddr 必是互相匹配的,因
为它们是从源主机发来的 IP 包或 ARP 包中解析出来的,代表了源主机的 MAC 地址和 IP
地址。find_entry 利用 ipaddr 作为参数执行后,返回一个 ARP 表项索引。如果该表项处于
empty 状态,那么该表项现在一定是新创建的,此时设置该表项为 stable 状态并设置该表项
其他字段值后即结束。如果该表项是处于 pending 状态,由于此时已经有了和 ipaddr 匹配的
MAC 地址返回,所以该表项也被设置为 stable 状态并同时设置该表项其他字段值。如果该
表项是处于 stable 状态,其实此时只需要将 ctime 的值复位即可,但是 LWIP 为了节省代码
量,它还是选择像上面的情况一样做相同的处理,这样虽然有些步骤是多余的,但并不影响
函数功能。最后都会检查该表项是否还有数据需要发送,如果有,则将所有数据包发送出去。
现在是时候从宏观上来看看到底 ARP 是怎么一个工作流程,以及它在整个 LWIP 协议
栈当中发挥的重要作用。关于这点,不得不借鉴网上某位大侠的了,不过对 TA 的图做了一

些小小的修改(脸红,用画图工具改的,Visio 表示不会用)。



        该图简洁明了的解释了基ᴀ所有 LWIP 的数据包接收与发送的全过程。我们可以看到几个熟
悉的身影:etharp_query、etharp_request、update_arp_entry。在前面已经讲过了的!
ARP 从功能上来说可以简单的分成两个部分:当有数据包输入时,更新 arp 表,如果是
ip 包则递交给 ip 层,如果是 arp 包,则针对不同的 arp 包类型做相应的响应;当向目的 ip
发送一个数据包的时候,需要通过 arp 实现 ip 到 MAC 地址的映射,必要时,需要发送广播
数据包获得目标机器的 MAC 地址。
          LWIP 利用 netif.input 指向的函数接收以太网数据包,通常这个函数是 ethernet_input。
注意,这里并不是说 ethernet_input 直接与底层硬件交互接收数据包,而是更底层的函数接
收到数据包后将数据包递交给 ethernet_input,ethernet_input 再对其进行处理。
以太网的帧类型可以是:IP,ARP,甚至可以是 pppoe,wlan 等。这里主要分析 IP 和
ARP 两种类型的数据包。ethernet_input 根据以太网首部的类型字段判断收到的数据包的类
型,如果是 IP 包,则将该包递交给 etharp_ip_input,如果是 ARP 包,则将该包递交给
etharp_arp_input。
        对于 ip 类型的数据包, etharp_ip_input 首先检查是否开启了 ETHARP_TRUST_IP_MAC
这个选项,如果开启了就是要用这个帧中的信息和 update_arp_entry 函数来更新 arp 表(利
用帧首部的源 mac 地址和帧数据中 ip 报文中的源 ip 地址),然后丢弃以太网帧首部,将 IP
报文通过 ip_input 函数递交给 ip 层。
        对于 arp 类型的数据包, etharp_arp_input 函数首先利用数据包头信息更新 arp 表的内容,
然后再判断该 ARP 数据包的类型,如果是 ARP 请求包,则首先判断这个包是不是给自己的,
如果是给自己的,则在原有包的基础上重组一个 ARP 应答包发送出去(注意此处并没有重
新分配一个 pbuf,而是借用了原来的缓冲结构)。如果不是给自己的,则直接忽略。如果是
ARP 应答包,主要的工作就是更新 arp 表,但是这一步已经在 arp 包刚进来的时候就处理了,
所以这里不需要再重复做,这样 ARP 包的处理也完毕。
         LWIP 利用 netif.output 指向的函数发送 IP 数据包,通常这个函数是 etharp_output。注意,
这里并不是说 etharp_output 直接与底层硬件交互发送数据包,而是将数据包做相应的处理,
主要是将 IP 数据包打包成以太网帧数据,最终递交给 netif.linkoutput 函数来发送的。
etharp_output 函数接收 IP 层要发送的数据包,并将数据包发送出去。由于是发送 ip 数
据包,所以函数一开始需要增加缓冲区大小,大小为以太网的数据首部的大小。然后检查 ip
地址,可以分为广播包,多播包,单播包(单播包又分为是局域网内部还是局域网外面)。
        广播包:判断目的 IP 地址是不是为全 1,或者是全 0(老版ᴀ中使用的),如果是广播
包则目的 IP 的 MAC 地址不需要查询 arp 表,直接将 MAC 地址设置为全 1 发送即可,即
MAC 六个字节值为 0xff,0xff,0xff,0xff,0xff,0xff。
       多播包:判断目的 ip 地址是不是 d 类地址,即 0xexxxxxxx,如果是多播的话,mac 地
址也是确定的,即将 MAC 地址 01-00-5e-00-00-00 的低 23 位设置为 IP 地址的低 23 位。对
于以上的两种数据包,etharp_output 直接调用函数 etharp_send_ip 将数据包发送出去。
       单播包:要比较目的 IP 和ᴀ地 IP 地址,看是否是局域网内的,不是局域网内的,则将
目的 IP 地址设置为默认网关的地址,然后再统一调用 etharp_query 函数将数据包发送出去,
注意这些数据包在这种情况下可能被连接在相关 ARP 表项的发送链表上,等待发送。 
原创粉丝点击