LwIP的ARP协议实现(4)

LwIP的ARP协议实现系列文章

LwIP中的ARP实现(1)之 ARP缓存表的数据结构
LwIP中的ARP实现(2)之 ARP缓存表的超时处理
LwIP中的ARP实现(3)之 发送ARP请求包
LwIP中的ARP实现(4)之 ARP数据包接收
LwIP中的ARP实现(5)之 ARP数据包发送

ARP数据包处理

以太网是有自己独立的寻址方式(MAC地址),而对于TCP/IP的上层协议(如TCP协议、IP协议),它们是以IP地址作为网络的标识,如果没有IP地址则无法进行收发数据。当数据通过网卡中接收回来的时候,LwIP内核就需要将数据进行分解,如果是IP数据报则递交给IP协议去处理,如果是ARP数据包则交由ARP协议去处理。
真正让LwIP内核去处理接收到的数据包是ethernet_input()函数。代码太多了,简单截取部分代码。

err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{
  struct eth_hdr *ethhdr;
  u16_t type;

  LWIP_ASSERT_CORE_LOCKED();
  
  //校验数据长度
  if (p->len <= SIZEOF_ETH_HDR) {
    ETHARP_STATS_INC(etharp.proterr);
    ETHARP_STATS_INC(etharp.drop);
    MIB2_STATS_NETIF_INC(netif, ifinerrors);
    goto free_and_return;
  }

  if (p->if_idx == NETIF_NO_INDEX) {
    p->if_idx = netif_get_index(netif);
  }

  /* ethhdr指针指向以太网帧头部,并且强制转换成eth_hdr结构 */
  ethhdr = (struct eth_hdr *)p->payload;

  //获取类型
  type = ethhdr->type;

  if (ethhdr->dest.addr[0] & 1) 
  {
    /*  这可能是多播或广播数据包,如果目标IP地址的第一个字节的bit0是1,
		那么有可能是多播或者是广播数据包,所以,还需要进行判断,
		如果是多播的,就将pbuf标记为链路层多播。 */
    if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) {
      if ((ethhdr->dest.addr[1] == LL_IP4_MULTICAST_ADDR_1) &&
          (ethhdr->dest.addr[2] == LL_IP4_MULTICAST_ADDR_2)) 
	  {
       /* 将pbuf标记为链路层多播 */
        p->flags |= PBUF_FLAG_LLMCAST;
      }
    }
    else if (eth_addr_cmp(ðhdr->dest, ðbroadcast)) 
	{
      /* 将pbuf标记为链路层广播 */
      p->flags |= PBUF_FLAG_LLBCAST;
    }
  }

  switch (type) {
     /* 如果是IP数据报 */
    case PP_HTONS(ETHTYPE_IP):
      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
        goto free_and_return;
      }
      /* 去掉太网首部 */
      if (pbuf_remove_header(p, next_hdr_offset)) 
	  {
        goto free_and_return;
      } 
	  else
	  {
        /* 递交到IP层处理 */
        ip4_input(p, netif);
      }
      break;

	//对于是ARP包
    case PP_HTONS(ETHTYPE_ARP):
      if (!(netif->flags & NETIF_FLAG_ETHARP)) 
	  {
        goto free_and_return;
      }
      /* 去掉太网首部 */
      if (pbuf_remove_header(p, next_hdr_offset)) 
	  {
        ETHARP_STATS_INC(etharp.lenerr);
        ETHARP_STATS_INC(etharp.drop);
        goto free_and_return;
      } 
	  else 
	  {
        /* 传递到ARP协议处理 */
        etharp_input(p, netif);
      }
      break;

    default:
      ETHARP_STATS_INC(etharp.proterr);
      ETHARP_STATS_INC(etharp.drop);
      MIB2_STATS_NETIF_INC(netif, ifinunknownprotos);
      goto free_and_return;
  }

  return ERR_OK;

free_and_return:
  pbuf_free(p);
  return ERR_OK;
}

ARP数据包的处理

重点来了,我们主要是讲解对收到的ARP数据包处理
ARP数据包的处理函数为etharp_input(),在这里它完成两个任务:

  1. 如果收到的是ARP应答包,说明本机之前发出的ARP请求包有了回应,就根据应答包更新自身的ARP缓存表;
  2. 如果收到的是ARP请求包,如果包中的目标IP地址与主机IP地址匹配,除了记录原主机的IP与MAC地址,更新自身的ARP表外,还要向源主机发送一个ARP应答包。但是如果如果包中目标IP地址与主机IP地址不匹配,则尽可能记录源主机的IP与MAC地址,更新自身的ARP表,并丢弃该请求包,为什么说是尽可能呢,因为主机的ARP缓存表是有限的,不可能记录太多的ARP表项,所以在有空闲的表项时才记录,如果没有空闲的表项,ARP觉得它自己已经尽力了,也记不住那么多表项。
