TCP三次握手源码分析(服务端接收ACK&TCP连接建立完成)
内核版本:Linux 3.10
内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且网页可全局搜索函数)
《TCP三次握手源码分析(客户端发送SYN)》
《TCP三次握手源码分析(服务端接收SYN以及发送SYN+ACK)》
《TCP三次握手源码分析(客户端接收SYN+ACK以及发送ACK)》
一、服务端接收ACK
客户端发送第三次握手ACK报文,此时客户端sock的状态已经是ESTABLISHED。
随着ACK报文到达服务端,经过网卡、软中断,依然进入到tcp_v4_rcv。
1.tcp_v4_rcv()函数
细节详情见《TCP三次握手源码分析(服务端接收SYN以及发送SYN+ACK)》。
// file: net/ipv4/tcp_ipv4.c int tcp_v4_rcv(struct sk_buff *skb) { ...... //服务端其实有两个socket:服务端调用socket()函数初始化的监听socket和用于跟客户端建立连接用的通信socket //根据报文的源地址和目的地址在established哈希表以及listen哈希表中查找连接 //之前服务端接收到客户端的SYN报文时,并未修改监听socket的状态,所以socket的状态依然是listen //通信socket目前还处于半连接队列中 sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); //最终在listen哈希表中查找到状态为listen的sock if (!sk) //没有找到接收的sock结构就跳转到no_tcp_socket goto no_tcp_socket; ...... ret = 0; if (!sock_owned_by_user(sk)) { //检测sock结构是否还可用(没有被用户锁定、没在使用) ...... { if (!tcp_prequeue(sk, skb)) //链入预处理队列 ret = tcp_v4_do_rcv(sk, skb); //处理数据包 } } ...... }
最终还是来到了tcp_v4_do_rcv()处理函数。
2.tcp_v4_do_rcv()函数
// file: net/ipv4/tcp_ipv4.c int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) { struct sock *rsk; ...... if (sk->sk_state == TCP_ESTABLISHED) { //如果sock已经处于ESTABLISHED状态 ...... //此时,服务器收到第三次握手的ACK,sock是同listen哈希表找到的监听sock,处于LISTEN状态 } if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb)) //检查数据块长度,检查校验和 goto csum_err; if (sk->sk_state == TCP_LISTEN) { //如果sock处理LISTEN状态 struct sock *nsk = tcp_v4_hnd_req(sk, skb); //在半连接队列中查到到第一次握手创建的request sock if (!nsk) //没有找到,丢弃数据包 goto discard; if (nsk != sk) { //找到的nsk是通信sock,与sk(监听sock)不是同一个sock sock_rps_save_rxhash(nsk, skb); if (tcp_child_process(sk, nsk, skb)) { //处理新创建的sock rsk = nsk; goto reset; } return 0; } } ...... }
tcp_v4_hnd_req()函数在半连接队列中找到了第一次握手时创建的request sock结构;创建通信sock,并链入全连接队列中。
// file: net/ipv4/tcp_ipv4.c static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb) { struct tcphdr *th = tcp_hdr(skb); const struct iphdr *iph = ip_hdr(skb); struct sock *nsk; struct request_sock **prev; // 查找连接请求结构(半连接队列查找),找到了第一次握手时创建的request sock结构 struct request_sock *req = inet_csk_search_req(sk, &prev, th->source, iph->saddr, iph->daddr); if (req) return tcp_check_req(sk, skb, req, prev, false); //创建通信sock结构,将连接请求链入这个sock结构的接收队列(全连接队列)中 ...... }
tcp_check_req()创建通信sock,加入ehash中,这样在下一次接收数据时,tcp_v4_rcv函数调用__inet_lookup_skb()查找sock结构则优先从ehash中获取通信sock;
同时删除半连接队列的request sock,还把通信sock链入到全连接队列。监听sock继续监听新的请求。
// file: net/ipv4/tcp_minisocks.c struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct request_sock **prev, bool fastopen) { struct tcp_options_received tmp_opt; struct sock *child; const struct tcphdr *th = tcp_hdr(skb); __be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK); bool paws_reject = false; ...... // 创建通信sock,并初始化 // inet_csk(sk)->icsk_af_ops在tcp_v4_init_sock()中挂入ipv4_specific结构,因此最终执行的是tcp_v4_syn_recv_sock() // tcp_v4_syn_recv_sock()克隆监听sock结构,并结合request sock的内容初始化一个新的通信sock,加入ehash、bhash中并修改通信sock的状态为SYN_RECV child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL); if (child == NULL) goto listen_overflow; inet_csk_reqsk_queue_unlink(sk, req, prev); //将request sock从半连接队列脱队 inet_csk_reqsk_queue_removed(sk, req); //递减半连接队列的计数器、删除SYNACK定时器 inet_csk_reqsk_queue_add(sk, req, child); //将通信sock链入到全连接队列 return child; ...... }
inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL)最终执行的是tcp_v4_syn_recv_sock()函数,里面做了很多事。
克隆监听sock结构,并结合request sock的内容初始化一个新的通信sock,加入ehash、bhash中并修改通信sock的状态为SYN_RECV等等。
此时监听sock为LISTEN状态,通信sock为SYN_RECV状态。这个地方与平时花的三次握手图稍微有些许差异,第三次握手时,服务端通信sock才被初始化为SYN_RECV状态。
3.tcp_child_process()函数
// file: net/ipv4/tcp_minisocks.c int tcp_child_process(struct sock *parent, struct sock *child, struct sk_buff *skb) { int ret = 0; int state = child->sk_state; if (!sock_owned_by_user(child)) { //检查客户端sock是否可用 ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb), skb->len); //根据监听sock的状态处理数据包 if (state == TCP_SYN_RECV && child->sk_state != state) //经过tcp_rcv_state_process()函数,监听sock的状态改变了 parent->sk_data_ready(parent, 0); //唤醒经常处理连接请求 } else { __sk_add_backlog(child, skb); //否则放入后备队列中 } bh_unlock_sock(child); //解锁 sock_put(child); //递减使用计数器 return ret; }
兜兜转转,最终还是进入到了根据sock状态处理数据包的tcp_rcv_state_process()函数。不过此时使用的是通信sock,状态为SYN_RECV。
二、建立连接
// file: net/ipv4/tcp_input.c 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; ...... req = tp->fastopen_rsk; //fastopen选项相关 ...... /* step 5: check the ACK field */ if (true) { int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) > 0; //检查ACK确认号的合法值 switch (sk->sk_state) { case TCP_SYN_RECV: if (acceptable) { if (req) { //fastopen走这个流程 tcp_synack_rtt_meas(sk, req); tp->total_retrans = req->num_retrans; reqsk_fastopen_remove(sk, req, false); } else { icsk->icsk_af_ops->rebuild_header(sk); tcp_init_congestion_control(sk); ////初始化拥塞控制 tcp_mtup_init(sk); //mtu探测初始化 tcp_init_buffer_space(sk); ////初始化接收和发送缓存空间 tp->copied_seq = tp->rcv_nxt; } smp_mb(); tcp_set_state(sk, TCP_ESTABLISHED); //修改通信sock状态为ESTABLISHED // 调用sock_def_wakeup唤醒该sock上等待队列的所有进程 // 即唤醒服务器程序来接收客户端的连接请求,对接了accept()内容中的inet_csk_wait_for_connect()函数 sk->sk_state_change(sk); ...... } } ...... return 0; }
三、总结
第三次握手,服务端所做的工作,就是把当前半连接对象删除,创建了通信sock后加入到全连接队列中,最后将新连接状态设置为 ESTABLISHED,唤醒服务器程序来接收客户端的连接请求。