Linux-5.9三次握手源码学习

  正常情况下,三次握手的步骤如下:

1.客户端发送 SYN 包,包里携带一个序列号(随机数)

2.服务端接收到一个 SYN 包,发送 ACK 包,包里携带一个序列号(随机数)和一个响应号(SYN 包序列号加一)。

3.客户端接收到 ACK 包,比较以下响应号是否是自己的序列号加一,如果是,则握手成功。

  文字描述见该文档,此处仅进行代码注释。

一、Listen

1.inet_listen 函数

    //socket 的状态是未连接或 socket 类型是数据流类型

    if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
        goto out;
  //sk_state 是 socket 过去的状态,其值是来自一个枚举数据结构,判断状态是否是 close 或 listen ,如果是的话,listen 失败。
    old_state = sk->sk_state;
    if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
        goto out;
    //将 backlog 写入,这个值据说被写入到 sk_max_ack_backlog 后,半连接数超过这个值时,会丢弃新的半连接。如果开启了 SYN cookie 的话,超过这个值会发送 cookie。
    WRITE_ONCE(sk->sk_max_ack_backlog, backlog);
    //这句注释是说,如果 socket 已经时 listen 状态的话,只能修改 backlog。
    /* Really, if the socket is already in listen state
     * we can only allow the backlog to be adjusted.
     */
    if (old_state != TCP_LISTEN) {
        /* Enable TFO w/o requiring TCP_FASTOPEN socket option.
         * Note that only TCP sockets (SOCK_STREAM) will reach here.
         * Also fastopen backlog may already been set via the option
         * because the socket was in TCP_LISTEN state previously but
         * was shutdown() rather than close().
         */
        tcp_fastopen = sock_net(sk)->ipv4.sysctl_tcp_fastopen;
        if ((tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
            (tcp_fastopen & TFO_SERVER_ENABLE) &&
            !inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
            fastopen_queue_tune(sk, backlog);
            tcp_fastopen_init_key_once(sock_net(sk));
        }
   //这个函数也很重要
        err = inet_csk_listen_start(sk);
        if (err)
            goto out;
        tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_LISTEN_CB, 0, NULL);
    }

2.inet_csk_listen_start 函数

    //初始化全连接队列

    reqsk_queue_alloc(&icsk->icsk_accept_queue);

    sk->sk_ack_backlog = 0;
    inet_csk_delack_init(sk);

    /* There is race window here: we announce ourselves listening,
     * but this transition is still not validated by get_port().
     * It is OK, because this socket enters to hash table only
     * after validation is complete.
     */
    //设置当前状态为 TCP_LISTEN
    inet_sk_state_store(sk, TCP_LISTEN);
    //检查端口号,如果 get_port 函数与 bind 时的函数一样,返回 0,其中 inet_num 是 bind 绑定时用的端口。
    if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
        //改成大端格式
        inet->inet_sport = htons(inet->inet_num);

        sk_dst_reset(sk);
        //把 socket 添加到哈希表中
        err = sk->sk_prot->hash(sk);

        if (likely(!err))
            return 0;
    }
    //如果端口不可用,设为i CLOSE
    inet_sk_set_state(sk, TCP_CLOSE);

二、Connect

1.tcp_v4_connect函数

……

    /* Socket identity is still unknown (sport may be zero).
     * However we set state to SYN-SENT and not releasing socket
     * lock select source port, enter ourselves into the hash tables and
     * complete initialization after this.
     */
    //此时套接字仍然未知(源端口号可能为 0),但可以将状态设为 SYN-SENT 并且不释放套接字选择源端口,将自己放入哈希表,在此之后完成初始化。
    tcp_set_state(sk, TCP_SYN_SENT);
    err = inet_hash_connect(tcp_death_row, sk);
    if (err)
        goto failure;

 ……

    //构建完整的 SYN 包并将其发送出去。

    err = tcp_connect(sk);
    if (err)
        goto failure;

