fix(libkernel): dissolve a UDP association on connect to AF_UNSPEC
connect() on a guest datagram socket checked the address family before
doing anything, so an AF_UNSPEC (null) address failed with EAFNOSUPPORT
and left the socket connected: getpeername() still reported the old
peer, send() without an address still went there, and datagrams from
any other sender were dropped by the host.
FreeBSD 11.4 disconnects a connected datagram socket first and only then
rejects the address. sys/kern/uipc_socket.c, soconnectat():
* If protocol is connection-based, can only connect once.
* Otherwise, if connected, try to disconnect first. This allows
* user to disconnect by connecting to, e.g., a null address.
*/
if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&
((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
(error = sodisconnect(so)))) {
udp_disconnect() (sys/netinet/udp_usrreq.c) clears the peer and resets
only the local address, keeping the local port:
in_pcbdisconnect(inp);
inp->inp_laddr.s_addr = INADDR_ANY;
after which in_pcbconnect_setup() (sys/netinet/in_pcb.c) returns
EAFNOSUPPORT for the full-size address, as main already does. connect(2)
documents it: "Datagram sockets may dissolve the association by
connecting to an invalid address, such as a null address."
A connected internet datagram socket given an address whose family byte
is 0 is now disconnected on the host first (AF_UNSPEC on Linux, a zeroed
address on Windows) and the call then fails as before. Linux also drops
an ephemeral local port on that disconnect, so the port is bound again
on the wildcard address, which is what FreeBSD leaves. A host
failure during the disconnect is returned as the call's error. Stream
sockets keep the previous path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f4cc8983b
commit
b8d71fc7f2
|
|
@ -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<sockaddr_in&>(address).sin_port : reinterpret_cast<sockaddr_in6&>(address).sin6_port;
|
||||
}
|
||||
int Disconnect(const Socket& socket) {
|
||||
sockaddr_storage peer{};
|
||||
socklen_t peerSize = sizeof(peer);
|
||||
if (::getpeername(socket.value, reinterpret_cast<sockaddr*>(&peer), &peerSize) != 0) return 0;
|
||||
sockaddr_storage local{};
|
||||
socklen_t localSize = sizeof(local);
|
||||
if (::getsockname(socket.value, reinterpret_cast<sockaddr*>(&local), &localSize) != 0) return Fail(NativeError());
|
||||
#ifdef _WIN32
|
||||
sockaddr_storage unspecified{};
|
||||
unspecified.ss_family = local.ss_family;
|
||||
if (::connect(socket.value, reinterpret_cast<sockaddr*>(&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<sockaddr*>(¤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<sockaddr*>(&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<const unsigned char*>(address)[1] == 0
|
||||
&& Disconnect(*socket) != 0)
|
||||
return -1;
|
||||
sockaddr_storage native{};
|
||||
socklen_t size;
|
||||
if (!Address(address, length, native, size)) return -1;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
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<unsigned char, 16> peerAddress{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
std::array<unsigned char, 16> 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<unsigned char, 16> peerName{};
|
||||
size = peerName.size();
|
||||
Require(getpeername_nid_postfix(dissolved, peerName.data(), &size) == 0 && peerName[2] == peerAddress[2] && peerName[3] == peerAddress[3]);
|
||||
const std::array<unsigned char, 16> 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<unsigned char, 16> 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);
|
||||
|
|
|
|||
|
|
@ -135,6 +135,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:<port>` stays `127.0.0.1:<port>`), 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))
|
||||
|
|
|
|||
Loading…
Reference in New Issue