LWIP协议栈---ARP协议(3)ARP数据包发送过程

ARP数据包发送过程

先看一些指向流程图,ARP数据包发送的过程:

image


主要看看右边这块内容,ip_output() 调用etharp_output() 函数发送出ip层的内容。而该函数又根据数据包是否是多播,广播,单播,分别调用不同的函数处理,最终是调用netif->linkoutput完成传输。

(1)广播与多播包发送:

​ 当发送数据包为多播或广播数据包时,etharp_output()会构造一个特殊的MAC地址,同时把ip地址和MAC地址传递给etharp_send_ip函数使用。

1.etharp_output()

先看代码。

err_t etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr)
{
  struct eth_addr *dest;    
  struct eth_addr mcastaddr;
  ip_addr_t *dst_addr = ipaddr;   //初始化目的IP地址。


  /* 调整PBUF 中payload 指针,使其指向以太网帧头部,失败则返回 */
  if (pbuf_header(q, sizeof(struct eth_hdr)) != 0) {
    return ERR_BUF;
  }


  /* 判断是否是广播地址? */
  if (ip_addr_isbroadcast(ipaddr, netif)) {
    /* 若是,则dest 指向广播地址的MAC */
    dest = (struct eth_addr *)&ethbroadcast;
  /*是多播地址吗? */
  } else if (ip_addr_ismulticast(ipaddr)) {
    /* 若是,则构造多播地址.*/
    mcastaddr.addr[0] = LL_MULTICAST_ADDR_0;
    mcastaddr.addr[1] = LL_MULTICAST_ADDR_1;
    mcastaddr.addr[2] = LL_MULTICAST_ADDR_2;
    mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
    mcastaddr.addr[4] = ip4_addr3(ipaddr);
    mcastaddr.addr[5] = ip4_addr4(ipaddr);
    /* dest 指向多播地址 */
    dest = &mcastaddr;
  
  } else {  //如果是单播地址
    s8_t i;
    /* 判断目的IP地址是否为本地的子网上,若不在,则修改ipaddr */
    if (!ip_addr_netcmp(ipaddr, &(netif->ip_addr), &(netif->netmask)) &&
        !ip_addr_islinklocal(ipaddr)) {
      {                                    //将数据包发送到网关上,由网关转发
        if (!ip_addr_isany(&netif->gw)) {  //若网关配置了
          dst_addr = &(netif->gw);         //更改目标ip地址为网关,由网关转发
        } else {                           //若网关未配置
          return ERR_RTE;
        }
      }
    }
    //对于 单播包,调用query函数 查询其mac 并发送数据包。
    return etharp_query(netif, dst_addr, q);
  }

  /*对于多播或广播,由于得到了MAC地址,所以直接发送*/
  return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr), dest);
}

我们整理一下执行的流程:

先判断数据包是单播,多播,还是广播。

  • 若是广播包,则将MAC地址配置为ff-ff-ff-ff-ff-ff-ff,然后发送

  • 若是多播包,则计算MAC地址,然后发送出去。

  • 若是单播包,则于本地IP进行比较,是否在同一个局域网内部,若不是,要将目标地址改为网关地址,再发送etharp_query(),查找MAC地址,并发送出去。

2. etharp_send_ip() 较为简单,就不再详解了。

static err_t etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst)
{
  struct eth_hdr *ethhdr = (struct eth_hdr *)p->payload; //以太网帧头部

  ETHADDR32_COPY(&ethhdr->dest, dst);
  ETHADDR16_COPY(&ethhdr->src, src);
  ethhdr->type = PP_HTONS(ETHTYPE_IP);
  return netif->linkoutput(netif, p);  //发送出去
}

(2)单播包的发送

接下来就是本篇最后一个函数的讲解了!

etharp_query()

​ 该函数功能是向指定的IP地址处发送一个IP数据包或一个ARP请求。

//q :指向以太网数据帧的pbuf.


