UDP P2P sockets

This commit is contained in:
nmzik
2026-09-17 23:15:26 +04:00
parent 57c5b206ca
commit cbedf5707f
+248 -66
View File
@@ -14,10 +14,11 @@
#endif
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
@@ -41,8 +42,11 @@
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace Libs::Network {
@@ -844,9 +848,37 @@ using SocketLength = socklen_t;
using SocketIoLength = size_t;
#endif
struct SocketTransport {
explicit SocketTransport(NativeSocket value): socket(value) {}
~SocketTransport() { Close(); }
int Close() {
if (socket == INVALID_NATIVE_SOCKET) {
return 0;
}
const auto value = std::exchange(socket, INVALID_NATIVE_SOCKET);
#if defined(_WIN32)
return closesocket(value);
#else
return ::close(value);
#endif
}
NativeSocket socket;
};
struct P2pEndpoint {
bool bound = false;
uint32_t address = 0;
uint16_t port = 0;
uint16_t virtual_port = 0;
int nonblocking = 0;
int broadcast = 0;
int send_buffer = 9216;
int receive_buffer = 40 * 1024;
};
struct SocketSlot {
bool used = false;
NativeSocket socket = INVALID_NATIVE_SOCKET;
std::shared_ptr<SocketTransport> transport;
std::optional<P2pEndpoint> p2p;
};
struct NetTimeval {
@@ -998,6 +1030,16 @@ static int ConvertSocketOptionLevel(int level) {
return (level == 0xffff ? SOL_SOCKET : level);
}
static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
switch (option) {
case 0x0020: return &endpoint.broadcast;
case 0x1001: return &endpoint.send_buffer;
case 0x1002: return &endpoint.receive_buffer;
case 0x1200: return &endpoint.nonblocking;
default: return nullptr;
}
}
static int ConvertMessageFlags(int flags) {
constexpr int guest_msg_peek = 0x00000002;
constexpr int guest_msg_dontroute = 0x00000004;
@@ -1104,19 +1146,30 @@ static int ConvertHostSockaddr(const sockaddr_storage* addr, SocketLength addrle
return 0;
}
static bool GetSocketBackend(int guest_fd, NativeSocket* out) {
static bool GetSocketBackend(int guest_fd, NativeSocket* out, SocketSlot* state = nullptr) {
EXIT_IF(out == nullptr);
if (guest_fd < 0 || guest_fd >= SOCKET_FD_MAX) {
SetGuestSocketError(Posix::POSIX_EBADF);
return false;
}
Common::LockGuard lock(g_socket_mutex);
if (!g_sockets[static_cast<size_t>(guest_fd)].used) {
const auto& slot = g_sockets[static_cast<size_t>(guest_fd)];
if (!slot.transport) {
SetGuestSocketError(Posix::POSIX_EBADF);
return false;
}
// Native-only operations cannot act on a transport shared by virtual ports.
if (slot.p2p && state == nullptr) {
SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
return false;
}
*out = g_sockets[static_cast<size_t>(guest_fd)].socket;
*out = slot.transport->socket;
if (state != nullptr) {
*state = slot;
}
return true;
}
@@ -1126,10 +1179,10 @@ bool KYTY_SYSV_ABI IsSocket(int s) {
}
Common::LockGuard lock(g_socket_mutex);
return g_sockets[static_cast<size_t>(s)].used;
return g_sockets[static_cast<size_t>(s)].transport != nullptr;
}
static bool TakeSocketBackend(int guest_fd, NativeSocket* out) {
static bool TakeSocketBackend(int guest_fd, SocketSlot* out) {
EXIT_IF(out == nullptr);
if (guest_fd < 0 || guest_fd >= SOCKET_FD_MAX) {
@@ -1138,22 +1191,24 @@ static bool TakeSocketBackend(int guest_fd, NativeSocket* out) {
Common::LockGuard lock(g_socket_mutex);
auto& slot = g_sockets[static_cast<size_t>(guest_fd)];
if (!slot.used) {
if (!slot.transport) {
return false;
}
*out = slot.socket;
*out = std::move(slot);
slot = {};
return true;
}
static int AllocSocketFd(NativeSocket socket) {
static int AllocSocketFd(const std::shared_ptr<SocketTransport>& transport, bool p2p = false) {
Common::LockGuard lock(g_socket_mutex);
for (int fd = SOCKET_FD_MIN; fd < SOCKET_FD_MAX; fd++) {
auto& slot = g_sockets[static_cast<size_t>(fd)];
if (!slot.used) {
slot.used = true;
slot.socket = socket;
if (!slot.transport) {
slot.transport = transport;
if (p2p) {
slot.p2p.emplace();
}
return fd;
}
}
@@ -1528,10 +1583,10 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
return 0;
}
#if defined(_WIN32)
struct HostRegistration {
EpollRegistration guest;
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
};
fd_set host_read {};
@@ -1543,14 +1598,23 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
std::vector<HostRegistration> host_registrations;
host_registrations.reserve(registrations.size());
int host_nfds = 0;
for (const auto& registration: registrations) {
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(registration.id, &socket)) {
SocketSlot state;
if (!GetSocketBackend(registration.id, &socket, &state)) {
continue;
}
#if defined(_WIN32)
if (host_registrations.size() >= 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
if ((registration.event.events & EPOLL_IN) != 0) {
FD_SET(socket, &host_read);
}
@@ -1558,7 +1622,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
FD_SET(socket, &host_write);
}
FD_SET(socket, &host_except);
host_registrations.push_back({registration, socket});
host_registrations.push_back({registration, socket, std::move(state)});
}
if (host_registrations.empty()) {
@@ -1573,8 +1637,8 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
host_timeout_ptr = &host_timeout;
}
const int result = ::select(0, &host_read, &host_write, &host_except, host_timeout_ptr);
if (result == SOCKET_ERROR) {
const int result = ::select(host_nfds, &host_read, &host_write, &host_except, host_timeout_ptr);
if (result < 0) {
return SetHostSocketError();
}
if (result == 0) {
@@ -1584,10 +1648,15 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
int count = 0;
for (const auto& registration: host_registrations) {
uint32_t ready = 0;
if (FD_ISSET(registration.socket, &host_read)) {
if ((registration.guest.event.events & EPOLL_IN) != 0 &&
FD_ISSET(registration.socket, &host_read)) {
if (registration.state.p2p) {
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
}
ready |= EPOLL_IN;
}
if (FD_ISSET(registration.socket, &host_write)) {
if ((registration.guest.event.events & EPOLL_OUT) != 0 &&
FD_ISSET(registration.socket, &host_write)) {
ready |= EPOLL_OUT;
}
if (FD_ISSET(registration.socket, &host_except)) {
@@ -1607,10 +1676,6 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
}
}
return count;
#else
(void)timeout;
return SetGuestSocketError(Posix::POSIX_ENOSYS);
#endif
}
int KYTY_SYSV_ABI EpollDestroy(int eid) {
@@ -1632,18 +1697,13 @@ int KYTY_SYSV_ABI SocketClose(int s) {
LOGF("\t s = %d\n", s);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!TakeSocketBackend(s, &socket)) {
SocketSlot slot;
if (!TakeSocketBackend(s, &slot)) {
return NET_ERROR_EBADF;
}
RemoveSocketFromEpolls(s);
#if defined(_WIN32)
const int result = closesocket(socket);
#else
const int result = ::close(socket);
#endif
if (result != 0) {
if (slot.transport.use_count() == 1 && slot.transport->Close() != 0) {
return NET_ERROR_EBADF;
}
@@ -1668,18 +1728,31 @@ int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
return -1;
}
NativeSocket socket = ::socket(host_family, type, protocol);
constexpr int socket_dgram_p2p = 6;
const bool p2p = type == socket_dgram_p2p;
if (p2p && (family != 2 || protocol != 0)) {
return SetGuestSocketError(Posix::POSIX_EPROTONOSUPPORT);
}
NativeSocket socket = ::socket(host_family, p2p ? SOCK_DGRAM : type, protocol);
if (socket == INVALID_NATIVE_SOCKET) {
return SetHostSocketError();
}
const int fd = AllocSocketFd(socket);
if (fd < 0) {
auto transport = std::make_shared<SocketTransport>(socket);
if (p2p) {
// Logical sockets keep their own blocking mode; the shared transport never blocks.
#if defined(_WIN32)
closesocket(socket);
u_long enabled = 1;
const int result = ioctlsocket(socket, FIONBIO, &enabled);
#else
::close(socket);
const int result = ::fcntl(socket, F_SETFL, O_NONBLOCK);
#endif
if (result != 0) {
return SetHostSocketError();
}
}
const int fd = AllocSocketFd(transport, p2p);
if (fd < 0) {
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
return -1;
}
@@ -1697,8 +1770,11 @@ int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
s, reinterpret_cast<uint64_t>(addr), addrlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (addr == nullptr || !GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = (addr == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
SocketSlot state;
if (addr == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
@@ -1708,6 +1784,67 @@ int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
return -1;
}
if (state.p2p) {
if (addrlen < 16) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
const auto& in = *reinterpret_cast<const sockaddr_in*>(&host_addr);
uint32_t address = 0;
std::memcpy(&address, &in.sin_addr, sizeof(address));
uint16_t virtual_port = 0;
std::memcpy(&virtual_port, static_cast<const uint8_t*>(addr) + 8, sizeof(virtual_port));
const auto requested_port = ntohs(virtual_port);
if (requested_port == 0) {
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
}
if (ntohs(in.sin_port) != 3658) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (requested_port == 5353 || requested_port >= 32768) {
return SetGuestSocketError(Posix::POSIX_EACCES);
}
Common::LockGuard lock(g_socket_mutex);
auto& slot = g_sockets[static_cast<size_t>(s)];
if (slot.transport != state.transport) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto& endpoint = *slot.p2p;
if (endpoint.bound) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
auto conflicts = [&](uint16_t port) {
return std::any_of(g_sockets.begin(), g_sockets.end(), [&](const auto& other) {
return other.p2p && other.p2p->bound && other.p2p->port == in.sin_port &&
other.p2p->address == address && other.p2p->virtual_port == port;
});
};
if (conflicts(virtual_port)) {
return SetGuestSocketError(Posix::POSIX_EADDRINUSE);
}
std::shared_ptr<SocketTransport> shared;
for (const auto& other: g_sockets) {
if (other.p2p && other.p2p->bound && other.p2p->port == in.sin_port &&
other.p2p->address == address) {
shared = other.transport;
break;
}
}
if (shared) {
slot.transport = std::move(shared);
} else if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) !=
0) {
return SetHostSocketError();
}
endpoint.bound = true;
endpoint.address = address;
endpoint.port = in.sin_port;
endpoint.virtual_port = virtual_port;
LOGF("\t UDPP2P bound UDP port=%u virtual port=%u\n", ntohs(endpoint.port),
ntohs(endpoint.virtual_port));
return 0;
}
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) != 0) {
return SetHostSocketError();
}
@@ -1724,8 +1861,10 @@ int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
s, reinterpret_cast<uint64_t>(addr), addrlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (addr == nullptr || !GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = (addr == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
if (addr == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (!GetSocketBackend(s, &socket)) {
return -1;
}
@@ -1751,7 +1890,6 @@ int KYTY_SYSV_ABI Listen(int s, int backlog) {
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
return -1;
}
@@ -1772,7 +1910,6 @@ int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
return -1;
}
@@ -1784,13 +1921,9 @@ int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
return SetHostSocketError();
}
const int fd = AllocSocketFd(accepted);
auto transport = std::make_shared<SocketTransport>(accepted);
const int fd = AllocSocketFd(transport);
if (fd < 0) {
#if defined(_WIN32)
closesocket(accepted);
#else
::close(accepted);
#endif
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
return -1;
}
@@ -1819,7 +1952,6 @@ int KYTY_SYSV_ABI Shutdown(int s, int how) {
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
return -1;
}
@@ -1848,12 +1980,14 @@ int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
"\t addrlen = 0x%016" PRIx64 "\n",
s, reinterpret_cast<uint64_t>(addr), reinterpret_cast<uint64_t>(addrlen));
NativeSocket socket = INVALID_NATIVE_SOCKET;
const bool socket_ok = GetSocketBackend(s, &socket);
if (addr == nullptr || addrlen == nullptr || !socket_ok) {
*Posix::GetErrorAddr() = (!socket_ok ? Posix::POSIX_EBADF : Posix::POSIX_EFAULT);
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
if (addr == nullptr || addrlen == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
sockaddr_storage host_addr {};
SocketLength host_addrlen = sizeof(host_addr);
@@ -1861,7 +1995,11 @@ int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
return SetHostSocketError();
}
return ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
const int result = ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
if (result == 0 && state.p2p) {
std::memcpy(static_cast<uint8_t*>(addr) + 8, &state.p2p->virtual_port, sizeof(uint16_t));
}
return result;
}
int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) {
@@ -1872,12 +2010,26 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
"\t optname = 0x%08" PRIx32 "\n",
s, static_cast<uint32_t>(level), static_cast<uint32_t>(optname));
NativeSocket socket = INVALID_NATIVE_SOCKET;
const bool socket_ok = GetSocketBackend(s, &socket);
if (optval == nullptr || optlen == nullptr || !socket_ok) {
*Posix::GetErrorAddr() = (!socket_ok ? Posix::POSIX_EBADF : Posix::POSIX_EFAULT);
NativeSocket socket = INVALID_NATIVE_SOCKET;
SocketSlot state;
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
if (optval == nullptr || optlen == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (state.p2p) {
const auto* value = level == 0xffff ? P2pSocketOption(*state.p2p, optname) : nullptr;
if (value == nullptr) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
if (*optlen < sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::memcpy(optval, value, sizeof(int));
*optlen = sizeof(int);
return 0;
}
// Guest socket options: SOL_SOCKET=0xffff, SO_ERROR=0x1007.
const bool socket_error = (level == 0xffff && optname == 0x1007);
@@ -1912,10 +2064,38 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
s, static_cast<uint32_t>(level), static_cast<uint32_t>(optname), optlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (optval == nullptr || !GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = (optval == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
SocketSlot state;
if (optval == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (!GetSocketBackend(s, &socket, &state)) {
return -1;
}
if (state.p2p) {
Common::LockGuard lock(g_socket_mutex);
auto& slot = g_sockets[static_cast<size_t>(s)];
if (slot.transport != state.transport) {
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto* option = level == 0xffff ? P2pSocketOption(*slot.p2p, optname) : nullptr;
if (option == nullptr) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int value = 0;
std::memcpy(&value, optval, sizeof(value));
if (optname == 0x1001 || optname == 0x1002) {
if (value <= 0) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
*option = optname == 0x1002 ? std::min(value, 512 * 1024) : value;
} else {
*option = value != 0;
}
return 0;
}
#if defined(_WIN32)
constexpr int ORBIS_SO_NBIO = 0x1200;
@@ -1962,8 +2142,11 @@ int64_t KYTY_SYSV_ABI Sendto(int s, const void* buf, uint64_t len, int flags, co
reinterpret_cast<uint64_t>(addr), addrlen);
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (buf == nullptr || !GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = (buf == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
if (buf == nullptr) {
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
// UDPP2P requires protocol framing; the native-only lookup rejects raw sends.
if (!GetSocketBackend(s, &socket)) {
return -1;
}
@@ -2017,7 +2200,6 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
NativeSocket socket = INVALID_NATIVE_SOCKET;
if (!GetSocketBackend(s, &socket)) {
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
return -1;
}
@@ -2107,7 +2289,7 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
} else
#endif
if (!GetSocketBackend(fd, &socket)) {
return SetGuestSocketError(Posix::POSIX_EBADF);
return -1;
}
#if defined(_WIN32)
if ((read && host_read.fd_count == FD_SETSIZE) ||