zoukankan      html  css  js  c++  java
  • 主动关闭 tcp fin-wait-2 time-wait 定时器

    后面整理相关信息

    //后面整理相关信息
    /*
     *    This function implements the receiving procedure of RFC 793 for
     *    all states except ESTABLISHED and TIME_WAIT.
     *    It's called from both tcp_v4_rcv and tcp_v6_rcv and should be
     *    address independent.
     */
    
    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
        const struct tcphdr *th = tcp_hdr(skb);
        struct request_sock *req;
        int queued = 0;
        bool acceptable;
    
        switch (sk->sk_state) {
        case TCP_CLOSE:
            goto discard;
    
        case TCP_LISTEN:
            //服务器端收到SYN
            /*
             * 在半连接的LISTEN状态下,只处理SYN段。如果是
             * ACK段,此时连接尚未开始建立,因此返回1。在调用
             * tcp_rcv_state_process()函数中会给对方发送RST段;
             * 如果接收的是RST段,则丢弃
             */
            if (th->ack)
                return 1;
    
            if (th->rst)
                goto discard;
    
            if (th->syn) {
                if (th->fin)
                    goto discard;
                /*
                 * 处理SYN段,主要由conn_request接口(TCP中为tcp_v4_conn_request)处理,
                 * icsk_af_ops成员在创建套接字时被初始化,参见tcp_v4_init_sock()
                 */
                 /*收到三次握手的第一步SYN,
                    则在tcp_v4_conn_request中创建连接请求控制块request_sock
                    */
                if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)//ipv4_specific--->tcp_v4_conn_request
                    return 1;
    
                consume_skb(skb);
                return 0;
            }
            goto discard;
    
        case TCP_SYN_SENT://客户端收到SYN+ACK
        /*
    对于TCP_SYN_SENT状态的sock,会调用tcp_rcv_synsent_state_process来进行处理
    解析tcp选项,获取服务端的支持情况, 比如sack, TFO, wscale, MSS, timestamp等
    如果有ack, 进行tcp_ack, 这时候可能fastopen确认了之前的数据
    调用tcp_finish_connect,TCP_SYN_SENT->TCP_ESTABLISHED
    如果包含fastopen cookie则保存
    判断是否需要立即ack还是延时ack
    如果包里没有ack,只有syn,则表示相互connect, TCP_SYN_SENT->TCP_SYN_RECV, 并发送synack 
    * 处理SYN_SENT状态下接收到的TCP段,如果返回值大于0,则表示需给对方发送RST段,该TCP段的释放由
     * tcp_rcv_state_process()的调用者来处理.
     */
            tp->rx_opt.saw_tstamp = 0;
            queued = tcp_rcv_synsent_state_process(sk, skb, th);
            if (queued >= 0)
                return queued;
    
            /* Do step6 onward by hand. */
    /*
             * 在处理了SYN_SENT状态下接收到的段之后,还
             * 需处理紧急数据,然后释放该段,最后检测是
             * 否有数据需要发送.
             */        
            tcp_urg(sk, skb, th);
            __kfree_skb(skb);
            tcp_data_snd_check(sk);
            return 0;
        }
    
        tp->rx_opt.saw_tstamp = 0;
        req = tp->fastopen_rsk;
        if (req) {
            WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
                sk->sk_state != TCP_FIN_WAIT1);
    
            if (!tcp_check_req(sk, skb, req, true))
                goto discard;
        }
    
        if (!th->ack && !th->rst && !th->syn)
            goto discard;
    
        if (!tcp_validate_incoming(sk, skb, th, 0))
            return 0;
    /*
             * 处理TCP段ACK标志,tcp_ack()返回非零值表示处理
             * ACK段成功,是正常的第三次握手TCP段
             */
        /* step 5: check the ACK field */
        acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
                          FLAG_UPDATE_TS_RECENT) > 0;
    /*
    tcp_rcv_state_process函数中对于ack的处理步骤中,假如连接处于FIN_WAIT_1,
    且数据均已经被确认完,则进入TIME_WAIT_2状态;如果无需在该状态等待(linger2<0),
    或者收到了乱序数据段,则直接关闭连接;如果需要等待,
    则需要判断等待时间与TIMEWAIT时间的大小关系,若>TIMEWAIT_LEN,
    则添加TIME_WAIT_2定时器,否则直接进入TIME_WAIT接管(其子状态仍然是FIN_WAIT_2),
    接管之后会添加TIME_WAIT定时器;
    */
        switch (sk->sk_state) {
        case TCP_SYN_RECV:
            if (!acceptable)
                return 1;
    
            if (!tp->srtt_us)
                tcp_synack_rtt_meas(sk, req);
    /*/这里是由tcp_v4_do_rcv里面的tcp_child_process走到这里,
    在tcp_child_process前会通过tcp_check_req创建一个新的struct sock
             Once we leave TCP_SYN_RECV, we no longer need req
             * so release it.
             */
            if (req) {
                tp->total_retrans = req->num_retrans;
                reqsk_fastopen_remove(sk, req, false);
            } else {
                /* Make sure socket is routed, for correct metrics. */
                icsk->icsk_af_ops->rebuild_header(sk);
                tcp_init_congestion_control(sk);
    
                tcp_mtup_init(sk);
                tp->copied_seq = tp->rcv_nxt;
                tcp_init_buffer_space(sk);
            }
            smp_mb();
            tcp_set_state(sk, TCP_ESTABLISHED);// TCP_SYN_RECV->TCP_ESTABLISHED
            sk->sk_state_change(sk);//sock_def_wakeup, 唤醒epoll
    /*
    sock_init_data中 有
    sk->sk_state_change    =    sock_def_wakeup;
    sk->sk_data_ready    =    sock_def_readable;
    sk->sk_write_space    =    sock_def_write_space;
    sk->sk_error_report    =    sock_def_error_report;
    sk->sk_destruct        =    sock_def_destruct;
    */
    //epoll然后调用ep_send_events->ep_scan_ready_list->ep_send_events_proc->ep_item_poll->tcp_poll
     /*
                     * 设置"子"传输控制块为ESTABLISHED状态
                     */
            /* Note, that this wakeup is only for marginal crossed SYN case.
             * Passively open sockets are not waked up, because
             * sk->sk_sleep == NULL and sk->sk_socket == NULL.
             */
             /*
                     * 发信号给那些将通过该套接字发送数据的进程,
                     * 通知他们套接字目前已经可以发送数据了
         sk_state_change()->sock_def_wakeup()->ep_poll_callback(), 添加到epoll的ready list中,并唤醒阻塞中的epoll。
    epoll然后调用ep_send_events->ep_scan_ready_list->ep_send_events_proc->ep_item_poll->tcp_poll
                     */
                     
            if (sk->sk_socket)
                sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
         /*
                     * 初始化传输控制块各字段,如果存在时间戳选项,
                     * 同时平滑RTT为零,则需计算重传超时时间等
                     */
            tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
            tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;
            tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
    
            if (tp->rx_opt.tstamp_ok)
                tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
    
            if (req) {
                /* Re-arm the timer because data may have been sent out.
                 * This is similar to the regular data transmission case
                 * when new data has just been ack'ed.
                 *
                 * (TFO) - we could try to be more aggressive and
                 * retransmitting any data sooner based on when they
                 * are sent out.
                 */
                tcp_rearm_rto(sk);
            } else
                tcp_init_metrics(sk);
    /*
                     * 为该套接字建立路由,初始化拥塞控制模块
                     */
                      /*
                     * 初始化与路径MTU有关的成员
                     */
            tcp_update_pacing_rate(sk);
    /*
                     * 更新最近一次发送数据包的时间
                     */
            /* Prevent spurious tcp_cwnd_restart() on first data packet */
            tp->lsndtime = tcp_time_stamp;
    
            tcp_initialize_rcv_mss(sk);
            /*
                     * 计算有关TCP首部预测的标志
                     */
            tcp_fast_path_on(tp);
            break;
    
        case TCP_FIN_WAIT1: {
            struct dst_entry *dst;
            int tmo;
    
            /* If we enter the TCP_FIN_WAIT1 state and we are a
             * Fast Open socket and this is the first acceptable
             * ACK we have received, this would have acknowledged
             * our SYNACK so stop the SYNACK timer.
             */
            if (req) {
                /* Return RST if ack_seq is invalid.
                 * Note that RFC793 only says to generate a
                 * DUPACK for it but for TCP Fast Open it seems
                 * better to treat this case like TCP_SYN_RECV
                 * above.
                 */
                if (!acceptable)
                    return 1;
                /* We no longer need the request sock. */
                reqsk_fastopen_remove(sk, req, false);
                tcp_rearm_rto(sk);
            }        /* 发送数据未确认完毕 */
            if (tp->snd_una != tp->write_seq)
                break;
              /* 如果通过ACK的确认,所有的发送段(包括FIN段)对方都已收到,则
            从FIN_WAIT1状态迁移到FIN_WAINT2状态, 并关闭发送方向的连接。
                     */
            tcp_set_state(sk, TCP_FIN_WAIT2); /* 进入FIN_WAIT_2状态 */
            sk->sk_shutdown |= SEND_SHUTDOWN;/* 关闭发送端 */
    
            dst = __sk_dst_get(sk);
            if (dst)/* 路由缓存确认 */
                dst_confirm(dst);
    
            if (!sock_flag(sk, SOCK_DEAD)) {
                /* Wake up lingering close() */
                sk->sk_state_change(sk); /* 套接口不是DEAD状态,状态发生变化,唤醒等待进程 */
                break;
            }
     /* linger2<0,无需在FIN_WAIT_2等待 */
            if (tp->linger2 < 0 || /* 收到期望序号以后的数据段(data, fin) */
                (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
                 after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {
                tcp_done(sk);/* 关闭连接 */
                NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
                return 1;
            }
        }
        /*
           sock结构进入TIME_WAIT状态有两种情况:一种是在真正进入了TIME_WAIT状态,还有一种是真实的状态是FIN_WAIT_2的TIME_WAIT状态。
        之所以让FIN_WAIT_2状态在没有接收到FIN包的情况下也可以进入TIME_WAIT状态是因为tcp_sock结构占用的资源要比tcp_timewait_sock
    结构占用的资源多,而且在TIME_WAIT下也可以处理连接的关闭。
     */                
            tmo = tcp_fin_time(sk); /* 获取FIN_WAIT_2等待时间 */
        /*这里说明了:主动关闭的一端在等待三次握手中的第三步FIN的时候,不会永远等待,
        如果对端等tmo还没发送FIN过来,则直接进入time_wait定时器tcp_time_wait(这里面会把之前的sk释放掉,用心的timewait_sock替换)超时阶段*/
            if (tmo > TCP_TIMEWAIT_LEN) {  /* > TIMEWAIT_LEN,加入FIN_WAIT_2定时器 */
        /*超过TCP_TIMEWAIT_LEN的时间在keepalive中实现,然后在在该keepalive定时器回调中进入tcp_time_wait*/
                inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
            } else if (th->fin || sock_owned_by_user(sk)) {
                /* Bad case. We could lose such FIN otherwise.
                 * It is not a big problem, but it looks confusing
                 * and not so rare event. We still can lose it now,
                 * if it spins in bh_lock_sock(), but it is really
                 * marginal case.
                 */ /* 有fin?? 或者 被用户进程锁定,加入FIN_WAIT_2定时器 */
                inet_csk_reset_keepalive_timer(sk, tmo);
            } else { /* 正常等待时间< TIMEWAIT_LEN,进入TIMEWAIT接管状态 */
                tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);//本端发送的fin已经收到确认,等待对方发送fin
                goto discard;
            }
            break;
        }
    
        case TCP_CLOSING:
          /*
                 * 如果通过ACK确认,所有的发送段(包括
                 * FIN段)对方都已经收到,则从CLOSING状态
                 * 迁移到TIME_WAIT状态,作2MSL超时等待
                 */
            if (tp->snd_una == tp->write_seq) {
                tcp_time_wait(sk, TCP_TIME_WAIT, 0);
                goto discard;
            }
            break;
    
        case TCP_LAST_ACK:
         /*
                 * 如果通过ACK确认,所有的发送段(包括FIN段)
                 * 对方都已经收到,则从LAST_ACK状态迁移到
                 * CLOSE状态,把相关的metrics更新到目的路由项
                 * 中,并关闭传输控制块。
                 */
            if (tp->snd_una == tp->write_seq) {
                tcp_update_metrics(sk);
                tcp_done(sk);
                goto discard;
            }
            break;
        }
        /*
         * 处理带外数据
         */
        /* step 6: check the URG bit */
        tcp_urg(sk, skb, th);
    /*
    FIN_WAIT_2状态的走向有以下几个流程触发点,
    (1)TIME_WAIT_2定时器未超时时间内,收到数据段触发; 
    (2)TIME_WAIT_2定时器超时触发; 
    (3)TIME_WAIT定时器未超时时间内,收到数据段触发;
    (4)TIME_WAIT定时器超时触发;
    *//*
         * 对于CLOSE_WAIT、CLOSING和LAST_ACK这三种状态,如果
         * 接收到已经确认过的段,则直接丢弃,否则与
         * FIN_WAIT1和FIN_WAIT2状态的处理方式一样。
         */
        /* step 7: process the segment text */
        switch (sk->sk_state) {
        case TCP_CLOSE_WAIT:
        case TCP_CLOSING:
        case TCP_LAST_ACK:
            if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
                break;
        case TCP_FIN_WAIT1:
        case TCP_FIN_WAIT2://TIME_WAIT_2定时器未超时时间内,收到数据段触发,如果设置FIN标记,则直接进入TIME_WAIT状态;
            /* RFC 793 says to queue data in these states,
             * RFC 1122 says we MUST send a reset.
             * BSD 4.4 also does reset.
             */
            if (sk->sk_shutdown & RCV_SHUTDOWN) {
                if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
                    after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
                    NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
                    tcp_reset(sk);
                    return 1;
                }
            }
            /* Fall through */
        case TCP_ESTABLISHED:
            tcp_data_queue(sk, skb);
            queued = 1;
            break;
        }
    
        /* tcp_data could move socket to TIME-WAIT */
        if (sk->sk_state != TCP_CLOSE) {
            tcp_data_snd_check(sk);
            tcp_ack_snd_check(sk);
        }
    
        if (!queued) {
    discard:
            tcp_drop(sk, skb);
        }
        return 0;
    }
    
    /* This routine deals with incoming acks, but not outgoing ones. */
    /*
     * tcp_ack()用于处理接收到有ACK标志的段,当接到有效的ACK后会更新
     * 发送窗口。
     * @skb: 接收到的ACK段
     * @flag: 标志,取值为FLAG_DATA等
     */
    static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
    {
        struct inet_connection_sock *icsk = inet_csk(sk);
        struct tcp_sock *tp = tcp_sk(sk);
        u32 prior_snd_una = tp->snd_una;
        u32 ack_seq = TCP_SKB_CB(skb)->seq;
        u32 ack = TCP_SKB_CB(skb)->ack_seq;
        u32 prior_in_flight;
        u32 prior_fackets;
        int prior_packets;
        int frto_cwnd = 0;
    
        /* If the ack is older than previous acks
         * then we can probably ignore it.
         */
         /*
         * 检验确认的序号是否落在SND.UNA和SND.NXT之间,否则
         * 是不合法的序号。
         * 如果确认的序号在SND.NXT的右边,则说明该序号的数据
         * 发送方还没有发送,直接返回。
         * 如果确认的序号在SND.UNA的左边,则说明已经接受过
         * 该序号的ACK了。因为每个有负载的TCP段都会顺便
         * 携带一个ACK序号,即使这个序号已经确认过。因此
         * 如果是一个重复的ACK就无需作处理直接返回即可。但
         * 如果段中带有SACK选项,则需对此进行处理
         */
        if (before(ack, prior_snd_una)) //说明接收到了重复的ack
            goto old_ack;
    
        /* If the ack includes data we haven't sent yet, discard
         * this segment (RFC793 Section 3.9).
         */
        if (after(ack, tp->snd_nxt))
            goto invalid_ack;
    
        if (after(ack, prior_snd_una)) //说明收到的是已发送出去包的ack
            flag |= FLAG_SND_UNA_ADVANCED;
    
        if (sysctl_tcp_abc) {
            if (icsk->icsk_ca_state < TCP_CA_CWR)
                tp->bytes_acked += ack - prior_snd_una;
            else if (icsk->icsk_ca_state == TCP_CA_Loss)
                /* we assume just one segment left network */
                tp->bytes_acked += min(ack - prior_snd_una,
                               tp->mss_cache);
        }
    
        prior_fackets = tp->fackets_out;
        /*
         * 获取正在传输中的段数
         */
        prior_in_flight = tcp_packets_in_flight(tp);
    
        if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) {
            /* Window is constant, pure forward advance.
             * No more checks are required.
             * Note, we use the fact that SND.UNA>=SND.WL2.
             */
             /*
             * 如果接收ACK执行的是快速路径,则更新发送窗口的左边界,
             * 添加FLAG_WIN_UPDATE标记,同时通知拥塞控制算法模块
             * 本次ACK是快速路径,如有必要,就作相应的处理
             */
            tcp_update_wl(tp, ack_seq);
            tp->snd_una = ack;
            flag |= FLAG_WIN_UPDATE;
    
            tcp_ca_event(sk, CA_EVENT_FAST_ACK);
    
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPACKS);
        } else {
            /*
                 * 如果接收ACK执行的是慢速路径,首先判断ACK段中是否有
                 * 数据负载,如果有,则添加FLAG_DATA标记.
                 */
            if (ack_seq != TCP_SKB_CB(skb)->end_seq)
                flag |= FLAG_DATA;
            else
                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPUREACKS);
    
            flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);
    
            if (TCP_SKB_CB(skb)->sacked)
                flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una);
    
            if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb)))
                flag |= FLAG_ECE;
    
            tcp_ca_event(sk, CA_EVENT_SLOW_ACK);
        }
    
        /* We passed data and got it acked, remove any soft error
         * log. Something worked...
         */
        sk->sk_err_soft = 0;
        icsk->icsk_probes_out = 0;
        tp->rcv_tstamp = tcp_time_stamp;
        prior_packets = tp->packets_out;
        if (!prior_packets)
            goto no_queue;
    
        /* See if we can take anything off of the retransmit queue. */
        flag |= tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una);
    
        if (tp->frto_counter)
            frto_cwnd = tcp_process_frto(sk, flag);
        /* Guarantee sacktag reordering detection against wrap-arounds */
        if (before(tp->frto_highmark, tp->snd_una))
            tp->frto_highmark = 0;
    
        if (tcp_ack_is_dubious(sk, flag)) {
            /* Advance CWND, if state allows this. */
            if ((flag & FLAG_DATA_ACKED) && !frto_cwnd &&
                tcp_may_raise_cwnd(sk, flag))
                tcp_cong_avoid(sk, ack, prior_in_flight);
            tcp_fastretrans_alert(sk, prior_packets - tp->packets_out,
                          flag);
        } else {
            if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
                tcp_cong_avoid(sk, ack, prior_in_flight);
        }
    
        if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP))
            dst_confirm(__sk_dst_get(sk));
    
        return 1;
    
    no_queue:
        /* If this ack opens up a zero window, clear backoff.  It was
         * being used to time the probes, and is probably far higher than
         * it needs to be for normal retransmission.
         */
        if (tcp_send_head(sk))
            tcp_ack_probe(sk);
        return 1;
    
    invalid_ack:
        SOCK_DEBUG(sk, "Ack %u after %u:%u
    ", ack, tp->snd_una, tp->snd_nxt);
        return -1;
    
    old_ack:
        if (TCP_SKB_CB(skb)->sacked) {//不为0,说明有携带该选项字段
            tcp_sacktag_write_queue(sk, skb, prior_snd_una);
            if (icsk->icsk_ca_state == TCP_CA_Open)
                tcp_try_keep_open(sk);
        }
    
        SOCK_DEBUG(sk, "Ack %u before %u:%u
    ", ack, tp->snd_una, tp->snd_nxt);
        return 0;
    }
    
    /*
     * 获取最近一次收到的段到目前为止的时间,
     * 即持续空闲时间。
     */
    static inline u32 keepalive_time_elapsed(const struct tcp_sock *tp)
    {
        const struct inet_connection_sock *icsk = &tp->inet_conn;
    
        return min_t(u32, tcp_time_stamp - icsk->icsk_ack.lrcvtime,
                  tcp_time_stamp - tp->rcv_tstamp);
    }
    
    static inline int tcp_fin_time(const struct sock *sk)
    {
        int fin_timeout = tcp_sk(sk)->linger2 ? : sysctl_tcp_fin_timeout;
        const int rto = inet_csk(sk)->icsk_rto;
    
       /*
        下面在来看看为什么rto的值要选择为icsk->icsk_rto的3.5倍,也就是RTO*3.5,而不是2倍、4倍呢?我们知道,在FIN_WAIT_2状态下接收到FIN包后,会给对端发送ACK包,
        完成TCP连接的关闭。但是最后的这个ACK包可能对端没有收到,在过了RTO(超时重传时间)时间后,对端会重新发送FIN包,这时需要再次给对端发送ACK包,所以TIME_WAIT
        状态的持续时间要保证对端可以重传两次FIN包。如果重传两次的话,TIME_WAIT的时间应该为RTO*(0.5+0.5+0.5)=RTO*1.5,但是这里却是RTO*3.5。这是因为在重传情况下,
        重传超时时间采用一种称为“指数退避”的方式计算。例如:当重传超时时间为1S的情况下发生了数据重传,我们就用重传超时时间为2S的定时器来重传数据,下一次用4S,
        一直增加到64S为止(参见tcp_retransmit_timer())。所以这里的RTO*3.5=RTO*0.5+RTO*1+RTO*2,其中RTO*0.5是第一次发送ACK的时间到对端的超时时间(系数就是乘以RTO
        的值),RTO*1是对端第一次重传FIN包到ACK包到达对端的超时时间,RTO*2是对端第二次重传FIN包到ACK包到达对端的超时时间。注意,重传超时时间的指数退避操作
        (就是乘以2)是在重传之后执行的,所以第一次重传的超时时间和第一次发送的超时时间相同。整个过程及时间分布如下图所示(注意:箭头虽然指向对端,只是用于描述
        过程,数据包并未被接收到):
        参考:http://blog.csdn.net/justlinux2010/article/details/9070057
       
        * 如果fin_timeout时间小于3.5*rto,则重新设置fin_timeout时间。
        */
        if (fin_timeout < (rto << 2) - (rto >> 1))
            fin_timeout = (rto << 2) - (rto >> 1);  //fin超时时间至少要保证3.5个rto
    
        return fin_timeout;
    }
    
    /*
     * Move a socket to time-wait or dead fin-wait-2 state.
     */
     
    /*
     * Move a socket to time-wait or dead fin-wait-2 state.
     */
    /*
     * @sk: 被取代的传输控制块。
     * @state: timewait控制块内部的状态,为FIN_WAIT2或TIME_WAIT
     * @timeo: 等待超时时间  //本端发送的fin已经收到确认,等待对方发送fin,或者主动关闭端收到了第二个fin进入time_wait状态
     sock结构进入TIME_WAIT状态有两种情况:一种是在真正进入了TIME_WAIT状态,还有一种是真实的状态是FIN_WAIT_2的TIME_WAIT状态。之所以让FIN_WAIT_2状态在没有
     接收到FIN包的情况下也可以进入TIME_WAIT状态是因为tcp_sock结构占用的资源要比tcp_timewait_sock结构占用的资源多,而且在TIME_WAIT下也可以处理连接的关闭。
     内核在处理时通过inet_timewait_sock结构的tw_substate成员来区分这种两种情况。
     */
     //参考:http://blog.csdn.net/justlinux2010/article/details/9070057
    /*

      tcp_tw_reuse参数用来设置是否可以在新的连接中重用TIME_WAIT状态的套接字。注意,重用的是TIME_WAIT套接字占用的端口号,而不是TIME_WAIT套接字的内存等。这个参数对客户端有意义,

    在主动发起连接的时候会在调用的inet_hash_connect()中会检查是否可以重用TIME_WAIT状态的套接字。如果你在服务器段设置这个参数的话,则没有什么作用,

    因为服务器端ESTABLISHED状态的套接字和监听套接字的本地IP、端口号是相同的,没有重用的概念。但并不是说服务器端就没有TIME_WAIT状态套接字。

    tcp_fin_timeout参数
      有些人对这个参数会有误解,认为这个参数是用来设置TIME_WAIT状态持续的时间的。linux的内核文档说的很明白,这个参数是用来设置保持在FIN_WAIT_2状态的时间.

    
    

    tcp_fin_timeout - INTEGER Time to hold socket in state FIN-WAIT-2, if it was closed
    by our side. Peer can be broken and never close its side,
    or even died unexpectedly. Default value is 60sec.
    Usual value used in 2.2 was 180 seconds, you may restore
    it, but remember that if your machine is even underloaded WEB server, you risk to overflow memory with kilotons of dead sockets,
    FIN-WAIT-2 sockets are less dangerous than FIN-WAIT-1,
    because they eat maximum 1.5K of memory, but they tend
    to live longer. Cf. tcp_max_orphans.


    */
    void tcp_time_wait(struct sock *sk, int state, int timeo) { struct inet_timewait_sock *tw = NULL; const struct inet_connection_sock *icsk = inet_csk(sk); const struct tcp_sock *tp = tcp_sk(sk); int recycle_ok = 0; /* * 如果启用tw_recycle,且ts_recent_stamp有效,则记录 * 相关时间戳信息到对端信息管理块中 tcp_timestamps参数用来设置是否启用时间戳选项,tcp_tw_recycle参数用来启用快速回收TIME_WAIT套接字。tcp_timestamps参数会影响到 tcp_tw_recycle参数的效果。如果没有时间戳选项的话,tcp_tw_recycle参数无效, */ if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp) /* * 调用的是tcp_v4_remember_stamp()。 如果没有时间戳选项,tp->rx_opt.ts_recent_stamp的值为0,这样局部变量recycle_ok的值为0,在后面就会使用默认的时间TCP_TIMEWAIT_LEN(60s) 作为TIME_WAIT状态的时间长度 允许重用timewait传输控制块,并且成功记录了时间戳,则recycle_ok为1 tcp_timestamps参数用来设置是否启用时间戳选项,tcp_tw_recycle参数用来启用快速回收TIME_WAIT套接字。tcp_timestamps参数会影响到tcp_tw_recycle参数的效果。如果没有时间戳选项的话,tcp_tw_recycle参数无效 */ recycle_ok = icsk->icsk_af_ops->remember_stamp(sk); /* * 如果当前系统中TIME_WATI状态的套接字数未 * 达到最大值,则允许分配timewait控制块。 * inet_twsk_alloc()用来分配timewait控制块,并根据 * 传输控制块设置其对应的属性和内部状态 */ if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets) tw = inet_twsk_alloc(sk, state); /* * 如果timewait控制块分配成功,则做相应设置, * 同时进入TIME_WAIT状态 */ if (tw != NULL) { //所以在TIME_WAIT套接字数量超过系统限制或者内存不足 struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw); /* * 根据超时重传时间计算TIME_WAIT状态的 * 超时时间,后者是前者的3.5倍。 * 为什么是3.5倍参见inet_twsk_schedule()函数 下面在来看看为什么rto的值要选择为icsk->icsk_rto的3.5倍,也就是RTO*3.5,而不是2倍、4倍呢?我们知道,在FIN_WAIT_2状态下接收到FIN包后,会给对 端发送ACK包,完成TCP连接的关闭。但是最后的这个ACK包可能对端没有收到,在过了RTO(超时重传时间)时间后,对端会重新发送FIN包,这时需要再次给对 端发送ACK包,所以TIME_WAIT状态的持续时间要保证对端可以重传两次FIN包。如果重传两次的话,TIME_WAIT的时间应该为RTO*(0.5+0.5+0.5)=RTO*1.5,但是 这里却是RTO*3.5。这是因为在重传情况下,重传超时时间采用一种称为“指数退避”的方式计算。例如:当重传超时时间为1S的情况下发生了数据重传,我们就用 重传超时时间为2S的定时器来重传数据,下一次用4S,一直增加到64S为止(参见tcp_retransmit_timer())。所以这里的RTO*3.5=RTO*0.5+RTO*1+RTO*2,其中 RTO*0.5是第一次发送ACK的时间到对端的超时时间(系数就是乘以RTO的值),RTO*1是对端第一次重传FIN包到ACK包到达对端的超时时间,RTO*2是对端第二次重传 FIN包到ACK包到达对端的超时时间。注意,重传超时时间的指数退避操作(就是乘以2)是在重传之后执行的,所以第一次重传的超时时间和第一次发送的超时时间 相同。整个过程及时间分布如下图所示(注意:箭头虽然指向对端,只是用于描述过程,数据包并未被接收到):参考:http://blog.csdn.net/justlinux2010/article/details/9070057 */ const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);//icsk->icsk_rto的值是超时重传的时间,这个值是根据网络情况动态计算的 /* * 从TCP控制块中获取对应的属性值 * 设置到timewait控制块中 */ tw->tw_rcv_wscale = tp->rx_opt.rcv_wscale; tcptw->tw_rcv_nxt = tp->rcv_nxt; tcptw->tw_snd_nxt = tp->snd_nxt; tcptw->tw_rcv_wnd = tcp_receive_window(tp); tcptw->tw_ts_recent = tp->rx_opt.ts_recent; tcptw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp; #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) if (tw->tw_family == PF_INET6) { struct ipv6_pinfo *np = inet6_sk(sk); struct inet6_timewait_sock *tw6; tw->tw_ipv6_offset = inet6_tw_offset(sk->sk_prot); tw6 = inet6_twsk((struct sock *)tw); ipv6_addr_copy(&tw6->tw_v6_daddr, &np->daddr); ipv6_addr_copy(&tw6->tw_v6_rcv_saddr, &np->rcv_saddr); tw->tw_ipv6only = np->ipv6only; } #endif #ifdef CONFIG_TCP_MD5SIG /* * The timewait bucket does not have the key DB from the * sock structure. We just make a quick copy of the * md5 key being used (if indeed we are using one) * so the timewait ack generating code has the key. */ do { struct tcp_md5sig_key *key; memset(tcptw->tw_md5_key, 0, sizeof(tcptw->tw_md5_key)); tcptw->tw_md5_keylen = 0; key = tp->af_specific->md5_lookup(sk, sk); if (key != NULL) { memcpy(&tcptw->tw_md5_key, key->key, key->keylen); tcptw->tw_md5_keylen = key->keylen; if (tcp_alloc_md5sig_pool(sk) == NULL) BUG(); } } while (0); #endif /* Linkage updates. */ /* * 将timewait控制块添加到tcp_hashinfo的ebash散列表中, * 将被替代的TCP控制块从ehash散列表中删除。这样 * FIN_WAIT2和TIME_WAIT状态下也可以进行输入的处理。 * 同时将该timewait控制块添加到bhash散列表中,但 * 并不删除该散列表中被替代的TCP控制块,因为 * 只要inet->num不为0,这个绑定关系就存在, * 即使该套接字已经关闭 */ __inet_twsk_hashdance(tw, sk, &tcp_hashinfo); /* Get the TIME_WAIT timeout firing. */ /* * TIME_WAIT的超时时间不得小于3.5倍的超时 * 重传的时间 */ if (timeo < rto) timeo = rto; /* * 允许重用timewait传输控制块,并且成功记录了时间戳, * 则recycle_ok为1,此时会使用rto来设置真正的TIME-WAIT * 状态的时间(参见tcp_timewait_state_process()), * 否则使用固定的TCP_TIMEWAIT_LEN来设置TIME-WAIT状态的 * 时间。 如果没有时间戳选项,tp->rx_opt.ts_recent_stamp的值为0,这样局部变量recycle_ok的值为0,在后面就会使用默认的时间TCP_TIMEWAIT_LEN(60s) 作为TIME_WAIT状态的时间长度 */ if (recycle_ok) {//在设置tcp_tw_recycle参数的情况下,tw->tw_timeout的值为rto,否则为TCP_TIMEWAIT_LEN。所以tcp_tw_recycle参数如果要实现对回收TIME_WAIT状态套接字的加速,需要这个时间rto小于TCP_TIMEWAIT_LEN。rto的值由下面的式子计算: tw->tw_timeout = rto; } else { tw->tw_timeout = TCP_TIMEWAIT_LEN; if (state == TCP_TIME_WAIT) timeo = TCP_TIMEWAIT_LEN; } /* * 进入TIME_WAIT状态,并启动TIME_WAIT定时器,超时时间 * 为timeo,但是上限为TCP_TIMEWAIT_LEN,即超时时间最多 * 不能超过TCP_TIMEWAIT_LEN。 */ inet_twsk_schedule(tw, &tcp_death_row, timeo, TCP_TIMEWAIT_LEN); inet_twsk_put(tw); //这里后会在后面释放原来的struct sock } else { /* Sorry, if we're out of memory, just CLOSE this * socket up. We've got bigger problems than * non-graceful socket closings. */ LIMIT_NETDEBUG(KERN_INFO "TCP: time wait bucket table overflow "); } /* * 将TCP中的一些测量值更新到它路由缓存项的 * 度量值中,然后关闭并释放传输控制块 */ tcp_update_metrics(sk); tcp_done(sk); } void tcp_done(struct sock *sk) { if (sk->sk_state == TCP_SYN_SENT || sk->sk_state == TCP_SYN_RECV) TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS); tcp_set_state(sk, TCP_CLOSE); tcp_clear_xmit_timers(sk); /* 设置关闭状态,这里是将发送和接收通道都关闭*/ sk->sk_shutdown = SHUTDOWN_MASK; /* * 如果SOCK_DEAD标志没有设置,则表示其他进程和它还有 * 关联,调用sk->sk_state_change来唤醒相关进程,该 * 成员函数在传输控制块状态更改时调用 */ if (!sock_flag(sk, SOCK_DEAD)) sk->sk_state_change(sk); else inet_csk_destroy_sock(sk); } /* * Process the FIN bit. This now behaves as it is supposed to work * and the FIN takes effect when it is validly part of sequence * space. Not before when we get holes. * * If we are ESTABLISHED, a received fin moves us to CLOSE-WAIT * (and thence onto LAST-ACK and finally, CLOSE, we never enter * TIME-WAIT) * * If we are in FINWAIT-1, a received FIN indicates simultaneous * close and we go into CLOSING (and later onto TIME-WAIT) * * If we are in FINWAIT-2, a received FIN moves us to TIME-WAIT. */ /* * 在TCP中还有些地方会通知套接字的faync_list队列 * 上的进程。比如,当TCP接收到FIN段后,如果 * 此时套接字未在DEAD状态,则唤醒等待该套接 * 字的进程。如果在发送接收方向都进行了关闭, * 或者此时该传输控制块处于CLOSE状态,则通知 * 异步等待该套接字的进程,该连接已经终止, * 否则通知进程连接可以进行写操作。 */ static void tcp_fin(struct sk_buff *skb, struct sock *sk, struct tcphdr *th) { struct tcp_sock *tp = tcp_sk(sk); inet_csk_schedule_ack(sk); sk->sk_shutdown |= RCV_SHUTDOWN; sock_set_flag(sk, SOCK_DONE); switch (sk->sk_state) { //状态变迁产考TCP/IP详解18.2 /* * 在SYN_RECV和ESTABLISHED状态,接收到FIN段后, * 将状态设置为CLOSE_WAIT,并确定延时发送 * ACK */ case TCP_SYN_RECV: case TCP_ESTABLISHED: /* Move to CLOSE_WAIT */ tcp_set_state(sk, TCP_CLOSE_WAIT); inet_csk(sk)->icsk_ack.pingpong = 1; break; /* * 在CLOSE_WAIT状态接收到FIN段,则说明 * 收到的是重复的FIN段,忽略。 * 在CLOSING状态接收FIN段,也将其忽略, * 因为在该状态只需等待ACK。 */ case TCP_CLOSE_WAIT: case TCP_CLOSING: /* Received a retransmission of the FIN, do * nothing. */ break; case TCP_LAST_ACK: /* RFC793: Remain in the LAST-ACK state. */ break; /* * 根据TCP状态迁移图,在FIN_WAIT1状态接收 * FIN段,则发送ACK,进入CLOSING状态,并 * 等待对端的ACK */ case TCP_FIN_WAIT1: /* This case occurs when a simultaneous close * happens, we must ack the received FIN and * enter the CLOSING state. */ tcp_send_ack(sk); tcp_set_state(sk, TCP_CLOSING); break; /* * 根据TCP状态迁移图,在FIN_WAIT2状态接收FIN段, * 则发送ACK,进入TIME_WAIT状态 */ case TCP_FIN_WAIT2: /* Received a FIN -- send ACK and enter TIME_WAIT. */ tcp_send_ack(sk); tcp_time_wait(sk, TCP_TIME_WAIT, 0); break; /* * 在LISTEN和CLOSE状态忽略FIN段 */ default: /* Only TCP_LISTEN and TCP_CLOSE are left, in these * cases we should never reach this piece of code. */ printk(KERN_ERR "%s: Impossible, sk->sk_state=%d ", __func__, sk->sk_state); break; } /* It _is_ possible, that we have something out-of-order _after_ FIN. * Probably, we should reset in this case. For now drop them. */ /* * 如果此时套接字未处于DEAD状态,则唤醒 * 等待该套接字的进程。如果在发送接收 * 方向上都进行了关闭,或此时该传输控制 * 块处于CLOSE状态,则唤醒异步等待该套接字 * 的进程,通知它们该链接已终止,否则通知 * 它们连接可以进行写操作 */ //被动断开的一方在收到FIN的时候,内核要通知给应用程序 __skb_queue_purge(&tp->out_of_order_queue); if (tcp_is_sack(tp)) tcp_sack_reset(&tp->rx_opt); sk_mem_reclaim(sk); if (!sock_flag(sk, SOCK_DEAD)) { sk->sk_state_change(sk); /* Do not send POLL_HUP for half duplex close. */ if (sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == TCP_CLOSE) sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_HUP); else sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN); } } /* When we get a reset we do this. */ static void tcp_reset(struct sock *sk) { /* We want the right error as BSD sees it (and indeed as we do). */ switch (sk->sk_state) { case TCP_SYN_SENT: sk->sk_err = ECONNREFUSED; break; case TCP_CLOSE_WAIT: sk->sk_err = EPIPE; break; case TCP_CLOSE: return; default: sk->sk_err = ECONNRESET; } /* This barrier is coupled with smp_rmb() in tcp_poll() */ smp_wmb(); if (!sock_flag(sk, SOCK_DEAD)) sk->sk_error_report(sk); tcp_done(sk); } /* * 在SYN_SENT状态下接收到的段,除紧急数据外其他的都由 * tcp_rcv_state_process()处理,以下代码显示的是SYN_SENT状态下接 * 收处理SYN+ACK段的情况. */ //客户端发送SYN后,收到SYN+ACK或者对端SYN(也就是两端同时发送SYN)的情况。 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, unsigned len) { struct tcp_sock *tp = tcp_sk(sk); struct inet_connection_sock *icsk = inet_csk(sk); int saved_clamp = tp->rx_opt.mss_clamp; /* * 解析段中的TCP选项,并保存到传输控制块中. */ tcp_parse_options(skb, &tp->rx_opt, 0); if (th->ack) { /* rfc793: * "If the state is SYN-SENT then * first check the ACK bit * If the ACK bit is set * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send * a reset (unless the RST bit is set, if so drop * the segment and return)" * * We do not send data with SYN, so that RFC-correct * test reduces to: */ if (TCP_SKB_CB(skb)->ack_seq != tp->snd_nxt)// goto reset_and_undo; if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr && !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, tcp_time_stamp)) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSACTIVEREJECTED); goto reset_and_undo; } /* Now ACK is acceptable. * * "If the RST bit is set * If the ACK was acceptable then signal the user "error: * connection reset", drop the segment, enter CLOSED state, * delete TCB, and return." */ /* * 在SYN_SENT状态下接收到了ACK+RST段,需调用tcp_reset() * 设置ECONNREFUSED错误码,同时通知等待该套接字的 * 进程,然后关闭该套接字. */ if (th->rst) { tcp_reset(sk); goto discard; } /* rfc793: * "fifth, if neither of the SYN or RST bits is set then * drop the segment and return." * * See note below! * --ANK(990513) */ /* * 在SYN_SENT状态下接收到的段必须存在SYN标志,否则 * 说明接收到的段无效,需跳转到discard_and_undo处执行, * 清除解析得到的TCP选项,然后丢弃该段. */ if (!th->syn) goto discard_and_undo; /* rfc793: * "If the SYN bit is on ... * are acceptable then ... * (our SYN has been ACKed), change the connection * state to ESTABLISHED..." */ /* * 从TCP首部标志中获取支持显式拥塞通知的特性. * 对于支持ECN的TCP段来说,SYN的ACK只设置ECE标志. * 如果接收到的段与之不符,表示对端不支持显式 * 拥塞通知. */ TCP_ECN_rcv_synack(tp, th); /* * 初始化与窗口有关的成员变量. */ tp->snd_wl1 = TCP_SKB_CB(skb)->seq; tcp_ack(sk, skb, FLAG_SLOWPATH); /* Ok.. it's good. Set up sequence numbers and * move to established. */ tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; /* RFC1323: The window in SYN & SYN/ACK segments is * never scaled. */ tp->snd_wnd = ntohs(th->window); tcp_init_wl(tp, TCP_SKB_CB(skb)->seq); if (!tp->rx_opt.wscale_ok) { tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0; tp->window_clamp = min(tp->window_clamp, 65535U); } /* * 根据是否支持时间戳选项来设置传输控制块 * 的相关字段. */ if (tp->rx_opt.saw_tstamp) { tp->rx_opt.tstamp_ok = 1; tp->tcp_header_len = sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;//如果在协商的时候TCP选项字段中支持时间戳选项,则以后的报文中都会带这个选项,所以tcp长度一直加了这个值 tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;//减这个是因为报文里面一直多了时间搓选项 tcp_store_ts_recent(tp); } else { tp->tcp_header_len = sizeof(struct tcphdr); } /* * 初始化PMTU、MSS等成员变量。 */ if (tcp_is_sack(tp) && sysctl_tcp_fack) tcp_enable_fack(tp); tcp_mtup_init(sk); tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); tcp_initialize_rcv_mss(sk); /* Remember, tcp_poll() does not lock socket! * Change state from SYN-SENT only after copied_seq * is initialized. */ tp->copied_seq = tp->rcv_nxt; smp_mb(); tcp_set_state(sk, TCP_ESTABLISHED); security_inet_conn_established(sk, skb); /* Make sure socket is routed, for correct metrics. */ icsk->icsk_af_ops->rebuild_header(sk); tcp_init_metrics(sk); tcp_init_congestion_control(sk); /* Prevent spurious tcp_cwnd_restart() on first data * packet. */ tp->lsndtime = tcp_time_stamp; tcp_init_buffer_space(sk); /* * 如果启用了连接保活,则启用连接保活 * 定时器。 */ if (sock_flag(sk, SOCK_KEEPOPEN)) inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp)); /* * 设置首部预测标志。 */ if (!tp->rx_opt.snd_wscale) __tcp_fast_path_on(tp, tp->snd_wnd); else tp->pred_flags = 0; /* * 如果套接字不处于SOCK_DEAD状态,则唤醒等待该 * 套接字的进程,同时向套接字的异步等待列表 * 上的进程发送信号,通知它们该套接字可以输 * 出数据了。 */ if (!sock_flag(sk, SOCK_DEAD)) { sk->sk_state_change(sk); sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); } /* * 连接建立完成,根据情况进入延时确认模式 */ if (sk->sk_write_pending || icsk->icsk_accept_queue.rskq_defer_accept || icsk->icsk_ack.pingpong) { /* Save one ACK. Data will be ready after * several ticks, if write_pending is set. * * It may be deleted, but with this feature tcpdumps * look so _wonderfully_ clever, that I was not able * to stand against the temptation 8) --ANK */ inet_csk_schedule_ack(sk); icsk->icsk_ack.lrcvtime = tcp_time_stamp; icsk->icsk_ack.ato = TCP_ATO_MIN; tcp_incr_quickack(sk); tcp_enter_quickack_mode(sk); inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX); discard: __kfree_skb(skb); return 0; } else { tcp_send_ack(sk); } return -1; } /* No ACK in the segment */ /* * 在SYN_SENT状态下如果接收到RST段,则跳转到 * discard_and_undo处,清除解析得到的TCP选项,并 * 将传输控制块丢弃。 */ if (th->rst) { /* rfc793: * "If the RST bit is set * * Otherwise (no ACK) drop the segment and return." */ goto discard_and_undo; } /* PAWS check. */ /* * 如果PAWS检测无效,则跳转到discard_and_undo处, * 清除解析得到的TCP选项,并将传输控制块 * 丢弃。 */ if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp && tcp_paws_reject(&tp->rx_opt, 0)) goto discard_and_undo; /* * 处理接收没有ACK标志的SYN段,处理同时打开的 * 情况。 * * 同时打开情况如下: * 每一段必须发送一个SYN,且这两个SYN必须都传 * 递到对端,这需要每一端都使用一个对端熟知 * 的端口作为本地端口,这称为同时打开。 * 当出现同时打开情况时,两端几乎在同时发送 * SYN并进入SYN_SENT状态;当每一端接收到SYN后状 * 态变为SYN_RECVD,发送SYN并对收到的SYN进行确认; * 当双方都收到对端的SYN及相应的ACK,状态变迁 * 为ESTABLISHED。 */ if (th->syn) {//同时打开,见樊东东 下P828 /* We see SYN without ACK. It is attempt of * simultaneous connect with crossed SYNs. * Particularly, it can be connect to self. */ /* * 在SYN_SENT状态下接收到SYN段,将其状态 * 设置为SYN_RECV。 */ tcp_set_state(sk, TCP_SYN_RECV); /* * 根据是否支持时间戳选项,设置TCP * 传输控制块相应的字段。 */ if (tp->rx_opt.saw_tstamp) { tp->rx_opt.tstamp_ok = 1; tcp_store_ts_recent(tp); tp->tcp_header_len = sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; } else { tp->tcp_header_len = sizeof(struct tcphdr); } /* * 初始化窗口相关的成员变量。 */ tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; /* RFC1323: The window in SYN & SYN/ACK segments is * never scaled. */ tp->snd_wnd = ntohs(th->window); tp->snd_wl1 = TCP_SKB_CB(skb)->seq; tp->max_window = tp->snd_wnd; /* * 从TCP首部标志中获取支持显式拥塞通知的特性。 * 对于支持ECN的TCP端来说,SYN段TCP首部中有设置 * ECE及CWR标志,接收到的段中有任何一个标志来 * 设置,都表示对端不支持显式拥塞通知。 */ TCP_ECN_rcv_syn(tp, th); /* * 初始化PMTU、MSS等成员变量。 */ tcp_mtup_init(sk); tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); tcp_initialize_rcv_mss(sk); /* * 最后发送SYN+ACK段给对端,并丢弃接收到的 * SYN段。 */ tcp_send_synack(sk); #if 0 /* Note, we could accept data and URG from this segment. * There are no obstacles to make this. * * However, if we ignore data in ACKless segments sometimes, * we have no reasons to accept it sometimes. * Also, seems the code doing it in step6 of tcp_rcv_state_process * is not flawless. So, discard packet for sanity. * Uncomment this return to process the data. */ return -1; #else goto discard; #endif } /* "fifth, if neither of the SYN or RST bits is set then * drop the segment and return." */ discard_and_undo: tcp_clear_options(&tp->rx_opt); tp->rx_opt.mss_clamp = saved_clamp; goto discard; reset_and_undo: tcp_clear_options(&tp->rx_opt); tp->rx_opt.mss_clamp = saved_clamp; return 1; } /* Does PAWS and seqno based validation of an incoming segment, flags will * play significant role here. */ static int tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, int syn_inerr) { struct tcp_sock *tp = tcp_sk(sk); /* RFC1323: H1. Apply PAWS check first. */ /* * 调用tcp_fast_parse_options()解析TCP选项,并检查时间戳 * 选项。如果首部中存在时间戳,但PAWS校验失败, * 并且不存在RST标志则还还需发送DACK给对端,说明 * 接收到的TCP段已确认过,然后丢弃该数据包 */ 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; } /* Reset is accepted even if it did not pass PAWS. */ } /* Step 1: check sequence number */ /* * 调用tcp_sequence()检测接收到的段序号是否在 * 接收窗口内,如果不是,则丢弃该数据包。 * 如果不是复位段(TCP首部中有RST标志)则还 * 需发送DACK给对端,说明接收到的TCP段不在 * 接收窗口内。 */ 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; } /* Step 2: check RST bit */ /* * 如果接收的段是复位段,则处理完 * 复位后丢弃该段。 */ if (th->rst) { tcp_reset(sk); goto discard; } /* ts_recent update must be made after we are sure that the packet * is in window. */ /* * 如果TCP首部中存在时间戳选项且有效, * 则保存该时间戳 */ tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq); /* step 3: check security and precedence [ignored] */ /* step 4: Check for a SYN in window. */ /* * 已建立连接的TCP接收到SYN段,则说明对端 * 发送了错误的信息,调用tcp_reset()作复位处理。 */ if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) { if (syn_inerr) 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; } return 1; discard: __kfree_skb(skb); return 0; } /* Look for tcp options. Normally only called on SYN and SYNACK packets. * But, this can also be called on packets in the established flow when * the fast version below fails. *//* * tcp_parse_options()解析TCP选项,但通常只在分析SYN和SYN+ACK * 段时被调用,此外慢速路径处理中,如果调用tcp_fast_parse_options() * 失败,也可能在建立连接后调用之。 */ void tcp_parse_options(struct sk_buff *skb, struct tcp_options_received *opt_rx, u8 **hvpp, int estab) { unsigned char *ptr; struct tcphdr *th = tcp_hdr(skb); /* * 如果TCP首部中包含TCP选项,即该首部 * 长度大于不包含TCP选项的普通TCP首部。 */ int length = (th->doff * 4) - sizeof(struct tcphdr); ptr = (unsigned char *)(th + 1); opt_rx->saw_tstamp = 0; while (length > 0) { int opcode = *ptr++; int opsize; /* * 取选项的第二个字节,即kind,根据 * 其值分别作处理 */ switch (opcode) { /* * TCPOPT_EOL表示选项表结束,因此 * 直接返回 */ case TCPOPT_EOL: return; /* * TCPOPT_NOP为空操作,只做填充用, * 因此选项长度减1,进入下一个 * 循环处理下一个选项 */ case TCPOPT_NOP: /* Ref: RFC 793 section 3.1 */ length--; continue; default: /* * 如果不是选项表结束标志也不是 * 空操作,则取选项长度,并检测 * 其合法性 */ opsize = *ptr++; if (opsize < 2) /* "silly options" */ return; if (opsize > length) return; /* don't parse partial options */ switch (opcode) { /* * TCPOPT_MSS用来通告最大段长度,TCPOPT_MSS * 宏定义为4,又因为MSS选项只能出现在 * SYN段中,因此判断TCP头部中SYN是否置位, * 此时调用参数estab应该为0.取最大段长度MSS, * user_mss是用户以TCP_MAXSEG选项调用setsockopt() * 设置的,将最大段长度上限值mss_clamp * 设置为选项中的MSS和user_mss两者中的较小值。 */ case TCPOPT_MSS: if (opsize == TCPOLEN_MSS && th->syn && !estab) { u16 in_mss = get_unaligned_be16(ptr); if (in_mss) { if (opt_rx->user_mss && opt_rx->user_mss < in_mss) in_mss = opt_rx->user_mss; opt_rx->mss_clamp = in_mss; } } break; /* * TCPOPT_WINDOW是窗口扩大因子选项,和最大 * 段长度选项一样,也只能出现在SYN段中, * 因此作同样的判断。 */ case TCPOPT_WINDOW: if (opsize == TCPOLEN_WINDOW && th->syn && !estab && sysctl_tcp_window_scaling) { /* * 取窗口扩大因子选项中的 * 位移数,将表示SYN段中包含 * 扩大因子选项的wscale_ok置为1, * 如果选项中的位移数大于14则 * 警告,和网络相关的pintk都必须 * 由net_ratelimit来监控。发送窗口 * 扩大因子snd_wscale赋值为位移数和 * 14两者中较小的值。 */ __u8 snd_wscale = *(__u8 *)ptr; opt_rx->wscale_ok = 1; if (snd_wscale > 14) { if (net_ratelimit()) printk(KERN_INFO "tcp_parse_options: Illegal window " "scaling value %d >14 received. ", snd_wscale); snd_wscale = 14; } opt_rx->snd_wscale = snd_wscale; } break; /* * 时间戳选项 */ case TCPOPT_TIMESTAMP: if ((opsize == TCPOLEN_TIMESTAMP) && ((estab && opt_rx->tstamp_ok) || (!estab && sysctl_tcp_timestamps))) { opt_rx->saw_tstamp = 1; opt_rx->rcv_tsval = get_unaligned_be32(ptr); opt_rx->rcv_tsecr = get_unaligned_be32(ptr + 4); } break; /* * TCPOPT_SACK_PERM为允许SACK选项,只能出现在 * SYN段中。将sack_ok置为1,表示SYN段中允许 * SACK选项。tcp_sack_reset()将tcp_options_received结构 * 中的三个与SACK有关的字段dsack、eff_sacks和 * num_sacks清零。 */ case TCPOPT_SACK_PERM: if (opsize == TCPOLEN_SACK_PERM && th->syn && !estab && sysctl_tcp_sack) { opt_rx->sack_ok = 1; tcp_sack_reset(opt_rx); } break; /* * TCPOLEN_SACK_BASE为2,TCPOLEN_SACK_PERBLOCK为8,因此 * if语句的意思是选项长度包含kind、length字段以及 * 一个至多个左右边界值对,且扣除kind和length字段 * 长度后能被左右边界值对大小整除,sack_ok为1表示 * 允许SACK。将tcp_skb_cb的sacked字段指向这些左右边界值 * 对的开始处。 */ case TCPOPT_SACK: if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) && !((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) && opt_rx->sack_ok) { TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *)th; } break; #ifdef CONFIG_TCP_MD5SIG case TCPOPT_MD5SIG: /* * The MD5 Hash has already been * checked (see tcp_v{4,6}_do_rcv()). */ break; #endif case TCPOPT_COOKIE: /* This option is variable length. */ switch (opsize) { case TCPOLEN_COOKIE_BASE: /* not yet implemented */ break; case TCPOLEN_COOKIE_PAIR: /* not yet implemented */ break; case TCPOLEN_COOKIE_MIN+0: case TCPOLEN_COOKIE_MIN+2: case TCPOLEN_COOKIE_MIN+4: case TCPOLEN_COOKIE_MIN+6: case TCPOLEN_COOKIE_MAX: /* 16-bit multiple */ opt_rx->cookie_plus = opsize; *hvpp = ptr; break; default: /* ignore option */ break; } break; } ptr += opsize-2; length -= opsize; } } }
  • 相关阅读:
    JAVA动态代理
    图解 Tomcat 体系结构
    AdvancedDataGrid的使用
    You have an error in your SQL syntax; check the manual that corresponds...错误解决方案
    更新整站索引时失败,错误原因: [Incorrect integer value: `` for column `uptime` at row 1]
    dedecms转换v9 卡住的解决办法
    IIS 涉及到500和403或者404友好错误的设置!
    PHP Warning: date(): It is not safe to rely on the system's timezone settings
    不能读取记录;在 'MSysObjects' 上没有读取数据权限
    PHP5.3.5如何连接MSSql Server2005
  • 原文地址:https://www.cnblogs.com/codestack/p/11117461.html
Copyright © 2011-2022 走看看