lwip源码分析之 TCP协议 数据输入 (一)

一,tcp的数据输入

ip层接收到数据后,经过处理需要将数据传递给tcp层。
ip层通过tcp_input函数将接收到的数据传递给tcp层,该函数是tcp数据的总入口,在此函数中,从ip数据包中提取tcp报文,检验报文的正确性,找到对应的tcp控制块,进入tcp状态机,将报文中的有效数据传递给应用层。

二,ip数据解析

ip层递交的数据仍然是以pbuf的形式出现的,其中payload指向的是tcp数据的首部,我们需要将tcp的首部与数据分离,其中tcp首部包括20字节的固定长度和可变长度的选项字段。但在此之前,我们还需要先检查tcp报文是否正确,代码注释如下:

  tcphdr = (struct tcp_hdr *)p->payload;  //从ip数据包中获取tcp首部

  //如果pbuf长度比默认tcp首部还小,这不好,丢弃
  if (p->len < TCP_HLEN) {
    
    
    /* drop short packets */
    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet (%"U16_F" bytes) discarded\n", p->tot_len));
    TCP_STATS_INC(tcp.lenerr);
    goto dropped;
  }

  //对输入的广播和多播的报文不做处理,丢弃
  if (ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()) ||
      ip_addr_ismulticast(ip_current_dest_addr())) {
    
    
    TCP_STATS_INC(tcp.proterr);
    goto dropped;
  }

//检查报文的校验字段
  IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) {
    
    
    //计算校验和
    u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
                               ip_current_src_addr(), ip_current_dest_addr());
    if (chksum != 0) {
    
    
        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F"\n",
          chksum));
      tcp_debug_print(tcphdr);
      TCP_STATS_INC(tcp.chkerr);
      goto dropped;
    }
  }

  hdrlen_bytes = TCPH_HDRLEN(tcphdr) * 4; //计算tcp 首部字节数,一个长度单位32bit

  //如果首部字节太大或者太小,丢弃报文
  if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) {
    
    
    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: invalid header length (%"U16_F")\n", (u16_t)hdrlen_bytes));
    TCP_STATS_INC(tcp.lenerr);
    goto dropped;
  }

  tcphdr_optlen = hdrlen_bytes - TCP_HLEN;  //计算选项长度=首部长度-固定20字节
  tcphdr_opt2 = NULL;

  //第一个pbuf的长度大于首部长度,说明选项字段在此pbuf中
  if (p->len >= hdrlen_bytes) {
    
    
    //所有选项数据放在第一个pbuf(也就是tcp首部所在的pbuf)
    tcphdr_opt1len = tcphdr_optlen;
    //向后移动payload指针,使payload指向tcp数据
    pbuf_header(p, -(s16_t)hdrlen_bytes); /* cannot fail */
  } else {
    
      //选项字段可能分为两份,一份在第一个pbuf,一份在下一个pbuf,移动payload跳过选项
    u16_t opt2len;
    LWIP_ASSERT("p->next != NULL", p->next != NULL);

    //pbuf的payload移动到固定tcp首部尾,此时的p->len为第一份选项字段的长度
    pbuf_header(p, -TCP_HLEN);

    tcphdr_opt1len = p->len;
    opt2len = tcphdr_optlen - tcphdr_opt1len; //计算第二份选项字段的长度

    pbuf_header(p, -(s16_t)tcphdr_opt1len); //移动payload指针跳过第一份选项

    //如果第二份选项超过下一个pbuf大小,不合理,丢弃报文
    if (opt2len > p->next->len) {
    
    
      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: options overflow second pbuf (%"U16_F" bytes)\n", p->next->len));
      TCP_STATS_INC(tcp.lenerr);
      goto dropped;
    }

    //p->next->payload指向的就是第二份选项字段
    tcphdr_opt2 = (u8_t*)p->next->payload;

    pbuf_header(p->next, -(s16_t)opt2len);  //移动第一个pbuf的payload跳过第二段选项
    p->tot_len -= opt2len;  //总长度随着payload后移而减少

    LWIP_ASSERT("p->len == 0", p->len == 0);
    LWIP_ASSERT("p->tot_len == p->next->tot_len", p->tot_len == p->next->tot_len);
  }

  //将tcp首部的网络字节的数据转换成主机字节
  tcphdr->src = lwip_ntohs(tcphdr->src);
  tcphdr->dest = lwip_ntohs(tcphdr->dest);
  seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno);
  ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno);
  tcphdr->wnd = lwip_ntohs(tcphdr->wnd);


  flags = TCPH_FLAGS(tcphdr); //获取tcp首部的标志位
  tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_FIN)) ? 1 : 0);  //计算tcp报文长度,报文标志位存在TCP_FIN或TCP_FIN时,长度要加一
  //到这儿,我们分离了ip数据包中的tcp首部和数据

