tcp/ip协议栈研究-tcp数据包接收(1)

880阅读 0评论2017-03-12 chinaitboy
分类:LINUX


tcp数据包的接收

* 相关数据结构
/* This is what the send packet queuing engine uses to pass
 * TCP per-packet control information to the transmission
 * code.  We also store the host-order sequence numbers in
 * here too.  This is 36 bytes on 32-bit architectures,
 * 40 bytes on 64-bit machines, if this grows please adjust
 * skbuff.h:skbuff->cb[xxx] size appropriately.
 */
//传输tcp包的控制结构
struct tcp_skb_cb {
    union {
        struct inet_skb_parm    h4;  
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
        struct inet6_skb_parm   h6;  
#endif
    } header;   /* For incoming frames      */
    __u32       seq;        /* Starting sequence number */
    __u32       end_seq;    /* SEQ + FIN + SYN + datalen    */
    __u32       when;       /* used to compute rtt's    */
    __u8        flags;      /* TCP header flags.        */
    /* NOTE: These must match up to the flags byte in a
     *       real TCP header.
     */
#define TCPCB_FLAG_FIN      0x01
#define TCPCB_FLAG_SYN      0x02
#define TCPCB_FLAG_RST      0x04
#define TCPCB_FLAG_PSH      0x08
#define TCPCB_FLAG_ACK      0x10
#define TCPCB_FLAG_URG      0x20
#define TCPCB_FLAG_ECE      0x40
#define TCPCB_FLAG_CWR      0x80
    __u8        sacked;     /* State flags for SACK/FACK.   */
#define TCPCB_SACKED_ACKED  0x01    /* SKB ACK'd by a SACK block    */
#define TCPCB_SACKED_RETRANS    0x02    /* SKB retransmitted        */
#define TCPCB_LOST      0x04    /* SKB is lost          */
#define TCPCB_TAGBITS       0x07    /* All tag bits         */
#define TCPCB_EVER_RETRANS  0x80    /* Ever retransmitted frame */
#define TCPCB_RETRANS       (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
    __u16       urg_ptr;    /* Valid w/URG flags is set.    */
    __u32       ack_seq;    /* Sequence number ACK'd    */
};

* tcp协议函数调用关系



* tcp包接收的主函数调用关系
tcp_v4_rcv()
   ->__inet_lookup()                // 查找sock结构
   -> xfrm4_policy_check()      // 安全策略检查
   -> tcp_prequeue()                // tcp包预处理
   ->tcp_v4_do_rcv()
      -> tcp_rcv_established()      //处理连接完成后的输入包
      ->tcp_v4_hnd_req()             // 处理listen状态
      ->tcp_rcv_state_process()    // 其他状态处理


* tcp包的接收主流程
/*


 *  From tcp_input.c
 */
int tcp_v4_rcv(struct sk_buff *skb)
{
    const struct iphdr *iph;
    struct tcphdr *th; 
    struct sock *sk; 
    int ret; 
    struct net *net = dev_net(skb->dev);

    // 不是属于自己的包,丢弃。 这里在ip包处理时已经检查过了?
    if (skb->pkt_type != PACKET_HOST)
        goto discard_it;

    /* Count it even if it's bad */
    // 对tcp包接收数量,做一个计数
    TCP_INC_STATS_BH(net, TCP_MIB_INSEGS);

    // 检查tcp头的长度是否正确,tcp的长度至少要是20字节(没有任何选项)
    if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
        goto discard_it;

    // 获取tcp包的头地址
    th = tcp_hdr(skb);

    // doff是tcp首部长度(大小4bit),表示tcp首部长度时,单位是32bit(4字节),所以总共可以表示(2^4-1)*4个字节\
    // 若tcp首部大小小于tcp头结构/4
    if (th->doff < sizeof(struct tcphdr) / 4) 
        goto bad_packet;
    if (!pskb_may_pull(skb, th->doff * 4))
        goto discard_it;

    /* An explanation is required here, I think.
     * Packet length and doff are validated by header prediction,
     * provided case of th->doff==0 is eliminated.
     * So, we defer the checks. */
    // 检查tcp的校验和
    if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
        goto bad_packet;

    // 获取tcp头地址
    th = tcp_hdr(skb);
    // 获取ip头地址
    iph = ip_hdr(skb);
    // 把tcp头中的seq,序列号字段从网络序,转换成主机序
    TCP_SKB_CB(skb)->seq = ntohl(th->seq);
    // 该数据包的结束序号是起始序号seq+syn+fin+datalen的值
    TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                    skb->len - th->doff * 4);
    // 把确认(ack)序号转换成主机序
    TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
    // 用于计算rtt值的when变量
    TCP_SKB_CB(skb)->when    = 0;
    // tcp头的标志位设置成ip头的tos的值?why?
    TCP_SKB_CB(skb)->flags   = iph->tos;
    // ack的类型,sack/fack
    TCP_SKB_CB(skb)->sacked  = 0;

    // 在活动socket的hash表中,查找socket结构和sock结构。
    // 目的和源的ip地址,以及两端的端口号,网络设备的索引号,和skb->dst->rt_iif变量,进行hash值的计算。
    // 若在hash表中找到则,根据socket的状态继续下面的处理,若没有找到则直接调用tcp_v4_send_reset()函数
    // 发送RESET分组。
    sk = __inet_lookup(net, &tcp_hashinfo, iph->saddr,
            th->source, iph->daddr, th->dest, inet_iif(skb));

    // 没有找到对应的sk,转到no_tcp_socket段执行
    if (!sk)
        goto no_tcp_socket;

    // 到这里说明已经找到相应的socket结构,开始对不同的状态进行处理。
