This patch does the following: 1. eliminates the fast/slow timeout lists for TCP and instead uses a callout entry for each timer. 2. Increases the TCP timer granularity to 10ms 3. Implements "bad retransmit" recovery, as presented in "On Estimating End-to-End Network Path Properties", by Allman and Paxson. This patch is the result of work done by Garrett Wollman and Jonathan Lemon. Notes: - This is patch v3 - there is still some debugging cruft in this. - User visible TCP timers are now in units of the system clock (10ms), not those of the slowtimeout (500ms). So if you have customized one of these values, (e.g.: net.inet.tcp.keepintvl), you'll need to multiply it by 50 to keep the same interval. - 4 new sysctls have been added, which are shown below, along with their default values: net.inet.tcp.delacktime: 20 Specifies the timeout for delayed acks (200ms). net.inet.tcp.slowstart_flightsize: 1 Specifies how many packets are included in the initial slow-start flight. Setting this value to 0 will probably result in a non-working system. Choosing a high value is would be considered antisocial. net.inet.tcp.local_slowstart_flightsize: 65535 Same as above, but only applies to slowstart on "local" networks. Note that the flightsize is constrained by the receiver's advertised window size. net.inet.tcp.msl: 3000 Specifies the "Maximum Segment Lifetime", 30sec. Index: kern/kern_timeout.c =================================================================== RCS file: /home/ncvs/src/sys/kern/kern_timeout.c,v retrieving revision 1.55.2.1 diff -u -r1.55.2.1 kern_timeout.c --- kern_timeout.c 1999/07/16 19:11:06 1.55.2.1 +++ kern_timeout.c 1999/08/06 04:13:56 @@ -125,8 +125,7 @@ c_links.sle); } else { c->c_flags = - (c->c_flags & ~CALLOUT_PENDING) - | CALLOUT_FIRED; + (c->c_flags & ~CALLOUT_PENDING); } splx(s); c_func(c_arg); @@ -218,11 +217,11 @@ * callout_init() - initialize a callout structure so that it can * safely be passed to callout_reset() and callout_stop() * - * defines two convenience macros: + * defines three convenience macros: * - * callout_pending() - returns number of ticks until callout fires, or 0 - * if not scheduled - * callout_fired() - returns truth if callout has already been fired + * callout_active() - returns truth if callout has not been serviced + * callout_pending() - returns truth if callout is still waiting for timeout + * callout_deactivate() - marks the callout as having been serviced */ void callout_reset(c, to_ticks, ftn, arg) @@ -240,13 +239,13 @@ /* * We could spl down here and back up at the TAILQ_INSERT_TAIL, * but there's no point since doing this setup doesn't take much - ^ time. + * time. */ if (to_ticks <= 0) to_ticks = 1; c->c_arg = arg; - c->c_flags = (c->c_flags & ~CALLOUT_FIRED) | CALLOUT_PENDING; + c->c_flags |= (CALLOUT_ACTIVE | CALLOUT_PENDING); c->c_func = ftn; c->c_time = ticks + to_ticks; TAILQ_INSERT_TAIL(&callwheel[c->c_time & callwheelmask], @@ -266,10 +265,11 @@ * Don't attempt to delete a callout that's not on the queue. */ if (!(c->c_flags & CALLOUT_PENDING)) { + c->c_flags &= ~CALLOUT_ACTIVE; splx(s); return; } - c->c_flags &= ~CALLOUT_PENDING; + c->c_flags &= ~(CALLOUT_ACTIVE | CALLOUT_PENDING); if (nextsoftcheck == c) { nextsoftcheck = TAILQ_NEXT(c, c_links.tqe); Index: sys/callout.h =================================================================== RCS file: /home/ncvs/src/sys/sys/callout.h,v retrieving revision 1.11.2.1 diff -u -r1.11.2.1 callout.h --- callout.h 1999/07/16 19:11:07 1.11.2.1 +++ callout.h 1999/08/04 04:43:55 @@ -59,8 +59,8 @@ }; #define CALLOUT_LOCAL_ALLOC 0x0001 /* was allocated from callfree */ -#define CALLOUT_PENDING 0x0002 /* callout is currently active */ -#define CALLOUT_FIRED 0x0004 /* callout has been fired */ +#define CALLOUT_ACTIVE 0x0002 /* callout is currently active */ +#define CALLOUT_PENDING 0x0004 /* callout is waiting for timeout */ struct callout_handle { struct callout *callout; @@ -73,10 +73,10 @@ extern struct callout_tailq *callwheel; extern int callwheelsize, callwheelbits, callwheelmask, softticks; -#define callout_fired(c) ((c)->c_flags & CALLOUT_FIRED) +#define callout_active(c) ((c)->c_flags & CALLOUT_ACTIVE) +#define callout_deactivate(c) ((c)->c_flags &= ~CALLOUT_ACTIVE) void callout_init __P((struct callout *)); -#define callout_pending(c) (((c)->c_flags & CALLOUT_PENDING) ? \ - ((c)->c_time - ticks) : 0) +#define callout_pending(c) ((c)->c_flags & CALLOUT_PENDING) void callout_reset __P((struct callout *, int, void (*)(void *), void *)); void callout_stop __P((struct callout *)); Index: netinet/in_proto.c =================================================================== RCS file: /home/ncvs/src/sys/netinet/in_proto.c,v retrieving revision 1.47 diff -u -r1.47 in_proto.c --- in_proto.c 1998/08/23 03:07:14 1.47 +++ in_proto.c 1999/07/24 16:56:20 @@ -91,7 +91,7 @@ PR_CONNREQUIRED|PR_IMPLOPCL|PR_WANTRCVD, tcp_input, 0, tcp_ctlinput, tcp_ctloutput, 0, - tcp_init, tcp_fasttimo, tcp_slowtimo, tcp_drain, + tcp_init, 0, tcp_slowtimo, tcp_drain, &tcp_usrreqs }, { SOCK_RAW, &inetdomain, IPPROTO_RAW, PR_ATOMIC|PR_ADDR, Index: netinet/tcp_input.c =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_input.c,v retrieving revision 1.82.2.1 diff -u -r1.82.2.1 tcp_input.c --- tcp_input.c 1999/04/20 19:09:15 1.82.2.1 +++ tcp_input.c 1999/08/08 16:44:46 @@ -88,7 +88,6 @@ SYSCTL_INT(_net_inet_tcp, OID_AUTO, delayed_ack, CTLFLAG_RW, &tcp_delack_enabled, 0, ""); -u_long tcp_now; struct inpcbhead tcb; struct inpcbinfo tcbinfo; @@ -115,7 +114,8 @@ (tp)->t_segq == NULL && \ (tp)->t_state == TCPS_ESTABLISHED) { \ if (tcp_delack_enabled) \ - tp->t_flags |= TF_DELACK; \ + callout_reset(tp->tt_delack, tcp_delacktime, \ + tcp_timer_delack, tp); \ else \ tp->t_flags |= TF_ACKNOW; \ (tp)->rcv_nxt += (ti)->ti_len; \ @@ -250,6 +250,8 @@ #undef GETTCP } +extern int d_rexmt_t; /* XXX */ + /* * TCP input routine, follows pages 65-76 of the * protocol specification dated September, 1981 very closely. @@ -491,9 +493,9 @@ * Segment received on connection. * Reset idle time and keep-alive timer. */ - tp->t_idle = 0; + tp->t_rcvtime = ticks; if (TCPS_HAVEESTABLISHED(tp->t_state)) - tp->t_timer[TCPT_KEEP] = tcp_keepidle; + callout_reset(tp->tt_keep, tcp_keepidle, tcp_timer_keep, tp); /* * Process options if not in LISTEN state, @@ -543,7 +545,7 @@ */ if ((to.to_flag & TOF_TS) != 0 && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { - tp->ts_recent_age = tcp_now; + tp->ts_recent_age = ticks; tp->ts_recent = to.to_tsval; } @@ -556,12 +558,25 @@ * this is a pure ack for outstanding data. */ ++tcpstat.tcps_predack; + /* + * "bad retransmit" recovery + */ + if (tp->t_rxtshift == 1 && + ticks < tp->t_badrxtwin) { + tp->snd_cwnd = tp->snd_cwnd_prev; + tp->snd_ssthresh = + tp->snd_ssthresh_prev; + tp->snd_nxt = tp->snd_max; + tp->t_badrxtwin = 0; +if (d_rexmt_t) +printf("recovery: %d %d\n", tp->snd_cwnd, tp->snd_ssthresh); + } if ((to.to_flag & TOF_TS) != 0) tcp_xmit_timer(tp, - tcp_now - to.to_tsecr + 1); - else if (tp->t_rtt && + ticks - to.to_tsecr + 1); + else if (tp->t_rtttime && SEQ_GT(ti->ti_ack, tp->t_rtseq)) - tcp_xmit_timer(tp, tp->t_rtt); + tcp_xmit_timer(tp, ticks - tp->t_rtttime); acked = ti->ti_ack - tp->snd_una; tcpstat.tcps_rcvackpack++; tcpstat.tcps_rcvackbyte += acked; @@ -579,9 +594,11 @@ * decide between more output or persist. */ if (tp->snd_una == tp->snd_max) - tp->t_timer[TCPT_REXMT] = 0; - else if (tp->t_timer[TCPT_PERSIST] == 0) - tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + callout_stop(tp->tt_rexmt); + else if (!callout_active(tp->tt_persist)) + callout_reset(tp->tt_rexmt, + tp->t_rxtcur, + tcp_timer_rexmt, tp); sowwakeup(so); if (so->so_snd.sb_cc) @@ -606,7 +623,8 @@ sbappend(&so->so_rcv, m); sorwakeup(so); if (tcp_delack_enabled) { - tp->t_flags |= TF_DELACK; + callout_reset(tp->tt_delack, tcp_delacktime, + tcp_timer_delack, tp); } else { tp->t_flags |= TF_ACKNOW; tcp_output(tp); @@ -728,6 +746,7 @@ taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { taop->tao_cc = to.to_cc; + tp->t_starttime = ticks; tp->t_state = TCPS_ESTABLISHED; /* @@ -738,9 +757,11 @@ * the other side is slow starting. */ if (tcp_delack_enabled && ((tiflags & TH_FIN) || (ti->ti_len != 0 && - in_localaddr(inp->inp_faddr)))) - tp->t_flags |= (TF_DELACK | TF_NEEDSYN); - else + in_localaddr(inp->inp_faddr)))) { + callout_reset(tp->tt_delack, tcp_delacktime, + tcp_timer_delack, tp); + tp->t_flags |= TF_NEEDSYN; + } else tp->t_flags |= (TF_ACKNOW | TF_NEEDSYN); /* @@ -751,7 +772,8 @@ tp->rcv_adv += min(tp->rcv_wnd, TCP_MAXWIN); tcpstat.tcps_connects++; soisconnected(so); - tp->t_timer[TCPT_KEEP] = tcp_keepinit; + callout_reset(tp->tt_keep, tcp_keepinit, + tcp_timer_keep, tp); dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; @@ -770,7 +792,7 @@ */ tp->t_flags |= TF_ACKNOW; tp->t_state = TCPS_SYN_RECEIVED; - tp->t_timer[TCPT_KEEP] = tcp_keepinit; + callout_reset(tp->tt_keep, tcp_keepinit, tcp_timer_keep, tp); dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; @@ -869,7 +891,8 @@ * ACKNOW will be turned on later. */ if (tcp_delack_enabled && ti->ti_len != 0) - tp->t_flags |= TF_DELACK; + callout_reset(tp->tt_delack, tcp_delacktime, + tcp_timer_delack, tp); else tp->t_flags |= TF_ACKNOW; /* @@ -878,13 +901,15 @@ * SYN_SENT --> ESTABLISHED * SYN_SENT* --> FIN_WAIT_1 */ + tp->t_starttime = ticks; if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; tiflags &= ~TH_SYN; } else { tp->t_state = TCPS_ESTABLISHED; - tp->t_timer[TCPT_KEEP] = tcp_keepidle; + callout_reset(tp->tt_keep, tcp_keepidle, + tcp_timer_keep, tp); } } else { /* @@ -897,7 +922,7 @@ * If there was no CC option, clear cached CC value. */ tp->t_flags |= TF_ACKNOW; - tp->t_timer[TCPT_REXMT] = 0; + callout_stop(tp->tt_rexmt); if (to.to_flag & TOF_CC) { if (taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { @@ -907,12 +932,16 @@ * SYN-SENT* -> FIN-WAIT-1* */ taop->tao_cc = to.to_cc; + tp->t_starttime = ticks; if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; - tp->t_timer[TCPT_KEEP] = tcp_keepidle; + callout_reset(tp->tt_keep, + tcp_keepidle, + tcp_timer_keep, + tp); } tp->t_flags |= TF_NEEDSYN; } else @@ -971,7 +1000,7 @@ if ((tiflags & TH_SYN) && (to.to_flag & TOF_CC) && tp->cc_recv != 0) { if (tp->t_state == TCPS_TIME_WAIT && - tp->t_duration > TCPTV_MSL) + (ticks - tp->t_starttime) > tcp_msl) goto dropwithreset; if (CC_GT(to.to_cc, tp->cc_recv)) { tp = tcp_close(tp); @@ -1080,7 +1109,7 @@ TSTMP_LT(to.to_tsval, tp->ts_recent)) { /* Check to see if ts_recent is over 24 days old. */ - if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) { + if ((int)(ticks - tp->ts_recent_age) > TCP_PAWS_IDLE) { /* * Invalidate ts_recent. If this segment updates * ts_recent, the age will be reset later and ts_recent @@ -1227,7 +1256,7 @@ */ if ((to.to_flag & TOF_TS) != 0 && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { - tp->ts_recent_age = tcp_now; + tp->ts_recent_age = ticks; tp->ts_recent = to.to_tsval; } @@ -1287,12 +1316,14 @@ * SYN-RECEIVED -> ESTABLISHED * SYN-RECEIVED* -> FIN-WAIT-1 */ + tp->t_starttime = ticks; if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; - tp->t_timer[TCPT_KEEP] = tcp_keepidle; + callout_reset(tp->tt_keep, tcp_keepidle, + tcp_timer_keep, tp); } /* * If segment contains data or ACK, will call tcp_reass() @@ -1347,7 +1378,7 @@ * to keep a constant cwnd packets in the * network. */ - if (tp->t_timer[TCPT_REXMT] == 0 || + if (!callout_active(tp->tt_rexmt) || ti->ti_ack != tp->snd_una) tp->t_dupacks = 0; else if (++tp->t_dupacks == tcprexmtthresh) { @@ -1359,8 +1390,8 @@ if (win < 2) win = 2; tp->snd_ssthresh = win * tp->t_maxseg; - tp->t_timer[TCPT_REXMT] = 0; - tp->t_rtt = 0; + callout_stop(tp->tt_rexmt); + tp->t_rtttime = 0; tp->snd_nxt = ti->ti_ack; tp->snd_cwnd = tp->t_maxseg; (void) tcp_output(tp); @@ -1418,6 +1449,22 @@ tcpstat.tcps_rcvackbyte += acked; /* + * If we just performed our first retransmit, and the ACK + * arrives within our recovery window, then it was a mistake + * to do the retransmit in the first place. Recover our + * original cwnd and ssthresh, and proceed to transmit where + * we left off. + */ + if (tp->t_rxtshift == 1 && ticks < tp->t_badrxtwin) { + tp->snd_cwnd = tp->snd_cwnd_prev; + tp->snd_ssthresh = tp->snd_ssthresh_prev; + tp->snd_nxt = tp->snd_max; + tp->t_badrxtwin = 0; /* XXX probably not required */ +if (d_rexmt_t) +printf("recovery: %d %d\n", tp->snd_cwnd, tp->snd_ssthresh); + } + + /* * If we have a timestamp reply, update smoothed * round trip time. If no timestamp is present but * transmit timer is running and timed sequence @@ -1427,9 +1474,9 @@ * Recompute the initial retransmit timer. */ if (to.to_flag & TOF_TS) - tcp_xmit_timer(tp, tcp_now - to.to_tsecr + 1); - else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) - tcp_xmit_timer(tp,tp->t_rtt); + tcp_xmit_timer(tp, ticks - to.to_tsecr + 1); + else if (tp->t_rtttime && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, ticks - tp->t_rtttime); /* * If all outstanding data is acked, stop retransmit @@ -1438,10 +1485,11 @@ * timer, using current (possibly backed-off) value. */ if (ti->ti_ack == tp->snd_max) { - tp->t_timer[TCPT_REXMT] = 0; + callout_stop(tp->tt_rexmt); needoutput = 1; - } else if (tp->t_timer[TCPT_PERSIST] == 0) - tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + } else if (!callout_active(tp->tt_persist)) + callout_reset(tp->tt_rexmt, tp->t_rxtcur, + tcp_timer_rexmt, tp); /* * If no data (only SYN) was ACK'd, @@ -1497,7 +1545,8 @@ */ if (so->so_state & SS_CANTRCVMORE) { soisdisconnected(so); - tp->t_timer[TCPT_2MSL] = tcp_maxidle; + callout_reset(tp->tt_2msl, tcp_maxidle, + tcp_timer_2msl, tp); } tp->t_state = TCPS_FIN_WAIT_2; } @@ -1515,11 +1564,14 @@ tcp_canceltimers(tp); /* Shorten TIME_WAIT [RFC-1644, p.28] */ if (tp->cc_recv != 0 && - tp->t_duration < TCPTV_MSL) - tp->t_timer[TCPT_2MSL] = - tp->t_rxtcur * TCPTV_TWTRUNC; + (ticks - tp->t_starttime) < tcp_msl) + callout_reset(tp->tt_2msl, + tp->t_rxtcur * + TCPTV_TWTRUNC, + tcp_timer_2msl, tp); else - tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + callout_reset(tp->tt_2msl, 2 * tcp_msl, + tcp_timer_2msl, tp); soisdisconnected(so); } break; @@ -1543,7 +1595,8 @@ * it and restart the finack timer. */ case TCPS_TIME_WAIT: - tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + callout_reset(tp->tt_2msl, 2 * tcp_msl, + tcp_timer_2msl, tp); goto dropafterack; } } @@ -1667,7 +1720,8 @@ * more input can be expected, send ACK now. */ if (tcp_delack_enabled && (tp->t_flags & TF_NEEDSYN)) - tp->t_flags |= TF_DELACK; + callout_reset(tp->tt_delack, tcp_delacktime, + tcp_timer_delack, tp); else tp->t_flags |= TF_ACKNOW; tp->rcv_nxt++; @@ -1679,6 +1733,7 @@ * enter the CLOSE_WAIT state. */ case TCPS_SYN_RECEIVED: + tp->t_starttime = ticks; case TCPS_ESTABLISHED: tp->t_state = TCPS_CLOSE_WAIT; break; @@ -1701,14 +1756,16 @@ tcp_canceltimers(tp); /* Shorten TIME_WAIT [RFC-1644, p.28] */ if (tp->cc_recv != 0 && - tp->t_duration < TCPTV_MSL) { - tp->t_timer[TCPT_2MSL] = - tp->t_rxtcur * TCPTV_TWTRUNC; + (ticks - tp->t_starttime) < tcp_msl) { + callout_reset(tp->tt_2msl, + tp->t_rxtcur * TCPTV_TWTRUNC, + tcp_timer_2msl, tp); /* For transaction client, force ACK now. */ tp->t_flags |= TF_ACKNOW; } else - tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + callout_reset(tp->tt_2msl, 2 * tcp_msl, + tcp_timer_2msl, tp); soisdisconnected(so); break; @@ -1716,7 +1773,8 @@ * In TIME_WAIT state restart the 2 MSL time_wait timer. */ case TCPS_TIME_WAIT: - tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + callout_reset(tp->tt_2msl, 2 * tcp_msl, + tcp_timer_2msl, tp); break; } } @@ -1865,7 +1923,7 @@ if (ti->ti_flags & TH_SYN) { tp->t_flags |= TF_RCVD_TSTMP; tp->ts_recent = to->to_tsval; - tp->ts_recent_age = tcp_now; + tp->ts_recent_age = ticks; } break; case TCPOPT_CC: @@ -1946,6 +2004,7 @@ panic("tcp_pulloutofband"); } +static int rtt_debug = 0; /* * Collect new round-trip time estimate * and update averages and current timeout. @@ -1953,10 +2012,15 @@ static void tcp_xmit_timer(tp, rtt) register struct tcpcb *tp; - short rtt; + int rtt; { register int delta; +/* top 5 bits should be 0, since we scale the value 8. (shift 5 bits) */ +if (rtt_debug) +if (rtt & 0xf8000000) + printf("rtt != 0 in upper 5 bits, losing precision\n"); + tcpstat.tcps_rttupdated++; tp->t_rttupdated++; if (tp->t_srtt != 0) { @@ -1997,7 +2061,7 @@ tp->t_srtt = rtt << TCP_RTT_SHIFT; tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); } - tp->t_rtt = 0; + tp->t_rtttime = 0; tp->t_rxtshift = 0; /* @@ -2014,6 +2078,10 @@ TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), max(tp->t_rttmin, rtt + 2), TCPTV_REXMTMAX); +if (rtt_debug) +printf("rtt: %d srtt: %d, var: %d, rexmt: %d, rxtcur: %d, rttmin: %d\n", + rtt, tp->t_srtt, tp->t_rttvar, TCP_REXMTVAL(tp), tp->t_rxtcur, tp->t_rttmin); + /* * We received an ack for a packet that wasn't retransmitted; * it is probably safe to discard any error indications we've @@ -2108,12 +2176,12 @@ * is also a minimum value; this is subject to time. */ if (rt->rt_rmx.rmx_locks & RTV_RTT) - tp->t_rttmin = rtt / (RTM_RTTUNIT / PR_SLOWHZ); - tp->t_srtt = rtt / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); + tp->t_rttmin = rtt / (RTM_RTTUNIT / hz); + tp->t_srtt = rtt / (RTM_RTTUNIT / (hz * TCP_RTT_SCALE)); tcpstat.tcps_usedrtt++; if (rt->rt_rmx.rmx_rttvar) { tp->t_rttvar = rt->rt_rmx.rmx_rttvar / - (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); + (RTM_RTTUNIT / (hz * TCP_RTTVAR_SCALE)); tcpstat.tcps_usedrttvar++; } else { /* default variation is +- 1 rtt */ @@ -2121,8 +2189,8 @@ tp->t_srtt * TCP_RTTVAR_SCALE / TCP_RTT_SCALE; } TCPT_RANGESET(tp->t_rxtcur, - ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, - tp->t_rttmin, TCPTV_REXMTMAX); + ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, + tp->t_rttmin, TCPTV_REXMTMAX); } /* * if there's an mtu associated with the route, use it @@ -2196,11 +2264,15 @@ bufsize = sb_max; (void)sbreserve(&so->so_rcv, bufsize); } + /* - * Don't force slow-start on local network. + * Set the slow-start flight size depending on whether this + * is a local network or not. */ - if (!in_localaddr(inp->inp_faddr)) - tp->snd_cwnd = mss; + if (in_localaddr(inp->inp_faddr)) + tp->snd_cwnd = mss * ss_fltsz_local; + else + tp->snd_cwnd = mss * ss_fltsz; if (rt->rt_rmx.rmx_ssthresh) { /* Index: netinet/tcp_output.c =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_output.c,v retrieving revision 1.32.2.1 diff -u -r1.32.2.1 tcp_output.c --- tcp_output.c 1999/04/07 22:25:52 1.32.2.1 +++ tcp_output.c 1999/08/04 04:42:38 @@ -40,6 +40,8 @@ #include #include +#include +#include #include #include #include @@ -67,6 +69,13 @@ extern struct mbuf *m_copypack(); #endif +int ss_fltsz = 1; +SYSCTL_INT(_net_inet_tcp, OID_AUTO, slowstart_flightsize, CTLFLAG_RW, + &ss_fltsz, 1, "Slow start flight size"); + +int ss_fltsz_local = TCP_MAXWIN; /* something large */ +SYSCTL_INT(_net_inet_tcp, OID_AUTO, local_slowstart_flightsize, CTLFLAG_RW, + &ss_fltsz_local, 1, "Slow start flight size for local networks"); /* * Tcp output routine: figure out what should be sent and send it. @@ -93,13 +102,20 @@ * to send, then transmit; otherwise, investigate further. */ idle = (tp->snd_max == tp->snd_una); - if (idle && tp->t_idle >= tp->t_rxtcur) + if (idle && (ticks - tp->t_rcvtime) >= tp->t_rxtcur) { /* * We have been idle for "a while" and no acks are * expected to clock out any data we send -- * slow start to get ack "clock" running again. - */ - tp->snd_cwnd = tp->t_maxseg; + * + * Set the slow-start flight size depending on whether + * this is a local network or not. + */ + if (in_localaddr(tp->t_inpcb->inp_faddr)) + tp->snd_cwnd = tp->t_maxseg * ss_fltsz_local; + else + tp->snd_cwnd = tp->t_maxseg * ss_fltsz; + } again: sendalot = 0; off = tp->snd_nxt - tp->snd_una; @@ -143,7 +159,7 @@ flags &= ~TH_FIN; win = 1; } else { - tp->t_timer[TCPT_PERSIST] = 0; + callout_stop(tp->tt_persist); tp->t_rxtshift = 0; } } @@ -194,10 +210,10 @@ */ len = 0; if (win == 0) { - tp->t_timer[TCPT_REXMT] = 0; + callout_stop(tp->tt_rexmt); tp->t_rxtshift = 0; tp->snd_nxt = tp->snd_una; - if (tp->t_timer[TCPT_PERSIST] == 0) + if (!callout_active(tp->tt_persist)) tcp_setpersist(tp); } } @@ -285,11 +301,11 @@ * persisting to move a small or zero window * (re)transmitting and thereby not persisting * - * tp->t_timer[TCPT_PERSIST] - * is set when we are in persist state. + * callout_active(tp->tt_persist) + * is true when we are in persist state. * tp->t_force * is set when we are called to send a persist packet. - * tp->t_timer[TCPT_REXMT] + * callout_active(tp->tt_rexmt) * is set when we are retransmitting * The output side is idle when both timers are zero. * @@ -299,8 +315,8 @@ * if window is nonzero, transmit what we can, * otherwise force out a byte. */ - if (so->so_snd.sb_cc && tp->t_timer[TCPT_REXMT] == 0 && - tp->t_timer[TCPT_PERSIST] == 0) { + if (so->so_snd.sb_cc && !callout_active(tp->tt_rexmt) && + !callout_active(tp->tt_persist)) { tp->t_rxtshift = 0; tcp_setpersist(tp); } @@ -358,7 +374,7 @@ /* Form timestamp option as shown in appendix A of RFC 1323. */ *lp++ = htonl(TCPOPT_TSTAMP_HDR); - *lp++ = htonl(tcp_now); + *lp++ = htonl(ticks); *lp = htonl(tp->ts_recent); optlen += TCPOLEN_TSTAMP_APPA; } @@ -563,7 +579,8 @@ * case, since we know we aren't doing a retransmission. * (retransmit and persist are mutually exclusive...) */ - if (len || (flags & (TH_SYN|TH_FIN)) || tp->t_timer[TCPT_PERSIST]) + if (len || (flags & (TH_SYN|TH_FIN)) + || callout_active(tp->tt_persist)) ti->ti_seq = htonl(tp->snd_nxt); else ti->ti_seq = htonl(tp->snd_max); @@ -609,7 +626,7 @@ * In transmit state, time the transmission and arrange for * the retransmit. In persist state, just set snd_max. */ - if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) { + if (tp->t_force == 0 || !callout_active(tp->tt_persist)) { tcp_seq startseq = tp->snd_nxt; /* @@ -630,8 +647,8 @@ * Time this transmission if not a retransmission and * not currently timing anything. */ - if (tp->t_rtt == 0) { - tp->t_rtt = 1; + if (tp->t_rtttime == 0) { + tp->t_rtttime = ticks; tp->t_rtseq = startseq; tcpstat.tcps_segstimed++; } @@ -645,11 +662,12 @@ * Initialize shift counter which is used for backoff * of retransmit time. */ - if (tp->t_timer[TCPT_REXMT] == 0 && + if (!callout_active(tp->tt_rexmt) && tp->snd_nxt != tp->snd_una) { - tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; - if (tp->t_timer[TCPT_PERSIST]) { - tp->t_timer[TCPT_PERSIST] = 0; + callout_reset(tp->tt_rexmt, tp->t_rxtcur, + tcp_timer_rexmt, tp); + if (callout_active(tp->tt_persist)) { + callout_stop(tp->tt_persist); tp->t_rxtshift = 0; } } @@ -732,7 +750,9 @@ if (win > 0 && SEQ_GT(tp->rcv_nxt+win, tp->rcv_adv)) tp->rcv_adv = tp->rcv_nxt + win; tp->last_ack_sent = tp->rcv_nxt; - tp->t_flags &= ~(TF_ACKNOW|TF_DELACK); + tp->t_flags &= ~TF_ACKNOW; + if (tcp_delack_enabled) + callout_stop(tp->tt_delack); if (sendalot) goto again; return (0); @@ -742,16 +762,17 @@ tcp_setpersist(tp) register struct tcpcb *tp; { - register int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1; + int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1; + int tt; - if (tp->t_timer[TCPT_REXMT]) - panic("tcp_output REXMT"); + if (callout_active(tp->tt_rexmt)) + panic("tcp_setpersist: retransmit pending"); /* * Start/restart persistance timer. */ - TCPT_RANGESET(tp->t_timer[TCPT_PERSIST], - t * tcp_backoff[tp->t_rxtshift], - TCPTV_PERSMIN, TCPTV_PERSMAX); + TCPT_RANGESET(tt, t * tcp_backoff[tp->t_rxtshift], + TCPTV_PERSMIN, TCPTV_PERSMAX); + callout_reset(tp->tt_persist, tt, tcp_timer_persist, tp); if (tp->t_rxtshift < TCP_MAXRXTSHIFT) tp->t_rxtshift++; } Index: netinet/tcp_seq.h =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_seq.h,v retrieving revision 1.8 diff -u -r1.8 tcp_seq.h --- tcp_seq.h 1997/02/22 09:41:41 1.8 +++ tcp_seq.h 1999/07/24 16:56:20 @@ -75,7 +75,7 @@ (tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = \ (tp)->iss -#define TCP_PAWS_IDLE (24 * 24 * 60 * 60 * PR_SLOWHZ) +#define TCP_PAWS_IDLE (24 * 24 * 60 * 60 * hz) /* timestamp wrap-around time */ #ifdef KERNEL Index: netinet/tcp_subr.c =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_subr.c,v retrieving revision 1.49.2.2 diff -u -r1.49.2.2 tcp_subr.c --- tcp_subr.c 1999/06/20 01:21:25 1.49.2.2 +++ tcp_subr.c 1999/08/08 16:44:02 @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -73,9 +74,11 @@ SYSCTL_INT(_net_inet_tcp, TCPCTL_MSSDFLT, mssdflt, CTLFLAG_RW, &tcp_mssdflt , 0, ""); +#if 0 static int tcp_rttdflt = TCPTV_SRTTDFLT / PR_SLOWHZ; SYSCTL_INT(_net_inet_tcp, TCPCTL_RTTDFLT, rttdflt, CTLFLAG_RW, &tcp_rttdflt , 0, ""); +#endif static int tcp_do_rfc1323 = 1; SYSCTL_INT(_net_inet_tcp, TCPCTL_DO_RFC1323, rfc1323, @@ -117,6 +120,8 @@ char align[(sizeof(struct inpcb) + ALIGNM1) & ~ALIGNM1]; } inp_tp_u; struct tcpcb tcb; + struct callout inp_tp_rexmt, inp_tp_persist, inp_tp_keep, inp_tp_2msl; + struct callout inp_tp_delack; }; #undef ALIGNMENT #undef ALIGNM1 @@ -132,6 +137,14 @@ tcp_iss = random(); /* wrong, but better than a constant */ tcp_ccgen = 1; tcp_cleartaocache(); + + tcp_delacktime = TCPTV_DELACK; + tcp_keepinit = TCPTV_KEEP_INIT; + tcp_keepidle = TCPTV_KEEP_IDLE; + tcp_keepintvl = TCPTV_KEEPINTVL; + tcp_maxpersistidle = TCPTV_KEEP_IDLE; + tcp_msl = TCPTV_MSL; + LIST_INIT(&tcb); tcbinfo.listhead = &tcb; if (!(getenv_int("net.inet.tcp.tcbhashsize", &hashsize))) @@ -145,6 +158,7 @@ &tcbinfo.porthashmask); tcbinfo.ipi_zone = zinit("tcpcb", sizeof(struct inp_tp), maxsockets, ZONE_INTERRUPT, 0); + if (max_protohdr < sizeof(struct tcpiphdr)) max_protohdr = sizeof(struct tcpiphdr); if (max_linkhdr + sizeof(struct tcpiphdr) > MHLEN) @@ -299,6 +313,13 @@ tp->t_segq = NULL; tp->t_maxseg = tp->t_maxopd = tcp_mssdflt; + /* Set up our timeouts. */ + callout_init(tp->tt_rexmt = &it->inp_tp_rexmt); + callout_init(tp->tt_persist = &it->inp_tp_persist); + callout_init(tp->tt_keep = &it->inp_tp_keep); + callout_init(tp->tt_2msl = &it->inp_tp_2msl); + callout_init(tp->tt_delack = &it->inp_tp_delack); + if (tcp_do_rfc1323) tp->t_flags = (TF_REQ_SCALE|TF_REQ_TSTMP); if (tcp_do_rfc1644) @@ -315,6 +336,7 @@ tp->t_rxtcur = TCPTV_RTOBASE; tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT; tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->t_rcvtime = ticks; inp->inp_ip_ttl = ip_defttl; inp->inp_ppcb = (caddr_t)tp; return (tp); /* XXX */ @@ -362,6 +384,16 @@ int dosavessthresh; /* + * Make sure that all of our timers are stopped before we + * delete the PCB. + */ + callout_stop(tp->tt_rexmt); + callout_stop(tp->tt_persist); + callout_stop(tp->tt_keep); + callout_stop(tp->tt_2msl); + callout_stop(tp->tt_delack); + + /* * If we got enough samples through the srtt filter, * save the rtt and rttvar in the routing entry. * 'Enough' is arbitrarily defined as the 16 samples. @@ -379,7 +411,7 @@ if ((rt->rt_rmx.rmx_locks & RTV_RTT) == 0) { i = tp->t_srtt * - (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); + (RTM_RTTUNIT / (hz * TCP_RTT_SCALE)); if (rt->rt_rmx.rmx_rtt && i) /* * filter this update to half the old & half @@ -395,7 +427,7 @@ } if ((rt->rt_rmx.rmx_locks & RTV_RTTVAR) == 0) { i = tp->t_rttvar * - (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); + (RTM_RTTUNIT / (hz * TCP_RTTVAR_SCALE)); if (rt->rt_rmx.rmx_rttvar && i) rt->rt_rmx.rmx_rttvar = (rt->rt_rmx.rmx_rttvar + i) / 2; @@ -698,7 +730,7 @@ tp->t_maxseg = mss; tcpstat.tcps_mturesent++; - tp->t_rtt = 0; + tp->t_rtttime = 0; tp->snd_nxt = tp->snd_una; tcp_output(tp); } Index: netinet/tcp_timer.c =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_timer.c,v retrieving revision 1.28 diff -u -r1.28 tcp_timer.c --- tcp_timer.c 1998/04/24 09:25:35 1.28 +++ tcp_timer.c 1999/08/08 17:02:06 @@ -63,116 +63,54 @@ #include #endif -int tcp_keepinit = TCPTV_KEEP_INIT; +int tcp_keepinit; SYSCTL_INT(_net_inet_tcp, TCPCTL_KEEPINIT, keepinit, CTLFLAG_RW, &tcp_keepinit , 0, ""); -int tcp_keepidle = TCPTV_KEEP_IDLE; +int tcp_keepidle; SYSCTL_INT(_net_inet_tcp, TCPCTL_KEEPIDLE, keepidle, CTLFLAG_RW, &tcp_keepidle , 0, ""); -static int tcp_keepintvl = TCPTV_KEEPINTVL; +int tcp_keepintvl; SYSCTL_INT(_net_inet_tcp, TCPCTL_KEEPINTVL, keepintvl, CTLFLAG_RW, &tcp_keepintvl , 0, ""); +int tcp_delacktime; +SYSCTL_INT(_net_inet_tcp, TCPCTL_DELACKTIME, delacktime, CTLFLAG_RW, + &tcp_delacktime, 0, "Time before a delayed ACK is sent"); + +int tcp_msl; +SYSCTL_INT(_net_inet_tcp, OID_AUTO, msl, CTLFLAG_RW, + &tcp_msl, 0, "Maximum segment lifetime"); + static int always_keepalive = 0; SYSCTL_INT(_net_inet_tcp, OID_AUTO, always_keepalive, CTLFLAG_RW, &always_keepalive , 0, ""); static int tcp_keepcnt = TCPTV_KEEPCNT; /* max idle probes */ -static int tcp_maxpersistidle = TCPTV_KEEP_IDLE; +int tcp_maxpersistidle; /* max idle time in persist */ int tcp_maxidle; /* - * Fast timeout routine for processing delayed acks - */ -void -tcp_fasttimo() -{ - register struct inpcb *inp; - register struct tcpcb *tp; - int s; - - if (tcp_delack_enabled) { - s = splnet(); - for (inp = tcb.lh_first; inp != NULL; inp = inp->inp_list.le_next) { - if ((tp = (struct tcpcb *)inp->inp_ppcb) && - (tp->t_flags & TF_DELACK)) { - tp->t_flags &= ~TF_DELACK; - tp->t_flags |= TF_ACKNOW; - tcpstat.tcps_delack++; - (void) tcp_output(tp); - } - } - splx(s); - } -} - -/* * Tcp protocol timeout routine called every 500 ms. - * Updates the timers in all active tcb's and - * causes finite state machine actions if timers expire. + * Updates timestatmps used for TCP. */ void tcp_slowtimo() { - register struct inpcb *ip, *ipnxt; - register struct tcpcb *tp; - register int i; int s; -#ifdef TCPDEBUG - int ostate; -#endif s = splnet(); tcp_maxidle = tcp_keepcnt * tcp_keepintvl; - ip = tcb.lh_first; - if (ip == NULL) { - splx(s); - return; - } - /* - * Search through tcb's and update active timers. - */ - for (; ip != NULL; ip = ipnxt) { - ipnxt = ip->inp_list.le_next; - tp = intotcpcb(ip); - if (tp == 0 || tp->t_state == TCPS_LISTEN) - continue; - for (i = 0; i < TCPT_NTIMERS; i++) { - if (tp->t_timer[i] && --tp->t_timer[i] == 0) { -#ifdef TCPDEBUG - ostate = tp->t_state; -#endif - tp = tcp_timers(tp, i); - if (tp == NULL) - goto tpgone; -#ifdef TCPDEBUG - if (tp->t_inpcb->inp_socket->so_options - & SO_DEBUG) - tcp_trace(TA_USER, ostate, tp, - (struct tcpiphdr *)0, - PRU_SLOWTIMO); -#endif - } - } - tp->t_idle++; - tp->t_duration++; - if (tp->t_rtt) - tp->t_rtt++; -tpgone: - ; - } tcp_iss += TCP_ISSINCR/PR_SLOWHZ; /* increment iss */ #ifdef TCP_COMPAT_42 if ((int)tcp_iss < 0) tcp_iss = TCP_ISSINCR; /* XXX */ #endif - tcp_now++; /* for timestamps */ splx(s); } @@ -183,10 +121,10 @@ tcp_canceltimers(tp) struct tcpcb *tp; { - register int i; - - for (i = 0; i < TCPT_NTIMERS; i++) - tp->t_timer[i] = 0; + callout_stop(tp->tt_2msl); + callout_stop(tp->tt_persist); + callout_stop(tp->tt_keep); + callout_stop(tp->tt_rexmt); } int tcp_backoff[TCP_MAXRXTSHIFT + 1] = @@ -197,175 +135,324 @@ /* * TCP timer processing. */ -struct tcpcb * -tcp_timers(tp, timer) - register struct tcpcb *tp; - int timer; + +static int d_delack_t = 0; +static int d_persist_t = 0; +static int d_2msl_t = 0; +static int d_keep_t = 0; +int d_rexmt_t = 0; /* XXX used in tcp_input too */ + +#include +void +tcp_timer_delack(xtp) + void *xtp; +{ + struct tcpcb *tp = xtp; + int s; + +if (d_delack_t) +printf("tcp_delack\n"); + + s = splnet(); + if (callout_pending(tp->tt_delack)) { + splx(s); + return; + } + callout_deactivate(tp->tt_delack); + + tp->t_flags |= TF_ACKNOW; + tcpstat.tcps_delack++; + (void) tcp_output(tp); + splx(s); +} + +void +tcp_timer_2msl(xtp) + void *xtp; { - register int rexmt; + struct tcpcb *tp = xtp; + int s; +#ifdef TCPDEBUG + int ostate; - switch (timer) { + ostate = tp->t_state; +#endif +if (d_2msl_t) +printf("tcp_2msl\n"); + s = splnet(); + if (callout_pending(tp->tt_2msl)) { + splx(s); + return; + } + callout_deactivate(tp->tt_2msl); /* * 2 MSL timeout in shutdown went off. If we're closed but * 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. */ - case TCPT_2MSL: - if (tp->t_state != TCPS_TIME_WAIT && - tp->t_idle <= tcp_maxidle) - tp->t_timer[TCPT_2MSL] = tcp_keepintvl; - else - tp = tcp_close(tp); - break; + 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 && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) + tcp_trace(TA_USER, ostate, tp, (struct tcpiphdr *)0, + PRU_SLOWTIMO); +#endif + splx(s); +} + +void +tcp_timer_keep(xtp) + void *xtp; +{ + struct tcpcb *tp = xtp; + int s; +#ifdef TCPDEBUG + int ostate; + + ostate = tp->t_state; +#endif +if (d_keep_t) +printf("tcp_keep\n"); + s = splnet(); + if (callout_pending(tp->tt_keep)) { + splx(s); + return; + } + callout_deactivate(tp->tt_keep); /* - * Retransmission timer went off. Message has not - * been acked within retransmit interval. Back off - * to a longer retransmit interval and retransmit one segment. + * Keep-alive timer went off; send something + * or drop connection if idle for too long. */ - case TCPT_REXMT: - if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { - tp->t_rxtshift = TCP_MAXRXTSHIFT; - tcpstat.tcps_timeoutdrop++; - tp = tcp_drop(tp, tp->t_softerror ? - tp->t_softerror : ETIMEDOUT); - break; - } - tcpstat.tcps_rexmttimeo++; - rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; - TCPT_RANGESET(tp->t_rxtcur, rexmt, - tp->t_rttmin, TCPTV_REXMTMAX); - tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; - /* - * If losing, let the lower level know and try for - * a better route. Also, if we backed off this far, - * our srtt estimate is probably bogus. Clobber it - * so we'll take the next rtt measurement as our srtt; - * move the current srtt into rttvar to keep the current - * retransmit times until then. - */ - if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { - in_losing(tp->t_inpcb); - tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); - tp->t_srtt = 0; - } - tp->snd_nxt = tp->snd_una; - /* - * Force a segment to be sent. - */ - tp->t_flags |= TF_ACKNOW; + tcpstat.tcps_keeptimeo++; + if (tp->t_state < TCPS_ESTABLISHED) + goto dropit; + if ((always_keepalive || + tp->t_inpcb->inp_socket->so_options & SO_KEEPALIVE) && + tp->t_state <= TCPS_CLOSING) { + if ((ticks - tp->t_rcvtime) >= tcp_keepidle + tcp_maxidle) + goto dropit; /* - * If timing a segment in this window, stop the timer. + * Send a packet designed to force a response + * if the peer is up and reachable: + * either an ACK if the connection is still alive, + * or an RST if the peer has closed the connection + * due to timeout or reboot. + * Using sequence number tp->snd_una-1 + * causes the transmitted zero-length segment + * to lie outside the receive window; + * by the protocol spec, this requires the + * correspondent TCP to respond. */ - tp->t_rtt = 0; + tcpstat.tcps_keepprobe++; +#ifdef TCP_COMPAT_42 /* - * Close the congestion window down to one segment - * (we'll open it by one segment for each ack we get). - * Since we probably have a window's worth of unacked - * data accumulated, this "slow start" keeps us from - * dumping all that data as back-to-back packets (which - * might overwhelm an intermediate gateway). - * - * There are two phases to the opening: Initially we - * open by one mss on each ack. This makes the window - * size increase exponentially with time. If the - * window is larger than the path can handle, this - * exponential growth results in dropped packet(s) - * almost immediately. To get more time between - * drops but still "push" the network to take advantage - * of improving conditions, we switch from exponential - * to linear window opening at some threshhold size. - * For a threshhold, we use half the current window - * size, truncated to a multiple of the mss. - * - * (the minimum cwnd that will give us exponential - * growth is 2 mss. We don't allow the threshhold - * to go below this.) + * The keepalive packet must have nonzero length + * to get a 4.2 host to respond. */ - { - u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; - if (win < 2) - win = 2; - tp->snd_cwnd = tp->t_maxseg; - tp->snd_ssthresh = win * tp->t_maxseg; - tp->t_dupacks = 0; - } - (void) tcp_output(tp); - break; + tcp_respond(tp, tp->t_template, (struct mbuf *)NULL, + tp->rcv_nxt - 1, tp->snd_una - 1, 0); +#else + tcp_respond(tp, tp->t_template, (struct mbuf *)NULL, + tp->rcv_nxt, tp->snd_una - 1, 0); +#endif + callout_reset(tp->tt_keep, tcp_keepintvl, tcp_timer_keep, tp); + } else + callout_reset(tp->tt_keep, tcp_keepidle, tcp_timer_keep, tp); + +#ifdef TCPDEBUG + if (tp->t_inpcb->inp_socket->so_options & SO_DEBUG) + tcp_trace(TA_USER, ostate, tp, (struct tcpiphdr *)0, + PRU_SLOWTIMO); +#endif + splx(s); + return; + +dropit: + tcpstat.tcps_keepdrops++; + tp = tcp_drop(tp, ETIMEDOUT); + +#ifdef TCPDEBUG + if (tp && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) + tcp_trace(TA_USER, ostate, tp, (struct tcpiphdr *)0, + PRU_SLOWTIMO); +#endif + splx(s); +} + +void +tcp_timer_persist(xtp) + void *xtp; +{ + struct tcpcb *tp = xtp; + int s; +#ifdef TCPDEBUG + int ostate; + + ostate = tp->t_state; +#endif +if (d_persist_t) +printf("tcp_persist\n"); + s = splnet(); + if (callout_pending(tp->tt_persist)) { + splx(s); + return; + } + callout_deactivate(tp->tt_persist); /* * Persistance timer into zero window. * Force a byte to be output, if possible. + */ + tcpstat.tcps_persisttimeo++; + /* + * Hack: if the peer is dead/unreachable, we do not + * time out if the window is closed. After a full + * backoff, drop the connection if the idle time + * (no responses to probes) reaches the maximum + * backoff that we would use if retransmitting. + */ + if (tp->t_rxtshift == TCP_MAXRXTSHIFT && + ((ticks - tp->t_rcvtime) >= tcp_maxpersistidle || + (ticks - tp->t_rcvtime) >= TCP_REXMTVAL(tp) * tcp_totbackoff)) { + tcpstat.tcps_persistdrop++; + tp = tcp_drop(tp, ETIMEDOUT); + goto out; + } + tcp_setpersist(tp); + tp->t_force = 1; + (void) tcp_output(tp); + tp->t_force = 0; + +out: +#ifdef TCPDEBUG + if (tp->t_inpcb->inp_socket->so_options & SO_DEBUG) + tcp_trace(TA_USER, ostate, tp, (struct tcpiphdr *)0, + PRU_SLOWTIMO); +#endif + splx(s); +} + +void +tcp_timer_rexmt(xtp) + void *xtp; +{ + struct tcpcb *tp = xtp; + int s; + int rexmt; +#ifdef TCPDEBUG + int ostate; + + ostate = tp->t_state; +#endif + +if (d_rexmt_t) +printf("retransmit #%d, srtt: %d, var: %d, rexmt: %d\n", + tp->t_rxtshift + 1, tp->t_srtt, tp->t_rttvar, TCP_REXMTVAL(tp)); + + s = splnet(); + if (callout_pending(tp->tt_rexmt)) { + splx(s); + return; + } + callout_deactivate(tp->tt_rexmt); + /* + * Retransmission timer went off. Message has not + * been acked within retransmit interval. Back off + * to a longer retransmit interval and retransmit one segment. */ - case TCPT_PERSIST: - tcpstat.tcps_persisttimeo++; + if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { + tp->t_rxtshift = TCP_MAXRXTSHIFT; + tcpstat.tcps_timeoutdrop++; + tp = tcp_drop(tp, tp->t_softerror ? + tp->t_softerror : ETIMEDOUT); + goto out; + } + if (tp->t_rxtshift == 1) { /* - * Hack: if the peer is dead/unreachable, we do not - * time out if the window is closed. After a full - * backoff, drop the connection if the idle time - * (no responses to probes) reaches the maximum - * backoff that we would use if retransmitting. + * first retransmit; record ssthresh and cwnd so they can + * be recovered if this turns out to be a "bad" retransmit. + * A retransmit is considered "bad" if an ACK for this + * segment is received within RTT/2 interval; the assumption + * here is that the ACK was already in flight. See + * "On Estimating End-to-End Network Path Properties" by + * Allman and Paxson for more details. */ - if (tp->t_rxtshift == TCP_MAXRXTSHIFT && - (tp->t_idle >= tcp_maxpersistidle || - tp->t_idle >= TCP_REXMTVAL(tp) * tcp_totbackoff)) { - tcpstat.tcps_persistdrop++; - tp = tcp_drop(tp, ETIMEDOUT); - break; - } - tcp_setpersist(tp); - tp->t_force = 1; - (void) tcp_output(tp); - tp->t_force = 0; - break; - + tp->snd_cwnd_prev = tp->snd_cwnd; + tp->snd_ssthresh_prev = tp->snd_ssthresh; + tp->t_badrxtwin = ticks + (tp->t_srtt >> (TCP_RTT_SHIFT + 1)); + } + tcpstat.tcps_rexmttimeo++; + rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; + TCPT_RANGESET(tp->t_rxtcur, rexmt, + tp->t_rttmin, TCPTV_REXMTMAX); /* - * Keep-alive timer went off; send something - * or drop connection if idle for too long. + * If losing, let the lower level know and try for + * a better route. Also, if we backed off this far, + * our srtt estimate is probably bogus. Clobber it + * so we'll take the next rtt measurement as our srtt; + * move the current srtt into rttvar to keep the current + * retransmit times until then. */ - case TCPT_KEEP: - tcpstat.tcps_keeptimeo++; - if (tp->t_state < TCPS_ESTABLISHED) - goto dropit; - if ((always_keepalive || - tp->t_inpcb->inp_socket->so_options & SO_KEEPALIVE) && - tp->t_state <= TCPS_CLOSING) { - if (tp->t_idle >= tcp_keepidle + tcp_maxidle) - goto dropit; - /* - * Send a packet designed to force a response - * if the peer is up and reachable: - * either an ACK if the connection is still alive, - * or an RST if the peer has closed the connection - * due to timeout or reboot. - * Using sequence number tp->snd_una-1 - * causes the transmitted zero-length segment - * to lie outside the receive window; - * by the protocol spec, this requires the - * correspondent TCP to respond. - */ - tcpstat.tcps_keepprobe++; -#ifdef TCP_COMPAT_42 - /* - * The keepalive packet must have nonzero length - * to get a 4.2 host to respond. - */ - tcp_respond(tp, tp->t_template, (struct mbuf *)NULL, - tp->rcv_nxt - 1, tp->snd_una - 1, 0); -#else - tcp_respond(tp, tp->t_template, (struct mbuf *)NULL, - tp->rcv_nxt, tp->snd_una - 1, 0); -#endif - tp->t_timer[TCPT_KEEP] = tcp_keepintvl; - } else - tp->t_timer[TCPT_KEEP] = tcp_keepidle; - break; - dropit: - tcpstat.tcps_keepdrops++; - tp = tcp_drop(tp, ETIMEDOUT); - break; + if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { + in_losing(tp->t_inpcb); + tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); + tp->t_srtt = 0; + } + tp->snd_nxt = tp->snd_una; + /* + * Force a segment to be sent. + */ + tp->t_flags |= TF_ACKNOW; + /* + * If timing a segment in this window, stop the timer. + */ + tp->t_rtttime = 0; + /* + * Close the congestion window down to one segment + * (we'll open it by one segment for each ack we get). + * Since we probably have a window's worth of unacked + * data accumulated, this "slow start" keeps us from + * dumping all that data as back-to-back packets (which + * might overwhelm an intermediate gateway). + * + * There are two phases to the opening: Initially we + * open by one mss on each ack. This makes the window + * size increase exponentially with time. If the + * window is larger than the path can handle, this + * exponential growth results in dropped packet(s) + * almost immediately. To get more time between + * drops but still "push" the network to take advantage + * of improving conditions, we switch from exponential + * to linear window opening at some threshhold size. + * For a threshhold, we use half the current window + * size, truncated to a multiple of the mss. + * + * (the minimum cwnd that will give us exponential + * growth is 2 mss. We don't allow the threshhold + * to go below this.) + */ + { + u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + if (win < 2) + win = 2; + tp->snd_cwnd = tp->t_maxseg; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_dupacks = 0; } - return (tp); + (void) tcp_output(tp); + +out: +#ifdef TCPDEBUG + if (tp && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) + tcp_trace(TA_USER, ostate, tp, (struct tcpiphdr *)0, + PRU_SLOWTIMO); +#endif + splx(s); } Index: netinet/tcp_timer.h =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_timer.h,v retrieving revision 1.13 diff -u -r1.13 tcp_timer.h --- tcp_timer.h 1997/09/07 05:26:48 1.13 +++ tcp_timer.h 1999/08/08 17:01:36 @@ -38,17 +38,6 @@ #define _NETINET_TCP_TIMER_H_ /* - * Definitions of the TCP timers. These timers are counted - * down PR_SLOWHZ times a second. - */ -#define TCPT_NTIMERS 4 - -#define TCPT_REXMT 0 /* retransmit */ -#define TCPT_PERSIST 1 /* retransmit persistence */ -#define TCPT_KEEP 2 /* keep alive */ -#define TCPT_2MSL 3 /* 2*msl quiet time timer */ - -/* * The TCPT_REXMT timer is used to force retransmissions. * The TCP has the TCPT_REXMT timer set whenever segments * have been sent for which ACKs are expected but not yet @@ -87,22 +76,22 @@ /* * Time constants. */ -#define TCPTV_MSL ( 30*PR_SLOWHZ) /* max seg lifetime (hah!) */ +#define TCPTV_MSL ( 30*hz) /* max seg lifetime (hah!) */ #define TCPTV_SRTTBASE 0 /* base roundtrip time; if 0, no idea yet */ -#define TCPTV_RTOBASE ( 3*PR_SLOWHZ) /* assumed RTO if no info */ -#define TCPTV_SRTTDFLT ( 3*PR_SLOWHZ) /* assumed RTT if no info */ +#define TCPTV_RTOBASE ( 3*hz) /* assumed RTO if no info */ +#define TCPTV_SRTTDFLT ( 3*hz) /* assumed RTT if no info */ -#define TCPTV_PERSMIN ( 5*PR_SLOWHZ) /* retransmit persistence */ -#define TCPTV_PERSMAX ( 60*PR_SLOWHZ) /* maximum persist interval */ +#define TCPTV_PERSMIN ( 5*hz) /* retransmit persistence */ +#define TCPTV_PERSMAX ( 60*hz) /* maximum persist interval */ -#define TCPTV_KEEP_INIT ( 75*PR_SLOWHZ) /* initial connect keep alive */ -#define TCPTV_KEEP_IDLE (120*60*PR_SLOWHZ) /* dflt time before probing */ -#define TCPTV_KEEPINTVL ( 75*PR_SLOWHZ) /* default probe interval */ +#define TCPTV_KEEP_INIT ( 75*hz) /* initial connect keepalive */ +#define TCPTV_KEEP_IDLE (120*60*hz) /* dflt time before probing */ +#define TCPTV_KEEPINTVL ( 75*hz) /* default probe interval */ #define TCPTV_KEEPCNT 8 /* max probes before drop */ -#define TCPTV_MIN ( 1*PR_SLOWHZ) /* minimum allowable value */ -#define TCPTV_REXMTMAX ( 64*PR_SLOWHZ) /* max allowable REXMT value */ +#define TCPTV_MIN ( 1*hz) /* minimum allowable value */ +#define TCPTV_REXMTMAX ( 64*hz) /* max allowable REXMT value */ #define TCPTV_TWTRUNC 8 /* RTO factor to truncate TW */ @@ -110,6 +99,8 @@ #define TCP_MAXRXTSHIFT 12 /* maximum retransmits */ +#define TCPTV_DELACK (hz / PR_FASTHZ) /* 200mS timeout */ + #ifdef TCPTIMERS static char *tcptimers[] = { "REXMT", "PERSIST", "KEEP", "2MSL" }; @@ -118,20 +109,39 @@ /* * Force a time value to be in a certain range. */ -#define TCPT_RANGESET(tv, value, tvmin, tvmax) { \ +#define TCPT_RANGESET(tv, value, tvmin, tvmax) do { \ (tv) = (value); \ if ((u_long)(tv) < (u_long)(tvmin)) \ (tv) = (tvmin); \ else if ((u_long)(tv) > (u_long)(tvmax)) \ (tv) = (tvmax); \ -} +} while(0) + +/* + * Convert slow-timeout ticks to timer ticks. We don't really want to do + * this as it is rather expensive, so this is only a transitional stage + * until we are able to update all the code which counts timer ticks. + */ +#define TCPT_TICKS(stt) ((stt) * hz / PR_SLOWHZ) +#define TCPT_SLOWHZ(tt) (((tt) * PR_SLOWHZ) / hz) #ifdef KERNEL extern int tcp_keepinit; /* time to establish connection */ extern int tcp_keepidle; /* time before keepalive probes begin */ +extern int tcp_keepintvl; /* time between keepalive probes */ extern int tcp_maxidle; /* time to drop after starting probes */ +extern int tcp_delacktime; /* time before sending a delayed ACK */ +extern int tcp_maxpersistidle; +extern int tcp_msl; extern int tcp_ttl; /* time to live for TCP segs */ extern int tcp_backoff[]; -#endif -#endif +void tcp_timer_2msl __P((void *xtp)); +void tcp_timer_keep __P((void *xtp)); +void tcp_timer_persist __P((void *xtp)); +void tcp_timer_rexmt __P((void *xtp)); +void tcp_timer_delack __P((void *xtp)); + +#endif /* KERNEL */ + +#endif /* !_NETINET_TCP_TIMER_H_ */ Index: netinet/tcp_usrreq.c =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_usrreq.c,v retrieving revision 1.40.2.2 diff -u -r1.40.2.2 tcp_usrreq.c --- tcp_usrreq.c 1999/06/05 01:25:12 1.40.2.2 +++ tcp_usrreq.c 1999/08/08 16:50:47 @@ -535,7 +535,7 @@ if (oinp) { if (oinp != inp && (otp = intotcpcb(oinp)) != NULL && otp->t_state == TCPS_TIME_WAIT && - otp->t_duration < TCPTV_MSL && + (ticks - otp->t_starttime) < tcp_msl && (otp->t_flags & TF_RCVD_CC)) otp = tcp_close(otp); else @@ -561,7 +561,7 @@ soisconnecting(so); tcpstat.tcps_connattempt++; tp->t_state = TCPS_SYN_SENT; - tp->t_timer[TCPT_KEEP] = tcp_keepinit; + callout_reset(tp->tt_keep, tcp_keepinit, tcp_timer_keep, tp); tp->iss = tcp_iss; tcp_iss += TCP_ISSINCR/2; tcp_sendseqinit(tp); @@ -808,7 +808,8 @@ soisdisconnected(tp->t_inpcb->inp_socket); /* To prevent the connection hanging in FIN_WAIT_2 forever. */ if (tp->t_state == TCPS_FIN_WAIT_2) - tp->t_timer[TCPT_2MSL] = tcp_maxidle; + callout_reset(tp->tt_2msl, tcp_maxidle, + tcp_timer_2msl, tp); } return (tp); } Index: netinet/tcp_var.h =================================================================== RCS file: /home/ncvs/src/sys/netinet/tcp_var.h,v retrieving revision 1.49 diff -u -r1.49 tcp_var.h --- tcp_var.h 1999/01/20 17:32:00 1.49 +++ tcp_var.h 1999/07/24 16:56:21 @@ -49,7 +49,11 @@ int t_dupacks; /* consecutive dup acks recd */ struct tcpiphdr *t_template; /* skeletal packet for transmit */ - int t_timer[TCPT_NTIMERS]; /* tcp timers */ + struct callout *tt_rexmt; /* retransmit timer */ + struct callout *tt_persist; /* retransmit persistence */ + struct callout *tt_keep; /* keepalive */ + struct callout *tt_2msl; /* 2*msl TIME_WAIT timer */ + struct callout *tt_delack; /* delayed ACK timer */ struct inpcb *t_inpcb; /* back pointer to internet pcb */ int t_state; /* state of this connection */ @@ -98,12 +102,12 @@ */ u_int t_maxopd; /* mss plus options */ - u_int t_idle; /* inactivity time */ - u_long t_duration; /* connection duration */ - int t_rtt; /* round trip time */ + u_long t_rcvtime; /* inactivity time */ + u_long t_starttime; /* time connection was established */ + int t_rtttime; /* round trip time */ tcp_seq t_rtseq; /* sequence number being timed */ - int t_rxtcur; /* current retransmit value */ + int t_rxtcur; /* current retransmit value (ticks) */ u_int t_maxseg; /* maximum segment size */ int t_srtt; /* smoothed round-trip time */ int t_rttvar; /* variance in round-trip time */ @@ -131,6 +135,10 @@ /* RFC 1644 variables */ tcp_cc cc_send; /* send connection count */ tcp_cc cc_recv; /* receive connection count */ +/* experimental */ + u_long snd_cwnd_prev; /* cwnd prior to retransmit */ + u_long snd_ssthresh_prev; /* ssthresh prior to retransmit */ + u_long t_badrxtwin; /* window for retransmit recovery */ }; /* @@ -305,7 +313,8 @@ #define TCPCTL_RECVSPACE 9 /* receive buffer space */ #define TCPCTL_KEEPINIT 10 /* receive buffer space */ #define TCPCTL_PCBLIST 11 /* list of all outstanding PCBs */ -#define TCPCTL_MAXID 12 +#define TCPCTL_DELACKTIME 12 /* time before sending delayed ACK */ +#define TCPCTL_MAXID 13 #define TCPCTL_NAMES { \ { 0, 0 }, \ @@ -320,6 +329,7 @@ { "recvspace", CTLTYPE_INT }, \ { "keepinit", CTLTYPE_INT }, \ { "pcblist", CTLTYPE_STRUCT }, \ + { "delacktime", CTLTYPE_INT }, \ } #ifdef KERNEL @@ -327,8 +337,9 @@ extern struct inpcbinfo tcbinfo; extern struct tcpstat tcpstat; /* tcp statistics */ extern int tcp_mssdflt; /* XXX */ -extern u_long tcp_now; /* for RFC 1323 timestamps */ extern int tcp_delack_enabled; +extern int ss_fltsz; +extern int ss_fltsz_local; void tcp_canceltimers __P((struct tcpcb *)); struct tcpcb *