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
48 changes: 41 additions & 7 deletions core/libs/prx/libSceNet/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,10 @@ const bool g_socketPollerRegistered = (KernelSetSocketPoller_nid_no_patch(&PollS
bool CloseSocketForKernel(int descriptor, int* error) {
{
std::lock_guard lk(g_mutex);
if (g_socks.count(descriptor) == 0) return false;
if (g_socks.count(descriptor) == 0) {
for (auto& epoll : g_epoll_socks) epoll.second.erase(descriptor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cleanup only runs when KernelCloseSocket_nid_no_patch is called. sceKernelClose in libkernel/File/src/Open.cpp closes GuestSockets directly and does not invoke that callback; the registration therefore remains until a later sceNetEpollWait fails to look up the descriptor. If the descriptor number is reused for another kernel socket before that wait, KernelLookupSocketNative succeeds for the replacement and the stale epoll registration watches the new socket. Please retire these registrations on the sceKernelClose path too (or key them by stable socket identity/generation), and add a regression that closes a registered socket with sceKernelClose, immediately reuses its descriptor, and proves the old registration cannot report readiness for the replacement.

return false;
}
}
*error = sceNetSocketClose(descriptor) == 0 ? 0 : *errno_slot();
return true;
Expand Down Expand Up @@ -1851,13 +1854,19 @@ int APS5_VABI sceNetEpollAbort(int eid, int flags) {
}

int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* event) {
bool kernel = false;
{
std::lock_guard lk(g_mutex);
kernel = g_socks.count(id) == 0;
}
kernel = kernel && GuestSocketIsOpen_nid_no_patch(id);
std::lock_guard lk(g_mutex);
if (g_epolls.count(eid) == 0) {
return fail(NET_EBADF);
}
if (op != NET_EPOLL_CTL_ADD && op != NET_EPOLL_CTL_MOD && op != NET_EPOLL_CTL_DEL) return fail(NET_EINVAL);
if (op != NET_EPOLL_CTL_DEL && !event) return fail(NET_EINVAL);
if (g_socks.count(id) == 0) return fail(NET_EBADF);
if (!kernel && g_socks.count(id) == 0) return fail(NET_EBADF);
auto& registrations = g_epoll_socks[eid];
if (op == NET_EPOLL_CTL_ADD) {
if (registrations.count(id) != 0) return fail(NET_EADDRINUSE);
Expand All @@ -1879,12 +1888,15 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int
int descriptor;
std::shared_ptr<const NetEpollEvent> registration;
Sock socket;
std::shared_ptr<void> kernelOwner;
NativeSocket native = INVALID_NATIVE_SOCKET;
};
const auto deadline = timeout > 0
? std::chrono::steady_clock::now() + std::chrono::microseconds(timeout)
: std::chrono::steady_clock::time_point::max();
for (;;) {
std::vector<Watch> watches;
std::vector<Watch> kernelWatches;
{
std::lock_guard lk(g_mutex);
if (g_epolls.count(eid) == 0) return fail(NET_EBADF);
Expand All @@ -1893,8 +1905,29 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int
if (registration != g_epoll_socks.end()) {
for (const auto& entry : registration->second) {
const auto socket = g_socks.find(entry.first);
if (socket == g_socks.end()) continue;
watches.push_back({entry.first, entry.second, socket->second});
if (socket == g_socks.end()) kernelWatches.push_back({entry.first, entry.second, Sock{}, nullptr, INVALID_NATIVE_SOCKET});
else watches.push_back({entry.first, entry.second, socket->second, nullptr, socket->second.native->value});
}
}
}
std::vector<Watch> closedWatches;
for (auto& watch : kernelWatches) {
std::uintptr_t native = 0;
if (KernelLookupSocketNative_nid_no_patch(watch.descriptor, &watch.kernelOwner, &native)) {
watch.native = static_cast<NativeSocket>(native);
watches.push_back(std::move(watch));
} else {
closedWatches.push_back(std::move(watch));
}
}
if (!closedWatches.empty()) {
std::lock_guard lk(g_mutex);
const auto registration = g_epoll_socks.find(eid);
if (registration != g_epoll_socks.end()) {
for (const auto& watch : closedWatches) {
const auto entry = registration->second.find(watch.descriptor);
if (entry != registration->second.end() && entry->second == watch.registration && g_socks.count(watch.descriptor) == 0)
registration->second.erase(entry);
}
}
}
Expand All @@ -1912,14 +1945,14 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int
#ifdef _WIN32
std::vector<WSAPOLLFD> descriptors(watches.size());
for (std::size_t index = 0; index < descriptors.size(); ++index) {
descriptors[index].fd = watches[index].socket.native->value;
descriptors[index].fd = watches[index].native;
if (watches[index].registration->events & 1) descriptors[index].events |= POLLRDNORM;
if (watches[index].registration->events & 2) descriptors[index].events |= POLLWRNORM;
}
#else
std::vector<pollfd> descriptors(watches.size());
for (std::size_t index = 0; index < descriptors.size(); ++index) {
descriptors[index].fd = watches[index].socket.native->value;
descriptors[index].fd = watches[index].native;
if (watches[index].registration->events & 1) descriptors[index].events |= POLLIN;
if (watches[index].registration->events & 2) descriptors[index].events |= POLLOUT;
}
Expand All @@ -1935,7 +1968,8 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int
#ifdef _WIN32
const int ready_count = WSAPoll(descriptors.data(), static_cast<ULONG>(descriptors.size()), wait_ms);
for (std::size_t index = 0; index < descriptors.size(); ++index)
descriptors[index].revents = closed_stream_events(watches[index].socket, descriptors[index].revents, descriptors[index].events);
if (!watches[index].kernelOwner)
descriptors[index].revents = closed_stream_events(watches[index].socket, descriptors[index].revents, descriptors[index].events);
#else
const int ready_count = ::poll(descriptors.data(), descriptors.size(), wait_ms);
#endif
Expand Down
3 changes: 3 additions & 0 deletions core/libs/prx/libkernel/File/src/Open.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "prx/libkernel/File/include/DescriptorControl.hpp"
#include "prx/libkernel/File/include/FileLock.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
#include "prx/libkernel/File/include/HostStreams.hpp"
#include "prx/libkernel/File/include/ArchivedFile.hpp"
Expand Down Expand Up @@ -303,6 +304,8 @@ int OpenInDirectory_nid_no_patch(int directory, const char* path, int flags, std

int APS5_VABI sceKernelClose(int d) {
if (File::IsHostStream(d)) return SCE_KERNEL_ERROR_EPERM;
int socketError = 0;
if (KernelCloseSocket_nid_no_patch(d, &socketError)) return socketError == 0 ? 0 : SceKernelError(socketError);
if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix());
EqueueDescriptorClosed(d);
#ifdef _WIN32
Expand Down
80 changes: 80 additions & 0 deletions core/libs/tests/GuestSceNet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <unistd.h>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/Socket/include/SocketSelectDeadline.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libSceNet/src/ResolverTestHooks.hpp"
Expand Down Expand Up @@ -1299,6 +1300,84 @@ static void CheckKernelSocketOption() {
Require(Failed(sceNetSetsockopt(kernel, 0xffff, 0x1001, &size, sizeof(size)), 9));
}

static void CheckEpollKernelSocket() {
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
const int receiver = socket_nid_postfix(2, 2, 0);
const int sender = socket_nid_postfix(2, 2, 0);
const int unpolled = socket_nid_postfix(2, 2, 0);
Require(receiver >= 0 && sender >= 0 && unpolled >= 0);
Require(GuestSocketIsOpen_nid_no_patch(receiver) && GuestSocketIsOpen_nid_no_patch(sender));
Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0 && size == address.size());
const int epoll = sceNetEpollCreate("epoll-kernel-socket", 0);
Require(epoll >= 0);
NetEpollEvent registration{};
registration.events = 1;
registration.data.u32 = 77;
Require(sceNetEpollControl(epoll, 1, receiver, &registration) == 0);
Require(Failed(sceNetEpollControl(epoll, 1, receiver, &registration), 48));
NetEpollEvent ready{};
Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0);
const char payload[] = "kernel";
Require(sendto_nid_postfix(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload));
Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1);
Require(ready.events == 1 && ready.ident == static_cast<std::uint64_t>(receiver) && ready.data.u32 == 77);
char received[sizeof(payload)]{};
Require(recv_nid_postfix(receiver, received, sizeof(received), 0) == sizeof(payload));
Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0);
registration.events = 2;
registration.data.u32 = 78;
Require(sceNetEpollControl(epoll, 2, receiver, &registration) == 0);
Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1);
Require(ready.events == 2 && ready.ident == static_cast<std::uint64_t>(receiver) && ready.data.u32 == 78);
Require(sceNetEpollControl(epoll, 3, receiver, nullptr) == 0);
Require(Failed(sceNetEpollControl(epoll, 3, receiver, nullptr), 2));
Require(Failed(sceNetEpollControl(epoll, 2, receiver, &registration), 2));
Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0);

Require(sceNetEpollControl(epoll, 1, sender, &registration) == 0);
Require(sceNetEpollControl(epoll, 1, unpolled, &registration) == 0);
Require(close_nid_postfix(sender) == 0);
Require(sceKernelClose(unpolled) == 0);
Require(sceNetEpollWait(epoll, &ready, 1, 1000) == 0);
Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0);
Require(Failed(sceNetEpollControl(epoll, 3, sender, nullptr), 9));
Require(Failed(sceNetEpollControl(epoll, 1, unpolled, &registration), 9));
Require(Failed(sceNetEpollControl(epoll, 1, 0x7ffffff0, &registration), 9));
const int stale = socket_nid_postfix(2, 2, 0);
Require(stale >= 0);
registration.events = 1;
registration.data.u32 = 79;
Require(sceNetEpollControl(epoll, 1, stale, &registration) == 0);
Require(sceKernelClose(stale) == 0);
std::vector<int> spare;
int reused = -1;
for (int attempt = 0; attempt < 64 && reused != stale; ++attempt) {
reused = socket_nid_postfix(2, 2, 0);
Require(reused >= 0);
if (reused != stale) spare.push_back(reused);
}
Require(reused == stale);
std::array<std::uint8_t, 16> reusedAddress{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(reused, reusedAddress.data(), reusedAddress.size()) == 0);
size = reusedAddress.size();
Require(getsockname_nid_postfix(reused, reusedAddress.data(), &size) == 0 && size == reusedAddress.size());
const int reusedSender = socket_nid_postfix(2, 2, 0);
Require(reusedSender >= 0);
Require(sendto_nid_postfix(reusedSender, payload, sizeof(payload), 0, reusedAddress.data(), reusedAddress.size()) == sizeof(payload));
Require(sceNetEpollWait(epoll, &ready, 1, 100000) == 0);
registration.data.u32 = 80;
Require(sceNetEpollControl(epoll, 1, reused, &registration) == 0);
Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1);
Require(ready.events == 1 && ready.ident == static_cast<std::uint64_t>(reused) && ready.data.u32 == 80);
Require(close_nid_postfix(reusedSender) == 0);
Require(close_nid_postfix(reused) == 0);
for (const int descriptor : spare) Require(close_nid_postfix(descriptor) == 0);
Require(sceNetEpollDestroy(epoll) == 0);
Require(close_nid_postfix(receiver) == 0);
}

