From e5495cbc4bb34c9c7e8b6e103a15d4b8c65304e3 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Fri, 7 Jul 2023 21:44:34 +0300 Subject: [PATCH] core: Abstract sockets --- src/common/CMakeLists.txt | 6 +- src/common/assert.h | 29 +- src/common/bit_cast.h | 23 + src/common/common_funcs.h | 9 + src/common/settings.h | 3 + src/common/socket_types.h | 143 ++++ src/core/CMakeLists.txt | 5 + src/core/hle/kernel/hle_ipc.cpp | 4 +- src/core/hle/kernel/hle_ipc.h | 10 +- src/core/hle/kernel/process.cpp | 2 +- src/core/hle/kernel/svc_wrapper.h | 2 +- src/core/hle/service/soc_u.cpp | 589 +++----------- src/core/hle/service/soc_u.h | 62 +- src/core/network/network.cpp | 747 ++++++++++++++++++ src/core/network/network.h | 150 ++++ src/core/network/network_interface.cpp | 218 +++++ src/core/network/network_interface.h | 31 + src/core/network/sockets.h | 174 ++++ src/network/room_member.h | 10 + .../shader/shader_jit_x64_compiler.cpp | 2 +- 20 files changed, 1679 insertions(+), 540 deletions(-) create mode 100644 src/common/bit_cast.h create mode 100644 src/common/socket_types.h create mode 100644 src/core/network/network.cpp create mode 100644 src/core/network/network.h create mode 100644 src/core/network/network_interface.cpp create mode 100644 src/core/network/network_interface.h create mode 100644 src/core/network/sockets.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index ddf715f2f..981e1d0c2 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -53,8 +53,7 @@ add_library(citra_common STATIC archives.h assert.h atomic_ops.h - detached_tasks.cpp - detached_tasks.h + bit_cast.h bit_field.h bit_set.h bounded_threadsafe_queue.h @@ -66,6 +65,8 @@ add_library(citra_common STATIC common_precompiled_headers.h common_types.h construct.h + detached_tasks.cpp + detached_tasks.h dynamic_library/dynamic_library.cpp dynamic_library/dynamic_library.h dynamic_library/fdk-aac.cpp @@ -111,6 +112,7 @@ add_library(citra_common STATIC settings.cpp settings.h slot_vector.h + socket_types.h serialization/atomic.h serialization/boost_discrete_interval.hpp serialization/boost_flat_set.h diff --git a/src/common/assert.h b/src/common/assert.h index b5ec4fe47..9d0eca6ca 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -13,26 +13,20 @@ // lambda and force the compiler to not inline it. #define ASSERT(_a_) \ - do \ +([&]() CITRA_NO_INLINE { \ if (!(_a_)) [[unlikely]] { \ - []() CITRA_NO_INLINE CITRA_NO_RETURN { \ - LOG_CRITICAL(Debug, "Assertion Failed!"); \ - Crash(); \ - exit(1); \ - }(); \ - } \ - while (0) + LOG_CRITICAL(Debug, "Assertion Failed!"); \ + Crash(); \ + } \ +}()) #define ASSERT_MSG(_a_, ...) \ - do \ - if (!(_a_)) [[unlikely]] { \ - [&]() CITRA_NO_INLINE CITRA_NO_RETURN { \ - LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ - Crash(); \ - exit(1); \ - }(); \ + ([&]() CITRA_NO_INLINE { \ + if (!(_a_)) [[unlikely]] { \ + LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ + Crash(); \ } \ - while (0) + }()) #define UNREACHABLE() \ ([]() CITRA_NO_INLINE CITRA_NO_RETURN { \ @@ -58,3 +52,6 @@ #define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!") #define UNIMPLEMENTED_MSG(_a_, ...) LOG_CRITICAL(Debug, _a_, __VA_ARGS__) + +#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") +#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) diff --git a/src/common/bit_cast.h b/src/common/bit_cast.h new file mode 100644 index 000000000..c6110c542 --- /dev/null +++ b/src/common/bit_cast.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#ifdef __cpp_lib_bit_cast +#include +#endif + +namespace Common { + +template +constexpr inline To BitCast(const From& from) { +#ifdef __cpp_lib_bit_cast + return std::bit_cast(from); +#else + return __builtin_bit_cast(To, from); +#endif +} + +} // namespace Common diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index ff7452f6c..3e77ea746 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -110,6 +110,15 @@ __declspec(dllimport) void __stdcall DebugBreak(void); return static_cast(key) == 0; \ } +#define CITRA_NON_COPYABLE(cls) \ + cls(const cls&) = delete; \ + cls& operator=(const cls&) = delete + +#define CITRA_NON_MOVEABLE(cls) \ + cls(cls&&) = delete; \ + cls& operator=(cls&&) = delete + + // Generic function to get last error message. // Call directly after the command or use the error num. // This function might change the error code. diff --git a/src/common/settings.h b/src/common/settings.h index c7bf565fe..63346e229 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -505,6 +505,9 @@ struct Values { // Miscellaneous Setting log_filter{"*:Info", "log_filter"}; + // Network + Setting network_interface{std::string(), "network_interface"}; + // Video Dumping std::string output_format; std::string format_options; diff --git a/src/common/socket_types.h b/src/common/socket_types.h new file mode 100644 index 000000000..3ed1730c6 --- /dev/null +++ b/src/common/socket_types.h @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "common/swap.h" + +namespace Network { + +/// Address families +enum class Domain : u32 { + UNSPEC = 0, + INET = 2, ///< Address family for IPv4 + INET6 = 23, +}; + +/// Socket types +enum class Type : u32 { + STREAM = 1, + DGRAM = 2, +}; + +/// Shutdown mode +enum class ShutdownHow { + RD, + WR, + RDWR, +}; + +enum class FcntlCmd : u32 { + GETFL = 3, + SETFL = 4, +}; + +enum class SendFlags : u32 { + OOB = 1, + PEEK = 2, + DONTWAIT = 4, +}; + +DECLARE_ENUM_FLAG_OPERATORS(SendFlags) + +/// Array of IPv4 address +using IPv4Address = std::array; + +/// Union to represent the 3DS sockaddr structure +union CTRSockAddr { + /// Structure to represent a raw sockaddr + struct { + u8 len; ///< The length of the entire structure, only the set fields count + u8 sa_family; ///< The address family of the sockaddr + u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family + } raw; + + /// Structure to represent the 3ds' sockaddr_in structure + struct CTRSockAddrIn { + u8 len; ///< The length of the entire structure + u8 sin_family; ///< The address family of the sockaddr_in + u16 sin_port; ///< The port associated with this sockaddr_in + u32 sin_addr; ///< The actual address of the sockaddr_in + } in; + static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size"); +}; + +struct CTRAddrInfo { + s32_le ai_flags; + s32_le ai_family; + s32_le ai_socktype; + s32_le ai_protocol; + s32_le ai_addrlen; + char ai_canonname[256]; + CTRSockAddr ai_addr; + + static u32 AddressInfoFlagsToPlatform(u32 flags) { + u32 ret = 0; + if (flags & 1) { + ret |= AI_PASSIVE; + } + if (flags & 2) { + ret |= AI_CANONNAME; + } + if (flags & 4) { + ret |= AI_NUMERICHOST; + } + if (flags & 8) { + ret |= AI_NUMERICSERV; + } + return ret; + } + + static u32 AddressInfoFlagsFromPlatform(u32 flags) { + u32 ret = 0; + if (flags & AI_PASSIVE) { + ret |= 1; + } + if (flags & AI_CANONNAME) { + ret |= 2; + } + if (flags & AI_NUMERICHOST) { + ret |= 4; + } + if (flags & AI_NUMERICSERV) { + ret |= 8; + } + return ret; + } + + /// Converts a platform-specific addrinfo to a 3ds addrinfo. + static CTRAddrInfo FromPlatform(const addrinfo& addr) { + CTRAddrInfo ctr_addr{ + .ai_flags = static_cast(AddressInfoFlagsFromPlatform(addr.ai_flags)), + .ai_family = static_cast(SocketDomainFromPlatform(addr.ai_family)), + .ai_socktype = static_cast(SocketTypeFromPlatform(addr.ai_socktype)), + .ai_protocol = static_cast(SocketProtocolFromPlatform(addr.ai_protocol)), + .ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr), + }; + ctr_addr.ai_addrlen = static_cast(ctr_addr.ai_addr.raw.len); + if (addr.ai_canonname) + std::strncpy(ctr_addr.ai_canonname, addr.ai_canonname, + sizeof(ctr_addr.ai_canonname) - 1); + return ctr_addr; + } + + /// Converts a platform-specific addrinfo to a 3ds addrinfo. + static addrinfo ToPlatform(const CTRAddrInfo& ctr_addr) { + // Only certain fields are meaningful in hints, copy them manually + addrinfo addr = { + .ai_flags = static_cast(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)), + .ai_family = static_cast(SocketDomainToPlatform(ctr_addr.ai_family)), + .ai_socktype = static_cast(SocketTypeToPlatform(ctr_addr.ai_socktype)), + .ai_protocol = static_cast(SocketProtocolToPlatform(ctr_addr.ai_protocol)), + }; + return addr; + } +}; +static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct"); + +constexpr u32 FLAG_MSG_PEEK = 0x2; +constexpr u32 FLAG_MSG_DONTWAIT = 0x80; +constexpr u32 FLAG_O_NONBLOCK = 0x800; + +} // namespace Network diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0e628cb55..c6c35c261 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -439,6 +439,11 @@ add_library(citra_core STATIC hw/rsa/rsa.h hw/y2r.cpp hw/y2r.h + network/network.cpp + network/network.h + network/network_interface.cpp + network/network_interface.h + network/sockets.h loader/3dsx.cpp loader/3dsx.h loader/elf.cpp diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index ad5e6ee21..e5e2824e9 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -304,13 +304,13 @@ MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptrsize); memory->ReadBlock(*process, address + static_cast(offset), dest_buffer, size); } -void MappedBuffer::Write(const void* src_buffer, std::size_t offset, std::size_t size) { +void MappedBuffer::Write(const void* src_buffer, std::size_t offset, std::size_t size) const { ASSERT(perms & IPC::W); ASSERT(offset + size <= this->size); memory->WriteBlock(*process, address + static_cast(offset), src_buffer, size); diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 47d98af2c..62ef8dc97 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -129,14 +129,16 @@ public: MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr process, u32 descriptor, VAddr address, u32 id); - // interface for service - void Read(void* dest_buffer, std::size_t offset, std::size_t size); - void Write(const void* src_buffer, std::size_t offset, std::size_t size); + // Interface for service + void Read(void* dest_buffer, std::size_t offset, std::size_t size) const; + + void Write(const void* src_buffer, std::size_t offset, std::size_t size) const; + std::size_t GetSize() const { return size; } - // interface for ipc helper + // Interface for ipc helper u32 GenerateDescriptor() const { return IPC::MappedBufferDesc(size, perms); } diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 7fb5c6aa2..b1889c4bc 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -1,7 +1,7 @@ // Copyright 2015 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. - +#pragma optimize("", off) #include #include #include diff --git a/src/core/hle/kernel/svc_wrapper.h b/src/core/hle/kernel/svc_wrapper.h index 77334152d..2bc55f540 100644 --- a/src/core/hle/kernel/svc_wrapper.h +++ b/src/core/hle/kernel/svc_wrapper.h @@ -3,7 +3,7 @@ // Refer to the license.txt file included. #pragma once - +#pragma optimize("", off) #include #include #include diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index eba312ee3..ab93e3fad 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -19,6 +19,7 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/soc_u.h" +#include "core/network/network.h" #ifdef _WIN32 #include @@ -153,112 +154,6 @@ static u32 SocketTypeFromPlatform(u32 type) { return -1; } -/// Holds the translation from system network errors to 3DS network errors -static const std::unordered_map error_map = {{ - {E2BIG, 1}, - {ERRNO(EACCES), 2}, - {ERRNO(EADDRINUSE), 3}, - {ERRNO(EADDRNOTAVAIL), 4}, - {ERRNO(EAFNOSUPPORT), 5}, - {EAGAIN, 6}, -#ifdef _WIN32 - {WSAEWOULDBLOCK, 6}, -#else -#if EAGAIN != EWOULDBLOCK - {EWOULDBLOCK, 6}, -#endif -#endif // _WIN32 - {ERRNO(EALREADY), 7}, - {ERRNO(EBADF), 8}, - {EBADMSG, 9}, - {EBUSY, 10}, - {ECANCELED, 11}, - {ECHILD, 12}, - {ERRNO(ECONNABORTED), 13}, - {ERRNO(ECONNREFUSED), 14}, - {ERRNO(ECONNRESET), 15}, - {EDEADLK, 16}, - {ERRNO(EDESTADDRREQ), 17}, - {EDOM, 18}, - {ERRNO(EDQUOT), 19}, - {EEXIST, 20}, - {ERRNO(EFAULT), 21}, - {EFBIG, 22}, - {ERRNO(EHOSTUNREACH), 23}, - {EIDRM, 24}, - {EILSEQ, 25}, - {ERRNO(EINPROGRESS), 26}, - {ERRNO(EINTR), 27}, - {ERRNO(EINVAL), 28}, - {EIO, 29}, - {ERRNO(EISCONN), 30}, - {EISDIR, 31}, - {ERRNO(ELOOP), 32}, - {ERRNO(EMFILE), 33}, - {EMLINK, 34}, - {ERRNO(EMSGSIZE), 35}, -#ifdef EMULTIHOP - {ERRNO(EMULTIHOP), 36}, -#endif - {ERRNO(ENAMETOOLONG), 37}, - {ERRNO(ENETDOWN), 38}, - {ERRNO(ENETRESET), 39}, - {ERRNO(ENETUNREACH), 40}, - {ENFILE, 41}, - {ERRNO(ENOBUFS), 42}, -#ifdef ENODATA - {ENODATA, 43}, -#endif - {ENODEV, 44}, - {ENOENT, 45}, - {ENOEXEC, 46}, - {ENOLCK, 47}, - {ENOLINK, 48}, - {ENOMEM, 49}, - {ENOMSG, 50}, - {ERRNO(ENOPROTOOPT), 51}, - {ENOSPC, 52}, -#ifdef ENOSR - {ENOSR, 53}, -#endif -#ifdef ENOSTR - {ENOSTR, 54}, -#endif - {ENOSYS, 55}, - {ERRNO(ENOTCONN), 56}, - {ENOTDIR, 57}, - {ERRNO(ENOTEMPTY), 58}, - {ERRNO(ENOTSOCK), 59}, - {ENOTSUP, 60}, - {ENOTTY, 61}, - {ENXIO, 62}, - {ERRNO(EOPNOTSUPP), 63}, - {EOVERFLOW, 64}, - {EPERM, 65}, - {EPIPE, 66}, - {EPROTO, 67}, - {ERRNO(EPROTONOSUPPORT), 68}, - {ERRNO(EPROTOTYPE), 69}, - {ERANGE, 70}, - {EROFS, 71}, - {ESPIPE, 72}, - {ESRCH, 73}, - {ERRNO(ESTALE), 74}, -#ifdef ETIME - {ETIME, 75}, -#endif - {ERRNO(ETIMEDOUT), 76}, -}}; - -/// Converts a network error from platform-specific to 3ds-specific -static int TranslateError(int error) { - const auto& found = error_map.find(error); - if (found != error_map.end()) { - return -found->second; - } - return error; -} - struct CTRLinger { u32_le l_onoff; u32_le l_linger; @@ -371,42 +266,6 @@ static void TranslateSockOptDataFromPlatform(std::vector& out, const std::ve } } -bool SOC_U::GetSocketBlocking(const SocketHolder& socket_holder) { - return socket_holder.blocking; -} -u32 SOC_U::SetSocketBlocking(SocketHolder& socket_holder, bool blocking) { - u32 posix_ret = 0; -#ifdef _WIN32 - unsigned long nonblocking = (blocking) ? 0 : 1; - int ret = ioctlsocket(socket_holder.socket_fd, FIONBIO, &nonblocking); - if (ret == SOCKET_ERROR_VALUE) { - posix_ret = TranslateError(GET_ERRNO); - return posix_ret; - } - socket_holder.blocking = blocking; -#else - int flags = ::fcntl(socket_holder.socket_fd, F_GETFL, 0); - if (flags == SOCKET_ERROR_VALUE) { - posix_ret = TranslateError(GET_ERRNO); - return posix_ret; - } - - flags &= ~O_NONBLOCK; - if (!blocking) { // O_NONBLOCK - flags |= O_NONBLOCK; - } - - socket_holder.blocking = blocking; - - const int ret = ::fcntl(socket_holder.socket_fd, F_SETFL, flags); - if (ret == SOCKET_ERROR_VALUE) { - posix_ret = TranslateError(GET_ERRNO); - return posix_ret; - } -#endif - return posix_ret; -} - static u32 SendRecvFlagsToPlatform(u32 flags) { u32 ret = 0; if (flags & 1) { @@ -513,173 +372,10 @@ struct CTRPollFD { }; Events events; ///< Events to poll for (input) Events revents; ///< Events received (output) - - /// Converts a platform-specific pollfd to a 3ds specific structure - static CTRPollFD FromPlatform(SOC::SOC_U& socu, pollfd const& fd, u8 has_libctru_bug) { - CTRPollFD result; - result.events.hex = Events::TranslateTo3DS(fd.events, has_libctru_bug).hex; - result.revents.hex = Events::TranslateTo3DS(fd.revents, has_libctru_bug).hex; - for (const auto& socket : socu.open_sockets) { - if (socket.second.socket_fd == fd.fd) { - result.fd = socket.first; - break; - } - } - return result; - } - - /// Converts a 3ds specific pollfd to a platform-specific structure - static pollfd ToPlatform(SOC::SOC_U& socu, CTRPollFD const& fd, u8& haslibctrbug) { - pollfd result; - u8 unused = 0; - result.events = Events::TranslateToPlatform(fd.events, false, haslibctrbug); - result.revents = Events::TranslateToPlatform(fd.revents, true, unused); - auto iter = socu.open_sockets.find(fd.fd); - result.fd = (iter != socu.open_sockets.end()) ? iter->second.socket_fd : 0; - if (iter == socu.open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", fd.fd); - } - return result; - } }; static_assert(std::is_trivially_copyable_v, "CTRPollFD is used with std::memcpy and must be trivially copyable"); -/// Union to represent the 3ds' sockaddr structure -union CTRSockAddr { - /// Structure to represent a raw sockaddr - struct { - u8 len; ///< The length of the entire structure, only the set fields count - u8 sa_family; ///< The address family of the sockaddr - u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family - } raw; - - /// Structure to represent the 3ds' sockaddr_in structure - struct CTRSockAddrIn { - u8 len; ///< The length of the entire structure - u8 sin_family; ///< The address family of the sockaddr_in - u16 sin_port; ///< The port associated with this sockaddr_in - u32 sin_addr; ///< The actual address of the sockaddr_in - } in; - static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size"); - - /// Convert a 3DS CTRSockAddr to a platform-specific sockaddr - static sockaddr ToPlatform(CTRSockAddr const& ctr_addr) { - sockaddr result; - ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn), - "Unhandled address size (len) in CTRSockAddr::ToPlatform"); - result.sa_family = SocketDomainToPlatform(ctr_addr.raw.sa_family); - memset(result.sa_data, 0, sizeof(result.sa_data)); - - // We can not guarantee ABI compatibility between platforms so we copy the fields manually - switch (result.sa_family) { - case AF_INET: { - sockaddr_in* result_in = reinterpret_cast(&result); - result_in->sin_port = ctr_addr.in.sin_port; - result_in->sin_addr.s_addr = ctr_addr.in.sin_addr; - memset(result_in->sin_zero, 0, sizeof(result_in->sin_zero)); - break; - } - default: - ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); - break; - } - return result; - } - - /// Convert a platform-specific sockaddr to a 3DS CTRSockAddr - static CTRSockAddr FromPlatform(sockaddr const& addr) { - CTRSockAddr result; - result.raw.sa_family = static_cast(SocketDomainFromPlatform(addr.sa_family)); - // We can not guarantee ABI compatibility between platforms so we copy the fields manually - switch (addr.sa_family) { - case AF_INET: { - sockaddr_in const* addr_in = reinterpret_cast(&addr); - result.raw.len = sizeof(CTRSockAddrIn); - result.in.sin_port = addr_in->sin_port; - result.in.sin_addr = addr_in->sin_addr.s_addr; - break; - } - default: - ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); - break; - } - return result; - } -}; - -struct CTRAddrInfo { - s32_le ai_flags; - s32_le ai_family; - s32_le ai_socktype; - s32_le ai_protocol; - s32_le ai_addrlen; - char ai_canonname[256]; - CTRSockAddr ai_addr; - - static u32 AddressInfoFlagsToPlatform(u32 flags) { - u32 ret = 0; - if (flags & 1) { - ret |= AI_PASSIVE; - } - if (flags & 2) { - ret |= AI_CANONNAME; - } - if (flags & 4) { - ret |= AI_NUMERICHOST; - } - if (flags & 8) { - ret |= AI_NUMERICSERV; - } - return ret; - } - - static u32 AddressInfoFlagsFromPlatform(u32 flags) { - u32 ret = 0; - if (flags & AI_PASSIVE) { - ret |= 1; - } - if (flags & AI_CANONNAME) { - ret |= 2; - } - if (flags & AI_NUMERICHOST) { - ret |= 4; - } - if (flags & AI_NUMERICSERV) { - ret |= 8; - } - return ret; - } - - /// Converts a platform-specific addrinfo to a 3ds addrinfo. - static CTRAddrInfo FromPlatform(const addrinfo& addr) { - CTRAddrInfo ctr_addr{ - .ai_flags = static_cast(AddressInfoFlagsFromPlatform(addr.ai_flags)), - .ai_family = static_cast(SocketDomainFromPlatform(addr.ai_family)), - .ai_socktype = static_cast(SocketTypeFromPlatform(addr.ai_socktype)), - .ai_protocol = static_cast(SocketProtocolFromPlatform(addr.ai_protocol)), - .ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr), - }; - ctr_addr.ai_addrlen = static_cast(ctr_addr.ai_addr.raw.len); - if (addr.ai_canonname) - std::strncpy(ctr_addr.ai_canonname, addr.ai_canonname, - sizeof(ctr_addr.ai_canonname) - 1); - return ctr_addr; - } - - /// Converts a platform-specific addrinfo to a 3ds addrinfo. - static addrinfo ToPlatform(const CTRAddrInfo& ctr_addr) { - // Only certain fields are meaningful in hints, copy them manually - addrinfo addr = { - .ai_flags = static_cast(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)), - .ai_family = static_cast(SocketDomainToPlatform(ctr_addr.ai_family)), - .ai_socktype = static_cast(SocketTypeToPlatform(ctr_addr.ai_socktype)), - .ai_protocol = static_cast(SocketProtocolToPlatform(ctr_addr.ai_protocol)), - }; - return addr; - } -}; - static u32 NameInfoFlagsToPlatform(u32 flags) { u32 ret = 0; if (flags & 1) { @@ -700,8 +396,6 @@ static u32 NameInfoFlagsToPlatform(u32 flags) { return ret; } -static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct"); - void SOC_U::PreTimerAdjust() { adjust_value_last = std::chrono::steady_clock::now(); } @@ -715,84 +409,64 @@ void SOC_U::PostTimerAdjust(Kernel::HLERequestContext& ctx, const std::string& c nullptr); } -void SOC_U::CleanupSockets() { - for (const auto& sock : open_sockets) - closesocket(sock.second.socket_fd); - open_sockets.clear(); -} - void SOC_U::Socket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x02, 3, 2); - u32 domain = SocketDomainToPlatform(rp.Pop()); // Address family - u32 type = SocketTypeToPlatform(rp.Pop()); - u32 protocol = SocketProtocolToPlatform(rp.Pop()); + const auto domain = rp.Pop(); + const auto type = rp.Pop(); + const u32 protocol = rp.Pop(); rp.PopPID(); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); // Only 0 is allowed according to 3dbrew, using 0 will let the OS decide which protocol to use - if (protocol != 0) { + if (protocol != 0) [[unlikely]] { rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code rb.Skip(1, false); return; } - if (domain != AF_INET) { + if (domain != Network::Domain::INET) [[unlikely]] { rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code rb.Skip(1, false); return; } - if (type != SOCK_DGRAM && type != SOCK_STREAM) { + if (type != Network::Type::DGRAM && type != Network::Type::STREAM) [[unlikely]] { rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code rb.Skip(1, false); return; } - u64 ret = static_cast(::socket(domain, type, protocol)); - u32 socketHandle = GetNextSocketID(); + auto socket = std::make_unique(); + const auto ret = socket->Initialize(domain, type, protocol); + const u32 socket_handle = GetNextSocketID(); - if ((s64)ret != SOCKET_ERROR_VALUE) { - open_sockets[socketHandle] = {static_cast(ret), true}; -#if _WIN32 - // Disable UDP connection reset - int new_behavior = 0; - unsigned long bytes_returned = 0; - WSAIoctl(static_cast(ret), _WSAIOW(IOC_VENDOR, 12), &new_behavior, - sizeof(new_behavior), NULL, 0, &bytes_returned, NULL, NULL); -#endif + if (ret == Network::Errno::SUCCESS) { + open_sockets.emplace(socket_handle, std::move(socket)); } - if ((s64)ret == SOCKET_ERROR_VALUE) - ret = TranslateError(GET_ERRNO); - rb.Push(RESULT_SUCCESS); - rb.Push(socketHandle); + rb.Push(socket_handle); } void SOC_U::Bind(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x05, 2, 4); - u32 socket_handle = rp.Pop(); - auto fd_info = open_sockets.find(socket_handle); - if (fd_info == open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); + const u32 socket_handle = rp.Pop(); + const u32 len = rp.Pop(); + rp.PopPID(); + const auto sock_addr_buf = rp.PopStaticBuffer(); + + auto socket = FindSocket(socket_handle); + if (!socket) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERR_INVALID_HANDLE); return; } - u32 len = rp.Pop(); - rp.PopPID(); - auto sock_addr_buf = rp.PopStaticBuffer(); - CTRSockAddr ctr_sock_addr; + Network::CTRSockAddr ctr_sock_addr{}; std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), len); - sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr); - - s32 ret = ::bind(fd_info->second.socket_fd, &sock_addr, sizeof(sock_addr)); - - if (ret != 0) - ret = TranslateError(GET_ERRNO); + const auto ret = socket->Bind(ctr_sock_addr); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); @@ -801,92 +475,87 @@ void SOC_U::Bind(Kernel::HLERequestContext& ctx) { void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x13, 3, 2); - u32 socket_handle = rp.Pop(); - auto fd_info = open_sockets.find(socket_handle); - if (fd_info == open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); + const u32 socket_handle = rp.Pop(); + const auto ctr_cmd = rp.Pop(); + const u32 ctr_arg = rp.Pop(); + rp.PopPID(); + + auto socket = FindSocket(socket_handle); + if (!socket) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERR_INVALID_HANDLE); return; } - u32 ctr_cmd = rp.Pop(); - u32 ctr_arg = rp.Pop(); - rp.PopPID(); - u32 posix_ret = 0; // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX) - SCOPE_EXIT({ - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(RESULT_SUCCESS); - rb.Push(posix_ret); - }); - - if (ctr_cmd == 3) { // F_GETFL - posix_ret = 0; - if (GetSocketBlocking(fd_info->second) == false) - posix_ret |= 4; // O_NONBLOCK - } else if (ctr_cmd == 4) { // F_SETFL - posix_ret = SetSocketBlocking(fd_info->second, !(ctr_arg & 4)); - } else { + // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX) + u32 posix_ret = 0; + switch (ctr_cmd) { + case Network::FcntlCmd::GETFL: + if (!socket->IsBlocking()) { + posix_ret |= 4; // O_NONBLOCK + } + break; + case Network::FcntlCmd::SETFL: + posix_ret = static_cast(socket->SetNonBlock(ctr_arg & 4)); + default: LOG_ERROR(Service_SOC, "Unsupported command ({}) in fcntl call", ctr_cmd); - posix_ret = TranslateError(EINVAL); // TODO: Find the correct error - return; + posix_ret = static_cast(Network::Errno::INVAL); // TODO: Find the correct error } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(posix_ret); } void SOC_U::Listen(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x03, 2, 2); - u32 socket_handle = rp.Pop(); - auto fd_info = open_sockets.find(socket_handle); - if (fd_info == open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); + const u32 socket_handle = rp.Pop(); + const u32 backlog = rp.Pop(); + rp.PopPID(); + + auto socket = FindSocket(socket_handle); + if (!socket) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERR_INVALID_HANDLE); return; } - u32 backlog = rp.Pop(); - rp.PopPID(); - s32 ret = ::listen(fd_info->second.socket_fd, backlog); - if (ret != 0) - ret = TranslateError(GET_ERRNO); + const auto ret = socket->Listen(backlog); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); rb.Push(ret); } +static_assert(sizeof(socklen_t) == sizeof(u32), "socklen_t has incorrect size!"); + void SOC_U::Accept(Kernel::HLERequestContext& ctx) { // TODO(Subv): Calling this function on a blocking socket will block the emu thread, // preventing graceful shutdown when closing the emulator, this can be fixed by always // performing nonblocking operations and spinlock until the data is available IPC::RequestParser rp(ctx, 0x04, 2, 2); const auto socket_handle = rp.Pop(); - auto fd_info = open_sockets.find(socket_handle); - if (fd_info == open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); + [[maybe_unused]] const auto max_addr_len = rp.Pop(); + rp.PopPID(); + + auto socket = FindSocket(socket_handle); + if (!socket) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERR_INVALID_HANDLE); return; } - [[maybe_unused]] const auto max_addr_len = static_cast(rp.Pop()); - rp.PopPID(); - sockaddr addr; - socklen_t addr_len = sizeof(addr); - u32 ret = static_cast(::accept(fd_info->second.socket_fd, &addr, &addr_len)); - if (static_cast(ret) != SOCKET_ERROR_VALUE) { - u32 socketID = GetNextSocketID(); - open_sockets[socketID] = {static_cast(ret), true}; - ret = socketID; - } + auto [result, err] = socket->Accept(); - CTRSockAddr ctr_addr; - std::vector ctr_addr_buf(sizeof(ctr_addr)); - if (static_cast(ret) == SOCKET_ERROR_VALUE) { - ret = TranslateError(GET_ERRNO); - } else { - ctr_addr = CTRSockAddr::FromPlatform(addr); - std::memcpy(ctr_addr_buf.data(), &ctr_addr, sizeof(ctr_addr)); + constexpr u32 size = sizeof(result.sockaddr_in); + std::vector ctr_addr_buf(size); + + u32 ret = static_cast(err); + if (err == Network::Errno::SUCCESS) { + const u32 socket_id = GetNextSocketID(); + open_sockets.emplace(socket_id, std::move(result.socket)); + std::memcpy(ctr_addr_buf.data(), &result.sockaddr_in, size); + ret = socket_id; } IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); @@ -899,7 +568,7 @@ void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x16, 0, 0); u32 host_id = 0; - auto info = GetDefaultInterfaceInfo(); + const auto info = GetDefaultInterfaceInfo(); if (info.has_value()) { host_id = info->address; } @@ -911,25 +580,19 @@ void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) { void SOC_U::Close(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x0B, 1, 2); - u32 socket_handle = rp.Pop(); - auto fd_info = open_sockets.find(socket_handle); - if (fd_info == open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); + const u32 socket_handle = rp.Pop(); + rp.PopPID(); + + auto socket = FindSocket(socket_handle); + if (!socket) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERR_INVALID_HANDLE); return; } - rp.PopPID(); - - s32 ret = 0; - - ret = closesocket(fd_info->second.socket_fd); + const auto ret = socket->Close(); open_sockets.erase(socket_handle); - if (ret != 0) - ret = TranslateError(GET_ERRNO); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); rb.Push(ret); @@ -938,59 +601,48 @@ void SOC_U::Close(Kernel::HLERequestContext& ctx) { void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x09, 4, 6); const u32 socket_handle = rp.Pop(); - const auto fd_info = open_sockets.find(socket_handle); - if (fd_info == open_sockets.end()) { - LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); + const u32 len = rp.Pop(); + const auto flags = rp.Pop(); + const u32 addr_len = rp.Pop(); + rp.PopPID(); + const auto dest_addr_buffer = rp.PopStaticBuffer(); + const auto input_mapped_buff = rp.PopMappedBuffer(); + + auto socket = FindSocket(socket_handle); + if (!socket) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERR_INVALID_HANDLE); return; } - const u32 len = rp.Pop(); - u32 flags = SendRecvFlagsToPlatform(rp.Pop()); - bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0; - flags &= ~MSGCUSTOM_HANDLE_DONTWAIT; -#ifdef _WIN32 - bool was_blocking = GetSocketBlocking(fd_info->second); - if (dont_wait && was_blocking) { - SetSocketBlocking(fd_info->second, false); - } -#else - if (dont_wait) { - flags |= MSG_DONTWAIT; - } -#endif // _WIN32 - const u32 addr_len = rp.Pop(); - rp.PopPID(); - const auto dest_addr_buffer = rp.PopStaticBuffer(); - auto input_mapped_buff = rp.PopMappedBuffer(); - std::vector input_buff(len); - input_mapped_buff.Read(input_buff.data(), 0, - std::min(input_mapped_buff.GetSize(), static_cast(len))); - - s32 ret = -1; - if (addr_len > 0) { - CTRSockAddr ctr_dest_addr; - std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(), sizeof(ctr_dest_addr)); - sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr); - ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast(input_buff.data()), - len, flags, &dest_addr, sizeof(dest_addr)); - } else { - ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast(input_buff.data()), - len, flags, nullptr, 0); - } - - const auto send_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; - -#ifdef _WIN32 - if (dont_wait && was_blocking) { - SetSocketBlocking(fd_info->second, true); +#if WIN32 + const bool unblock = True(flags & Network::SendFlags::DONTWAIT) && socket->IsBlocking(); + if (unblock) { + socket->SetNonBlock(true); } #endif - if (ret == SOCKET_ERROR_VALUE) { - ret = TranslateError(send_error); + std::vector message(len); + const size_t size = std::min(input_mapped_buff.GetSize(), len); + input_mapped_buff.Read(message.data(), 0, size); + + const auto [result, err] = [&] { + if (addr_len > 0) { + Network::CTRSockAddr ctr_dest_addr; + std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(), sizeof(ctr_dest_addr)); + return socket->SendTo(flags, message, &ctr_dest_addr); + } else { + return socket->SendTo(flags, message, nullptr); + } + }(); + + const u32 ret = err != Network::Errno::SUCCESS ? static_cast(err) : result; + +#if WIN32 + if (unblock) { + socket->SetNonBlock(false); } +#endif IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); @@ -1430,7 +1082,7 @@ void SOC_U::InitializeSockets(Kernel::HLERequestContext& ctx) { void SOC_U::ShutdownSockets(Kernel::HLERequestContext& ctx) { // TODO(Subv): Implement IPC::RequestParser rp(ctx, 0x19, 0, 0); - CleanupSockets(); + open_sockets.clear(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -1674,6 +1326,15 @@ void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) { rb.PushStaticBuffer(std::move(serv), 1); } +Network::Socket* SOC_U::FindSocket(u32 handle) { + const auto it = open_sockets.find(handle); + if (it == open_sockets.end()) { + LOG_ERROR(Service_SOC, "Invalid socket handle: {}", handle); + return nullptr; + } + return it->second.get(); +} + SOC_U::SOC_U() : ServiceFramework("soc:U") { static const FunctionInfo functions[] = { // clang-format off @@ -1714,19 +1375,9 @@ SOC_U::SOC_U() : ServiceFramework("soc:U") { }; RegisterHandlers(functions); - -#ifdef _WIN32 - WSADATA data; - WSAStartup(MAKEWORD(2, 2), &data); -#endif } -SOC_U::~SOC_U() { - CleanupSockets(); -#ifdef _WIN32 - WSACleanup(); -#endif -} +SOC_U::~SOC_U() = default; std::optional SOC_U::GetDefaultInterfaceInfo() { if (this->interface_info_cached) { diff --git a/src/core/hle/service/soc_u.h b/src/core/hle/service/soc_u.h index 4c56d3152..9d11b166a 100644 --- a/src/core/hle/service/soc_u.h +++ b/src/core/hle/service/soc_u.h @@ -9,6 +9,7 @@ #include #include "core/hle/result.h" #include "core/hle/service/service.h" +#include "core/network/sockets.h" namespace Core { class System; @@ -16,26 +17,6 @@ class System; namespace Service::SOC { -/// Holds information about a particular socket -struct SocketHolder { -#ifdef _WIN32 - using SOCKET = unsigned long long; - SOCKET socket_fd; ///< The socket descriptor -#else - int socket_fd; ///< The socket descriptor -#endif // _WIN32 - - bool blocking = true; ///< Whether the socket is blocking or not. - -private: - template - void serialize(Archive& ar, const unsigned int) { - ar& socket_fd; - ar& blocking; - } - friend class boost::serialization::access; -}; - class SOC_U final : public ServiceFramework { public: SOC_U(); @@ -63,11 +44,8 @@ private: static const std::unordered_map> sockopt_map; static std::pair TranslateSockOpt(int level, int opt); - bool GetSocketBlocking(const SocketHolder& socket_holder); - u32 SetSocketBlocking(SocketHolder& socket_holder, bool blocking); - // From - // https://github.com/devkitPro/libctru/blob/1de86ea38aec419744149daf692556e187d4678a/libctru/include/3ds/services/soc.h#L15 + // From https://github.com/devkitPro/libctru/blob/1de86ea3/libctru/include/3ds/services/soc.h#L15 enum class NetworkOpt { NETOPT_MAC_ADDRESS = 0x1004, ///< The mac address of the interface NETOPT_ARP_TABLE = 0x3002, ///< The ARP table @@ -123,30 +101,14 @@ private: void GetAddrInfoImpl(Kernel::HLERequestContext& ctx); void GetNameInfoImpl(Kernel::HLERequestContext& ctx); - // Socked ids - u32 next_socket_id = 3; - u32 GetNextSocketID() { - return next_socket_id++; - } + Network::Socket* FindSocket(u32 handle); - // System timer adjust - std::chrono::time_point adjust_value_last; void PreTimerAdjust(); void PostTimerAdjust(Kernel::HLERequestContext& ctx, const std::string& caller_method); - /// Close all open sockets - void CleanupSockets(); - - /// Holds info about the currently open sockets - friend struct CTRPollFD; - std::unordered_map open_sockets; - - /// Cache interface info for the current session - /// These two fields are not saved to savestates on purpose - /// as network interfaces may change and it's better to. - /// obtain them again between play sessions. - bool interface_info_cached = false; - InterfaceInfo interface_info; + [[nodiscard]] u32 GetNextSocketID() { + return next_socket_id++; + } template void serialize(Archive& ar, const unsigned int) { @@ -154,6 +116,18 @@ private: ar& open_sockets; } friend class boost::serialization::access; + +private: + std::chrono::time_point adjust_value_last; + std::unordered_map> open_sockets; + u32 next_socket_id = 3; + + // Cache interface info for the current session + // These two fields are not saved to savestates on purpose + // as network interfaces may change and it's better to. + // obtain them again between play sessions. + bool interface_info_cached = false; + InterfaceInfo interface_info; }; std::shared_ptr GetService(Core::System& system); diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp new file mode 100644 index 000000000..8c7a353c9 --- /dev/null +++ b/src/core/network/network.cpp @@ -0,0 +1,747 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include "common/error.h" + +#ifdef _WIN32 +#include +#include +#elif CITRA_UNIX +#include +#include +#include +#include +#include +#include +#include +#include +#else +#error "Unimplemented platform" +#endif + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/network/network.h" +#include "core/network/network_interface.h" +#include "core/network/sockets.h" + +namespace Network { + +namespace { + +#ifdef _WIN32 + +using socklen_t = int; + +#define ERRNO(x) WSA##x + +void Initialize() { + WSADATA wsa_data; + (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); +} + +void Finalize() { + WSACleanup(); +} + +sockaddr TranslateFromSockAddrIn(CTRSockAddr ctr_addr) { + sockaddr result; + ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddr), + "Unhandled address size ({})", static_cast(ctr_addr.raw.len)); + result.sa_family = ctr_addr.raw.sa_family; + std::memset(result.sa_data, 0, sizeof(result.sa_data)); + +#if CITRA_UNIX + result.sin_len = sizeof(result); +#endif + + // We can not guarantee ABI compatibility between platforms so we copy the fields manually + const auto domain = static_cast(result.sa_family); + switch (domain) { + case Domain::INET: { + sockaddr_in result_in; + std::memcpy(&result_in, &result, sizeof(result_in)); + result_in.sin_port = ctr_addr.in.sin_port; + result_in.sin_addr.s_addr = ctr_addr.in.sin_addr; + std::memset(result_in.sin_zero, 0, sizeof(result_in.sin_zero)); + break; + } + default: + ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); + break; + } + return result; +} + +LINGER MakeLinger(bool enable, u32 linger_value) { + ASSERT(linger_value <= std::numeric_limits::max()); + + LINGER value; + value.l_onoff = enable ? 1 : 0; + value.l_linger = static_cast(linger_value); + return value; +} + +bool EnableNonBlock(SOCKET fd, bool enable) { + u_long value = enable ? 1 : 0; + return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; +} + +#elif CITRA_UNIX // ^ _WIN32 v CITRA_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; + +#define ERRNO(x) x + +void Initialize() {} + +void Finalize() {} + +sockaddr TranslateFromSockAddrIn(SockAddrIn input) { + sockaddr_in result; + + switch (static_cast(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), 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; +} + +#endif + +Errno TranslateNativeError(int e) { + switch (e) { + case E2BIG: + return Errno::TBIG; + case ERRNO(EACCES): + return Errno::ACCES; + case ERRNO(EADDRINUSE): + return Errno::ADDRINUSE; + case ERRNO(EADDRNOTAVAIL): + return Errno::ADDRNOTAVAIL; + case ERRNO(EAFNOSUPPORT): + return Errno::AFNOSUPPORT; + case EAGAIN: +#if WIN32 || EAGAIN != EWOULDBLOCK + case ERRNO(EWOULDBLOCK): +#endif + return Errno::WOULDBLOCK; + case ERRNO(EALREADY): + return Errno::ALREADY; + case ERRNO(EBADF): + return Errno::BADF; + case EBADMSG: + return Errno::BADMSG; + case EBUSY: + return Errno::BUSY; + case ECANCELED: + return Errno::CANCELED; + case ECHILD: + return Errno::CHILD; + case ERRNO(ECONNABORTED): + return Errno::CONNABORTED; + case ERRNO(ECONNREFUSED): + return Errno::CONNREFUSED; + case ERRNO(ECONNRESET): + return Errno::CONNRESET; + case EDEADLK: + return Errno::DEADLK; + case ERRNO(EDESTADDRREQ): + return Errno::DESTADDRREQ; + case EDOM: + return Errno::DOM; + case ERRNO(EDQUOT): + return Errno::DQUOT; + case EEXIST: + return Errno::EXIST; + case ERRNO(EFAULT): + return Errno::FAULT; + case EFBIG: + return Errno::FBIG; + case ERRNO(EHOSTUNREACH): + return Errno::HOSTUNREACH; + case EIDRM: + return Errno::IDRM; + case EILSEQ: + return Errno::ILSEQ; + case ERRNO(EINPROGRESS): + return Errno::INPROGRESS; + case ERRNO(EINTR): + return Errno::INTR; + case ERRNO(EINVAL): + return Errno::INVAL; + case EIO: + return Errno::IO; + case ERRNO(EISCONN): + return Errno::ISCONN; + case EISDIR: + return Errno::ISDIR; + case ERRNO(ELOOP): + return Errno::LOOP; + case ERRNO(EMFILE): + return Errno::MFILE; + case EMLINK: + return Errno::MLINK; + case ERRNO(EMSGSIZE): + return Errno::MSGSIZE; +#ifdef EMULTIHOP + case ERRNO(EMULTIHOP): + return Errno::MULTIHOP; +#endif + case ERRNO(ENAMETOOLONG): + return Errno::NAMETOOLONG; + case ERRNO(ENETDOWN): + return Errno::NETDOWN; + case ERRNO(ENETRESET): + return Errno::NETRESET; + case ERRNO(ENETUNREACH): + return Errno::NETUNREACH; + case ENFILE: + return Errno::NFILE; + case ERRNO(ENOBUFS): + return Errno::NOBUFS; +#ifdef ENODATA + case ENODATA: + return Errno::NODATA; +#endif + case ENODEV: + return Errno::NODEV; + case ENOENT: + return Errno::NOENT; + case ENOEXEC: + return Errno::NOEXEC; + case ENOLCK: + return Errno::NOLCK; + case ENOLINK: + return Errno::NOLINK; + case ENOMEM: + return Errno::NOMEM; + case ENOMSG: + return Errno::NOMSG; + case ERRNO(ENOPROTOOPT): + return Errno::NOPROTOOPT; + case ENOSPC: + return Errno::NOSPC; +#ifdef ENOSR + case ENOSR: + return Errno::NOSR; +#endif +#ifdef ENOSTR + case ENOSTR: + return Errno::NOSTR; +#endif + case ENOSYS: + return Errno::NOSYS; + case ERRNO(ENOTCONN): + return Errno::NOTCONN; + case ENOTDIR: + return Errno::NOTDIR; + case ERRNO(ENOTEMPTY): + return Errno::NOTEMPTY; + case ERRNO(ENOTSOCK): + return Errno::NOTSOCK; + case ENOTSUP: + return Errno::NOTSUP; + case ENOTTY: + return Errno::NOTTY; + case ENXIO: + return Errno::NXIO; + case ERRNO(EOPNOTSUPP): + return Errno::OPNOTSUPP; + case EOVERFLOW: + return Errno::OVERFLOW; + case EPERM: + return Errno::PERM; + case EPIPE: + return Errno::PIPE; + case EPROTO: + return Errno::PROTO; + case ERRNO(EPROTONOSUPPORT): + return Errno::PROTONOSUPPORT; + case ERRNO(EPROTOTYPE): + return Errno::PROTOTYPE; + case ERANGE: + return Errno::RANGE; + case EROFS: + return Errno::ROFS; + case ESPIPE: + return Errno::PIPE; + case ESRCH: + return Errno::SRCH; + case ERRNO(ESTALE): + return Errno::STALE; +#ifdef ETIME + case ETIME: + return Errno::TIME; +#endif + case ERRNO(ETIMEDOUT): + return Errno::TIMEDOUT; + default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", e); + return Errno::OTHER; + } +} + +Errno GetAndLogLastError() { +#ifdef _WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + const Errno err = TranslateNativeError(e); + if (err == Errno::WOULDBLOCK || err == Errno::TIMEDOUT) { + 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; + } +} + +CTRSockAddr TranslateToSockAddrIn(sockaddr input) { + CTRSockAddr result; + result.raw.sa_family = input.sa_family; + + // We can not guarantee ABI compatibility between platforms so we copy the fields manually + const auto domain = static_cast(input.sa_family); + switch (domain) { + case Domain::INET: { + sockaddr_in addr_in; + std::memcpy(&addr_in, &input, sizeof(addr_in)); + result.raw.len = sizeof(CTRSockAddr::CTRSockAddrIn); + result.in.sin_port = addr_in.sin_port; + result.in.sin_addr = addr_in.sin_addr.s_addr; + break; + } + default: + ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); + break; + } + 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(~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 GetHostIPv4Address() { + const auto network_interface = Network::GetSelectedNetworkInterface(); + if (!network_interface.has_value()) { + LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); + return {}; + } + + std::array 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 Poll(std::vector& pollfds, s32 timeout) { + const size_t num = pollfds.size(); + + std::vector 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(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 +Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) { + const int result = + setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast(&value), sizeof(value)); + if (result != SOCKET_ERROR) { + return Errno::SUCCESS; + } + return GetAndLogLastError(); +} + +Errno Socket::Initialize(Domain domain, Type type, u32 protocol) { + fd = socket(TranslateDomain(domain), TranslateType(type), protocol); + if (fd == INVALID_SOCKET) { + return GetAndLogLastError(); + } + +#if _WIN32 + // Disable UDP connection reset + int new_behavior = 0; + unsigned long bytes_returned = 0; + WSAIoctl(fd, _WSAIOW(IOC_VENDOR, 12), &new_behavior, + sizeof(new_behavior), NULL, 0, &bytes_returned, NULL, NULL); +#endif + + return Errno::SUCCESS; +} + +std::pair 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(new_socket), + .sockaddr_in = TranslateToSockAddrIn(addr), + }; + + return {std::move(result), Errno::SUCCESS}; +} + +Errno Socket::Connect(CTRSockAddr 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 Socket::GetPeerName() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { + return {CTRSockAddr{}, GetAndLogLastError()}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; +} + +std::pair Socket::GetSockName() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { + return {CTRSockAddr{}, GetAndLogLastError()}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; +} + +Errno Socket::Bind(CTRSockAddr 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 Socket::Recv(int flags, std::vector& message) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast(std::numeric_limits::max())); + + const auto result = + recv(fd, reinterpret_cast(message.data()), static_cast(message.size()), 0); + if (result != SOCKET_ERROR) { + return {static_cast(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +std::pair Socket::RecvFrom(int flags, std::vector& message, CTRSockAddr* addr) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast(std::numeric_limits::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(message.data()), + static_cast(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(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +std::pair Socket::Send(std::span message, int flags) { + ASSERT(message.size() < static_cast(std::numeric_limits::max())); + ASSERT(flags == 0); + + const auto result = send(fd, reinterpret_cast(message.data()), + static_cast(message.size()), 0); + if (result != SOCKET_ERROR) { + return {static_cast(result), Errno::SUCCESS}; + } + + return {-1, GetAndLogLastError()}; +} + +std::pair Socket::SendTo(SendFlags flags, std::span message, + const CTRSockAddr* addr) { + const sockaddr* to = nullptr; + const int to_len = 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(message.data()), + static_cast(message.size()), static_cast(flags), to, to_len); + if (result != SOCKET_ERROR) { + return {static_cast(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(fd, SO_REUSEADDR, enable ? 1 : 0); +} + +Errno Socket::SetKeepAlive(bool enable) { + return SetSockOpt(fd, SO_KEEPALIVE, enable ? 1 : 0); +} + +Errno Socket::SetBroadcast(bool enable) { + return SetSockOpt(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) { + blocking = !enable; + if (EnableNonBlock(fd, enable)) { + return Errno::SUCCESS; + } + return GetAndLogLastError(); +} + +bool Socket::IsOpened() const { + return fd != INVALID_SOCKET; +} + +} // namespace Network diff --git a/src/core/network/network.h b/src/core/network/network.h new file mode 100644 index 000000000..50379a76e --- /dev/null +++ b/src/core/network/network.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/socket_types.h" + +#ifdef _WIN32 +#include +#undef OVERFLOW +#elif CITRA_UNIX +#include +#endif + +namespace Network { + +class SocketBase; +class Socket; + +/// Error code for network functions +enum class Errno { + SUCCESS, + TBIG, + ACCES, + ADDRINUSE, + ADDRNOTAVAIL, + AFNOSUPPORT, + WOULDBLOCK, + ALREADY, + BADF, + BADMSG, + BUSY, + CANCELED, + CHILD, + CONNABORTED, + CONNREFUSED, + CONNRESET, + DEADLK, + DESTADDRREQ, + DOM, + DQUOT, + EXIST, + FAULT, + FBIG, + HOSTUNREACH, + IDRM, + ILSEQ, + INPROGRESS, + INTR, + INVAL, + IO, + ISCONN, + ISDIR, + LOOP, + MFILE, + MLINK, + MSGSIZE, + MULTIHOP, + NAMETOOLONG, + NETDOWN, + NETRESET, + NETUNREACH, + NFILE, + NOBUFS, + NODATA, + NODEV, + NOENT, + NOEXEC, + NOLCK, + NOLINK, + NOMEM, + NOMSG, + NOPROTOOPT, + NOSPC, + NOSR, + NOSTR, + NOSYS, + NOTCONN, + NOTDIR, + NOTEMPTY, + NOTSOCK, + NOTSUP, + NOTTY, + NXIO, + OPNOTSUPP, + OVERFLOW, + PERM, + PIPE, + PROTO, + PROTONOSUPPORT, + PROTOTYPE, + RANGE, + ROFS, + SPIPE, + SRCH, + STALE, + TIME, + TIMEDOUT, + 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 CITRA_UNIX +constexpr IPv4Address TranslateIPv4(in_addr addr) { + const u32 bytes = addr.s_addr; + return IPv4Address{static_cast(bytes), static_cast(bytes >> 8), + static_cast(bytes >> 16), static_cast(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 GetHostIPv4Address(); + +} // namespace Network diff --git a/src/core/network/network_interface.cpp b/src/core/network/network_interface.cpp new file mode 100644 index 000000000..6295f6fd6 --- /dev/null +++ b/src/core/network/network_interface.cpp @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "common/bit_cast.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/network/network_interface.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +namespace Network { + +#ifdef _WIN32 + +std::vector GetAvailableNetworkInterfaces() { + std::vector 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 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( + *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( + *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 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 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(*ifa->ifa_addr).sin_addr}, + .subnet_mask{Common::BitCast(*ifa->ifa_netmask).sin_addr}, + .gateway{in_addr{.s_addr = gateway}}}); + continue; + } + + // ignore header + file.ignore(std::numeric_limits::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 ) + 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(*ifa->ifa_addr).sin_addr}, + .subnet_mask{Common::BitCast(*ifa->ifa_netmask).sin_addr}, + .gateway{in_addr{.s_addr = gateway}}}); + } + + freeifaddrs(ifaddr); + + return result; +} + +#endif + +std::optional 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::find_if(network_interfaces.begin(), network_interfaces.end(), [&selected_network_interface](const auto& iface) { + return iface.name == selected_network_interface; + }); + + if (res == network_interfaces.end()) { + LOG_DEBUG(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/network/network_interface.h b/src/core/network/network_interface.h new file mode 100644 index 000000000..635d8032b --- /dev/null +++ b/src/core/network/network_interface.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace Network { + +struct NetworkInterface { + std::string name; + struct in_addr ip_address; + struct in_addr subnet_mask; + struct in_addr gateway; +}; + +std::vector GetAvailableNetworkInterfaces(); + +std::optional GetSelectedNetworkInterface(); + +void SelectFirstNetworkInterface(); + +} // namespace Network diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h new file mode 100644 index 000000000..d08cef6f5 --- /dev/null +++ b/src/core/network/sockets.h @@ -0,0 +1,174 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#elif !CITRA_UNIX +#error "Platform not implemented" +#endif + +#include "common/common_types.h" +#include "core/network/network.h" + +namespace Network { + +class SocketBase { +public: +#ifdef CITRA_UNIX + using SOCKET = int; + static constexpr SOCKET INVALID_SOCKET = -1; + static constexpr SOCKET SOCKET_ERROR = -1; +#endif + + struct AcceptResult { + std::unique_ptr socket; + CTRSockAddr sockaddr_in; + }; + + SocketBase() = default; + explicit SocketBase(SOCKET fd_) : fd{fd_} {} + virtual ~SocketBase() = default; + + CITRA_NON_COPYABLE(SocketBase); + CITRA_NON_MOVEABLE(SocketBase); + + virtual Errno Initialize(Domain domain, Type type, u32 protocol) = 0; + + virtual Errno Close() = 0; + + virtual std::pair Accept() = 0; + + virtual Errno Connect(CTRSockAddr addr_in) = 0; + + virtual std::pair GetPeerName() = 0; + + virtual std::pair GetSockName() = 0; + + virtual Errno Bind(CTRSockAddr addr) = 0; + + virtual Errno Listen(s32 backlog) = 0; + + virtual Errno Shutdown(ShutdownHow how) = 0; + + virtual std::pair Recv(int flags, std::vector& message) = 0; + + virtual std::pair RecvFrom(int flags, std::vector& message, + CTRSockAddr* addr) = 0; + + virtual std::pair Send(std::span message, int flags) = 0; + + virtual std::pair SendTo(SendFlags flags, std::span message, + const CTRSockAddr* 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; + + [[nodiscard]] SOCKET GetFD() const { + return fd; + } + + [[nodiscard]] bool IsBlocking() const { + return blocking; + } + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& fd; + ar& blocking; + } + friend class boost::serialization::access; + +protected: + SOCKET fd = INVALID_SOCKET; + bool blocking = true; +}; + +class Socket : public SocketBase { +public: + Socket() = default; + explicit Socket(SOCKET fd_) : SocketBase{fd_} {} + + ~Socket() override; + + Socket(Socket&& rhs) noexcept; + + Errno Initialize(Domain domain, Type type, u32 protocol) override; + + Errno Close() override; + + std::pair Accept() override; + + Errno Connect(CTRSockAddr addr_in) override; + + std::pair GetPeerName() override; + + std::pair GetSockName() override; + + Errno Bind(CTRSockAddr addr) override; + + Errno Listen(s32 backlog) override; + + Errno Shutdown(ShutdownHow how) override; + + std::pair Recv(int flags, std::vector& message) override; + + std::pair RecvFrom(int flags, std::vector& message, CTRSockAddr* addr) override; + + std::pair Send(std::span message, int flags) override; + + std::pair SendTo(SendFlags flags, std::span message, + const CTRSockAddr* 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 + Errno SetSockOpt(SOCKET fd, int option, T value); + + bool IsOpened() const override; +}; + +std::pair Poll(std::vector& poll_fds, s32 timeout); + +} // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index ee1c921d4..61417778a 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -10,6 +10,7 @@ #include #include #include "common/common_types.h" +#include "common/socket_types.h" #include "network/room.h" namespace Network { @@ -44,6 +45,15 @@ private: friend class boost::serialization::access; }; +/// Information about the received proxy packets. +struct ProxyPacket { + SockAddrIn local_endpoint; + SockAddrIn remote_endpoint; + Protocol protocol; + bool broadcast; + std::vector data; +}; + /// Represents a chat message. struct ChatEntry { std::string nickname; ///< Nickname of the client who sent this message. diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index 85681ab83..513efd5ae 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -1,7 +1,7 @@ // Copyright 2015 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. - +#pragma optimize("", off) #include "common/arch.h" #if CITRA_ARCH(x86_64)