mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Lukas Prause <lukas.prause@ikt.uni-hannover.de>
To: Eric Dumazet <edumazet@google.com>
Cc: "David S. Miller" <davem@davemloft.net>,
	David Ahern <dsahern@kernel.org>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Simon Horman <horms@kernel.org>,
	Neal Cardwell <ncardwell@google.com>,
	Kuniyuki Iwashima <kuniyu@google.com>,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
	fmancera@suse.de, ebiggers@kernel.org, bpf@vger.kernel.org,
	Tim Fuechsel <t.fuechsel@gmx.de>
Subject: Re: Re: [PATCHv5 net-next] tcp: Add TCP ROCCET congestion control module.
Date: Tue, 8 Sep 2026 13:22:21 +0200	[thread overview]
Message-ID: <cee2e4d2-3a0f-46d2-9607-b72eb95f785f@ikt.uni-hannover.de> (raw)
In-Reply-To: <CANn89iKaXghXPHTQU1sQdNEyfT3RA5AR_gY9+FrhKx59GUVOBQ@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 7454 bytes --]

Hi Eric,
thank you for the review. On a few points, I’d have some comments. We
think that this AI review is, at some point, a little bit misleading.
We are using the cong_control functionality to implement stricter packet
pacing than the default value.
With this, we aim to implement our own alternative to PRR.

> 1. Concurrency data race on module parameters in socket init
> ------------------------------------------------------------
> In roccettcp_init():
>     param_check(true);
>     param_precompute();
>
> Both functions mutate global static variables (beta, bic_scale,
beta_scale,
> cube_rtt_scale, cube_factor) that are marked __read_mostly.
> Calling this in roccettcp_init() on every socket creation causes
unsynchronized
> concurrent writes across CPUs. Furthermore, param_check() calls pr_err() /
> pr_info(), which will spam the kernel log on every connection creation
if an
> invalid parameter was configured.
>
> Global scale factors should only be precomputed at module init time
> (roccettcp_register), or validated using kernel_param_ops.
I agree with you. We will fix that.

> 2. Bypassing PRR and broken loss recovery via .cong_control
> ----------------------------------------------------------
> By defining .cong_control = roccet_control, TCP core completely delegates
> congestion control and skips PRR (tcp_cwnd_reduction()).
>
> However, in roccet_control():
>     if (!ca->is_in_recovery)
>         roccettcp_cong_avoid(sk, ack, rs->acked_sacked);
>
> During recovery (TCP_CA_Recovery), ROCCET does nothing to regulate cwnd or
> clock out retransmissions according to RFC 6937. Unless you have a full
> custom recovery engine (like BBR), you should stick to .cong_avoid so the
> stack handles PRR and loss recovery properly.
If we are in recovery we still do packet pacing to 100 percent of the
cwnd instead of 120.

> 3. Broken .ssthresh callback and ignored ECN (TCP_CA_CWR)
> ---------------------------------------------------------
> roccettcp_ssthresh() simply returns tp->snd_ssthresh without
recalculation:
>     static u32 roccettcp_ssthresh(struct sock *sk)
>     {
>         return tcp_sk(sk)->snd_ssthresh;
>     }
>
> The recalculation was moved to roccettcp_state(..., TCP_CA_Recovery).
> This breaks the TCP stack contract:
> a) tcp_init_cwnd_reduction() initializes PRR state using the return
value of
>    icsk_ca_ops->ssthresh(sk). Returning the unreduced ssthresh
corrupts PRR.
> b) When ECN CE marks arrive, tcp_enter_cwr() transitions to TCP_CA_CWR
and calls
>    icsk_ca_ops->ssthresh(sk). Since roccettcp_state() only checks for
Loss and
>    Recovery, TCP_CA_CWR is completely ignored. Consequently, neither
ssthresh
>    nor cwnd is ever reduced on ECN.
We are not using the PRR from the TCP stack, because we are implementing
cong_control.
I agree that ECNs are ignored here. We will fix that.

> 4. Congestion avoidance starvation in ORBITER
> ---------------------------------------------
> In roccettcp_cong_avoid():
>     send = tp->snd_nxt - ca->interval_snd_seq_start;
>     received = tp->snd_una - ca->interval_una_seq_start;
>     send_more_than_acked =
>         send > received + ((tcp_snd_cwnd(tp) * tp->mss_cache) / 100);
>     ...
>     if (!tcp_is_cwnd_limited(sk) || send_more_than_acked)
>         return;
>
> During steady-state transmission, the in-flight byte count (send -
received)
> is approximately cwnd * mss. This is ~100x larger than the 1% guard space
> ((cwnd * mss) / 100). Thus, send_more_than_acked evaluates to true on
almost
> every ACK, returning before bictcp_update() or tcp_cong_avoid_ai() can
run.
> Window growth in congestion avoidance is completely starved.
That is not true. Here we are monitoring the bytes sent and received
over a time interval of 5 RTT

