IP protocol data output of lwip source code analysis

One, guide

lwip provides an interface to the transport layer so that the transport layer can pass data to the ip layer. This chapter introduces how this interface function can encapsulate the data of the transport layer into an ip datagram and send the datagram.

Second, source code analysis

The transport layer, taking the TCP protocol as an example, calls ip_output_if() to pass the tcp datagram to the ip layer, and ip_output_if() selects different ip version sending functions according to the destination ip.

1, ipv4 output

If the destination ip is ipv4, use the following function to send.

This function encapsulates the upper layer datagram into ip data, fills the ip header, and calls the sending function of the network interface to send the data.

//通过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); //调用网络接口的发送函数(链路层)
}

In the above code, it should be noted that when the tcp layer retransmits the message, the data passed is a previously encapsulated ip data, so it is enough to directly call the interface sending function. When dest is null, it means that this is a retransmitted message.

2. ip data is sent in fragments

The packet size sent by the ip layer is limited by the maximum data frame of the interface. If the sent ip data is greater than the maximum data frame length (mtu) of the interface, the ip data needs to be sent in fragments.

In lwip, ip4_frag() completes this function.

Before the beginning of the code, you need to know a new friend, a structure pbuf_custom_refthat two members, pc is pbuf its own, original is it references pbuf, pc original point of payload data area. This structure is used to avoid copying the input pbuf chain during sharding.

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

The fragmentation process is shown in the figure below. What
Insert picture description here
this function does is shown in the figure above. Cut the input pbuf chain into equal-length pieces, and then send the pieces through the interface's sending function neitif->output().

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 output

Guess you like

Origin blog.csdn.net/weixin_44821644/article/details/111658238