9.9.1 Why
如果应用进程调用close系统调用关闭socket,但此时socket与对端的通信尚未完成,则这个socket被称为“孤儿socket”。如果孤儿socekt进入FIN_WAIT2状态(或socket进入FIN_WAIT2状态后再成为孤儿socket),会等待对端发送FIN以彻底结束连接。但如果对端一直不发送FIN则这个孤儿socekt会一直存在,从而一直占用系统资源。为了解决这个问题,需要在孤儿socket进入FIN_WAIT2状态时设置FIN_WAIT2定时器,如果定时器超时时仍然没有收到FIN,则关闭socket。
9.9.2 When
开启FIN_WAIT2定时器的函数也是inet_csk_reset_keepalive_timer。设置FIN_WAIT2定时器的时机主要有两个:
(1)应用进程调用close系统调用而socekt正处于TCP_FIN_WAIT2状态时:
2059 void tcp_close(struct sock *sk, long timeout)
2060 {
...
2183 if (sk->sk_state == TCP_FIN_WAIT2) {
2184 struct tcp_sock *tp = tcp_sk(sk);
2185 if (tp->linger2 < 0) {
...
2190 } else {
2191 const int tmo = tcp_fin_time(sk);
2192
2193 if (tmo > TCP_TIMEWAIT_LEN) { //等待对端发FIN的时间长度大于TIME_WAIT的时间
2194 inet_csk_reset_keepalive_timer(sk,
2195 tmo - TCP_TIMEWAIT_LEN);
2196 } else {
...
然后socket就会成为孤儿socket。不过FIN_WAIT2定时器会一直“看护”它。(2)孤儿socket进入FIN_WAIT2状态时:
5600 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
5601 const struct tcphdr *th, unsigned int len)
5602 {
...
5751 case TCP_FIN_WAIT1:
...
5780 if (!sock_flag(sk, SOCK_DEAD))
5781 /* Wake up lingering close() */
5782 sk->sk_state_change(sk);
5783 else {
5784 int tmo;
5785
5786 if (tp->linger2 < 0 ||
5787 (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && //ACK中有数据或FIN标记位
5788 after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) { //ACK中有数据,孤儿socket不需要数据,马上关掉TCP
5789 tcp_done(sk);
5790 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
5791 return 1;
5792 }
5793
5794 tmo = tcp_fin_time(sk);
5795 if (tmo > TCP_TIMEWAIT_LEN) {
5796 inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
...
可见FIN_WAIT2定时器功能开启的必要条件是应用进程没有使用TCP_LINGER2 socket选项将tp->linger2的值设为小于0。
与Keepalive定时器一样,FIN_WAIT2定时器只能用SO_KEEPALIVE socket选项拆除。
FIN_WAIT2定时器的超时时间为tcp_fin_time减去TCP_TIMEWAIT_LEN的值:
1132 static inline int tcp_fin_time(const struct sock *sk)
1133 {
1134 int fin_timeout = tcp_sk(sk)->linger2 ? : sysctl_tcp_fin_timeout;
1135 const int rto = inet_csk(sk)->icsk_rto;
1136
1137 if (fin_timeout < (rto << 2) - (rto >> 1))
1138 fin_timeout = (rto << 2) - (rto >> 1);
1139
1140 return fin_timeout;
1141 }
其中tcp_sk(sk)->linger2由TCP_LINGER2 socket选项赋值,sysctl_tcp_fin_timeout的值由net.ipv4.tcp_fin_timeout内核选项决定,默认与TCP_TIMEWAIT_LEN一样(60s)。
9.9.3 What
FIN_WAIT2定时器的超时为tcp_keepalive_timer:
558 static void tcp_keepalive_timer (unsigned long data)
559 {
560 struct sock *sk = (struct sock *) data;
561 struct inet_connection_sock *icsk = inet_csk(sk);
562 struct tcp_sock *tp = tcp_sk(sk);
563 u32 elapsed;
...
578 if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) { //socket为孤儿socket且处于FIN_WAIT2状态
579 if (tp->linger2 >= 0) {
580 const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;
581
582 if (tmo > 0) {
583 tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); //进入TIME_WAIT状态
584 goto out;
585 }
586 }
587 tcp_send_active_reset(sk, GFP_ATOMIC);//发送RST
588 goto death;
589 }
...
635 death:
636 tcp_done(sk); //关闭本地TCP
637
638 out:
639 bh_unlock_sock(sk);
640 sock_put(sk);
641 }
FIN_WAIT2定时器的超时动作有两种:进入TIME_WAIT状态或发送RST报文然后关闭TCP。