diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c index 298dec19b3f4..01f2316dce90 100644 --- a/sys/netinet/tcp_input.c +++ b/sys/netinet/tcp_input.c @@ -2301,8 +2301,12 @@ process_ACK: * compressed state. */ if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { + int timeout; + soisdisconnected(so); - callout_reset(tp->tt_2msl, tcp_maxidle, + timeout = (tcp_fast_finwait2_recycle) ? + tcp_finwait2_timeout : tcp_maxidle; + callout_reset(tp->tt_2msl, timeout, tcp_timer_2msl, tp); } tp->t_state = TCPS_FIN_WAIT_2; diff --git a/sys/netinet/tcp_reass.c b/sys/netinet/tcp_reass.c index 298dec19b3f4..01f2316dce90 100644 --- a/sys/netinet/tcp_reass.c +++ b/sys/netinet/tcp_reass.c @@ -2301,8 +2301,12 @@ process_ACK: * compressed state. */ if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { + int timeout; + soisdisconnected(so); - callout_reset(tp->tt_2msl, tcp_maxidle, + timeout = (tcp_fast_finwait2_recycle) ? + tcp_finwait2_timeout : tcp_maxidle; + callout_reset(tp->tt_2msl, timeout, tcp_timer_2msl, tp); } tp->t_state = TCPS_FIN_WAIT_2; diff --git a/sys/netinet/tcp_subr.c b/sys/netinet/tcp_subr.c index 4257603571c0..14287b200364 100644 --- a/sys/netinet/tcp_subr.c +++ b/sys/netinet/tcp_subr.c @@ -329,6 +329,7 @@ tcp_init(void) tcp_rexmit_min = TCPTV_MIN; tcp_rexmit_slop = TCPTV_CPU_VAR; tcp_inflight_rttthresh = TCPTV_INFLIGHT_RTTTHRESH; + tcp_finwait2_timeout = TCPTV_FINWAIT2_TIMEOUT; INP_INFO_LOCK_INIT(&tcbinfo, "tcp"); LIST_INIT(&tcb); diff --git a/sys/netinet/tcp_timer.c b/sys/netinet/tcp_timer.c index 86d9c6a3e1e8..33e48063de5f 100644 --- a/sys/netinet/tcp_timer.c +++ b/sys/netinet/tcp_timer.c @@ -96,6 +96,15 @@ static int always_keepalive = 1; SYSCTL_INT(_net_inet_tcp, OID_AUTO, always_keepalive, CTLFLAG_RW, &always_keepalive , 0, "Assume SO_KEEPALIVE on all TCP connections"); +int tcp_fast_finwait2_recycle = 0; +SYSCTL_INT(_net_inet_tcp, OID_AUTO, fast_finwait2_recycle, CTLFLAG_RW, + &tcp_fast_finwait2_recycle, 0, "Recycle closed FIN_WAIT_2 connections faster"); + +int tcp_finwait2_timeout; +SYSCTL_PROC(_net_inet_tcp, OID_AUTO, finwait2_timeout, CTLTYPE_INT|CTLFLAG_RW, + &tcp_finwait2_timeout, 0, sysctl_msec_to_ticks, "I", ""); + + static int tcp_keepcnt = TCPTV_KEEPCNT; /* max idle probes */ int tcp_maxpersistidle; @@ -211,13 +220,24 @@ tcp_timer_2msl(xtp) * still waiting for peer to close and connection has been idle * too long, or if 2MSL time is up from TIME_WAIT, delete connection * control block. Otherwise, check again in a bit. + * + * If fastrecycle of FIN_WAIT_2, in FIN_WAIT_2 and receiver has closed, + * there's no point in hanging onto FIN_WAIT_2 socket. Just close it. + * Ignore fact that there were recent incoming segments. */ - if (tp->t_state != TCPS_TIME_WAIT && - (ticks - tp->t_rcvtime) <= tcp_maxidle) - callout_reset(tp->tt_2msl, tcp_keepintvl, - tcp_timer_2msl, tp); - else - tp = tcp_close(tp); + if (tcp_fast_finwait2_recycle && tp->t_state == TCPS_FIN_WAIT_2 && + tp->t_inpcb && tp->t_inpcb->inp_socket && + (tp->t_inpcb->inp_socket->so_rcv.sb_state & SBS_CANTRCVMORE)) { + tcpstat.tcps_finwait2_drops++; + tp = tcp_close(tp); + } else { + if (tp->t_state != TCPS_TIME_WAIT && + (ticks - tp->t_rcvtime) <= tcp_maxidle) + callout_reset(tp->tt_2msl, tcp_keepintvl, + tcp_timer_2msl, tp); + else + tp = tcp_close(tp); + } #ifdef TCPDEBUG if (tp != NULL && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) diff --git a/sys/netinet/tcp_timer.h b/sys/netinet/tcp_timer.h index 1e1c239c7ff1..8d8742de2fdf 100644 --- a/sys/netinet/tcp_timer.h +++ b/sys/netinet/tcp_timer.h @@ -89,6 +89,8 @@ #define TCPTV_INFLIGHT_RTTTHRESH (10*hz/1000) /* below which inflight disengages, in msec */ +#define TCPTV_FINWAIT2_TIMEOUT (60*hz) /* FIN_WAIT_2 timeout if no receiver */ + /* * Minimum retransmit timer is 3 ticks, for algorithmic stability. * TCPT_RANGESET() will add another TCPTV_CPU_VAR to deal with @@ -152,6 +154,9 @@ extern int tcp_backoff[]; struct tcptw; +extern int tcp_finwait2_timeout; +extern int tcp_fast_finwait2_recycle; + void tcp_timer_init(void); void tcp_timer_2msl(void *xtp); struct tcptw * diff --git a/sys/netinet/tcp_timewait.c b/sys/netinet/tcp_timewait.c index 4257603571c0..14287b200364 100644 --- a/sys/netinet/tcp_timewait.c +++ b/sys/netinet/tcp_timewait.c @@ -329,6 +329,7 @@ tcp_init(void) tcp_rexmit_min = TCPTV_MIN; tcp_rexmit_slop = TCPTV_CPU_VAR; tcp_inflight_rttthresh = TCPTV_INFLIGHT_RTTTHRESH; + tcp_finwait2_timeout = TCPTV_FINWAIT2_TIMEOUT; INP_INFO_LOCK_INIT(&tcbinfo, "tcp"); LIST_INIT(&tcb); diff --git a/sys/netinet/tcp_usrreq.c b/sys/netinet/tcp_usrreq.c index c4362cfb0214..57195a436184 100644 --- a/sys/netinet/tcp_usrreq.c +++ b/sys/netinet/tcp_usrreq.c @@ -1581,9 +1581,14 @@ tcp_usrclosed(tp) if (tp && tp->t_state >= TCPS_FIN_WAIT_2) { soisdisconnected(tp->t_inpcb->inp_socket); /* To prevent the connection hanging in FIN_WAIT_2 forever. */ - if (tp->t_state == TCPS_FIN_WAIT_2) - callout_reset(tp->tt_2msl, tcp_maxidle, + if (tp->t_state == TCPS_FIN_WAIT_2) { + int timeout; + + timeout = (tcp_fast_finwait2_recycle) ? + tcp_finwait2_timeout : tcp_maxidle; + callout_reset(tp->tt_2msl, timeout, tcp_timer_2msl, tp); + } } } diff --git a/sys/netinet/tcp_var.h b/sys/netinet/tcp_var.h index 4c50a82f78b3..46d396d31e9a 100644 --- a/sys/netinet/tcp_var.h +++ b/sys/netinet/tcp_var.h @@ -413,6 +413,8 @@ struct tcpstat { u_long tcps_hc_added; /* entry added to hostcache */ u_long tcps_hc_bucketoverflow; /* hostcache per bucket limit hit */ + u_long tcps_finwait2_drops; /* Drop FIN_WAIT_2 connection after time limit */ + /* SACK related stats */ u_long tcps_sack_recovery_episode; /* SACK recovery episodes */ u_long tcps_sack_rexmits; /* SACK rexmit segments */ @@ -455,6 +457,7 @@ struct xtcpcb { #define TCPCTL_SACK 14 /* Selective Acknowledgement,rfc 2018 */ #define TCPCTL_DROP 15 /* drop tcp connection */ #define TCPCTL_MAXID 16 +#define TCPCTL_FINWAIT2_TIMEOUT 17 #define TCPCTL_NAMES { \ { 0, 0 }, \ diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c index 5f5f54babdd3..d69a0cb0fe76 100644 --- a/usr.bin/netstat/inet.c +++ b/usr.bin/netstat/inet.c @@ -436,6 +436,7 @@ tcp_stats(u_long off __unused, const char *name, int af1 __unused) p(tcps_timeoutdrop, "\t\t%lu connection%s dropped by rexmit timeout\n"); p(tcps_persisttimeo, "\t%lu persist timeout%s\n"); p(tcps_persistdrop, "\t\t%lu connection%s dropped by persist timeout\n"); + p(tcps_finwait2_drops, "\t%lu Connection%s (fin_wait_2) dropped because of timeout\n"); p(tcps_keeptimeo, "\t%lu keepalive timeout%s\n"); p(tcps_keepprobe, "\t\t%lu keepalive probe%s sent\n"); p(tcps_keepdrops, "\t\t%lu connection%s dropped by keepalive\n");