process:
    // 若sock的状态是TIME_WAIT,直接跳到do_time_wait标签处进行处理。
    if (sk->sk_state == TCP_TIME_WAIT)
        goto do_time_wait; 
    
    // 检查xfrm4框架是否有ipsec相关安全策略,若有,且没有通过安全检查,则直接丢弃该分组
    if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
        goto discard_and_relse;
    // 调用reset??
    nf_reset(skb);

    // 若该sock的过滤器被激活,则调用sk_filter来检查socket buffer,若返回负数,丢弃数据包。
    // 否则继续处理数据包
    if (sk_filter(sk, skb))
        goto discard_and_relse;

    skb->dev = NULL;

    // 加锁
    bh_lock_sock_nested(sk);
    ret = 0;
    // 若sk没有被其他用户进程使用,则进行以下处理
    // 说明:若socket没有被上半部处理锁定,就尝试把数据包放到预处理队列中,
    // 预处理队列是用户空间缓冲区,这样可以加快数据包的传输效率。
    if (!sock_owned_by_user(sk)) {
#ifdef CONFIG_NET_DMA
        struct tcp_sock *tp = tcp_sk(sk);
        if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
            tp->ucopy.dma_chan = get_softnet_dma();
        if (tp->ucopy.dma_chan)
            ret = tcp_v4_do_rcv(sk, skb);
        else
#endif
        {
            // 若sk没有被用户进程使用,此时在这里进行处理
            // 若tcp_prequeue返回0,表示没有用户和该sock联系,
            // 此时tcp_v4_do_rcv被调用,继续进行进行tcp接收的慢路径处理
            if (!tcp_prequeue(sk, skb))
                ret = tcp_v4_do_rcv(sk, skb);
        }
    } else // 若用户进程正在使用该sk,则把该sk放入到sk_backlog链表中
        sk_add_backlog(sk, skb);
    // 解sock锁
    bh_unlock_sock(sk);
    
    // 减少sock的引用计数,说明sock已经被释放
    sock_put(sk);

    return ret;

// 若数据包到这个标签,说明目前还没有属于该数据包的活动socket。
no_tcp_socket:
    // 若没有找到tcp的sk,则检查是否有安全策略,若不符合安全策略,直接丢弃
    if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
        goto discard_it;

    // 检查该数据包的长度是否合法,检查该数据包的checksum是否合法
    if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
bad_packet:
        // 若是一个坏包,只添加引用计数
        TCP_INC_STATS_BH(net, TCP_MIB_INERRS);
    } else {
        // 若数据包本身没有问题,直接发送RESET分组
        tcp_v4_send_reset(NULL, skb);
    }

    // 丢弃该数据包(ip包)
discard_it:
    /* Discard frame. */
    kfree_skb(skb);
    return 0;

discard_and_relse:
    // 减少sock的引用计数,并且丢弃该数据包
    sock_put(sk);
    goto discard_it;

