Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/libs/prx/libSceNet/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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)
Expand Down
17 changes: 14 additions & 3 deletions core/libs/prx/libSceNet/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1463,11 +1463,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;
Expand Down Expand Up @@ -1499,8 +1510,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;
}

Expand Down
66 changes: 66 additions & 0 deletions core/libs/tests/GuestNetBooleanReadback.cpp
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions docs/dev/debt/libSceNet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- [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.
- [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.
Expand Down
Loading