lwip源码分析 之 IP协议 数据输出

一,导读

lwip提供接口给传输层,使传输层能将数据传递给ip层。本章介绍该接口函数如何实现将传输层的数据封装成ip数据报,并将数据报发送出去。

二,源码分析

运输层,以TCP协议为例,调用ip_output_if()将tcp数据报传递给ip层,ip_output_if()会根据目的ip选择不同ip版本发送函数。

1,ipv4输出

若目的ip是ipv4,则使用以下函数发送。

该函数将上层数据报封装成ip数据,并填充ip首部,调用网络接口的发送函数发送数据。

//通过netif输出ip数据报 pbuf
err_t ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
             u8_t ttl, u8_t tos,
             u8_t proto, struct netif *netif)
{
    
    
  struct ip_hdr *iphdr;
  ip4_addr_t dest_addr;
  u32_t chk_sum = 0;

  //目的ip地址不在pbuf中
  if (dest != LWIP_IP_HDRINCL) {
    
    
    u16_t ip_hlen = IP_HLEN;  //ip首部长度20字节

    //pbuf的payload前移20字节作为ip首部
    if (pbuf_header(p, IP_HLEN)) {
    
    
	  //移动失败 返回
      return ERR_BUF;
     }

    iphdr = (struct ip_hdr *)p->payload;  //payload指向ip首部,接下来要填充ip首部

    IPH_TTL_SET(iphdr, ttl);  //设置ip存活时间
    IPH_PROTO_SET(iphdr, proto);  //协议类型

    chk_sum += PP_NTOHS(proto | (ttl << 8));  //校验和

    ip4_addr_copy(iphdr->dest, *dest);  //填充目的ip
	//更新校验和
    chk_sum += ip4_addr_get_u32(&iphdr->dest) & 0xFFFF;
    chk_sum += ip4_addr_get_u32(&iphdr->dest) >> 16;

    IPH_VHL_SET(iphdr, 4, ip_hlen / 4); //填充版本号和首部长度
    IPH_TOS_SET(iphdr, tos);  //设置服务类型

    chk_sum += PP_NTOHS(tos | (iphdr->_v_hl << 8));

    IPH_LEN_SET(iphdr, lwip_htons(p->tot_len));

    chk_sum += iphdr->_len;

    IPH_OFFSET_SET(iphdr, 0); //设置第一个分片的片偏移量0
    IPH_ID_SET(iphdr, lwip_htons(ip_id)); //填充标志位

    chk_sum += iphdr->_id;

    ++ip_id;  //ip_id更新,ip_id用于填充ip数据报的id字段,每个ip数据报不一样

    //填充源ip地址
    if (src == NULL) {
    
    
      //若src是null,使用0.0.0.0 广播地址?
      ip4_addr_copy(iphdr->src, *IP4_ADDR_ANY4);
    } else {
    
    
      //否则复制输入的源ip
      ip4_addr_copy(iphdr->src, *src);
    }

    //计算校验和
    chk_sum += ip4_addr_get_u32(&iphdr->src) & 0xFFFF;
    chk_sum += ip4_addr_get_u32(&iphdr->src) >> 16;
    chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF);
    chk_sum = (chk_sum >> 16) + chk_sum;
    chk_sum = ~chk_sum;
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
    
    
      iphdr->_chksum = (u16_t)chk_sum; /* network order */
    }

    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
    
    
      IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));
    }
  } else {
    
    
    //ip首部已经在pbuf中
    iphdr = (struct ip_hdr *)p->payload;
    ip4_addr_copy(dest_addr, iphdr->dest);
    dest = &dest_addr;	//获取目的ip
  
  }

  //若发送的数据长度大于网络接口支持的最大长度,则进行ip分片
  if (netif->mtu && (p->tot_len > netif->mtu)) {
    
    
    return ip4_frag(p, netif, dest);
  }

  return netif->output(netif, p, dest); //调用网络接口的发送函数(链路层)
}

上述代码中需要注意的是当tcp层进行报文重传时,传递下来的数据是一个之前封装完成的ip数据,所以直接调用接口发送函数就行。dest为null时,说明这是一个重传的报文。

2,ip数据分片发送

ip层发送的分组大小是受接口的最大数据帧限制的。若发送的ip数据大于接口最大数据帧长度(mtu),需要将ip数据进行分片发送。

在lwip中,ip4_frag()完成该功能。

在开始代码前,需要先认识一位新朋友,结构体 pbuf_custom_ref 它两个成员,pc是它本身的pbuf,original是它所引用的pbuf,pc的payload指向original的数据区。使用这个结构体是为了避免在分片时对输入的pbuf链进行复制。

struct pbuf_custom_ref {
    
    
  struct pbuf_custom pc;  //本地的pbuf
  struct pbuf *original;  //引用的pbuf
};

分片过程如下图
在这里插入图片描述
该函数所做的就是上图所示。把输入的pbuf链中切割成等长的分片,再将分片通过接口的发送函数neitif->output()发送出去。