// 当sock处于time_wait状态时,跳到这里进行处理
do_time_wait:
    // 安全策略检查(ipsec)
    if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
        inet_twsk_put(inet_twsk(sk));
        goto discard_it;
    }
    // 若ip包的长度太短,校验和不正确,丢弃该数据包
    if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
        TCP_INC_STATS_BH(net, TCP_MIB_INERRS);
        inet_twsk_put(inet_twsk(sk));
        goto discard_it;
    }
    
    // 根据到达数据包的类型(syn,fin,数据)等,决定如何处理该包。
    // 以下是接收到各种数据包的情况:
    //  TCP_TW_SUCCESS   0   晚到的数据分组或是重复的ack,此时直接丢弃该数据包
    //  TCP_TW_RST   1  收到FIN,发送RESET给对方
    // TCP_TW_ACK   2  最后的ACK,直接发送ACK到对方
    //  TCP_TW_SYN  3  收到SYN,尝试重新打开连接
    switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
    case TCP_TW_SYN: {
        struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
                            &tcp_hashinfo,
                            iph->daddr, th->dest,
                            inet_iif(skb));
        if (sk2) {
            inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
            inet_twsk_put(inet_twsk(sk));
            sk = sk2;
            goto process;
        }
        /* Fall through to ACK */
    }

    case TCP_TW_ACK:
        tcp_v4_timewait_ack(sk, skb);
        break;
    case TCP_TW_RST:
        goto no_tcp_socket;
    case TCP_TW_SUCCESS:;
    }

    goto discard_it;
}



* 快速路径-预处理队列
对于一个tcp包,有两个输入路径:慢速路径和快速路径。

. 慢速路径
慢速路径是一般的tcp包输入处理方式。
每个socket都有两个缓存区队列,一个是接受缓存队列(receive queue ),另一个是(backlog queue),当接受队列满了或是socket正在被占用时,就会把数据包放入到backlog队列中。在慢速路径中,只有在包含有序数据段的检查合法后,才放到tcp的socket接收队列中。这个过程比较复杂,并在接收函数的“后半部分”进行处理。一旦数据包被放到socket的接收队列,socket就被唤醒,此时调度器开始执行用户级的程序将会从该队列中读取数据包。慢速路径处理过程中,当接收队列满了或用户进程的socket被锁定,数据包将会被防到backlog queue队列中。

. 快速路径
为了提高tcp的数据处理效率,使用了快速路径(fast path)。
除了慢速路径的两个处理队列外,linux实现的tcp协议栈还有第三个缓冲队列,称为:预缓存队列(prequeue.)。该队列被快速路径使用。

/* Packet is added to VJ-style prequeue for processing in process
 * context, if a reader task is waiting. Apparently, this exciting
 * idea (VJ's mail "Re: query about TCP header on tcp-ip" of 07 Sep 93)
 * failed somewhere. Latency? Burstiness? Well, at least now we will
 * see, why it failed. 8)8)               --ANK
 *
 * NOTE: is this not too big to inline?
 */
// 该函数把tcp数据包放到预处理队列中。
// 若用户的socket被锁定(ucopy.task值非空),当socket被唤醒,且调用read操作时,内核立即处理预处理队列中的数据。
/*
* 该函数实现以下功能:若用户正阻塞在该socket上read,则把当前数据包放入prequeue队列,然后则按以下逻辑处理:
* (1) 若prequeue队列长度大于接收队列长度,则轮训prequeue队列调用tcp_v4_do_rcv进行处理,直到给队列的数据被处理完成,
* (2) 若prequeue队列长度为1,说明只有当前数据包被压入该队列,则唤醒该socket,并开始处理该数据包
*/
static inline int tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{
    // 获取tcp_sock的指针
    struct tcp_sock *tp = tcp_sk(sk);

    // 若目前有一个用户正阻塞在该socket上,就会把数据包放到prequeue(预处理队列)中
    // 用户可以通过sysctl来控制该系统行为
    if (!sysctl_tcp_low_latency && tp->ucopy.task) {
        // 把数据包添加到prequeue队列的尾部
        __skb_queue_tail(&tp->ucopy.prequeue, skb);
        // 更新prequeue队列长度
        tp->ucopy.memory += skb->truesize;

        // 若目前的prequeue的数据长度大于数据接收队列的长度
        if (tp->ucopy.memory > sk->sk_rcvbuf) {
            struct sk_buff *skb1;

            BUG_ON(sock_owned_by_user(sk));
            // 调用backlog_rcv函数处理prequeue中的数据,直到处理完该队列的数据为止
            while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL) {
                //处理从用户缓冲区中取出的数据包,这里sk_backlog_rcv其实调用的就是tcp_v4_do_rcv函数
                sk->sk_backlog_rcv(sk, skb1);
                // 统计tcp数据包的个数
                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPREQUEUEDROPPED);
            }    
            // 此时prequeue队列中的数据被处理完成,队列的长度设为为0
            tp->ucopy.memory = 0;

        } else if (skb_queue_len(&tp->ucopy.prequeue) == 1) {
            // 当prequeue队列的长度为1时,唤醒该socket,并开始处理prequeue队列的数据
            wake_up_interruptible(sk->sk_sleep);
            if (!inet_csk_ack_scheduled(sk))
                // 重设ack发送的时间,目的是为了等待数据的到来,和数据一切发送
                inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
                                  (3 * TCP_RTO_MIN) / 4,
                              TCP_RTO_MAX);
        }
        // 返回1,表示我们已经把数据包放到prequeue队列中了,
        return 1;
    }
    // 返回0,表示我们没有把数据放到prequeue队列中
    // 意味着:调用者的prequeue队列满了, 需要把数据放到backlog接受队列
    return 0;
}


