LwIP的ARP协议实现(5)

LwIP的ARP协议实现系列文章

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

ARP数据包发送

我们知道一个数据包从底层传递进来的流程是怎么样的,如果是ARP数据包就会给ARP去处理,如果是IP数据报就使用ip4_input()函数传递到上层,这些处理在后面的章节讲解。那么如果上层协议想要发送数据,也肯定需要经过ARP协议将IP地址映射为MAC地址才能完成发送操作,IP数据报通过ip4_output()函数将上层数据包传递到ARP协议处理,关于IP协议是怎么样传递的我们暂且不说,那么ARP通过etharp_output()函数接收到IP数据报后,就会进行发送,ARP会先从数据包中进行分析,看看这个IP数据报是单播数据包还是多播或者是广播数据包,然后进行不同的处理:

  1. 对于多播或者是广播数据包,这种处理就很简单,直接将数据包丢给网卡就行了(调用ethernet_output()函数)。
  2. 对于单播包的处理稍微麻烦一点,ARP协议需要根据IP地址找到对应的MAC地址,然后才能正确发送,如果找不到MAC地址的话,还要延迟发送数据包,ARP协议首先会创建一个ARP表项,然后将数据包挂到ARP表项对应的缓存队列上,与此同时会发出一个ARP请求包,等待目标主机的回应后再发送IP数据报。

此处需要注意的是,对于PBUFF_ERF、PBUF_POOL、PBUF_RAM类型的数据包是不允许直接挂到ARP表项对应的缓存队列上的,因为此时内核需要等待目标主机的ARP应答,而这段时间里,这些数据有可能会被上层改动,这是不允许的,所以LwIP需要将这些pbuf数据包拷贝到新的空间,等待发送。

etharp_output()函数被IP层的ip4_output()函数调用,IP层传递一个数据包到ARP中,etharp_output()会根据数据包的目标IP地址选择不同的处理。

err_t
etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
{
  const struct eth_addr *dest;
  struct eth_addr mcastaddr;
  const ip4_addr_t *dst_addr = ipaddr;

  if (ip4_addr_isbroadcast(ipaddr, netif)) 
  {
    /* 如果是广播数据包,目标MAC地址设置为FF-FF-FF-FF-FF-FF-FF */
    dest = (const struct eth_addr *)ðbroadcast;
  } 
  else if (ip4_addr_ismulticast(ipaddr)) 
  {
    /* 如果是多播数据包,目标MAC地址设置为多播地址:01-00-5E-XX-XX-XX */
    mcastaddr.addr[0] = LL_IP4_MULTICAST_ADDR_0;
    mcastaddr.addr[1] = LL_IP4_MULTICAST_ADDR_1;
    mcastaddr.addr[2] = LL_IP4_MULTICAST_ADDR_2;
    mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
    mcastaddr.addr[4] = ip4_addr3(ipaddr);
    mcastaddr.addr[5] = ip4_addr4(ipaddr);
    /* destination Ethernet address is multicast */
    dest = &mcastaddr;
  } 
  else 
  {
    /* 如果是单播目标地IP地址 */
    netif_addr_idx_t i;
    /* 判断目标IP地址是否与主机处于同一子网上,
       如果不是,则修改IP地址,发向网关,请求网关转发,
	   则需要修改IP地址,IP地址为网关的IP地址,目的是为了让网关进行转发。*/
    if (!ip4_addr_netcmp(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) &&
        !ip4_addr_islinklocal(ipaddr))
	{
#if LWIP_AUTOIP
      struct ip_hdr *iphdr = LWIP_ALIGNMENT_CAST(struct ip_hdr *, q->payload);
      if (!ip4_addr_islinklocal(&iphdr->src))
#endif 
      {
#ifdef LWIP_HOOK_ETHARP_GET_GW
        dst_addr = LWIP_HOOK_ETHARP_GET_GW(netif, ipaddr);
        if (dst_addr == NULL)
#endif 
        {
           /* 判断一下网关地址是否有效 */
          if (!ip4_addr_isany_val(*netif_ip4_gw(netif)))
		  {
            /* 发送到默认网关,让网关进行转发 */
            dst_addr = netif_ip4_gw(netif);
            
          }
		  else
		  {
            /* 没有默认网关可用,返回错误 */
            return ERR_RTE;
          }
        }
      }
    }
	
    /* 遍历ARP缓存表 */
    for (i = 0; i < ARP_TABLE_SIZE; i++)
	{
      if ((arp_table[i].state >= ETHARP_STATE_STABLE) &&
#if ETHARP_TABLE_MATCH_NETIF
          (arp_table[i].netif == netif) &&
#endif
          (ip4_addr_cmp(dst_addr, &arp_table[i].ipaddr))) 
	  {
        /* 如果找到目标IP地址对应的表项,直接发送 */
        ETHARP_SET_ADDRHINT(netif, i);
        return etharp_output_to_arp_index(netif, q, i);
      }
    }
    /* 如果没有找到与目标IP地址对应的ARP表项 */
    return etharp_query(netif, dst_addr, q);
  }

  /* 而对于多播、广播数据包,直接能得到对应的MAC地址,可以进行发送*/
  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), dest, ETHTYPE_IP);
}

