注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4
随着SYNACK报文的发送,连接建立随着第二次握手报文来到客户端。客户端接收到这个SYNACK报文,就认为连接建立了。仍然从TCP层开始分析,依然是由tcp_v4_rcv()入手。
int tcp_v4_rcv(struct sk_buff *skb)
{
...
//根据报文的源和目的地址在established哈希表以及listen哈希表中查找连接
//由于之前调用connect时已经将连接加入到established哈希表中
//所以在接收到服务端的SYNACK时,就能从established表中找到对应的连接
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
if (!sk)
goto no_tcp_socket;
...
ret = 0;
if (!sock_owned_by_user(sk)) {//如果sk没有被用户锁定,及没在使用
if (!tcp_prequeue(sk, skb))
ret = tcp_v4_do_rcv(sk, skb);//进入到主处理函数
}
...
}
然后还是来到老地方——tcp_v4_do_rcv()。
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
...
}
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;
...
}
不过因为此时我们socket的状态是SYN_SENT,所以就直接进入tcp_rcv_state_process()的处理流程了。
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;
bool acceptable;
u32 synack_stamp;
tp->rx_opt.saw_tstamp = 0;
switch (sk->sk_state) {
...
case TCP_SYN_SENT:
//进入到synack报文的处理流程
queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
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;
}
...
}
层层深入,最后进入的归宿是tcp_rcv_synsent_state_process()。
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选项
tcp_parse_options(skb, &tp->rx_opt, 0, &foc);
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
tp->rx_opt.rcv_tsecr -= tp->tsoffset;
if (th->ack) {//处理带ACK标志的报文
//如果接收到的确认号小于或等于已发送未确认的序列号,
//或者大于下次要发送数据的序列号,非法报文,发送RST报文
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;
//如果开启了时间戳选项,且回显时间戳不为空
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
//且回显时间戳不在当前时间和SYN报文发送的时间窗内,就认为该报文非法
!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);
tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
//确认ACK的确认号正常
tcp_ack(sk, skb, FLAG_SLOWPATH);
/* 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) {
tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 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;
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
tcp_store_ts_recent(tp);//记录对端的时间戳
} else {
tp->tcp_header_len = sizeof(struct tcphdr);
}
if (tcp_is_sack(tp) && sysctl_tcp_fack)
tcp_enable_fack(tp);
tcp_mtup_init(sk);//mtu探测初始化
tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
tcp_initialize_rcv_mss(sk);
/* 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();
//连接建立完成,将连接状态推向established
//然后唤醒等在该socket的所有睡眠进程
tcp_finish_connect(sk, skb);
//快速开启选项
if ((tp->syn_fastopen || tp->syn_data) &&
tcp_rcv_fastopen_synack(sk, skb, &foc))
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) {
//设置ICSK_ACK_SCHED标识,有ACK等待发送,当前不发送
inet_csk_schedule_ack(sk);
//最后一次接收到数据包的时间
icsk->icsk_ack.lrcvtime = tcp_time_stamp;
//设置快速确认模式,以及快速确认模式下可以发送的ACK报文数
tcp_enter_quickack_mode(sk);
//激活延迟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;
}
//没有ACK标记,只有RST标记,丢弃报文
if (th->rst) {
goto discard_and_undo;
}
/* PAWS check. */
//话说这个PAMS到底是个啥。。。
if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
tcp_paws_reject(&tp->rx_opt, 0))
goto discard_and_undo;
//在SYNSENT状态收到syn报文,说明这是同时打开的场景
if (th->syn) {
/* We see SYN without ACK. It is attempt of
* simultaneous connect with crossed SYNs.
* Particularly, it can be connect to self.
*/
//设置连接状态为SYN_RECV
tcp_set_state(sk, TCP_SYN_RECV);
...//暂不分析
}
...
}
我们知道,客户端收到这个SYNACK报文后就会进入ESTABLISHED状态,这主要是tcp_finish_connect()里操作的。
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);
//对于客户端来说,此时连接已经建立,设置连接状态为established
tcp_set_state(sk, TCP_ESTABLISHED);
if (skb != NULL) {
icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
security_inet_conn_established(sk, skb);
}
/* Make sure socket is routed, for correct metrics. */
icsk->icsk_af_ops->rebuild_header(sk);
//初始化TCP metrics,用于保存连接相关的路由信息
tcp_init_metrics(sk);
//初始化拥塞控制
tcp_init_congestion_control(sk);
//记录最后一个数据包发送的时间戳
tp->lsndtime = tcp_time_stamp;
//初始化接收缓存和发送缓存
tcp_init_buffer_space(sk);
//如果使用了SO_KEEPALIVE选项,那就激活保活定时器
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)) {
//指向sock_def_wakeup,唤醒该socket上所有睡眠的进程
sk->sk_state_change(sk);
//如果进程使用了异步通知,发送SIGIO信号通知进程可写
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
}
}
tcp_finish_connect()中主要有以下几点重要操作:
- 将socket状态推向ESTABLISHED,也就意味着在客户端来看,连接已经建立
- 然后是初始化路由和拥塞控制等一下参数
- 同时如果用户开启了保活定时器,此时开始生效,计算连接空闲时间
- 最后就是唤醒该socket上所有睡眠的进程,如果有进程使用异步通知,则发送SIGIO信号通知进程可写
最后就是发送第三次握手报文——ACK报文。不过ACK报文并不一定是马上发送,在一下几种情况下会延迟发送。
- 当前刚好有数据等待发送
- 用户设置了TCP_DEFER_ACCEPT选项
- 禁用快速确认模式,可通过TCP_QUICKACK选项设置
不过即使延迟,也最多延迟200ms,这个通过延迟ACK定时器操作。
如果是马上发送ACK报文,则通过tcp_send_ack()发送。
/* This routine sends an ack and also updates the window. */
void tcp_send_ack(struct sock *sk)
{
struct sk_buff *buff;
/* If we have been reset, we may not send again. */
if (sk->sk_state == TCP_CLOSE)
return;
tcp_ca_event(sk, CA_EVENT_NON_DELAYED_ACK);
/* We are not putting this on the write queue, so
* tcp_transmit_skb() will set the ownership to this
* sock.
*/
buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));
if (buff == NULL) {//分配失败
//和上面讲到的延迟ACK一样,设置延迟ACK,稍后再发送
inet_csk_schedule_ack(sk);
//超时时间为200ms
inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN;
//激活延迟ACK定时器
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
TCP_DELACK_MAX, TCP_RTO_MAX);
return;
}
/* Reserve space for headers and prepare control bits. */
skb_reserve(buff, MAX_TCP_HEADER);
//初始化无数据的skb
tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);
/* We do not want pure acks influencing TCP Small Queues or fq/pacing
* too much.
* SKB_TRUESIZE(max(1 .. 66, MAX_TCP_HEADER)) is unfortunately ~784
* We also avoid tcp_wfree() overhead (cache line miss accessing
* tp->tsq_flags) by using regular sock_wfree()
*/
skb_set_tcp_pure_ack(buff);
/* Send it off, this clears delayed acks for us. */
skb_mstamp_get(&buff->skb_mstamp);
//又到了这个路径,往后就是IP层了
tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));
}