* tcp 数据包接收的后半部分处理。
该函数主要进行一下步骤的处理:
. 查看socket的状态,若是TCP_ESTABLISHED,说明tcp连接已经建立起来,此时进行“快速路径”的处理。调用tcp_rcv_established函数,进行处理。
. 是否有完整的tcp头,且完成校验和的计算
. 若是TCP_LISTEN状态,调用tcp_v4_hnd_req函数处理连接请求。
. 最后调用tcp_rcv_state_process函数处理所有阶段的数据包。

/* The socket must have it's spinlock held when we get
 * here.
 *
 * We have a potential double-lock case here, so even when
 * doing backlog processing we use the BH locking scheme.
 * This is because we cannot sleep with the original spinlock
 * held.
 */
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
    struct sock *rsk;
#ifdef CONFIG_TCP_MD5SIG
    /*
     * We really want to reject the packet as early as possible
     * if:
     *  o We're expecting an MD5'd packet and this is no MD5 tcp option
     *  o There is an MD5 option and we're not expecting one
     */
    if (tcp_v4_inbound_md5_hash(sk, skb))
        goto discard;
#endif  

    // 若sock的状态是已经建立成功(TCP_ESTABLISHED),则进入快速路径预处理程序
    if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
        TCP_CHECK_TIMER(sk);
        if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
            rsk = sk;
            goto reset;
        }
        TCP_CHECK_TIMER(sk);
        return 0;
    }
    // 检查tcp头的长度和校验和是否正确
    if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
        goto csum_err;

    // 若socket是listen状态,调用tcp_v4_hnd_req函数处理连接请求
    if (sk->sk_state == TCP_LISTEN) {
        struct sock *nsk = tcp_v4_hnd_req(sk, skb);
        if (!nsk)
            goto discard;

        if (nsk != sk) {
            if (tcp_child_process(sk, nsk, skb)) {
                rsk = nsk;
                goto reset;
            }
            return 0;
        }
    }

    // 处理所有其他状态的socket
    TCP_CHECK_TIMER(sk);
    if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
        rsk = sk;
        goto reset;
    }
    TCP_CHECK_TIMER(sk);
    return 0;

// 发送reset数据包
reset:
    tcp_v4_send_reset(rsk, skb);
discard:
    kfree_skb(skb);
    /* Be careful here. If this function gets more complicated and
     * gcc suffers from register pressure on the x86, sk (in %ebx)
     * might be destroyed here. This current version compiles correctly,
     * but you have been warned.
     */
    return 0;

csum_err:
    TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
    goto discard;
}


*  处理连接状态的socket
   连接状态的数据包分为快速路径和慢速路径,都是在tcp_rcv_established函数中完成的。
    当tcp三次握手完成后,一个tcp连接就建立起来,此时建立连接的双方就可以开始进行发送数据包进行通信。那么如何保证通信的正确性,如何最大限度的利用连接通道提高发送效率,如何防止发送速度过快导致数据包的丢失,这是该函数需要处理的问题。
    该函数的主要目的是尽可能快的把数据复制到上层的用户空间。


* 快速路径预处理

int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
            struct tcphdr *th, unsigned len) 
{
    struct tcp_sock *tp = tcp_sk(sk);

    /*   
     *  Header prediction.
     *  The code loosely follows the one in the famous
     *  "30 instruction TCP receive" Van Jacobson mail.
     *
     *  Van's trick is to deposit buffers into socket queue
     *  on a device interrupt, to call tcp_recv function
     *  on the receive process context and checksum and copy
     *  the buffer to user space. smart...
     *
     *  Our current scheme is not silly either but we take the
     *  extra cost of the net_bh soft interrupt processing...
     *  We do checksum and copy also but from device to kernel.
     */

