Changeset View
Changeset View
Standalone View
Standalone View
sys/netinet/tcp_input.c
Show First 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | |||||
#include "opt_ipsec.h" | #include "opt_ipsec.h" | ||||
#include "opt_tcpdebug.h" | #include "opt_tcpdebug.h" | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#ifdef TCP_HHOOK | #ifdef TCP_HHOOK | ||||
#include <sys/hhook.h> | #include <sys/hhook.h> | ||||
#endif | #endif | ||||
#include <sys/limits.h> | |||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/mbuf.h> | #include <sys/mbuf.h> | ||||
#include <sys/proc.h> /* for proc0 declaration */ | #include <sys/proc.h> /* for proc0 declaration */ | ||||
#include <sys/protosw.h> | #include <sys/protosw.h> | ||||
#include <sys/sdt.h> | #include <sys/sdt.h> | ||||
#include <sys/signalvar.h> | #include <sys/signalvar.h> | ||||
#include <sys/socket.h> | #include <sys/socket.h> | ||||
#include <sys/socketvar.h> | #include <sys/socketvar.h> | ||||
▲ Show 20 Lines • Show All 263 Lines • ▼ Show 20 Lines | cc_conn_init(struct tcpcb *tp) | ||||
u_int maxseg; | u_int maxseg; | ||||
int rtt; | int rtt; | ||||
INP_WLOCK_ASSERT(tp->t_inpcb); | INP_WLOCK_ASSERT(tp->t_inpcb); | ||||
tcp_hc_get(&inp->inp_inc, &metrics); | tcp_hc_get(&inp->inp_inc, &metrics); | ||||
maxseg = tcp_maxseg(tp); | maxseg = tcp_maxseg(tp); | ||||
if (tp->t_srtt == 0 && (rtt = metrics.rmx_rtt)) { | if (tp->t_srtt == 0 && (rtt = metrics.rmx_rtt * SBT_1US)) { | ||||
jeff: It might be nice to change the unit of this variable so we're not multiplying it out everywhere. | |||||
tp->t_srtt = rtt; | tp->t_srtt = rtt; | ||||
tp->t_rttbest = tp->t_srtt + TCP_RTT_SCALE; | tp->t_rttbest = tp->t_srtt; | ||||
TCPSTAT_INC(tcps_usedrtt); | TCPSTAT_INC(tcps_usedrtt); | ||||
if (metrics.rmx_rttvar) { | if (metrics.rmx_rttvar) { | ||||
tp->t_rttvar = metrics.rmx_rttvar; | tp->t_rttvar = metrics.rmx_rttvar * SBT_1US; | ||||
TCPSTAT_INC(tcps_usedrttvar); | TCPSTAT_INC(tcps_usedrttvar); | ||||
} else { | } else { | ||||
/* default variation is +- 1 rtt */ | /* default variation is +- 1 rtt */ | ||||
tp->t_rttvar = | tp->t_rttvar = (tp->t_srtt >> 1); | ||||
tp->t_srtt * TCP_RTTVAR_SCALE / TCP_RTT_SCALE; | |||||
} | } | ||||
TCPT_RANGESET(tp->t_rxtcur, | TCPT_RANGESET(tp->t_rxtcur, | ||||
((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, | tp->t_srtt + 4*tp->t_rttvar, | ||||
tp->t_rttmin, TCPTV_REXMTMAX); | tp->t_rttmin, TCPTV_REXMTMAX*tick_sbt); | ||||
} | } | ||||
if (metrics.rmx_ssthresh) { | if (metrics.rmx_ssthresh) { | ||||
/* | /* | ||||
* There's some sort of gateway or interface | * There's some sort of gateway or interface | ||||
* buffer limit on the path. Use this to set | * buffer limit on the path. Use this to set | ||||
* the slow start threshold, but set the | * the slow start threshold, but set the | ||||
* threshold to no less than 2*mss. | * threshold to no less than 2*mss. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 107 Lines • ▼ Show 20 Lines | |||||
/* | /* | ||||
* Indicate whether this ack should be delayed. We can delay the ack if | * Indicate whether this ack should be delayed. We can delay the ack if | ||||
* following conditions are met: | * following conditions are met: | ||||
* - There is no delayed ack timer in progress. | * - There is no delayed ack timer in progress. | ||||
* - Our last ack wasn't a 0-sized window. We never want to delay | * - Our last ack wasn't a 0-sized window. We never want to delay | ||||
* the ack that opens up a 0-sized window. | * the ack that opens up a 0-sized window. | ||||
* - LRO wasn't used for this segment. We make sure by checking that the | * - LRO wasn't used for this segment. We make sure by checking that the | ||||
* segment size is not larger than the MSS. | * segment size is not larger than the MSS. | ||||
* - the calculated delay is greater than 2ms | |||||
*/ | */ | ||||
#define DELAY_ACK(tp, tlen) \ | #define DELAY_ACK(tp, tlen) \ | ||||
((!tcp_timer_active(tp, TT_DELACK) && \ | (((!tcp_timer_active(tp, TT_DELACK) && \ | ||||
(tp->t_flags & TF_RXWIN0SENT) == 0) && \ | (tp->t_flags & TF_RXWIN0SENT) == 0) && \ | ||||
(tlen <= tp->t_maxseg) && \ | (tlen <= tp->t_maxseg) && \ | ||||
(V_tcp_delack_enabled || (tp->t_flags & TF_NEEDSYN))) | (V_tcp_delack_enabled || (tp->t_flags & TF_NEEDSYN))) && \ | ||||
tp->t_delack > 2*SBT_1MS) | |||||
static void inline | static void inline | ||||
cc_ecnpkt_handler(struct tcpcb *tp, struct tcphdr *th, uint8_t iptos) | cc_ecnpkt_handler(struct tcpcb *tp, struct tcphdr *th, uint8_t iptos) | ||||
{ | { | ||||
INP_WLOCK_ASSERT(tp->t_inpcb); | INP_WLOCK_ASSERT(tp->t_inpcb); | ||||
if (CC_ALGO(tp)->ecnpkt_handler != NULL) { | if (CC_ALGO(tp)->ecnpkt_handler != NULL) { | ||||
switch (iptos & IPTOS_ECN_MASK) { | switch (iptos & IPTOS_ECN_MASK) { | ||||
Show All 16 Lines | if (CC_ALGO(tp)->ecnpkt_handler != NULL) { | ||||
if (tp->t_flags & TF_DELACK) | if (tp->t_flags & TF_DELACK) | ||||
tp->ccv->flags |= CCF_DELACK; | tp->ccv->flags |= CCF_DELACK; | ||||
else | else | ||||
tp->ccv->flags &= ~CCF_DELACK; | tp->ccv->flags &= ~CCF_DELACK; | ||||
CC_ALGO(tp)->ecnpkt_handler(tp->ccv); | CC_ALGO(tp)->ecnpkt_handler(tp->ccv); | ||||
if (tp->ccv->flags & CCF_ACKNOW) | if (tp->ccv->flags & CCF_ACKNOW) | ||||
tcp_timer_activate(tp, TT_DELACK, tcp_delacktime); | tcp_timer_activate(tp, TT_DELACK, tp->t_delack); | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* TCP input handling is split into multiple parts: | * TCP input handling is split into multiple parts: | ||||
* tcp6_input is a thin wrapper around tcp_input for the extended | * tcp6_input is a thin wrapper around tcp_input for the extended | ||||
* ip6_protox[] call format in ip6_input | * ip6_protox[] call format in ip6_input | ||||
* tcp_input handles primary segment validation, inpcb lookup and | * tcp_input handles primary segment validation, inpcb lookup and | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | tcp_input(struct mbuf **mp, int *offp, int proto) | ||||
int optlen = 0; | int optlen = 0; | ||||
#ifdef INET | #ifdef INET | ||||
int len; | int len; | ||||
#endif | #endif | ||||
int tlen = 0, off; | int tlen = 0, off; | ||||
int drop_hdrlen; | int drop_hdrlen; | ||||
int thflags; | int thflags; | ||||
int rstreason = 0; /* For badport_bandlim accounting purposes */ | int rstreason = 0; /* For badport_bandlim accounting purposes */ | ||||
sbintime_t t; | |||||
uint8_t iptos; | uint8_t iptos; | ||||
struct m_tag *fwd_tag = NULL; | struct m_tag *fwd_tag = NULL; | ||||
#ifdef INET6 | #ifdef INET6 | ||||
struct ip6_hdr *ip6 = NULL; | struct ip6_hdr *ip6 = NULL; | ||||
int isipv6; | int isipv6; | ||||
#else | #else | ||||
const void *ip6 = NULL; | const void *ip6 = NULL; | ||||
#endif /* INET6 */ | #endif /* INET6 */ | ||||
Show All 9 Lines | #ifdef TCPDEBUG | ||||
struct tcphdr tcp_savetcp; | struct tcphdr tcp_savetcp; | ||||
short ostate = 0; | short ostate = 0; | ||||
#endif | #endif | ||||
#ifdef INET6 | #ifdef INET6 | ||||
isipv6 = (mtod(m, struct ip *)->ip_v == 6) ? 1 : 0; | isipv6 = (mtod(m, struct ip *)->ip_v == 6) ? 1 : 0; | ||||
#endif | #endif | ||||
t = tcp_ts_getsbintime(); | |||||
off0 = *offp; | off0 = *offp; | ||||
m = *mp; | m = *mp; | ||||
*mp = NULL; | *mp = NULL; | ||||
to.to_flags = 0; | to.to_flags = 0; | ||||
TCPSTAT_INC(tcps_rcvtotal); | TCPSTAT_INC(tcps_rcvtotal); | ||||
#ifdef INET6 | #ifdef INET6 | ||||
if (isipv6) { | if (isipv6) { | ||||
▲ Show 20 Lines • Show All 888 Lines • ▼ Show 20 Lines | |||||
* the buffer to better manage the socket buffer resources. | * the buffer to better manage the socket buffer resources. | ||||
*/ | */ | ||||
int | int | ||||
tcp_autorcvbuf(struct mbuf *m, struct tcphdr *th, struct socket *so, | tcp_autorcvbuf(struct mbuf *m, struct tcphdr *th, struct socket *so, | ||||
struct tcpcb *tp, int tlen) | struct tcpcb *tp, int tlen) | ||||
{ | { | ||||
int newsize = 0; | int newsize = 0; | ||||
if (V_tcp_do_autorcvbuf && (so->so_rcv.sb_flags & SB_AUTOSIZE) && | if ((V_tcp_do_autorcvbuf & !!(so->so_rcv.sb_flags & SB_AUTOSIZE) & | ||||
tp->t_srtt != 0 && tp->rfbuf_ts != 0 && | !!tp->t_srtt & !!tp->rfbuf_ts) && | ||||
TCP_TS_TO_TICKS(tcp_ts_getticks() - tp->rfbuf_ts) > | tcp_ts_getsbintime() - TCP_TS_TO_SBT(tp->rfbuf_ts) > | ||||
(tp->t_srtt >> TCP_RTT_SHIFT)) { | tp->t_srtt) { | ||||
if (tp->rfbuf_cnt > (so->so_rcv.sb_hiwat / 8 * 7) && | if (tp->rfbuf_cnt > ((so->so_rcv.sb_hiwat / 8) * 7) && | ||||
so->so_rcv.sb_hiwat < V_tcp_autorcvbuf_max) { | so->so_rcv.sb_hiwat < V_tcp_autorcvbuf_max) { | ||||
newsize = min(so->so_rcv.sb_hiwat + | newsize = min(so->so_rcv.sb_hiwat + | ||||
V_tcp_autorcvbuf_inc, V_tcp_autorcvbuf_max); | V_tcp_autorcvbuf_inc, V_tcp_autorcvbuf_max); | ||||
} | } | ||||
TCP_PROBE6(receive__autoresize, NULL, tp, m, tp, th, newsize); | TCP_PROBE6(receive__autoresize, NULL, tp, m, tp, th, newsize); | ||||
/* Start over with next RTT. */ | /* Start over with next RTT. */ | ||||
tp->rfbuf_ts = 0; | tp->rfbuf_ts = 0; | ||||
Show All 14 Lines | tcp_do_segment(struct mbuf *m, struct tcphdr *th, struct socket *so, | ||||
int rstreason, todrop, win; | int rstreason, todrop, win; | ||||
uint32_t tiwin; | uint32_t tiwin; | ||||
uint16_t nsegs; | uint16_t nsegs; | ||||
char *s; | char *s; | ||||
struct in_conninfo *inc; | struct in_conninfo *inc; | ||||
struct mbuf *mfree; | struct mbuf *mfree; | ||||
struct tcpopt to; | struct tcpopt to; | ||||
int tfo_syn; | int tfo_syn; | ||||
sbintime_t t; | |||||
#ifdef TCPDEBUG | #ifdef TCPDEBUG | ||||
/* | /* | ||||
* The size of tcp_saveipgen must be the size of the max ip header, | * The size of tcp_saveipgen must be the size of the max ip header, | ||||
* now IPv6. | * now IPv6. | ||||
*/ | */ | ||||
u_char tcp_saveipgen[IP6_HDR_LEN]; | u_char tcp_saveipgen[IP6_HDR_LEN]; | ||||
struct tcphdr tcp_savetcp; | struct tcphdr tcp_savetcp; | ||||
short ostate = 0; | short ostate = 0; | ||||
#endif | #endif | ||||
t = tcp_ts_getsbintime(); | |||||
thflags = th->th_flags; | thflags = th->th_flags; | ||||
inc = &tp->t_inpcb->inp_inc; | inc = &tp->t_inpcb->inp_inc; | ||||
tp->sackhint.last_sack_ack = 0; | tp->sackhint.last_sack_ack = 0; | ||||
sack_changed = 0; | sack_changed = 0; | ||||
nsegs = max(1, m->m_pkthdr.lro_nsegs); | nsegs = max(1, m->m_pkthdr.lro_nsegs); | ||||
/* | /* | ||||
* If this is either a state-changing packet or current state isn't | * If this is either a state-changing packet or current state isn't | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | #endif | ||||
} | } | ||||
/* | /* | ||||
* Segment received on connection. | * Segment received on connection. | ||||
* Reset idle time and keep-alive timer. | * Reset idle time and keep-alive timer. | ||||
* XXX: This should be done after segment | * XXX: This should be done after segment | ||||
* validation to ignore broken/spoofed segs. | * validation to ignore broken/spoofed segs. | ||||
*/ | */ | ||||
tp->t_rcvtime = ticks; | tp->t_rcvtime = t; | ||||
/* | /* | ||||
* Scale up the window into a 32-bit value. | * Scale up the window into a 32-bit value. | ||||
* For the SYN_SENT state the scale is zero. | * For the SYN_SENT state the scale is zero. | ||||
*/ | */ | ||||
tiwin = th->th_win << tp->snd_scale; | tiwin = th->th_win << tp->snd_scale; | ||||
/* | /* | ||||
Show All 40 Lines | |||||
#endif | #endif | ||||
/* | /* | ||||
* If echoed timestamp is later than the current time, | * If echoed timestamp is later than the current time, | ||||
* fall back to non RFC1323 RTT calculation. Normalize | * fall back to non RFC1323 RTT calculation. Normalize | ||||
* timestamp if syncookies were used when this connection | * timestamp if syncookies were used when this connection | ||||
* was established. | * was established. | ||||
*/ | */ | ||||
if ((to.to_flags & TOF_TS) && (to.to_tsecr != 0)) { | if ((to.to_flags & TOF_TS) && (to.to_tsecr != 0)) { | ||||
to.to_tsecr -= tp->ts_offset; | if (to.to_tsecr == tp->t_lasttsecr + MAX_TS_STEP) { | ||||
if (TSTMP_GT(to.to_tsecr, tcp_ts_getticks())) | tp->t_lasttsecr = to.to_tsecr; | ||||
to.to_tsecr = tp->t_lasttsval; | |||||
} else if (TSTMP_GT(to.to_tsecr, TCP_SBT_TO_TS(t))) | |||||
to.to_tsecr = 0; | to.to_tsecr = 0; | ||||
else | |||||
tp->t_lasttsecr = to.to_tsecr; | |||||
} | } | ||||
/* | /* | ||||
* Process options only when we get SYN/ACK back. The SYN case | * Process options only when we get SYN/ACK back. The SYN case | ||||
* for incoming connections is handled in tcp_syncache. | * for incoming connections is handled in tcp_syncache. | ||||
* According to RFC1323 the window field in a SYN (i.e., a <SYN> | * According to RFC1323 the window field in a SYN (i.e., a <SYN> | ||||
* or <SYN,ACK>) segment itself is never scaled. | * or <SYN,ACK>) segment itself is never scaled. | ||||
* XXX this is traditional behavior, may need to be cleaned up. | * XXX this is traditional behavior, may need to be cleaned up. | ||||
*/ | */ | ||||
if (tp->t_state == TCPS_SYN_SENT && (thflags & TH_SYN)) { | if (tp->t_state == TCPS_SYN_SENT && (thflags & TH_SYN)) { | ||||
if ((to.to_flags & TOF_SCALE) && | if ((to.to_flags & TOF_SCALE) && | ||||
(tp->t_flags & TF_REQ_SCALE)) { | (tp->t_flags & TF_REQ_SCALE)) { | ||||
tp->t_flags |= TF_RCVD_SCALE; | tp->t_flags |= TF_RCVD_SCALE; | ||||
tp->snd_scale = to.to_wscale; | tp->snd_scale = to.to_wscale; | ||||
} | } | ||||
/* | /* | ||||
* Initial send window. It will be updated with | * Initial send window. It will be updated with | ||||
* the next incoming segment to the scaled value. | * the next incoming segment to the scaled value. | ||||
*/ | */ | ||||
tp->snd_wnd = th->th_win; | tp->snd_wnd = th->th_win; | ||||
if (to.to_flags & TOF_TS) { | if (to.to_flags & TOF_TS) { | ||||
tp->t_flags |= TF_RCVD_TSTMP; | tp->t_flags |= TF_RCVD_TSTMP; | ||||
tp->ts_recent = to.to_tsval; | tp->ts_recent = to.to_tsval; | ||||
tp->ts_recent_age = tcp_ts_getticks(); | tp->ts_recent_age = t; | ||||
} | } | ||||
if (to.to_flags & TOF_MSS) | if (to.to_flags & TOF_MSS) | ||||
tcp_mss(tp, to.to_mss); | tcp_mss(tp, to.to_mss); | ||||
if ((tp->t_flags & TF_SACK_PERMIT) && | if ((tp->t_flags & TF_SACK_PERMIT) && | ||||
(to.to_flags & TOF_SACKPERM) == 0) | (to.to_flags & TOF_SACKPERM) == 0) | ||||
tp->t_flags &= ~TF_SACK_PERMIT; | tp->t_flags &= ~TF_SACK_PERMIT; | ||||
if (IS_FASTOPEN(tp->t_flags)) { | if (IS_FASTOPEN(tp->t_flags)) { | ||||
if (to.to_flags & TOF_FASTOPEN) | if (to.to_flags & TOF_FASTOPEN) | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | if (tp->t_state == TCPS_ESTABLISHED && | ||||
/* | /* | ||||
* If last ACK falls within this segment's sequence numbers, | * If last ACK falls within this segment's sequence numbers, | ||||
* record the timestamp. | * record the timestamp. | ||||
* NOTE that the test is modified according to the latest | * NOTE that the test is modified according to the latest | ||||
* proposal of the tcplw@cray.com list (Braden 1993/04/26). | * proposal of the tcplw@cray.com list (Braden 1993/04/26). | ||||
*/ | */ | ||||
if ((to.to_flags & TOF_TS) != 0 && | if ((to.to_flags & TOF_TS) != 0 && | ||||
SEQ_LEQ(th->th_seq, tp->last_ack_sent)) { | SEQ_LEQ(th->th_seq, tp->last_ack_sent)) { | ||||
tp->ts_recent_age = tcp_ts_getticks(); | tp->ts_recent_age = t; | ||||
tp->ts_recent = to.to_tsval; | tp->ts_recent = to.to_tsval; | ||||
} | } | ||||
if (tlen == 0) { | if (tlen == 0) { | ||||
if (SEQ_GT(th->th_ack, tp->snd_una) && | if (SEQ_GT(th->th_ack, tp->snd_una) && | ||||
SEQ_LEQ(th->th_ack, tp->snd_max) && | SEQ_LEQ(th->th_ack, tp->snd_max) && | ||||
!IN_RECOVERY(tp->t_flags) && | !IN_RECOVERY(tp->t_flags) && | ||||
(to.to_flags & TOF_SACK) == 0 && | (to.to_flags & TOF_SACK) == 0 && | ||||
TAILQ_EMPTY(&tp->snd_holes)) { | TAILQ_EMPTY(&tp->snd_holes)) { | ||||
/* | /* | ||||
* This is a pure ack for outstanding data. | * This is a pure ack for outstanding data. | ||||
*/ | */ | ||||
if (ti_locked == TI_RLOCKED) | if (ti_locked == TI_RLOCKED) | ||||
INP_INFO_RUNLOCK(&V_tcbinfo); | INP_INFO_RUNLOCK(&V_tcbinfo); | ||||
ti_locked = TI_UNLOCKED; | ti_locked = TI_UNLOCKED; | ||||
TCPSTAT_INC(tcps_predack); | TCPSTAT_INC(tcps_predack); | ||||
/* | /* | ||||
* "bad retransmit" recovery. | * "bad retransmit" recovery. | ||||
*/ | */ | ||||
if (tp->t_rxtshift == 1 && | if (tp->t_rxtshift == 1 && | ||||
tp->t_flags & TF_PREVVALID && | tp->t_flags & TF_PREVVALID && | ||||
(int)(ticks - tp->t_badrxtwin) < 0) { | (t - tp->t_badrxtwin) < 0) { | ||||
cc_cong_signal(tp, th, CC_RTO_ERR); | cc_cong_signal(tp, th, CC_RTO_ERR); | ||||
} | } | ||||
/* | /* | ||||
* Recalculate the transmit timer / rtt. | * Recalculate the transmit timer / rtt. | ||||
* | * | ||||
* Some boxes send broken timestamp replies | * Some boxes send broken timestamp replies | ||||
* during the SYN+ACK phase, ignore | * during the SYN+ACK phase, ignore | ||||
* timestamps of 0 or we could calculate a | * timestamps of 0 or we could calculate a | ||||
* huge RTT and blow up the retransmit timer. | * huge RTT and blow up the retransmit timer. | ||||
*/ | */ | ||||
if ((to.to_flags & TOF_TS) != 0 && | if ((to.to_flags & TOF_TS) != 0 && | ||||
to.to_tsecr) { | to.to_tsecr) { | ||||
uint32_t t; | u_int curts; | ||||
sbintime_t rtt; | |||||
t = tcp_ts_getticks() - to.to_tsecr; | curts = (uint32_t)TCP_SBT_TO_TS(t); | ||||
if (!tp->t_rttlow || tp->t_rttlow > t) | /* | ||||
tp->t_rttlow = t; | * cope with frequent wrap | ||||
tcp_xmit_timer(tp, | */ | ||||
TCP_TS_TO_TICKS(t) + 1); | if (__predict_true(curts > to.to_tsecr)) | ||||
rtt = curts - to.to_tsecr; | |||||
else | |||||
rtt = UINT_MAX - to.to_tsecr + curts; | |||||
rtt = TCP_TS_TO_SBT(rtt); | |||||
if (!tp->t_rttlow || tp->t_rttlow > rtt) | |||||
tp->t_rttlow = rtt; | |||||
tcp_xmit_timer(tp, rtt + SBT_MINTS); | |||||
} else if (tp->t_rtttime && | } else if (tp->t_rtttime && | ||||
SEQ_GT(th->th_ack, tp->t_rtseq)) { | SEQ_GT(th->th_ack, tp->t_rtseq)) { | ||||
if (!tp->t_rttlow || | if (!tp->t_rttlow || | ||||
tp->t_rttlow > ticks - tp->t_rtttime) | tp->t_rttlow > t - tp->t_rtttime) | ||||
tp->t_rttlow = ticks - tp->t_rtttime; | tp->t_rttlow = t - tp->t_rtttime; | ||||
tcp_xmit_timer(tp, | tcp_xmit_timer(tp, t - tp->t_rtttime); | ||||
ticks - tp->t_rtttime); | |||||
} | } | ||||
acked = BYTES_THIS_ACK(tp, th); | acked = BYTES_THIS_ACK(tp, th); | ||||
#ifdef TCP_HHOOK | #ifdef TCP_HHOOK | ||||
/* Run HHOOK_TCP_ESTABLISHED_IN helper hooks. */ | /* Run HHOOK_TCP_ESTABLISHED_IN helper hooks. */ | ||||
hhook_run_tcp_est_in(tp, th, &to); | hhook_run_tcp_est_in(tp, th, &to); | ||||
#endif | #endif | ||||
▲ Show 20 Lines • Show All 214 Lines • ▼ Show 20 Lines | #endif | ||||
tfo_partial_ack = 1; | tfo_partial_ack = 1; | ||||
} | } | ||||
/* | /* | ||||
* If there's data, delay ACK; if there's also a FIN | * If there's data, delay ACK; if there's also a FIN | ||||
* ACKNOW will be turned on later. | * ACKNOW will be turned on later. | ||||
*/ | */ | ||||
if (DELAY_ACK(tp, tlen) && tlen != 0 && !tfo_partial_ack) | if (DELAY_ACK(tp, tlen) && tlen != 0 && !tfo_partial_ack) | ||||
tcp_timer_activate(tp, TT_DELACK, | tcp_timer_activate(tp, TT_DELACK, | ||||
tcp_delacktime); | tp->t_delack); | ||||
else | else | ||||
tp->t_flags |= TF_ACKNOW; | tp->t_flags |= TF_ACKNOW; | ||||
if ((thflags & TH_ECE) && V_tcp_do_ecn) { | if ((thflags & TH_ECE) && V_tcp_do_ecn) { | ||||
tp->t_flags |= TF_ECN_PERMIT; | tp->t_flags |= TF_ECN_PERMIT; | ||||
TCPSTAT_INC(tcps_ecn_shs); | TCPSTAT_INC(tcps_ecn_shs); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 174 Lines • ▼ Show 20 Lines | #endif | ||||
/* | /* | ||||
* RFC 1323 PAWS: If we have a timestamp reply on this segment | * RFC 1323 PAWS: If we have a timestamp reply on this segment | ||||
* and it's less than ts_recent, drop it. | * and it's less than ts_recent, drop it. | ||||
*/ | */ | ||||
if ((to.to_flags & TOF_TS) != 0 && tp->ts_recent && | if ((to.to_flags & TOF_TS) != 0 && tp->ts_recent && | ||||
TSTMP_LT(to.to_tsval, tp->ts_recent)) { | TSTMP_LT(to.to_tsval, tp->ts_recent)) { | ||||
/* Check to see if ts_recent is over 24 days old. */ | /* Check to see if ts_recent is over 24 days old. */ | ||||
if (tcp_ts_getticks() - tp->ts_recent_age > TCP_PAWS_IDLE) { | /* Check to see if ts_recent is over MSL OLD */ | ||||
if (t - tp->ts_recent_age > TCP_PAWS_IDLE_SBT) { | |||||
/* | /* | ||||
* Invalidate ts_recent. If this segment updates | * Invalidate ts_recent. If this segment updates | ||||
* ts_recent, the age will be reset later and ts_recent | * ts_recent, the age will be reset later and ts_recent | ||||
* will get a valid value. If it does not, setting | * will get a valid value. If it does not, setting | ||||
* ts_recent to zero will at least satisfy the | * ts_recent to zero will at least satisfy the | ||||
* requirement that zero be placed in the timestamp | * requirement that zero be placed in the timestamp | ||||
* echo reply when ts_recent isn't valid. The | * echo reply when ts_recent isn't valid. The | ||||
* age isn't reset until we get a valid ts_recent | * age isn't reset until we get a valid ts_recent | ||||
▲ Show 20 Lines • Show All 137 Lines • ▼ Show 20 Lines | #endif | ||||
* limitations as described in Stevens TCP/IP Illustrated | * limitations as described in Stevens TCP/IP Illustrated | ||||
* Vol. 2 p.869. In such cases, we can still calculate the | * Vol. 2 p.869. In such cases, we can still calculate the | ||||
* RTT correctly when RCV.NXT == Last.ACK.Sent. | * RTT correctly when RCV.NXT == Last.ACK.Sent. | ||||
*/ | */ | ||||
if ((to.to_flags & TOF_TS) != 0 && | if ((to.to_flags & TOF_TS) != 0 && | ||||
SEQ_LEQ(th->th_seq, tp->last_ack_sent) && | SEQ_LEQ(th->th_seq, tp->last_ack_sent) && | ||||
SEQ_LEQ(tp->last_ack_sent, th->th_seq + tlen + | SEQ_LEQ(tp->last_ack_sent, th->th_seq + tlen + | ||||
((thflags & (TH_SYN|TH_FIN)) != 0))) { | ((thflags & (TH_SYN|TH_FIN)) != 0))) { | ||||
tp->ts_recent_age = tcp_ts_getticks(); | tp->ts_recent_age = t; | ||||
tp->ts_recent = to.to_tsval; | tp->ts_recent = to.to_tsval; | ||||
} | } | ||||
/* | /* | ||||
* If the ACK bit is off: if in SYN-RECEIVED state or SENDSYN | * If the ACK bit is off: if in SYN-RECEIVED state or SENDSYN | ||||
* flag is on (half-synchronized state), then queue data for | * flag is on (half-synchronized state), then queue data for | ||||
* later processing; else drop segment and return. | * later processing; else drop segment and return. | ||||
*/ | */ | ||||
Show All 32 Lines | if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == | ||||
tp->rcv_scale = tp->request_r_scale; | tp->rcv_scale = tp->request_r_scale; | ||||
tp->snd_wnd = tiwin; | tp->snd_wnd = tiwin; | ||||
} | } | ||||
/* | /* | ||||
* Make transitions: | * Make transitions: | ||||
* SYN-RECEIVED -> ESTABLISHED | * SYN-RECEIVED -> ESTABLISHED | ||||
* SYN-RECEIVED* -> FIN-WAIT-1 | * SYN-RECEIVED* -> FIN-WAIT-1 | ||||
*/ | */ | ||||
tp->t_starttime = ticks; | tp->t_starttime = t; | ||||
if (tp->t_flags & TF_NEEDFIN) { | if (tp->t_flags & TF_NEEDFIN) { | ||||
tcp_state_change(tp, TCPS_FIN_WAIT_1); | tcp_state_change(tp, TCPS_FIN_WAIT_1); | ||||
tp->t_flags &= ~TF_NEEDFIN; | tp->t_flags &= ~TF_NEEDFIN; | ||||
} else { | } else { | ||||
tcp_state_change(tp, TCPS_ESTABLISHED); | tcp_state_change(tp, TCPS_ESTABLISHED); | ||||
TCP_PROBE5(accept__established, NULL, tp, | TCP_PROBE5(accept__established, NULL, tp, | ||||
m, tp, th); | m, tp, th); | ||||
if (IS_FASTOPEN(tp->t_flags) && tp->t_tfo_pending) { | if (IS_FASTOPEN(tp->t_flags) && tp->t_tfo_pending) { | ||||
▲ Show 20 Lines • Show All 320 Lines • ▼ Show 20 Lines | process_ACK: | ||||
/* | /* | ||||
* If we just performed our first retransmit, and the ACK | * If we just performed our first retransmit, and the ACK | ||||
* arrives within our recovery window, then it was a mistake | * arrives within our recovery window, then it was a mistake | ||||
* to do the retransmit in the first place. Recover our | * to do the retransmit in the first place. Recover our | ||||
* original cwnd and ssthresh, and proceed to transmit where | * original cwnd and ssthresh, and proceed to transmit where | ||||
* we left off. | * we left off. | ||||
*/ | */ | ||||
if (tp->t_rxtshift == 1 && tp->t_flags & TF_PREVVALID && | if (tp->t_rxtshift > 0 && tp->t_flags & TF_PREVVALID && | ||||
(int)(ticks - tp->t_badrxtwin) < 0) | (t - tp->t_badrxtwin) < 0) | ||||
cc_cong_signal(tp, th, CC_RTO_ERR); | cc_cong_signal(tp, th, CC_RTO_ERR); | ||||
/* | /* | ||||
* If we have a timestamp reply, update smoothed | * If we have a timestamp reply, update smoothed | ||||
* round trip time. If no timestamp is present but | * round trip time. If no timestamp is present but | ||||
* transmit timer is running and timed sequence | * transmit timer is running and timed sequence | ||||
* number was acked, update smoothed round trip time. | * number was acked, update smoothed round trip time. | ||||
* Since we now have an rtt measurement, cancel the | * Since we now have an rtt measurement, cancel the | ||||
* timer backoff (cf., Phil Karn's retransmit alg.). | * timer backoff (cf., Phil Karn's retransmit alg.). | ||||
* Recompute the initial retransmit timer. | * Recompute the initial retransmit timer. | ||||
* | * | ||||
* Some boxes send broken timestamp replies | * Some boxes send broken timestamp replies | ||||
* during the SYN+ACK phase, ignore | * during the SYN+ACK phase, ignore | ||||
* timestamps of 0 or we could calculate a | * timestamps of 0 or we could calculate a | ||||
* huge RTT and blow up the retransmit timer. | * huge RTT and blow up the retransmit timer. | ||||
*/ | */ | ||||
if ((to.to_flags & TOF_TS) != 0 && to.to_tsecr) { | if ((to.to_flags & TOF_TS) != 0 && to.to_tsecr) { | ||||
uint32_t t; | sbintime_t rtt; | ||||
t = tcp_ts_getticks() - to.to_tsecr; | rtt = TCP_TS_TO_SBT(((uint32_t)TCP_SBT_TO_TS(t)) - to.to_tsecr); | ||||
if (!tp->t_rttlow || tp->t_rttlow > t) | if (!tp->t_rttlow || tp->t_rttlow > rtt) | ||||
tp->t_rttlow = t; | tp->t_rttlow = rtt; | ||||
tcp_xmit_timer(tp, TCP_TS_TO_TICKS(t) + 1); | tcp_xmit_timer(tp, rtt + SBT_MINTS); | ||||
} else if (tp->t_rtttime && SEQ_GT(th->th_ack, tp->t_rtseq)) { | } else if (tp->t_rtttime && SEQ_GT(th->th_ack, tp->t_rtseq)) { | ||||
if (!tp->t_rttlow || tp->t_rttlow > ticks - tp->t_rtttime) | if (!tp->t_rttlow || tp->t_rttlow > t - tp->t_rtttime) | ||||
tp->t_rttlow = ticks - tp->t_rtttime; | tp->t_rttlow = t - tp->t_rtttime; | ||||
tcp_xmit_timer(tp, ticks - tp->t_rtttime); | tcp_xmit_timer(tp, t - tp->t_rtttime); | ||||
} | } | ||||
/* | /* | ||||
* If all outstanding data is acked, stop retransmit | * If all outstanding data is acked, stop retransmit | ||||
* timer and remember to restart (more output or persist). | * timer and remember to restart (more output or persist). | ||||
* If there is more data to be acked, restart retransmit | * If there is more data to be acked, restart retransmit | ||||
* timer, using current (possibly backed-off) value. | * timer, using current (possibly backed-off) value. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 302 Lines • ▼ Show 20 Lines | if (thflags & TH_FIN) { | ||||
} | } | ||||
switch (tp->t_state) { | switch (tp->t_state) { | ||||
/* | /* | ||||
* In SYN_RECEIVED and ESTABLISHED STATES | * In SYN_RECEIVED and ESTABLISHED STATES | ||||
* enter the CLOSE_WAIT state. | * enter the CLOSE_WAIT state. | ||||
*/ | */ | ||||
case TCPS_SYN_RECEIVED: | case TCPS_SYN_RECEIVED: | ||||
tp->t_starttime = ticks; | tp->t_starttime = t; | ||||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||||
case TCPS_ESTABLISHED: | case TCPS_ESTABLISHED: | ||||
tcp_state_change(tp, TCPS_CLOSE_WAIT); | tcp_state_change(tp, TCPS_CLOSE_WAIT); | ||||
break; | break; | ||||
/* | /* | ||||
* If still in FIN_WAIT_1 STATE FIN has not been acked so | * If still in FIN_WAIT_1 STATE FIN has not been acked so | ||||
* enter the CLOSING state. | * enter the CLOSING state. | ||||
Show All 38 Lines | |||||
check_delack: | check_delack: | ||||
KASSERT(ti_locked == TI_UNLOCKED, ("%s: check_delack ti_locked %d", | KASSERT(ti_locked == TI_UNLOCKED, ("%s: check_delack ti_locked %d", | ||||
__func__, ti_locked)); | __func__, ti_locked)); | ||||
INP_INFO_UNLOCK_ASSERT(&V_tcbinfo); | INP_INFO_UNLOCK_ASSERT(&V_tcbinfo); | ||||
INP_WLOCK_ASSERT(tp->t_inpcb); | INP_WLOCK_ASSERT(tp->t_inpcb); | ||||
if (tp->t_flags & TF_DELACK) { | if (tp->t_flags & TF_DELACK) { | ||||
tp->t_flags &= ~TF_DELACK; | tp->t_flags &= ~TF_DELACK; | ||||
tcp_timer_activate(tp, TT_DELACK, tcp_delacktime); | tcp_timer_activate(tp, TT_DELACK, tp->t_delack); | ||||
} | } | ||||
INP_WUNLOCK(tp->t_inpcb); | INP_WUNLOCK(tp->t_inpcb); | ||||
return; | return; | ||||
dropafterack: | dropafterack: | ||||
/* | /* | ||||
* Generate an ACK dropping incoming segment if it occupies | * Generate an ACK dropping incoming segment if it occupies | ||||
* sequence space, where the ACK reflects our state. | * sequence space, where the ACK reflects our state. | ||||
▲ Show 20 Lines • Show All 274 Lines • ▼ Show 20 Lines | tcp_pulloutofband(struct socket *so, struct tcphdr *th, struct mbuf *m, | ||||
panic("tcp_pulloutofband"); | panic("tcp_pulloutofband"); | ||||
} | } | ||||
/* | /* | ||||
* Collect new round-trip time estimate | * Collect new round-trip time estimate | ||||
* and update averages and current timeout. | * and update averages and current timeout. | ||||
*/ | */ | ||||
void | void | ||||
tcp_xmit_timer(struct tcpcb *tp, int rtt) | tcp_xmit_timer(struct tcpcb *tp, sbintime_t rtt) | ||||
{ | { | ||||
int delta; | int64_t delta; | ||||
uint64_t expected_samples, shift, var_shift; | |||||
INP_WLOCK_ASSERT(tp->t_inpcb); | INP_WLOCK_ASSERT(tp->t_inpcb); | ||||
/* | |||||
* track this | |||||
*/ | |||||
if (rtt < SBT_1NS*100) | |||||
return; | |||||
/* RFC 7323 Appendix G RTO Calculation Modification */ | |||||
/* ExpectedSamples = ceiling(FlightSize / (SMSS * 2)) */ | |||||
/* roundup(x, y) == ceiling(x / y) * y */ | |||||
expected_samples = ((tcp_compute_pipe(tp) + ((tp->t_maxseg*2)-1)) / (tp->t_maxseg*2)); | |||||
/* | |||||
* alpha' = alpha / ExpectedSamples => | |||||
* alpha = 1 / 1 >> TCP_RTT_SHIFT | |||||
* alpha' = 1 / 1 >> (TCP_RTT_SHIFT + shift) | |||||
**/ | |||||
shift = max(fls(expected_samples + 1), 0) + TCP_RTT_SHIFT; | |||||
TCPSTAT_INC(tcps_rttupdated); | TCPSTAT_INC(tcps_rttupdated); | ||||
tp->t_rttupdated++; | tp->t_rttupdated++; | ||||
if ((tp->t_srtt != 0) && (tp->t_rxtshift <= TCP_RTT_INVALIDATE)) { | if ((tp->t_srtt != 0) && (tp->t_rxtshift <= TCP_RTT_INVALIDATE)) { | ||||
/* | /* | ||||
* srtt is stored as fixed point with 5 bits after the | * The following magic | ||||
* binary point (i.e., scaled by 8). The following magic | |||||
* is equivalent to the smoothing algorithm in rfc793 with | * is equivalent to the smoothing algorithm in rfc793 with | ||||
* an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed | * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed | ||||
* point). Adjust rtt to origin 0. | * point) when FlightSize is 1. Adjust rtt to origin 0. | ||||
*/ | */ | ||||
delta = ((rtt - 1) << TCP_DELTA_SHIFT) | |||||
- (tp->t_srtt >> (TCP_RTT_SHIFT - TCP_DELTA_SHIFT)); | |||||
if ((tp->t_srtt += delta) <= 0) | /* | ||||
tp->t_srtt = 1; | * original calculation: | ||||
* delta = ((rtt - 1) << TCP_DELTA_SHIFT) | |||||
* - (tp->t_srtt >> (TCP_RTT_SHIFT - TCP_DELTA_SHIFT)); | |||||
*/ | |||||
delta = ((rtt - 1) >> shift) - (tp->t_srtt >> shift); | |||||
tp->t_srtt = max(tp->t_srtt + delta, SBT_1US); | |||||
/* | /* | ||||
* We accumulate a smoothed rtt variance (actually, a | * We accumulate a smoothed rtt variance (actually, a | ||||
* smoothed mean difference), then set the retransmit | * smoothed mean difference), then set the retransmit | ||||
* timer to smoothed rtt + 4 times the smoothed variance. | * timer to smoothed rtt + 4 times the smoothed variance. | ||||
* rttvar is stored as fixed point with 4 bits after the | * rttvar is stored as fixed point with 4 bits after the | ||||
* binary point (scaled by 16). The following is | * binary point (scaled by 16). The following is | ||||
* equivalent to rfc793 smoothing with an alpha of .75 | * equivalent to rfc793 smoothing with an alpha of .75 | ||||
* (rttvar = rttvar*3/4 + |delta| / 4). This replaces | * (rttvar = rttvar*3/4 + |delta| / 4). This replaces | ||||
* rfc793's wired-in beta. | * rfc793's wired-in beta. | ||||
*/ | */ | ||||
if (delta < 0) | /* | ||||
delta = -delta; | * delta has already implicitly been divided by 8 | ||||
delta -= tp->t_rttvar >> (TCP_RTTVAR_SHIFT - TCP_DELTA_SHIFT); | * se we need to multiply by 2 - similarly shift | ||||
if ((tp->t_rttvar += delta) <= 0) | * needs to be adjusted down by one | ||||
tp->t_rttvar = 1; | */ | ||||
var_shift = TCP_RTT_SHIFT - TCP_RTTVAR_SHIFT; | |||||
delta = (abs(delta) << var_shift) - (tp->t_rttvar >> (shift-var_shift)); | |||||
tp->t_rttvar = max(tp->t_rttvar + delta, SBT_1US); | |||||
if (tp->t_rttbest > tp->t_srtt + tp->t_rttvar) | if (tp->t_rttbest > tp->t_srtt + tp->t_rttvar) | ||||
tp->t_rttbest = tp->t_srtt + tp->t_rttvar; | tp->t_rttbest = tp->t_srtt + tp->t_rttvar; | ||||
} else { | } else { | ||||
/* | /* | ||||
* No rtt measurement yet - use the unsmoothed rtt. | * No rtt measurement yet - use the unsmoothed rtt. | ||||
* Set the variance to half the rtt (so our first | * Set the variance to half the rtt (so our first | ||||
* retransmit happens at 3*rtt). | * retransmit happens at 3*rtt). | ||||
*/ | */ | ||||
tp->t_srtt = rtt << TCP_RTT_SHIFT; | tp->t_srtt = rtt; | ||||
tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); | tp->t_rttvar = rtt >> 1; | ||||
tp->t_rttbest = tp->t_srtt + tp->t_rttvar; | tp->t_rttbest = tp->t_srtt + tp->t_rttvar; | ||||
} | } | ||||
tp->t_rtttime = 0; | tp->t_rtttime = 0; | ||||
tp->t_rxtshift = 0; | tp->t_rxtshift = 0; | ||||
/* | /* | ||||
* the retransmit should happen at rtt + 4 * rttvar. | * the retransmit should happen at rtt + 4 * rttvar. | ||||
* Because of the way we do the smoothing, srtt and rttvar | * Because of the way we do the smoothing, srtt and rttvar | ||||
* will each average +1/2 tick of bias. When we compute | * will each average +1/2 tick of bias. When we compute | ||||
* the retransmit timer, we want 1/2 tick of rounding and | * the retransmit timer, we want 1/2 tick of rounding and | ||||
* 1 extra tick because of +-1/2 tick uncertainty in the | * 1 extra tick because of +-1/2 tick uncertainty in the | ||||
* firing of the timer. The bias will give us exactly the | * firing of the timer. The bias will give us exactly the | ||||
* 1.5 tick we need. But, because the bias is | * 1.5 tick we need. But, because the bias is | ||||
* statistical, we have to test that we don't drop below | * statistical, we have to test that we don't drop below | ||||
* the minimum feasible timer (which is 2 ticks). | * the minimum feasible timer (which is 2 ticks). | ||||
*/ | */ | ||||
TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), | TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), max(tp->t_rttmin, rtt+2), TCPTV_REXMTMAX*tick_sbt); | ||||
max(tp->t_rttmin, rtt + 2), TCPTV_REXMTMAX); | |||||
/* | /* | ||||
* We received an ack for a packet that wasn't retransmitted; | * We received an ack for a packet that wasn't retransmitted; | ||||
* it is probably safe to discard any error indications we've | * it is probably safe to discard any error indications we've | ||||
* received recently. This isn't quite right, but close enough | * received recently. This isn't quite right, but close enough | ||||
* for now (a route might have failed after we sent a segment, | * for now (a route might have failed after we sent a segment, | ||||
* and the return path might not be symmetrical). | * and the return path might not be symmetrical). | ||||
*/ | */ | ||||
tp->t_softerror = 0; | tp->t_softerror = 0; | ||||
} | } | ||||
/* | /* | ||||
* Determine a reasonable value for maxseg size. | * Determine a reasonable value for maxseg size. | ||||
* If the route is known, check route for mtu. | * If the route is known, check route for mtu. | ||||
* If none, use an mss that can be handled on the outgoing interface | * If none, use an mss that can be handled on the outgoing interface | ||||
* without forcing IP to fragment. If no route is found, route has no mtu, | * without forcing IP to fragment. If no route is found, route has no mtu, | ||||
* or the destination isn't local, use a default, hopefully conservative | * or the destination isn't local, use a default, hopefully conservative | ||||
* size (usually 512 or the default IP max size, but no more than the mtu | * size (usually 512 or the default IP max size, but no more than the mtu | ||||
* of the interface), as we can't discover anything about intervening | * of the interface), as we can't discover anything about intervening | ||||
* gateways or networks. We also initialize the congestion/slow start | * gateways or networks. We also initialize the congestion/slow start | ||||
* window to be a single segment if the destination isn't local. | * window to be a single segment if the destination isn't local. | ||||
* While looking at the routing entry, we also initialize other path-dependent | * While looking at the routing entry, we also initialize other path-dependent | ||||
* parameters from pre-set or cached values in the routing entry. | * parameters from pre-set or cached values in the routing entry. | ||||
* | * | ||||
* NOTE that resulting t_maxseg doesn't include space for TCP options or | o * NOTE that resulting t_maxseg doesn't include space for TCP options or | ||||
* IP options, e.g. IPSEC data, since length of this data may vary, and | * IP options, e.g. IPSEC data, since length of this data may vary, and | ||||
* thus it is calculated for every segment separately in tcp_output(). | * thus it is calculated for every segment separately in tcp_output(). | ||||
* | * | ||||
* NOTE that this routine is only called when we process an incoming | * NOTE that this routine is only called when we process an incoming | ||||
* segment, or an ICMP need fragmentation datagram. Outgoing SYN/ACK MSS | * segment, or an ICMP need fragmentation datagram. Outgoing SYN/ACK MSS | ||||
* settings are handled in tcp_mssopt(). | * settings are handled in tcp_mssopt(). | ||||
*/ | */ | ||||
void | void | ||||
▲ Show 20 Lines • Show All 309 Lines • Show Last 20 Lines |
It might be nice to change the unit of this variable so we're not multiplying it out everywhere. We should use the opportunity created by the churn to standardize on a unit elsewhere as well.