#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tcp_timer.c,v 1.99 2022/11/04 09:00:58 ozaki-r Exp $");
#ifdef _KERNEL_OPT
#include "opt_inet.h"
#include "opt_tcp_debug.h"
#include "opt_net_mpsafe.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/callout.h>
#include <sys/workqueue.h>
#include <sys/cprng.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#ifdef INET6
#include <netinet/ip6.h>
#include <netinet6/in6_pcb.h>
#endif
#include <netinet/tcp.h>
#include <netinet/tcp_fsm.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <netinet/tcp_private.h>
#include <netinet/tcp_congctl.h>
#ifdef TCP_DEBUG
#include <netinet/tcp_debug.h>
#endif
u_int tcp_keepinit = 0;
u_int tcp_keepidle = 0;
u_int tcp_keepintvl = 0;
u_int tcp_keepcnt = 0;
int tcp_maxpersistidle = 0;
static callout_t tcp_slowtimo_ch;
#ifdef NET_MPSAFE
static struct workqueue *tcp_slowtimo_wq;
static struct work tcp_slowtimo_wk;
#endif
static void tcp_slowtimo_work(struct work *, void *);
static void tcp_slowtimo(void *);
int tcp_delack_ticks = 0;
void tcp_timer_rexmt(void *);
void tcp_timer_persist(void *);
void tcp_timer_keep(void *);
void tcp_timer_2msl(void *);
const tcp_timer_func_t tcp_timer_funcs[TCPT_NTIMERS] = {
tcp_timer_rexmt,
tcp_timer_persist,
tcp_timer_keep,
tcp_timer_2msl,
};
void
tcp_timer_init(void)
{
if (tcp_keepinit == 0)
tcp_keepinit = TCPTV_KEEP_INIT;
if (tcp_keepidle == 0)
tcp_keepidle = TCPTV_KEEP_IDLE;
if (tcp_keepintvl == 0)
tcp_keepintvl = TCPTV_KEEPINTVL;
if (tcp_keepcnt == 0)
tcp_keepcnt = TCPTV_KEEPCNT;
if (tcp_maxpersistidle == 0)
tcp_maxpersistidle = TCPTV_KEEP_IDLE;
if (tcp_delack_ticks == 0)
tcp_delack_ticks = TCP_DELACK_TICKS;
}
void
tcp_slowtimo_init(void)
{
#ifdef NET_MPSAFE
int error;
error = workqueue_create(&tcp_slowtimo_wq, "tcp_slowtimo",
tcp_slowtimo_work, NULL, PRI_SOFTNET, IPL_SOFTNET, WQ_MPSAFE);
if (error != 0)
panic("%s: workqueue_create failed (%d)\n", __func__, error);
#endif
callout_init(&tcp_slowtimo_ch, CALLOUT_MPSAFE);
callout_reset(&tcp_slowtimo_ch, 1, tcp_slowtimo, NULL);
}
void
tcp_delack(void *arg)
{
struct tcpcb *tp = arg;
mutex_enter(softnet_lock);
if ((tp->t_flags & (TF_DEAD | TF_DELACK)) != TF_DELACK) {
mutex_exit(softnet_lock);
return;
}
if (!callout_expired(&tp->t_delack_ch)) {
mutex_exit(softnet_lock);
return;
}
tp->t_flags |= TF_ACKNOW;
KERNEL_LOCK(1, NULL);
(void) tcp_output(tp);
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
}
static void
tcp_slowtimo_work(struct work *wk, void *arg)
{
mutex_enter(softnet_lock);
tcp_iss_seq += TCP_ISSINCR + (TCP_ISS_RANDOM_MASK & cprng_fast32());
tcp_now++;
mutex_exit(softnet_lock);
callout_schedule(&tcp_slowtimo_ch, hz / PR_SLOWHZ);
}
static void
tcp_slowtimo(void *arg)
{
#ifdef NET_MPSAFE
workqueue_enqueue(tcp_slowtimo_wq, &tcp_slowtimo_wk, NULL);
#else
tcp_slowtimo_work(NULL, NULL);
#endif
}
void
tcp_canceltimers(struct tcpcb *tp)
{
int i;
for (i = 0; i < TCPT_NTIMERS; i++)
TCP_TIMER_DISARM(tp, i);
}
const int tcp_backoff[TCP_MAXRXTSHIFT + 1] =
{ 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 };
const int tcp_totbackoff = 511;
void
tcp_timer_rexmt(void *arg)
{
struct tcpcb *tp = arg;
uint32_t rto;
#ifdef TCP_DEBUG
struct socket *so = NULL;
short ostate;
#endif
mutex_enter(softnet_lock);
if ((tp->t_flags & TF_DEAD) != 0) {
mutex_exit(softnet_lock);
return;
}
if (!callout_expired(&tp->t_timer[TCPT_REXMT])) {
mutex_exit(softnet_lock);
return;
}
KERNEL_LOCK(1, NULL);
if ((tp->t_flags & TF_PMTUD_PEND) && tp->t_inpcb &&
SEQ_GEQ(tp->t_pmtud_th_seq, tp->snd_una) &&
SEQ_LT(tp->t_pmtud_th_seq, (int)(tp->snd_una + tp->t_ourmss))) {
extern struct sockaddr_in icmpsrc;
struct icmp icmp;
tp->t_flags &= ~TF_PMTUD_PEND;
icmp.icmp_nextmtu = tp->t_pmtud_nextmtu;
icmp.icmp_ip.ip_len = tp->t_pmtud_ip_len;
icmp.icmp_ip.ip_hl = tp->t_pmtud_ip_hl;
icmpsrc.sin_addr = in4p_faddr(tp->t_inpcb);
icmp_mtudisc(&icmp, icmpsrc.sin_addr);
inpcb_notifyall(&tcbtable, icmpsrc.sin_addr, EMSGSIZE,
tcp_mtudisc);
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
return;
}
#ifdef TCP_DEBUG
so = tp->t_inpcb->inp_socket;
ostate = tp->t_state;
#endif
tcp_free_sackholes(tp);
tp->snd_fack = tp->snd_una;
if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) {
tp->t_rxtshift = TCP_MAXRXTSHIFT;
TCP_STATINC(TCP_STAT_TIMEOUTDROP);
tp = tcp_drop(tp, tp->t_softerror ?
tp->t_softerror : ETIMEDOUT);
goto out;
}
TCP_STATINC(TCP_STAT_REXMTTIMEO);
rto = TCP_REXMTVAL(tp);
if (rto < tp->t_rttmin)
rto = tp->t_rttmin;
TCPT_RANGESET(tp->t_rxtcur, rto * tcp_backoff[tp->t_rxtshift],
tp->t_rttmin, TCPTV_REXMTMAX);
TCP_TIMER_ARM(tp, TCPT_REXMT, tp->t_rxtcur);
if (tp->t_mtudisc && tp->t_rxtshift > TCP_MAXRXTSHIFT / 6) {
TCP_STATINC(TCP_STAT_PMTUBLACKHOLE);
tp->t_mtudisc = 0;
}
if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) {
inpcb_losing(tp->t_inpcb);
tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT);
tp->t_srtt = 0;
}
tp->snd_nxt = tp->snd_una;
tp->snd_high = tp->snd_max;
tp->t_rtttime = 0;
if (tp->t_state == TCPS_SYN_SENT)
tp->t_flags |= TF_SYN_REXMT;
tp->t_congctl->slow_retransmit(tp);
(void) tcp_output(tp);
out:
#ifdef TCP_DEBUG
if (tp && so->so_options & SO_DEBUG)
tcp_trace(TA_USER, ostate, tp, NULL,
PRU_SLOWTIMO | (TCPT_REXMT << 8));
#endif
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
}
void
tcp_timer_persist(void *arg)
{
struct tcpcb *tp = arg;
uint32_t rto;
#ifdef TCP_DEBUG
struct socket *so = NULL;
short ostate;
#endif
mutex_enter(softnet_lock);
if ((tp->t_flags & TF_DEAD) != 0) {
mutex_exit(softnet_lock);
return;
}
if (!callout_expired(&tp->t_timer[TCPT_PERSIST])) {
mutex_exit(softnet_lock);
return;
}
KERNEL_LOCK(1, NULL);
#ifdef TCP_DEBUG
so = tp->t_inpcb->inp_socket;
ostate = tp->t_state;
#endif
rto = TCP_REXMTVAL(tp);
if (rto < tp->t_rttmin)
rto = tp->t_rttmin;
if (tp->t_rxtshift == TCP_MAXRXTSHIFT &&
((tcp_now - tp->t_rcvtime) >= tcp_maxpersistidle ||
(tcp_now - tp->t_rcvtime) >= rto * tcp_totbackoff)) {
TCP_STATINC(TCP_STAT_PERSISTDROPS);
tp = tcp_drop(tp, ETIMEDOUT);
goto out;
}
TCP_STATINC(TCP_STAT_PERSISTTIMEO);
tcp_setpersist(tp);
tp->t_force = 1;
(void) tcp_output(tp);
tp->t_force = 0;
out:
#ifdef TCP_DEBUG
if (tp && so->so_options & SO_DEBUG)
tcp_trace(TA_USER, ostate, tp, NULL,
PRU_SLOWTIMO | (TCPT_PERSIST << 8));
#endif
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
}
void
tcp_timer_keep(void *arg)
{
struct tcpcb *tp = arg;
struct socket *so = NULL;
#ifdef TCP_DEBUG
short ostate;
#endif
mutex_enter(softnet_lock);
if ((tp->t_flags & TF_DEAD) != 0) {
mutex_exit(softnet_lock);
return;
}
if (!callout_expired(&tp->t_timer[TCPT_KEEP])) {
mutex_exit(softnet_lock);
return;
}
KERNEL_LOCK(1, NULL);
#ifdef TCP_DEBUG
ostate = tp->t_state;
#endif
TCP_STATINC(TCP_STAT_KEEPTIMEO);
if (TCPS_HAVEESTABLISHED(tp->t_state) == 0)
goto dropit;
so = tp->t_inpcb->inp_socket;
KASSERT(so != NULL);
if (so->so_options & SO_KEEPALIVE &&
tp->t_state <= TCPS_CLOSE_WAIT) {
if ((tp->t_maxidle > 0) &&
((tcp_now - tp->t_rcvtime) >=
tp->t_keepidle + tp->t_maxidle))
goto dropit;
TCP_STATINC(TCP_STAT_KEEPPROBE);
(void)tcp_respond(tp, tp->t_template,
NULL, NULL, tp->rcv_nxt,
tp->snd_una - 1, 0);
TCP_TIMER_ARM(tp, TCPT_KEEP, tp->t_keepintvl);
} else
TCP_TIMER_ARM(tp, TCPT_KEEP, tp->t_keepidle);
#ifdef TCP_DEBUG
if (tp && so->so_options & SO_DEBUG)
tcp_trace(TA_USER, ostate, tp, NULL,
PRU_SLOWTIMO | (TCPT_KEEP << 8));
#endif
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
return;
dropit:
TCP_STATINC(TCP_STAT_KEEPDROPS);
(void) tcp_drop(tp, ETIMEDOUT);
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
}
void
tcp_timer_2msl(void *arg)
{
struct tcpcb *tp = arg;
#ifdef TCP_DEBUG
struct socket *so = NULL;
short ostate;
#endif
mutex_enter(softnet_lock);
if ((tp->t_flags & TF_DEAD) != 0) {
mutex_exit(softnet_lock);
return;
}
if (!callout_expired(&tp->t_timer[TCPT_2MSL])) {
mutex_exit(softnet_lock);
return;
}
KERNEL_LOCK(1, NULL);
tcp_free_sackholes(tp);
tp->snd_fack = tp->snd_una;
#ifdef TCP_DEBUG
so = tp->t_inpcb->inp_socket;
ostate = tp->t_state;
#endif
if (tp->t_state != TCPS_TIME_WAIT &&
((tp->t_maxidle == 0) ||
((tcp_now - tp->t_rcvtime) <= tp->t_maxidle)))
TCP_TIMER_ARM(tp, TCPT_2MSL, tp->t_keepintvl);
else
tp = tcp_close(tp);
#ifdef TCP_DEBUG
if (tp && so->so_options & SO_DEBUG)
tcp_trace(TA_USER, ostate, tp, NULL,
PRU_SLOWTIMO | (TCPT_2MSL << 8));
#endif
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
}