diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 603a96cf3..8f1fd2bfd 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -167,6 +167,35 @@ bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, Fail(47); return false; } +std::uint16_t& PortOf(sockaddr_storage& address) { + return address.ss_family == AF_INET ? reinterpret_cast(address).sin_port : reinterpret_cast(address).sin6_port; +} +int Disconnect(const Socket& socket) { + sockaddr_storage peer{}; + socklen_t peerSize = sizeof(peer); + if (::getpeername(socket.value, reinterpret_cast(&peer), &peerSize) != 0) return 0; + sockaddr_storage local{}; + socklen_t localSize = sizeof(local); + if (::getsockname(socket.value, reinterpret_cast(&local), &localSize) != 0) return Fail(NativeError()); +#ifdef _WIN32 + sockaddr_storage unspecified{}; + unspecified.ss_family = local.ss_family; + if (::connect(socket.value, reinterpret_cast(&unspecified), localSize) != 0) return Fail(NativeError()); +#else + sockaddr unspecified{}; + unspecified.sa_family = AF_UNSPEC; + if (::connect(socket.value, &unspecified, sizeof(unspecified)) != 0) return Fail(NativeError()); +#endif + sockaddr_storage current{}; + socklen_t currentSize = sizeof(current); + if (::getsockname(socket.value, reinterpret_cast(¤t), ¤tSize) != 0) return Fail(NativeError()); + if (PortOf(current) != 0) return 0; + sockaddr_storage wildcard{}; + wildcard.ss_family = local.ss_family; + PortOf(wildcard) = PortOf(local); + if (::bind(socket.value, reinterpret_cast(&wildcard), localSize) != 0) return Fail(NativeError()); + return 0; +} void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* length) { unsigned char bytes[28]{}; bytes[0] = native.ss_family == AF_INET ? 16 : 28; @@ -597,6 +626,9 @@ int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* de int APS5_VABI connect_nid_postfix(int descriptor, const void* address, std::uint32_t length) { const auto socket = Lookup(descriptor); if (!socket) return -1; + if (socket->family != GuestUnix && socket->type == 2 && address && length >= 2 && static_cast(address)[1] == 0 + && Disconnect(*socket) != 0) + return -1; sockaddr_storage native{}; socklen_t size; if (!Address(address, length, native, size)) return -1; diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp index 5b4dc2694..fb9acba81 100644 --- a/core/libs/tests/GuestUdp.cpp +++ b/core/libs/tests/GuestUdp.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include extern "C" { @@ -14,6 +15,7 @@ int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); 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 getpeername_nid_postfix(int, void*, std::uint32_t*); int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*); int APS5_VABI _ioctl_nid_postfix(int, std::uint64_t, void*); std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); @@ -204,6 +206,45 @@ int main() { Require(ioctl_nid_postfix(receiver, 0x4004667f, &queued) == -1); Require(_ioctl_nid_postfix(receiver, 0x4004667f, &queued) == -1 && *__error_nid_postfix() == 9); Require(close_nid_postfix(sender) == 0); + const int peer = socket_nid_postfix(2, 2, 0); + const int other = socket_nid_postfix(2, 2, 0); + const int dissolved = socket_nid_postfix(2, 2, 0); + Require(peer >= 0 && other >= 0 && dissolved >= 0); + std::array peerAddress{16, 2, 0, 0, 127, 0, 0, 1}; + std::array dissolvedAddress{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(peer, peerAddress.data(), peerAddress.size()) == 0); + Require(bind_nid_postfix(dissolved, dissolvedAddress.data(), dissolvedAddress.size()) == 0); + size = peerAddress.size(); + Require(getsockname_nid_postfix(peer, peerAddress.data(), &size) == 0); + size = dissolvedAddress.size(); + Require(getsockname_nid_postfix(dissolved, dissolvedAddress.data(), &size) == 0); + Require(connect_nid_postfix(dissolved, peerAddress.data(), peerAddress.size()) == 0); + std::array peerName{}; + size = peerName.size(); + Require(getpeername_nid_postfix(dissolved, peerName.data(), &size) == 0 && peerName[2] == peerAddress[2] && peerName[3] == peerAddress[3]); + const std::array unspecified{16, 0}; + Require(connect_nid_postfix(dissolved, unspecified.data(), unspecified.size()) == -1 && *__error_nid_postfix() == 47); + size = peerName.size(); + Require(getpeername_nid_postfix(dissolved, peerName.data(), &size) == -1 && *__error_nid_postfix() == 57); + std::array localName{}; + size = localName.size(); + Require(getsockname_nid_postfix(dissolved, localName.data(), &size) == 0); + Require(localName[2] == dissolvedAddress[2] && localName[3] == dissolvedAddress[3]); +#ifndef _WIN32 + Require(localName[4] == 0 && localName[5] == 0 && localName[6] == 0 && localName[7] == 0); +#endif + Require(send_nid_postfix(dissolved, "x", 1, 0) == -1 && *__error_nid_postfix() == 39); + const char unrelated[] = "unrelated sender"; + Require(sendto_nid_postfix(other, unrelated, sizeof(unrelated), 0, dissolvedAddress.data(), dissolvedAddress.size()) == sizeof(unrelated)); + queued = 0; + for (int i = 0; i < 1000 && queued == 0; ++i) { + Require(ioctl_nid_postfix(dissolved, 0x4004667f, &queued) == 0); + if (!queued) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + Require(queued == sizeof(unrelated)); + Require(recvfrom_nid_postfix(dissolved, buffer, sizeof(buffer), 0, nullptr, nullptr) == sizeof(unrelated)); + Require(std::strcmp(buffer, unrelated) == 0); + for (const int descriptor : {peer, other, dissolved}) Require(close_nid_postfix(descriptor) == 0); unsigned char parsed[16]{}; Require(inet_pton_nid_postfix(2, "127.0.0.1", parsed) == 1); Require(parsed[0] == 127 && parsed[1] == 0 && parsed[2] == 0 && parsed[3] == 1); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index d8bb23517..4e3eff86f 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -141,6 +141,7 @@ - [read, write and vector I/O](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. Guest socket descriptors start at `0x10000000` and are allocated upward, so the descriptor range selects the guest socket table or the host descriptor table. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore nor suppress: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; `SO_NOSIGPIPE` is not an option here, and raises nothing on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor - [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits only on guest sockets, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A descriptor below the socket range (files, pipes, stdio) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), `POLLPRI` on Windows, which `WSAPoll` rejects, and an infinite timeout with no socket to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked - [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0 +- [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) 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 a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had - [msync](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`, not measured on the console: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range throws: FreeBSD returns `EBUSY` when the range holds `mlock`ed pages, and locked pages are not tracked - [sigtimedwait](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) throws: guest signals are delivered when raised and never left pending, so there is nothing to wait for or take. PPSA85300 imports it and fails to load without it. Its only call site is VLC's `vlc_writev` ([filesystem.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/posix/filesystem.c#L243-L278)), which polls with a zero timeout to drain a `SIGPIPE` only after `writev` fails with `EPIPE` - [pause, sigwait, sigwaitinfo](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) throw, as `sigtimedwait` does: guest signals are never left pending, so `sigwait` and `sigwaitinfo` have nothing to take, and `pause` would wait for a handler that `raise`, `kill` and `pthread_kill` only run on the calling thread. PPSA68091, PPSA99169, PPSA99420 and PPSA97358 import them and fail to load without them. PPSA68091 calls `pause` at one site (0x8F7266), in a loop that waits while a global holds another thread's `errno` address. PPSA99169 only stores the address of `pause` in a table of symbol addresses (0x30DB1). PPSA99420 calls `pause` at 0x3BD80F6 and 0x3C9E220, `sigwait` at 0x3C9E4C7 and `sigwaitinfo` at 0x3C9E588. PPSA97358 calls `sigwait` from QEMU's `sigwait_compat` thread ([compatfd.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/compatfd.c#L28-L53)), which `qemu_signal_init` ([main-loop.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/main-loop.c#L110-L147)) starts for `SIGIO`, `SIGALRM` and `SIGBUS` when the main loop is created, so it reaches the throw at start-up, and from the vCPU thread of the dummy accelerator ([dummy-cpus.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/accel/dummy-cpus.c#L48-L56))