TCP三次握手源码分析(客户端接收SYN+ACK以及发送ACK)
内核版本:Linux 3.10
内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且网页可全局搜索函数)
《TCP三次握手源码分析(客户端发送SYN)》
《TCP三次握手源码分析(服务端接收SYN以及发送SYN+ACK)》
一、客户端接收SYN+ACK
随着SYN+ACK报文的发送,连接建立随着第二次握手报文来到客户端。
客户端接收到这个SYN+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) { ...... //根据报文的源地址和目的地址在established哈希表以及listen哈希表中查找连接 //由于之前调用connect时已经sock接链入到established哈希表中(查询端口的时候链入) sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); //最终在ehash中查找到状态为SYN_SENT的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()处理函数。
// net/ipv4/tcp_ipv4.c int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) { ...... 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; }
由于sock是SYN_SENT状态,所以直接进入到tcp_rcv_state_process(),不过相较于服务端,会进入另外一个分支处理。
// 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) { ...... switch (sk->sk_state) { case TCP_CLOSE: //处理CLOSE状态的sock ...... case TCP_LISTEN: //处理LISTEN状态的sock ...... case TCP_SYN_SENT: //处理SYN_SENT状态的sock queued = tcp_rcv_synsent_state_process(sk, skb, th, len); //处理SYN+ACK报文 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; } ...... }
2.tcp_rcv_synsent_state_process()函数
// file: net/ipv4/tcp_input.c 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_parse_options(skb, &tp->rx_opt, 0, &foc); //分析该请求的tcp各个选项,比如时间戳、窗口大小、快速开启等选项 if (tp->rx_opt.saw_tstamp) tp->rx_opt.rcv_tsecr -= tp->tsoffset; if (th->ack) { //处理带ACK标识的报文 //如果接收到的确认号小于或等于已发送未确认的序列号, //或者大于下次要发送数据的序列号,非法报文,发送RST报文 // ack <= una || ack > nxt 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; //如果开启了时间戳选项,且回显时间戳不为空 //且回显时间戳不在当前时间和SYN报文发送的时间窗内,就认为该报文非法 //对端时间戳不合法 if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr && !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); //ecn标识 tcp_init_wl(tp, TCP_SKB_CB(skb)->seq); //记录窗口更新时数据包序号 tcp_ack(sk, skb, FLAG_SLOWPATH); //确认ACK的确认号正常(可能会更新发送窗口) tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; //更新下一个要接收的序号 tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; //更新窗口中最小的序号 tp->snd_wnd = ntohs(th->window); //获取发送窗口 if (!tp->rx_opt.wscale_ok) { //没有窗口扩大因子 tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0; //设置为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; //tcp首部需要增加时间戳长度 tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; //mss需要减去时间戳长度 tcp_store_ts_recent(tp); //设置回显时间戳 } else { tp->tcp_header_len = sizeof(struct tcphdr); //记录tcp首部长度 } if (tcp_is_sack(tp) && sysctl_tcp_fack) //有sack选项,开启了fack算法,则打标记 tcp_enable_fack(tp); tcp_mtup_init(sk); //MTU探测相关初始化 tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); //计算mss tcp_initialize_rcv_mss(sk); //初始化rcv_mss 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)) //fastopen处理 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) { //延迟确认 inet_csk_schedule_ack(sk); //设置ICSK_ACK_SCHED标识,有ACK等待发送,当前不发送 icsk->icsk_ack.lrcvtime = tcp_time_stamp; tcp_enter_quickack_mode(sk); //进入快速ack模式 //激活延迟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; } ...... }
里面比较重要的三个函数:
tcp_ack()删除第一次握手的发送队列以及在connect时设置的重传定时器;
tcp_finish_connect()连接建立并唤醒在该socket睡眠的进程;
tcp_send_ack()发送ACK,发起第三次握手请求。
3.tcp_finish_connect()函数
tcp_finish_connect()函数的主要工作:
- 将sock状态推向ESTABLISHED,也就意味着在客户端来看,连接已经建立;
- 然后是初始化路由和拥塞控制等参数;
- 同时如果用户开启了保活定时器,此时开始生效,计算连接空闲时间;
- 最后就是唤醒该socket上所有睡眠的进程,如果有进程使用异步通知,则发送SIGIO信号通知进程可写;
// file: net/ipv4/tcp_input.c 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); //对于客户端来说,此时连接已经建立,设置连接状态为ESTABLISHED if (skb != NULL) { icsk->icsk_af_ops->sk_rx_dst_set(sk, skb); //设置接收路由缓存 security_inet_conn_established(sk, skb); } icsk->icsk_af_ops->rebuild_header(sk); //检查或重建路由 tcp_init_metrics(sk); tcp_init_congestion_control(sk); //初始化拥塞控制 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)); 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)) { sk->sk_state_change(sk); //指向sock_def_wakeup,唤醒该socket上所有睡眠的进程 sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); //如果进程使用了异步通知,发送SIGIO信号通知进程可写 } }
4.tcp_send_ack()函数,发送ACK
// net/ipv4/tcp_output.c void tcp_send_ack(struct sock *sk) { struct sk_buff *buff; if (sk->sk_state == TCP_CLOSE) return; //分配skb,失败需要启用延迟ack定时器 buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC)); if (buff == NULL) { inet_csk_schedule_ack(sk); inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN; inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX); return; } //预留头部空间,准备控制信息 skb_reserve(buff, MAX_TCP_HEADER); tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK); TCP_SKB_CB(buff)->when = tcp_time_stamp; tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC)); //...传递到IP层,最终经网卡发送到服务端 }