三,给谁的报文?

tcp报文解析完成后,就需要确定这报文是传递给那个tcp控制块的。这需要根据报文的源地址和目标地址在各控制块链表中查找。

1,在active链表?

以下代码确定了报文要送达的tcp控制块

  //检查数据是否是给tcp_active_pcbs链表中的tcp
  for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
    
    
    LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED);
    LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT);
    LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN);

    //找到报文对应的tcp控制块,并将该pcb放到tcp_active_pcbs链表头,以方便随后的读取
    if (pcb->remote_port == tcphdr->src &&
        pcb->local_port == tcphdr->dest &&
        ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
        ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) {
    
    

      LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb);
      if (prev != NULL) {
    
    
        prev->next = pcb->next;
        pcb->next = tcp_active_pcbs;
        tcp_active_pcbs = pcb;
      } else {
    
    
        TCP_STATS_INC(tcp.cachehit);
      }
      LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb);
      break;
    }
    prev = pcb; //把对应的pcb放到prev
  }

2,在timewait链表?

  if (pcb == NULL) {
    
    

    //在tcp_tw_pcbs链表找
    for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
    
    
      LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
      if (pcb->remote_port == tcphdr->src &&
          pcb->local_port == tcphdr->dest &&
          ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
          ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) {
    
    
       //对于处于timewait状态的pcb,不需要将它放在链表头部
        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for TIME_WAITing connection.\n"));
        tcp_timewait_input(pcb);  //timewait处理数据
        pbuf_free(p); //释放数据
        return;
      }
    }

其中tcp_timewait_input()忽略所有的rst,ack和fin置位报文,且报文数据会被删除。

3,给listen链表的?

    //在tcp_listen_pcbs链表中找
    for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
    
    
      if (lpcb->local_port == tcphdr->dest) {
    
     //端口对上了
        if (IP_IS_ANY_TYPE_VAL(lpcb->local_ip)) {
    
     
  
          break;

        } else if (IP_ADDR_PCB_VERSION_MATCH_EXACT(lpcb, ip_current_dest_addr())) {
    
    
          if (ip_addr_cmp(&lpcb->local_ip, ip_current_dest_addr())) {
    
    
            
            break;
          } else if (ip_addr_isany(&lpcb->local_ip)) {
    
    
            
            break;
          }
        }
      }
      prev = (struct tcp_pcb *)lpcb;  //当前的pcb赋值到prev
    }

    //如果在listen链表找到对象,则将该对象pcb放到链表头
    if (lpcb != NULL) {
    
    
      if (prev != NULL) {
    
    
        ((struct tcp_pcb_listen *)prev)->next = lpcb->next;

        lpcb->next = tcp_listen_pcbs.listen_pcbs;

        tcp_listen_pcbs.listen_pcbs = lpcb;
      } else {
    
    
        TCP_STATS_INC(tcp.cachehit);
      }

      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection.\n"));
      tcp_listen_input(lpcb); //listen对象处理数据
      pbuf_free(p); //释放数据
      return;
    }
  }

其中tcp_listen_input()会检查报文是否是连接报文,若是则创建新的pcb并加入active队列,发送syn和ack,mss报文。

四,数据处理后

细心的你会发现,以上三个链表中,只有active链表没有处理报文的代码,由于这部分代码是最复杂的,其实现需要使用TCP状态机来搞定,在下一章再续TCP状态机,这一节先把tcp_input的最后看完。

