mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
Merge pull request #9543 from Queuse/codex/net-oob-inline
feat(libSceNet): receive urgent TCP data inline
This commit is contained in:
4 files changed
+170
-1
No files matched your search
@@ -116,6 +116,17 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_refused_connect PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
|
||||
endif()
|
||||
add_test_executable(guest_net_oob_inline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetOobInline.cpp)
|
||||
target_include_directories(guest_net_oob_inline_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_net_oob_inline_tests PRIVATE libSceNet libkernel libc)
|
||||
configure_windows_unwind(guest_net_oob_inline_tests)
|
||||
add_test(NAME guest_net_oob_inline COMMAND guest_net_oob_inline_tests)
|
||||
set_tests_properties(guest_net_oob_inline PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
target_link_libraries(guest_net_oob_inline_tests PRIVATE ws2_32)
|
||||
set_tests_properties(guest_net_oob_inline PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test_executable(guest_socket_host_write_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketHostWrite.cpp)
|
||||
target_include_directories(guest_socket_host_write_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
|
||||
@@ -149,6 +149,7 @@ constexpr int NET_SO_REUSEADDR = 0x4;
|
||||
constexpr int NET_SO_KEEPALIVE = 0x8;
|
||||
constexpr int NET_SO_BROADCAST = 0x20;
|
||||
constexpr int NET_SO_ONESBCAST = 0x10000;
|
||||
constexpr int NET_SO_OOBINLINE = 0x100;
|
||||
constexpr int NET_SO_SNDBUF = 0x1001;
|
||||
constexpr int NET_SO_RCVBUF = 0x1002;
|
||||
constexpr int NET_SO_SCE_SNDTIMEO = 0x1105;
|
||||
@@ -272,6 +273,7 @@ struct Sock {
|
||||
bool aborted = false;
|
||||
bool sending_shut = false;
|
||||
bool read_shut = false;
|
||||
bool oobInline = false;
|
||||
int reported_error = 0;
|
||||
int snd_buffer = 0;
|
||||
int rcv_buffer = 0;
|
||||
@@ -1472,7 +1474,12 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval
|
||||
if (!optval || optlen < sizeof(int)) return fail(NET_EINVAL);
|
||||
int value = 0;
|
||||
std::memcpy(&value, optval, sizeof(value));
|
||||
if (optname == NET_SO_NBIO) {
|
||||
if (optname == NET_SO_OOBINLINE) {
|
||||
const int enabled = value != 0;
|
||||
if (socket.type == NET_SOCK_STREAM &&
|
||||
::setsockopt(socket.native->value, SOL_SOCKET, SO_OOBINLINE, reinterpret_cast<const char*>(&enabled), sizeof(enabled)) != 0)
|
||||
return fail(native_error());
|
||||
} else if (optname == NET_SO_NBIO) {
|
||||
#ifndef _WIN32
|
||||
const int old_flags = fcntl(socket.native->value, F_GETFL, 0);
|
||||
if (old_flags < 0 || fcntl(socket.native->value, F_SETFL,
|
||||
@@ -1498,6 +1505,7 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval
|
||||
std::lock_guard lk(g_mutex);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
if (optname == NET_SO_OOBINLINE && it->second.native == socket.native) it->second.oobInline = value != 0;
|
||||
if (optname == NET_SO_NBIO) it->second.native->nonblock = value != 0;
|
||||
if (optname == NET_SO_RCVTIMEO) it->second.native->receiveTimeout = value;
|
||||
if (optname == NET_SO_SNDTIMEO) it->second.native->sendTimeout = value;
|
||||
@@ -1524,6 +1532,20 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint
|
||||
std::memcpy(optval, &value, *optlen);
|
||||
return 0;
|
||||
}
|
||||
if (level == NET_SOL_SOCKET && optname == NET_SO_OOBINLINE) {
|
||||
int enabled = 0;
|
||||
if (socket.type == NET_SOCK_STREAM) {
|
||||
NativeLength size = sizeof(enabled);
|
||||
if (::getsockopt(socket.native->value, SOL_SOCKET, SO_OOBINLINE, reinterpret_cast<char*>(&enabled), &size) != 0)
|
||||
return fail(native_error());
|
||||
} else {
|
||||
enabled = socket.oobInline;
|
||||
}
|
||||
const int value = enabled != 0 ? NET_SO_OOBINLINE : 0;
|
||||
*optlen = std::min(*optlen, static_cast<std::uint32_t>(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<std::uint32_t>(sizeof(socket.protocol)));
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckFlag(int socket, int expected) {
|
||||
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;
|
||||
Require(sceNetGetsockopt(socket, 0xffff, 0x100, bytes.data(), &size) == 0 && 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; }));
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckInheritance(int family, int beforeListen, bool changeAfterListen) {
|
||||
std::fprintf(stderr, "inheritance family=%d beforeListen=%d changeAfterListen=%d\n",
|
||||
family, beforeListen, changeAfterListen);
|
||||
const int listener = sceNetSocket("inheritance-listener", family, 1, 6);
|
||||
const int client = sceNetSocket("inheritance-client", family, 1, 6);
|
||||
Require(listener >= 0 && client >= 0);
|
||||
Require(sceNetSetsockopt(listener, 0xffff, 0x100, &beforeListen, sizeof(beforeListen)) == 0);
|
||||
CheckFlag(listener, beforeListen != 0 ? 256 : 0);
|
||||
std::array<std::uint8_t, 28> address{};
|
||||
std::uint32_t size = family == 2 ? 16 : 28;
|
||||
address[0] = size;
|
||||
address[1] = family;
|
||||
if (family == 2) { address[4] = 127; address[7] = 1; }
|
||||
else address[23] = 1;
|
||||
Require(sceNetBind_nid_postfix(listener, address.data(), size) == 0);
|
||||
Require(sceNetListen(listener, 1) == 0);
|
||||
const int setting = changeAfterListen ? 1 - beforeListen : beforeListen;
|
||||
if (changeAfterListen)
|
||||
Require(sceNetSetsockopt(listener, 0xffff, 0x100, &setting, sizeof(setting)) == 0);
|
||||
CheckFlag(listener, setting != 0 ? 256 : 0);
|
||||
Require(sceNetGetsockname(listener, address.data(), &size) == 0);
|
||||
Require(sceNetConnect(client, address.data(), size) == 0);
|
||||
const int accepted = sceNetAccept(listener, nullptr, nullptr);
|
||||
Require(accepted >= 0);
|
||||
CheckFlag(accepted, setting != 0 ? 256 : 0);
|
||||
CheckFlag(client, 0);
|
||||
const int opposite = 1 - setting;
|
||||
Require(sceNetSetsockopt(listener, 0xffff, 0x100, &opposite, sizeof(opposite)) == 0);
|
||||
CheckFlag(listener, opposite != 0 ? 256 : 0);
|
||||
CheckFlag(accepted, setting != 0 ? 256 : 0);
|
||||
Require(sceNetSetsockopt(listener, 0xffff, 0x100, &setting, sizeof(setting)) == 0);
|
||||
Require(sceNetSetsockopt(accepted, 0xffff, 0x100, &opposite, sizeof(opposite)) == 0);
|
||||
CheckFlag(accepted, opposite != 0 ? 256 : 0);
|
||||
CheckFlag(listener, setting != 0 ? 256 : 0);
|
||||
CheckFlag(client, 0);
|
||||
for (const int socket : {accepted, client, listener}) Require(sceNetSocketClose(socket) == 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
Require(sceNetInit_nid_postfix() == 0);
|
||||
for (const int family : {2, 28}) {
|
||||
for (const int beforeListen : {0, 1}) {
|
||||
for (const bool changeAfterListen : {false, true})
|
||||
CheckInheritance(family, beforeListen, changeAfterListen);
|
||||
}
|
||||
const int datagram = sceNetSocket("inline-datagram", family, 2, 17);
|
||||
const int listener = sceNetSocket("inline-listener", family, 1, 6);
|
||||
const int client = sceNetSocket("inline-client", family, 1, 6);
|
||||
Require(datagram >= 0 && listener >= 0 && client >= 0);
|
||||
for (const int socket : {datagram, listener}) {
|
||||
CheckFlag(socket, 0);
|
||||
for (const int setting : {1, -1, 0, 256, 0, 1}) {
|
||||
Require(sceNetSetsockopt(socket, 0xffff, 0x100, &setting, sizeof(setting)) == 0);
|
||||
CheckFlag(socket, setting != 0 ? 256 : 0);
|
||||
}
|
||||
const int on = 1;
|
||||
Require(sceNetSetsockopt(socket, 0xffff, 0x100, &on, 3) == static_cast<int>(0x80410116u));
|
||||
Require(*sceNetErrnoLoc() == 22);
|
||||
Require(sceNetSetsockopt(socket, 0xffff, 0x100, nullptr, 4) == static_cast<int>(0x80410116u));
|
||||
}
|
||||
CheckFlag(client, 0);
|
||||
std::array<std::uint8_t, 28> address{};
|
||||
std::uint32_t size = family == 2 ? 16 : 28;
|
||||
address[0] = size;
|
||||
address[1] = family;
|
||||
if (family == 2) { address[4] = 127; address[7] = 1; }
|
||||
else address[23] = 1;
|
||||
Require(sceNetBind_nid_postfix(listener, address.data(), size) == 0);
|
||||
Require(sceNetListen(listener, 1) == 0);
|
||||
Require(sceNetGetsockname(listener, address.data(), &size) == 0);
|
||||
Require(sceNetConnect(client, address.data(), size) == 0);
|
||||
const int accepted = sceNetAccept(listener, nullptr, nullptr);
|
||||
Require(accepted >= 0);
|
||||
CheckFlag(accepted, 256);
|
||||
const int timeout = 500000;
|
||||
Require(sceNetSetsockopt(accepted, 0xffff, 0x1106, &timeout, sizeof(timeout)) == 0);
|
||||
std::shared_ptr<void> owner;
|
||||
std::uintptr_t native = 0;
|
||||
Require(KernelFindSocketNative_nid_no_patch(client, &owner, &native));
|
||||
Require(::send(native, "A", 1, 0) == 1);
|
||||
Require(::send(native, "!", 1, MSG_OOB) == 1);
|
||||
Require(::send(native, "B", 1, 0) == 1);
|
||||
std::array<char, 3> received{};
|
||||
std::size_t count = 0;
|
||||
while (count < received.size()) {
|
||||
const auto result = sceNetRecv(accepted, received.data() + count, received.size() - count, 0);
|
||||
Require(result > 0);
|
||||
count += result;
|
||||
}
|
||||
Require(std::memcmp(received.data(), "A!B", 3) == 0);
|
||||
const int off = 0;
|
||||
Require(sceNetSetsockopt(listener, 0xffff, 0x100, &off, sizeof(off)) == 0);
|
||||
CheckFlag(listener, 0);
|
||||
CheckFlag(accepted, 256);
|
||||
for (const int socket : {accepted, client, listener, datagram}) Require(sceNetSocketClose(socket) == 0);
|
||||
int value = 0;
|
||||
size = sizeof(value);
|
||||
Require(sceNetGetsockopt(listener, 0xffff, 0x100, &value, &size) == static_cast<int>(0x80410109u));
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
- [sceNetConnect to AF_UNSPEC](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux and on Windows a socket bound to an explicit port keeps its local address as well (`127.0.0.1:<port>` stays `127.0.0.1:<port>`), because a bound socket cannot be bound again, and `sceNetGetsockname` reports that host address. On Windows a `sendto` on such a host socket to a non-loopback address fails with Winsock error 10051 (measured on the host socket, not through `sceNetSendto`), where FreeBSD sends it from the wildcard address. One bound to port 0 is bound again to the wildcard address with the port it had
|
||||
- [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
|
||||
- [SO_OOBINLINE](../../../core/libs/prx/libSceNet/Export.cpp) uses native TCP urgent-data handling; its urgent-pointer and signalling behavior has not been compared with PS5. The flag bit (0x100), non-stream flag storage and truncated readback follow FreeBSD 11.4 [sosetopt/sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c). Null pointers retain the existing libSceNet `EINVAL` result.
|
||||
- [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.
|
||||
|
||||
Reference in new issue
Block a user