小议LWIP——ARP协议1

在TCP/IP网络中,ARP协议始终是相当重要的一个环节,它主要是负责将32位的IP地址(IPv6则是128位)映射为48位的MAC地址(规定每一种网络通信设备具备唯一的MAC地址),从而可以在底层数据链路层中标记不同的网络通信设备。

ARP协议的本质

由于IP地址和MAC地址没有固定的映射规则,所以为了描述这种映射关系,在实现上LWIP采用ARP缓存表这种结构来存储这种映射关系。简单的说就是需要将一条条的(IP地址,MAC地址)存储起来,当发送IP数据包时可以根据该表进行查询相应主机的MAC地址。
在windows中的控制台中输入arp -a会得到这台主机的ARP缓存表:
这里写图片描述
可以看到局域网中的(IP地址,MAC地址)之间一一对应的关系。

LWIP中ARP缓存表数据结构

如果接触过虚拟存储器中的页表,那么ARP缓存表也一定很快就能理解。ARP缓存表由一个个缓存表项(entry)组成,这些表项的核心当然是刚刚提过的IP地址和MAC地址的映射关系。那么是不是光记录这个信息就足够了呢?我们先来看看LWIP中的ARP缓存表项的实现。
1、ARP缓存表的实现:

static struct etharp_entry arp_table[ARP_TABLE_SIZE];

即用了简单的数组来实现ARP缓存表。

2、etharp_entry(缓存表项)数据结构

struct etharp_entry {
#if ARP_QUEUEING
  /** Pointer to queue of pending outgoing packets on this ARP entry. */
  struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
  /** Pointer to a single pending outgoing packet on this ARP entry. */
  struct pbuf *q;
#endif /* ARP_QUEUEING */
  ip_addr_t ipaddr;
  struct netif *netif;
  struct eth_addr ethaddr;
  u8_t state;
  u8_t ctime;
};

可以看到上述的结构中不仅有ipaddr(IP地址)和ethaddr(MAC地址),还有其他成员。
netif : 描述网络接口,主要是考虑到有些网络设备(如路由器)连接不同的网络,会有不通的(IP,MAC)地址映射对。
p:当上层应用需要发送IP数据包时,而现在ARP缓存表尚未建立该目的IP地址的表项,需要将待发送的IP地址组织起来,待建立好目的IP地址的表项再发送出去。
state:描述该表项的状态。主要是由于目前网络拓扑结构不固定,随时可能有设备加入或者离开本网络。
ctime:记录该表项的时间信息,主要是为更新state而记录时间。

3、etharp_q_entry(放置带发送数据包的链表)的数据结构如下:

struct etharp_q_entry {
  struct etharp_q_entry *next;
  struct pbuf *p;
};

即简单的将带发送的数据包用链表形式串起来,等建立起相应的ARP表项再发送出去。

4、etharp_state(表项的状态)的数据结构如下:

enum etharp_state {
  ETHARP_STATE_EMPTY = 0,
  ETHARP_STATE_PENDING,
  ETHARP_STATE_STABLE,
  ETHARP_STATE_STABLE_REREQUESTING
#if ETHARP_SUPPORT_STATIC_ENTRIES
  ,ETHARP_STATE_STATIC
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};

在ARP初始化的时候,每个缓存表项的state均为empty状态。所以设备在刚开始的时候会进行广播,获得本局域网中的设备地址对。在等待其他设备应答的时候,LWIP就会将表项状态设置为pending状态。当LWIP收到来自其他主机回应的地址对时,就会将这些地址对写入到各表项,同时更新这些表项的state状态为stable。
由于设备可以随时加入和离开网络,所以需要每隔一段时间进行一个update操作,目的就是维持ARP表项为目前最新的映射关系。以下为update的时间:
1、pending持续最长的时间为10s
2、stable持续的最长时间为20min
源码的操作如下:

