From e679486ed3deb96727ca35b2da8d1aa55a3ede14 Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 6 Oct 2026 23:36:29 +0400 Subject: [PATCH] net: translate native UDP socket options and microsecond timeouts --- src/libs/network.cpp | 96 ++++++++++++++++++++++++++++----- tests/KernelFileSystemTests.cpp | 91 ++++++++++++++++++++++++++++++- 2 files changed, 174 insertions(+), 13 deletions(-) diff --git a/src/libs/network.cpp b/src/libs/network.cpp index 4bc30e0b6..795d0e05c 100644 --- a/src/libs/network.cpp +++ b/src/libs/network.cpp @@ -992,6 +992,7 @@ static int ConvertHostSocketError(int error) { case WSAENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break; case WSAENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break; case WSAENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break; + case WSAENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break; case WSAEOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break; case WSAEPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break; case WSAESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break; @@ -1028,6 +1029,7 @@ static int ConvertHostSocketError(int error) { case ENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break; case ENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break; case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break; + case ENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break; case EOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break; case EPIPE: posix_error = Posix::POSIX_EPIPE; break; case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break; @@ -1066,6 +1068,26 @@ static int ConvertSocketOptionLevel(int level) { return (level == 0xffff ? SOL_SOCKET : level); } +static int ConvertSocketOptionName(int level, int option) { + if (level == 0xffff) { + switch (option) { + case 0x0020: return SO_BROADCAST; + case 0x1001: return SO_SNDBUF; + case 0x1002: return SO_RCVBUF; + case 0x1007: return SO_ERROR; + case 0x1105: return SO_SNDTIMEO; + default: break; + } + } else if (level == 6 && option == 1) { + return TCP_NODELAY; + } +#if defined(_WIN32) + return option; +#else + return -1; +#endif +} + static int* P2pSocketOption(P2pEndpoint& endpoint, int option) { switch (option) { case 0x0020: return &endpoint.broadcast; @@ -2149,21 +2171,41 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32 return 0; } - // Guest socket options: SOL_SOCKET=0xffff, SO_ERROR=0x1007. const bool socket_error = (level == 0xffff && optname == 0x1007); -#if !defined(_WIN32) - if (!socket_error) { + const bool send_timeout = (level == 0xffff && optname == 0x1105); + optname = ConvertSocketOptionName(level, optname); + if (optname < 0) { return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT); } +#if defined(_WIN32) + DWORD timeout {}; +#else + timeval timeout {}; #endif - if (socket_error) { - optname = SO_ERROR; - } SocketLength len = static_cast(*optlen); - if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast(optval), + void* value = optval; + if (send_timeout) { + if (*optlen < sizeof(int)) { + return SetGuestSocketError(Posix::POSIX_EINVAL); + } + value = &timeout; + len = sizeof(timeout); + } + if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast(value), &len) != 0) { return SetHostSocketError(); } + if (send_timeout) { +#if defined(_WIN32) + const int64_t usec = static_cast(timeout) * 1000; +#else + const int64_t usec = static_cast(timeout.tv_sec) * 1'000'000 + timeout.tv_usec; +#endif + const int guest_timeout = + static_cast(std::min(usec, std::numeric_limits::max())); + std::memcpy(optval, &guest_timeout, sizeof(guest_timeout)); + len = sizeof(guest_timeout); + } if (socket_error && len >= static_cast(sizeof(int))) { auto* error = static_cast(optval); *error = ConvertHostSocketError(*error); @@ -2215,6 +2257,18 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval, return 0; } +#if defined(__linux__) + if (level == 0xffff && optname == 0x10000) { + if (optlen != sizeof(int)) { + return SetGuestSocketError(Posix::POSIX_EINVAL); + } + int enabled = 0; + std::memcpy(&enabled, optval, sizeof(enabled)); + // Native sends preserve 255.255.255.255 without changing broadcast permission. + return enabled != 0 ? 0 : SetGuestSocketError(Posix::POSIX_ENOPROTOOPT); + } +#endif + constexpr int ORBIS_SO_NBIO = 0x1200; if (ConvertSocketOptionLevel(level) == SOL_SOCKET && optname == ORBIS_SO_NBIO && optlen >= sizeof(int)) { @@ -2230,14 +2284,32 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval, #endif return failed ? SetHostSocketError() : 0; } -#if !defined(_WIN32) - // Guest TCP options: IPPROTO_TCP=6, TCP_NODELAY=1. - if (level != 6 || optname != 1) { + const bool send_timeout = (level == 0xffff && optname == 0x1105); + optname = ConvertSocketOptionName(level, optname); + if (optname < 0) { return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT); } - level = IPPROTO_TCP; - optname = TCP_NODELAY; +#if defined(_WIN32) + DWORD timeout {}; +#else + timeval timeout {}; #endif + if (send_timeout) { + if (optlen != sizeof(int)) { + return SetGuestSocketError(Posix::POSIX_EINVAL); + } + int usec = 0; + std::memcpy(&usec, optval, sizeof(usec)); + usec = std::max(usec, 0); +#if defined(_WIN32) + timeout = static_cast((static_cast(usec) + 999) / 1000); +#else + timeout.tv_sec = usec / 1'000'000; + timeout.tv_usec = usec % 1'000'000; +#endif + optval = &timeout; + optlen = sizeof(timeout); + } if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast(optval), static_cast(optlen)) != 0) { diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index 8878d7203..a67ee782b 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -34,6 +34,11 @@ #include #include +#if defined(__linux__) +#include +#include +#endif + namespace Libs::LibKernelApr { void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols); } @@ -1065,6 +1070,8 @@ void CheckSocketWakeup() { {"OXXX4mUk3uk", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func}); const auto *getsockopt_symbol = symbols.Find( {"xphrZusl78E", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func}); + const auto *setsockopt_symbol = symbols.Find( + {"2mKX2Spso7I", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func}); const auto *send_symbol = symbols.Find( {"beRjXBn-z+o", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func}); const auto *sendto_symbol = symbols.Find( @@ -1075,11 +1082,12 @@ void CheckSocketWakeup() { {"304ooNZxWDY", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func}); const auto *errno_symbol = symbols.Find( {"HQOwnfMGipQ", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func}); - Check(connect_symbol && getsockopt_symbol && send_symbol && sendto_symbol && + Check(connect_symbol && getsockopt_symbol && setsockopt_symbol && send_symbol && sendto_symbol && recv_symbol && recvfrom_symbol && errno_symbol, "Net socket and errno exports resolve with the guest ABI versions"); using Connect = int (KYTY_SYSV_ABI *)(int, const void *, uint32_t); using Getsockopt = int (KYTY_SYSV_ABI *)(int, int, int, void *, uint32_t *); + using Setsockopt = int (KYTY_SYSV_ABI *)(int, int, int, const void *, uint32_t); using Send = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int); using Sendto = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int, const void *, uint32_t); using Recv = int (KYTY_SYSV_ABI *)(int, void *, size_t, int); @@ -1087,6 +1095,7 @@ void CheckSocketWakeup() { using Errno = int *(KYTY_SYSV_ABI *)(); const auto net_connect = reinterpret_cast(connect_symbol->vaddr); const auto net_getsockopt = reinterpret_cast(getsockopt_symbol->vaddr); + const auto net_setsockopt = reinterpret_cast(setsockopt_symbol->vaddr); const auto net_send = reinterpret_cast(send_symbol->vaddr); const auto net_sendto = reinterpret_cast(sendto_symbol->vaddr); const auto net_recv = reinterpret_cast(recv_symbol->vaddr); @@ -1157,6 +1166,86 @@ void CheckSocketWakeup() { Net::Getsockname(datagram_writer, expected_peer.data(), &expected_peer_size) == 0 && Net::Setsockopt(datagram, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0, "create nonblocking loopback datagrams for Net ABI verification"); + *net_errno = Libs::Posix::POSIX_EINVAL; + *Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL; + for (const int option : {0x1001, 0x1002}) { + constexpr int requested = 16384; + int actual = 0; + uint32_t size = sizeof(actual); + Check(net_setsockopt(datagram, 0xffff, option, &requested, sizeof(requested)) == 0 && + net_getsockopt(datagram, 0xffff, option, &actual, &size) == 0 && + actual >= requested && size == sizeof(actual), + "Net UDP send and receive buffers accept guest option numbers"); + } + for (const int value : {1, 0}) { + int actual = -1; + uint32_t size = sizeof(actual); + Check(net_setsockopt(datagram, 0xffff, 0x20, &value, sizeof(value)) == 0 && + net_getsockopt(datagram, 0xffff, 0x20, &actual, &size) == 0 && + actual == value && size == sizeof(actual), + "Net UDP broadcast option can be enabled and disabled"); + } + int timeout = 0; + int *timeout_value = &timeout; +#if defined(__linux__) + int broadcast = -1; + uint32_t broadcast_size = sizeof(broadcast); + Check(net_setsockopt(datagram, 0xffff, 0x10000, &enabled, sizeof(enabled)) == 0 && + net_getsockopt(datagram, 0xffff, 0x20, &broadcast, &broadcast_size) == 0 && + broadcast == 0, + "preserving the all-ones destination does not enable broadcast permission"); + const long page_size = sysconf(_SC_PAGESIZE); + Check(page_size > 0, "get host page size for socket timeout boundary"); + void *timeout_pages = mmap(nullptr, page_size * 2, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Check(timeout_pages != MAP_FAILED && + mprotect(static_cast(timeout_pages) + page_size, page_size, + PROT_NONE) == 0, + "guard memory after the four-byte socket timeout"); + timeout_value = reinterpret_cast(static_cast(timeout_pages) + + page_size - sizeof(int)); +#endif + for (const int value : {1500000, 0, -1}) { + *timeout_value = value; + Check(net_setsockopt(datagram, 0xffff, 0x1105, timeout_value, sizeof(int)) == 0, + "Net send timeout reads a four-byte microsecond value"); + *timeout_value = -2; + uint32_t size = sizeof(int); + Check(net_getsockopt(datagram, 0xffff, 0x1105, timeout_value, &size) == 0 && + *timeout_value == std::max(value, 0) && size == sizeof(int), + "Net send timeout returns four-byte microseconds and disables nonpositive values"); + } +#if defined(__linux__) + Check(munmap(timeout_pages, page_size * 2) == 0, "free socket timeout guard pages"); +#endif + Check(*net_errno == Libs::Posix::POSIX_EINVAL && + *Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL, + "successful Net socket option calls preserve both guest errno values"); + Check(net_setsockopt(datagram, 0xffff, 0x1105, nullptr, sizeof(timeout)) == + Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT && + net_getsockopt(datagram, 0xffff, 0x1105, &timeout, nullptr) == + Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT, + "Net socket options reject null value and length pointers"); + uint32_t short_size = sizeof(timeout) - 1; + Check(net_setsockopt(datagram, 0xffff, 0x1105, &timeout, short_size) == + Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL && + net_getsockopt(datagram, 0xffff, 0x1105, &timeout, &short_size) == + Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL, + "Net send timeout rejects undersized values"); + uint32_t option_size = sizeof(timeout); + Check(net_setsockopt(datagram, 0xffff, 0x7fffffff, &timeout, option_size) == + Libs::Network::NET_ERROR_ENOPROTOOPT && + *net_errno == Libs::Posix::POSIX_ENOPROTOOPT && + net_getsockopt(datagram, 0xffff, 0x7fffffff, &timeout, &option_size) == + Libs::Network::NET_ERROR_ENOPROTOOPT && + *net_errno == Libs::Posix::POSIX_ENOPROTOOPT, + "Net unknown socket options return protocol-option errors"); +#if defined(__linux__) + Check(net_setsockopt(datagram, 0xffff, 0x1007, &timeout, option_size) == + Libs::Network::NET_ERROR_ENOPROTOOPT && + *net_errno == Libs::Posix::POSIX_ENOPROTOOPT, + "Net native protocol-option errors retain their guest error code"); +#endif received.fill(0); *net_errno = Libs::Posix::POSIX_EINVAL; Check(net_sendto(datagram_writer, payload, sizeof(payload), 0,