注:本文分析基于3.10.107内核版本
之前我们一直考虑的是TCP建链过程中出现问题时,双方用户是如何感知,并处理的。在TCP建链的三次我手中主要是由超时重传定时器以及SYNACK定时器来进行链接状态的维护。下面我们来考虑一下在TCP四次挥手的断链过程中各个socket的状态维护和处理。
在具体分析之前,我们来看下这四次挥手时双方socket状态的转换过程。借用下百科上的图,
此次我们先从FIN_WAIT2定时器上说起。
FIN_WAIT2定时器的注册
FIN_WAIT2定时器其实和SYNACK定时器共用一个定时器实例,在定时器超时处理函数里根据socket的状态来进行区别。因此该定时器的注册时机和SYNACK定时器是一样,都在调用socket系统调用时,内核通过tcp_v4_init_sock()函数进行注册。详见TCP超时重传定时器梳理,以及**Linux socket系统调用(一)**的2.1.2.3 sock初始化章节。
FIN_WAIT2定时器的触发
从上面四次挥手的示意图,可以看出FIN_WAIT2的状态出现在服务端回复客户端的FIN报文之后,客户端的socket状态就进入FIN_WAIT2,此时也就会触发FIN_WAIT2定时器。
TCP层的收包过程如下:
tcp_v4_rcv() --> tcp_v4_do_rcv() --> tcp_rcv_state_process()
其中tcp_rcv_state_process()函数的功能是:负责socket状态除ESTABLISHED 和TIME_WAIT之外的所有报文的接收和处理。这里我们只先关注FIN_WAIT2状态。
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;
...
case TCP_FIN_WAIT1:
...
tcp_set_state(sk, TCP_FIN_WAIT2);//设置socket状态为FIN_WAIT2
...
if (!sock_flag(sk, SOCK_DEAD))
/* Wake up lingering close() */
sk->sk_state_change(sk);
else {
int tmo;
if (tp->linger2 < 0 ||
(TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {
tcp_done(sk);
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
return 1;
}
tmo = tcp_fin_time(sk);
//TCP_TIMEWAIT_LEN的值为60HZ,即60s
if (tmo > TCP_TIMEWAIT_LEN) {
//这里便是FIN_WAIT2定时器的激活
//注意它的超时时间是(tmo - TCP_TIMEWAIT_LEN)
inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
} else if (th->fin || sock_owned_by_user(sk)) {
/* Bad case. We could lose such FIN otherwise.
* It is not a big problem, but it looks confusing
* and not so rare event. We still can lose it now,
* if it spins in bh_lock_sock(), but it is really
* marginal case.
*/
//该报文是ACK+FIN
//或者此时用户正在使用该sock,也使用FIN_WAIT2定时器处理
inet_csk_reset_keepalive_timer(sk, tmo);
} else {
//也就是<=60的正常情况,都是进入这个函数处理,和我想象的不太一样
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);//这里是进入TIME_WAIT定时器了
goto discard;
}
}
}
break;
...
}
}
...
/* tcp_data could move socket to TIME-WAIT */
if (sk->sk_state != TCP_CLOSE) {
//检测发送队列中是否还有数据要发送,有的话就发送
tcp_data_snd_check(sk);
tcp_ack_snd_check(sk);
}
...
}
tcp_fin_time()函数里对fin_wait2状态的超时时间进行计算,最小为3.5倍RTO时间。
static inline int tcp_fin_time(const struct sock *sk)
{
//sysctl_tcp_fin_timeout的值即为/proc/sys/net/ipv4/tcp_fin_timeout,默认为60S
int fin_timeout = tcp_sk(sk)->linger2 ? : sysctl_tcp_fin_timeout;
//icsk_rto的值在创建sock时初始化为1HZ
const int rto = inet_csk(sk)->icsk_rto;
//fin_timeout最小为3.5倍的RTO,这个3.5倍不知道怎么来的。。。
if (fin_timeout < (rto << 2) - (rto >> 1))
fin_timeout = (rto << 2) - (rto >> 1);
return fin_timeout;
}
FIN_WAIT2定时器的超时处理
FIN_WAIT2定时器的超时处理由tcp_keepalive_timer负责,该超时函数还负责保活定时器以及SYNACK定时器的超时处理,主要根据此时socket的状态区分。
static void tcp_keepalive_timer (unsigned long data)
{
struct sock *sk = (struct sock *) data;
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
u32 elapsed;
...
//通过判断sock状态为TCP_FIN_WAIT2,确定这个是FIN_WAIT2定时器
if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
if (tp->linger2 >= 0) {
const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;
//超过TCP_TIMEWAIT_LEN(60s)的时间,交由TIME_WAIT定时器处理
if (tmo > 0) {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
//走到这里的情况,是使用SO_LINGER,将l_linger设置成负数
//有一点不明白的是,按照书上介绍使用SO_LINGER,将l_linger设置0时,应该是发送RST报文直接复位
//但是这里明显走不到发送RST的流程
//此处分析先保留,后续看下close里怎么处理SO_LINGER选项的
tcp_send_active_reset(sk, GFP_ATOMIC);//发送RST报文,复位连接
goto death;
}
...
death:
tcp_done(sk);//将socket状态设置为close,并释放相关资源
...
}
所以总结起来,FIN_WAIT2定时器的流程比较简单,同样也比较怪异(至少我是这么觉得的)。表现在如果设置 tcp_fin_timeout大于60s时,超过60秒的时间才作为FIN_WAIT2状态的超时时间,同时还要将这部分超时时间再传递给tcp_time_wait()处理。系统一般默认tcp_fin_timeout为60秒,也就是不会触发FIN_WAIT2定时器,直接进入tcp_time_wait()函数。这个就涉及到TIME_WAIT定时器了,我们后续再分析。