static void CheckBufferSizes() {
const int socket = sceNetSocket("buffer-sizes", 2, 2, 17);
Require(socket >= 0);
Expand Down Expand Up @@ -1947,6 +2026,7 @@ int main() {
CheckBufferSizes();
CheckOnesBroadcast();
CheckKernelSocketOption();
CheckEpollKernelSocket();

const int epoll = sceNetEpollCreate("guest-sce-net", 0);
Require(epoll >= 0);
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libSceNet.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
- [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
- [sceNetGetSockInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) is not measured on a console; shadPS4 stubs it. It lists only IPv6 sockets and `flags` 0x1000 adds IPv4 sockets, reported with the IPv4 addresses, all-zero IPv6 addresses and no `F_IPV6` (0x400000), which every IPv6 socket sets ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L251-L343), [flags](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L698-L705), [docs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/docs/net.md#L301-L304)). The default of leaving IPv4 sockets out, the zero IPv6 addresses of an IPv4 socket and accepting 0x40 are inferred from those names, not documented. An IPv4 socket without 0x1000 fails with `EAFNOSUPPORT`. The other gaps are those of `sceNetGetSockInfo`.
- [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.
- [sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take sockets made with libkernel's `socket()`. PPSA02429 registers such sockets and retries the failed `ADD` and `DEL` about 1.6 million times in its first seconds. This follows the position taken for PPSA30839 in `sceNetSocketClose` and `sceNetSetsockopt`, that both libraries work on the same kernel sockets; epoll on them is not measured on a console. libkernel's `close()` and `sceKernelClose` retire their registrations at once, so a socket that reuses the number starts unregistered.
- [MSG_DONTWAIT](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - console ordering between competing readers and writers is unmeasured. The per-call behavior follows FreeBSD 11.4's `soreceive_generic` and `sosend_generic` (`sys/kern/uipc_socket.c`); `0x80` is FreeBSD's flag value (`sys/sys/socket.h`). On Windows native sockets stay non-blocking. `MSG_DONTWAIT` and guest `SO_NBIO` return one host transfer's result. Blocking stream sends queue the whole buffer with one deadline and return progress after a timeout or host error. SSL uses this path through the native owner, retaining the socket, its mode and timeouts. Readiness waits poll at most every 10 ms for local close, so idle blocked operations wake periodically.
- [sceNetRecv, sceNetRecvfrom and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) on a datagram socket with no local address wait on Windows and macOS until a bind, connect or send gives it one, checking the host socket every 10 ms. Winsock fails such a receive with `WSAEINVAL`, where FreeBSD 11.4's `soreceive_dgram` waits for a datagram (`sys/kern/uipc_socket.c`). On macOS the host receive waits, but a receive-side `shutdown` does not wake it. A non-blocking socket fails with `EAGAIN` at once, and the receive timeout ends the wait with `EAGAIN`; when the address comes first, the call waits for a datagram only for what is left of the timeout. A `sceNetShutdown` of the receive side ends the wait and the receive returns 0, and a zero-length receive returns 0 without waiting, as `soreceive_dgram` does when the socket can't receive more or nothing was asked for. An abort or close ends the wait, and the host receive then fails as it did before
- [sceNetResolverAbort and sceNetResolverStartNtoaMultipleRecords](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) follow shadPS4 ([net.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net.cpp), [net_resolver.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net_resolver.cpp)); the `(int rid, int flags)` signature of the abort also matches [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs). Unverified on hardware. Lookups run synchronously over the host resolver, which cannot be interrupted: an abort while a lookup runs on another thread returns at once, and that lookup fails with `EINTR` without writing its output only when the host call returns; the abort flags are then ignored, as shadPS4 does for a running lookup. With nothing running, abort flag 1 (NTOA preservation) or 2 (ATON preservation) makes the next `sceNetResolverStartNtoa*` or `sceNetResolverStartAton` call on that resolver fail with `EINTR`, reported by `sceNetResolverGetError`, after its arguments are checked. Unlike shadPS4, which ignores them, other abort flag bits throw, and `sceNetResolverStartNtoaMultipleRecords` throws for any flag, including async (1) and 0x10000 (IP address literals disabled), as `sceNetResolverStartNtoaMultipleRecordsEx` does
Expand Down
Loading