IP protocol data input of lwip source code analysis (2)

A guide

In the previous chapter ip data input , the input ip group will be judged whether it is a fragmented group. If it is a fragmented ip data, the fragments need to be temporarily stored, and after receiving all the fragments, the fragments are reassembled into a complete ip data and passed to the transport layer.

This chapter introduces how lwip implements the reloading of fragments.

Second, reinstall the data structure

Since the time of ip packet reaching the destination point in the network transmission process is uncertain, the latter packet may reach the destination point earlier than the previous packet.

As shown in the figure, fragments A, B, and C represent a complete ip datagram, their destination addresses are all 192.168.1.1, the unit of chip offset is word (32bit), the unit of total length is byte, and finally The MF flag indicates whether there is data afterwards.

The fragment offset of the A fragment is 0, indicating that it is the first fragment of ip data, and the data length is 1420-20=1400. (20-byte ip header). The offset of the B fragment is 1400/8=175; the MF of the C fragment is 0, indicating that it is the last fragment. Note that the arrival time of the ABC three-segment to the destination station is uncertain.

Insert picture description here
The figure is only for convenience, and the specific ip header is not in the position shown in the figure.

To this end, we need to temporarily store the received packets, wait for all packets to be received, and then pass the data to the upper layer. In lwip, there is a special structure responsible for caching these packets.

Use the schematic diagram to understand the meaning of the structure members.

//重装数据结构体
struct ip_reassdata {
    
    
  struct ip_reassdata *next;
  struct pbuf *p; //ip数据报pbuf链
  struct ip_hdr iphdr;  //ip数据报首部(即第一个分组的ip首部)
  u16_t datagram_len; //完整ip数据报大小
  u8_t flags; //标志是否最后一个分组
  u8_t timer; //超时间隔
};

An ip_reassdata chain is maintained in lwip, and each ip_reassdata structure represents an ip datagram that is being reinstalled. When a packet is received, the pbuf of the packet will be connected to the pbuf chain in the corresponding ip_reassdata. When the ip datagram is completely received, it will be submitted to the upper layer.
Insert picture description here
In the above figure, the 8-byte data in the header of the ip fragment is modified into a structure ip_reass_helper, which is used to connect pbuf and determine whether the data is complete. The definition is as follows (detailed analysis later):

PACK_STRUCT_BEGIN
struct ip_reass_helper {
    
    
  PACK_STRUCT_FIELD(struct pbuf *next_pbuf);  
  PACK_STRUCT_FIELD(u16_t start); //该分组数据起始序号
  PACK_STRUCT_FIELD(u16_t end); //数据结束序号
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

Three, code analysis

1. Fragmentation processing

This function first checks whether the fragment size exceeds the limit, finds the corresponding datagram from the reassembly datagram chain, inserts the fragment into the datagram, and decides whether to reassemble the pbuf into a complete ip datagram according to the insertion result and submit it to the upper layer. When understanding fragments to reassemble pbuf, use the schematic diagram above to understand.

//将输入的分组插入对应reassdata的pbuf链,若重装完成则返回完整数据的pbuf
struct pbuf *
ip4_reass(struct pbuf *p)
{
    
    
  struct pbuf *r; 
  struct ip_hdr *fraghdr; //输入分片的首部
  struct ip_reassdata *ipr; //分片对应的重装数据报
  struct ip_reass_helper *iprh; //pbuf中被强制转换的8个字节,用于指向下一个pbuf与分组起始和结束
  u16_t offset, len, clen;  //offset:片偏移;len:分片长度;clen:分片的pbuf数量
  int valid;  //分片插入链表的结果
  int is_last;  //最后一个分片
  
  fraghdr = (struct ip_hdr*)p->payload; //获取分组首部

  //ip首部不正常
  if ((IPH_HL(fraghdr) * 4) != IP_HLEN) {
    
    
    goto nullreturn;
  }
  //得到分组的片偏移量(相对于0的偏移量)
  offset = (lwip_ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;
  len = lwip_ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4; //获取分组的数据长度

  clen = pbuf_clen(p);  //获取输入分组的pbuf数量
  //若将输入报文的pbuf加上reassdatagrams链表所有的pbuf的数量超出限制
  if ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) {
    
    
#if IP_REASS_FREE_OLDEST
    //则释放掉最老的reassdatagrams,并再次检查长度是否超出
    if (!ip_reass_remove_oldest_datagram(fraghdr, clen) ||
        ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS))
#endif /* IP_REASS_FREE_OLDEST */
    {
    
    
      //长度还是超出,返回错误
      /* @todo: send ICMP time exceeded here? */
      /* drop this pbuf */
      goto nullreturn;
    }
  }
  //遍历分组重装链表,找到该分组对应的reassdatagrams
  for (ipr = reassdatagrams; ipr != NULL; ipr = ipr->next) {
    
    
    //找到对应的reassdatagrams,退出循环 此时ipr不为空
    if (IP_ADDRESSES_AND_ID_MATCH(&ipr->iphdr, fraghdr)) {
    
    
      break;
    }
  }
  if (ipr == NULL) {
    
    
    //未找到其对应的ip数据报,新建一个数据报
    ipr = ip_reass_enqueue_new_datagram(fraghdr, clen);
    if (ipr == NULL) {
    
    
      goto nullreturn;
    }
  } else {
    
    
    //找到对应的数据报,若输入分组的片偏移为0,则是数据报的第一个分组
    if (((lwip_ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) == 0) &&
      ((lwip_ntohs(IPH_OFFSET(&ipr->iphdr)) & IP_OFFMASK) != 0)) {
    
    
      //复制输入分组的ip首部到ipr的iphdr成员,由它的首部作为完整数据报的ip首部
      SMEMCPY(&ipr->iphdr, fraghdr, IP_HLEN); 
    }
  }