err_t etharp_query(struct netif *netif, ip_addr_t *ipaddr, struct pbuf *q)
{
  struct eth_addr * srcaddr = (struct eth_addr *)netif->hwaddr;
  err_t result = ERR_MEM;
  s8_t i; /* ARP entry index */


  /*调用函数 查找或创建一个ARP表项 */
  i = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD);

  /*若i<0 ,查询失败 */
  if (i < 0) {
    return (err_t)i;
  }

  /* 若状态为empty 说明是刚创建的,改变状态为stable */
  if (arp_table[i].state == ETHARP_STATE_EMPTY) {
    arp_table[i].state = ETHARP_STATE_PENDING;
  }

  /* 如果表项为pending 状态,或者数据包为空 */
  if ((arp_table[i].state == ETHARP_STATE_PENDING) || (q == NULL)) {
    result = etharp_request(netif, ipaddr);  //发送ARP请求包。
    if (result != ERR_OK) {
    }
    if (q == NULL) {
      return result;
    }
  }
 //如果 数据包不为空,根据表项的状态,则进行数据包的发送,或者将数据包挂接在缓冲队列上。
  if (arp_table[i].state >= ETHARP_STATE_STABLE) {
    result = etharp_send_ip(netif, q, srcaddr, &(arp_table[i].ethaddr));
      
  } else if (arp_table[i].state == ETHARP_STATE_PENDING) { //若是 pending状态,则 挂接数据包

    struct pbuf *p;
    int copy_needed = 0;  //是否需要重新拷贝数据包?
    p = q;
    while (p) {
   
      if(p->type != PBUF_ROM) {  //是否需要重新拷贝数据包,数据包由 PBUF_ROM类型的PBUF组成,才不需要拷贝
        copy_needed = 1;
        break;
      }
      p = p->next;
    }
    if(copy_needed) {
      p = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
      if(p != NULL) {
        if (pbuf_copy(p, q) != ERR_OK) { //执行拷贝动作
          pbuf_free(p);
          p = NULL;
        }
      }
    } else {        //如果不需要拷贝
     
      p = q;
      pbuf_ref(p);  //增加pbuf 的ret的值。
    }
  
      
      //到这里,p指向了我们需要挂接的数据包,下面执行挂接操作
    if (p != NULL) {
      struct etharp_q_entry *new_entry;
      /* allocate a new arp queue entry */
      new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
      if (new_entry != NULL) {  //申请成功,进行挂接
        new_entry->next = 0;
        new_entry->p = p;
        if(arp_table[i].q != NULL) {  //若缓冲队列不为空,
         
          struct etharp_q_entry *r;
          r = arp_table[i].q;
          while (r->next != NULL) { //找到最后一个缓冲包结构
            r = r->next;
          }
          r->next = new_entry;  //将新的数据包挂接在队列尾部
        } else {                 //缓冲队列为空
          /*直接挂接在缓冲队列首部 */
          arp_table[i].q = new_entry;
        }
        result = ERR_OK;
      } else {  //etharp_q_entry 结构申请失败,则释放数据包空间
        pbuf_free(p);
        result = ERR_MEM;
      }
    }
  }
  return result;
}

​ 具体的执行流程如下:

  1. 首先调用etharp_find_entry()函数返回一个arp表项,该表项可能是pending 或stable状态,也有可能是新申请的empty状态,然后更改为pending 状态。

  2. 判断要发送的数据包是否为空,或者 ARP表项 是否为pending 状态,只要符合一个成立,就发送一个arp请求包。

  3. 如果待发送的数据包不为空,要根据ARP表项的状态进行不同的操作:

    • 若是stable状态,则调用 etharp_send_ip()发送出去。

    • 若是pending状态,则将数据包 挂接到 表项的待发送数据链表上,当内核收到arp应答时,会改变为stable状态,并把数据包发送出去。

  4. 接下来将数据包挂接到发送队列上:

    • 要判断pbuf的类型,对于PBUF_REF,PBUF_POOL,PBUF_RAM类型的数据包,不能直接挂接在发送链表上,因为这些数据被挂接在发送队列中不会立刻被发送,在等待期间,数据可能被上层更改。

    • 需要将数据拷贝到新的pbuf中,然后将新的PBUF挂接到缓冲队列。

posted @ 2023-02-26 18:00  雨落城  阅读(878)  评论(0)    收藏  举报