> 5. curr_min_rtt poisoning and premature slow-start exit
> -------------------------------------------------------
> In roccettcp_reset(), ca->curr_rtt is 0 and ca->curr_min_rtt is ~0U.
> In update_min_rtt():
>     if (ca->curr_rtt < ca->curr_min_rtt) {
>         ca->curr_min_rtt = max(ca->curr_rtt, 1);
>         ...
>
> If an ACK arrives before an RTT sample is collected (e.g.
sample->rtt_us <= 0),
> ca->curr_rtt is 0. ca->curr_min_rtt is set to max(0, 1) = 1 us.
> Because 1 us is smaller than any real path RTT, ca->curr_min_rtt
remains stuck
> at 1 us.
> Then in update_srrtt():
>     u32 rrtt = div_u64(100 * (u64)(ca->curr_rtt - ca->curr_min_rtt),
>                        ca->curr_min_rtt);
> For a 20 ms RTT (20000 us), rrtt evaluates to 1,999,900. curr_srrtt
immediately
> exceeds sr_rtt_upper_bound and terminates slow start on the very first
sample.
That could be a problem, and we will implement a fix for it.

> 6. Perpetual min-RTT probe loop
> -------------------------------
> In roccet_min_rtt_probe(), when the probe finishes:
>     ca->probe_min_rtt_until = 0;
>     ca->state = ORBITER;
>
> ca->next_min_rtt_probe is not updated when exiting the probe. If no
new lower
> RTT was found during the probe, next_min_rtt_probe remains in the past.
> On the next ACK, after(now, ca->next_min_rtt_probe) immediately
evaluates to
> true, trapping the flow in a continuous probe loop and repeatedly
halving cwnd
> down to TCP_INIT_CWND.
That is not true. Every time a new minimum RTT is found, we calculate
ca->next_min_rtt_probe, and before we enter probing, the current minimum
RTT is set to u32 max.
This means the next RTT is the new minimum RTT.

> 7. 71-minute timestamp wrap and permanent DRAIN state
> -----------------------------------------------------
> now is calculated via jiffies_to_usecs(tcp_jiffies32), which wraps every
> ~71.5 minutes (2^32 us).
>
> In roccet_control():
>     else if ((s32)now - ca->roccet_last_event_time_us <= 100 *
USEC_PER_MSEC)
>         ca->state = DRAIN;
>
> ca->roccet_last_event_time_us is initialized to 0. When bit 31 of now
is set
> (for 35.7 minutes out of every 71.5 minutes), (s32)now is negative, so
> (s32)now - 0 is negative and always <= 100000. Newly created
connections during
> that 35-minute window will immediately enter and remain permanently
stuck in
> DRAIN state.
>
> Also, before() and after() from <net/tcp.h> are sequence number comparison
> macros (32-bit TCP seqno), not time comparison macros.
This is a problem, and we will fix it.
In the past, we got a hint that we should use the before and after
macros here.

> 8. Re-implementing tcp_update_pacing_rate()
> -------------------------------------------
> roccet_control() duplicates the pacing rate calculation from
tcp_input.c and
> hardcodes 100% pacing instead of honoring sysctl_tcp_pacing_ca_ratio.
If you
> switch to .cong_avoid, the core stack will manage pacing updates
automatically.
We do this as an alternative to PRR with stricter pacing.

> 9. Coding style & formatting
> ----------------------------
> - Declarations after statements (ISO C90 violations) in roccettcp_init(),
>   update_srrtt(), roccettcp_acked(), and roccettcp_register().
> - C++ style comments (//) instead of /* ... */ in multiple places.
> - Commented-out dead code: //WRITE_ONCE(sk->sk_pacing_rate, 0);
> - Typos in comments ("sRrTT", "probeing", "has was idle", "defer", etc.)
>   and references to non-existent struct fields ("curr_min_rtt_timed.rtt").
Of course, we will correct that.

[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4930 bytes --]

  reply	other threads:[~2026-09-08 11:27 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-31 17:03 Tim Fuechsel
2026-08-31 18:57 ` Eric Dumazet
2026-09-08 11:22   ` Lukas Prause [this message]
2026-09-04 22:25 ` netdev-bot+sashiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cee2e4d2-3a0f-46d2-9607-b72eb95f785f@ikt.uni-hannover.de \
    --to=lukas.prause@ikt.uni-hannover.de \
    --cc=bpf@vger.kernel.org \
    --cc=davem@davemloft.net \
    --cc=dsahern@kernel.org \
    --cc=ebiggers@kernel.org \
    --cc=edumazet@google.com \
    --cc=fmancera@suse.de \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=kuniyu@google.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ncardwell@google.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=t.fuechsel@gmx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®