Repository navigation
fix(libkernel): refuse a loopback TCP connect at once on Windows - #5141
Conversation
afe0a5d to
f87099f
Compare
CnRJay
left a comment
There was a problem hiding this comment.
The diagnosis is right: Winsock retries a SYN after a RST, and FreeBSD doesn't. One case this change makes worse, though: a loopback listener whose accept backlog is full.
Windows answers a SYN to a listener with a full backlog with a RST, the same as for a closed port. FreeBSD instead drops the SYN when the listen queue is full (or answers through its syncache), and the client's retransmission connects once the server has accepted the earlier connections. On main, Windows' SYN retries happen to cover that case. With TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS the connect fails with ECONNREFUSED at once. A title or homebrew that opens a burst of connections to a local server, for example several HTTP requests to an in-process service, or a server that calls listen with a small backlog, would then see refusals that neither FreeBSD nor main produce.
Ways to keep the fast refusal for closed ports without breaking busy ones:
- Keep one SYN retransmission with a short initial RTO (
TCP_INITIAL_RTO_PARAMETERS{Rtt = a few ms, MaxSynRetransmissions = 1}) rather than none. A closed port then fails in a few milliseconds, and a full backlog gets one more chance. - Or retry the connect once in
connectitself after a short delay when it is refused on loopback.
A test with a loopback listener of backlog 1 and two quick connects would pin the behaviour either way.
|
Windows/AMD check with PS5VR (PPSA99177 v1.2.0, catalog release): Windows 11 (26300), Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), WinLibs GCC 15.2.0, Release, relinked with Time from launch until the title logs
On
With the PR the title shows its home screen ("Put on your PlayStation VR2", the phone-link QR code for port 8090): That's the only frame it presents without a headset: its main loop draws the home screen once and then only presents while a VR session is active ( AI-assisted: yes (Claude Code) |
adriadam10
left a comment
There was a problem hiding this comment.
Thanks, the diagnosis and the PS5VR numbers hold up. One point to resolve before merging, the one CnRJay raised and that is still open:
core/libs/prx/libkernel/Socket/include/SocketConnect.hpp:17:TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONSalso turns a loopback listener with a full accept backlog into an immediateECONNREFUSED, because Windows answers that SYN with a RST just like a closed port. FreeBSD drops the SYN (or answers through the syncache) and the client's retransmission connects once the server accepts, andmaincurrently gets the same result from Windows' retries. A burst of connections to a local server with a smalllistenbacklog would regress. Keeping one SYN retransmission with a short initial RTO (MaxSynRetransmissions = 1,Rttof a few ms) keeps the closed-port case fast and leaves the full-backlog case one more chance.core/libs/tests/GuestRefusedConnect.cpp: add a case with a loopback listener of backlog 1 and a second quick connect that must succeed once the first is accepted, so the behaviour is pinned either way, and keep the closed-port timing check.
Please also report the closed-port time with whatever setting you choose (it should still be well under the 1 s limit).
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
|
NVIDIA check with PS5VR (PPSA99177 v1.2.0, catalog release) as a second vendor next to the AMD run above, plus the backlog-full case from the review.
Every run reaches the home screen ("Put on your PlayStation VR2") with
So the regression raised in review reproduces on Windows 11 26220. A SYN to a full backlog gets a RST. On Verdict: the PS5VR speed-up holds on NVIDIA: the library screen comes about 42 s sooner, with the gap falling from 44.9 s to 2.0 s. The backlog-full regression is real, though, so the requested change still applies (keep a retransmission or retry for loopback). AI-assisted: yes (Claude Code) |
Windows answers a SYN to a closed port with a RST and then retransmits the SYN, so connect to a closed loopback port takes about 2 s before ECONNREFUSED; FreeBSD fails at once. PS5VR (PPSA99177) makes about 33 such connects to 127.0.0.1:17600 and reaches its home screen 45 s late. When no socket listens on the destination port (GetExtendedTcpTable), a loopback connect now sets SIO_TCP_INITIAL_RTO with no SYN retransmissions and fails at once. A port with a listener keeps the retransmissions: Windows also answers a SYN to a full accept backlog with a RST, and the retransmission connects once the server accepts, as the console's does after FreeBSD drops the SYN. guest_refused_connect requires ECONNREFUSED in under a second for a closed port through libkernel connect and sceNetConnect, and a second connect to a listener with backlog 1 to succeed once the server accepts the first, through both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
f87099f to
831c583
Compare
|
Thanks @CnRJay for the backlog case and @adriadam10 for the review; @screamm's probe confirmed the regression. I changed the approach instead of keeping one retransmission: the fast refusal now applies only when nothing listens on the destination port. Before a loopback connect, libkernel reads the listening TCP sockets ( One retransmission with a short initial RTO would still refuse whenever the server accepts later than that RTO. With a backlog of 1 and a server that accepts after 100 ms, a retry a few milliseconds later still finds the backlog full.
Windows 11 (26200), WinLibs GCC 15.2.0 r7, Release, head 831c583 on
The call in AI-assisted (Claude Code). |
|
Re-ran my backlog probe on head
So @adriadam10's point is resolved: a full backlog behaves as on
Not checked: full build and non-GPU suite, IPv6 Written with Claude Code assistance (validation by a subagent, reviewed in this session). |
adriadam10
left a comment
There was a problem hiding this comment.
Checked the new head against the open point: the fast refusal now applies only when GetExtendedTcpTable shows no listener on the destination port (IPv4 and IPv6), so a full backlog keeps Windows' retransmissions; guest_refused_connect covers both the closed port and backlog 1 through connect and sceNetConnect. Local build and ctest pass, and the Windows results from @screamm and @LeandroLP23 on this head cover the Windows behaviour; merging once the Windows build is green.

