Thread (5 messages) flat view 5 messages, 3 authors, 5d ago

Re: [PATCH net] tcp: initialize skb tx timestamp key before cloning

From: Jason Xing <hidden>
Date: 2026-09-17 08:27:37

On Thu, Sep 17, 2026 at 1:32 AM Eric Dumazet [off-list ref] wrote:
On Wed, Sep 16, 2026 at 10:23 AM David Wei [off-list ref] wrote:
quoted
On 2026-09-15 15:21, Eric Dumazet wrote:
quoted
On Tue, Sep 15, 2026 at 2:44 PM David Wei [off-list ref] wrote:
quoted
tcp_tx_timestamp() can select skbs from the rtx queue when all the
copied data has been sent in tcp_sendmsg_locked(). These skbs may be
already cloned, sharing the same shinfo, and handed off into the lower
tx layers.

tcp_tx_timestamp() sets tx_flags before setting skb tskey, racing with
any reader of both. It is possible to observe a valid tx_flag, but an
uninitialized tskey, which produces a large underflow after subtracting
the socket tskey.

Initialize skb tskey just before cloning while its shinfo is still
private. In tcp_tx_timestamp(), if it selects a cloned skb from the rtx
queue from the current tcp_sendmsg_locked() call (i.e. tx_flags and
txstamp_ack are 0) and the pre-init skb tskey matches, then tx_flags can
be set safely.

If the skb is not shared, coming from the write queue, then both tskey
and tx_flags are set, as before.

Fixes: 838eb9687691 ("tcp: tcp_tx_timestamp() must look at the rtx queue")
Assisted-by: LLM
Signed-off-by: David Wei <redacted>
The commit 838eb9687691 is a best-effort attempt because it's likely
to miss reporting qdisc/driver timestamps. Everything is caused by
failing to accurately spot the last portion of each send syscall.
Things like this (when memory is under pressure) happen.
quoted
quoted
pw-bot: cr

Sorry, this patch significantly hurts TCP performance and has many bugs.

I am sure your LLM can find a cheaper solution if you ask it to try harder.
This wasn't a yolo patch straight from an LLM, I tried quite hard to
reason with the bug and possible solutions. Maybe that's the problem -
the LLM is actually smarter than me :)
quoted
Some hints if needed:

First idea:

1) In tcp_tx_timestamp():

Keep shinfo / tcb accesses inside if (unlikely(tsflags)) so normal
traffic pays zero cost.
When falling back to skb = skb_rb_last(&sk->tcp_rtx_queue):
Verify TCP_SKB_CB(skb)->end_seq == tp->write_seq and
!tcp_has_tx_tstamp(skb). If either fails, do not touch skb.
Always write shinfo->tskey before sock_tx_timestamp(sk, sockc,
&shinfo->tx_flags) (and do the same in
bpf_sock_ops_enable_tx_tstamp()).
If pre-initializing shinfo->tskey in __tcp_transmit_skb() before
skb_clone(oskb, gfp_mask) (which already dirties shinfo->dataref in
the same cache line), guard it with !tcp_has_tx_tstamp(oskb) and use
TCP_SKB_CB(oskb)->end_seq - 1 (or seq + len - 1), without the bogus if
(shinfo->tskey != tskey) check in tcp_tx_timestamp().
It minimizes the scope of handling this case without hurting normal
traffic performance. Indeed.
quoted
quoted
2) In tcp_sendmsg_locked() (to preserve TX_SCHED / TX_SOFTWARE /
TX_HARDWARE on non-blocking partial sends):

Before calling tcp_push_one() / __tcp_push_pending_frames() at the end
of the loop (when skb->len >= size_goal), check if
(!sk_stream_memory_free(sk)) goto wait_for_space;.
At wait_for_space:, only call tcp_push() if timeo != 0 (since when
!timeo, sk_stream_wait_memory() immediately returns -EAGAIN and out:
will run tcp_tx_timestamp() before tcp_push()).
The minor flaw of net timestamping is just the case where
wait_for_space and do_error conditions are met. Moving
sk_stream_wait_memory ahead of tcp_push is probably a good idea to
detect if this time the skb happens to be the last one. But it only
covers the non-blocking scenario, right?
quoted
quoted
Second idea:

We can solve this cleanly and minimally by:

Reordering the writes in tcp_tx_timestamp() (and
bpf_sock_ops_enable_tx_tstamp()) so shinfo->tskey is written first via
WRITE_ONCE(), followed by publishing shinfo->tx_flags via
smp_store_release() in __sock_tx_timestamp().
It takes care of all the readers and writers tx_flags. I'm not sure if
adding the pairs here is worth it. Admittedly it's cleaner and easy to
understand.

No offense, Eric, I'm wondering if we need to revert the commit
838eb9687691 or just let the bad case happen silently, which both are
actually a best effort. If so, the users indeed lose one timestamp
record for that send syscall, but fortunately, the bad latency will
soon be reflected on the next send syscall. I think this is how we
discussed it at Netconf a few months ago? I'm not so sure if we need
to add too much code just to work around the minor flaw of net
timestamping feature.

Thanks,
Jason
quoted
I did think about this solution. I still don't fully grasp the memory
model and how READ/WRITE_ONCE and acquire-release works, so I wasn't
entirely sure if it was sufficient.

There could be situations where the skb is pushed early, and the lower
layers copy the shinfo e.g. GSO and COW. I don't have experience with
this code, so I find it hard to evaluate plausible paths that LLMs
suggest.

That's why I opted for the pre-set tskey before clone approach in this
RFC. I didn't think this would affect perf that badly, but yeah it is a
really hot function.

I believe I can make the GSO path work with acquire-release. For COW, do
you think it is actually relevant?
I am traveling today back to France, I will answer to your email in
~24 hours or so.

Thanks.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help