Hi Stefano, I apologize for the long wait. The issue took a while to represent itself. As usual, this was done with the assistance of Claude. After the issue occurred again the table got extracted to show that in fact two IPs that were not supposed to be there got in the table (more details below). Also, thanks to both of you for the responses. Original response --------------------------- Thanks for going through it. The stalls came back this week, about two weeks after the last pasta restart, so we could check what you asked on a live instance. 1. The insertion, now demonstrated The pasta in Docker's rootless bundle is static, non-PIE and not stripped, so low_rtt_dst sits at a fixed address. We read it as root from /proc//mem while pasta kept running: slot 0 10.0.0.181 the host's own LAN address slot 1 192.168.5.3 a WireGuard peer, over the Internet slot 2 10.0.0.80 the host's second address slot 3 192.168.122.1 libvirt bridge, host side slot 4 10.0.0.187 a Windows desktop on Wi-Fi, RTT 2-6 ms slot 5-7 empty The desktop was the peer that stalled: through pasta it got 14-33 KB/s, with retransmissions and backoff inside the namespace, while SSH from the same machine to the host ran at about 9 MB/s. 10.0.0.1 from the first report wasn't there, because pasta had been restarted since. 2. Why: the ~0U case, reproduced For a passively opened socket, the handshake gives no RTT sample if the SYN-ACK was retransmitted (tcp_synack_rtt_meas() only measures when req->num_retrans is 0, and a retransmitted SYN from the client also increments it, via tcp_rtx_synack()). The only fallback is the timestamp echo in tcp_ack_update_rtt(), so if the client didn't negotiate timestamps, tcpi_min_rtt is still ~0U when pasta checks it. Windows doesn't offer timestamps on the connections it opens, by default, which fits the desktop. Reproducer: from a Linux client with net.ipv4.tcp_timestamps = 0, drop incoming SYN-ACKs from the pasta host for 2 seconds, with an nftables input rule on the client's fixed local port, while connecting to a port that pasta forwards. The client's SYN retries make the server retransmit the SYN-ACK, and the connection completes after about 2 s. Results, on Linux 6.18.57, with pasta built from 8ba4132 plus your check and some logging: - A plain listener on the same host, reading TCP_INFO right after accept(): tcpi_min_rtt 0xffffffff, tcpi_rtt 0, tcpi_total_retrans 3. The same client without the drop: tcpi_min_rtt 5423 us. - pasta: your check fired, the client stayed out of the table, and transfers from it stayed at full speed. Without the check, it would have been added. - With timestamps enabled on the client, the same test gets a measured RTT from the timestamp echo, and nothing is skipped. We never saw 0, so skipping only ~0U looks right to me. 3. Patches Attached, against current master (7380393): 0001 tcp: Don't add peers to low RTT table before RTT is measured 0002 tcp: Log additions to low RTT table 0001 is your condition as you wrote it. 0002 logs only additions, with flow_dbg(): the skip itself happens on every tcp_prepare_flags() call until the first sample, which was 28 times for one connection in the test above, so logging it would be noisy. Each patch builds without warnings; I haven't run the test suite. I didn't do the expiry part. I'm happy to try it if you have a preference between tcp_rtt_dst_check() and tcp_update_seqack_wnd(). We're keeping the patched build running and will report if a remote peer still gets into the table. Thanks, Lynxes On Wed, Nov 4, 2026 at 2:53 PM Stefano Brivio wrote: > > Hi Lynxes, > > Thanks for the report, and sorry for the delay. Some comments inline: > > On Wed, 28 Oct 2026 14:15:59 +0100 > Lynxes Exe wrote: > > > Hi, > > > > A disclaimer before anything is owed: what you're about to read was > > written by Claude, and is the result of a bug fixing session that occurred > > on my personal home infrastructure. It was not intended as a bug hunt. > > Since it seems like this issue has been unreported and I couldn't find > > anything similar to it, and since what Claude reported seems potentially > > plausible, I'm putting it here. > > If AI assisted/generated bug reports are unwanted, I apologize. > > At least as far as I'm concerned, in term of bugs reports, whatever > makes sense and is usable is welcome. This report has a few dubious > aspects (see below) but it might still be valuable, I think. > > > Original report > > -------------------------- > > I think a peer address can get stuck in pasta's low-RTT destination > > table and then lose window clamping for good. > > This part wasn't clearly demonstrated. > > > That caused every new > > TCP connection from our LAN router's address to stall on RTOs until > > pasta was restarted. Details below. > > This looks plausible and somewhat demonstrated. > > > I haven't found an existing report on Bugzilla or this list. > > Correct, no known issues in this sense so far. > > > Version: pasta 2026_10_02.cba3570-3-g8ba4132 (the logic below is > > unchanged in current master) > > Setup: rootless Docker 29.6.1 via rootlesskit, --net=pasta, > > --port-driver=implicit, --mtu=65520 (pasta --tcp-ports=auto > > --udp-ports=auto --no-map-gw --ipv4-only ...) > > Kernel: 6.18.57 (Arch linux-lts), x86_64 > > > > What we think is happening > > -------------------------- > > We suspect the low-RTT destination table (low_rtt_dst[], > > tcp_rtt_dst_check() / tcp_rtt_dst_low()). Our LAN router's address, > > 10.0.0.1, ended up in it even though the router is several > > milliseconds away. > > Nothing clearly confirms the "ended up in it" part (yet). > > > [...] > > > > We could not read the table directly (no ptrace into pasta), so > > "10.0.0.1 is in the table" is inferred from this behaviour and the > > code path. > > ...I guess the problem here is training a language model about the > difference between "inferred" and "suspected". :) > > One could ptrace() as root by the way, but that wouldn't help either. > > Could you make sure about this with a print in tcp_rtt_dst_check()? If > that's not the case, you might need to further trace what's going on in > tcp_update_seqack_wnd() instead. > > Rebuilding and using a modified version of passt is quick (it just > depends on a C library): > > git clone git://passt.top/passt && cd passt > [edit as needed] > make && sudo make install # Or make install as root > > Both Podman and Docker / rootlesskit will pick up the local build under > /usr/local/bin. You can cleanly remove it with 'make uninstall' as root > once you're done. > > > Why we think 10.0.0.1 got into the table > > ---------------------------------------- > > A real RTT of 10 us or less to the router is impossible here > > (measured RTT 2-6 ms). Our suspicion comes from reading the kernel > > source; we have not caught it on a live socket: > > > > - tcp_rtt_dst_check() tests > > (int)tinfo->tcpi_min_rtt > LOW_RTT_THRESHOLD. > > - The kernel initialises rtt_min to ~0U, both in tcp_init_sock() > > (net/ipv4/tcp.c) and for accepted sockets in > > tcp_create_openreq_child() (tcp_minisocks.c). tcpi_min_rtt stays > > ~0U until the first valid RTT sample. > > - tcp_synack_rtt_meas() takes no sample when the SYN-ACK was > > retransmitted (rtt_us = -1 unless !req->num_retrans). The same > > applies on the active side when the SYN was retransmitted (Karn). > > - Cast to int, ~0U becomes -1, which passes the "<= 10 us" test. > > - pasta runs this check in tcp_prepare_flags() when it sends the SYN > > (inbound) or SYN-ACK (outbound) to the guest. That is right after > > the host-side handshake, before any data ACK could provide a > > sample. > > ...maybe, yes. > > > We measured exactly such handshake retransmissions on this > > Wi-Fi/NAT path: 6 of 1,800 fresh connections took ~1.05 s, i.e. one > > 1 s retry. With hundreds of inbound and outbound connections to > > 10.0.0.1 per day, one retried handshake would be enough to insert it > > permanently. > > > > The night the stalls became severe, the published container was also > > restarted about ten times while clients kept connecting, which made > > unusual handshakes more likely. We cannot tell exactly when the > > insertion happened. > > > > > > Suggested fixes > > --------------- > > 1. In tcp_rtt_dst_check(), treat tcpi_min_rtt == ~0U (and 0) as > > "no sample" and skip the insertion. Compare as unsigned, or check > > tcpi_rtt / tcpi_rcv_rtt as well. > > I'm not sure if we actually risk hitting 0: strictly speaking that > would mean less than 1 µs which looks unlikely (even for simple veth > pairs or local guests), but in practice there might be rounding errors > somewhere. > > Do we need to skip 0? From a quick glance of the kernel paths involved > I'd say we don't. > > Could you test this perhaps? Not build tested but I think you could > modify the initial early return condition to: > > if (!min_rtt_cap || > (int)tinfo->tcpi_min_rtt > LOW_RTT_THRESHOLD || > tinfo->tcpi_min_rtt == ~0U /* not measured yet */) > return; > > > 2. Don't trust a single sample forever: give entries a lifetime, or > > evict an address when a later connection to it shows a min RTT > > above the threshold. > > Reasonable. I guess we could add a timestamp and check if the entry > expired (minutes?) where we update the RTT in > tcp_update_seqack_wnd()... or even in tcp_rtt_dst_check(). No strong > preference on my side, I didn't really think it through. > > > 3. Log (at debug level) when an address enters the table. It was > > invisible here; the only visible signs were retransmissions > > inside the namespace. > > Yeah, that would be good. I added this feature before we started > logging anything, I think. Feel free to send a patch for that. > > > Workaround: restarting pasta clears the table. > > > > Happy to test a patch or collect more data. > > Let me know if you can check those few things I suggested, thanks. > > -- > Stefano >