在上一个函数中,会调用etharp_output_to_arp_index()这个函数,因为是ARP找到了IP地址与MAC地址对应的表项,从而能直接进行发送,除此之外,ARP还需要更新ARP表项,我们知道,LwIP中的ARP表项生存时间是5分钟(300秒),那么在APP表项的生存时间即将到来的时候,ARP需要更新表项,为什么要在发送数据的时候更新呢?因为如果不发送数据,那就没必要更新ARP表项,这样子表项在生存时间到来的时候就会被系统删除,回收ARP表项空间,而一直使用的ARP表项需要是谁更新,更新的方式也有两种:

  1. 如果ARP表项还差15秒就过期了,LwIP会通过广播的方式发送一个ARP请求包,试图得到主机的回应。
  2. 而如果ARP表项还差30秒就过期了,那么LwIP会通过单播的方式向目标主机发送一个请求包并试图得到回应。
    在这种情况下发送ARP请求包的时候,表项的状态会由ETHARP_STATE_STABLE变成ETHARP_STATE_STABLE_REREQUESTING_1,如果目标主机回应了,那就更新ARP缓存表中的表项。
    当然,如果还没那么快到期的话,那就直接调用ethernet_output()函数将数据包传递给网卡进行发送。
#define ARP_MAXAGE                      300

/* 即将到期的时间 */
#define ARP_AGE_REREQUEST_USED_UNICAST   (ARP_MAXAGE - 30)
#define ARP_AGE_REREQUEST_USED_BROADCAST (ARP_MAXAGE - 15)

static err_t
etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, netif_addr_idx_t arp_idx)
{
  /* 如果arp表项即将过期:LwIP会发送一个ARP请求包,但只有当它的状态是ETHARP_STATE_STABLE才能请求*/
  if (arp_table[arp_idx].state == ETHARP_STATE_STABLE) 
  {
    /* 还差15秒到期 */
    if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST) 
	{
      /* 使用广播方式发出请求包 */
      if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) 
	  {
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
      }
    } 
	/* 还差30秒到期 */
	else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST) 
	{
      /* 发出单播请求(持续15秒),以防止不必要的广播 */
      if (etharp_request_dst(netif, &arp_table[arp_idx].ipaddr, &arp_table[arp_idx].ethaddr) == ERR_OK) 
	  {
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
      }
    }
  }

  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), &arp_table[arp_idx].ethaddr, ETHTYPE_IP);
}

而如果在ARP缓存表中没有找到目标IP地址对应的表项,LwIP就会调用etharp_query()函数,那么ARP协议就会创建一个表项,这也是ARP协议的核心处理,对于刚创建的表项,它在初始化网卡信息后会被设置为ETHARP_STATE_PENDING状态,与此同时一个ARP请求包将被广播出去,这个时候的表项是无法发送数据的,只有等待到目标主机回应了一个ARP应答包才能发送数据,那么这些数据在这段时间中将被挂到表项的等待队列上,在ARP表项处于ETHARP_STATE_STABLE状态完成数据的发送。
函数的处理逻辑是很清晰的,首先调用etharp_find_entry()函数在ARP缓存表中查找表项,如果没有找到就尝试创建表项并且返回表项的索引,当然ARP缓存表中可能存在表项,可能为新创建的表项(ETHARP_STATE_EMPTY),也可能为ETHARP_STATE_PENDING或者ETHARP_STATE_STABLE状态。如果是新创建的表项,那么表项肯定没有其他信息,LwIP就会初始化一些信息,如网卡,然后就将表项设置为ETHARP_STATE_PENDING状态。
挂载的这些数据在等待到目标主机产生ARP应答的时候会发送出去,此时的发送就是延时了,所以在没有ARP表项的时候,发送数据会产生延时,在指定等待ARP应答时间内如果等不到目标主机的应答,那么这个表项将被系统回收,同时数据也无法发送出去。