2.tcp_connect 函数

    //完成所有可以独立完成的套接字设置

    tcp_connect_init(sk);
    //申请空间
    buff = tcp_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
    if (unlikely(!buff))
        return -ENOBUFS;
    //准备数据
    tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
    tcp_mstamp_refresh(tp);
    tp->retrans_stamp = tcp_time_stamp(tp);
    //添加到发送队列
    tcp_connect_queue_skb(sk, buff);
    tcp_ecn_send_syn(sk, buff);
    tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);
 
    /* Send off SYN; include data in Fast Open. */
    //发送 SYN 包,在 Fast Open 中包含数据
    err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
          tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
    if (err == -ECONNREFUSED)
        return err;
 
    /* Timer for repeating the SYN until an answer. */
    //启动重传计时器,TCP_RTO_MAX 是最大重传时间,单位应该是秒
    inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
                  inet_csk(sk)->icsk_rto, TCP_RTO_MAX);

三、服务器响应 SYN

 1.tcp_v4_do_rcv 函数

/* The socket must have it's spinlock held when we get
 * here, unless it is a TCP_LISTEN socket.
 *
 * 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.
 */

 //当我们到达这里时,socket 必须保持自己的自旋锁,除非他是一个 LISTEN 套接字。

//我们有一个潜在的双锁方案,即使在进行 backlog 处理时,也使用 BH 锁方案。

//因为我们不能在持有自旋锁下进行睡眠。

    //判断状态是否是 listen 状态,事实上确实是 listen 状态

    if (sk->sk_state == TCP_LISTEN) {
        //从半连接队列里检查是否有 socket ,由于有 TFO 的存在,这里分了两种情况,后面单独列出 tcp_v4_cookie_check 函数。
        //在接收 SYN 包时,没有 ACK 字段,可以忽略
        struct sock *nsk = tcp_v4_cookie_check(sk, skb);
……
    //根据不同的状态做出不同的处理
    if (tcp_rcv_state_process(sk, skb)) {
        rsk = sk;
        goto reset;
    }

2.tcp_v4_cookie_check 函数

static struct sock *tcp_v4_cookie_check(struct sock *sk, struct sk_buff *skb)
{
//看这个宏有没有定义,有定义就取出链表中的连接,并校验它的 cookie ,否则直接返回传入的 socket
#ifdef CONFIG_SYN_COOKIES
    const struct tcphdr *th = tcp_hdr(skb);

    if (!th->syn)
        sk = cookie_v4_check(sk, skb);
#endif
    return sk;
}

3.tcp_rcv_state_process 函数

/*
 *  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.
 */
//这个函数为除了建立超时之外的所有状态提供 RFC 793 的接收流程。可以被 tcp_v4_rcv 和 ftcp_v6_rcv 调用。
    //当前处于 listen 状态
    case TCP_LISTEN:
        //如果 ACK 为 1 ,返回
        if (th->ack)
            return 1;
        //如果 RST 为 1 ,删除
        if (th->rst)
            goto discard;
        //SYN 为真时,进行处理
        if (th->syn) {
            //如果 FIN 位为 1 ,删除
            if (th->fin)
                goto discard;
            /* It is possible that we process SYN packets from backlog,
             * so we need to make sure to disable BH and RCU right there.
             */
            rcu_read_lock();
            local_bh_disable();
            //负责服务端响应 SYN 的所有逻辑,函数指针指向 tcp_v4_conn_request 函数
            acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
            local_bh_enable();
            rcu_read_unlock();

            if (!acceptable)
                return 1;
            consume_skb(skb);
            return 0;
        }
        goto discard;

4.tcp_v4_conn_request 函数

    /* Never answer to SYNs send to broadcast or multicast */
    //不响应广播或多播,因为 TCP 是一对一连接的
    if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
        goto drop;
    //真正的处理函数
    return tcp_conn_request(&tcp_request_sock_ops,
                &tcp_request_sock_ipv4_ops, sk, skb);

5.tcp_conn_request 函数

    /* TW buckets are converted to open requests without
     * limitations, they conserve resources and peer is
     * evidently real one.
     */
    //TW buckets 无限制的被转换为打开的请求,可以节省资源并确定对面是真的。
    //判断半连接队列是否满了
    if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
         inet_csk_reqsk_queue_is_full(sk)) && !isn) {
        want_cookie = tcp_syn_flood_action(sk, rsk_ops->slab_name);
        if (!want_cookie)
            goto drop;
    }
    //判断全连接队列是否满了
    if (sk_acceptq_is_full(sk)) {
        NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
        goto drop;
    }
    //分配一个 request socket
    req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
    if (!req)
        goto drop;
