正常情况下,三次握手的步骤如下:
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;
未完待续,持续更新!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!