Skip to content
Open
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
68 changes: 53 additions & 15 deletions core/libs/prx/libSceNet/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
Expand Down Expand Up @@ -1673,25 +1674,46 @@ struct NetSockInfo {
static_assert(sizeof(NetSockInfo) == 0xA0 && offsetof(NetSockInfo, s) == 0x24 && offsetof(NetSockInfo, local_adr) == 0x34 &&
offsetof(NetSockInfo, local_port) == 0x3C && offsetof(NetSockInfo, recv_buffer_size) == 0x64);

static int sock_buffer_size(const Sock& socket, bool send, std::int32_t& value) {
struct SockInfoSource {
std::shared_ptr<void> owner;
NativeSocket native;
int family;
int type;
bool nonblock;
int rcv_buffer;
int snd_buffer;
};

static bool find_sock_info_source(int s, SockInfoSource& source) {
{
std::lock_guard lk(g_mutex);
const auto it = g_socks.find(s);
if (it != g_socks.end()) {
const Sock& socket = it->second;
source = {socket.native, socket.native->value, socket.family, socket.type, socket.native->nonblock, socket.rcv_buffer, socket.snd_buffer};
return true;
}
}
KernelSocketPoll::Description kernel;
if (!KernelDescribeSocket_nid_no_patch(s, &kernel)) return false;
source = {kernel.owner, static_cast<NativeSocket>(kernel.native), kernel.family, kernel.type, kernel.nonblocking, 0, 0};
return true;
}

static int sock_buffer_size(const SockInfoSource& socket, bool send, std::int32_t& value) {
value = send ? socket.snd_buffer : socket.rcv_buffer;
if (value != 0) return 0;
NativeLength size = sizeof(value);
return ::getsockopt(socket.native->value, SOL_SOCKET, send ? SO_SNDBUF : SO_RCVBUF, reinterpret_cast<char*>(&value), &size) == 0 ? 0 : native_error();
return ::getsockopt(socket.native, SOL_SOCKET, send ? SO_SNDBUF : SO_RCVBUF, reinterpret_cast<char*>(&value), &size) == 0 ? 0 : native_error();
}

static int fill_sock_info(int s, NetSockInfo& record) {
Sock socket;
{
std::lock_guard lk(g_mutex);
const auto it = g_socks.find(s);
if (it == g_socks.end()) return NET_EBADF;
socket = it->second;
}
SockInfoSource socket;
if (!find_sock_info_source(s, socket)) return NET_EBADF;
if (socket.family != NET_AF_INET) return NET_EAFNOSUPPORT;
sockaddr_in local{};
NativeLength length = sizeof(local);
if (::getsockname(socket.native->value, reinterpret_cast<sockaddr*>(&local), &length) != 0) {
if (::getsockname(socket.native, reinterpret_cast<sockaddr*>(&local), &length) != 0) {
#ifdef _WIN32
if (WSAGetLastError() != WSAEINVAL) return native_error();
#else
Expand All @@ -1700,7 +1722,7 @@ static int fill_sock_info(int s, NetSockInfo& record) {
}
sockaddr_in remote{};
length = sizeof(remote);
if (::getpeername(socket.native->value, reinterpret_cast<sockaddr*>(&remote), &length) != 0) {
if (::getpeername(socket.native, reinterpret_cast<sockaddr*>(&remote), &length) != 0) {
const int error = native_error();
if (error != NET_ENOTCONN) return error;
}
Expand All @@ -1713,18 +1735,34 @@ static int fill_sock_info(int s, NetSockInfo& record) {
record.remote_adr = remote.sin_addr.s_addr;
record.local_port = local.sin_port;
record.remote_port = remote.sin_port;
record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0);
record.flags = NET_SOCKINFO_F_SELF | (socket.nonblock ? NET_SOCKINFO_F_NONBLOCK : 0);
#ifdef _WIN32
u_long pending = 0;
if (::ioctlsocket(socket.native, FIONREAD, &pending) == 0) record.recv_queue_length = static_cast<std::int32_t>(pending);
#else
int pending = 0;
if (::ioctl(socket.native, FIONREAD, &pending) == 0) record.recv_queue_length = pending;
#endif
return 0;
}

int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) {
if ((flags & ~NET_SOCKINFO_TIME_WAIT) != 0) return fail(NET_EINVAL);
std::vector<int> listed;
if (s < 0) {
std::lock_guard lk(g_mutex);
for (const auto& [descriptor, socket] : g_socks) {
if (socket.family == NET_AF_INET) listed.push_back(descriptor);
{
std::lock_guard lk(g_mutex);
for (const auto& [descriptor, socket] : g_socks) {
if (socket.family == NET_AF_INET) listed.push_back(descriptor);
}
}
std::vector<int> kernel;
KernelListSockets_nid_no_patch(&kernel);
for (const int descriptor : kernel) {
KernelSocketPoll::Description description;
if (KernelDescribeSocket_nid_no_patch(descriptor, &description) && description.family == NET_AF_INET) listed.push_back(descriptor);
}
std::sort(listed.begin(), listed.end());
}
if (!info) return s < 0 ? static_cast<int>(listed.size()) : fail(NET_EINVAL);
if (n < 1) return fail(NET_EINVAL);
Expand Down
11 changes: 11 additions & 0 deletions core/libs/prx/libkernel/Socket/include/SocketPoll.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <memory>
#include <vector>

namespace KernelSocketPoll {
constexpr short Readable = 0x1;
Expand All @@ -25,6 +26,14 @@ struct NativeOwner {
};

using NativeLookup = bool (*)(int descriptor, std::shared_ptr<void>* owner, std::uintptr_t* native);

struct Description {
std::shared_ptr<void> owner;
std::uintptr_t native;
int family;
int type;
bool nonblocking;
};
}

extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller);
Expand All @@ -39,3 +48,5 @@ extern "C" bool KernelFindSocketNative_nid_no_patch(int descriptor, std::shared_
extern "C" bool KernelLookupSocketNative_nid_no_patch(int descriptor, std::shared_ptr<void>* owner, std::uintptr_t* native);
extern "C" int KernelReserveSocketNumber_nid_no_patch(std::uintptr_t native);
extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number);
extern "C" bool KernelDescribeSocket_nid_no_patch(int descriptor, KernelSocketPoll::Description* description);
extern "C" void KernelListSockets_nid_no_patch(std::vector<int>* descriptors);
13 changes: 13 additions & 0 deletions core/libs/prx/libkernel/Socket/src/Udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,19 @@ extern "C" bool KernelLookupSocketNative_nid_no_patch(int descriptor, std::share
return KernelFindSocketNative_nid_no_patch(descriptor, owner, native);
}

extern "C" bool KernelDescribeSocket_nid_no_patch(int descriptor, KernelSocketPoll::Description* description) {
const auto socket = Find(descriptor);
if (!socket) return false;
std::lock_guard lock(socket->modeMutex);
*description = {socket, static_cast<std::uintptr_t>(socket->value), socket->family, socket->type, socket->options.nonblocking};
return true;
}

extern "C" void KernelListSockets_nid_no_patch(std::vector<int>* descriptors) {
std::lock_guard lock(socketsMutex);
for (const auto& [descriptor, socket] : sockets) descriptors->push_back(descriptor);
}

extern "C" {
int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
const auto type = File::FcntlArgumentOf(command);
Expand Down
38 changes: 37 additions & 1 deletion core/libs/tests/GuestSceNet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI close_nid_postfix(int);
bool GuestSocketIsOpen_nid_no_patch(int);
int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t);
Expand Down Expand Up @@ -1519,6 +1520,20 @@ static void CheckSockInfo() {
Require(sceNetGetsockname(client, clientAddress.data(), &size) == 0 && size == 16);
RequireSockInfo(client, 1, 1, clientAddress, listening);
RequireSockInfo(accepted, 1, 1, listening, clientAddress);
const char queued[] = "queued";
constexpr auto queuedLength = static_cast<std::int32_t>(sizeof(queued));
Require(sceNetSend(client, queued, sizeof(queued), 0) == queuedLength);
std::array<std::uint8_t, 0xa0> pending{};
std::int32_t pendingLength = 0;
for (int attempt = 0; attempt < 500 && pendingLength != queuedLength; ++attempt) {
Require(sceNetGetSockInfo(accepted, pending.data(), 1, 0) == 1);
std::memcpy(&pendingLength, pending.data() + 0x2c, sizeof(pendingLength));
if (pendingLength != queuedLength) std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
Require(pendingLength == queuedLength);
char drained[sizeof(queued)] = {};
Require(sceNetRecv(accepted, drained, sizeof(drained), 0) == queuedLength && std::memcmp(drained, queued, sizeof(queued)) == 0);
RequireSockInfo(accepted, 1, 1, listening, clientAddress);
Require(sceNetConnect(datagram, listening.data(), listening.size()) == 0);
RequireSockInfo(datagram, 2, 1, datagramAddress, listening);
const int nonblocking = 1;
Expand All @@ -1540,14 +1555,35 @@ static void CheckSockInfo() {
const int inet6 = sceNetSocket("sockinfo-inet6", 28, 2, 17);
Require(inet6 >= 0 ? Failed(sceNetGetSockInfo(inet6, record.data(), 1, 0), 47) : *sceNetErrnoLoc() == 47);
Require(std::all_of(record.begin(), record.end(), [](auto byte) { return byte == 0xa5; }));
const int kernel = socket_nid_postfix(2, 2, 0);
std::array<std::uint8_t, 16> kernelAddress{16, 2, 0, 0, 127, 0, 0, 1};
Require(kernel >= 0 && bind_nid_postfix(kernel, kernelAddress.data(), kernelAddress.size()) == 0);
size = kernelAddress.size();
Require(getsockname_nid_postfix(kernel, kernelAddress.data(), &size) == 0 && size == 16);
std::array<std::uint8_t, 0xa0> kernelRecord;
kernelRecord.fill(0xa5);
Require(sceNetGetSockInfo(kernel, kernelRecord.data(), 1, 0) == 1);
std::array<std::uint8_t, 0xa0> kernelExpected{};
std::memcpy(kernelExpected.data() + 0x24, &kernel, sizeof(kernel));
kernelExpected[0x28] = 2;
std::memcpy(kernelExpected.data() + 0x34, kernelAddress.data() + 4, 4);
std::memcpy(kernelExpected.data() + 0x3c, kernelAddress.data() + 2, 2);
kernelExpected[0x48] = 1;
std::memcpy(kernelExpected.data() + 0x64, kernelRecord.data() + 0x64, 8);
Require(std::equal(kernelExpected.begin(), kernelExpected.end(), kernelRecord.begin()));
std::int32_t kernelBuffers[2] = {};
std::memcpy(kernelBuffers, kernelRecord.data() + 0x64, sizeof(kernelBuffers));
Require(kernelBuffers[0] > 0 && kernelBuffers[1] > 0);
std::vector<int> expected = earlier;
expected.insert(expected.end(), {datagram, listener, client, accepted});
expected.insert(expected.end(), {datagram, listener, client, accepted, kernel});
std::sort(expected.begin(), expected.end());
Require(RequireSockInfoList(0) == expected && RequireSockInfoList(0x40) == expected);
Require(sceNetGetSockInfo(datagram, record.data(), 1, 0x40) == 1 && std::equal(configured.begin(), configured.end(), record.begin()));
if (inet6 >= 0) Require(sceNetSocketClose(inet6) == 0);
for (const int descriptor : {accepted, client, listener, datagram}) Require(sceNetSocketClose(descriptor) == 0);
Require(Failed(sceNetGetSockInfo(datagram, record.data(), 1, 0), 9));
Require(close_nid_postfix(kernel) == 0);
Require(Failed(sceNetGetSockInfo(kernel, record.data(), 1, 0), 9));
Require(RequireSockInfoList(0) == earlier);
}

Expand Down
4 changes: 3 additions & 1 deletion docs/dev/debt/libSceNet.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
- [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.
- [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.
- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, send queue length, 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.
- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket. A PS4 Pro (firmware 10.71) reports 0 for an empty queue and counts each queued datagram's payload plus 16 bytes, as FreeBSD 11.4's receive buffer does (10 then 30 bytes queued: 26, then 72, and 46 after one receive). No host matches that on a datagram socket: Linux counts the first datagram (0 for an empty one, which then blocks the queue for a title that reads only above 0), Windows every queued payload byte, and macOS adds 16 bytes for the first datagram and 32 for each later one. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, and a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2.
- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - a socket made with `socket()` is described from the libkernel table and listed, as on that console; its buffer sizes are the host's, and the other sceNet calls still answer `EBADF` for it (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
- [sceNetSocketClose, sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - close and concurrent registration ordering are unmeasured on a console. Closed descriptors retire their registrations, and a wait discards snapshots invalidated by close, DEL or MOD; descriptor identity follows [Linux epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html). ADD, MOD and DEL reject closed or invalid socket descriptors with `EBADF`, as [Linux epoll_ctl(2)](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) does; this error ordering is unmeasured on a console. Duplicate-descriptor alias lifetime is not modeled by this epoll backend.
Expand Down
Loading