core: Abstract sockets
This commit is contained in:
@ -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
|
||||
|
@ -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__)
|
||||
|
23
src/common/bit_cast.h
Normal file
23
src/common/bit_cast.h
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <version>
|
||||
|
||||
#ifdef __cpp_lib_bit_cast
|
||||
#include <bit>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename To, typename From>
|
||||
constexpr inline To BitCast(const From& from) {
|
||||
#ifdef __cpp_lib_bit_cast
|
||||
return std::bit_cast<To>(from);
|
||||
#else
|
||||
return __builtin_bit_cast(To, from);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
@ -110,6 +110,15 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
return static_cast<T>(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.
|
||||
|
@ -505,6 +505,9 @@ struct Values {
|
||||
// Miscellaneous
|
||||
Setting<std::string> log_filter{"*:Info", "log_filter"};
|
||||
|
||||
// Network
|
||||
Setting<std::string> network_interface{std::string(), "network_interface"};
|
||||
|
||||
// Video Dumping
|
||||
std::string output_format;
|
||||
std::string format_options;
|
||||
|
143
src/common/socket_types.h
Normal file
143
src/common/socket_types.h
Normal file
@ -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<u8, 4>;
|
||||
|
||||
/// 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<s32_le>(AddressInfoFlagsFromPlatform(addr.ai_flags)),
|
||||
.ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)),
|
||||
.ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)),
|
||||
.ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)),
|
||||
.ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr),
|
||||
};
|
||||
ctr_addr.ai_addrlen = static_cast<s32_le>(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<int>(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)),
|
||||
.ai_family = static_cast<int>(SocketDomainToPlatform(ctr_addr.ai_family)),
|
||||
.ai_socktype = static_cast<int>(SocketTypeToPlatform(ctr_addr.ai_socktype)),
|
||||
.ai_protocol = static_cast<int>(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
|
@ -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
|
||||
|
@ -304,13 +304,13 @@ MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process
|
||||
perms = desc.perms;
|
||||
}
|
||||
|
||||
void MappedBuffer::Read(void* dest_buffer, std::size_t offset, std::size_t size) {
|
||||
void MappedBuffer::Read(void* dest_buffer, std::size_t offset, std::size_t size) const {
|
||||
ASSERT(perms & IPC::R);
|
||||
ASSERT(offset + size <= this->size);
|
||||
memory->ReadBlock(*process, address + static_cast<VAddr>(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<VAddr>(offset), src_buffer, size);
|
||||
|
@ -129,14 +129,16 @@ public:
|
||||
MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> 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);
|
||||
}
|
||||
|
@ -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 <algorithm>
|
||||
#include <memory>
|
||||
#include <boost/serialization/array.hpp>
|
||||
|
@ -3,7 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma optimize("", off)
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
@ -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 <winsock2.h>
|
||||
@ -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<int, int> 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<u8>& 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>,
|
||||
"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<sockaddr_in*>(&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<u8>(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<sockaddr_in const*>(&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<s32_le>(AddressInfoFlagsFromPlatform(addr.ai_flags)),
|
||||
.ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)),
|
||||
.ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)),
|
||||
.ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)),
|
||||
.ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr),
|
||||
};
|
||||
ctr_addr.ai_addrlen = static_cast<s32_le>(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<int>(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)),
|
||||
.ai_family = static_cast<int>(SocketDomainToPlatform(ctr_addr.ai_family)),
|
||||
.ai_socktype = static_cast<int>(SocketTypeToPlatform(ctr_addr.ai_socktype)),
|
||||
.ai_protocol = static_cast<int>(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<u32>()); // Address family
|
||||
u32 type = SocketTypeToPlatform(rp.Pop<u32>());
|
||||
u32 protocol = SocketProtocolToPlatform(rp.Pop<u32>());
|
||||
const auto domain = rp.Pop<Network::Domain>();
|
||||
const auto type = rp.Pop<Network::Type>();
|
||||
const u32 protocol = rp.Pop<u32>();
|
||||
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<u64>(::socket(domain, type, protocol));
|
||||
u32 socketHandle = GetNextSocketID();
|
||||
auto socket = std::make_unique<Network::Socket>();
|
||||
const auto ret = socket->Initialize(domain, type, protocol);
|
||||
const u32 socket_handle = GetNextSocketID();
|
||||
|
||||
if ((s64)ret != SOCKET_ERROR_VALUE) {
|
||||
open_sockets[socketHandle] = {static_cast<decltype(SocketHolder::socket_fd)>(ret), true};
|
||||
#if _WIN32
|
||||
// Disable UDP connection reset
|
||||
int new_behavior = 0;
|
||||
unsigned long bytes_returned = 0;
|
||||
WSAIoctl(static_cast<SOCKET>(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<u32>();
|
||||
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<u32>();
|
||||
const u32 len = rp.Pop<u32>();
|
||||
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<u32>();
|
||||
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<u32>();
|
||||
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<u32>();
|
||||
const auto ctr_cmd = rp.Pop<Network::FcntlCmd>();
|
||||
const u32 ctr_arg = rp.Pop<u32>();
|
||||
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>();
|
||||
u32 ctr_arg = rp.Pop<u32>();
|
||||
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<u32>(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<u32>(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<u32>();
|
||||
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<u32>();
|
||||
const u32 backlog = rp.Pop<u32>();
|
||||
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<u32>();
|
||||
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<u32>();
|
||||
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<socklen_t>();
|
||||
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<socklen_t>(rp.Pop<u32>());
|
||||
rp.PopPID();
|
||||
sockaddr addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
u32 ret = static_cast<u32>(::accept(fd_info->second.socket_fd, &addr, &addr_len));
|
||||
|
||||
if (static_cast<s32>(ret) != SOCKET_ERROR_VALUE) {
|
||||
u32 socketID = GetNextSocketID();
|
||||
open_sockets[socketID] = {static_cast<decltype(SocketHolder::socket_fd)>(ret), true};
|
||||
ret = socketID;
|
||||
}
|
||||
auto [result, err] = socket->Accept();
|
||||
|
||||
CTRSockAddr ctr_addr;
|
||||
std::vector<u8> ctr_addr_buf(sizeof(ctr_addr));
|
||||
if (static_cast<s32>(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<u8> ctr_addr_buf(size);
|
||||
|
||||
u32 ret = static_cast<u32>(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<u32>();
|
||||
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<u32>();
|
||||
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<u32>();
|
||||
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<u32>();
|
||||
const auto flags = rp.Pop<Network::SendFlags>();
|
||||
const u32 addr_len = rp.Pop<u32>();
|
||||
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>();
|
||||
u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>());
|
||||
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<u32>();
|
||||
rp.PopPID();
|
||||
const auto dest_addr_buffer = rp.PopStaticBuffer();
|
||||
|
||||
auto input_mapped_buff = rp.PopMappedBuffer();
|
||||
std::vector<u8> input_buff(len);
|
||||
input_mapped_buff.Read(input_buff.data(), 0,
|
||||
std::min(input_mapped_buff.GetSize(), static_cast<size_t>(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<const char*>(input_buff.data()),
|
||||
len, flags, &dest_addr, sizeof(dest_addr));
|
||||
} else {
|
||||
ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast<const char*>(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<u8> message(len);
|
||||
const size_t size = std::min<size_t>(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<u32>(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::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() {
|
||||
if (this->interface_info_cached) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <boost/serialization/unordered_map.hpp>
|
||||
#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 <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& socket_fd;
|
||||
ar& blocking;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class SOC_U final : public ServiceFramework<SOC_U> {
|
||||
public:
|
||||
SOC_U();
|
||||
@ -63,11 +44,8 @@ private:
|
||||
|
||||
static const std::unordered_map<u64, std::pair<int, int>> sockopt_map;
|
||||
static std::pair<int, int> 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<std::chrono::steady_clock> 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<u32, SocketHolder> 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 <class Archive>
|
||||
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<std::chrono::steady_clock> adjust_value_last;
|
||||
std::unordered_map<u32, std::unique_ptr<Network::Socket>> 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<SOC_U> GetService(Core::System& system);
|
||||
|
747
src/core/network/network.cpp
Normal file
747
src/core/network/network.cpp
Normal file
@ -0,0 +1,747 @@
|
||||
// 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 CITRA_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 "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<u32>(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<Domain>(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<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;
|
||||
}
|
||||
|
||||
#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<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;
|
||||
}
|
||||
|
||||
#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<Domain>(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<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_DEBUG(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, 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<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(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<CTRSockAddr, Errno> 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<CTRSockAddr, Errno> 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<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, CTRSockAddr* 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(std::span<const 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(SendFlags flags, std::span<const u8> 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<const char*>(message.data()),
|
||||
static_cast<int>(message.size()), static_cast<int>(flags), to, to_len);
|
||||
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) {
|
||||
blocking = !enable;
|
||||
if (EnableNonBlock(fd, enable)) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
return GetAndLogLastError();
|
||||
}
|
||||
|
||||
bool Socket::IsOpened() const {
|
||||
return fd != INVALID_SOCKET;
|
||||
}
|
||||
|
||||
} // namespace Network
|
150
src/core/network/network.h
Normal file
150
src/core/network/network.h
Normal file
@ -0,0 +1,150 @@
|
||||
// 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>
|
||||
#undef OVERFLOW
|
||||
#elif CITRA_UNIX
|
||||
#include <netinet/in.h>
|
||||
#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<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
|
218
src/core/network/network_interface.cpp
Normal file
218
src/core/network/network_interface.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
// 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/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/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::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
|
31
src/core/network/network_interface.h
Normal file
31
src/core/network/network_interface.h
Normal file
@ -0,0 +1,31 @@
|
||||
// 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
|
174
src/core/network/sockets.h
Normal file
174
src/core/network/sockets.h
Normal file
@ -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 <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <boost/serialization/access.hpp>
|
||||
|
||||
#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<SocketBase> 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<AcceptResult, Errno> Accept() = 0;
|
||||
|
||||
virtual Errno Connect(CTRSockAddr addr_in) = 0;
|
||||
|
||||
virtual std::pair<CTRSockAddr, Errno> GetPeerName() = 0;
|
||||
|
||||
virtual std::pair<CTRSockAddr, Errno> GetSockName() = 0;
|
||||
|
||||
virtual Errno Bind(CTRSockAddr 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,
|
||||
CTRSockAddr* addr) = 0;
|
||||
|
||||
virtual std::pair<s32, Errno> Send(std::span<const u8> message, int flags) = 0;
|
||||
|
||||
virtual std::pair<s32, Errno> SendTo(SendFlags flags, std::span<const u8> 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 <class Archive>
|
||||
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<AcceptResult, Errno> Accept() override;
|
||||
|
||||
Errno Connect(CTRSockAddr addr_in) override;
|
||||
|
||||
std::pair<CTRSockAddr, Errno> GetPeerName() override;
|
||||
|
||||
std::pair<CTRSockAddr, Errno> GetSockName() override;
|
||||
|
||||
Errno Bind(CTRSockAddr 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, CTRSockAddr* addr) override;
|
||||
|
||||
std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override;
|
||||
|
||||
std::pair<s32, Errno> SendTo(SendFlags flags, std::span<const u8> 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 <typename T>
|
||||
Errno SetSockOpt(SOCKET fd, int option, T value);
|
||||
|
||||
bool IsOpened() const override;
|
||||
};
|
||||
|
||||
std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout);
|
||||
|
||||
} // namespace Network
|
@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#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<u8> data;
|
||||
};
|
||||
|
||||
/// Represents a chat message.
|
||||
struct ChatEntry {
|
||||
std::string nickname; ///< Nickname of the client who sent this message.
|
||||
|
@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user