• 一文详解TCP三次握手的状态迁移(从应用层到内核传输层)


    主要参考了《深入linux内核架构》和《精通Linux内核网络》相关章节

    详解TCP三次握手的状态迁移(从应用层到传输层)

    linux版本 4.12

    所有状态

    // sock的状态
    enum {  
        TCP_ESTABLISHED = 1,  
        TCP_SYN_SENT,  
        TCP_SYN_RECV,  
        TCP_FIN_WAIT1,  
        TCP_FIN_WAIT2,  
        TCP_TIME_WAIT,  
        TCP_CLOSE,  
        TCP_CLOSE_WAIT, 
        TCP_LAST_ACK,  
        TCP_LISTEN,  
        TCP_CLOSING,   /* 现在是有效状态 */  
        TCP_MAX_STATES /* 标志状态定义的结束! */ 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    close->listen->sys_recv->established
    close->sys_sent->established

    closed->listen

    int listen(int sockfd, int backlog);

    Accept incoming connections and a queue limit for incoming connections.

    backlog保存的是完成三次握手、等待accept的全连接的最大长度,而不是半连接的。

    负载不高时,backlog不用太大。(For complete connections)

    系统最大的、未处理的全连接数量为:min(backlog, somaxconn),net.core.somaxconn默认为128。

    这个值最终存储于sk->sk_max_ack_backlog,sk->sk_ack_backlog 保存现有半连接队列大小。

    sys_listen

    net\socket.c

    SYSCALL_DEFINE2(listen, int, fd, int, backlog)
    {
        struct socket *sock;
        int err, fput_needed;
        int somaxconn;
     
        /* 通过文件描述符fd,找到对应的socket。
         * 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
         * 然后从file实例的private_data成员中获取socket实例。
         */
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
     
        if (sock) {
            /* backlog不能超过系统参数somaxconn */
            somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
            if ((unsigned int)backlog > somaxconn)
                backlog = somaxconn;
     
            err = security_socket_listen(sock, backlog);
     
            if (! err)
                /* socket层的操作函数,如果是SOCK_STREAM的话,proto_ops是inet_stream_ops,
                 * 接下来调用的是inet_listen()。
                 */
                err = sock->ops->listen(sock, backlog);
     
            fput_light(sock->file, fput_needed);
        }    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    inet_listen

    这是传输层提供给socket层(与协议无关层)的 listen 接口

    net\ipv4\af_inet.c

    /*
     *	Move a socket into listening state.
     */
    int inet_listen(struct socket *sock, int backlog)
    {
    	struct sock *sk = sock->sk;
    	unsigned char old_state;
    	int err;
    
    	lock_sock(sk);
    
    	err = -EINVAL;
    	if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM) // struct socket的检验
    		goto out;
    
    	old_state = sk->sk_state;
    	if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN))) // struct sock的检验
    		goto out;
    
    	/* 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().
    		 */
            // 开启TFO(TCP FAST OPEN)选项
    		if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
    		    (sysctl_tcp_fastopen & TFO_SERVER_ENABLE) &&
    		    !inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
    			fastopen_queue_tune(sk, backlog);
    			tcp_fastopen_init_key_once(true);
    		}
    		/* 启动监听 */
    		err = inet_csk_listen_start(sk, backlog);
    		if (err)
    			goto out;
    	}
    	sk->sk_max_ack_backlog = backlog;
    	err = 0;
    
    out:
    	release_sock(sk);
    	return err;
    }
    EXPORT_SYMBOL(inet_listen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    inet_csk_listen_start 启动监听

    启动监听时,做的工作主要包括:

    1. 创建半连接队列的实例,初始化全连接队列。

    2. 初始化sock的一些变量,把它的状态设为TCP_LISTEN。

    3. 检查端口是否可用,防止bind()后其它进程修改了端口信息。

    4. 把sock链接进入监听哈希表listening_hash中。

    int inet_csk_listen_start(struct sock *sk, int backlog)
    {
    	struct inet_connection_sock *icsk = inet_csk(sk);
    	struct inet_sock *inet = inet_sk(sk);
    	int err = -EADDRINUSE;
    
        /* 初始化全连接队列 */
    	reqsk_queue_alloc(&icsk->icsk_accept_queue);
    
    	sk->sk_max_ack_backlog = backlog;
    	sk->sk_ack_backlog = 0;
    	inet_csk_delack_init(sk); /* icsk->icsk_ack结构清零 */
    
    	/* 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.
    	 */
    	sk_state_store(sk, TCP_LISTEN);
        
        /* 检查端口是否仍然可用,防止bind()后其它进程修改了端口信息 */
    	if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
    		inet->inet_sport = htons(inet->inet_num);
    
    		sk_dst_reset(sk);
    		err = sk->sk_prot->hash(sk); /* 把sock链接入监听哈希表中 */
    
    		if (likely(!err))
    			return 0;
    	}
    
    	sk->sk_state = TCP_CLOSE;
    	return err;
    }
    EXPORT_SYMBOL_GPL(inet_csk_listen_start);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    listen->sys_recv 第一次握手

    接收到消息(客户端收到SYN+ACK,或者是服务器端收到SYN)后,如果套接字处于TCP_ESTABLISHED状态,tcp_v4_rcv(接收TCP消息的函数)就会调用tcp_v4_do_rcv,tcp_v4_do_rcv来调用该方法。

    这里线分析 服务器端收到SYN后的处理(第一次握手

    • 对于syncookie,服务端不保存任何状态
    • 对于fastopen,新建sock进入TCP_SYN_RCV状态, 并插入等待accpet队列,并把数据部分放倒接收队列中, 并设置重传定时器
    • 对于一般的syn包,request_sock设置为TCP_NEW_SYN_RECV,插入ehash表, 设置req_timer定时器,重传synack
    img

    tcp_rcv_state_process

    net\ipv4\tcp_input.c

    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;
    ...
        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) { // 收到syn
                if (th->fin)
                    goto discard;
                /*
                 * 处理SYN段,主要由conn_request接口(TCP中为tcp_v4_conn_request,接着调用tcp_conn_request)处理,
                 * icsk_af_ops成员在创建套接字时被初始化,参见tcp_v4_init_sock()
                 */
                 /*收到三次握手的第一步SYN,
                    则在tcp_v4_conn_request中创建连接请求控制块request_sock
                    */
                
                /* 我们可能会处理来自 backlog 队列的 SYN 数据包,因此我们需要确保在此处禁用 BH。*/
                local_bh_disable();
    			acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
    			local_bh_enable();
    
    			if (!acceptable)
    				return 1;
    			consume_skb(skb);
    			return 0;
            }
            goto discard;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    tcp_conn_request 分配并初始化request_sock,设置NEW_SYN_RECV,然后发送synack

    net\ipv4\tcp_input.c

    ...    
        /*
         * 可以接收并处理连接请求,调用inet_reqsk_alloc()分配一个连接请求
         * 块,用于保存连接请求信息,同时初始化在建立连接过程中用来发送
         * ACK、RST段的操作集合,以便在建立连接过程中能方便地调用这些接口
         */
    	req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie); // 分配request_sock, 进入TCP_NEW_SYN_RECV状态
    	if (!req)
    		goto drop;
    
    	tcp_rsk(req)->af_specific = af_ops; // af_ops 为 全局静态常量tcp_request_sock_ipv4_ops
    	tcp_rsk(req)->ts_off = 0;
    
        // TCP选项置空后初始化 mss_clamp和user_mss
    	tcp_clear_options(&tmp_opt);
    	tmp_opt.mss_clamp = af_ops->mss_clamp; // TCP_MSS_DEFAULT=536
    	tmp_opt.user_mss  = tp->rx_opt.user_mss; // listen sock设置的或是tw的 ,用户在 ioctl 中请求的 mss
    	tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc); // 开启syncookie后则不用考虑fastopen, syncookie不允许使用tcp扩展
    ...
        
       if (want_cookie) {
            /*
             * 如果启动了syncookies,则每60秒警告一次可能受
             * synflood攻击,同时由客户端IP地址、客户端端口、
             * 服务器IP地址、服务器端口、客户端初始序列号
             * 等要素经hash运算后加密得到服务端初始化序列号
             */
             //如果开启了syncookie选项,则需要检查收到的第三步ack和这个isn值是否一致
            isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
            //cookie_v4_init_sequence生成syncookie,并作为ack的起始序号
            req->cookie_ts = tmp_opt.tstamp_ok;
            if (!tmp_opt.tstamp_ok)
                inet_rsk(req)->ecn_ok = 0;
        }
    
        tcp_rsk(req)->snt_isn = isn;
        tcp_rsk(req)->txhash = net_tx_rndhash();
        tcp_openreq_init_rwin(req, sk, dst);// 设置初始化rwnd
        if (!want_cookie) {
            tcp_reqsk_record_syn(sk, req, skb);// 如果设置保存TCP_SAVE_SYN标记,则保存
            fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst);
            // 验证后创建fastopen sock,并把数据部分放入接收队列中
        }
        if (fastopen_sk) {// 验证并创建fastsocket成功, 进入TCP_SYN_RCV状态
            af_ops->send_synack(fastopen_sk, dst, &fl, req,
                        &foc, TCP_SYNACK_FASTOPEN);//tcp_v4_send_synac
            /* Add the child socket directly into the accept queue */
            inet_csk_reqsk_queue_add(sk, req, fastopen_sk);// 直接添加到等待accept的队列
            sk->sk_data_ready(sk);
            bh_unlock_sock(fastopen_sk);
            sock_put(fastopen_sk);
        } else {
            tcp_rsk(req)->tfo_listener = false;
            if (!want_cookie)
                inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);// 调用reqsk_queue_hash_req,转成socket *插入ehash,并设置定时器
            af_ops->send_synack(sk, dst, &fl, req, &foc,
                        !want_cookie ? TCP_SYNACK_NORMAL :
                               TCP_SYNACK_COOKIE);// tcp_v4_send_synack,发送synack
            if (want_cookie) {
                reqsk_free(req);// 启用syncookie的话,可以直接释放req
                return 0;
            }
        }
        reqsk_put(req);
        return 0;
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    syn_recv->established 第三次握手(非TFO)

    tcp_v4_rcv 查找控制块(request_sock),转为sock插入accept队列

    ...
    lookup:
         /* 查找控制块 查找传输控制块  先查找ehash 然后查找listen hash*/
        sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
                       th->dest, &refcounted);
        if (!sk)
            goto no_tcp_socket;
    
    process:
           /* TIME_WAIT转过去处理 */
        if (sk->sk_state == TCP_TIME_WAIT)
            goto do_time_wait;
        /*
    三次握手的第二阶段,服务器发送synack后,
    会进入TCP_NEW_SYN_RECV状态,并插入ehash中。
    收到握手最后一个ack后,会找到TCP_NEW_SYN_RECV状态的req
    ,然后创建一个新的sock进入TCP_SYN_RECV状态
    ,最终进入TCP_ESTABLISHED状态. 并放入accept队列通知select/epoll
        */
     /* TCP_NEW_SYN_RECV状态处理 */
        if (sk->sk_state == TCP_NEW_SYN_RECV) {
            struct request_sock *req = inet_reqsk(sk);
            struct sock *nsk;
      /* 获取控制块 */
            sk = req->rsk_listener;
            if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {
                reqsk_put(req);
                goto discard_it;
            }
            if (unlikely(sk->sk_state != TCP_LISTEN)) {
                /* 从连接队列移除控制块 */
                inet_csk_reqsk_queue_drop_and_put(sk, req);
                goto lookup;/* 根据skb参数重新查找控制块 */
            }
            /* We own a reference on the listener, increase it again
             * as we might lose it too soon.
             */
            sock_hold(sk);
            refcounted = true;
             /* 处理第三次握手ack,成功返回新控制块 */
            nsk = tcp_check_req(sk, skb, req, false); // 创建新的sock进入TCP_SYN_RECV state 并插入accept队列
            if (!nsk) {
                reqsk_put(req);
                goto discard_and_relse;
            }
             /* 未新建控制块,进一步处理 */
            if (nsk == sk) {
                reqsk_put(req);
                /*tcp_child_process中调用tcp_rcv_state_process来
                处理TCP_SYN_RECV状态的child sock
                。并进入TCP_ESTABLISHED状态*/
            } else if (tcp_child_process(sk, nsk, skb)) {
                //nsk进入ehash
            /* 有新建控制块,进行初始化等 */
                tcp_v4_send_reset(nsk, skb); /* 失败发送rst */
                goto discard_and_relse;
            } else {
                sock_put(sk);
                return 0;
            }
        }
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    tcp_child_process

    服务器接收到消息(ACK)后(第三次握手),如果套接字处于TCP_ESTABLISHED状态,tcp_v4_rcv(接收TCP消息的函数)就会调用tcp_v4_do_rcv(tcp_v4_do_rcv来调用该方法)或者直接调用该方法。

    收到握手最后一个ack后,会找到TCP_NEW_SYN_RECV状态的req,然后创建一个新的sock进入TCP_SYN_RECV状态,最终进入TCP_ESTABLISHED状态, 并放入accept队列通知select/epoll

    net\ipv4\tcp_minisocks.c

    /*
     * Queue segment on the new socket if the new socket is active, otherwise we just shortcircuit this and continue with the new socket.
     *
     * For the vast majority of cases child->sk_state will be TCP_SYN_RECV when entering. But other states are possible due to a race condition where after __inet_lookup_established() fails but before the listener locked is obtained, other packets cause the same connection to be created.
     */
     int tcp_child_process(struct sock *parent, struct sock *child,
    		      struct sk_buff *skb)
    {
    	int ret = 0;
    	int state = child->sk_state;
    
    	/* record NAPI ID of child */
    	sk_mark_napi_id(child, skb);
    
    	tcp_segs_in(tcp_sk(child), skb);
         
        // 如果用户进程没有锁住child,则让child重新处理该ACK报文,这可以让child
    	// 套接字由TCP_SYN_RECV迁移到TCP_ESTABLISH状态
    	if (!sock_owned_by_user(child)) {
    		ret = tcp_rcv_state_process(child, skb);
    
            // child套接字状态发生了迁移,唤醒监听套接字上的进程,可能由于调用accept()而block
    		if (state == TCP_SYN_RECV && child->sk_state != state)
    			parent->sk_data_ready(parent);
    	} else {
    		/* Alas, it is possible again, because we do lookup
    		 * in main socket hash table and lock on listening
    		 * socket does not protect us more.
    		 */
            // 缓存该skb后续处理
    		__sk_add_backlog(child, skb);
    	}
    
    	bh_unlock_sock(child);
    	sock_put(child);
    	return ret;
    }
    EXPORT_SYMBOL(tcp_child_process);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    tcp_rcv_state_process 为发送数据和接收数据做工作,然后设置sock的ESTABLISHED

    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
    			  struct tcphdr *th, unsigned len)
    {
    ...
    	switch (sk->sk_state) {
        case TCP_SYN_RECV:
            if (!acceptable)
                return 1;
    
            if (!tp->srtt_us)
                tcp_synack_rtt_meas(sk, req);
             /* 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); // 回收fastopen req
            } else {
                /* Make sure socket is routed, for correct metrics. */
               icsk->icsk_af_ops->rebuild_header(sk); // 调用inet_sk_rebuild_header或inet6_sk_rebuild_header,根据ACK包的信息重新计算路由
                tcp_init_congestion_control(sk); // 初始化拥塞控制算法
    
                tcp_mtup_init(sk); // 初始化MTU探测功能
                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_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
             */
            sk->sk_state_change(sk); // sock_def_wakeup, 唤醒epoll
                
            // 进行其他的初始化,如初始化传输控制块各字段
            ...
            break
        }
    ...
    /* step 7: process the segment text */
    	switch (sk->sk_state) {
                ...
            case TCP_ESTABLISHED:
            tcp_data_queue(sk, skb); // 如果带数据部分则处理,比如客户端设置了deferaccept的时候
            queued = 1;
            break;
        }
    
        /* tcp_data could move socket to TIME-WAIT */
        if (sk->sk_state != TCP_CLOSE) {
            tcp_data_snd_check(sk); // 给数据一个发送机会,tcp_push_pending_frame
            tcp_ack_snd_check(sk); // 检查是否有ack被推迟,判断是否需要立即发送
        }
    
        if (!queued) {
    discard:
            tcp_drop(sk, skb);
        }
        return 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    closed->sys_sent

    sys_conect

    SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
    		int, addrlen)
    {
    	struct socket *sock;
    	struct sockaddr_storage address;
    	int err, fput_needed;
    
    	sock = sockfd_lookup_light(fd, &err, &fput_needed); /* 查找文件句柄对应的socket */
    	if (!sock)
    		goto out;
        /* 从用户态复制地址参数到内核中 */
    	err = move_addr_to_kernel(uservaddr, addrlen, &address);
    	if (err < 0)
    		goto out_put;
    
        /* 安全审计 */
    	err =
    	    security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
    	if (err)
    		goto out_put;
    
        /* 调用传输层的connet方法inet_stream_connect或inet_dgram_connect */
    	err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
    				 sock->file->f_flags);
    out_put:
    	fput_light(sock->file, fput_needed);
    out:
    	return err;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    inet_stream_connect

    inet_stream_connect加了个锁(锁住socket的sock)然后调用了__inet_stream_connect

    __inet_stream_connect()函数主要功能如下:
    (1)调用tcp_v4_connect函数建立与服务器联系并发送SYN段;
    (2)获取连接超时时间timeo,如果timeo不为0,则会调用inet_wait_for_connect一直等待到连接成功或超时;

    /*
     *	Connect to a remote host. There is regrettably still a little
     *	TCP 'magic' in here.
     */
    int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
    			  int addr_len, int flags, int is_sendmsg)
    {
    	struct sock *sk = sock->sk;
    	int err;
    	long timeo;
    
    	/*
    * uaddr 可以为 NULL 并且 addr_len 可以为 0 如果: sk 是 TCP fastopen 活动套接字并且 TCP_FASTOPEN_CONNECT sockopt 已设置并且我们已经有此套接字的有效 cookie。
    * 在这种情况下,用户可以在 connect() 之后调用 write()。 write() 将调用调用 __inet_stream_connect() 的 tcp_sendmsg_fastopen()。
    	 */
    	if (uaddr) { // uaddr不为空
    		if (addr_len < sizeof(uaddr->sa_family))
    			return -EINVAL;
    
    		if (uaddr->sa_family == AF_UNSPEC) { /* 未指定地址类型,错误 */
    			err = sk->sk_prot->disconnect(sk, flags);
    			sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
    			goto out;
    		}
    	}
    
    	switch (sock->state) {
    	default:
    		err = -EINVAL;
    		goto out;
    	case SS_CONNECTED: /* 已经与对方端口连接 */
    		err = -EISCONN;
    		goto out;
    	case SS_CONNECTING: /* 正在连接过程中 */
    		if (inet_sk(sk)->defer_connect)
    			err = is_sendmsg ? -EINPROGRESS : -EISCONN;
    		else
    			err = -EALREADY;
    		/* Fall out of switch with err, set for this state */
    		break;
    	case SS_UNCONNECTED: /* 只有此状态才能调用connect */
    		err = -EISCONN;
    		if (sk->sk_state != TCP_CLOSE) /* 如果不是TCP_CLOSE状态,说明已经连接了 */
    			goto out;
    
        /* 调用传输层接口tcp_v4_connect建立与服务器连接,并发送SYN段 */
    		err = sk->sk_prot->connect(sk, uaddr, addr_len);
    		if (err < 0)
    			goto out;
    	 /* 发送SYN段后,设置状态为SS_CONNECTING */
    		sock->state = SS_CONNECTING;
    
        /* 表示设置了 fastopen_connect 并且存在 cookie,因此我们将连接推迟到写入第一个数据帧 */
    		if (!err && inet_sk(sk)->defer_connect)
    			goto out;
    
    		/* Just entered SS_CONNECTING state; the only
    		 * difference is that return value in non-blocking
    		 * case is EINPROGRESS, rather than EALREADY.
    		 */
        /* 如果是以非阻塞方式进行连接,则默认的返回值为EINPROGRESS,表示正在连接 */
    		err = -EINPROGRESS;
    		break;
    	}
    
        /* 获取连接超时时间,如果指定非阻塞方式,则不等待直接返回 */
    	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
    
        /* 发送完SYN后,连接状态一般为这两种状态,但是如果连接建立非常快,则可能越过这两种状态 */
    	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    		int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
    				tcp_sk(sk)->fastopen_req &&
    				tcp_sk(sk)->fastopen_req->data ? 1 : 0;
    
    		/* Error code is set above */
    		if (!timeo || !inet_wait_for_connect(sk, timeo, writebias)) /* 等待连接完成或超时 */
    			goto out;
    
    		err = sock_intr_errno(timeo);
    		if (signal_pending(current))
    			goto out;
    	}
    
    	/* Connection was closed by RST, timeout, ICMP error
    	 * or another process disconnected us.
    	 */
    	if (sk->sk_state == TCP_CLOSE) /* 运行到这里说明连接建立失败 */
    		goto sock_error;
    
    	/* sk->sk_err may be not zero now, if RECVERR was ordered by user
    	 * and error was received after socket entered established state.
    	 * Hence, it is handled normally after connect() return successfully.
    	 */
    
    	sock->state = SS_CONNECTED; /* 连接建立成功,设置为已经连接状态 */
    	err = 0;
    out:
    	return err;
    
    sock_error:
    	err = sock_error(sk) ? : -ECONNABORTED;
    	sock->state = SS_UNCONNECTED;
    	if (sk->sk_prot->disconnect(sk, flags))
    		sock->state = SS_DISCONNECTING;
    	goto out;
    }
    EXPORT_SYMBOL(__inet_stream_connect);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    tcp_v4_connect TCP报头初始化并发送SYN,sk进入SYN_SENT状态,并插入ehash

    net\ipv4\tcp_ipv4.c

    /* This will initiate an outgoing connection. */
    int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
    {
    	struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
    	struct inet_sock *inet = inet_sk(sk);
    	struct tcp_sock *tp = tcp_sk(sk);
    	__be16 orig_sport, orig_dport;
    	__be32 daddr, nexthop;
    	struct flowi4 *fl4;
    	struct rtable *rt;
    	int err;
    	struct ip_options_rcu *inet_opt;
    	struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row;
    
        /* 校验目的地址的长度及地址族的有效性 */
    	if (addr_len < sizeof(struct sockaddr_in))
    		return -EINVAL;
    
    	if (usin->sin_family != AF_INET)
    		return -EAFNOSUPPORT;
    
        /* 将下一跳和目的地址都设置为源地址 */
    	nexthop = daddr = usin->sin_addr.s_addr;
    	inet_opt = rcu_dereference_protected(inet->inet_opt,
    					     lockdep_sock_is_held(sk));
    	if (inet_opt && inet_opt->opt.srr) { /* 如果是源站路由,则下一跳设置为选项中的地址 */
    		if (!daddr)
    			return -EINVAL;
    		nexthop = inet_opt->opt.faddr;
    	}
    
    	orig_sport = inet->inet_sport;
    	orig_dport = usin->sin_port;
    	fl4 = &inet->cork.fl.u.ip4;
        /* 根据下一跳地址等信息查找目的路由缓存项。 */
    	rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
    			      RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
    			      IPPROTO_TCP,
    			      orig_sport, orig_dport, sk);
    	if (IS_ERR(rt)) {
    		err = PTR_ERR(rt);
    		if (err == -ENETUNREACH)
    			IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
    		return err;
    	}
    
    	if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) { /* 对TCP来说,不能使用多播和组播路由项 */
    		ip_rt_put(rt);
    		return -ENETUNREACH;
    	}
    
    	if (!inet_opt || !inet_opt->opt.srr) /* 如果没有启用源路由选项,则使用路由缓存项中的目的地址 */
    		daddr = fl4->daddr;
    
    	if (!inet->inet_saddr) /* 如果未设置传输控制块中的源地址,则使用路由缓存项中的源地址 */
    		inet->inet_saddr = fl4->saddr;
    	sk_rcv_saddr_set(sk, inet->inet_saddr);
    
         /* 如果传输控制块中的时间戳和目的地址已经使用过,则说明传输控制块之前已建立连接并? */
    	if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
    		/* Reset inherited state */
            /* 重新初始化相关成员 */
    		tp->rx_opt.ts_recent	   = 0;
    		tp->rx_opt.ts_recent_stamp = 0;
    		if (likely(!tp->repair))
    			tp->write_seq	   = 0;
    	}
    
         /* 设置目的地址和目标端口 */
    	inet->inet_dport = usin->sin_port;
    	sk_daddr_set(sk, daddr);
    
        /* 设置IP首部选项长度 */
    	inet_csk(sk)->icsk_ext_hdr_len = 0;
    	if (inet_opt)
    		inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;
    
        /* 初始化MSS上限 */
    	tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT;
    
    	/* 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.
    	 */
    	tcp_set_state(sk, TCP_SYN_SENT); /* 设置为TCP_SYN_SENT状态 */
    	err = inet_hash_connect(tcp_death_row, sk); /* 将传输控制添加到ehash散列表中,并动态分配端口 */
    	if (err)
    		goto failure;
    
    	sk_set_txhash(sk);
    
        /* 如果源端口或者目的端口发生改变,则需要重新查找路由 */
    	rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
    			       inet->inet_sport, inet->inet_dport, sk);
    	if (IS_ERR(rt)) {
    		err = PTR_ERR(rt);
    		rt = NULL;
    		goto failure;
    	}
    	/* OK, now commit destination to socket.  */
    	sk->sk_gso_type = SKB_GSO_TCPV4;
    	sk_setup_caps(sk, &rt->dst); /* 设置路由,并根据路由更新网络设备的特性 */
    	rt = NULL;
    
    	if (likely(!tp->repair)) {
    		if (!tp->write_seq) /* 还未计算初始序号 */
    			tp->write_seq = secure_tcp_seq(inet->inet_saddr, /* 根据双方地址、端口计算初始序号 */
    						       inet->inet_daddr,
    						       inet->inet_sport,
    						       usin->sin_port);
    		tp->tsoffset = secure_tcp_ts_off(inet->inet_saddr,
    						 inet->inet_daddr);
    	}
    
        /* 根据初始序号和当前时间,随机算一个初始id */
    	inet->inet_id = tp->write_seq ^ jiffies;
    
    	if (tcp_fastopen_defer_connect(sk, &err))
    		return err;
    	if (err)
    		goto failure;
    
        /* 建立一个SYN段并调用tcp_transmit_skb发送 */
    	err = tcp_connect(sk);
    
    	if (err)
    		goto failure;
    
    	return 0;
    
    failure:
    	/*
    	 * This unhashes the socket and releases the local port,
    	 * if necessary.
    	 */
    	tcp_set_state(sk, TCP_CLOSE);
    	ip_rt_put(rt);
    	sk->sk_route_caps = 0;
    	inet->inet_dport = 0;
    	return err;
    }
    EXPORT_SYMBOL(tcp_v4_connect);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    sys_sent->established 第二次握手

    对于协议栈的接收 synack 路径

    • tcp_v4_rcv
      • ->__inet_lookup_skb() // 在ehash中找到对应的sk
      • ->!sock_owned_by_user() // connect()即使阻塞也不占有锁
        • ->!tcp_prepare() // 对于synack,不会排入prepare队列
        • ->tcp_v4_do_rcv()
          • ->tcp_rcv_state_process() // 进入TCP_SYN_SENT状态处理逻辑
            • -> tcp_rcv_synsent_state_process

    当客户端connect()之后,sock进入TCP_SYN_SENT状态,并插入到ehash中, 如果是阻塞socket则connect()等待握手完成

    本文考虑收到服务端synack的过程,也就是客户端握手的第二阶段;

    发送SYN段后,连接的状态变为SYN_SENT。此时如果收到SYNACK段,处理函数为tcp_rcv_state_process()。

    连接状态不为ESTABLISHED或TIME_WAIT时的处理函数为tcp_rcv_state_process()。

    tcp_rcv_synsent_state_process()用于SYN_SENT状态的处理,具体又分两种场景

    (1) 接收到SYNACK
    一般情况下会收到服务端的SYNACK,处理如下:
    检查ack_seq是否合法。如果使用了时间戳选项,检查回显的时间戳是否合法。检查TCP的标志位是否合法。如果SYNACK是合法的,更新sock的各种信息。

    把连接的状态设置为TCP_ESTABLISHED,唤醒调用connect()的进程。判断是马上发送ACK,还是延迟发送。

    (2) 接收到SYN
    本端之前发送出一个SYN,现在又接收到了一个SYN,双方同时向对端发起建立连接的请求。
    处理如下:把连接状态置为SYN_RECV。更新sock的各种信息。构造和发送SYNACK。接者对端也会回应SYNACK,之后的处理流程和服务器端接收ACK类似

    当tcp_rcv_synsent_state_process()的返回值大于0时,会导致上层调用函数发送一个被动的RST。

    Q:那么什么情况下此函数的返回值会大于0?

    A:收到一个ACK段,但ack_seq的序号不正确,或者回显的时间戳不正确。

    tcp_rcv_synsent_state_process

    同时打开时,在SYN_SENT状态,收到SYN段后,状态变为SYN_RECV,然后发送SYNACK。之后如果收到合法的SYNACK后,就能完成连接的建立。

    对于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
      • 唤醒此socket等待队列上的进程(即调用connect的进程)
      • 如果使用了异步通知,则发送SIGIO通知异步通知队列上的进程可写
      • 如果包含fastopen cookie则保存
      • 判断是否需要立即ack还是延时ack
    • 如果包里没有ack,只有syn,则表示相互connect, TCP_SYN_SENT->TCP_SYN_RECV, 并发送synack
    static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
    					 const struct tcphdr *th)
    {
    	struct inet_connection_sock *icsk = inet_csk(sk); // 客户端sk
    	struct tcp_sock *tp = tcp_sk(sk);
    	struct tcp_fastopen_cookie foc = { .len = -1 };
    	int saved_clamp = tp->rx_opt.mss_clamp;
    	bool fastopen_fail;
    
    	tcp_parse_options(skb, &tp->rx_opt, 0, &foc); // 解析tcp选项,可能带fastopen cookie
    	if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
    		tp->rx_opt.rcv_tsecr -= tp->tsoffset;  // 在repair模式下的时间修正
    
    	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)"
    		 */
    		if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) || // 初始化的时候snd_una设置为syn序号,返回的ack为syn+1, 或者fastopen的时候更大
    		    after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) // ack的是还没有发送的数据
    			goto reset_and_undo;
    
    		if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
    		    !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, // retrans_stamp会在发送syn的时候记录,接收包需要在时间范围内
    			     tcp_time_stamp)) {
    			NET_INC_STATS(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."
    		 */
    
    		if (th->rst) {
    			tcp_reset(sk); // 进入TCP_CLOSE状态
    			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)
    		 */
    		if (!th->syn)  // 如果rst和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_rcv_synack(tp, th);
    
    		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
    		tcp_ack(sk, skb, FLAG_SLOWPATH);  // ack确认,有可能fastopen的数据被确认了
    
    		/* 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) { // 如果对方不支持wsacle
    			tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0; // 本机发送给对方的最大窗口也不能带wscale的大小
    			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首部需要增加时间戳长度 */
    			tp->advmss	    -= TCPOLEN_TSTAMP_ALIGNED; /* mss需要减去时间戳长度 */
    			tcp_store_ts_recent(tp); /* 设置回显时间戳 */
    		} else {
    			tp->tcp_header_len = sizeof(struct tcphdr);
    		}
    
    		if (tcp_is_sack(tp) && sysctl_tcp_fack) // 服务端支持sack,并且系统支持fack,则开启fack
    			tcp_enable_fack(tp);
    
    		tcp_mtup_init(sk); // 此时收到对方的tcp MSS选项,可以初始化mtu探测区间
    		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); // 使用pmtu更新探测区间和mss
    		tcp_initialize_rcv_mss(sk); // 更新对对方mss的猜测,不会超过TCP_MSS_DEFAULT=536
    
    		/* 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_finish_connect主要是客户端进入连接完成状态(TCP_ESTABLISHED),可以发送数据了
    		tcp_finish_connect(sk, skb);  // TCP_SYN_SENT->TCP_ESTABLISHED
    
    		fastopen_fail = (tp->syn_fastopen || tp->syn_data) && // fastopen处理,保存cookie
    				tcp_rcv_fastopen_synack(sk, skb, &foc);
    
    		if (!sock_flag(sk, SOCK_DEAD)) {
    			sk->sk_state_change(sk); /* 指向sock_def_wakeup,唤醒调用connect()的进程 */
    			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); /* 如果使用了异步通知,则发送SIGIO通知进程可写 */
    		}
    		if (fastopen_fail)
    			return -1;
    		if (sk->sk_write_pending || // 还有数据等待写
    		    icsk->icsk_accept_queue.rskq_defer_accept || // client设置了TCP_DEFER_ACCEPT, 先不ack,等待有数据发送的时候
    		    icsk->icsk_ack.pingpong) { // pingpong模式,没有开启快速ack
                // 延时ack,等待数据一起发送
    			/* 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); // 标记有ack被推迟
    			tcp_enter_quickack_mode(sk); // 进入快速ack模式,加速慢启动
    			inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, // 重置延时ack定时器
    						  TCP_DELACK_MAX, TCP_RTO_MAX);
    
    discard:
    			tcp_drop(sk, skb);
    			return 0;
    		} else {
    			tcp_send_ack(sk); // 不需要等待,立即发送ack
    		}
    		return -1;
    	}
    
    	/* No ACK in the segment */
    	// 没有ack,但是设置rst, 忽略这个包
    	if (th->rst) {
    		/* rfc793:
    		 * "If the RST bit is set
    		 *
    		 *      Otherwise (no ACK) drop the segment and return."
    		 */
    
    		goto discard_and_undo;
    	}
    
    	/* PAWS check. */
    	if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
    	    tcp_paws_reject(&tp->rx_opt, 0))
    		goto discard_and_undo;
    
    	if (th->syn) {  /* 收到了SYN段,即同时打开 */ //相互connect
    		/* We see SYN without ACK. It is attempt of
    		 * simultaneous connect with crossed SYNs.
    		 * Particularly, it can be connect to self.
    		 */
            /* 发送SYN后,状态为SYN_SENT,如果此时也收到SYN,
             * 状态则变为SYN_RECV。
             */
    		tcp_set_state(sk, TCP_SYN_RECV);
    
    		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->copied_seq = tp->rcv_nxt;
    		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; /* 记录最近更新发送窗口的ACK序号 */
    		tp->max_window = tp->snd_wnd; /* 目前见过的对端的最大通告窗口 */
    
    		tcp_ecn_rcv_syn(tp, th);
    
    		tcp_mtup_init(sk); /* TCP的MTU初始化  mss更新 */
    		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
    		tcp_initialize_rcv_mss(sk); /* 对端有效发送MSS估值的初始化 */
    
    		tcp_send_synack(sk);
    #if 0
    		/* Note, we could accept data and URG from this segment.
    		 * There are no obstacles to make this (except that we must
    		 * either change tcp_recvmsg() to prevent it from returning data
    		 * before 3WHS completes per RFC793, or employ TCP Fast Open).
    		 *
    		 * 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233

    tcp_finish_connect 设置ESTABLISHED,相关初始化

    tcp_finish_connect()用来完成连接的建立,主要做了以下事情:

    1. 把连接的状态从SYN_SENT置为ESTABLISHED。
    2. 根据路由缓存,初始化TCP相关的变量。
    3. 获取默认的拥塞控制算法。
    4. 调整发送缓存和接收缓存的大小。
    5. 如果使用了SO_KEEPALIVE选项,激活保活定时器。
    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);
    
        tcp_set_state(sk, TCP_ESTABLISHED); /* 设置为已连接状态 */
    
        if (skb) { /* 设置接收路由缓存 */
            icsk->icsk_af_ops->sk_rx_dst_set(sk, skb); // inet_sk_rx_dst_set
            security_inet_conn_established(sk, skb);
        }
    
        /* Make sure socket is routed, for correct metrics.  */
        icsk->icsk_af_ops->rebuild_header(sk); /* 检查或重建路由 */
    
        /* 根据路由缓存,初始化TCP相关变量 */
        tcp_init_metrics(sk);
    
        tcp_init_congestion_control(sk); // 调用拥塞算法init函数
    
        /* 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));
    
        /* 如果对端的窗口扩大因子为0 */
        if (!tp->rx_opt.snd_wscale) 
            __tcp_fast_path_on(tp, tp->snd_wnd);  /* 设置首部预测字段 */
        else
            tp->pred_flags = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    参考博客

    使用的内核版本不太同

    Socket层实现系列 — listen()的实现

    linux内核对TCP的连接状态管理

    Linux内核源码-sys_connect()

    tcp 客户端 synack的接收 以及 相互connect

    TCP连接建立系列 — 客户端接收SYNACK和发送ACK

    tcp syn-synack-ack 服务端 接收 SYN tcp_v4_do_rcv分析

    tcp syn-synack-ack 服务端发送syn-ack

    深入理解TCP协议及其源代码

  • 相关阅读:
    Bean的加载方式
    Linux工具-远程登录/访问
    pnpm install报错 Value of “this“ must be of type URLSearchParams
    封装以及static修饰符的应用
    SAP MM学习笔记27- 购买依赖(采购申请)
    gin 模版
    谷粒学苑 —— 8、课程管理:课程发布页面3 —— 信息确认及发布
    java基于springboot+vue的疫情网课管理系统 elementui
    【机器学习】线性回归【下】正则化最小二乘估计
    最近公共祖先 LCA
  • 原文地址:https://blog.csdn.net/qq_53111905/article/details/126251509