    tp->rx_opt.saw_tstamp = 0; 

    /*  pred_flags is 0xS?10 << 16 + snd_wnd
     *  if header_prediction is to be made
     *  'S' will always be tp->tcp_header_len >> 2
     *  '?' will be 0 for the fast path, otherwise pred_flags is 0 to
     *  turn it off (when there are holes in the receive
     *   space for instance)
     *  PSH flag is ignored.
     */
    // 设置了PUSH位,所以PUSH位被忽略
    // 且序列号是有序的(也就是说socket下一个期望要接受的序列号正好和接收到的包序列号相等)
    // 或者说,不是碎片包
    if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
        TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
        int tcp_header_len = tp->tcp_header_len;

        /* Timestamp header prediction: tcp_header_len
         * is automatically equal to th->doff*4 due to pred_flags
         * match.
         */

        /* Check timestamp */
        // 检查是否只有时间戳选项,若不只一个时间选项,还有做其他处理
        if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
            // 存在时间戳选项
            __be32 *ptr = (__be32 *)(th + 1);

            /* No? Slow path! */
            // 还有其他的选项,进入“慢速路径”
            if (*ptr != htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16)
                      | (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP))
                goto slow_path;

            // Saw TIMESTAMP on last packet,说明在最新数据包中包含时间戳选项
            tp->rx_opt.saw_tstamp = 1;
            // 获取时间戳的值
            ++ptr;
            tp->rx_opt.rcv_tsval = ntohl(*ptr);
            // 获取echo reply time stamp的值
            ++ptr;
            tp->rx_opt.rcv_tsecr = ntohl(*ptr);
        
            /* If PAWS failed, check it more carefully in slow path */
            // 若PAWS选项失败,进入“慢速路径”(若回应时间戳的值小于下一个回应时间戳的值,进入“慢速路径”进一步检查)
            // 关于PAWS的概念可以查看
            if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
                goto slow_path;
            
            /* DO NOT update ts_recent here, if checksum fails
             * and timestamp was corrupted part, it will result
             * in a hung connection since we will drop all
             * future packets due to the PAWS test.
             */
        }   

        // 检查tcp头的长度是否太小
        if (len <= tcp_header_len) {
            /* Bulk data transfer: sender */
            // 数据包长度和头部长度相等,说明该数据包只有包头,不包含数据
            if (len == tcp_header_len) {
                // 若数据长度等于tcp头的长度,说明是一个不带数据的信息包
                /* Predicted packet is in window by definition.
                 * seq == rcv_nxt and rcv_wup <= rcv_nxt.
                 * Hence, check seq<=rcv_wup reduces to:
                 */
                // 若期望的数据包在规定的窗口范围内,调用tcp_store_ts_recent()函数进行处理
                // ???
                if (tcp_header_len ==
                    (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
                    tp->rcv_nxt == tp->rcv_wup)
                    tcp_store_ts_recent(tp);

                /* We know that such packets are checksummed
                 * on entry.
                 */
                tcp_ack(sk, skb, 0);
                __kfree_skb(skb);
                tcp_data_snd_check(sk);
                return 0;
            } else { /* Header too small */
                // tcp的头部太小,丢弃该tcp包
                TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
                goto discard;
            }
        } else { 
            // 到这里说明数据包的长度大于tcp头长度,也就是带有数据的包,这里开始处理数据
            int eaten = 0; 
            int copied_early = 0; 
            // 若读取到的序列号和下一次期望接收的序列号相等(可以查看rfc793)
            // 且 数据包的长度小于,用户空间缓冲区ucopy的长度ucopy.len
            if (tp->copied_seq == tp->rcv_nxt &&
                len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
                if (tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
                    copied_early = 1; 
                    eaten = 1; 
                }    
#endif
                // 检查目前的socket的进程描述符是否是当前进程
                // socket正在被本用户使用,且没有谁copy过该数据包
                if (tp->ucopy.task == current &&
                    sock_owned_by_user(sk) && !copied_early) {
                    // 设置该进程为RUNNING
                    __set_current_state(TASK_RUNNING);

                    // 且把该数据包复制到ucopy缓冲区空间中???
                    if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
                        eaten = 1; 
                }    

                if (eaten) {
                    // 若把已经包数据包复制到用户接收缓冲区中
                    /* Predicted packet is in window by definition.
                     * seq == rcv_nxt and rcv_wup <= rcv_nxt.
                     * Hence, check seq<=rcv_wup reduces to:
                     */ ?????
                    if (tcp_header_len ==
                        (sizeof(struct tcphdr) +
                         TCPOLEN_TSTAMP_ALIGNED) &&
                        tp->rcv_nxt == tp->rcv_wup)
                        tcp_store_ts_recent(tp);

                    tcp_rcv_rtt_measure_ts(sk, skb);

                    __skb_pull(skb, tcp_header_len);
                    tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
                    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
                }
                if (copied_early)
                    tcp_cleanup_rbuf(sk, skb->len);
            }

            if (!eaten) {
                // 若没有复制到用户缓冲区中,可能是校验和不对
                //  这里进行校验和的计算,若成功,查看是否socket的空间足够,若不够,走慢速路径
                // 
                if (tcp_checksum_complete_user(sk, skb))
                    goto csum_error;
                    
                /* Predicted packet is in window by definition.
                 * seq == rcv_nxt and rcv_wup <= rcv_nxt.
                 * Hence, check seq<=rcv_wup reduces to:
                 */
                if (tcp_header_len ==
                    (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
                    tp->rcv_nxt == tp->rcv_wup)
                    tcp_store_ts_recent(tp);

                tcp_rcv_rtt_measure_ts(sk, skb);

                // 检查socket的预留空间是否足够
                if ((int)skb->truesize > sk->sk_forward_alloc)
                    goto step5;

                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

                /* Bulk data transfer: receiver */
                // 移除skb中的tcp头的部分
                __skb_pull(skb, tcp_header_len);
                // 把数据放入到接收队列中
                __skb_queue_tail(&sk->sk_receive_queue, skb);
                skb_set_owner_r(skb, sk);
                // 设置rcv_nxt的值
                tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
            }

            tcp_event_data_recv(sk, skb);

            if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
                /* Well, only one small jumplet in fast path... */
                tcp_ack(sk, skb, FLAG_DATA);
                tcp_data_snd_check(sk);
                if (!inet_csk_ack_scheduled(sk))
                    goto no_ack;
            }

            if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
                __tcp_ack_snd_check(sk, 0);
no_ack:
#ifdef CONFIG_NET_DMA
            if (copied_early)
                __skb_queue_tail(&sk->sk_async_wait_queue, skb);
            else
#endif
            if (eaten)
                __kfree_skb(skb);
            else
                sk->sk_data_ready(sk, 0);
            return 0;
        }
    }

    // 慢速路径处理,到这里说明有一些原因无法处理
