diff --git a/share/man/man4/tcp.4 b/share/man/man4/tcp.4 --- a/share/man/man4/tcp.4 +++ b/share/man/man4/tcp.4 @@ -31,7 +31,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd November 30, 2023 +.Dd July 6, 2024 .Dt TCP 4 .Os .Sh NAME @@ -699,6 +699,9 @@ .It Va insecure_syn Use criteria defined in RFC793 instead of RFC5961 for accepting SYN segments. Default is false. +.It Va insecure_ack +Use criteria defined in RFC793 for validating SEG.ACK. +Default is false. .It Va isn_reseed_interval The interval (in seconds) specifying how often the secret data used in RFC 1948 initial sequence number calculations should be reseeded. diff --git a/sys/netinet/in_kdtrace.h b/sys/netinet/in_kdtrace.h --- a/sys/netinet/in_kdtrace.h +++ b/sys/netinet/in_kdtrace.h @@ -330,6 +330,9 @@ SDT_PROBE_DECLARE(mib, tcp, count, tcps_tlpresends); SDT_PROBE_DECLARE(mib, tcp, count, tcps_tlpresend_bytes); + +SDT_PROBE_DECLARE(mib, tcp, count, tcps_rcvghostack); +SDT_PROBE_DECLARE(mib, tcp, count, tcps_rcvacktooold); #endif SDT_PROBE_DECLARE(ip, , , receive); diff --git a/sys/netinet/in_kdtrace.c b/sys/netinet/in_kdtrace.c --- a/sys/netinet/in_kdtrace.c +++ b/sys/netinet/in_kdtrace.c @@ -339,6 +339,8 @@ MIB_PROBE_TCP(tcps_tlpresends); MIB_PROBE_TCP(tcps_tlpresend_bytes); +MIB_PROBE_TCP(tcps_rcvghostack); +MIB_PROBE_TCP(tcps_rcvacktooold); #endif SDT_PROBE_DEFINE6_XLATE(ip, , , receive, diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c --- a/sys/netinet/tcp_input.c +++ b/sys/netinet/tcp_input.c @@ -202,6 +202,11 @@ &VNET_NAME(tcp_insecure_rst), 0, "Follow RFC793 instead of RFC5961 criteria for accepting RST packets"); +VNET_DEFINE(int, tcp_insecure_ack) = 0; +SYSCTL_INT(_net_inet_tcp, OID_AUTO, insecure_ack, CTLFLAG_VNET | CTLFLAG_RW, + &VNET_NAME(tcp_insecure_ack), 0, + "Follow RFC793 criteria for validating SEG.ACK"); + VNET_DEFINE(int, tcp_recvspace) = 1024*64; #define V_tcp_recvspace VNET(tcp_recvspace) SYSCTL_INT(_net_inet_tcp, TCPCTL_RECVSPACE, recvspace, CTLFLAG_VNET | CTLFLAG_RW, @@ -2438,6 +2443,32 @@ /* * Ack processing. */ + if (SEQ_LT(tp->snd_max, tp->iss)) + /* At least 2**31 - 1 byte have been sent. */ + tp->t_flags2 |= TF2_NO_ISS_CHECK; + if (!V_tcp_insecure_ack) { + /* Send a challenge ACK for ghost ACKs. */ + if (((tp->t_flags2 & TF2_NO_ISS_CHECK) == 0) && + SEQ_LEQ(th->th_ack, tp->iss)) { + TCPSTAT_INC(tcps_rcvghostack); + /* Send challenge ACK. */ + tcp_respond(tp, mtod(m, void *), th, m, + tp->rcv_nxt, tp->snd_nxt, TH_ACK); + tp->last_ack_sent = tp->rcv_nxt; + m = NULL; + goto drop; + } + /* Send a challenge ACK for ACKs being too old (RFC 5961). */ + if (SEQ_LT(th->th_ack, tp->snd_una - tp->max_sndwnd)) { + TCPSTAT_INC(tcps_rcvacktooold); + /* Send challenge ACK. */ + tcp_respond(tp, mtod(m, void *), th, m, + tp->rcv_nxt, tp->snd_nxt, TH_ACK); + tp->last_ack_sent = tp->rcv_nxt; + m = NULL; + goto drop; + } + } switch (tp->t_state) { /* * In SYN_RECEIVED state, the ack ACKs our SYN, so enter diff --git a/sys/netinet/tcp_stacks/bbr.c b/sys/netinet/tcp_stacks/bbr.c --- a/sys/netinet/tcp_stacks/bbr.c +++ b/sys/netinet/tcp_stacks/bbr.c @@ -7711,6 +7711,28 @@ bbr = (struct tcp_bbr *)tp->t_fb_ptr; lost = bbr->r_ctl.rc_lost; nsegs = max(1, m->m_pkthdr.lro_nsegs); + if (SEQ_LT(tp->snd_max, tp->iss)) + /* At least 2**31 - 1 byte have been sent. */ + tp->t_flags2 |= TF2_NO_ISS_CHECK; + if (!V_tcp_insecure_ack) { + /* Send a challenge ACK for ghost ACKs. */ + if (((tp->t_flags2 & TF2_NO_ISS_CHECK) == 0) && + SEQ_LEQ(th->th_ack, tp->iss)) { + TCPSTAT_INC(tcps_rcvghostack); + /* Send challenge ACK. */ + ctf_do_dropafterack(m, tp, th, thflags, tlen, ret_val); + bbr->r_wanted_output = 1; + return (1); + } + /* Send a challenge ACK for ACKs being too old (RFC 5961). */ + if (SEQ_LT(th->th_ack, tp->snd_una - tp->max_sndwnd)) { + TCPSTAT_INC(tcps_rcvacktooold); + /* Send challenge ACK. */ + ctf_do_dropafterack(m, tp, th, thflags, tlen, ret_val); + bbr->r_wanted_output = 1; + return (1); + } + } if (SEQ_GT(th->th_ack, tp->snd_max)) { ctf_do_dropafterack(m, tp, th, thflags, tlen, ret_val); bbr->r_wanted_output = 1; diff --git a/sys/netinet/tcp_stacks/rack.c b/sys/netinet/tcp_stacks/rack.c --- a/sys/netinet/tcp_stacks/rack.c +++ b/sys/netinet/tcp_stacks/rack.c @@ -12472,6 +12472,32 @@ INP_WLOCK_ASSERT(tptoinpcb(tp)); rack = (struct tcp_rack *)tp->t_fb_ptr; + if (SEQ_LT(tp->snd_max, tp->iss)) + /* At least 2**31 - 1 byte have been sent. */ + tp->t_flags2 |= TF2_NO_ISS_CHECK; + if (!V_tcp_insecure_ack) { + /* Send a challenge ACK for ghost ACKs. */ + if (((tp->t_flags2 & TF2_NO_ISS_CHECK) == 0) && + SEQ_LEQ(th->th_ack, tp->iss)) { + TCPSTAT_INC(tcps_rcvghostack); + /* Send challenge ACK. */ + __ctf_do_dropafterack(m, tp, th, thflags, tlen, ret_val, + &rack->r_ctl.challenge_ack_ts, + &rack->r_ctl.challenge_ack_cnt); + rack->r_wanted_output = 1; + return (1); + } + /* Send a challenge ACK for ACKs being too old (RFC 5961). */ + if (SEQ_LT(th->th_ack, tp->snd_una - tp->max_sndwnd)) { + TCPSTAT_INC(tcps_rcvacktooold); + /* Send challenge ACK. */ + __ctf_do_dropafterack(m, tp, th, thflags, tlen, ret_val, + &rack->r_ctl.challenge_ack_ts, + &rack->r_ctl.challenge_ack_cnt); + rack->r_wanted_output = 1; + return (1); + } + } if (SEQ_GT(th->th_ack, tp->snd_max)) { __ctf_do_dropafterack(m, tp, th, thflags, tlen, ret_val, &rack->r_ctl.challenge_ack_ts, diff --git a/sys/netinet/tcp_var.h b/sys/netinet/tcp_var.h --- a/sys/netinet/tcp_var.h +++ b/sys/netinet/tcp_var.h @@ -843,7 +843,8 @@ #define TF2_MBUF_QUEUE_READY 0x00020000 /* Inputs can be queued */ #define TF2_DONT_SACK_QUEUE 0x00040000 /* Don't wake on sack */ #define TF2_CANNOT_DO_ECN 0x00080000 /* The stack does not do ECN */ -#define TF2_PROC_SACK_PROHIBIT 0x00100000 /* Due to small MSS size do not process sack's */ +#define TF2_PROC_SACK_PROHIBIT 0x00100000 /* Due to small MSS size do not process sack's */ +#define TF2_NO_ISS_CHECK 0x00200000 /* Don't check SEG.ACK against ISS */ /* * Structure to hold TCP options that are only used during segment @@ -1085,8 +1086,11 @@ uint64_t tcps_tlpresends; /* number of tlp resends */ uint64_t tcps_tlpresend_bytes; /* number of bytes resent by tlp */ + /* SEG.ACK validation failures */ + uint64_t tcps_rcvghostack; /* received ACK for data never sent */ + uint64_t tcps_rcvacktooold; /* received ACK for data too long ago */ - uint64_t _pad[3]; /* 3 TBD placeholder for STABLE */ + uint64_t _pad[1]; /* 1 TBD placeholder for STABLE */ }; #define tcps_rcvmemdrop tcps_rcvreassfull /* compat */ @@ -1276,6 +1280,7 @@ VNET_DECLARE(int, tcp_initcwnd_segments); VNET_DECLARE(int, tcp_insecure_rst); VNET_DECLARE(int, tcp_insecure_syn); +VNET_DECLARE(int, tcp_insecure_ack); VNET_DECLARE(uint32_t, tcp_map_entries_limit); VNET_DECLARE(uint32_t, tcp_map_split_limit); VNET_DECLARE(int, tcp_minmss); @@ -1323,6 +1328,7 @@ #define V_tcp_initcwnd_segments VNET(tcp_initcwnd_segments) #define V_tcp_insecure_rst VNET(tcp_insecure_rst) #define V_tcp_insecure_syn VNET(tcp_insecure_syn) +#define V_tcp_insecure_ack VNET(tcp_insecure_ack) #define V_tcp_map_entries_limit VNET(tcp_map_entries_limit) #define V_tcp_map_split_limit VNET(tcp_map_split_limit) #define V_tcp_minmss VNET(tcp_minmss) diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c --- a/usr.bin/netstat/inet.c +++ b/usr.bin/netstat/inet.c @@ -642,8 +642,12 @@ "{N:/UDP tunneled pkt%s}\n"); p(tcps_tunneled_errs, "\t\t{:received-bad-udp-tunneled-pkts/%ju} " "{N:/UDP tunneled pkt cnt with error%s}\n"); - p(tcps_rcvacktoomuch, "\t\t{:received-acks-for-unsent-data/%ju} " - "{N:/ack%s for unsent data}\n"); + p(tcps_rcvacktoomuch, "\t\t{:received-acks-for-data-not-yet-sent/%ju} " + "{N:/ack%s for data not yet sent}\n"); + p(tcps_rcvghostack, "\t\t{:received-acks-for-data-never-been-sent/%ju} " + "{N:/ack%s for data never been sent (ghost acks)}\n"); + p(tcps_rcvacktooold, "\t\t{:received-acks-for-data-being-too-old/%ju} " + "{N:/ack%s for data being too old}\n"); p2(tcps_rcvpack, tcps_rcvbyte, "\t\t" "{:received-in-sequence-packets/%ju} {N:/packet%s} " "({:received-in-sequence-bytes/%ju} {N:/byte%s}) "