What
On FreeBSD, a TCP
connectfails withECONNREFUSEDas soon as the peer answers the SYN with a reset (POSIXconnect: "The target address was not listening for connections or refused the connection request"). Windows instead retransmits the SYN twice after a reset. On this machine a blocking connect to a loopback port with no listener takes 2.04 s, against 0.2 ms once the retransmissions are off.Homebrew that looks for a local service by connecting to
127.0.0.1waits that long on every attempt. PS5VR (PPSA99177) probes its helper service on port 17600 up to 21 times before its library screen (main.cpp,app_bridge.c). Onmainits log showsps5vr: sandbox openat 23.7 s andps5vr: dev service absentat 68.6 s: 45 s of refused connects. With this change the two lines are 2.0 s apart, which is PS5VR's own 20 × 100 ms sleep.Before a stream socket connects to a loopback address (127.0.0.0/8 or
::1), libkernel'sconnectand libSceNet'ssceNetConnectnow check whether any socket listens on the destination port (GetExtendedTcpTablewithTCP_TABLE_OWNER_PID_LISTENER, IPv4 and IPv6). When none does, they setSIO_TCP_INITIAL_RTOon the host socket withTCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS, which sets the SYN retransmissions to 0, so the reset fails the connect at once, as on FreeBSD.The check is
RefuseClosedLoopbackAtOnce_nid_no_patchin a newlibkernel/Socket/src/SocketConnect.cpp; libSceNet calls it from libkernel, and libkernel linksiphlpapion Windows.Tested
Windows 11 (26200), WinLibs GCC 15.2.0 r7, Release,
mainff23657 (rebased onto 0e6a5e7 afterwards, no conflicts):guest_refused_connect, with libkernel'sconnectand withsceNetConnect:127.0.0.1:0, reads its port, closes it, connects to that port and requiresECONNREFUSED(61 /0x8041013D) in under one second;It passes in 1.05 s. With
main's behaviour it aborts in the closed-port case (2.34 s), and without the listener check in the backlog case (0.40 s).A probe calling libkernel's
socket,listen,connectandaccept, 3 rounds per case:mainECONNREFUSEDafter 2047-2050 msECONNREFUSEDat oncePS5VR (PPSA99177, catalog 1.2.0), relinked with this
main, second run (warm shader cache):ps5vr: sandbox openat 7.11 s,ps5vr: dev service absentat 9.25 s, its home screen'slibrary: list Library -> ok, 7 itemsat 10.26 s. Onmainthe first two lines are about 45 s apart.All 27 socket tests pass:
guest_refused_connect,guest_sce_net, the fourguest_datagram_*, theguest_socket_*tests andpatched_libraries.tools/check_conventions.py --base upstream/mainpasses.Linux (Ubuntu 24.04 in WSL, GCC 13.3, Release, the CI's configure options, lavapipe), merged onto
main92e5041 together with fix(libSceSystemService): report the host time zone and summer time #5084:guest_refused_connect,guest_sce_net, the fourguest_datagram_*, theguest_socket_*tests andpatched_librariespass, exceptguest_socket_read_write. Full ctest: 1263/1273; the ten failures (nineagc_*tests andguest_socket_read_write) fail the same way onmain92e5041 alone in this setup.git merge-tree: merges cleanly withmain, fix(libkernel): report SO_ERROR and the other SOL_SOCKET options like FreeBSD #4796, fix(libSceNet): let a datagram receive on an unbound socket wait on Windows #5966, fix(libSceNet): close a libkernel socket through sceNetSocketClose #6140 and fix(libSceNet): set an option on a libkernel socket through sceNetSetsockopt #6142. feat(libkernel): carry kqueue read and write events on pipes and terminals #4910 conflicts only in files this PR does not touch. The call insceNetConnectsits before the address-family check, and libSceNet declares the helper instead of including a header, so the change stays off the lines those PRs edit.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code