diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index f900648cf2..5c8043e925 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(guest_net_protocol_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetProtocol.cpp) target_include_directories(guest_net_protocol_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_net_protocol_tests PRIVATE libSceNet libkernel libc) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 4c4efc2636..5f54a71073 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -153,6 +153,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; @@ -1516,6 +1518,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_PROTOCOL) { if (socket.type == NET_SOCK_RAW) NotImplemented_nid_no_patch(__func__); *optlen = std::min(*optlen, static_cast(sizeof(socket.protocol))); diff --git a/core/libs/tests/GuestNetSocketState.cpp b/core/libs/tests/GuestNetSocketState.cpp new file mode 100644 index 0000000000..2101d0c167 --- /dev/null +++ b/core/libs/tests/GuestNetSocketState.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" + + +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 5e541df47d..c55f082018 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -43,6 +43,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. - [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. +- [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. - [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