[RFCv2 2/3] tcp: Adjust congestion window handling for mtu probe

From: Leonard Crestez
Date: Wed May 26 2021 - 06:38:55 EST


On very fast links after successive succesful MTU probes the cwnd (measured in
packets) shrinks and does not grow again because tcp_is_cwnd_limited returns
false unless at least half the window is being used. If snd_cwnd falls below 11
then no more probes are sent despite the link being otherwise idle.

When preparing an mtu probe linux checks for snd_cwnd >= 11 and for 2 more
packets to fit alongside what is currently in flight. The reasoning behind
these constants is unclear. Replace this with checks based on the required
probe size:

* Skip probing if congestion window is too small to ever fit a probe.
* Wait for the congestion window to drain if too many packets are
already in flight.

This is very similar to snd_wnd logic except packets are counted instead of
bytes. This patch also adds more documentation regarding how "return 0"
works in tcp_mtu_probe because I found it difficult to understand.

This patch allows mtu probing at smaller cwnd values and does not contradict
any standard. Since "0" is only returned if packets are in flight no stalls
should happen expect when many acks are lost.

Removing the snd_cwnd >= 11 check also allows probing to happen for
bursty traffic where the cwnd is reset to 10 after a few hundred ms of
idling.

It does not completely solve the problem of very small cwnds on fast links.

Signed-off-by: Leonard Crestez <cdleonard@xxxxxxxxx>
---
net/ipv4/tcp_output.c | 30 +++++++++++++++++++++---------
1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 9691f435477b..362f97cfb09e 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -2328,32 +2328,35 @@ static int tcp_mtu_probe_is_rack(const struct sock *sk)
* changes resulting in larger path MTUs.
*
* Returns 0 if we should wait to probe (no cwnd available),
* 1 if a probe was sent,
* -1 otherwise
+ *
+ * Caller won't queue future write attempts if this returns 0. Zero is only
+ * returned if acks are pending from packets in flight which will trigger
+ * tcp_write_xmit again later.
*/
static int tcp_mtu_probe(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb, *nskb, *next;
struct net *net = sock_net(sk);
int probe_size;
int size_needed;
+ int packets_needed;
int copy, len;
int mss_now;
int interval;

/* Not currently probing/verifying,
* not in recovery,
- * have enough cwnd, and
* not SACKing (the variable headers throw things off)
*/
if (likely(!icsk->icsk_mtup.enabled ||
icsk->icsk_mtup.probe_size ||
inet_csk(sk)->icsk_ca_state != TCP_CA_Open ||
- tp->snd_cwnd < 11 ||
tp->rx_opt.num_sacks || tp->rx_opt.dsack))
return -1;

/* Use binary search for probe_size between tcp_mss_base,
* and current mss_clamp. if (search_high - search_low)
@@ -2375,10 +2378,11 @@ static int tcp_mtu_probe(struct sock *sk)
/* Without RACK send enough extra packets to trigger fast retransmit
* This is dynamic DupThresh + 1
*/
size_needed = probe_size + (tp->reordering + 1) * tp->mss_cache;
}
+ packets_needed = DIV_ROUND_UP(size_needed, tp->mss_cache);

interval = icsk->icsk_mtup.search_high - icsk->icsk_mtup.search_low;
/* When misfortune happens, we are reprobing actively,
* and then reprobe timer has expired. We stick with current
* probing process by not resetting search range to its orignal.
@@ -2394,22 +2398,30 @@ static int tcp_mtu_probe(struct sock *sk)

/* Have enough data in the send queue to probe? */
if (tp->write_seq - tp->snd_nxt < size_needed)
return -1;

+ /* Can probe fit inside congestion window? */
+ if (packets_needed > tp->snd_cwnd)
+ return -1;
+
+ /* Can probe fit inside receiver window? If not then skip probing.
+ * The receiver might increase the window as data is processed but
+ * don't assume that.
+ * If some data is inflight (between snd_una and snd_nxt) we wait for it to
+ * clear below.
+ */
if (tp->snd_wnd < size_needed)
return -1;
+
+ /* Do we need for more acks to clear the receive window? */
if (after(tp->snd_nxt + size_needed, tcp_wnd_end(tp)))
return 0;

- /* Do we need to wait to drain cwnd? With none in flight, don't stall */
- if (tcp_packets_in_flight(tp) + 2 > tp->snd_cwnd) {
- if (!tcp_packets_in_flight(tp))
- return -1;
- else
- return 0;
- }
+ /* Do we need the congestion window to clear? */
+ if (tcp_packets_in_flight(tp) + packets_needed > tp->snd_cwnd)
+ return 0;

if (!tcp_can_coalesce_send_queue_head(sk, probe_size))
return -1;

/* We're allowed to probe. Build it now. */
--
2.25.1