slow_path: 
    if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
        goto csum_error;

    /*   
     * RFC1323: H1. Apply PAWS check first.
     */
    // 检查时间戳标记和PAW是否合法,若不合法丢弃该数据包
    if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
        tcp_paws_discard(sk, skb)) {
        if (!th->rst) {
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
            tcp_send_dupack(sk, skb);
            goto discard;
        }    
        /* Resets are accepted even if PAWS failed.

           ts_recent update must be made after we are sure
           that the packet is in window.
         */
    }    

    /*   
     *  Standard slow path.
     */
    // 标准慢速路径
    // 检查所有输入数据包的序列号
    if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
        /* RFC793, page 37: "In all states except SYN-SENT, all reset
         * (RST) segments are validated by checking their SEQ-fields."
         * And page 69: "If an incoming segment is not acceptable,
         * an acknowledgment should be sent in reply (unless the RST bit
         * is set, if so drop the segment and return)".
         */
        if (!th->rst)
            tcp_send_dupack(sk, skb);
        goto discard;
    }

    // 若我们接收到了一个RESET包,丢弃该数据包
    if (th->rst) {
        tcp_reset(sk);
        goto discard;
    }

    tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);

    if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
        TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONSYN);
        tcp_reset(sk);
        return 1;
    }

step5:
    // 第5部处理建立建立状态的数据包
    if (th->ack)
        tcp_ack(sk, skb, FLAG_SLOWPATH);

    tcp_rcv_rtt_measure_ts(sk, skb);

    //第6部处理urgent标志
    /* Process urgent data. */
    tcp_urg(sk, skb, th);

    // 第7部处理包的数据部分
    /* step 7: process the segment text */
    tcp_data_queue(sk, skb);

    tcp_data_snd_check(sk);
    tcp_ack_snd_check(sk);
    return 0;

csum_error:
    TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);

discard:
    __kfree_skb(skb);
    return 0;
}


上一篇:tcp_ip协议栈源码分析-ip数据包接收
下一篇:netfilter 源码分析