diff --git a/src/libs/network.cpp b/src/libs/network.cpp index 28201e751..02e3cf450 100644 --- a/src/libs/network.cpp +++ b/src/libs/network.cpp @@ -14,10 +14,11 @@ #endif #else #include +#include #include #include -#include #include +#include #include #endif @@ -41,8 +42,11 @@ #include #include #include +#include #include +#include #include +#include #include 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 transport; + std::optional 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(guest_fd)].used) { + const auto& slot = g_sockets[static_cast(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(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(s)].used; + return g_sockets[static_cast(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(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& 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(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 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(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(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(&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(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(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 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(&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(&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(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(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(addr), reinterpret_cast(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(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(level), static_cast(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(level), static_cast(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(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(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) ||