From 1aa5b3089b8a6258b5978caed88b91e819fb80c4 Mon Sep 17 00:00:00 2001 From: ntamas94 Date: Fri, 23 Oct 2026 00:09:05 +0200 Subject: [PATCH 1/4] fix(libSceNet): report the receive queue length in sceNetGetSockInfo sceNetGetSockInfo always reported 0 in recv_queue_length, so a title that reads a socket only when its record shows queued data never reads. The record now carries the host's FIONREAD count; a socket the host refuses FIONREAD for keeps 0, the value a PS4 Pro on firmware 10.71 reports for an empty queue. The datagram difference between hosts and the other measured values of the record are recorded as technical debt. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNet/Export.cpp | 8 ++++++++ core/libs/tests/GuestSceNet.cpp | 14 ++++++++++++++ docs/dev/debt/libSceNet.md | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 450868e186a..6435bde7e13 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -1714,6 +1715,13 @@ static int fill_sock_info(int s, NetSockInfo& record) { record.local_port = local.sin_port; record.remote_port = remote.sin_port; record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0); +#ifdef _WIN32 + u_long pending = 0; + if (::ioctlsocket(socket.native->value, FIONREAD, &pending) == 0) record.recv_queue_length = static_cast(pending); +#else + int pending = 0; + if (::ioctl(socket.native->value, FIONREAD, &pending) == 0) record.recv_queue_length = pending; +#endif return 0; } diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 60298ce61c7..25eff266fe5 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -1519,6 +1519,20 @@ static void CheckSockInfo() { Require(sceNetGetsockname(client, clientAddress.data(), &size) == 0 && size == 16); RequireSockInfo(client, 1, 1, clientAddress, listening); RequireSockInfo(accepted, 1, 1, listening, clientAddress); + const char queued[] = "queued"; + constexpr auto queuedLength = static_cast(sizeof(queued)); + Require(sceNetSend(client, queued, sizeof(queued), 0) == queuedLength); + std::array pending{}; + std::int32_t pendingLength = 0; + for (int attempt = 0; attempt < 500 && pendingLength != queuedLength; ++attempt) { + Require(sceNetGetSockInfo(accepted, pending.data(), 1, 0) == 1); + std::memcpy(&pendingLength, pending.data() + 0x2c, sizeof(pendingLength)); + if (pendingLength != queuedLength) std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + Require(pendingLength == queuedLength); + char drained[sizeof(queued)] = {}; + Require(sceNetRecv(accepted, drained, sizeof(drained), 0) == queuedLength && std::memcmp(drained, queued, sizeof(queued)) == 0); + RequireSockInfo(accepted, 1, 1, listening, clientAddress); Require(sceNetConnect(datagram, listening.data(), listening.size()) == 0); RequireSockInfo(datagram, 2, 1, datagramAddress, listening); const int nonblocking = 1; diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index f9f697287ef..b4aef0d0c2b 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -36,7 +36,8 @@ - [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) stores `SO_ONESBCAST` (`0x10000`, [shadPS4's `ORBIS_NET_SO_ONESBCAST`](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net_types.h)) per socket and `sceNetGetsockopt` reads back the value that was set, as shadPS4 does; PPSA34501 sets it and crashes later when it fails with `EOPNOTSUPP`. Sends ignore it. FreeBSD 11.4's [ip(4)](https://man.freebsd.org/cgi/man.cgi?manpath=FreeBSD+11.4-RELEASE&query=ip&sektion=4) documents the like-named `IPPROTO_IP` option `IP_ONESBCAST`: a datagram sent to an interface's directed broadcast address, such as 192.168.2.255, leaves through that interface with its destination rewritten to 255.255.255.255, and only on a socket allowed to set `SO_BROADCAST`. Here host routing selects the outgoing interface and the destination is not rewritten. shadPS4 also leaves sends unchanged, and its [TODO](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp#L2467-L2470) reads the option the other way round: a send to 255.255.255.255 would be converted to the interface's broadcast address. Whether the console's `SOL_SOCKET` option behaves like `IP_ONESBCAST`, and what it reads back, is not verified on hardware. FreeBSD 11.4's `sogetopt()` reads several boolean socket options back as their flag bit (`so_options & optname`, `sys/kern/uipc_socket.c`), not as the value set; whether the console does so for `0x10000` is unknown - [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden. - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) set `TCP_NODELAY` (`IPPROTO_TCP`, 1) on the host socket of a stream socket and read it back as `TF_NODELAY` (4) or 0, as FreeBSD 11.4's [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1626-L1650) does. Every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_NOPUSH` and `TCP_KEEPIDLE` among them, which FreeBSD implements) fails with `ENOPROTOOPT`, and the `IPPROTO_TCP` level on a socket that is not a stream socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/ip_output.c#L943-L944) answers it. A set on a connection that FreeBSD's [`tcp_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) refuses with `ECONNRESET` (refused, reset or in `TIME_WAIT`) succeeds here because the host accepts it. A null value with a nonzero length fails with `EINVAL`, where FreeBSD's `kern_setsockopt` returns `EFAULT`, and a get into less than an `int` fails with `EINVAL`, where `sooptcopyout` truncates. None of this was measured on a console. -- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, queue lengths, virtual ports, state, bandwidth, drops and waits, sets no status flag except `SELF` (0x1) and `NONBLOCK` (0x200000), and reports the host's buffer sizes where none was set. An IPv6 socket fails with `EAFNOSUPPORT` and is not counted or listed; shadPS4 lists it with zero addresses. `flags` bits other than 0x40 fail with `EINVAL`, where shadPS4 ignores `flags`; 0x40 adds sockets in TIME_WAIT ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs)), but closed sockets are not kept. Otherwise the `s` below 0 count and list, the return value and `EINVAL` for a null `info` or an `n` below 1 follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/06e813ff8c8e2a6d81b02cabfcc9543c3debf78e/src/core/libraries/net/net.cpp#L1498-L1522). Not measured on a console. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, send queue length, virtual ports, state, bandwidth, drops and waits, sets no status flag except `SELF` (0x1) and `NONBLOCK` (0x200000), and reports the host's buffer sizes where none was set. An IPv6 socket fails with `EAFNOSUPPORT` and is not counted or listed; shadPS4 lists it with zero addresses. `flags` bits other than 0x40 fail with `EINVAL`, where shadPS4 ignores `flags`; 0x40 adds sockets in TIME_WAIT ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs)), but closed sockets are not kept. Otherwise the `s` below 0 count and list, the return value and `EINVAL` for a null `info` or an `n` below 1 follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/06e813ff8c8e2a6d81b02cabfcc9543c3debf78e/src/core/libraries/net/net.cpp#L1498-L1522). Not measured on a console. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket, and on a datagram socket the first datagram on Linux but every queued byte on Windows. A PS4 Pro (firmware 10.71) reports 0 for an empty queue; a filled queue is not measured. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. - [SO_SNDBUF and SO_RCVBUF validation](../../../core/libs/prx/libSceNet/Export.cpp) remains unverified on PS5, and positive sizes remain subject to host buffer limits. Nonpositive sizes fail with `EINVAL` before changing the native socket, following FreeBSD 11.4 [sosetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2494-L2509). - [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [sceNetSocketClose, sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - close and concurrent registration ordering are unmeasured on a console. Closed descriptors retire their registrations, and a wait discards snapshots invalidated by close, DEL or MOD; descriptor identity follows [Linux epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html). ADD, MOD and DEL reject closed or invalid socket descriptors with `EBADF`, as [Linux epoll_ctl(2)](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) does; this error ordering is unmeasured on a console. Duplicate-descriptor alias lifetime is not modeled by this epoll backend. From 84a475d8c2662dd2f9ccf53d37efa8b63af19581 Mon Sep 17 00:00:00 2001 From: ntamas94 Date: Fri, 23 Oct 2026 01:10:42 +0200 Subject: [PATCH 2/4] fix(libSceNet): describe sockets made with socket() in sceNetGetSockInfo The console shares one descriptor space between the kernel socket calls and libSceNet, and sceNetGetSockInfo on a PS4 Pro (firmware 10.71) describes and lists a socket made with socket() as it does one made with sceNetSocket. AnyPS5 answered EBADF for such a descriptor and left it out of the s < 0 list. libkernel now describes one of its sockets (native handle with its owner, family, type, non-blocking mode) and lists its descriptors; sceNetGetSockInfo reads its own table first and the kernel's otherwise, without adopting the socket into libSceNet. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNet/Export.cpp | 64 ++++++++++++++----- .../libkernel/Socket/include/SocketPoll.hpp | 11 ++++ core/libs/prx/libkernel/Socket/src/Udp.cpp | 13 ++++ core/libs/tests/GuestSceNet.cpp | 24 ++++++- docs/dev/debt/libSceNet.md | 2 +- 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 6435bde7e13..29ddaedb762 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -1674,25 +1674,46 @@ struct NetSockInfo { static_assert(sizeof(NetSockInfo) == 0xA0 && offsetof(NetSockInfo, s) == 0x24 && offsetof(NetSockInfo, local_adr) == 0x34 && offsetof(NetSockInfo, local_port) == 0x3C && offsetof(NetSockInfo, recv_buffer_size) == 0x64); -static int sock_buffer_size(const Sock& socket, bool send, std::int32_t& value) { +struct SockInfoSource { + std::shared_ptr owner; + NativeSocket native; + int family; + int type; + bool nonblock; + int rcv_buffer; + int snd_buffer; +}; + +static bool find_sock_info_source(int s, SockInfoSource& source) { + { + std::lock_guard lk(g_mutex); + const auto it = g_socks.find(s); + if (it != g_socks.end()) { + const Sock& socket = it->second; + source = {socket.native, socket.native->value, socket.family, socket.type, socket.native->nonblock, socket.rcv_buffer, socket.snd_buffer}; + return true; + } + } + KernelSocketPoll::Description kernel; + if (!KernelDescribeSocket_nid_no_patch(s, &kernel)) return false; + source = {kernel.owner, static_cast(kernel.native), kernel.family, kernel.type, kernel.nonblocking, 0, 0}; + return true; +} + +static int sock_buffer_size(const SockInfoSource& socket, bool send, std::int32_t& value) { value = send ? socket.snd_buffer : socket.rcv_buffer; if (value != 0) return 0; NativeLength size = sizeof(value); - return ::getsockopt(socket.native->value, SOL_SOCKET, send ? SO_SNDBUF : SO_RCVBUF, reinterpret_cast(&value), &size) == 0 ? 0 : native_error(); + return ::getsockopt(socket.native, SOL_SOCKET, send ? SO_SNDBUF : SO_RCVBUF, reinterpret_cast(&value), &size) == 0 ? 0 : native_error(); } static int fill_sock_info(int s, NetSockInfo& record) { - Sock socket; - { - std::lock_guard lk(g_mutex); - const auto it = g_socks.find(s); - if (it == g_socks.end()) return NET_EBADF; - socket = it->second; - } + SockInfoSource socket; + if (!find_sock_info_source(s, socket)) return NET_EBADF; if (socket.family != NET_AF_INET) return NET_EAFNOSUPPORT; sockaddr_in local{}; NativeLength length = sizeof(local); - if (::getsockname(socket.native->value, reinterpret_cast(&local), &length) != 0) { + if (::getsockname(socket.native, reinterpret_cast(&local), &length) != 0) { #ifdef _WIN32 if (WSAGetLastError() != WSAEINVAL) return native_error(); #else @@ -1701,7 +1722,7 @@ static int fill_sock_info(int s, NetSockInfo& record) { } sockaddr_in remote{}; length = sizeof(remote); - if (::getpeername(socket.native->value, reinterpret_cast(&remote), &length) != 0) { + if (::getpeername(socket.native, reinterpret_cast(&remote), &length) != 0) { const int error = native_error(); if (error != NET_ENOTCONN) return error; } @@ -1714,13 +1735,13 @@ static int fill_sock_info(int s, NetSockInfo& record) { record.remote_adr = remote.sin_addr.s_addr; record.local_port = local.sin_port; record.remote_port = remote.sin_port; - record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0); + record.flags = NET_SOCKINFO_F_SELF | (socket.nonblock ? NET_SOCKINFO_F_NONBLOCK : 0); #ifdef _WIN32 u_long pending = 0; - if (::ioctlsocket(socket.native->value, FIONREAD, &pending) == 0) record.recv_queue_length = static_cast(pending); + if (::ioctlsocket(socket.native, FIONREAD, &pending) == 0) record.recv_queue_length = static_cast(pending); #else int pending = 0; - if (::ioctl(socket.native->value, FIONREAD, &pending) == 0) record.recv_queue_length = pending; + if (::ioctl(socket.native, FIONREAD, &pending) == 0) record.recv_queue_length = pending; #endif return 0; } @@ -1729,10 +1750,19 @@ int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) { if ((flags & ~NET_SOCKINFO_TIME_WAIT) != 0) return fail(NET_EINVAL); std::vector listed; if (s < 0) { - std::lock_guard lk(g_mutex); - for (const auto& [descriptor, socket] : g_socks) { - if (socket.family == NET_AF_INET) listed.push_back(descriptor); + { + std::lock_guard lk(g_mutex); + for (const auto& [descriptor, socket] : g_socks) { + if (socket.family == NET_AF_INET) listed.push_back(descriptor); + } + } + std::vector kernel; + KernelListSockets_nid_no_patch(&kernel); + for (const int descriptor : kernel) { + KernelSocketPoll::Description description; + if (KernelDescribeSocket_nid_no_patch(descriptor, &description) && description.family == NET_AF_INET) listed.push_back(descriptor); } + std::sort(listed.begin(), listed.end()); } if (!info) return s < 0 ? static_cast(listed.size()) : fail(NET_EINVAL); if (n < 1) return fail(NET_EINVAL); diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 97e68db1734..fc7515a3dee 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace KernelSocketPoll { constexpr short Readable = 0x1; @@ -25,6 +26,14 @@ struct NativeOwner { }; using NativeLookup = bool (*)(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); + +struct Description { + std::shared_ptr owner; + std::uintptr_t native; + int family; + int type; + bool nonblocking; +}; } extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller); @@ -39,3 +48,5 @@ extern "C" bool KernelFindSocketNative_nid_no_patch(int descriptor, std::shared_ extern "C" bool KernelLookupSocketNative_nid_no_patch(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); extern "C" int KernelReserveSocketNumber_nid_no_patch(std::uintptr_t native); extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number); +extern "C" bool KernelDescribeSocket_nid_no_patch(int descriptor, KernelSocketPoll::Description* description); +extern "C" void KernelListSockets_nid_no_patch(std::vector* descriptors); diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 22a664494ca..cbd20224068 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -1036,6 +1036,19 @@ extern "C" bool KernelLookupSocketNative_nid_no_patch(int descriptor, std::share return KernelFindSocketNative_nid_no_patch(descriptor, owner, native); } +extern "C" bool KernelDescribeSocket_nid_no_patch(int descriptor, KernelSocketPoll::Description* description) { + const auto socket = Find(descriptor); + if (!socket) return false; + std::lock_guard lock(socket->modeMutex); + *description = {socket, static_cast(socket->value), socket->family, socket->type, socket->options.nonblocking}; + return true; +} + +extern "C" void KernelListSockets_nid_no_patch(std::vector* descriptors) { + std::lock_guard lock(socketsMutex); + for (const auto& [descriptor, socket] : sockets) descriptors->push_back(descriptor); +} + extern "C" { int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) { const auto type = File::FcntlArgumentOf(command); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 25eff266fe5..63ca7bdb42f 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -91,6 +91,7 @@ int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); int APS5_VABI open_nid_postfix(const char*, int, int); int APS5_VABI socket_nid_postfix(int, int, int); int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); +int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); int APS5_VABI close_nid_postfix(int); bool GuestSocketIsOpen_nid_no_patch(int); int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); @@ -1554,14 +1555,35 @@ static void CheckSockInfo() { const int inet6 = sceNetSocket("sockinfo-inet6", 28, 2, 17); Require(inet6 >= 0 ? Failed(sceNetGetSockInfo(inet6, record.data(), 1, 0), 47) : *sceNetErrnoLoc() == 47); Require(std::all_of(record.begin(), record.end(), [](auto byte) { return byte == 0xa5; })); + const int kernel = socket_nid_postfix(2, 2, 0); + std::array kernelAddress{16, 2, 0, 0, 127, 0, 0, 1}; + Require(kernel >= 0 && bind_nid_postfix(kernel, kernelAddress.data(), kernelAddress.size()) == 0); + size = kernelAddress.size(); + Require(getsockname_nid_postfix(kernel, kernelAddress.data(), &size) == 0 && size == 16); + std::array kernelRecord; + kernelRecord.fill(0xa5); + Require(sceNetGetSockInfo(kernel, kernelRecord.data(), 1, 0) == 1); + std::array kernelExpected{}; + std::memcpy(kernelExpected.data() + 0x24, &kernel, sizeof(kernel)); + kernelExpected[0x28] = 2; + std::memcpy(kernelExpected.data() + 0x34, kernelAddress.data() + 4, 4); + std::memcpy(kernelExpected.data() + 0x3c, kernelAddress.data() + 2, 2); + kernelExpected[0x48] = 1; + std::memcpy(kernelExpected.data() + 0x64, kernelRecord.data() + 0x64, 8); + Require(std::equal(kernelExpected.begin(), kernelExpected.end(), kernelRecord.begin())); + std::int32_t kernelBuffers[2] = {}; + std::memcpy(kernelBuffers, kernelRecord.data() + 0x64, sizeof(kernelBuffers)); + Require(kernelBuffers[0] > 0 && kernelBuffers[1] > 0); std::vector expected = earlier; - expected.insert(expected.end(), {datagram, listener, client, accepted}); + expected.insert(expected.end(), {datagram, listener, client, accepted, kernel}); std::sort(expected.begin(), expected.end()); Require(RequireSockInfoList(0) == expected && RequireSockInfoList(0x40) == expected); Require(sceNetGetSockInfo(datagram, record.data(), 1, 0x40) == 1 && std::equal(configured.begin(), configured.end(), record.begin())); if (inet6 >= 0) Require(sceNetSocketClose(inet6) == 0); for (const int descriptor : {accepted, client, listener, datagram}) Require(sceNetSocketClose(descriptor) == 0); Require(Failed(sceNetGetSockInfo(datagram, record.data(), 1, 0), 9)); + Require(close_nid_postfix(kernel) == 0); + Require(Failed(sceNetGetSockInfo(kernel, record.data(), 1, 0), 9)); Require(RequireSockInfoList(0) == earlier); } diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index b4aef0d0c2b..f8ce345e2f8 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -37,7 +37,7 @@ - [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden. - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) set `TCP_NODELAY` (`IPPROTO_TCP`, 1) on the host socket of a stream socket and read it back as `TF_NODELAY` (4) or 0, as FreeBSD 11.4's [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1626-L1650) does. Every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_NOPUSH` and `TCP_KEEPIDLE` among them, which FreeBSD implements) fails with `ENOPROTOOPT`, and the `IPPROTO_TCP` level on a socket that is not a stream socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/ip_output.c#L943-L944) answers it. A set on a connection that FreeBSD's [`tcp_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) refuses with `ECONNRESET` (refused, reset or in `TIME_WAIT`) succeeds here because the host accepts it. A null value with a nonzero length fails with `EINVAL`, where FreeBSD's `kern_setsockopt` returns `EFAULT`, and a get into less than an `int` fails with `EINVAL`, where `sooptcopyout` truncates. None of this was measured on a console. - [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, send queue length, virtual ports, state, bandwidth, drops and waits, sets no status flag except `SELF` (0x1) and `NONBLOCK` (0x200000), and reports the host's buffer sizes where none was set. An IPv6 socket fails with `EAFNOSUPPORT` and is not counted or listed; shadPS4 lists it with zero addresses. `flags` bits other than 0x40 fail with `EINVAL`, where shadPS4 ignores `flags`; 0x40 adds sockets in TIME_WAIT ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs)), but closed sockets are not kept. Otherwise the `s` below 0 count and list, the return value and `EINVAL` for a null `info` or an `n` below 1 follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/06e813ff8c8e2a6d81b02cabfcc9543c3debf78e/src/core/libraries/net/net.cpp#L1498-L1522). Not measured on a console. -- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket, and on a datagram socket the first datagram on Linux but every queued byte on Windows. A PS4 Pro (firmware 10.71) reports 0 for an empty queue; a filled queue is not measured. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket, and on a datagram socket the first datagram on Linux but every queued byte on Windows. A PS4 Pro (firmware 10.71) reports 0 for an empty queue; a filled queue is not measured. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, and a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2. A socket made with `socket()` is described from the libkernel table and listed, as on that console; its buffer sizes are the host's, and the other sceNet calls still answer `EBADF` for it (not measured on a console). - [SO_SNDBUF and SO_RCVBUF validation](../../../core/libs/prx/libSceNet/Export.cpp) remains unverified on PS5, and positive sizes remain subject to host buffer limits. Nonpositive sizes fail with `EINVAL` before changing the native socket, following FreeBSD 11.4 [sosetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2494-L2509). - [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [sceNetSocketClose, sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - close and concurrent registration ordering are unmeasured on a console. Closed descriptors retire their registrations, and a wait discards snapshots invalidated by close, DEL or MOD; descriptor identity follows [Linux epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html). ADD, MOD and DEL reject closed or invalid socket descriptors with `EBADF`, as [Linux epoll_ctl(2)](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) does; this error ordering is unmeasured on a console. Duplicate-descriptor alias lifetime is not modeled by this epoll backend. From 645bb2973c93bec2a81e5cefcf6b4bdd2fa3e8d4 Mon Sep 17 00:00:00 2001 From: ntamas94 Date: Fri, 23 Oct 2026 08:58:54 +0200 Subject: [PATCH 3/4] docs(libSceNet): note the FreeBSD and macOS datagram receive queue counts Co-Authored-By: Claude Opus 5.5 --- docs/dev/debt/libSceNet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index b4aef0d0c2b..08fc445c719 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -37,7 +37,7 @@ - [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden. - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) set `TCP_NODELAY` (`IPPROTO_TCP`, 1) on the host socket of a stream socket and read it back as `TF_NODELAY` (4) or 0, as FreeBSD 11.4's [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1626-L1650) does. Every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_NOPUSH` and `TCP_KEEPIDLE` among them, which FreeBSD implements) fails with `ENOPROTOOPT`, and the `IPPROTO_TCP` level on a socket that is not a stream socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/ip_output.c#L943-L944) answers it. A set on a connection that FreeBSD's [`tcp_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) refuses with `ECONNRESET` (refused, reset or in `TIME_WAIT`) succeeds here because the host accepts it. A null value with a nonzero length fails with `EINVAL`, where FreeBSD's `kern_setsockopt` returns `EFAULT`, and a get into less than an `int` fails with `EINVAL`, where `sooptcopyout` truncates. None of this was measured on a console. - [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, send queue length, virtual ports, state, bandwidth, drops and waits, sets no status flag except `SELF` (0x1) and `NONBLOCK` (0x200000), and reports the host's buffer sizes where none was set. An IPv6 socket fails with `EAFNOSUPPORT` and is not counted or listed; shadPS4 lists it with zero addresses. `flags` bits other than 0x40 fail with `EINVAL`, where shadPS4 ignores `flags`; 0x40 adds sockets in TIME_WAIT ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs)), but closed sockets are not kept. Otherwise the `s` below 0 count and list, the return value and `EINVAL` for a null `info` or an `n` below 1 follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/06e813ff8c8e2a6d81b02cabfcc9543c3debf78e/src/core/libraries/net/net.cpp#L1498-L1522). Not measured on a console. -- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket, and on a datagram socket the first datagram on Linux but every queued byte on Windows. A PS4 Pro (firmware 10.71) reports 0 for an empty queue; a filled queue is not measured. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket. On a datagram socket the hosts differ: Linux counts the first datagram (0 for an empty one, which then blocks the queue for a title that reads only above 0), Windows every queued payload byte, and macOS adds 16 bytes for the first datagram and 32 for each later one; FreeBSD 11.4's receive buffer counts each datagram's payload plus its 16-byte address. A PS4 Pro (firmware 10.71) reports 0 for an empty queue; a filled queue is not measured. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. - [SO_SNDBUF and SO_RCVBUF validation](../../../core/libs/prx/libSceNet/Export.cpp) remains unverified on PS5, and positive sizes remain subject to host buffer limits. Nonpositive sizes fail with `EINVAL` before changing the native socket, following FreeBSD 11.4 [sosetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2494-L2509). - [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [sceNetSocketClose, sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - close and concurrent registration ordering are unmeasured on a console. Closed descriptors retire their registrations, and a wait discards snapshots invalidated by close, DEL or MOD; descriptor identity follows [Linux epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html). ADD, MOD and DEL reject closed or invalid socket descriptors with `EBADF`, as [Linux epoll_ctl(2)](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) does; this error ordering is unmeasured on a console. Duplicate-descriptor alias lifetime is not modeled by this epoll backend. From 641b92a91cc4e451d39d7fec93a2490a49b4c10a Mon Sep 17 00:00:00 2001 From: ntamas94 Date: Fri, 23 Oct 2026 09:08:40 +0200 Subject: [PATCH 4/4] docs(libSceNet): record the console's datagram receive queue count Co-Authored-By: Claude Opus 5.5 --- docs/dev/debt/libSceNet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 08fc445c719..24b8977a492 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -37,7 +37,7 @@ - [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden. - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) set `TCP_NODELAY` (`IPPROTO_TCP`, 1) on the host socket of a stream socket and read it back as `TF_NODELAY` (4) or 0, as FreeBSD 11.4's [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1626-L1650) does. Every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_NOPUSH` and `TCP_KEEPIDLE` among them, which FreeBSD implements) fails with `ENOPROTOOPT`, and the `IPPROTO_TCP` level on a socket that is not a stream socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/ip_output.c#L943-L944) answers it. A set on a connection that FreeBSD's [`tcp_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) refuses with `ECONNRESET` (refused, reset or in `TIME_WAIT`) succeeds here because the host accepts it. A null value with a nonzero length fails with `EINVAL`, where FreeBSD's `kern_setsockopt` returns `EFAULT`, and a get into less than an `int` fails with `EINVAL`, where `sooptcopyout` truncates. None of this was measured on a console. - [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, send queue length, virtual ports, state, bandwidth, drops and waits, sets no status flag except `SELF` (0x1) and `NONBLOCK` (0x200000), and reports the host's buffer sizes where none was set. An IPv6 socket fails with `EAFNOSUPPORT` and is not counted or listed; shadPS4 lists it with zero addresses. `flags` bits other than 0x40 fail with `EINVAL`, where shadPS4 ignores `flags`; 0x40 adds sockets in TIME_WAIT ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs)), but closed sockets are not kept. Otherwise the `s` below 0 count and list, the return value and `EINVAL` for a null `info` or an `n` below 1 follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/06e813ff8c8e2a6d81b02cabfcc9543c3debf78e/src/core/libraries/net/net.cpp#L1498-L1522). Not measured on a console. -- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket. On a datagram socket the hosts differ: Linux counts the first datagram (0 for an empty one, which then blocks the queue for a title that reads only above 0), Windows every queued payload byte, and macOS adds 16 bytes for the first datagram and 32 for each later one; FreeBSD 11.4's receive buffer counts each datagram's payload plus its 16-byte address. A PS4 Pro (firmware 10.71) reports 0 for an empty queue; a filled queue is not measured. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket. A PS4 Pro (firmware 10.71) reports 0 for an empty queue and counts each queued datagram's payload plus 16 bytes, as FreeBSD 11.4's receive buffer does (10 then 30 bytes queued: 26, then 72, and 46 after one receive). No host matches that on a datagram socket: Linux counts the first datagram (0 for an empty one, which then blocks the queue for a title that reads only above 0), Windows every queued payload byte, and macOS adds 16 bytes for the first datagram and 32 for each later one. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. - [SO_SNDBUF and SO_RCVBUF validation](../../../core/libs/prx/libSceNet/Export.cpp) remains unverified on PS5, and positive sizes remain subject to host buffer limits. Nonpositive sizes fail with `EINVAL` before changing the native socket, following FreeBSD 11.4 [sosetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2494-L2509). - [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [sceNetSocketClose, sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - close and concurrent registration ordering are unmeasured on a console. Closed descriptors retire their registrations, and a wait discards snapshots invalidated by close, DEL or MOD; descriptor identity follows [Linux epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html). ADD, MOD and DEL reject closed or invalid socket descriptors with `EBADF`, as [Linux epoll_ctl(2)](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) does; this error ordering is unmeasured on a console. Duplicate-descriptor alias lifetime is not modeled by this epoll backend.