Linux SYNACK报文的接收及ACK报文的发送

注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

随着SYNACK报文的发送,连接建立随着第二次握手报文来到客户端。客户端接收到这个SYNACK报文,就认为连接建立了。仍然从TCP层开始分析,依然是由tcp_v4_rcv()入手。

int tcp_v4_rcv(struct sk_buff *skb)
{
...
    //根据报文的源和目的地址在established哈希表以及listen哈希表中查找连接
    //由于之前调用connect时已经将连接加入到established哈希表中
    //所以在接收到服务端的SYNACK时,就能从established表中找到对应的连接
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    if (!sk)
        goto no_tcp_socket;
...
    ret = 0;
    if (!sock_owned_by_user(sk)) {//如果sk没有被用户锁定,及没在使用
        if (!tcp_prequeue(sk, skb))
            ret = tcp_v4_do_rcv(sk, skb);//进入到主处理函数

    }
...
}

然后还是来到老地方——tcp_v4_do_rcv()

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
    struct sock *rsk;

    if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
        ...
    }

    if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
        goto csum_err;

    if (sk->sk_state == TCP_LISTEN) {
        ...
    } else
        sock_rps_save_rxhash(sk, skb);

    if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
        rsk = sk;
        goto reset;
    }
    return 0;
...
}

不过因为此时我们socket的状态是SYN_SENT,所以就直接进入tcp_rcv_state_process()的处理流程了。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
              const struct tcphdr *th, unsigned int len)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct request_sock *req;
    int queued = 0;
    bool acceptable;
    u32 synack_stamp;

    tp->rx_opt.saw_tstamp = 0;

    switch (sk->sk_state) {
...
    case TCP_SYN_SENT:
        //进入到synack报文的处理流程
        queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
        if (queued >= 0)
            return queued;

        /* Do step6 onward by hand. */
        tcp_urg(sk, skb, th);
        __kfree_skb(skb);
        tcp_data_snd_check(sk);
        return 0;
    }
...
}

层层深入,最后进入的归宿是tcp_rcv_synsent_state_process()

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
                     const struct tcphdr *th, unsigned int len)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    struct tcp_fastopen_cookie foc = { .len = -1 };
    int saved_clamp = tp->rx_opt.mss_clamp;
    //分析TCP选项
    tcp_parse_options(skb, &tp->rx_opt, 0, &foc);
    if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
        tp->rx_opt.rcv_tsecr -= tp->tsoffset;

    if (th->ack) {//处理带ACK标志的报文
        //如果接收到的确认号小于或等于已发送未确认的序列号,
        //或者大于下次要发送数据的序列号,非法报文,发送RST报文
        if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
            after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
            goto reset_and_undo;

        //如果开启了时间戳选项,且回显时间戳不为空
        if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
            //且回显时间戳不在当前时间和SYN报文发送的时间窗内,就认为该报文非法
            !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;
        }

        if (th->rst) {//ack报文不允许出现rst标志
            tcp_reset(sk);
            goto discard;
        }

        if (!th->syn)//除了上面的几种标志位和SYN标志位,其余报文都丢弃
            goto discard_and_undo;

        TCP_ECN_rcv_synack(tp, th);

        tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
        //确认ACK的确认号正常
        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);

        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;
            tp->advmss      -= TCPOLEN_TSTAMP_ALIGNED;
            tcp_store_ts_recent(tp);//记录对端的时间戳
        } else {
            tp->tcp_header_len = sizeof(struct tcphdr);
        }

        if (tcp_is_sack(tp) && sysctl_tcp_fack)
            tcp_enable_fack(tp);

        tcp_mtup_init(sk);//mtu探测初始化
        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();
        //连接建立完成,将连接状态推向established
        //然后唤醒等在该socket的所有睡眠进程
        tcp_finish_connect(sk, skb);
        //快速开启选项
        if ((tp->syn_fastopen || tp->syn_data) &&
            tcp_rcv_fastopen_synack(sk, skb, &foc))
            return -1;

        /* 如果有以下情况,不会马上发送ACK报文
         * 1.有数据等待发送
         * 2.用户设置了TCP_DEFER_ACCEPT选项
         * 3.禁用快速确认模式,可通过TCP_QUICKACK设置
        */
        if (sk->sk_write_pending ||
            icsk->icsk_accept_queue.rskq_defer_accept ||
            icsk->icsk_ack.pingpong) {

            //设置ICSK_ACK_SCHED标识,有ACK等待发送,当前不发送
            inet_csk_schedule_ack(sk);
            //最后一次接收到数据包的时间
            icsk->icsk_ack.lrcvtime = tcp_time_stamp;
            //设置快速确认模式,以及快速确认模式下可以发送的ACK报文数
            tcp_enter_quickack_mode(sk);
            //激活延迟ACK定时器,超时时间为200ms
            //最多延迟200ms就会发送ACK报文
            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);//否则马上发送ACK报文,即第三次握手报文
        }
        return -1;
    }
    //没有ACK标记,只有RST标记,丢弃报文
    if (th->rst) {

        goto discard_and_undo;
    }

    /* PAWS check. */
    //话说这个PAMS到底是个啥。。。
    if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
        tcp_paws_reject(&tp->rx_opt, 0))
        goto discard_and_undo;

    //在SYNSENT状态收到syn报文,说明这是同时打开的场景
    if (th->syn) {
        /* We see SYN without ACK. It is attempt of
         * simultaneous connect with crossed SYNs.
         * Particularly, it can be connect to self.
         */
        //设置连接状态为SYN_RECV
        tcp_set_state(sk, TCP_SYN_RECV);
        ...//暂不分析
    }
