net: fix socket wakeups and readiness checks
This commit is contained in:
parent
314d37155a
commit
46850f9574
|
|
@ -16,6 +16,7 @@
|
|||
#include <arpa/inet.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
#include "libs/libs.h"
|
||||
#include "libs/network.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
|
@ -37,6 +39,7 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -832,10 +835,12 @@ struct NetEtherAddr {
|
|||
using NativeSocket = SOCKET;
|
||||
static constexpr NativeSocket INVALID_NATIVE_SOCKET = INVALID_SOCKET;
|
||||
using SocketLength = int;
|
||||
using SocketIoLength = int;
|
||||
#else
|
||||
using NativeSocket = int;
|
||||
static constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
|
||||
using SocketLength = socklen_t;
|
||||
using SocketIoLength = size_t;
|
||||
#endif
|
||||
|
||||
struct SocketSlot {
|
||||
|
|
@ -953,6 +958,7 @@ static int SetHostSocketError(int error) {
|
|||
case EADDRINUSE: posix_error = Posix::POSIX_EADDRINUSE; break;
|
||||
case EADDRNOTAVAIL: posix_error = Posix::POSIX_EADDRNOTAVAIL; break;
|
||||
case EAFNOSUPPORT: posix_error = Posix::POSIX_EAFNOSUPPORT; break;
|
||||
case EAGAIN: posix_error = Posix::POSIX_EWOULDBLOCK; break;
|
||||
case EBADF: posix_error = Posix::POSIX_EBADF; break;
|
||||
case EFAULT: posix_error = Posix::POSIX_EFAULT; break;
|
||||
case EINVAL: posix_error = Posix::POSIX_EINVAL; break;
|
||||
|
|
@ -989,7 +995,6 @@ static int ConvertSocketOptionLevel(int level) {
|
|||
}
|
||||
|
||||
static int ConvertMessageFlags(int flags) {
|
||||
#if defined(_WIN32)
|
||||
constexpr int guest_msg_peek = 0x00000002;
|
||||
constexpr int guest_msg_dontroute = 0x00000004;
|
||||
constexpr int guest_msg_waitall = 0x00000040;
|
||||
|
|
@ -1006,6 +1011,14 @@ static int ConvertMessageFlags(int flags) {
|
|||
if ((flags & guest_msg_waitall) != 0) {
|
||||
host_flags |= MSG_WAITALL;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
if ((flags & guest_msg_dontwait) != 0) {
|
||||
host_flags |= MSG_DONTWAIT;
|
||||
}
|
||||
if ((flags & guest_msg_nosignal) != 0) {
|
||||
host_flags |= MSG_NOSIGNAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
flags &= ~(guest_msg_peek | guest_msg_dontroute | guest_msg_waitall | guest_msg_dontwait |
|
||||
guest_msg_nosignal);
|
||||
|
|
@ -1015,9 +1028,6 @@ static int ConvertMessageFlags(int flags) {
|
|||
}
|
||||
|
||||
return host_flags;
|
||||
#else
|
||||
return flags;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int ConvertGuestSockaddr(const void* addr, uint32_t addrlen, sockaddr_storage* out,
|
||||
|
|
@ -1942,34 +1952,30 @@ int64_t KYTY_SYSV_ABI Sendto(int s, const void* buf, uint64_t len, int flags, co
|
|||
return -1;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
const int host_flags = ConvertMessageFlags(flags);
|
||||
if (host_flags < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int host_len = static_cast<int>(len > 0x7fffffffu ? 0x7fffffffu : len);
|
||||
int result = 0;
|
||||
const auto host_len = static_cast<SocketIoLength>(
|
||||
std::min<uint64_t>(len, std::numeric_limits<SocketIoLength>::max()));
|
||||
int64_t result = 0;
|
||||
if (addr == nullptr) {
|
||||
result = ::send(socket, static_cast<const char*>(buf), host_len, host_flags);
|
||||
} else {
|
||||
sockaddr_storage host_addr {};
|
||||
int host_addrlen = 0;
|
||||
SocketLength host_addrlen = 0;
|
||||
if (ConvertGuestSockaddr(addr, addrlen, &host_addr, &host_addrlen) != 0) {
|
||||
return -1;
|
||||
}
|
||||
result = ::sendto(socket, static_cast<const char*>(buf), host_len, host_flags,
|
||||
reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen);
|
||||
}
|
||||
if (result == SOCKET_ERROR) {
|
||||
if (result < 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int64_t KYTY_SYSV_ABI Recv(int s, void* buf, uint64_t len, int flags) {
|
||||
|
|
@ -2000,35 +2006,31 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
|
|||
return -1;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
const int host_flags = ConvertMessageFlags(flags);
|
||||
if (host_flags < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int host_len = static_cast<int>(len > 0x7fffffffu ? 0x7fffffffu : len);
|
||||
int result = 0;
|
||||
const auto host_len = static_cast<SocketIoLength>(
|
||||
std::min<uint64_t>(len, std::numeric_limits<SocketIoLength>::max()));
|
||||
int64_t result = 0;
|
||||
if (addr == nullptr) {
|
||||
result = ::recv(socket, static_cast<char*>(buf), host_len, host_flags);
|
||||
} else {
|
||||
sockaddr_storage host_addr {};
|
||||
int host_addrlen = sizeof(host_addr);
|
||||
SocketLength host_addrlen = sizeof(host_addr);
|
||||
result = ::recvfrom(socket, static_cast<char*>(buf), host_len, host_flags,
|
||||
reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen);
|
||||
if (result != SOCKET_ERROR &&
|
||||
if (result >= 0 &&
|
||||
ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (result == SOCKET_ERROR) {
|
||||
if (result < 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfds,
|
||||
|
|
@ -2062,7 +2064,6 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
|||
LOGF("\n");
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
fd_set host_read {};
|
||||
fd_set host_write {};
|
||||
fd_set host_except {};
|
||||
|
|
@ -2070,91 +2071,92 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
|||
FD_ZERO(&host_write);
|
||||
FD_ZERO(&host_except);
|
||||
|
||||
std::array<int, FD_SETSIZE> read_map {};
|
||||
std::array<int, FD_SETSIZE> write_map {};
|
||||
std::array<int, FD_SETSIZE> except_map {};
|
||||
int read_count = 0;
|
||||
int write_count = 0;
|
||||
int except_count = 0;
|
||||
|
||||
std::vector<std::pair<int, NativeSocket>> descriptors;
|
||||
int host_nfds = 0;
|
||||
for (int fd = 0; fd < nfds; fd++) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!GetSocketBackend(fd, &socket)) {
|
||||
const bool read = GuestFdIsSet(readfds, fd);
|
||||
const bool write = GuestFdIsSet(writefds, fd);
|
||||
const bool except = GuestFdIsSet(exceptfds, fd);
|
||||
if (!read && !write && !except) {
|
||||
continue;
|
||||
}
|
||||
if (GuestFdIsSet(readfds, fd) && read_count < FD_SETSIZE) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
#if !defined(_WIN32)
|
||||
if (fd < 3) {
|
||||
socket = fd;
|
||||
} else
|
||||
#endif
|
||||
if (!GetSocketBackend(fd, &socket)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EBADF);
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if ((read && host_read.fd_count == FD_SETSIZE) ||
|
||||
(write && host_write.fd_count == FD_SETSIZE) ||
|
||||
(except && host_except.fd_count == FD_SETSIZE)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
#else
|
||||
if (socket >= FD_SETSIZE) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
host_nfds = std::max(host_nfds, socket + 1);
|
||||
#endif
|
||||
descriptors.emplace_back(fd, socket);
|
||||
if (read) {
|
||||
FD_SET(socket, &host_read);
|
||||
read_map[static_cast<size_t>(read_count++)] = fd;
|
||||
}
|
||||
if (GuestFdIsSet(writefds, fd) && write_count < FD_SETSIZE) {
|
||||
if (write) {
|
||||
FD_SET(socket, &host_write);
|
||||
write_map[static_cast<size_t>(write_count++)] = fd;
|
||||
}
|
||||
if (GuestFdIsSet(exceptfds, fd) && except_count < FD_SETSIZE) {
|
||||
if (except) {
|
||||
FD_SET(socket, &host_except);
|
||||
except_map[static_cast<size_t>(except_count++)] = fd;
|
||||
}
|
||||
}
|
||||
|
||||
timeval host_timeout {};
|
||||
timeval host_timeout {};
|
||||
timeval* host_timeout_ptr = nullptr;
|
||||
if (timeout != nullptr) {
|
||||
const auto* guest_timeout = static_cast<const NetTimeval*>(timeout);
|
||||
host_timeout.tv_sec = static_cast<long>(guest_timeout->tv_sec);
|
||||
host_timeout.tv_usec = static_cast<long>(guest_timeout->tv_usec);
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
|
||||
fd_set* read_ptr = (read_count != 0 ? &host_read : nullptr);
|
||||
fd_set* write_ptr = (write_count != 0 ? &host_write : nullptr);
|
||||
fd_set* except_ptr = (except_count != 0 ? &host_except : nullptr);
|
||||
if (read_ptr == nullptr && write_ptr == nullptr && except_ptr == nullptr) {
|
||||
if (host_timeout_ptr != nullptr) {
|
||||
const auto sleep_ms =
|
||||
static_cast<DWORD>(host_timeout.tv_sec * 1000 + host_timeout.tv_usec / 1000);
|
||||
Sleep(sleep_ms);
|
||||
if (guest_timeout->tv_sec < 0 || guest_timeout->tv_usec < 0 ||
|
||||
guest_timeout->tv_usec >= 1'000'000) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
GuestFdZero(readfds, nfds);
|
||||
GuestFdZero(writefds, nfds);
|
||||
GuestFdZero(exceptfds, nfds);
|
||||
return 0;
|
||||
host_timeout.tv_sec = static_cast<decltype(host_timeout.tv_sec)>(guest_timeout->tv_sec);
|
||||
host_timeout.tv_usec = static_cast<decltype(host_timeout.tv_usec)>(guest_timeout->tv_usec);
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
|
||||
const int result = ::select(0, read_ptr, write_ptr, except_ptr, host_timeout_ptr);
|
||||
if (result == SOCKET_ERROR) {
|
||||
return SetHostSocketError();
|
||||
int result = 0;
|
||||
#if defined(_WIN32)
|
||||
if (descriptors.empty()) {
|
||||
Sleep(host_timeout_ptr == nullptr ? INFINITE :
|
||||
static_cast<DWORD>(host_timeout.tv_sec * 1000 + host_timeout.tv_usec / 1000));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
result = ::select(host_nfds, readfds != nullptr ? &host_read : nullptr,
|
||||
writefds != nullptr ? &host_write : nullptr,
|
||||
exceptfds != nullptr ? &host_except : nullptr, host_timeout_ptr);
|
||||
if (result < 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
}
|
||||
|
||||
GuestFdZero(readfds, nfds);
|
||||
GuestFdZero(writefds, nfds);
|
||||
GuestFdZero(exceptfds, nfds);
|
||||
for (int i = 0; i < read_count; i++) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (GetSocketBackend(read_map[static_cast<size_t>(i)], &socket) &&
|
||||
FD_ISSET(socket, &host_read)) {
|
||||
GuestFdSet(readfds, read_map[static_cast<size_t>(i)]);
|
||||
for (const auto& [fd, socket]: descriptors) {
|
||||
if (FD_ISSET(socket, &host_read)) {
|
||||
GuestFdSet(readfds, fd);
|
||||
}
|
||||
if (FD_ISSET(socket, &host_write)) {
|
||||
GuestFdSet(writefds, fd);
|
||||
}
|
||||
if (FD_ISSET(socket, &host_except)) {
|
||||
GuestFdSet(exceptfds, fd);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < write_count; i++) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (GetSocketBackend(write_map[static_cast<size_t>(i)], &socket) &&
|
||||
FD_ISSET(socket, &host_write)) {
|
||||
GuestFdSet(writefds, write_map[static_cast<size_t>(i)]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < except_count; i++) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (GetSocketBackend(except_map[static_cast<size_t>(i)], &socket) &&
|
||||
FD_ISSET(socket, &host_except)) {
|
||||
GuestFdSet(exceptfds, except_map[static_cast<size_t>(i)]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Net
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
#include "common/threads.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/network.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
|
@ -76,6 +79,63 @@ void CheckSaveRename(const std::filesystem::path &root,
|
|||
"renamed save contents");
|
||||
}
|
||||
|
||||
void CheckSocketWakeup() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
// Guest sockaddr_in: length, family, network-order port/address, padding.
|
||||
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
|
||||
const int listener = Net::Socket(2, 1, 0);
|
||||
Check(listener >= 0, "create loopback listener");
|
||||
Check(Net::Bind(listener, address.data(), address.size()) == 0, "bind loopback");
|
||||
Check(Net::Listen(listener, 1) == 0, "listen on loopback");
|
||||
uint32_t address_size = address.size();
|
||||
Check(Net::Getsockname(listener, address.data(), &address_size) == 0,
|
||||
"get assigned loopback port");
|
||||
const int writer = Net::Socket(2, 1, 0);
|
||||
Check(writer >= 0 && Net::Connect(writer, address.data(), address_size) == 0,
|
||||
"connect wake socket");
|
||||
const int reader = Net::Accept(listener, nullptr, nullptr);
|
||||
Check(reader >= 0, "accept wake socket");
|
||||
Check(Net::SocketClose(listener) == 0, "close listener");
|
||||
const int enabled = 1;
|
||||
Check(Net::Setsockopt(writer, 6, 1, &enabled, sizeof(enabled)) == 0,
|
||||
"enable TCP_NODELAY");
|
||||
|
||||
std::array<uint64_t, 16> readable {};
|
||||
const auto bit = uint64_t {1} << (reader % 64);
|
||||
readable[reader / 64] = bit;
|
||||
const std::array<int64_t, 2> immediate {0, 0};
|
||||
Check(Net::Select(reader + 1, readable.data(), nullptr, nullptr,
|
||||
immediate.data()) == 0 && readable[reader / 64] == 0,
|
||||
"empty socket is not readable");
|
||||
const char payload[] = "wake";
|
||||
Check(Net::Send(writer, payload, sizeof(payload), 0x20000) == sizeof(payload),
|
||||
"send wake bytes with guest MSG_NOSIGNAL");
|
||||
readable[reader / 64] = bit;
|
||||
const std::array<int64_t, 2> deadline {1, 0};
|
||||
Check(Net::Select(reader + 1, readable.data(), nullptr, nullptr,
|
||||
deadline.data()) == 1 && readable[reader / 64] == bit,
|
||||
"select reports the guest descriptor after wake");
|
||||
std::array<char, sizeof(payload)> received {};
|
||||
Check(Net::Recv(reader, received.data(), received.size(), 0x42) == sizeof(payload) &&
|
||||
std::memcmp(received.data(), payload, sizeof(payload)) == 0,
|
||||
"guest PEEK and WAITALL preserve the wake bytes");
|
||||
Check(Net::Recv(reader, received.data(), received.size(), 0x40) == sizeof(payload),
|
||||
"consume wake bytes with guest WAITALL");
|
||||
#if !defined(_WIN32)
|
||||
Check(Net::Recv(reader, received.data(), received.size(), 0x80) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"empty nonblocking receive translates guest errno");
|
||||
#endif
|
||||
Check(Net::SocketClose(reader) == 0 && Net::SocketClose(writer) == 0,
|
||||
"close wake sockets");
|
||||
readable[reader / 64] = bit;
|
||||
Check(Net::Select(reader + 1, readable.data(), nullptr, nullptr,
|
||||
immediate.data()) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EBADF &&
|
||||
readable[reader / 64] == bit,
|
||||
"closed descriptor fails without clearing input fd_set");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
|
|
@ -93,6 +153,7 @@ int main() {
|
|||
CheckSaveRename(temporary.Path(), "first-save");
|
||||
CheckSaveRename(temporary.Path(), "replacement-save");
|
||||
FileSystem::Shutdown();
|
||||
CheckSocketWakeup();
|
||||
subsystems.Destroy();
|
||||
|
||||
std::printf("KernelFileSystemTests: all cases passed\n");
|
||||
|
|
|
|||
Loading…
Reference in New Issue