PROC文件tcp_fin_timeout默认为60秒,内核中相应的变量为init_net.ipv4.sysctl_tcp_fin_timeout,不过其以jiffies表示,默认值为TCP_FIN_TIMEOUT,即(60 * HZ)。此值表示一个不再被应用层使用(执行了close调用)的TCP连接处于FIN_WAIT_2状态的时长,如果在此时间内未能接收到对端的FIN结束报文,内核将复位此连接。但是除此之外,如果应用层是执行shutdown(SHUT_WR)操作关闭了套接口的发送,TCP连接还可进行接收操作,此种情况下的TCP连接处于FIN_WAIT_2状态不受tcp_fin_timeout的时间限制,将会永久的等待对端去关闭连接或者本地使用close关闭。
$ cat /proc/sys/net/ipv4/tcp_fin_timeout
60
$
static struct ctl_table ipv4_net_table[] = {
{
.procname = "tcp_fin_timeout",
.data = &init_net.ipv4.sysctl_tcp_fin_timeout,
},
}
#define TCP_TIMEWAIT_LEN (60*HZ)
#define TCP_FIN_TIMEOUT TCP_TIMEWAIT_LEN
static int __net_init tcp_sk_init(struct net *net)
{
net->ipv4.sysctl_tcp_retries1 = TCP_RETR1;
net->ipv4.sysctl_tcp_retries2 = TCP_RETR2;
net->ipv4.sysctl_tcp_orphan_retries = 0;
net->ipv4.sysctl_tcp_fin_timeout = TCP_FIN_TIMEOUT;
}
一、linger2时长
sysctl_tcp_fin_timeout是针对所有套接口的全局配置,内核针对单个套接口的FIN_WAIT_2超时时间提供了linger2套接口选项,其值优先级高于全局的sysctl_tcp_fin_timeout。应用层可通过setsockopt系统调用设置套接口的linger2值,用户层以秒值下发,内核中将其装换为jiffies为单位的值保存在TCP套接口结构的linger2成员中。linger2的值不能够大于sysctl_tcp_fin_timeout的时间值,否则将其置为0。另外,如果用户层下发的值小于0,linger2设置为-1。TCP_LINGER2的设置逻辑如下:
static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, unsigned int optlen)
{
struct tcp_sock *tp = tcp_sk(sk);
switch (optname) {
case TCP_LINGER2:
if (val < 0)
tp->linger2 = -1;
else if (val > net->ipv4.sysctl_tcp_fin_timeout / HZ)
tp->linger2 = 0;
else
tp->linger2 = val * HZ;
break;
}
}
内核中获取FIN_WAIT_2超时时间由函数tcp_fin_time实现。由其代码可见如果linger2的时间值不为零,取其值,否则,使用sysctl_tcp_fin_timeout的时间值。但是,最终的FIN_WAIT_2超时时间还与当前连接的超时重传时间RTO有关,其不能大于RTO的3.5倍(rto << 2) - (rto >> 1)的结果值。RTO*3.5表示的时长可允许对端的FIN报文重传2次。
static inline int tcp_fin_time(const struct sock *sk)
{
int fin_timeout = tcp_sk(sk)->linger2 ? : sock_net(sk)->ipv4.sysctl_tcp_fin_timeout;
const int rto = inet_csk(sk)->icsk_rto;
if (fin_timeout < (rto << 2) - (rto >> 1))
fin_timeout = (rto << 2) - (rto >> 1);
return fin_timeout;
}
二、超时定时器设置
FIN_WAIT_2状态的超时定时器设置分两种情况,其一是由应用层的shutdown系统调用所引发;其二是由close系统调用引发。先看第一种情况,shutdown可关闭接收或者发送方向的流量(仅关闭发送方向时触发FIN报文发送),导致套接口处于半关闭状态,并且套接口状态走到FIN_WAIT_1(关闭接收方向套接口状态不变),等待对端响应ACK报文。如果对端不响应ACK报文,本端FIN报文会进行超时重传,直到出错处理。
反之,当接收到对端回应的ACK报文时,处理流程进入函数tcp_rcv_state_process的TCP_FIN_WAIT1分支。由于此时应用层并没有close套接口,其SOCK_DEAD标志未设置,仅是将套接口的状态设置为TCP_FIN_WAIT2,退出处理流程,留待本端应用层close系统调用去结束连接;或者对端发送FIN报文来结束连接。
套接口的SOCK_DEAD标志没有置位还有一种可能是,应用层调用了close接口,但是设置了套接口的SOCK_LINGER选项,注意其与TCP_LINGER2不同,设置了此选项后,tcp_close函数不会立即置位SOCK_DEAD,而是等待sk_lingertime规定的时长,所以即使应用层调用了tcp_close操作,如果还在sk_lingertime时长内,SOCK_DEAD标志也还没有设置。通常应用层未设置SOCK_LINGER选项,这不是默认情况。如果是应用层进程在退出时自动调用了套接口的close函数,内核将忽略SOCK_LINGER选项设置不执行等待。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
switch (sk->sk_state) {
case TCP_FIN_WAIT1: {
tcp_set_state(sk, TCP_FIN_WAIT2);
sk->sk_shutdown |= SEND_SHUTDOWN;
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk);
break;
}
但是,如果本地的应用层直接使用close调用完全关闭双向的连接而不是shutdown,看一下tcp_rcv_state_process函数接下来的处理。如果用户层设置的套接口linger2值小于零,内核将不会在FIN_WAIT_2状态等待,直接销毁套接口。如果FIN_WAIT_2的超时时间(tmo)大于TCP_TIMEWAIT_LEN(60秒)的时长,启动keepalive定时器,定时时长为二者之差(tmo - TCP_TIMEWAIT_LEN)。以上对tcp_fin_time函数的介绍可知,默认情况下其值等于TCP_TIMEWAIT_LEN的值,内核默认不执行此分支。
如果完全关闭的套接口在FIN_WAIT_1状态时,接收到的是一个带有FIN标志的报文(FIN+ACK报文)或者此处的套接口还未被应用层释放。启动keepalive定时器,定时时长为FIN_WAIT_2的超时时间。需要注意的是sock_owned_by_user能够成立的条件十分苛刻:仅在tcp_close函数释放套接口owner前,release_sock函数的spin_lock_bh未获得锁而等待时发生。
对于HTTP服务端来说,如果客户端发送了FIN报文结束连接,HTTP服务端通常回复FIN+ACK报文,实现3个报文结束连接。所以通常情况下客户端会启动tmo时长的keepalive定时器。
bind失败的解决方案
docx
5星
超过95%的资源
27KB
下载
if (tp->linger2 < 0) {
tcp_done(sk);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
return 1;
}
tmo = tcp_fin_time(sk);
if (tmo > TCP_TIMEWAIT_LEN) {
inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
} else if (th->fin || sock_owned_by_user(sk)) {
inet_csk_reset_keepalive_timer(sk, tmo);
} else {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto discard;
}
break;
}
}
除了以上情况之外,如果FIN_WAIT_2的超时时间小于等于TCP_TIMEWAIT_LEN的值,并且接收到的报文不带有FIN标志(仅ACK),此种情况为TCPIP协议中定义的FIN_WAIT_2状态。启动TIME_WAIT定时器。定时时长设置为FIN_WAIT_2的超时时间,如果其小于当前连接的重传超时时间RTO的话,使用RTO时间,以允许FIN报文至少重传一次。tcp_time_wait函数最后销毁TCP套接口。
void tcp_time_wait(struct sock *sk, int state, int timeo)
{
struct inet_timewait_sock *tw;
if (timeo < rto)
timeo = rto;
inet_twsk_schedule(tw, timeo);
tcp_done(sk);
}
最后,接着半连接的套接口流程。如果应用层使用close系统调用主动关闭此半连接时,套接口的状态已经处于TCP_FIN_WAIT2了(tcp_rcv_state_process函数中设置)。如果TCP套接口的linger2小于零,直接发送重置reset报文;否则,根据FIN_WAIT_2的超时时间与TCP_TIMEWAIT_LEN的大小关系,启动keepalive定时器或者time_wait定时器,防止对端一直不发送FIN结束报文导致本端套接口不能释放。
但是,如果本地应用层不close此半连接,并且对端也不结束连接,不发送FIN报文,此连接将一直存在。
void tcp_close(struct sock *sk, long timeout)
{
if (sk->sk_state == TCP_FIN_WAIT2) {
struct tcp_sock *tp = tcp_sk(sk);
if (tp->linger2 < 0) {
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, GFP_ATOMIC);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONLINGER);
} else {
const int tmo = tcp_fin_time(sk);
if (tmo > TCP_TIMEWAIT_LEN) {
inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
} else {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
}
}
三、定时器处理
如上所述套接口的FIN_WAIT_2状态没有单独的定时器,其使用TCP的保活定时器keepalive和time_wait定时器实现计时。保活定时器超时处理函数为tcp_keepalive_timer,如果TCP套接口的linger2大于等于零,并且tcp_fin_time得到的超时时间大于TCP_TIMEWAIT_LEN(60秒)时间时,启动time_wait定时器,定时时长设置为二者差值;否则,直接发送重置reset报文到对端。默认情况下tcp_fin_time时间等于TCP_TIMEWAIT_LEN时间,故在FIN_WAIT_2状态超时的处理为发送连接重置reset报文。
static void tcp_keepalive_timer (struct timer_list *t)
{
struct sock *sk = from_timer(sk, t, sk_timer);
struct tcp_sock *tp = tcp_sk(sk);
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;
if (tmo > 0) {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
tcp_send_active_reset(sk, GFP_ATOMIC);
goto death;
}
}
定时器time_wait的超时处理函数为tw_timer_handler,清理time_wait套接口。
static void tw_timer_handler(struct timer_list *t)
{
struct inet_timewait_sock *tw = from_timer(tw, t, tw_timer);
if (tw->tw_kill)
__NET_INC_STATS(twsk_net(tw), LINUX_MIB_TIMEWAITKILLED);
else
__NET_INC_STATS(twsk_net(tw), LINUX_MIB_TIMEWAITED);
inet_twsk_kill(tw);
}