void
etharp_input(struct pbuf *p, struct netif *netif)
{
  struct etharp_hdr *hdr;
  /* these are aligned properly, whereas the ARP header fields might not be */
  ip4_addr_t sipaddr, dipaddr;
  u8_t for_us;

  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ERROR("netif != NULL", (netif != NULL), return;);

  hdr = (struct etharp_hdr *)p->payload;

  /* 判断ARP包的合法性,判断ARP包的合法性,已经类型是否为以太网、硬件地址长度是否为ETH_HWADDR_LEN、
     协议地址长度是否为sizeof(ip4_addr_t)以及协议是否为ARP协议,如果都满足则表示ARP包合法。 */
  if ((hdr->hwtype != PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET)) ||
      (hdr->hwlen != ETH_HWADDR_LEN) ||
      (hdr->protolen != sizeof(ip4_addr_t)) ||
      (hdr->proto != PP_HTONS(ETHTYPE_IP)))  {
    ETHARP_STATS_INC(etharp.proterr);
    ETHARP_STATS_INC(etharp.drop);
    pbuf_free(p);
    return;
  }
  ETHARP_STATS_INC(etharp.recv);


  //拷贝源IP地址与目标IP地址
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr);
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr);

  /* 看看主机网卡是否配置了IP地址 */
  if (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
    for_us = 0;
  } 
  else 
  {
     /* 判断ARP数据包的目标IP地址与主机IP地址是否一样 */
    for_us = (u8_t)ip4_addr_cmp(&dipaddr, netif_ip4_addr(netif));
  }

  /* 更新ARP缓存表项 */
  etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
                          for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);

 /* 更新完毕,根据包的类型处理 */
  switch (hdr->opcode) 
  {
    /* ARP请求包 */
    case PP_HTONS(ARP_REQUEST):
      if (for_us) {
         /* 是请求自己的,那就要做出应答 */
        etharp_raw(netif,
                   (struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
                   (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
                   &hdr->shwaddr, &sipaddr,
                   ARP_REPLY);
				   
      } 
	   /* 不是给自己的,如果不是给自己的,原因有两种,一种是网卡自身尚未配置IP地址,这样子就只打印相关调试信息。
	      另一种是ARP包中的目标IP地址与主机IP地址不符合,也不用做出回应,直接丢弃即可,并输出相关调试信息*/
	  else if (ip4_addr_isany_val(*netif_ip4_addr(netif))) 
	  {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: we are unconfigured, ARP request ignored.\n"));
      }
	  else
	  {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: ARP request was not for us.\n"));
      }
      break;
	  
	/* 对于ARP应答包 不用处理,前面已经更新ARP表项了*/
    case PP_HTONS(ARP_REPLY):
      break;
    default:
      ETHARP_STATS_INC(etharp.err);
      break;
  }
  pbuf_free(p);
}

更新ARP表项

etharp_update_arp_entry()函数是用于更新ARP缓存表的,它会在收到一个ARP数据包的时候被调用,它会先查找一个ARP表项,如果没有找到这个ARP表项的记录,就会去新建一个ARP表项,然后重置ARP表项的参数(状态、网卡。IP地址与对应的MAC地址以及生存时间等),然后检测ARP表项中是否挂载数据包,如果有就将这些数据包发送出去。
表项的更新方式,动态表项有两种方式,分别为ETHARP_FLAG_TRY_HARD和ETHARP_FLAG_FIND_ONLY。前者表示无论如何都要创建一个表项,如果ARP缓存表中没有空间了,那就需要回收较老的表项,将他们删除,然后建立新的表项。而如果是后者,就让内核尽量更新表项,如果ARP缓存表中没有空间了,那么也无能为力,实在是添加不了新的表项。

static err_t
etharp_update_arp_entry(struct netif *netif, const ip4_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)
{
  s16_t i;
  if (ip4_addr_isany(ipaddr) ||
      ip4_addr_isbroadcast(ipaddr, netif) ||
      ip4_addr_ismulticast(ipaddr)) {
    return ERR_ARG;
  }
  
 /* 查找或者创建ARP表项,并且返回索引值 */
  i = etharp_find_entry(ipaddr, flags, netif);
  
  /* 如果索引值不合法,更新ARP表项失败 */
  if (i < 0) {
    return (err_t)i;
  }
  
  /* 设置表项状态为ETHARP_STATE_STABLE */
  arp_table[i].state = ETHARP_STATE_STABLE;

  /* 记录网卡 */
  arp_table[i].netif = netif;
  
  /* 插入ARP索引树 */
  mib2_add_arp_entry(netif, &arp_table[i].ipaddr);

  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: updating stable entry %"S16_F"\n", i));
  
  /* 更新缓存表中的MAC地址 */
  SMEMCPY(&arp_table[i].ethaddr, ethaddr, ETH_HWADDR_LEN);
  
  /* 重置生存时间 */
  arp_table[i].ctime = 0;
  
  /* 如果表项上与未发送的数据包,那就将这些数据包发送出去 */
#if ARP_QUEUEING		//使用队列方式
  while (arp_table[i].q != NULL) 
  {
    struct pbuf *p;
    /* 定义q指向ARP表项中的数据包缓存队列 */
    struct etharp_q_entry *q = arp_table[i].q;
	
    /* 指向下一个数据包节点 */
    arp_table[i].q = q->next;
	
    /* 获取pbuf数据包 */
    p = q->p;
	
    /* 释放MEMP_ARP_QUEUE类型的内存块 */
    memp_free(MEMP_ARP_QUEUE, q);
#else 
  if (arp_table[i].q != NULL) {
    struct pbuf *p = arp_table[i].q;
    arp_table[i].q = NULL;
#endif 
     /* 发送缓存队列的数据包 */
    ethernet_output(netif, p, (struct eth_addr *)(netif->hwaddr), ethaddr, ETHTYPE_IP);
    /* free the queued IP packet */
    pbuf_free(p);
  }
  return ERR_OK;
}

ARP数据包处理流程

欢迎关注杰杰个人公众号,一起进入物联网全栈开发~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值