Skip to content

ip/tcp: propagate peer close to spliced loopback writer#1

Open
rafael2knokia wants to merge 22 commits into
frontfrom
tcpsplice-close-fix
Open

ip/tcp: propagate peer close to spliced loopback writer#1
rafael2knokia wants to merge 22 commits into
frontfrom
tcpsplice-close-fix

Conversation

@rafael2knokia

Copy link
Copy Markdown
Owner

When two TCP conversations on the same kernel connect to each other on loopback, tcpincoming() splices them via tcpsplice(), installing tcpbypass() on each side's wq to copy blocks directly into the other side's rq. When one side closes, tcpsetstate(Closed) clears its own bypass but leaves the peer's wq with a kick that still points at us; the next user write reaches tcpbypass(), sees the missing peer and silently drops the block, and qbwrite() returns blocklen(b). The writer therefore sees writes "succeeding" forever and never observes the peer close.

Hang up the peer's wq from the Closed arm of tcpsetstate() so its next user write returns "connection closed" via qbwrite() instead of running the now-stale bypass.

Reproducer: misc/plan9/arm64/loopback-close-probe.go in the Go plan9-arm64 port (https://go-review.googlesource.com/c/go/+/719643) runs three scenarios and reports the writer Write() failing within a few milliseconds after the peer's Close(), as on Linux and the BSDs, instead of accepting 1 GiB of dropped data.

The same bug should exist in 9legacy since the splice logic predates the fork.

cinaplenrek and others added 2 commits June 16, 2026 18:09
This is in preparation to x2APIC implementation,
which extends the apicno to 32 bit integers.
This also saves 2*255*8 ~ 4K for pointers
in the data segment for pc64.
oridb and others added 20 commits June 17, 2026 22:54
when creating a new refcount block, we should set its
refcount to 1, rather than leaving it at the default
of 0. This patch was adapted from the corresponding
OpenBSD patch by chris@abditory.io·
The n argument to strncat dictates the maximum added bytes,
not how many are stored + added.
previous logic was to always keep 1 block in our buffer
once filled due to the flast flag requirement. This creates
a potential overflow with multiple calls of byte input with
1 block and some change. Instead just keep [0, block] in the
buffer. Simplifies since we only need to do something 'unusual'
if we are exactly at a block boundary.
Because there is some strange lockups on thinkpads
when toggling brightness buttons when in legacy bios mode,
x2APIC is not enabled by default unless ACPI tables explicitely
list x2APIC's or *x2apic= boot parameter is specified.
Previous fix for x509 does not build in APE because strecpy()
is not available. Rewrite it to use seprint() instead.
also fix hmac_sha2_384 and hmac_sha2_512.
No need to re-check *x2apic parameters for machno != 0.
If we are not the bootstrap processor and lapicbase == nil
then we have to enable x2APIC as well.
…able and add more error messages

accessmbox() skips subsquent permission checks when it finds out that
the directory is not writable/execable for other, which disallows the
user from mailing themselves when they have permissions turned off for
other.

Also, introduce a d_nombox error for when the "mbox" directory doesn't
exist in the mailbox, and a d_noperm for when upas/send's invoker
can't write to "mbox"; this is clearer than the d_unknown catchall
error used before.
When two TCP conversations on the same kernel connect to each other
on loopback, tcpincoming() splices them via tcpsplice(), installing
tcpbypass() on each side's wq to copy blocks directly into the other
side's rq.  When one side closes, tcpsetstate(Closed) clears its own
bypass but leaves the peer's wq with a kick that still points at us;
the next user write reaches tcpbypass(), sees the missing peer and
silently drops the block, and qbwrite() returns blocklen(b).  The
writer therefore sees writes "succeeding" forever and never observes
the peer close.

Hang up the peer's wq from the Closed arm of tcpsetstate() so its
next user write returns "connection closed" via qbwrite() instead of
running the now-stale bypass.

Reproducer: misc/plan9/arm64/loopback-close-probe.go in the Go
plan9-arm64 port (https://go-review.googlesource.com/c/go/+/719643)
runs three scenarios and reports the writer Write() failing within a
few milliseconds after the peer's Close(), as on Linux and the BSDs,
instead of accepting 1 GiB of dropped data.

The same bug should exist in 9legacy since the splice logic predates
the fork.
c95f856 ("ip/tcp: propagate peer close to spliced loopback writer")
clears the surviving end's bypass pointer and hangs up its wq so that a
write into a now-dead splice partner fails fast.  However, when that
surviving end later runs tcpclose() the Established arm sees
bypass==nil, falls through to the Syn_received clause and calls
tcpoutput(), which sends a FIN to the peer's old (l, r)addr/port tuple.

The peer is already iphtrem'd, so iphtlook() fails and we sndrst()
back.  Normally the RST harmlessly dies in iphtlook() too, but under
SMP it can race against a concurrent active connect from another conv
that just happens to be assigned the same ephemeral lport
(1/32768 chance), and that fresh conv suddenly observes Econrefused on
its first read - visible in Go's net.TestVariousDeadlines as a
"Copy = 0, <nil>; want timeout" flake at sub-millisecond deadlines.

Mark the surviving end with a new Tcpctl.bypeerclosed flag from
tcpsetstate(Closed) before clearing its bypass (the order matters so
that on TSO a tcpclose() seeing bypass==nil is guaranteed to also see
bypeerclosed==1), and teach tcpclose() to localclose() locally instead
of sending the doomed FIN.

Verified with the new misc/plan9/amd64/guest-deadline-stress.rc
harness in the Go plan9-arm64 port: 30 -count iterations of
net.TestVariousDeadlines (= 1710 deadline cycles) on -smp 4 pass
cleanly, where the previous patched kernel reproduced 5-6 failures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants