Merge pull request #9542 from Queuse/codex/net-boolean-readback

feat(libSceNet): read reuse and broadcast flags
This commit is contained in:
Adrià authored and GitHub committed 2026-10-26 12:58:12 +01:00
commit 0c6a6be7fe
4 files changed
+92 -3

No files matched your search

+11
View File
@@ -47,6 +47,17 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
endif()
endforeach()
add_test_executable(guest_net_boolean_readback_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetBooleanReadback.cpp)
target_include_directories(guest_net_boolean_readback_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_net_boolean_readback_tests PRIVATE libSceNet libkernel libc)
configure_windows_unwind(guest_net_boolean_readback_tests)
add_test(NAME guest_net_boolean_readback COMMAND guest_net_boolean_readback_tests)
set_tests_properties(guest_net_boolean_readback PROPERTIES TIMEOUT 20)
if(WIN32)
target_link_libraries(guest_net_boolean_readback_tests PRIVATE ws2_32)
set_tests_properties(guest_net_boolean_readback PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
endif()
add_test_executable(net_blocking_send_tests ${LIBS_INCLUDE_DIR}/tests/NetBlockingSend.cpp)
target_include_directories(net_blocking_send_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(net_blocking_send_tests PRIVATE libkernel libc libSceNetCtl)
+14 -3
View File
@@ -1560,11 +1560,22 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint
std::memcpy(optval, &value, *optlen);
return 0;
}
if (*optlen < sizeof(int)) return fail(NET_EINVAL);
if (*optlen < sizeof(int) &&
!(level == NET_SOL_SOCKET && (optname == NET_SO_REUSEADDR || optname == NET_SO_BROADCAST))) return fail(NET_EINVAL);
if (level == NET_IPPROTO_TCP) return fail(NET_ENOPROTOOPT);
if (level != NET_SOL_SOCKET) return fail(NET_EOPNOTSUPP);
int value = 0;
switch (timeout_option(optname)) {
case NET_SO_REUSEADDR:
case NET_SO_BROADCAST: {
int enabled = 0;
NativeLength size = sizeof(enabled);
const int nativeOption = optname == NET_SO_REUSEADDR ? SO_REUSEADDR : SO_BROADCAST;
if (::getsockopt(socket.native->value, SOL_SOCKET, nativeOption, reinterpret_cast<char*>(&enabled), &size) != 0)
return fail(native_error());
value = enabled != 0 ? optname : 0;
break;
}
case NET_SO_NBIO: value = socket.native->nonblock ? 1 : 0; break;
case NET_SO_RCVTIMEO: value = socket.native->receiveTimeout; break;
case NET_SO_SNDTIMEO: value = socket.native->sendTimeout; break;
@@ -1596,8 +1607,8 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint
}
default: return fail(NET_EOPNOTSUPP);
}
std::memcpy(optval, &value, sizeof(value));
*optlen = sizeof(value);
*optlen = std::min(*optlen, static_cast<std::uint32_t>(sizeof(value)));
std::memcpy(optval, &value, *optlen);
return 0;
}
@@ -0,0 +1,66 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
#include "prx/libSceNet/libSceNet.hpp"
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
static void Require(bool condition, std::source_location location = std::source_location::current()) {
if (!condition) {
std::fprintf(stderr, "check failed at line %u\n", location.line());
std::abort();
}
}
int main() {
Require(sceNetInit_nid_postfix() == 0);
for (const int family : {2, 28}) {
for (const int type : {1, 2}) {
const int socket = sceNetSocket("boolean-readback", family, type, type == 1 ? 6 : 17);
Require(socket >= 0);
std::shared_ptr<void> owner;
std::uintptr_t native = 0;
Require(KernelFindSocketNative_nid_no_patch(socket, &owner, &native));
for (const int option : {4, 32}) {
if (option == 32 && (family != 2 || type != 2)) continue;
for (const int setting : {1, -1, 0, 4, 0}) {
Require(sceNetSetsockopt(socket, 0xffff, option, &setting, sizeof(setting)) == 0);
for (const std::uint32_t capacity : {0u, 1u, 3u, 4u, 8u}) {
std::array<std::uint8_t, 8> bytes;
bytes.fill(0xa5);
std::uint32_t size = capacity;
const int expected = setting != 0 ? option : 0;
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; }));
}
}
const int enabled = 1;
Require(::setsockopt(native, SOL_SOCKET, option == 4 ? SO_REUSEADDR : SO_BROADCAST,
reinterpret_cast<const char*>(&enabled), sizeof(enabled)) == 0);
int value = 0;
std::uint32_t size = sizeof(value);
Require(sceNetGetsockopt(socket, 0xffff, option, &value, &size) == 0 && value == option);
Require(sceNetGetsockopt(socket, 0xffff, option, nullptr, &size) == static_cast<int>(0x80410116u));
Require(*sceNetErrnoLoc() == 22);
Require(sceNetGetsockopt(socket, 0xffff, option, &value, nullptr) == static_cast<int>(0x80410116u));
}
Require(sceNetSocketClose(socket) == 0);
int value = 0;
std::uint32_t size = sizeof(value);
Require(sceNetGetsockopt(socket, 0xffff, 4, &value, &size) == static_cast<int>(0x80410109u));
Require(*sceNetErrnoLoc() == 9);
}
}
}
+1
View File
@@ -40,6 +40,7 @@
- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. Windows sockets stay non-blocking and the library waits for readiness until the stored timeout. Blocking stream sends return the queued byte count on timeout, or `EAGAIN` when no bytes were queued.
- [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take `SO_SNDTIMEO` and `SO_RCVTIMEO` (0x1005, 0x1006) as a 16-byte `struct timeval`, as FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/uipc_socket.c#L2657-L2690) do, and Sony's 0x1105 and 0x1106 as an `int` of microseconds. A [console log of ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/issues/49) (firmware 12.20) shows receives timing out every 100 ms after it gave 0x1006 a `{0, 100000}` timeval. The errors for a value shorter than a timeval (`EINVAL`) or out of range (`EDOM`), and `sceNetGetsockopt` cutting the timeval to the caller's length, follow FreeBSD's `sooptcopyin` and `sooptcopyout` and are not measured on a console
- [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
- [SO_REUSEADDR and SO_BROADCAST readback](../../../core/libs/prx/libSceNet/Export.cpp) follows FreeBSD 11.4's [sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2674-L2688): enabled options return their flag bit, and output is truncated to the supplied length. PS5 results are unverified. Values reflect the native socket; host restrictions on setting the options are unchanged. Null pointers retain libSceNet's existing `EINVAL` result. Windows passes `SO_REUSEADDR` directly to Winsock, which permits a second bind to an occupied address and port under its [reuse rules](https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse), unlike FreeBSD's separate `SO_REUSEPORT` requirement for duplicate unicast binds. Guest `SO_REUSEPORT` (`0x200`) still fails with `EOPNOTSUPP`.
- [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.
- [SO_KEEPALIVE](../../../core/libs/prx/libSceNet/Export.cpp) uses the host TCP keepalive timers and probe count, which can differ from the console. The boolean flag, bit-valued readback and truncated output follow FreeBSD 11.4's [sosetopt and sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c), without PS5 hardware verification. Non-stream sockets retain the flag without sending probes, as FreeBSD does. Null pointers retain the existing libSceNet `EINVAL` result instead of FreeBSD's `EFAULT`.
- [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) - `TCP_MAXSEG`, `TCP_NOPUSH`, `TCP_KEEPIDLE` and other TCP options remain unsupported (`ENOPROTOOPT`). `TCP_NODELAY` sets on refused, reset or `TIME_WAIT` sockets can succeed through the host where FreeBSD 11.4's [tcp_ctloutput](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) returns `ECONNRESET`. A null setter value returns `EINVAL` rather than `kern_setsockopt`'s `EFAULT`. The non-stream `IPPROTO_TCP` error (`EINVAL`) and `TF_NODELAY` flag readback follow FreeBSD, not PS5 measurements.