  //到此,我们为输入分组找到了对应的数据报
  is_last = (IPH_OFFSET(fraghdr) & PP_NTOHS(IP_MF)) == 0; //是否是最后一个分组.MF=0
  if (is_last) {
    
    
    u16_t datagram_len = (u16_t)(offset + len); //计算数据报长度
    //u16 类型内存溢出
    if ((datagram_len < offset) || (datagram_len > (0xFFFF - IP_HLEN))) {
    
    
     goto nullreturn;
    }
  }
  /* @todo: trim pbufs if fragments are overlapping */
  //将分片插入链表并检查重装是否完成
  valid = ip_reass_chain_frag_into_datagram_and_validate(ipr, p, is_last);

  //插入失败
  if (valid == IP_REASS_VALIDATE_PBUF_DROPPED) {
    
    
    goto nullreturn;  
  }
  //到此,分片的插入完成
  //ip_reass_pbufcount增加clen个pbuf
  ip_reass_pbufcount = (u16_t)(ip_reass_pbufcount + clen);
  if (is_last) {
    
    
    //最后一个分片已经到来
    u16_t datagram_len = (u16_t)(offset + len); //计算数据报长度
    ipr->datagram_len = datagram_len;
    ipr->flags |= IP_REASS_FLAG_LASTFRAG; //设置数据报标志
  }