经过上面确定了报文是给某个tcp后,先检查该tcp连接中,是否有数据仍然未被应用程序读取,若是,必须先让应用程序处理完上次未读取完成的数据后才进入下一步。

  //来到这,说明该数据的对象是处于active链表中,就是一个已存在的连接的报文
  if (pcb != NULL) {
    
    
   //确定了pcb,创建输入报文结构体
    inseg.next = NULL;
    inseg.len = p->tot_len;
    inseg.p = p;
    inseg.tcphdr = tcphdr;

    //初始化接收的参数
    recv_data = NULL; //指向最终确认的有序pbuf,被应用程序调用
    recv_flags = 0; //接收数据的处理结果
    recv_acked = 0; //接收的数据字节数

    //如果tcp报文首部中有TCP_PSH标志,将pbuf的相应标志置位
    if (flags & TCP_PSH) {
    
    
      p->flags |= PBUF_FLAG_PUSH;
    }

    //如果当前的tcp控制块有数据未被上层应用读取
    if (pcb->refused_data != NULL) {
    
    
      //调用上层应用的数据回调函数来处理未读取的数据,若有未读取的数据,只能将tcp终止
      if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
        ((pcb->refused_data != NULL) && (tcplen > 0))) {
    
    
        //如果对方的窗口通告为0,为防止死锁,启动零窗口探查
        if (pcb->rcv_ann_wnd == 0) {
    
    
          //发送无数据的ack(探查)
          tcp_send_empty_ack(pcb);
        }
        TCP_STATS_INC(tcp.drop);
        MIB2_STATS_INC(mib2.tcpinerrs);
        goto aborted;
      }
    }

随后进入tcp状态机处理接收报文,tcp_process()将处理结果放在全局变量recv_flag中。接下来根据recv_flag的值,决定pcb的命运。

recv_acked本地发送的数据被对方确认的字节数;
recv_data指向被正确接收到的字节数据;

    tcp_input_pcb = pcb;
    err = tcp_process(pcb); //进入状态机处理,根据处理结果recv_flag作出以下反应:

    //若返回ERR_ABRT,说明pcb已经被终止
    if (err != ERR_ABRT) {
    
    
      //若收到复位报文,则tcp连接重置,移除该tcp,并释放内存
      if (recv_flags & TF_RESET) {
    
    
        //调用错误回调函数
        TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
        tcp_pcb_remove(&tcp_active_pcbs, pcb);
        memp_free(MEMP_TCP_PCB, pcb);
      } else {
    
    
        err = ERR_OK;
        //如果有数据被确认,调用sent回调(用于向send_buff继续填充要发送数据)
        if (recv_acked > 0) {
    
    
          
            acked16 = recv_acked; //表示已经被确认的字节数
            //调用sent回调函数,通知应用程序可以继续发送数据
            TCP_EVENT_SENT(pcb, (u16_t)acked16, err);
            if (err == ERR_ABRT) {
    
    
              goto aborted;
            }
          }
          recv_acked = 0;
        }
        //TODO 延时关闭连接?
        if (tcp_input_delayed_close(pcb)) {
    
    
          goto aborted;
        }
        //接收到新数据,回调recv函数通知应用程序
        if (recv_data != NULL) {
    
      

          LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);

          //TODO 关闭接收,释放内存,同时通知对方接收数据不完整?
          if (pcb->flags & TF_RXCLOSED) {
    
    
            pbuf_free(recv_data);
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
            if (rest != NULL) {
    
    
              pbuf_free(rest);
            }
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
            tcp_abort(pcb);	//发送复位报文
            goto aborted;
          }
        
          TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);  //回调recv函数通知应用程序
          if (err == ERR_ABRT) {
    
    
            goto aborted;
          }

          //如果上层无法接收数据,则先保存在pcb->refused_data
          if (err != ERR_OK) {
    
    
            pcb->refused_data = recv_data;
            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
          }
        }

        //接收到fin
        if (recv_flags & TF_GOT_FIN) {
    
      
          if (pcb->refused_data != NULL) {
    
      //仍然有未被应用读取的数据?
            /* Delay this if we have refused data. */
            pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN;
          } else {
    
    
            /* correct rcv_wnd as the application won't call tcp_recved()
               for the FIN's seqno */
            if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) {
    
    
              pcb->rcv_wnd++;
            }
            //调用recv通知程序对方准备关闭连接
            TCP_EVENT_CLOSED(pcb, err);
            if (err == ERR_ABRT) {
    
    
              goto aborted;
            }
          }
        }

        tcp_input_pcb = NULL;
        if (tcp_input_delayed_close(pcb)) {
    
    
          goto aborted;
        }
        /* Try to send something out. */
        tcp_output(pcb);	//tcp输出
      }
    }

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/111339566
今日推荐