扫描二维码关注公众号,回复: 12490054 查看本文章
err_t ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest)
{
    
    
  struct pbuf *rambuf;  //分片的第一个pbuf
#if !LWIP_NETIF_TX_SINGLE_PBUF
  struct pbuf *newpbuf;
  u16_t newpbuflen = 0;
  u16_t left_to_copy; //一个分片中需要复制的数据
#endif
  struct ip_hdr *original_iphdr;  //第一个分片ip首部
  struct ip_hdr *iphdr;	
  const u16_t nfb = (netif->mtu - IP_HLEN) / 8; //分片中允许最大数据量
  u16_t left, fragsize; //left:剩下未复制的数据,fragsize:分片大小
  u16_t ofo;  //分片偏移量
  int last; //最后一个分片标志
  u16_t poff = IP_HLEN; //数据在pbuf中的偏移(ip数据第一个pbuf的poff为20,其余为0)
  u16_t tmp;  //offset偏移字段(3位标志+13位分片偏移量)

  original_iphdr = (struct ip_hdr *)p->payload; //指向当前ip数据首部
  iphdr = original_iphdr;
  
  tmp = lwip_ntohs(IPH_OFFSET(iphdr));  //暂存第一个分片offset字段 
  ofo = tmp & IP_OFFMASK; //得到分片偏移量 应该是0?
 
  left = p->tot_len - IP_HLEN;  //待发送的数据长度

  //将数据分片并发送
  while (left) {
    
    

    fragsize = LWIP_MIN(left, nfb * 8); //计算当前分片有效数据大小

    //申请一个ram类型pbuf,其包含链路层的首部和ip首部,作为分片的第一个pbuf
    rambuf = pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM);  
    if (rambuf == NULL) {
    
    
      goto memerr;
    }
   
    SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);  //将源ip的首部复制到rambuf的payload,ranbuf是一个链路层的数据包了
    iphdr = (struct ip_hdr *)rambuf->payload; //现在修改iphdr,使其指向第一个分片首部

    left_to_copy = fragsize;	//需要复制的数据大小等于当前分片有效数据的大小

    //使用pbuf链填充ip分片,该pbuf链引用了原pbuf
    while (left_to_copy) {
    
    
      struct pbuf_custom_ref *pcr;  //此类型pbuf引用其他pbuf
      u16_t plen = p->len - poff; //当复制第一个pbuf时,plen=p->len-20,否则plen=p->len
      newpbuflen = LWIP_MIN(left_to_copy, plen);
   
      if (!newpbuflen) {
    
      //plen=0,要复制的pbuf的数据为0,跳过它
        poff = 0;
        p = p->next;
        continue;
      }
      //申请一个ref的pbuf
      pcr = ip_frag_alloc_pbuf_custom_ref();
      if (pcr == NULL) {
    
    
        pbuf_free(rambuf);
        goto memerr;
      }
   
      //将p中newpbuflen长度的数据复制到pcr->pc的pbuf(pcr->pc的pbuf的payload指向ip数据)
      newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc,
        (u8_t*)p->payload + poff, newpbuflen);
      //此时,newbuf指向pcr->pc,newbuf的payload指向p的一段数据
      if (newpbuf == NULL) {
    
    
        ip_frag_free_pbuf_custom_ref(pcr);
        pbuf_free(rambuf);
        goto memerr;
      }
      pbuf_ref(p);  //p的引用次数++
      pcr->original = p;  //pcr的源pbuf指向p
      pcr->pc.custom_free_function = ipfrag_free_pbuf_custom; //初始化pbuf_custom_ref释放函数

      pbuf_cat(rambuf, newpbuf);  //将newpbuf插入rambuf链表尾
      left_to_copy -= newpbuflen; //更新接下来需要复制的数据量
      if (left_to_copy) {
    
     //若还有数据需要复制
        poff = 0; //p的偏移应该为0,因为除了第一个p的payload中包含首部,其余的pbuf的payload中全是数据(看图)
        p = p->next;  //接着复制下一个pbuf
      }
    }
    //填充完一个分片,此时pbuf的偏移为newpbuflen。
    poff += newpbuflen;
	//是否是最后最后一个分片
    last = (left <= netif->mtu - IP_HLEN);
	//设置offset字段
    tmp = (IP_OFFMASK & (ofo));
    if (!last) {
    
    
      tmp = tmp | IP_MF;  //不是最后一个分片,设置标志位
    }
    //填充当前分片的首部
    IPH_OFFSET_SET(iphdr, lwip_htons(tmp));
    IPH_LEN_SET(iphdr, lwip_htons(fragsize + IP_HLEN));
    IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
    
    
      IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); //由首部计算校验和
    }
#endif /* CHECKSUM_GEN_IP */

    //网络接口输出分片
    netif->output(netif, rambuf, dest);
    
    pbuf_free(rambuf);  //释放pbuf
    left -= fragsize; //更新剩下的未发送的数据
    ofo += nfb; //更新分片的片偏移(分片偏移量增加一个分片的长度)
  }
  return ERR_OK;
memerr:
  MIB2_STATS_INC(mib2.ipfragfails);
  return ERR_MEM;
}

3,ipv6输出

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/111658238