  //若数据报已完成重装,构建一个存放完整ip数据报的pbuf链(填充首部,连接pbuf),并将ipr从reassdatagrams链表删除
  if (valid == IP_REASS_VALIDATE_TELEGRAM_FINISHED) {
    
    
    struct ip_reassdata *ipr_prev;
    ipr->datagram_len += IP_HLEN; //设置数据报总长度 数据长度+首部长度20字节

    r = ((struct ip_reass_helper*)ipr->p->payload)->next_pbuf;  //保存第二个pbuf(后面有用)

    fraghdr = (struct ip_hdr*)(ipr->p->payload);  //fraghdr现在是第一个pbuf的payload,指向ip数据报的首部(我们需要填充这个首部)
    SMEMCPY(fraghdr, &ipr->iphdr, IP_HLEN); //结合示意图。用之前保存在iphadr中的首部数据填充pbuf首部
    IPH_LEN_SET(fraghdr, lwip_htons(ipr->datagram_len));  //设置数据报的长度
    IPH_OFFSET_SET(fraghdr, 0);   //设置片偏移为0
    IPH_CHKSUM_SET(fraghdr, 0);
    /* @todo: do we need to set/calculate the correct checksum? */
#if CHECKSUM_GEN_IP
    IF__NETIF_CHECKSUM_ENABLED(ip_current_input_netif(), NETIF_CHECKSUM_GEN_IP) {
    
    
      IPH_CHKSUM_SET(fraghdr, inet_chksum(fraghdr, IP_HLEN)); //设置校验和
    }
#endif /* CHECKSUM_GEN_IP */

    p = ipr->p; //p是数据报第一个pbuf

    //将同一数据报中分片的pbuf链连接连接在一起
    while (r != NULL) {
    
    
      iprh = (struct ip_reass_helper*)r->payload; //iprh->next_pbuf指向下一个分片

      pbuf_header(r, -IP_HLEN); //将下一个分片第一个pbuf的payload后移,指向数据区,隐藏掉ip首部(我们只需要一个ip首部)
      pbuf_cat(p, r); //将分片的pbuf链 连接到p上
      r = iprh->next_pbuf;  //获取下一个分片的pbuf链
    }

    //确定ipr_prev,并删除ipr
    if (ipr == reassdatagrams) {
    
    
      ipr_prev = NULL;
    } else {
    
    
      for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
    
    
        if (ipr_prev->next == ipr) {
    
    
          break;
        }
      }
    }

    ip_reass_dequeue_datagram(ipr, ipr_prev); //从链表中删除ipr

    ip_reass_pbufcount -= pbuf_clen(p); //重装pbuf数量减少

    MIB2_STATS_INC(mib2.ipreasmoks);

    return p;
  }
  /* the datagram is not (yet?) reassembled completely */
  LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass_pbufcount: %d out\n", ip_reass_pbufcount));
  return NULL;

nullreturn:
  LWIP_DEBUGF(IP_REASS_DEBUG,("ip4_reass: nullreturn\n"));
  IPFRAG_STATS_INC(ip_frag.drop);
  pbuf_free(p);
  return NULL;
}

2. Group insert

The implementation of inserting packets into datagrams mentioned above is also an important function. This function inserts the fragment into the linked list and checks whether the data in the linked list is complete. If the data is complete, it returns 1. The complete data will be handed over to the upper layer in ip4_reass().

The validvalue indicates whether the data is complete. Valid is initially 1. In the process of inserting the packet into the linked list, the valid is modified by checking whether the data of the packet is continuous. If there is a discontinuity in the packet, then the datagram must be incomplete, and the valid is 0. Only after traversing the linked list Only when valid is 1 and the last packet is received, the datagram is complete.

In this function, pay attention to ip_reass_helperthe role of this structure. The start and end of the structure are the key to judging whether the data is continuous.

