TCP FIN_WAIT2定时器

注:本文分析基于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定时器了,我们后续再分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值