Index: sys/netinet/tcp_input.c =================================================================== --- sys/netinet/tcp_input.c +++ sys/netinet/tcp_input.c @@ -1497,6 +1497,7 @@ struct mbuf *mfree; struct tcpopt to; int tfo_syn; + u_int maxseg; #ifdef TCPDEBUG /* @@ -2488,8 +2489,6 @@ #endif if (SEQ_LEQ(th->th_ack, tp->snd_una)) { - u_int maxseg; - maxseg = tcp_maxseg(tp); if (tlen == 0 && (tiwin == tp->snd_wnd || @@ -2579,7 +2578,21 @@ tp->snd_cwnd += maxseg; (void) tp->t_fb->tfb_tcp_output(tp); goto drop; - } else if (tp->t_dupacks == tcprexmtthresh) { + } else if (tp->t_dupacks == tcprexmtthresh || + (tp->t_flags & TF_SACK_PERMIT && + V_tcp_do_rfc6675_pipe && + tp->sackhint.sacked_bytes > + (tcprexmtthresh - 1) * maxseg)) { +enter_recovery: + /* + * Above is the RFC6675 trigger condition of + * more than (dupthresh-1)*maxseg sacked data. + * If the count of holes in the + * scoreboard is >= dupthresh, we could + * also enter loss recovery, but don't + * have that value readily available. + */ + tp->t_dupacks = tcprexmtthresh; tcp_seq onxt = tp->snd_nxt; /* @@ -2613,6 +2626,8 @@ tp->snd_recover = tp->snd_nxt; tp->snd_cwnd = maxseg; (void) tp->t_fb->tfb_tcp_output(tp); + if (SEQ_GT(th->th_ack, tp->snd_una)) + goto resume_partialack; goto drop; } tp->snd_nxt = th->th_ack; @@ -2699,10 +2714,19 @@ */ if ((tp->t_flags & TF_SACK_PERMIT) && (to.to_flags & TOF_SACK) && - sack_changed) + sack_changed) { tp->t_dupacks++; + /* limit overhead by setting maxseg last */ + if (!IN_FASTRECOVERY(tp->t_flags) && + (tp->sackhint.sacked_bytes > + ((tcprexmtthresh - 1) * + (maxseg = tcp_maxseg(tp))))) { + goto enter_recovery; + } + } } +resume_partialack: KASSERT(SEQ_GT(th->th_ack, tp->snd_una), ("%s: th_ack <= snd_una", __func__)); Index: sys/netinet/tcp_sack.c =================================================================== --- sys/netinet/tcp_sack.c +++ sys/netinet/tcp_sack.c @@ -750,6 +750,16 @@ else sblkp--; } + if (!(to->to_flags & TOF_SACK)) + /* + * If this ACK did not contain any + * SACK blocks, any only moved the + * left edge right, it is a pure + * cumulative ACK. Do not count + * DupAck for this. Also required + * for RFC6675 rescue retransmission. + */ + sack_changed = 0; tp->sackhint.delivered_data = delivered_data; tp->sackhint.sacked_bytes += delivered_data - left_edge_delta; KASSERT((delivered_data >= 0), ("delivered_data < 0")); @@ -800,6 +810,31 @@ if (tp->snd_cwnd > tp->snd_ssthresh) tp->snd_cwnd = tp->snd_ssthresh; tp->t_flags |= TF_ACKNOW; + /* + * RFC6675 rescue retransmission + * Add a hole between th_ack (snd_una is not yet set) and snd_max, + * if this was a pure cumulative ACK and no data was send beyond + * recovery point. Since the data in the socket has not been freed + * at this point, we check if the scoreboard is empty, and the ACK + * delivered some new data, indicating a full ACK. Also, if the + * recovery point is still at snd_max, we are probably application + * limited. However, this inference might not always be true. The + * rescue retransmission may rarely be slightly premature + * compared to RFC6675. + * The corresponding ACK+SACK will cause any further outstanding + * segments to be retransmitted. This addresses a corner case, when + * the trailing packets of a window are lost and no further data + * is available for sending. + */ + if ((V_tcp_do_rfc6675_pipe) && + SEQ_LT(th->th_ack, tp->snd_recover) && + (tp->snd_recover == tp->snd_max) && + TAILQ_EMPTY(&tp->snd_holes) && + (tp->sackhint.delivered_data > 0)) { + struct sackhole *hole; + int maxseg = tcp_maxseg(tp); + hole = tcp_sackhole_insert(tp, SEQ_MAX(th->th_ack, tp->snd_max - maxseg), tp->snd_max, NULL); + } (void) tp->t_fb->tfb_tcp_output(tp); }