...
}

我们知道,客户端收到这个SYNACK报文后就会进入ESTABLISHED状态,这主要是tcp_finish_connect()里操作的。

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct inet_connection_sock *icsk = inet_csk(sk);
    //对于客户端来说,此时连接已经建立,设置连接状态为established
    tcp_set_state(sk, TCP_ESTABLISHED);

    if (skb != NULL) {
        icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
        security_inet_conn_established(sk, skb);
    }

    /* Make sure socket is routed, for correct metrics.  */
    icsk->icsk_af_ops->rebuild_header(sk);
    //初始化TCP metrics,用于保存连接相关的路由信息 
    tcp_init_metrics(sk);
    //初始化拥塞控制
    tcp_init_congestion_control(sk);

    //记录最后一个数据包发送的时间戳
    tp->lsndtime = tcp_time_stamp;
    //初始化接收缓存和发送缓存
    tcp_init_buffer_space(sk);
    //如果使用了SO_KEEPALIVE选项,那就激活保活定时器
    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;

    if (!sock_flag(sk, SOCK_DEAD)) {
        //指向sock_def_wakeup,唤醒该socket上所有睡眠的进程
        sk->sk_state_change(sk);
        //如果进程使用了异步通知,发送SIGIO信号通知进程可写
        sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
    }
}

tcp_finish_connect()中主要有以下几点重要操作:

  1. 将socket状态推向ESTABLISHED,也就意味着在客户端来看,连接已经建立
  2. 然后是初始化路由和拥塞控制等一下参数
  3. 同时如果用户开启了保活定时器,此时开始生效,计算连接空闲时间
  4. 最后就是唤醒该socket上所有睡眠的进程,如果有进程使用异步通知,则发送SIGIO信号通知进程可写

最后就是发送第三次握手报文——ACK报文。不过ACK报文并不一定是马上发送,在一下几种情况下会延迟发送。

  1. 当前刚好有数据等待发送
  2. 用户设置了TCP_DEFER_ACCEPT选项
  3. 禁用快速确认模式,可通过TCP_QUICKACK选项设置

不过即使延迟,也最多延迟200ms,这个通过延迟ACK定时器操作。

如果是马上发送ACK报文,则通过tcp_send_ack()发送。

/* This routine sends an ack and also updates the window. */
void tcp_send_ack(struct sock *sk)
{
    struct sk_buff *buff;

    /* If we have been reset, we may not send again. */
    if (sk->sk_state == TCP_CLOSE)
        return;

    tcp_ca_event(sk, CA_EVENT_NON_DELAYED_ACK);

    /* We are not putting this on the write queue, so
     * tcp_transmit_skb() will set the ownership to this
     * sock.
     */
    buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));
    if (buff == NULL) {//分配失败
        //和上面讲到的延迟ACK一样,设置延迟ACK,稍后再发送
        inet_csk_schedule_ack(sk);
        //超时时间为200ms
        inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN;
        //激活延迟ACK定时器
        inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
                      TCP_DELACK_MAX, TCP_RTO_MAX);
        return;
    }

    /* Reserve space for headers and prepare control bits. */
    skb_reserve(buff, MAX_TCP_HEADER);
    //初始化无数据的skb
    tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);

    /* We do not want pure acks influencing TCP Small Queues or fq/pacing
     * too much.
     * SKB_TRUESIZE(max(1 .. 66, MAX_TCP_HEADER)) is unfortunately ~784
     * We also avoid tcp_wfree() overhead (cache line miss accessing
     * tp->tsq_flags) by using regular sock_wfree()
     */
    skb_set_tcp_pure_ack(buff);

    /* Send it off, this clears delayed acks for us. */
    skb_mstamp_get(&buff->skb_mstamp);
    //又到了这个路径,往后就是IP层了
    tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));
}

猜你喜欢

转载自blog.csdn.net/u010039418/article/details/80554976
今日推荐