From e8d989b6c0352b8dd4cbe0ea4f2d51745320f9e3 Mon Sep 17 00:00:00 2001 From: Queuze <58829992+lucaslima430@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:02:35 -0300 Subject: [PATCH 1/2] feat(libSceNet): expose socket type and listen state --- core/libs/prx/libSceNet/CMakeLists.txt | 10 +++ core/libs/prx/libSceNet/Export.cpp | 8 +++ core/libs/tests/GuestNetSocketState.cpp | 86 +++++++++++++++++++++++++ docs/dev/debt/libSceNet.md | 1 + 4 files changed, 105 insertions(+) create mode 100644 core/libs/tests/GuestNetSocketState.cpp diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index 95faf4584b..6f6c164061 100644 --- a/core/libs/prx/libSceNet/CMakeLists.txt +++ b/core/libs/prx/libSceNet/CMakeLists.txt @@ -4,6 +4,16 @@ target_link_libraries(libSceNet PRIVATE libSceNetCtl) configure_windows_unwind(libSceNet) if(BUILD_TESTING) + add_test_executable(guest_net_socket_state_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetSocketState.cpp) + target_include_directories(guest_net_socket_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_socket_state_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_socket_state_tests) + add_test(NAME guest_net_socket_state COMMAND guest_net_socket_state_tests) + set_tests_properties(guest_net_socket_state PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_net_socket_state PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(net_dontwait_tests ${LIBS_INCLUDE_DIR}/tests/NetDontWait.cpp) target_include_directories(net_dontwait_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(net_dontwait_tests PRIVATE libSceNet libkernel libc) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 808cdc7364..4ec0d53151 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -281,6 +281,8 @@ constexpr int NET_SO_SNDBUF = 0x1001; constexpr int NET_SO_RCVBUF = 0x1002; constexpr int NET_SO_SCE_SNDTIMEO = 0x1105; constexpr int NET_SO_SCE_RCVTIMEO = 0x1106; +constexpr int NET_SO_TYPE = 0x1008; +constexpr int NET_SO_ACCEPTCONN = 0x2; constexpr int NET_MSG_PEEK = 0x2; constexpr int NET_MSG_TRUNC = 0x10; constexpr int NET_MSG_DONTWAIT = 0x80; @@ -1536,6 +1538,12 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint socket = it->second; } if (optval == nullptr || optlen == nullptr) return fail(NET_EINVAL); + if (level == NET_SOL_SOCKET && (optname == NET_SO_TYPE || optname == NET_SO_ACCEPTCONN)) { + const int value = optname == NET_SO_TYPE ? socket.type : socket.listening ? NET_SO_ACCEPTCONN : 0; + *optlen = std::min(*optlen, static_cast(sizeof(value))); + std::memcpy(optval, &value, *optlen); + return 0; + } if (level == NET_SOL_SOCKET && (optname == NET_SO_RCVTIMEO || optname == NET_SO_SNDTIMEO)) { const int microseconds = optname == NET_SO_RCVTIMEO ? socket.native->receiveTimeout.load() : socket.native->sendTimeout.load(); const KernelTimeval timeout{microseconds / 1000000, microseconds % 1000000}; diff --git a/core/libs/tests/GuestNetSocketState.cpp b/core/libs/tests/GuestNetSocketState.cpp new file mode 100644 index 0000000000..e35111a46d --- /dev/null +++ b/core/libs/tests/GuestNetSocketState.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" { +int APS5_VABI sceNetInit_nid_postfix(); +int APS5_VABI sceNetSocket(const char*, int, int, int); +int APS5_VABI sceNetSocketClose(int); +int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); +int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); +int APS5_VABI sceNetListen(int, int); +int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); +int APS5_VABI sceNetAccept(int, void*, std::uint32_t*); +int APS5_VABI sceNetGetsockopt(int, int, int, void*, std::uint32_t*); +int* APS5_VABI sceNetErrnoLoc(); +} + +static void Require(bool condition) { + if (!condition) std::abort(); +} + +static void CheckOption(int socket, int option, int expected) { + for (const std::uint32_t capacity : {0u, 1u, 3u, 4u, 8u}) { + std::array bytes; + bytes.fill(0xa5); + std::uint32_t size = capacity; + Require(sceNetGetsockopt(socket, 0xffff, option, bytes.data(), &size) == 0); + Require(size == std::min(capacity, 4u)); + Require(std::memcmp(bytes.data(), &expected, size) == 0); + Require(std::all_of(bytes.begin() + size, bytes.end(), [](auto byte) { return byte == 0xa5; })); + } +} + +int main() { + Require(sceNetInit_nid_postfix() == 0); + for (const int family : {2, 28}) { + const int datagram = sceNetSocket("state-datagram", family, 2, 17); + const int listener = sceNetSocket("state-listener", family, 1, 6); + const int client = sceNetSocket("state-client", family, 1, 6); + Require(datagram >= 0 && listener >= 0 && client >= 0); + CheckOption(datagram, 0x1008, 2); + CheckOption(datagram, 2, 0); + CheckOption(listener, 0x1008, 1); + CheckOption(listener, 2, 0); + std::array address{}; + const std::uint32_t addressSize = family == 2 ? 16 : 28; + address[0] = addressSize; + address[1] = family; + if (family == 2) { + address[4] = 127; + address[7] = 1; + } else { + address[23] = 1; + } + Require(sceNetBind_nid_postfix(listener, address.data(), addressSize) == 0); + Require(sceNetListen(listener, 1) == 0); + CheckOption(listener, 2, 2); + CheckOption(client, 2, 0); + std::uint32_t size = addressSize; + Require(sceNetGetsockname(listener, address.data(), &size) == 0); + Require(sceNetConnect(client, address.data(), size) == 0); + const int accepted = sceNetAccept(listener, nullptr, nullptr); + Require(accepted >= 0); + CheckOption(listener, 2, 2); + CheckOption(client, 0x1008, 1); + CheckOption(client, 2, 0); + CheckOption(accepted, 0x1008, 1); + CheckOption(accepted, 2, 0); + int value = 0; + size = sizeof(value); + for (const int option : {2, 0x1008}) { + Require(sceNetGetsockopt(listener, 0xffff, option, nullptr, &size) == static_cast(0x80410116u)); + Require(*sceNetErrnoLoc() == 22); + Require(sceNetGetsockopt(listener, 0xffff, option, &value, nullptr) == static_cast(0x80410116u)); + Require(*sceNetErrnoLoc() == 22); + } + for (const int socket : {accepted, client, listener, datagram}) Require(sceNetSocketClose(socket) == 0); + for (const int option : {2, 0x1008}) { + Require(sceNetGetsockopt(listener, 0xffff, option, &value, &size) == static_cast(0x80410109u)); + Require(*sceNetErrnoLoc() == 9); + } + } +} diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index f10d1eb873..4502ef809d 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -35,6 +35,7 @@ - [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. +- [SO_TYPE and SO_ACCEPTCONN](../../../core/libs/prx/libSceNet/Export.cpp) read the guest socket type and listening flag, with bit-valued listening results and truncated output following FreeBSD 11.4's [sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2667-L2693), without PS5 verification. Null pointers retain the existing libSceNet `EINVAL` result instead of FreeBSD's pointer handling. - [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. - [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 - [MSG_DONTWAIT](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - console ordering between competing readers and writers is unmeasured. The per-call behavior follows FreeBSD 11.4's `soreceive_generic` and `sosend_generic` (`sys/kern/uipc_socket.c`); `0x80` is FreeBSD's flag value (`sys/sys/socket.h`). On Windows native sockets stay non-blocking. `MSG_DONTWAIT` and guest `SO_NBIO` return one host transfer's result. Blocking stream sends queue the whole buffer with one deadline and return progress after a timeout or host error. SSL uses this path through the native owner, retaining the socket, its mode and timeouts. Readiness waits poll at most every 10 ms for local close, so idle blocked operations wake periodically. From d00aba22b434fa74cb6f367dad0c1f8903190546 Mon Sep 17 00:00:00 2001 From: Queuze <58829992+lucaslima430@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:27:21 -0300 Subject: [PATCH 2/2] test(net): use canonical public declarations --- core/libs/tests/GuestNetSocketState.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/core/libs/tests/GuestNetSocketState.cpp b/core/libs/tests/GuestNetSocketState.cpp index e35111a46d..2101d0c167 100644 --- a/core/libs/tests/GuestNetSocketState.cpp +++ b/core/libs/tests/GuestNetSocketState.cpp @@ -3,20 +3,8 @@ #include #include #include -#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNet/libSceNet.hpp" -extern "C" { -int APS5_VABI sceNetInit_nid_postfix(); -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetSocketClose(int); -int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); -int APS5_VABI sceNetListen(int, int); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetAccept(int, void*, std::uint32_t*); -int APS5_VABI sceNetGetsockopt(int, int, int, void*, std::uint32_t*); -int* APS5_VABI sceNetErrnoLoc(); -} static void Require(bool condition) { if (!condition) std::abort();