//将分片插入分组链
static int
ip_reass_chain_frag_into_datagram_and_validate(struct ip_reassdata *ipr, struct pbuf *new_p, int is_last)
{
    
    
  struct ip_reass_helper *iprh, *iprh_tmp, *iprh_prev=NULL;
  struct pbuf *q;
  u16_t offset, len;  //offset:分片片偏移;len:分片数据长度
  struct ip_hdr *fraghdr;
  int valid = 1;  //数据连续标志。初始化valid为1,当出现前后分组数据不连续时,valid为0,说明数据报不完整

  fraghdr = (struct ip_hdr*)new_p->payload; //获取当前分片的首部
  len = lwip_ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4; //计算len:分片的数据长度
  offset = (lwip_ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;  //计算分片的片偏移

  iprh = (struct ip_reass_helper*)new_p->payload; //将分片首部的8个字节的数据转换成ip_reass_helper
  iprh->next_pbuf = NULL;
  iprh->start = offset; //填充iprh的开始序号
  iprh->end = offset + len; //结束序号

  //遍历分组链表中的分组的第一个pbuf的ip_reass_helper结构体(序号从小到大)
  //并在合适的位置插入new_pbuf
  for (q = ipr->p; q != NULL;) {
    
    
    //分组的ip_reass_helper,重要!用于确定分组插入位置
    iprh_tmp = (struct ip_reass_helper*)q->payload; 
    if (iprh->start < iprh_tmp->start) {
    
     
      //输入分组start小于当前分组,可插入
      iprh->next_pbuf = q;
      if (iprh_prev != NULL) {
    
     
        //前面的pbuf与当前的pbuf数据内容出现覆盖
        if ((iprh->start < iprh_prev->end) || (iprh->end > iprh_tmp->start)) {
    
    
          goto freepbuf;
        }
        iprh_prev->next_pbuf = new_p;

        //前一个pbuf的结尾不等当前pbuf的结尾序号,说明两个pbuf数据不是连续的
        if (iprh_prev->end != iprh->start) {
    
    
          valid = 0;
        }
      } else {
    
      //当前分片偏移最小
        //若当前分片出现覆盖,退出
        if (iprh->end > iprh_tmp->start) {
    
    
          goto freepbuf;
        }
        ipr->p = new_p; //插入链表的最前
      }
      break;
    } else if (iprh->start == iprh_tmp->start) {
    
      //接收到重复的分组
      goto freepbuf;
    } else if (iprh->start < iprh_tmp->end) {
    
       //分片的内容与iprh_temp的内容重叠,退出

      goto freepbuf;
    } else {
    
      //iprh->start > iprh_tmp->end
      //检查数据报是否连续
      if (iprh_prev != NULL) {
    
    
        if (iprh_prev->end != iprh_tmp->start) {
    
    
          valid = 0;  //不连续
        }
      }
    }
    q = iprh_tmp->next_pbuf;  //检查下一个分片
    iprh_prev = iprh_tmp;
  }

  if (q == NULL) {
    
    
    //若q为null,输入分组序号最高 则说明分组应该在链表的最末
    if (iprh_prev != NULL) {
    
    
      iprh_prev->next_pbuf = new_p; //插入链尾
      //判断是否连续
      if (iprh_prev->end != iprh->start) {
    
    
        valid = 0;  
      }
    } else {
    
    
      ipr->p = new_p; //到这里只能是链表为null
    }
  }

  //若最后一个分组已经收到
  if (is_last || ((ipr->flags & IP_REASS_FLAG_LASTFRAG) != 0)) {
    
    
    //且输入分片与它前面的分片的数据连续无断点
    if (valid) {
    
    
      //检查数据报首部是否为null或者偏移是不是0
      if ((ipr->p == NULL) || (((struct ip_reass_helper*)ipr->p->payload)->start != 0)) {
    
    
        valid = 0;  //以上情况说不对
      } else {
    
    
        //检查输入分组之后的分组是否也是连续的
        iprh_prev = iprh; 
        q = iprh->next_pbuf;  //从输入分组之后开始遍历检查
        while (q != NULL) {
    
    
          iprh = (struct ip_reass_helper*)q->payload;
          if (iprh_prev->end != iprh->start) {
    
    
            //出现不连续的数据
            valid = 0;  
            break;  //跳出循环。不用再检查了。
          }
          iprh_prev = iprh;
          q = iprh->next_pbuf;
        }

        if (valid) {
    
    
          LWIP_ASSERT("sanity check", ipr->p != NULL);
          LWIP_ASSERT("sanity check",
            ((struct ip_reass_helper*)ipr->p->payload) != iprh);
          LWIP_ASSERT("validate_datagram:next_pbuf!=NULL",
            iprh->next_pbuf == NULL);
        }
      }
    }
    //返回1说明数据报重装完成
    return valid ? IP_REASS_VALIDATE_TELEGRAM_FINISHED : IP_REASS_VALIDATE_PBUF_QUEUED;
  }
  /* If we come here, not all fragments were received, yet! */
  return IP_REASS_VALIDATE_PBUF_QUEUED; /* not yet valid! */
#if IP_REASS_CHECK_OVERLAP
freepbuf:
  ip_reass_pbufcount -= pbuf_clen(new_p);
  pbuf_free(new_p);
  return IP_REASS_VALIDATE_PBUF_DROPPED;
#endif /* IP_REASS_CHECK_OVERLAP */
}

Insert picture description here

Guess you like

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