#define ARP_MAXAGE            240  //240*5s = 20min
#define ARP_MAXPENDING        2   //2*5s = 10s

etharp_tmr(void)
{
  u8_t i;

  /* 遍历ARP表项 */
  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
    u8_t state = arp_table[i].state;
    if (state != ETHARP_STATE_EMPTY
#if ETHARP_SUPPORT_STATIC_ENTRIES
      && (state != ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
      ) {
      arp_table[i].ctime++;
      if ((arp_table[i].ctime >= ARP_MAXAGE) ||
          ((arp_table[i].state == ETHARP_STATE_PENDING)  &&
           (arp_table[i].ctime >= ARP_MAXPENDING))) {
        /* 表项过期了,则将该表项进行删除*/
        etharp_free_entry(i);
      }
      else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING) {
        /* 这里我也不是很明白。。。 */
        arp_table[i].state = ETHARP_STATE_STABLE;
      }
    }
  }
}

其实上述代码原理并不难,就是依据每个表项中的ctime信息来对state状态进行更新。

接收数据包

LWIP中的处理流程如下:
这里写图片描述
由流程图可以知道,无论接收到的是IP数据包或者ARP数据包,均有可能对ARP缓存表进行更新。下面简单讲述一下ARP协议核心的几个函数:

/*arp数据包处理函数*/
etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
{
  struct etharp_hdr *hdr;
  struct eth_hdr *ethhdr;

  ip_addr_t sipaddr, dipaddr;
  u8_t for_us;

  ...

    /* 判断接收到的ARP包是否是给我们的 */
  if (ip_addr_isany(&netif->ip_addr)) { /* 若IP地址为NULL或者为全0,则不是给我们的,将for_us设为0 */
    for_us = 0;
  } else {
    /*否则将目的IP和网卡IP进行匹配,相同时将for_us置1 */
    for_us = (u8_t)ip_addr_cmp(&dipaddr, &(netif->ip_addr));
  }

/*更新arp缓存表,若arp包给我们的则硬性更换ARP表项,否则采用尝试更换*/
 etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
                   for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);

 /* 通过ARP请求类型做出相应的处理 */
  switch (hdr->opcode) {
  /* 是否是ARP请求? */
  case PP_HTONS(ARP_REQUEST):
    /* 判断是否是给我们的ARP报文 */
    if (for_us) {/* 若是ARP请求,则需要我们主机应答 */

      hdr->opcode = htons(ARP_REPLY);//将ARP报文类型改成ARP响应类型


     /*构造ARP应答报文,将主机IP以及MAC地址写入到应答报文*/
      IPADDR2_COPY(&hdr->dipaddr, &hdr->sipaddr);
      IPADDR2_COPY(&hdr->sipaddr, &netif->ip_addr);

      ETHADDR16_COPY(&hdr->dhwaddr, &hdr->shwaddr);
      ETHADDR16_COPY(&ethhdr->dest, &hdr->shwaddr);
      ETHADDR16_COPY(&hdr->shwaddr, ethaddr);
      ETHADDR16_COPY(&ethhdr->src, ethaddr);

      /* 通过low_level_input函数发送ARP应答报文 */
      netif->linkoutput(netif, p);

    } else if (ip_addr_isany(&netif->ip_addr)) {
      /*若arp请求报文不是给我们的,且我们的IP地址尚未配置,则打印对应的调试信息*/
    } else {
      /*若arp请求报文不是给我们的,且我们的IP地址已经配置,则也打印对应的调试信息*/
    }
    break;
  case PP_HTONS(ARP_REPLY): 
    /* 若是ARP应答报文,则不用再做什么工作了,因为之前就应经更新了ARP表*/
    break;
  default:
    break;
  }
  /* 释放数据包 */
  pbuf_free(p);
}

以上源代码均经过笔者修剪过,有兴趣的朋友可以直接下载源码进行研究,LWIP源码下载地址
今天先写到这里吧,等后面接着写ARP协议2

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值