……
//将 socket 添加到半连接队列,并开启定时器
            inet_csk_reqsk_queue_hash_add(sk, req,
                tcp_timeout_init((struct sock *)req));
//发送 SYN 包
        af_ops->send_synack(sk, dst, &fl, req, &foc,
                    !want_cookie ? TCP_SYNACK_NORMAL :
                           TCP_SYNACK_COOKIE,
                    skb);

四、客户端响应 SYN-ACK

 1.tcp_rcv_state_process 函数

    case TCP_SYN_SENT:
        tp->rx_opt.saw_tstamp = 0;
        //刷新 TCP 的时钟,确保其是单向增长的值
        tcp_mstamp_refresh(tp);
        //客户端响应 SYNN-ACK 的主要逻辑
        queued = tcp_rcv_synsent_state_process(sk, skb, th);
        if (queued >= 0)
            return queued;

 2.tcp_rcv_synsent_state_process 函数

//一些检验

……

    //处理传入的 ACK

   tcp_ack(sk, skb, FLAG_SLOWPATH);

……

   //完成连接

   tcp_finish_connect(sk, skb);
……
        } else {
            //发送 ACK 并更新窗口
            tcp_send_ack(sk);

3.tcp_ack 函数

    /* If the ack is older than previous acks
     * then we can probably ignore it.
     */
    //如果本次收到的 ACK 比之前的更古老,就忽略它
    if (before(ack, prior_snd_una)) {
        /* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
        if (before(ack, prior_snd_una - tp->max_window)) {
            if (!(flag & FLAG_NO_CHALLENGE_ACK))
                tcp_send_challenge_ack(sk);
            return -1;
        }
        goto old_ack;
    }
 
    /* See if we can take anything off of the retransmit queue. */
    //检查是否需要从重传队列中取出什么
    flag |= tcp_clean_rtx_queue(sk, skb, prior_fack, prior_snd_una,
                    &sack_state, flag & FLAG_ECE);
……
    //删除重传计时器
    /* If needed, reset TLP/RTO timer when RACK doesn't set. */
    if (flag & FLAG_SET_XMIT_TIMER)
        tcp_set_xmit_timer(sk);

4.tcp_finish_connect 函数

    //设置状态为 ESTABLISHED 状态

    tcp_set_state(sk, TCP_ESTABLISHED);
    //记录下收到响应包的时间
    icsk->icsk_ack.lrcvtime = tcp_jiffies32;
    //初始化传输
    tcp_init_transfer(sk, BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB, skb);
    //开启保活计时器
    if (sock_flag(sk, SOCK_KEEPOPEN))
        inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));

5.tcp_clean_rtx_queue 函数

/* Remove acknowledged frames from the retransmission queue. If our packet
 * is before the ack sequence we can discard it as it's confirmed to have
 * arrived at the other end.
 */
//从重传队列中移除已确认的帧,相应序列号之前的包可以丢弃了,因为我们已经确认它的到达。
 

6.tcp_init_transfer 函数

    //MTU 探测初始化,主要是通过 MTU 计算出 MSS 的值。

    tcp_mtup_init(sk);
    icsk->icsk_af_ops->rebuild_header(sk);
    tcp_init_metrics(sk);

    /* Initialize the congestion window to start the transfer.
     * Cut cwnd down to 1 per RFC5681 if SYN or SYN-ACK has been
     * retransmitted. In light of RFC6298 more aggressive 1sec
     * initRTO, we only reset cwnd when more than 1 SYN/SYN-ACK
     * retransmission has occurred.
     */