err_t
etharp_query(struct netif *netif, const ip4_addr_t *ipaddr, struct pbuf *q)
{
  struct eth_addr *srcaddr = (struct eth_addr *)netif->hwaddr;
  err_t result = ERR_MEM;
  int is_new_entry = 0;
  s16_t i_err;
  netif_addr_idx_t i;

  /* 检是否为单播地址 */
  if (ip4_addr_isbroadcast(ipaddr, netif) ||
      ip4_addr_ismulticast(ipaddr) ||
      ip4_addr_isany(ipaddr)) {
    return ERR_ARG;
  }

  /* 在ARP缓存中查找表项,如果没有则尝试创建表项 */
  i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif);

  /* 没有发现表项或者没有创建表项成功 */
  if (i_err < 0) {
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not create ARP entry\n"));
    if (q) {
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: packet dropped\n"));
      ETHARP_STATS_INC(etharp.memerr);
    }
    return (err_t)i_err;
  }
  LWIP_ASSERT("type overflow", (size_t)i_err < NETIF_ADDR_IDX_MAX);
  
  //找到对应的表项或者创建表项成功
  i = (netif_addr_idx_t)i_err;

 /* 将新表项标记为待处理 */
  if (arp_table[i].state == ETHARP_STATE_EMPTY)
  {
    is_new_entry = 1;
	//设置表项的状态
    arp_table[i].state = ETHARP_STATE_PENDING;
	
    /* 记录网卡 */
    arp_table[i].netif = netif;
  }

  /* 是否有新的表项 */
  if (is_new_entry || (q == NULL)) 
  {
    /* 发送ARP请求包*/
    result = etharp_request(netif, ipaddr);
    if (result != ERR_OK)
	{

    }
    if (q == NULL) {
      return result;
    }
  }

  LWIP_ASSERT("q != NULL", q != NULL);
  /* 表项状态是否稳定 */
  if (arp_table[i].state >= ETHARP_STATE_STABLE)
  {
    ETHARP_SET_ADDRHINT(netif, i);
	
    /* 发送数据包 */
    result = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);
	
    
  } 
  else if (arp_table[i].state == ETHARP_STATE_PENDING)
  {
    /* 如果表项是ETHARP_STATE_PENDING状态 */
	/* 将给数据包'q'排队 */
    struct pbuf *p;
    int copy_needed = 0;
	
	/* 如果q包含必须拷贝的pbuf,请将整个链复制到一个新的PBUF_RAM */
    p = q;
    while (p) 
	{
      LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == 0));
      if (PBUF_NEEDS_COPY(p)) 
	  {
	    //需要拷贝
        copy_needed = 1;
        break;
      }
      p = p->next;
    }
    if (copy_needed) 
	{
      /* 将整个数据包复制到新的pbuf中 */
      p = pbuf_clone(PBUF_LINK, PBUF_RAM, q);
    }
	else 
	{
      /* 引用旧的pbuf就足够了 */
      p = q;
      pbuf_ref(p);
    }
    /* packet could be taken over? */
    if (p != NULL) {
      
#if ARP_QUEUEING		/* 如果使用队列 */
      struct etharp_q_entry *new_entry;
	  
      /* 分配一个新的arp队列表项 */
      new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
	  
      if (new_entry != NULL) 
	  {
        unsigned int qlen = 0;
        new_entry->next = 0;
        new_entry->p = p;
        if (arp_table[i].q != NULL) 
		{
          /* 队列已经存在,将新数据包插入队列后面 */
          struct etharp_q_entry *r;
          r = arp_table[i].q;
          qlen++;
          while (r->next != NULL)
		  {
            r = r->next;
            qlen++;
          }
          r->next = new_entry;
        }
		else
		{
          /* 队列不存在,数据包就是队列的第一个节点 */
          arp_table[i].q = new_entry;
        }
#if ARP_QUEUE_LEN
        if (qlen >= ARP_QUEUE_LEN) {
          struct etharp_q_entry *old;
          old = arp_table[i].q;
          arp_table[i].q = arp_table[i].q->next;
          pbuf_free(old->p);
          memp_free(MEMP_ARP_QUEUE, old);
        }
#endif
      
        result = ERR_OK;
      }
	  else 
	  {
        /* 申请内存失败 */
        pbuf_free(p);
        result = ERR_MEM;
      }
#else 
      /* 如果只是挂载单个数据包,那么始终只为每个ARP请求排队一个数据包,就需要释放先前排队的数据包*/
      if (arp_table[i].q != NULL)
	  {
        pbuf_free(arp_table[i].q);
      }
      arp_table[i].q = p;
      result = ERR_OK;
#endif
    } else {
      ETHARP_STATS_INC(etharp.memerr);
      result = ERR_MEM;
    }
  }
  return result;
}

总得来说,整个ARP的工作流程是很清晰的。
欢迎关注杰杰个人公众号,更多干货等着你!

你可能感兴趣的:(LwIP,杰杰开源社区)