diff options
Diffstat (limited to 'src/core/internal_network')
-rw-r--r-- | src/core/internal_network/network.cpp | 639 | ||||
-rw-r--r-- | src/core/internal_network/network.h | 84 | ||||
-rw-r--r-- | src/core/internal_network/network_interface.cpp | 219 | ||||
-rw-r--r-- | src/core/internal_network/network_interface.h | 29 | ||||
-rw-r--r-- | src/core/internal_network/socket_proxy.cpp | 296 | ||||
-rw-r--r-- | src/core/internal_network/socket_proxy.h | 97 | ||||
-rw-r--r-- | src/core/internal_network/sockets.h | 174 |
7 files changed, 1538 insertions, 0 deletions
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp new file mode 100644 index 000000000..447fbffaa --- /dev/null +++ b/src/core/internal_network/network.cpp @@ -0,0 +1,639 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <cstring> +#include <limits> +#include <utility> +#include <vector> + +#include "common/error.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#elif YUZU_UNIX +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <netinet/in.h> +#include <poll.h> +#include <sys/socket.h> +#include <unistd.h> +#else +#error "Unimplemented platform" +#endif + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" +#include "core/internal_network/sockets.h" +#include "network/network.h" + +namespace Network { + +namespace { + +#ifdef _WIN32 + +using socklen_t = int; + +void Initialize() { + WSADATA wsa_data; + (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); +} + +void Finalize() { + WSACleanup(); +} + +sockaddr TranslateFromSockAddrIn(SockAddrIn input) { + sockaddr_in result; + +#if YUZU_UNIX + result.sin_len = sizeof(result); +#endif + + switch (static_cast<Domain>(input.family)) { + case Domain::INET: + result.sin_family = AF_INET; + break; + default: + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family); + result.sin_family = AF_INET; + break; + } + + result.sin_port = htons(input.portno); + + auto& ip = result.sin_addr.S_un.S_un_b; + ip.s_b1 = input.ip[0]; + ip.s_b2 = input.ip[1]; + ip.s_b3 = input.ip[2]; + ip.s_b4 = input.ip[3]; + + sockaddr addr; + std::memcpy(&addr, &result, sizeof(addr)); + return addr; +} + +LINGER MakeLinger(bool enable, u32 linger_value) { + ASSERT(linger_value <= std::numeric_limits<u_short>::max()); + + LINGER value; + value.l_onoff = enable ? 1 : 0; + value.l_linger = static_cast<u_short>(linger_value); + return value; +} + +bool EnableNonBlock(SOCKET fd, bool enable) { + u_long value = enable ? 1 : 0; + return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; +} + +Errno TranslateNativeError(int e) { + switch (e) { + case WSAEBADF: + return Errno::BADF; + case WSAEINVAL: + return Errno::INVAL; + case WSAEMFILE: + return Errno::MFILE; + case WSAENOTCONN: + return Errno::NOTCONN; + case WSAEWOULDBLOCK: + return Errno::AGAIN; + case WSAECONNREFUSED: + return Errno::CONNREFUSED; + case WSAEHOSTUNREACH: + return Errno::HOSTUNREACH; + case WSAENETDOWN: + return Errno::NETDOWN; + case WSAENETUNREACH: + return Errno::NETUNREACH; + case WSAEMSGSIZE: + return Errno::MSGSIZE; + default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", e); + return Errno::OTHER; + } +} + +#elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX + +using SOCKET = int; +using WSAPOLLFD = pollfd; +using ULONG = u64; + +constexpr SOCKET SOCKET_ERROR = -1; + +constexpr int SD_RECEIVE = SHUT_RD; +constexpr int SD_SEND = SHUT_WR; +constexpr int SD_BOTH = SHUT_RDWR; + +void Initialize() {} + +void Finalize() {} + +sockaddr TranslateFromSockAddrIn(SockAddrIn input) { + sockaddr_in result; + + switch (static_cast<Domain>(input.family)) { + case Domain::INET: + result.sin_family = AF_INET; + break; + default: + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family); + result.sin_family = AF_INET; + break; + } + + result.sin_port = htons(input.portno); + + result.sin_addr.s_addr = input.ip[0] | input.ip[1] << 8 | input.ip[2] << 16 | input.ip[3] << 24; + + sockaddr addr; + std::memcpy(&addr, &result, sizeof(addr)); + return addr; +} + +int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) { + return poll(fds, static_cast<nfds_t>(nfds), timeout); +} + +int closesocket(SOCKET fd) { + return close(fd); +} + +linger MakeLinger(bool enable, u32 linger_value) { + linger value; + value.l_onoff = enable ? 1 : 0; + value.l_linger = linger_value; + return value; +} + +bool EnableNonBlock(int fd, bool enable) { + int flags = fcntl(fd, F_GETFL); + if (flags == -1) { + return false; + } + if (enable) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + return fcntl(fd, F_SETFL, flags) == 0; +} + +Errno TranslateNativeError(int e) { + switch (e) { + case EBADF: + return Errno::BADF; + case EINVAL: + return Errno::INVAL; + case EMFILE: + return Errno::MFILE; + case ENOTCONN: + return Errno::NOTCONN; + case EAGAIN: + return Errno::AGAIN; + case ECONNREFUSED: + return Errno::CONNREFUSED; + case EHOSTUNREACH: + return Errno::HOSTUNREACH; + case ENETDOWN: + return Errno::NETDOWN; + case ENETUNREACH: + return Errno::NETUNREACH; + case EMSGSIZE: + return Errno::MSGSIZE; + default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", e); + return Errno::OTHER; + } +} + +#endif + +Errno GetAndLogLastError() { +#ifdef _WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + const Errno err = TranslateNativeError(e); + if (err == Errno::AGAIN) { + return err; + } + LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); + return err; +} + +int TranslateDomain(Domain domain) { + switch (domain) { + case Domain::INET: + return AF_INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); + return 0; + } +} + +int TranslateType(Type type) { + switch (type) { + case Type::STREAM: + return SOCK_STREAM; + case Type::DGRAM: + return SOCK_DGRAM; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return 0; + } +} + +int TranslateProtocol(Protocol protocol) { + switch (protocol) { + case Protocol::TCP: + return IPPROTO_TCP; + case Protocol::UDP: + return IPPROTO_UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); + return 0; + } +} + +SockAddrIn TranslateToSockAddrIn(sockaddr input_) { + sockaddr_in input; + std::memcpy(&input, &input_, sizeof(input)); + + SockAddrIn result; + + switch (input.sin_family) { + case AF_INET: + result.family = Domain::INET; + break; + default: + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family); + result.family = Domain::INET; + break; + } + + result.portno = ntohs(input.sin_port); + + result.ip = TranslateIPv4(input.sin_addr); + + return result; +} + +short TranslatePollEvents(PollEvents events) { + short result = 0; + + if (True(events & PollEvents::In)) { + events &= ~PollEvents::In; + result |= POLLIN; + } + if (True(events & PollEvents::Pri)) { + events &= ~PollEvents::Pri; +#ifdef _WIN32 + LOG_WARNING(Service, "Winsock doesn't support POLLPRI"); +#else + result |= POLLPRI; +#endif + } + if (True(events & PollEvents::Out)) { + events &= ~PollEvents::Out; + result |= POLLOUT; + } + + UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events); + + return result; +} + +PollEvents TranslatePollRevents(short revents) { + PollEvents result{}; + const auto translate = [&result, &revents](short host, PollEvents guest) { + if ((revents & host) != 0) { + revents &= static_cast<short>(~host); + result |= guest; + } + }; + + translate(POLLIN, PollEvents::In); + translate(POLLPRI, PollEvents::Pri); + translate(POLLOUT, PollEvents::Out); + translate(POLLERR, PollEvents::Err); + translate(POLLHUP, PollEvents::Hup); + + UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents); + + return result; +} + +} // Anonymous namespace + +NetworkInstance::NetworkInstance() { + Initialize(); +} + +NetworkInstance::~NetworkInstance() { + Finalize(); +} + +std::optional<IPv4Address> GetHostIPv4Address() { + const auto network_interface = Network::GetSelectedNetworkInterface(); + if (!network_interface.has_value()) { + LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); + return {}; + } + + std::array<char, 16> ip_addr = {}; + ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) != + nullptr); + return TranslateIPv4(network_interface->ip_address); +} + +std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { + const size_t num = pollfds.size(); + + std::vector<WSAPOLLFD> host_pollfds(pollfds.size()); + std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) { + WSAPOLLFD result; + result.fd = fd.socket->GetFD(); + result.events = TranslatePollEvents(fd.events); + result.revents = 0; + return result; + }); + + const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); + if (result == 0) { + ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), + [](WSAPOLLFD fd) { return fd.revents == 0; })); + return {0, Errno::SUCCESS}; + } + + for (size_t i = 0; i < num; ++i) { + pollfds[i].revents = TranslatePollRevents(host_pollfds[i].revents); + } + + if (result > 0) { + return {result, Errno::SUCCESS}; + } + + ASSERT(result == SOCKET_ERROR); + + return {-1, GetAndLogLastError()}; +} + +Socket::~Socket() { + if (fd == INVALID_SOCKET) { + return; + } + (void)closesocket(fd); + fd = INVALID_SOCKET; +} + +Socket::Socket(Socket&& rhs) noexcept { + fd = std::exchange(rhs.fd, INVALID_SOCKET); +} + +template <typename T> +Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) { + const int result = + setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value)); + if (result != SOCKET_ERROR) { + return Errno::SUCCESS; + } + return GetAndLogLastError(); +} + +Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { + fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol)); + if (fd != INVALID_SOCKET) { + return Errno::SUCCESS; + } + + return GetAndLogLastError(); +} + +std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + const SOCKET new_socket = accept(fd, &addr, &addrlen); + + if (new_socket == INVALID_SOCKET) { + return {AcceptResult{}, GetAndLogLastError()}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + + AcceptResult result{ + .socket = std::make_unique<Socket>(new_socket), + .sockaddr_in = TranslateToSockAddrIn(addr), + }; + + return {std::move(result), Errno::SUCCESS}; +} + +Errno Socket::Connect(SockAddrIn addr_in) { + const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in); + if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + return GetAndLogLastError(); +} + +std::pair<SockAddrIn, Errno> Socket::GetPeerName() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { + return {SockAddrIn{}, GetAndLogLastError()}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; +} + +std::pair<SockAddrIn, Errno> Socket::GetSockName() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { + return {SockAddrIn{}, GetAndLogLastError()}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; +} + +Errno Socket::Bind(SockAddrIn addr) { + const sockaddr addr_in = TranslateFromSockAddrIn(addr); + if (bind(fd, &addr_in, sizeof(addr_in)) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + return GetAndLogLastError(); +} + +Errno Socket::Listen(s32 backlog) { + if (listen(fd, backlog) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + return GetAndLogLastError(); +} + +Errno Socket::Shutdown(ShutdownHow how) { + int host_how = 0; + switch (how) { + case ShutdownHow::RD: + host_how = SD_RECEIVE; + break; + case ShutdownHow::WR: + host_how = SD_SEND; + break; + case ShutdownHow::RDWR: + host_how = SD_BOTH; + break; + default: + UNIMPLEMENTED_MSG("Unimplemented flag how={}", how); + return Errno::SUCCESS; + } + if (shutdown(fd, host_how) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + return GetAndLogLastError(); +} + +std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + const auto result = + recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0); + if (result != SOCKET_ERROR) { + return {static_cast<s32>(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + sockaddr addr_in{}; + socklen_t addrlen = sizeof(addr_in); + socklen_t* const p_addrlen = addr ? &addrlen : nullptr; + sockaddr* const p_addr_in = addr ? &addr_in : nullptr; + + const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()), + static_cast<int>(message.size()), 0, p_addr_in, p_addrlen); + if (result != SOCKET_ERROR) { + if (addr) { + ASSERT(addrlen == sizeof(addr_in)); + *addr = TranslateToSockAddrIn(addr_in); + } + return {static_cast<s32>(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) { + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + ASSERT(flags == 0); + + const auto result = send(fd, reinterpret_cast<const char*>(message.data()), + static_cast<int>(message.size()), 0); + if (result != SOCKET_ERROR) { + return {static_cast<s32>(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) { + ASSERT(flags == 0); + + const sockaddr* to = nullptr; + const int tolen = addr ? sizeof(sockaddr) : 0; + sockaddr host_addr_in; + + if (addr) { + host_addr_in = TranslateFromSockAddrIn(*addr); + to = &host_addr_in; + } + + const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()), + static_cast<int>(message.size()), 0, to, tolen); + if (result != SOCKET_ERROR) { + return {static_cast<s32>(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +Errno Socket::Close() { + [[maybe_unused]] const int result = closesocket(fd); + ASSERT(result == 0); + fd = INVALID_SOCKET; + + return Errno::SUCCESS; +} + +Errno Socket::SetLinger(bool enable, u32 linger) { + return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger)); +} + +Errno Socket::SetReuseAddr(bool enable) { + return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0); +} + +Errno Socket::SetKeepAlive(bool enable) { + return SetSockOpt<u32>(fd, SO_KEEPALIVE, enable ? 1 : 0); +} + +Errno Socket::SetBroadcast(bool enable) { + return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0); +} + +Errno Socket::SetSndBuf(u32 value) { + return SetSockOpt(fd, SO_SNDBUF, value); +} + +Errno Socket::SetRcvBuf(u32 value) { + return SetSockOpt(fd, SO_RCVBUF, value); +} + +Errno Socket::SetSndTimeo(u32 value) { + return SetSockOpt(fd, SO_SNDTIMEO, value); +} + +Errno Socket::SetRcvTimeo(u32 value) { + return SetSockOpt(fd, SO_RCVTIMEO, value); +} + +Errno Socket::SetNonBlock(bool enable) { + if (EnableNonBlock(fd, enable)) { + return Errno::SUCCESS; + } + return GetAndLogLastError(); +} + +bool Socket::IsOpened() const { + return fd != INVALID_SOCKET; +} + +void Socket::HandleProxyPacket(const ProxyPacket& packet) { + LOG_WARNING(Network, "ProxyPacket received, but not in Proxy mode!"); +} + +} // namespace Network diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h new file mode 100644 index 000000000..36994c22e --- /dev/null +++ b/src/core/internal_network/network.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <optional> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/socket_types.h" + +#ifdef _WIN32 +#include <winsock2.h> +#elif YUZU_UNIX +#include <netinet/in.h> +#endif + +namespace Network { + +class SocketBase; +class Socket; + +/// Error code for network functions +enum class Errno { + SUCCESS, + BADF, + INVAL, + MFILE, + NOTCONN, + AGAIN, + CONNREFUSED, + HOSTUNREACH, + NETDOWN, + NETUNREACH, + TIMEDOUT, + MSGSIZE, + OTHER, +}; + +/// Cross-platform poll fd structure + +enum class PollEvents : u16 { + // Using Pascal case because IN is a macro on Windows. + In = 1 << 0, + Pri = 1 << 1, + Out = 1 << 2, + Err = 1 << 3, + Hup = 1 << 4, + Nval = 1 << 5, +}; + +DECLARE_ENUM_FLAG_OPERATORS(PollEvents); + +struct PollFD { + SocketBase* socket; + PollEvents events; + PollEvents revents; +}; + +class NetworkInstance { +public: + explicit NetworkInstance(); + ~NetworkInstance(); +}; + +#ifdef _WIN32 +constexpr IPv4Address TranslateIPv4(in_addr addr) { + auto& bytes = addr.S_un.S_un_b; + return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4}; +} +#elif YUZU_UNIX +constexpr IPv4Address TranslateIPv4(in_addr addr) { + const u32 bytes = addr.s_addr; + return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8), + static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)}; +} +#endif + +/// @brief Returns host's IPv4 address +/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array +std::optional<IPv4Address> GetHostIPv4Address(); + +} // namespace Network diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp new file mode 100644 index 000000000..057fd3661 --- /dev/null +++ b/src/core/internal_network/network_interface.cpp @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <fstream> +#include <sstream> +#include <vector> + +#include "common/bit_cast.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/internal_network/network_interface.h" + +#ifdef _WIN32 +#include <iphlpapi.h> +#else +#include <cerrno> +#include <ifaddrs.h> +#include <net/if.h> +#endif + +namespace Network { + +#ifdef _WIN32 + +std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { + std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses; + DWORD ret = ERROR_BUFFER_OVERFLOW; + DWORD buf_size = 0; + + // retry up to 5 times + for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) { + ret = GetAdaptersAddresses( + AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, + nullptr, adapter_addresses.data(), &buf_size); + + if (ret != ERROR_BUFFER_OVERFLOW) { + break; + } + + adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1); + } + + if (ret != NO_ERROR) { + LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses"); + return {}; + } + + std::vector<NetworkInterface> result; + + for (auto current_address = adapter_addresses.data(); current_address != nullptr; + current_address = current_address->Next) { + if (current_address->FirstUnicastAddress == nullptr || + current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) { + continue; + } + + if (current_address->OperStatus != IfOperStatusUp) { + continue; + } + + const auto ip_addr = Common::BitCast<struct sockaddr_in>( + *current_address->FirstUnicastAddress->Address.lpSockaddr) + .sin_addr; + + ULONG mask = 0; + if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength, + &mask) != NO_ERROR) { + LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask"); + continue; + } + + struct in_addr gateway = {.S_un{.S_addr{0}}}; + if (current_address->FirstGatewayAddress != nullptr && + current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) { + gateway = Common::BitCast<struct sockaddr_in>( + *current_address->FirstGatewayAddress->Address.lpSockaddr) + .sin_addr; + } + + result.emplace_back(NetworkInterface{ + .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})}, + .ip_address{ip_addr}, + .subnet_mask = in_addr{.S_un{.S_addr{mask}}}, + .gateway = gateway}); + } + + return result; +} + +#else + +std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { + struct ifaddrs* ifaddr = nullptr; + + if (getifaddrs(&ifaddr) != 0) { + LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}", + std::strerror(errno)); + return {}; + } + + std::vector<NetworkInterface> result; + + for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) { + continue; + } + + if (ifa->ifa_addr->sa_family != AF_INET) { + continue; + } + + if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) { + continue; + } + + u32 gateway{}; + + std::ifstream file{"/proc/net/route"}; + if (!file.is_open()) { + LOG_ERROR(Network, "Failed to open \"/proc/net/route\""); + + result.emplace_back(NetworkInterface{ + .name{ifa->ifa_name}, + .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr}, + .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr}, + .gateway{in_addr{.s_addr = gateway}}}); + continue; + } + + // ignore header + file.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + + bool gateway_found = false; + + for (std::string line; std::getline(file, line);) { + std::istringstream iss{line}; + + std::string iface_name; + iss >> iface_name; + if (iface_name != ifa->ifa_name) { + continue; + } + + iss >> std::hex; + + u32 dest{}; + iss >> dest; + if (dest != 0) { + // not the default route + continue; + } + + iss >> gateway; + + u16 flags{}; + iss >> flags; + + // flag RTF_GATEWAY (defined in <linux/route.h>) + if ((flags & 0x2) == 0) { + continue; + } + + gateway_found = true; + break; + } + + if (!gateway_found) { + gateway = 0; + } + + result.emplace_back(NetworkInterface{ + .name{ifa->ifa_name}, + .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr}, + .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr}, + .gateway{in_addr{.s_addr = gateway}}}); + } + + freeifaddrs(ifaddr); + + return result; +} + +#endif + +std::optional<NetworkInterface> GetSelectedNetworkInterface() { + const auto& selected_network_interface = Settings::values.network_interface.GetValue(); + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); + if (network_interfaces.empty()) { + LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); + return std::nullopt; + } + + const auto res = + std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) { + return iface.name == selected_network_interface; + }); + + if (res == network_interfaces.end()) { + LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + return std::nullopt; + } + + return *res; +} + +void SelectFirstNetworkInterface() { + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); + + if (network_interfaces.empty()) { + return; + } + + Settings::values.network_interface.SetValue(network_interfaces[0].name); +} + +} // namespace Network diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h new file mode 100644 index 000000000..175e61b1f --- /dev/null +++ b/src/core/internal_network/network_interface.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <optional> +#include <string> +#include <vector> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#endif + +namespace Network { + +struct NetworkInterface { + std::string name; + struct in_addr ip_address; + struct in_addr subnet_mask; + struct in_addr gateway; +}; + +std::vector<NetworkInterface> GetAvailableNetworkInterfaces(); +std::optional<NetworkInterface> GetSelectedNetworkInterface(); +void SelectFirstNetworkInterface(); + +} // namespace Network diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp new file mode 100644 index 000000000..7d5d37bbc --- /dev/null +++ b/src/core/internal_network/socket_proxy.cpp @@ -0,0 +1,296 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <chrono> +#include <thread> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/zstd_compression.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" +#include "core/internal_network/socket_proxy.h" + +namespace Network { + +ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {} + +ProxySocket::~ProxySocket() { + if (fd == INVALID_SOCKET) { + return; + } + fd = INVALID_SOCKET; +} + +void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { + if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno || + closed) { + return; + } + + if (!broadcast && packet.broadcast) { + LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode"); + return; + } + + auto decompressed = packet; + decompressed.data = Common::Compression::DecompressDataZSTD(packet.data); + + std::lock_guard guard(packets_mutex); + received_packets.push(decompressed); +} + +template <typename T> +Errno ProxySocket::SetSockOpt(SOCKET fd_, int option, T value) { + LOG_DEBUG(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) { + protocol = socket_protocol; + SetSockOpt(fd, SO_TYPE, type); + + return Errno::SUCCESS; +} + +std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() { + LOG_WARNING(Network, "(STUBBED) called"); + return {AcceptResult{}, Errno::SUCCESS}; +} + +Errno ProxySocket::Connect(SockAddrIn addr_in) { + LOG_WARNING(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +std::pair<SockAddrIn, Errno> ProxySocket::GetPeerName() { + LOG_WARNING(Network, "(STUBBED) called"); + return {SockAddrIn{}, Errno::SUCCESS}; +} + +std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() { + LOG_WARNING(Network, "(STUBBED) called"); + return {SockAddrIn{}, Errno::SUCCESS}; +} + +Errno ProxySocket::Bind(SockAddrIn addr) { + if (is_bound) { + LOG_WARNING(Network, "Rebinding Socket is unimplemented!"); + return Errno::SUCCESS; + } + local_endpoint = addr; + is_bound = true; + + return Errno::SUCCESS; +} + +Errno ProxySocket::Listen(s32 backlog) { + LOG_WARNING(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +Errno ProxySocket::Shutdown(ShutdownHow how) { + LOG_WARNING(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) { + LOG_WARNING(Network, "(STUBBED) called"); + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + return {static_cast<s32>(0), Errno::SUCCESS}; +} + +std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + // TODO (flTobi): Verify the timeout behavior and break when connection is lost + const auto timestamp = std::chrono::steady_clock::now(); + // When receive_timeout is set to zero, the socket is supposed to wait indefinitely until a + // packet arrives. In order to prevent lost packets from hanging the emulation thread, we set + // the timeout to 5s instead + const auto timeout = receive_timeout == 0 ? 5000 : receive_timeout; + while (true) { + { + std::lock_guard guard(packets_mutex); + if (received_packets.size() > 0) { + return ReceivePacket(flags, message, addr, message.size()); + } + } + + if (!blocking) { + return {-1, Errno::AGAIN}; + } + + std::this_thread::yield(); + + const auto time_diff = std::chrono::steady_clock::now() - timestamp; + const auto time_diff_ms = + std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count(); + + if (time_diff_ms > timeout) { + return {-1, Errno::TIMEDOUT}; + } + } +} + +std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& message, + SockAddrIn* addr, std::size_t max_length) { + ProxyPacket& packet = received_packets.front(); + if (addr) { + addr->family = Domain::INET; + addr->ip = packet.local_endpoint.ip; // The senders ip address + addr->portno = packet.local_endpoint.portno; // The senders port number + } + + bool peek = (flags & FLAG_MSG_PEEK) != 0; + std::size_t read_bytes; + if (packet.data.size() > max_length) { + read_bytes = max_length; + message.clear(); + std::copy(packet.data.begin(), packet.data.begin() + read_bytes, + std::back_inserter(message)); + message.resize(max_length); + + if (protocol == Protocol::UDP) { + if (!peek) { + received_packets.pop(); + } + return {-1, Errno::MSGSIZE}; + } else if (protocol == Protocol::TCP) { + std::vector<u8> numArray(packet.data.size() - max_length); + std::copy(packet.data.begin() + max_length, packet.data.end(), + std::back_inserter(numArray)); + packet.data = numArray; + } + } else { + read_bytes = packet.data.size(); + message.clear(); + std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message)); + message.resize(max_length); + if (!peek) { + received_packets.pop(); + } + } + + return {static_cast<u32>(read_bytes), Errno::SUCCESS}; +} + +std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flags) { + LOG_WARNING(Network, "(STUBBED) called"); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + ASSERT(flags == 0); + + return {static_cast<s32>(0), Errno::SUCCESS}; +} + +void ProxySocket::SendPacket(ProxyPacket& packet) { + if (auto room_member = room_network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), + packet.data.size()); + room_member->SendProxyPacket(packet); + } + } +} + +std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) { + ASSERT(flags == 0); + + if (!is_bound) { + LOG_ERROR(Network, "ProxySocket is not bound!"); + return {static_cast<s32>(message.size()), Errno::SUCCESS}; + } + + if (auto room_member = room_network.GetRoomMember().lock()) { + if (!room_member->IsConnected()) { + return {static_cast<s32>(message.size()), Errno::SUCCESS}; + } + } + + ProxyPacket packet; + packet.local_endpoint = local_endpoint; + packet.remote_endpoint = *addr; + packet.protocol = protocol; + packet.broadcast = broadcast && packet.remote_endpoint.ip[3] == 255; + + auto& ip = local_endpoint.ip; + auto ipv4 = Network::GetHostIPv4Address(); + // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address, + // replace it with a "fake" routing address + if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) { + if (auto room_member = room_network.GetRoomMember().lock()) { + packet.local_endpoint.ip = room_member->GetFakeIpAddress(); + } + } + + packet.data.clear(); + std::copy(message.begin(), message.end(), std::back_inserter(packet.data)); + + SendPacket(packet); + + return {static_cast<s32>(message.size()), Errno::SUCCESS}; +} + +Errno ProxySocket::Close() { + fd = INVALID_SOCKET; + closed = true; + + return Errno::SUCCESS; +} + +Errno ProxySocket::SetLinger(bool enable, u32 linger) { + struct Linger { + u16 linger_enable; + u16 linger_time; + } values; + values.linger_enable = enable ? 1 : 0; + values.linger_time = static_cast<u16>(linger); + + return SetSockOpt(fd, SO_LINGER, values); +} + +Errno ProxySocket::SetReuseAddr(bool enable) { + return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0); +} + +Errno ProxySocket::SetBroadcast(bool enable) { + broadcast = enable; + return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0); +} + +Errno ProxySocket::SetSndBuf(u32 value) { + return SetSockOpt(fd, SO_SNDBUF, value); +} + +Errno ProxySocket::SetKeepAlive(bool enable) { + return Errno::SUCCESS; +} + +Errno ProxySocket::SetRcvBuf(u32 value) { + return SetSockOpt(fd, SO_RCVBUF, value); +} + +Errno ProxySocket::SetSndTimeo(u32 value) { + send_timeout = value; + return SetSockOpt(fd, SO_SNDTIMEO, static_cast<int>(value)); +} + +Errno ProxySocket::SetRcvTimeo(u32 value) { + receive_timeout = value; + return SetSockOpt(fd, SO_RCVTIMEO, static_cast<int>(value)); +} + +Errno ProxySocket::SetNonBlock(bool enable) { + blocking = !enable; + return Errno::SUCCESS; +} + +bool ProxySocket::IsOpened() const { + return fd != INVALID_SOCKET; +} + +} // namespace Network diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h new file mode 100644 index 000000000..f12b5f567 --- /dev/null +++ b/src/core/internal_network/socket_proxy.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <mutex> +#include <vector> +#include <queue> + +#include "common/common_funcs.h" +#include "core/internal_network/sockets.h" +#include "network/network.h" + +namespace Network { + +class ProxySocket : public SocketBase { +public: + YUZU_NON_COPYABLE(ProxySocket); + YUZU_NON_MOVEABLE(ProxySocket); + + explicit ProxySocket(RoomNetwork& room_network_) noexcept; + ~ProxySocket() override; + + void HandleProxyPacket(const ProxyPacket& packet) override; + + Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override; + + Errno Close() override; + + std::pair<AcceptResult, Errno> Accept() override; + + Errno Connect(SockAddrIn addr_in) override; + + std::pair<SockAddrIn, Errno> GetPeerName() override; + + std::pair<SockAddrIn, Errno> GetSockName() override; + + Errno Bind(SockAddrIn addr) override; + + Errno Listen(s32 backlog) override; + + Errno Shutdown(ShutdownHow how) override; + + std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override; + + std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override; + + std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr, + std::size_t max_length); + + std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override; + + void SendPacket(ProxyPacket& packet); + + std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) override; + + Errno SetLinger(bool enable, u32 linger) override; + + Errno SetReuseAddr(bool enable) override; + + Errno SetBroadcast(bool enable) override; + + Errno SetKeepAlive(bool enable) override; + + Errno SetSndBuf(u32 value) override; + + Errno SetRcvBuf(u32 value) override; + + Errno SetSndTimeo(u32 value) override; + + Errno SetRcvTimeo(u32 value) override; + + Errno SetNonBlock(bool enable) override; + + template <typename T> + Errno SetSockOpt(SOCKET fd, int option, T value); + + bool IsOpened() const override; + +private: + bool broadcast = false; + bool closed = false; + u32 send_timeout = 0; + u32 receive_timeout = 0; + bool is_bound = false; + SockAddrIn local_endpoint{}; + bool blocking = true; + std::queue<ProxyPacket> received_packets; + Protocol protocol; + + std::mutex packets_mutex; + + RoomNetwork& room_network; +}; + +} // namespace Network diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h new file mode 100644 index 000000000..2e328c645 --- /dev/null +++ b/src/core/internal_network/sockets.h @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <map> +#include <memory> +#include <utility> + +#if defined(_WIN32) +#elif !YUZU_UNIX +#error "Platform not implemented" +#endif + +#include "common/common_types.h" +#include "core/internal_network/network.h" +#include "network/network.h" + +// TODO: C++20 Replace std::vector usages with std::span + +namespace Network { + +class SocketBase { +public: +#ifdef YUZU_UNIX + using SOCKET = int; + static constexpr SOCKET INVALID_SOCKET = -1; + static constexpr SOCKET SOCKET_ERROR = -1; +#endif + + struct AcceptResult { + std::unique_ptr<SocketBase> socket; + SockAddrIn sockaddr_in; + }; + + SocketBase() = default; + explicit SocketBase(SOCKET fd_) : fd{fd_} {} + + virtual ~SocketBase() = default; + + virtual SocketBase& operator=(const SocketBase&) = delete; + + // Avoid closing sockets implicitly + virtual SocketBase& operator=(SocketBase&&) noexcept = delete; + + virtual Errno Initialize(Domain domain, Type type, Protocol protocol) = 0; + + virtual Errno Close() = 0; + + virtual std::pair<AcceptResult, Errno> Accept() = 0; + + virtual Errno Connect(SockAddrIn addr_in) = 0; + + virtual std::pair<SockAddrIn, Errno> GetPeerName() = 0; + + virtual std::pair<SockAddrIn, Errno> GetSockName() = 0; + + virtual Errno Bind(SockAddrIn addr) = 0; + + virtual Errno Listen(s32 backlog) = 0; + + virtual Errno Shutdown(ShutdownHow how) = 0; + + virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0; + + virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, + SockAddrIn* addr) = 0; + + virtual std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) = 0; + + virtual std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) = 0; + + virtual Errno SetLinger(bool enable, u32 linger) = 0; + + virtual Errno SetReuseAddr(bool enable) = 0; + + virtual Errno SetKeepAlive(bool enable) = 0; + + virtual Errno SetBroadcast(bool enable) = 0; + + virtual Errno SetSndBuf(u32 value) = 0; + + virtual Errno SetRcvBuf(u32 value) = 0; + + virtual Errno SetSndTimeo(u32 value) = 0; + + virtual Errno SetRcvTimeo(u32 value) = 0; + + virtual Errno SetNonBlock(bool enable) = 0; + + virtual bool IsOpened() const = 0; + + virtual void HandleProxyPacket(const ProxyPacket& packet) = 0; + + [[nodiscard]] SOCKET GetFD() const { + return fd; + } + +protected: + SOCKET fd = INVALID_SOCKET; +}; + +class Socket : public SocketBase { +public: + Socket() = default; + explicit Socket(SOCKET fd_) : SocketBase{fd_} {} + + ~Socket() override; + + Socket(const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + + Socket(Socket&& rhs) noexcept; + + // Avoid closing sockets implicitly + Socket& operator=(Socket&&) noexcept = delete; + + Errno Initialize(Domain domain, Type type, Protocol protocol) override; + + Errno Close() override; + + std::pair<AcceptResult, Errno> Accept() override; + + Errno Connect(SockAddrIn addr_in) override; + + std::pair<SockAddrIn, Errno> GetPeerName() override; + + std::pair<SockAddrIn, Errno> GetSockName() override; + + Errno Bind(SockAddrIn addr) override; + + Errno Listen(s32 backlog) override; + + Errno Shutdown(ShutdownHow how) override; + + std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override; + + std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override; + + std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override; + + std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) override; + + Errno SetLinger(bool enable, u32 linger) override; + + Errno SetReuseAddr(bool enable) override; + + Errno SetKeepAlive(bool enable) override; + + Errno SetBroadcast(bool enable) override; + + Errno SetSndBuf(u32 value) override; + + Errno SetRcvBuf(u32 value) override; + + Errno SetSndTimeo(u32 value) override; + + Errno SetRcvTimeo(u32 value) override; + + Errno SetNonBlock(bool enable) override; + + template <typename T> + Errno SetSockOpt(SOCKET fd, int option, T value); + + bool IsOpened() const override; + + void HandleProxyPacket(const ProxyPacket& packet) override; +}; + +std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout); + +} // namespace Network |