//初始化拥塞窗口以开始传输。如果已经重传,cwnd 减少到 1。如果是一秒初始化,在超过一次 SYN/SYN-ACK 时重置 cwnd
    if (tp->total_retrans > 1 && tp->undo_marker)
        tp->snd_cwnd = 1;
    else
        tp->snd_cwnd = tcp_init_cwnd(tp, __sk_dst_get(sk));
    tp->snd_cwnd_stamp = tcp_jiffies32;

    bpf_skops_established(sk, bpf_op, skb);
    /* Initialize congestion control unless BPF initialized it already: */
    //初始化拥塞控制,除非 BPF 已初始化
    if (!icsk->icsk_ca_initialized)
        tcp_init_congestion_control(sk);
    tcp_init_buffer_space(sk);

7.tcp_send_ack 函数

  函数实现在 __tcp_send_ack(sk, tcp_sk(sk)->rcv_nxt); 函数中。

    /* If we have been reset, we may not send again. */
    //如果是关闭状态,就不再发送了
    if (sk->sk_state == TCP_CLOSE)
        return;

    /* We are not putting this on the write queue, so
     * tcp_transmit_skb() will set the ownership to this
     * sock.
     */
    //申请和构造 ACK 包
    buff = alloc_skb(MAX_TCP_HEADER,
             sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN));
    if (unlikely(!buff)) {
        struct inet_connection_sock *icsk = inet_csk(sk);
        unsigned long delay;
        //如果此次没有发送成功,增加重传时间,每重传一次,时间增加一倍
        delay = TCP_DELACK_MAX << icsk->icsk_ack.retry;
        //如果小于最大重传时间,重传次数加一
        if (delay < TCP_RTO_MAX)
            icsk->icsk_ack.retry++;
        inet_csk_schedule_ack(sk);
        icsk->icsk_ack.ato = TCP_ATO_MIN;
        inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, delay, TCP_RTO_MAX);
        return;
    }
……
    /* Send it off, this clears delayed acks for us. */
   //最后将数据发送出去
    __tcp_transmit_skb(sk, buff, 0, (__force gfp_t)0, rcv_nxt);

五、服务器响应 ACK

//最新的代码里实在没有找到相关处理函数,先溜去准备面试了,如果还做网络,再扩展一下试试,每个阶段写一篇!

1.tcp_v4_do_rcv 函数

    if (sk->sk_state == TCP_LISTEN) {
        //如果存在 ack 选项
        struct sock *nsk = tcp_v4_cookie_check(sk, skb);

        if (!nsk)
            goto discard;
        if (nsk != sk) {
            if (tcp_child_process(sk, nsk, skb)) {
                rsk = nsk;
                goto reset;
            }
            return 0;
        }
    } else
        sock_rps_save_rxhash(sk, skb);
    //对收到的数据包做一些处理
    if (tcp_rcv_state_process(sk, skb)) {
        rsk = sk;
        goto reset;
    }
    return 0;

2.tcp_rcv_state_process 函数

    case TCP_LISTEN:
        //存在 ack 段,不做处理
        if (th->ack)
            return 1;

六、服务器 accept

1.inet_csk_accept 函数

    //接收队列

    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    //判断是否有连接
    if (reqsk_queue_empty(queue)) {
        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
        //如果是非阻塞套接字,不能休眠
        /* If this is a non blocking socket don't sleep */
        error = -EAGAIN;
        if (!timeo)
            goto out_err;

        error = inet_csk_wait_for_connect(sk, timeo);
        if (error)
            goto out_err;
    }
    //取一个连接出来
    req = reqsk_queue_remove(queue, sk);
    newsk = req->sk;

2.reqsk_queue_empty 函数

 //只有一行,就是从全连接队列里队列里取一个连接

    return READ_ONCE(queue->rskq_accept_head) == NULL;

3.reqsk_queue_remove 函数

    //链表头给一个中间变量

    req = queue->rskq_accept_head;
    if (req) {
        sk_acceptq_removed(parent);
        //链表头赋值为链表第二个元素
        WRITE_ONCE(queue->rskq_accept_head, req->dl_next);
        if (queue->rskq_accept_head == NULL)
            queue->rskq_accept_tail = NULL;
    }
    spin_unlock_bh(&queue->rskq_lock);
    返回最初的链表头
    return req;

 

 

未完待续,持续更新!

posted @ 2022-03-27 18:34  一只吃水饺的胡桃夹子  阅读(285)  评论(0编辑  收藏  举报