// Copyright 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include #include "common/archives.h" #include "common/assert.h" #include "common/bit_field.h" #include "common/common_types.h" #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/swap.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/soc/soc_u.h" #ifdef _WIN32 #include #include // MinGW does not define several errno constants #ifndef _MSC_VER #define EBADMSG 104 #define ENODATA 120 #define ENOMSG 122 #define ENOSR 124 #define ENOSTR 125 #define ETIME 137 #endif // _MSC_VER #else #include #include #include #include #include #include #include #include #include #include #include #endif #ifdef _WIN32 #define WSAEAGAIN WSAEWOULDBLOCK #define WSAEMULTIHOP -1 // Invalid dummy value #define ERRNO(x) WSA##x #define GET_ERRNO WSAGetLastError() #define poll(x, y, z) WSAPoll(x, y, z); #define SHUT_RD SD_RECEIVE #define SHUT_WR SD_SEND #define SHUT_RDWR SD_BOTH #else #define ERRNO(x) x #define GET_ERRNO errno #define closesocket(x) close(x) #endif #define MSGCUSTOM_HANDLE_DONTWAIT 0x80000000 SERIALIZE_EXPORT_IMPL(Service::SOC::SOC_U) // Change according the debugging needs #define LOG_SEND_RECV LOG_TRACE #define LOG_POLL LOG_TRACE namespace Service::SOC { const s32 SOCKET_ERROR_VALUE = -1; static u32 SocketProtocolToPlatform(u32 protocol) { switch (protocol) { case 0: return 0; case 6: return IPPROTO_TCP; case 17: return IPPROTO_UDP; default: break; } return -1; } static u32 SocketProtocolFromPlatform(u32 protocol) { switch (protocol) { case 0: return 0; case IPPROTO_TCP: return 6; case IPPROTO_UDP: return 17; default: break; } return -1; } static u32 SocketDomainToPlatform(u32 domain) { switch (domain) { case 0: return AF_UNSPEC; case 2: return AF_INET; case 23: return AF_INET6; default: break; } return -1; } static u32 SocketDomainFromPlatform(u32 domain) { switch (domain) { case AF_UNSPEC: return 0; case AF_INET: return 2; case AF_INET6: return 23; default: break; } return -1; } static u32 SocketTypeToPlatform(u32 type) { switch (type) { case 0: return 0; case 1: return SOCK_STREAM; case 2: return SOCK_DGRAM; default: break; } return -1; } static u32 SocketTypeFromPlatform(u32 type) { switch (type) { case 0: return 0; case SOCK_STREAM: return 1; case SOCK_DGRAM: return 2; default: break; } 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}, #ifdef _WIN32 {WSAESHUTDOWN, 56}, #endif {ENOTDIR, 57}, {ERRNO(ENOTEMPTY), 58}, #ifdef _WIN32 {ERRNO(ENOTSOCK), 8}, #else {ERRNO(ENOTSOCK), 59}, #endif {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; }; /// Holds the translation from system network socket options to 3DS network socket options /// Note: -1 = No effect/unavailable static constexpr u64 sockopt_map_key(int i, int j) { return (u64)i << 32 | (unsigned int)j; } const std::unordered_map> SOC_U::sockopt_map = {{ {sockopt_map_key(SOC_SOL_IP, 0x0007), {IPPROTO_IP, IP_TOS}}, {sockopt_map_key(SOC_SOL_IP, 0x0008), {IPPROTO_IP, IP_TTL}}, {sockopt_map_key(SOC_SOL_IP, 0x0009), {IPPROTO_IP, IP_MULTICAST_LOOP}}, // Never used? {sockopt_map_key(SOC_SOL_IP, 0x000A), {IPPROTO_IP, IP_MULTICAST_TTL}}, // Never used? {sockopt_map_key(SOC_SOL_IP, 0x000B), {IPPROTO_IP, IP_ADD_MEMBERSHIP}}, // Never used? {sockopt_map_key(SOC_SOL_IP, 0x000C), {IPPROTO_IP, IP_DROP_MEMBERSHIP}}, // Never used? {sockopt_map_key(SOC_SOL_SOCKET, 0x0004), {SOL_SOCKET, SO_REUSEADDR}}, {sockopt_map_key(SOC_SOL_SOCKET, 0x0080), {SOL_SOCKET, SO_LINGER}}, {sockopt_map_key(SOC_SOL_SOCKET, 0x0100), {SOL_SOCKET, SO_OOBINLINE}}, {sockopt_map_key(SOC_SOL_SOCKET, 0x1001), {SOL_SOCKET, SO_SNDBUF}}, {sockopt_map_key(SOC_SOL_SOCKET, 0x1002), {SOL_SOCKET, SO_RCVBUF}}, {sockopt_map_key(SOC_SOL_SOCKET, 0x1003), {SOL_SOCKET, SO_SNDLOWAT}}, // Not supported in winsock2 {sockopt_map_key(SOC_SOL_SOCKET, 0x1004), {SOL_SOCKET, SO_RCVLOWAT}}, // Not supported in winsock2 {sockopt_map_key(SOC_SOL_SOCKET, 0x1008), {SOL_SOCKET, SO_TYPE}}, {sockopt_map_key(SOC_SOL_SOCKET, 0x1009), {SOL_SOCKET, SO_ERROR}}, {sockopt_map_key(SOC_SOL_TCP, 0x2001), {IPPROTO_TCP, TCP_NODELAY}}, {sockopt_map_key(SOC_SOL_TCP, 0x2002), {IPPROTO_TCP, -1}}, // TCP_MAXSEG, never used? {sockopt_map_key(SOC_SOL_TCP, 0x2003), {IPPROTO_TCP, -1}}, // TCP_STDURG, never used? }}; /// Converts a socket option from platform-specific to 3ds-specific std::pair SOC_U::TranslateSockOpt(int level, int opt) { const auto& found = sockopt_map.find(sockopt_map_key(level, opt)); if (found != sockopt_map.end()) { return found->second; } return std::make_pair(SOL_SOCKET, opt); } void SOC_U::TranslateSockOptDataToPlatform(std::vector& out, const std::vector& in, int platform_level, int platform_opt) { // linger structure may be different between 3DS and platform if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER && in.size() == sizeof(CTRLinger)) { linger linger_out; linger_out.l_onoff = static_cast( reinterpret_cast(in.data())->l_onoff); linger_out.l_linger = static_cast( reinterpret_cast(in.data())->l_linger); out.resize(sizeof(linger)); std::memcpy(out.data(), &linger_out, sizeof(linger)); return; } // Other options should have the size of an int, even for booleans int value; if (in.size() == sizeof(u8)) { value = static_cast(*reinterpret_cast(in.data())); } else if (in.size() == sizeof(u16)) { value = static_cast(*reinterpret_cast(in.data())); } else if (in.size() == sizeof(u32)) { value = static_cast(*reinterpret_cast(in.data())); } else { LOG_ERROR( Service_SOC, "Unknown sockopt data combination: in_size={}, platform_level={}, platform_opt={}", in.size(), platform_level, platform_opt); out = in; return; } // Setting TTL to 0 means resetting it to the default value. if (platform_level == IPPROTO_IP && platform_opt == IP_TTL && value == 0) { value = SOC_TTL_DEFAULT; } out.resize(sizeof(int)); std::memcpy(out.data(), &value, sizeof(int)); } static u32 TranslateSockOptSizeToPlatform(int platform_level, int platform_opt) { if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER) return sizeof(linger); return sizeof(int); } static void TranslateSockOptDataFromPlatform(std::vector& out, const std::vector& in, int platform_level, int platform_opt) { if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER && in.size() == sizeof(linger)) { CTRLinger linger_out; linger_out.l_onoff = static_cast( reinterpret_cast(in.data())->l_onoff); linger_out.l_linger = static_cast( reinterpret_cast(in.data())->l_linger); std::memcpy(out.data(), &linger_out, std::min(out.size(), sizeof(CTRLinger))); return; } if (out.size() == sizeof(u8) && in.size() == sizeof(int)) { *reinterpret_cast(out.data()) = static_cast(*reinterpret_cast(in.data())); } else if (out.size() == sizeof(u16_le) && in.size() == sizeof(int)) { *reinterpret_cast(out.data()) = static_cast(*reinterpret_cast(in.data())); } else if (out.size() == sizeof(u32_le) && in.size() == sizeof(int)) { *reinterpret_cast(out.data()) = static_cast(*reinterpret_cast(in.data())); } else { LOG_ERROR(Service_SOC, "Unknown sockopt data combination: in_size={}, out_size={}, platform_level={}, " "platform_opt={}", in.size(), out.size(), platform_level, platform_opt); out = in; return; } } 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; } std::optional> SOC_U::GetSocketHolder(u32 ctr_socket_fd, u32 process_id, IPC::RequestParser& rp) { if (initialized_processes.find(process_id) == initialized_processes.end()) { LOG_DEBUG(Service_SOC, "Process not initialized: pid={}", process_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultNotInitialized); return std::nullopt; } auto fd_info = created_sockets.find(ctr_socket_fd); if (fd_info == created_sockets.end()) { LOG_DEBUG(Service_SOC, "Invalid socket: pid={}, fd={}", process_id, ctr_socket_fd); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultInvalidSocketDescriptor); return std::nullopt; } if (fd_info->second.ownerProcess != process_id && !fd_info->second.isGlobal) { LOG_DEBUG(Service_SOC, "Invalid process owner: pid={}, fd={}, owner_pid={}", process_id, ctr_socket_fd, fd_info->second.ownerProcess); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultWrongProcess); return std::nullopt; } return std::ref(fd_info->second); } void SOC_U::CloseAndDeleteAllSockets(s32 process_id) { std::erase_if(created_sockets, [process_id](const auto& entry) { if (process_id == -1 || entry.second.ownerProcess == static_cast(process_id)) { closesocket(entry.second.socket_fd); return true; } return false; }); } static u32 SendRecvFlagsToPlatform(u32 flags) { u32 ret = 0; if (flags & 1) { ret |= MSG_OOB; } if (flags & 2) { ret |= MSG_PEEK; } if (flags & 4) { // Magic value to decide platform specific action ret |= MSGCUSTOM_HANDLE_DONTWAIT; } return ret; } static s32 ShutdownHowToPlatform(s32 how) { if (how == 0) { return SHUT_RD; } if (how == 1) { return SHUT_WR; } if (how == 2) { return SHUT_RDWR; } return -1; } /// Structure to represent the 3ds' pollfd structure, which is different than most implementations struct CTRPollFD { u32 fd; ///< Socket handle union Events { u32 hex; ///< The complete value formed by the flags BitField<0, 1, u32> pollrdnorm; BitField<1, 1, u32> pollrdband; // The 3DS OS mistakenly uses POLLRDBAND as POLLPRI, BitField<2, 1, u32> pollpri; // this is reflected in the translate functions below. BitField<3, 1, u32> pollwrnorm; BitField<4, 1, u32> pollwrband; BitField<5, 1, u32> pollerr; BitField<6, 1, u32> pollhup; BitField<7, 1, u32> pollnval; /// Translates the resulting events of a Poll operation from platform-specific to 3ds /// specific static Events TranslateTo3DS(u32 input_event, u8 haslibctrbug) { Events ev = {}; if (input_event & POLLRDNORM) ev.pollrdnorm.Assign(1); if (input_event & POLLRDBAND) ev.pollpri.Assign(1); if (input_event & POLLHUP) ev.pollhup.Assign(1); if (input_event & POLLERR) ev.pollerr.Assign(1); if (input_event & POLLWRNORM) { if (haslibctrbug) { ev.pollwrband.Assign(1); } else { ev.pollwrnorm.Assign(1); } } if (input_event & POLLWRBAND) ev.pollwrband.Assign(1); if (input_event & POLLNVAL) ev.pollnval.Assign(1); return ev; } /// Translates the resulting events of a Poll operation from 3ds specific to platform /// specific static u32 TranslateToPlatform(Events input_event, bool isOutput, u8& has_libctru_bug) { #if _WIN32 constexpr bool isWin = true; #else constexpr bool isWin = false; #endif has_libctru_bug = 0; if (!input_event.pollwrnorm && input_event.pollwrband) { // Fixes a bug in libctru and some homebrew using libcurl // having the wrong value for POLLOUT input_event.pollwrnorm.Assign(1); input_event.pollwrband.Assign(0); has_libctru_bug = 1; } u32 ret = 0; if (input_event.pollrdnorm) ret |= POLLRDNORM; if (input_event.pollrdband && !isWin) ret |= POLLPRI; if (input_event.pollhup && isOutput) ret |= POLLHUP; if (input_event.pollerr && isOutput) ret |= POLLERR; if (input_event.pollwrnorm) ret |= POLLWRNORM; if (input_event.pollwrband && !isWin) ret |= POLLWRBAND; if (input_event.pollnval && isOutput) ret |= POLLNVAL; return ret; } }; 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.created_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.created_sockets.find(fd.fd); ASSERT(iter != socu.created_sockets.end()); result.fd = iter->second.socket_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 std::array sa_data; ///< 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 CTRSockAddrIn6 { u8 len; ///< The length of the entire structure u8 sin6_family; ///< The address family of the sockaddr_in6 u16 sin6_port; ///< The port associated with this sockaddr_in6 std::array sin6_addr; ///< The actual address of the sockaddr_in6 u32 sin6_flowinfo; ///< The flow info of the sockaddr_in6 u32 sin6_scope_id; ///< The scope ID of the sockaddr_in6 } in6; static_assert(sizeof(CTRSockAddrIn6) == 28, "Invalid CTRSockAddrIn6 size"); /// Convert a 3DS CTRSockAddr to a platform-specific sockaddr static std::pair ToPlatform(CTRSockAddr const& ctr_addr) { sockaddr_storage result{}; socklen_t result_len = sizeof(result.ss_family); ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn) || ctr_addr.raw.len == sizeof(CTRSockAddrIn6), "Unhandled address size (len) in CTRSockAddr::ToPlatform"); result.ss_family = SocketDomainToPlatform(ctr_addr.raw.sa_family); // We can not guarantee ABI compatibility between platforms so we copy the fields manually switch (result.ss_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; result_len = sizeof(sockaddr_in); break; } case AF_INET6: { sockaddr_in6* result_in6 = reinterpret_cast(&result); result_in6->sin6_port = ctr_addr.in6.sin6_port; memcpy(&result_in6->sin6_addr, ctr_addr.in6.sin6_addr.data(), sizeof(result_in6->sin6_addr)); result_in6->sin6_flowinfo = ctr_addr.in6.sin6_flowinfo; result_in6->sin6_scope_id = ctr_addr.in6.sin6_scope_id; result_len = sizeof(sockaddr_in6); break; } default: ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); break; } return std::make_pair(result, result_len); } /// Convert a platform-specific sockaddr to a 3DS CTRSockAddr static CTRSockAddr FromPlatform(sockaddr_storage const& addr) { CTRSockAddr result; result.raw.sa_family = static_cast(SocketDomainFromPlatform(addr.ss_family)); // We can not guarantee ABI compatibility between platforms so we copy the fields manually switch (addr.ss_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; } case AF_INET6: { sockaddr_in6 const* addr_in6 = reinterpret_cast(&addr); result.raw.len = sizeof(CTRSockAddrIn6); result.in6.sin6_port = addr_in6->sin6_port; memcpy(result.in6.sin6_addr.data(), &addr_in6->sin6_addr, sizeof(result.in6.sin6_addr)); result.in6.sin6_flowinfo = addr_in6->sin6_flowinfo; result.in6.sin6_scope_id = addr_in6->sin6_scope_id; 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(*reinterpret_cast(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; } }; struct HostByNameData { static constexpr u32 max_entries = 24; u16_le addr_type; u16_le addr_len; u16_le addr_count; u16_le alias_count; std::array h_name; std::array, max_entries> aliases; std::array, max_entries> addresses; }; static_assert(sizeof(HostByNameData) == 0x1A88, "Invalid HostByNameData size"); static u32 NameInfoFlagsToPlatform(u32 flags) { u32 ret = 0; if (flags & 1) { ret |= NI_NOFQDN; } if (flags & 2) { ret |= NI_NUMERICHOST; } if (flags & 4) { ret |= NI_NAMEREQD; } if (flags & 8) { ret |= NI_NUMERICSERV; } if (flags & 16) { ret |= NI_DGRAM; } return ret; } static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct"); void SOC_U::Socket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 domain = SocketDomainToPlatform(rp.Pop()); // Address family const u32 type = SocketTypeToPlatform(rp.Pop()); const u32 protocol = SocketProtocolToPlatform(rp.Pop()); const u32 pid = 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) { rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code rb.Skip(1, false); return; } if (domain != AF_INET) { rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code rb.Skip(1, false); return; } if (type != SOCK_DGRAM && type != SOCK_STREAM) { 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(); if ((s64)ret != SOCKET_ERROR_VALUE) { created_sockets[socketHandle] = { .socket_fd = static_cast(ret), .blocking = true, .isGlobal = false, .shutdown_rd = false, .ownerProcess = pid, }; #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 ((s64)ret == SOCKET_ERROR_VALUE) { ret = TranslateError(GET_ERRNO); } else { ret = socketHandle; } LOG_DEBUG(Service_SOC, "called, pid={}, ret={}", pid, static_cast(ret)); rb.Push(ResultSuccess); rb.Push(static_cast(ret)); } void SOC_U::Bind(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); const u32 len = rp.Pop(); const u32 pid = rp.PopPID(); auto sock_addr_buf = rp.PopStaticBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); CTRSockAddr ctr_sock_addr; std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), std::min(len, sizeof(ctr_sock_addr))); auto [sock_addr, sock_addr_len] = CTRSockAddr::ToPlatform(ctr_sock_addr); s32 ret = ::bind(holder.socket_fd, reinterpret_cast(&sock_addr), sock_addr_len); if (ret != 0) ret = TranslateError(GET_ERRNO); LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); const u32 ctr_cmd = rp.Pop(); const u32 ctr_arg = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); u32 posix_ret = 0; // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX) SCOPE_EXIT({ LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(posix_ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(posix_ret); }); if (ctr_cmd == 3) { // F_GETFL posix_ret = 0; if (GetSocketBlocking(holder) == false) posix_ret |= 4; // O_NONBLOCK } else if (ctr_cmd == 4) { // F_SETFL posix_ret = SetSocketBlocking(holder, !(ctr_arg & 4)); } else { LOG_ERROR(Service_SOC, "Unsupported command ({}) in fcntl call", ctr_cmd); posix_ret = TranslateError(EINVAL); // TODO: Find the correct error return; } } void SOC_U::Listen(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); const u32 backlog = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); s32 ret = ::listen(holder.socket_fd, backlog); if (ret != 0) ret = TranslateError(GET_ERRNO); LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } void SOC_U::Accept(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); const auto max_addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); struct AsyncData { // Input u32 max_addr_len{}; SocketHolder* fd_info; u32 pid; u32 socket_handle; // Output s32 ret{}; int accept_error; sockaddr_storage addr; }; auto async_data = std::make_shared(); async_data->max_addr_len = max_addr_len; async_data->fd_info = &holder; async_data->pid = pid; async_data->socket_handle = socket_handle; ctx.RunAsync( [async_data](Kernel::HLERequestContext& ctx) { socklen_t addr_len = sizeof(async_data->addr); async_data->ret = static_cast( ::accept(async_data->fd_info->socket_fd, reinterpret_cast(&async_data->addr), &addr_len)); async_data->accept_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; return 0; }, [this, async_data](Kernel::HLERequestContext& ctx) { if (static_cast(async_data->ret) != SOCKET_ERROR_VALUE) { u32 socketID = GetNextSocketID(); created_sockets[socketID] = { .socket_fd = static_cast(async_data->ret), .blocking = true, .isGlobal = false, .shutdown_rd = false, .ownerProcess = async_data->pid, }; async_data->ret = socketID; } CTRSockAddr ctr_addr; std::vector ctr_addr_buf(sizeof(ctr_addr)); if (static_cast(async_data->ret) == SOCKET_ERROR_VALUE) { async_data->ret = TranslateError(async_data->accept_error); } else { ctr_addr = CTRSockAddr::FromPlatform(async_data->addr); std::memcpy(ctr_addr_buf.data(), &ctr_addr, sizeof(ctr_addr)); } if (ctr_addr_buf.size() > async_data->max_addr_len) { LOG_DEBUG(Service_SOC, "CTRSockAddr is too long, truncating data."); ctr_addr_buf.resize(async_data->max_addr_len); } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->pid, async_data->socket_handle, static_cast(async_data->ret)); IPC::RequestBuilder rb(ctx, 0x04, 2, 2); rb.Push(ResultSuccess); rb.Push(async_data->ret); rb.PushStaticBuffer(std::move(ctr_addr_buf), 0); }); } void SOC_U::SockAtMark(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } [[maybe_unused]] SocketHolder& holder = socket_holder_optional->get(); bool is_at_mark = false; int func_res = 0; #ifdef _WIN32 u_long atMark = 0; func_res = ::ioctlsocket(holder.socket_fd, SIOCATMARK, &atMark); is_at_mark = atMark != 0; #else #ifdef ANDROID func_res = 0; LOG_WARNING(Service_SOC, "(STUBBED) called"); #else func_res = ::sockatmark(holder.socket_fd); #endif is_at_mark = func_res > 0; #endif // _WIN32 u32 ret; if (func_res == SOCKET_ERROR_VALUE) { ret = TranslateError(GET_ERRNO); } else { ret = is_at_mark ? 1 : 0; } LOG_POLL(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 host_id = 0; auto info = GetDefaultInterfaceInfo(); if (info.has_value()) { host_id = info->address; } IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(host_id); } void SOC_U::Close(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); s32 ret = 0; ret = closesocket(holder.socket_fd); if (ret != 0) { ret = TranslateError(GET_ERRNO); } LOG_DEBUG(Service_SOC, "pid={}, fd={}, ret={}", pid, socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); const u32 len = rp.Pop(); u32 flags = SendRecvFlagsToPlatform(rp.Pop()); const u32 addr_len = rp.Pop(); const u32 pid = rp.PopPID(); const auto dest_addr_buffer = rp.PopStaticBuffer(); auto input_mapped_buff = rp.PopMappedBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0; flags &= ~MSGCUSTOM_HANDLE_DONTWAIT; #ifdef _WIN32 bool was_blocking = GetSocketBlocking(holder); if (dont_wait && was_blocking) { SetSocketBlocking(holder, false); } #else if (dont_wait) { flags |= MSG_DONTWAIT; } #endif // _WIN32 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(), std::min(addr_len, sizeof(ctr_dest_addr))); auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr); ret = static_cast( ::sendto(holder.socket_fd, reinterpret_cast(input_buff.data()), len, flags, reinterpret_cast(&dest_addr), dest_addr_len)); } else { ret = static_cast(::sendto(holder.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(holder, true); } #endif if (ret == SOCKET_ERROR_VALUE) { ret = TranslateError(send_error); } LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } s32 SOC_U::SendToImpl(SocketHolder& holder, u32 len, u32 flags, u32 addr_len, const std::vector& input_buff, const u8* dest_addr_buff) { bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0; flags &= ~MSGCUSTOM_HANDLE_DONTWAIT; #ifdef _WIN32 bool was_blocking = GetSocketBlocking(holder); if (dont_wait && was_blocking) { SetSocketBlocking(holder, false); } #else if (dont_wait) { flags |= MSG_DONTWAIT; } #endif // _WIN32 s32 ret = -1; if (addr_len > 0) { CTRSockAddr ctr_dest_addr; std::memcpy(&ctr_dest_addr, dest_addr_buff, std::min(addr_len, sizeof(ctr_dest_addr))); auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr); ret = static_cast( ::sendto(holder.socket_fd, reinterpret_cast(input_buff.data()), len, flags, reinterpret_cast(&dest_addr), dest_addr_len)); } else { ret = static_cast(::sendto(holder.socket_fd, reinterpret_cast(input_buff.data()), len, flags, nullptr, 0)); } auto send_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; #ifdef _WIN32 if (dont_wait && was_blocking) { SetSocketBlocking(holder, true); } #endif if (ret == SOCKET_ERROR_VALUE) ret = TranslateError(send_error); return ret; } void SOC_U::SendToSingle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); u32 len = rp.Pop(); u32 flags = SendRecvFlagsToPlatform(rp.Pop()); u32 addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto input_buff = rp.PopStaticBuffer(); auto dest_addr_buff = rp.PopStaticBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); s32 ret = SendToImpl(holder, len, flags, addr_len, input_buff, dest_addr_buff.data()); LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } // A poll busy loop has to be used when recieving from a blocking socket // because calling shutdown on a socket that's currently in a blocking // recv call does not make it return, which causes some games to hang. void SOC_U::RecvBusyWaitForEvent(SocketHolder& holder) { constexpr int timeout_ms = 100; while (true) { pollfd poll_fd{}; poll_fd.fd = holder.socket_fd; poll_fd.events = POLLIN; // Check for available data. int res = ::poll(&poll_fd, 1, timeout_ms); // Break if there is any event, error or socket RD shutdown. if (res != 0 || holder.shutdown_rd) { break; } } } void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); u32 len = rp.Pop(); u32 flags = SendRecvFlagsToPlatform(rp.Pop()); u32 addr_len = rp.Pop(); const u32 pid = rp.PopPID(); Kernel::MappedBuffer& buffer = rp.PopMappedBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0; flags &= ~MSGCUSTOM_HANDLE_DONTWAIT; #ifdef _WIN32 bool was_blocking = GetSocketBlocking(holder); if (dont_wait && was_blocking) { SetSocketBlocking(holder, false); } #else if (dont_wait) { flags |= MSG_DONTWAIT; } #endif // _WIN32 bool needs_async = GetSocketBlocking(holder) && !dont_wait; struct AsyncData { // Input u32 len{}; u32 flags{}; u32 addr_len{}; SocketHolder* fd_info; u32 socket_handle; #ifdef _WIN32 bool dont_wait; bool was_blocking; #endif bool is_blocking; // Output s32 ret{}; int recv_error; Kernel::MappedBuffer* buffer; std::vector output_buff; std::vector addr_buff; }; auto async_data = std::make_shared(); async_data->buffer = &buffer; async_data->ret = -1; async_data->len = len; async_data->flags = flags; async_data->addr_len = addr_len; async_data->output_buff.resize(len); async_data->addr_buff.resize(addr_len); async_data->fd_info = &holder; async_data->socket_handle = socket_handle; #ifdef _WIN32 async_data->dont_wait = dont_wait; async_data->was_blocking = was_blocking; #endif async_data->is_blocking = needs_async; ctx.RunAsync( [async_data](Kernel::HLERequestContext& ctx) { sockaddr_storage src_addr; socklen_t src_addr_len = sizeof(src_addr); CTRSockAddr ctr_src_addr; // Windows, why do you have to be so special... if (async_data->is_blocking) { RecvBusyWaitForEvent(*async_data->fd_info); } if (async_data->addr_len > 0) { async_data->ret = static_cast(::recvfrom( async_data->fd_info->socket_fd, reinterpret_cast(async_data->output_buff.data()), async_data->len, async_data->flags, reinterpret_cast(&src_addr), &src_addr_len)); if (async_data->ret >= 0 && src_addr_len > 0) { ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); std::memcpy(async_data->addr_buff.data(), &ctr_src_addr, std::min(async_data->addr_len, sizeof(ctr_src_addr))); } } else { async_data->ret = static_cast( ::recvfrom(async_data->fd_info->socket_fd, reinterpret_cast(async_data->output_buff.data()), async_data->len, async_data->flags, NULL, 0)); async_data->addr_buff.resize(0); } async_data->recv_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; return 0; }, [this, async_data](Kernel::HLERequestContext& ctx) { if (async_data->ret == SOCKET_ERROR_VALUE) { async_data->ret = TranslateError(async_data->recv_error); } else { async_data->buffer->Write(async_data->output_buff.data(), 0, async_data->ret); } #ifdef _WIN32 if (async_data->dont_wait && async_data->was_blocking) { SetSocketBlocking(*async_data->fd_info, true); } #else (void)this; #endif LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", async_data->socket_handle, static_cast(async_data->ret)); IPC::RequestBuilder rb(ctx, 0x07, 2, 4); rb.Push(ResultSuccess); rb.Push(async_data->ret); rb.PushStaticBuffer(std::move(async_data->addr_buff), 0); rb.PushMappedBuffer(*async_data->buffer); }, needs_async); } void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); u32 len = rp.Pop(); u32 flags = SendRecvFlagsToPlatform(rp.Pop()); u32 addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0; flags &= ~MSGCUSTOM_HANDLE_DONTWAIT; #ifdef _WIN32 bool was_blocking = GetSocketBlocking(holder); if (dont_wait && was_blocking) { SetSocketBlocking(holder, false); } #else if (dont_wait) { flags |= MSG_DONTWAIT; } #endif // _WIN32 bool needs_async = GetSocketBlocking(holder) && !dont_wait; struct AsyncData { // Input u32 len{}; u32 flags{}; u32 addr_len{}; SocketHolder* fd_info; u32 socket_handle; #ifdef _WIN32 bool dont_wait; bool was_blocking; #endif bool is_blocking; // Output s32 ret{}; int recv_error; std::vector output_buff; std::vector addr_buff; }; auto async_data = std::make_shared(); async_data->ret = -1; async_data->len = len; async_data->flags = flags; async_data->addr_len = addr_len; async_data->output_buff.resize(len); async_data->addr_buff.resize(addr_len); async_data->fd_info = &holder; async_data->socket_handle = socket_handle; #ifdef _WIN32 async_data->dont_wait = dont_wait; async_data->was_blocking = was_blocking; #endif async_data->is_blocking = needs_async; ctx.RunAsync( [async_data](Kernel::HLERequestContext& ctx) { sockaddr_storage src_addr; socklen_t src_addr_len = sizeof(src_addr); CTRSockAddr ctr_src_addr; if (async_data->is_blocking) { RecvBusyWaitForEvent(*async_data->fd_info); } if (async_data->addr_len > 0) { // Only get src adr if input adr available async_data->ret = static_cast(::recvfrom( async_data->fd_info->socket_fd, reinterpret_cast(async_data->output_buff.data()), async_data->len, async_data->flags, reinterpret_cast(&src_addr), &src_addr_len)); if (async_data->ret >= 0 && src_addr_len > 0) { ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); std::memcpy(async_data->addr_buff.data(), &ctr_src_addr, std::min(async_data->addr_len, sizeof(ctr_src_addr))); } } else { async_data->ret = static_cast( ::recvfrom(async_data->fd_info->socket_fd, reinterpret_cast(async_data->output_buff.data()), async_data->len, async_data->flags, NULL, 0)); async_data->addr_buff.resize(0); } async_data->recv_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; return 0; }, [this, async_data](Kernel::HLERequestContext& ctx) { #ifdef _WIN32 if (async_data->dont_wait && async_data->was_blocking) { SetSocketBlocking(*async_data->fd_info, true); } #else (void)this; #endif s32 total_received = async_data->ret; if (async_data->ret == SOCKET_ERROR_VALUE) { async_data->ret = TranslateError(async_data->recv_error); total_received = 0; } // Write only the data we received to avoid overwriting parts of the buffer with zeros async_data->output_buff.resize(total_received); LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", async_data->socket_handle, static_cast(async_data->ret)); IPC::RequestBuilder rb(ctx, 0x08, 3, 4); rb.Push(ResultSuccess); rb.Push(async_data->ret); rb.Push(total_received); rb.PushStaticBuffer(std::move(async_data->output_buff), 0); rb.PushStaticBuffer(std::move(async_data->addr_buff), 1); }, needs_async); } void SOC_U::Poll(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 nfds = rp.Pop(); s32 timeout = rp.Pop(); const u32 pid = rp.PopPID(); auto input_fds = rp.PopStaticBuffer(); struct AsyncData { // Input s32 timeout; u32 nfds; // Input/Output std::vector platform_pollfd; std::vector has_libctru_bug; std::vector ctr_fds; // Output s32 ret; int poll_error; }; auto async_data = std::make_shared(); async_data->timeout = timeout; async_data->nfds = nfds; async_data->ctr_fds.resize(nfds); std::memcpy(async_data->ctr_fds.data(), input_fds.data(), nfds * sizeof(CTRPollFD)); // The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different // sizes) // so we have to copy the data in order async_data->platform_pollfd.resize(nfds); async_data->has_libctru_bug.resize(nfds, false); for (u32 i = 0; i < nfds; i++) { if (!GetSocketHolder(async_data->ctr_fds[i].fd, pid, rp)) { return; } async_data->platform_pollfd[i] = CTRPollFD::ToPlatform(*this, async_data->ctr_fds[i], async_data->has_libctru_bug[i]); } ctx.RunAsync( [async_data](Kernel::HLERequestContext& ctx) { async_data->ret = ::poll(async_data->platform_pollfd.data(), async_data->nfds, async_data->timeout); if (async_data->ret == SOCKET_ERROR_VALUE) { async_data->poll_error = GET_ERRNO; } return 0; }, [this, async_data](Kernel::HLERequestContext& ctx) { // Now update the output 3ds_pollfd structure for (u32 i = 0; i < async_data->nfds; i++) { async_data->ctr_fds[i] = CTRPollFD::FromPlatform( *this, async_data->platform_pollfd[i], async_data->has_libctru_bug[i]); } std::vector output_fds(async_data->nfds * sizeof(CTRPollFD)); std::memcpy(output_fds.data(), async_data->ctr_fds.data(), async_data->nfds * sizeof(CTRPollFD)); if (async_data->ret == SOCKET_ERROR_VALUE) { async_data->ret = TranslateError(async_data->poll_error); } IPC::RequestBuilder rb(ctx, static_cast(ctx.CommandHeader().command_id.Value()), 2, 2); rb.Push(ResultSuccess); rb.Push(async_data->ret); rb.PushStaticBuffer(std::move(output_fds), 0); LOG_POLL(Service_SOC, "called, fd_count={}, ret={}", async_data->nfds, static_cast(async_data->ret)); }, timeout != 0); } void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); const auto max_addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); sockaddr_storage dest_addr; socklen_t dest_addr_len = sizeof(dest_addr); s32 ret = ::getsockname(holder.socket_fd, reinterpret_cast(&dest_addr), &dest_addr_len); CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); std::vector dest_addr_buff(sizeof(ctr_dest_addr)); std::memcpy(dest_addr_buff.data(), &ctr_dest_addr, sizeof(ctr_dest_addr)); if (ret != 0) ret = TranslateError(GET_ERRNO); if (dest_addr_buff.size() > max_addr_len) { LOG_DEBUG(Service_SOC, "CTRSockAddr is too long, truncating data."); dest_addr_buff.resize(max_addr_len); } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(ResultSuccess); rb.Push(ret); rb.PushStaticBuffer(std::move(dest_addr_buff), 0); } void SOC_U::Shutdown(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); s32 how = ShutdownHowToPlatform(rp.Pop()); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); s32 ret = ::shutdown(holder.socket_fd, how); if (ret != 0) { ret = TranslateError(GET_ERRNO); } else { if (how == SHUT_RD || how == SHUT_RDWR) { holder.shutdown_rd = true; } } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } static std::vector HostEntToHostByNameData(struct hostent* result) { std::vector hbn_data_out(sizeof(HostByNameData)); HostByNameData& hbn_data = *reinterpret_cast(hbn_data_out.data()); hbn_data.addr_type = result->h_addrtype; hbn_data.addr_len = result->h_length; std::strncpy(hbn_data.h_name.data(), result->h_name, 255); u16 count; for (count = 0; count < HostByNameData::max_entries; count++) { char* curr = result->h_aliases[count]; if (!curr) { break; } std::strncpy(hbn_data.aliases[count].data(), curr, 255); } hbn_data.alias_count = count; for (count = 0; count < HostByNameData::max_entries; count++) { char* curr = result->h_addr_list[count]; if (!curr) { break; } std::memcpy(hbn_data.addresses[count].data(), curr, result->h_length); } hbn_data.addr_count = count; return hbn_data_out; } void SOC_U::GetHostByAddr(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); [[maybe_unused]] int addr_len = static_cast(rp.Pop()); int type = static_cast(SocketDomainToPlatform(rp.Pop())); [[maybe_unused]] u32 out_buf_len = rp.Pop(); auto addr = rp.PopStaticBuffer(); auto [platform_addr, platform_addr_len] = CTRSockAddr::ToPlatform(*reinterpret_cast(addr.data())); struct hostent* result = ::gethostbyaddr(reinterpret_cast(&platform_addr), platform_addr_len, type); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(ResultSuccess); s32 ret; if (!result) { rb.Push(ret = TranslateError(GET_ERRNO)); } else { rb.Push(ret = 0); rb.PushStaticBuffer(HostEntToHostByNameData(result), 0); } LOG_DEBUG(Service_SOC, "called, ret={}", static_cast(ret)); } void SOC_U::GetHostByName(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); [[maybe_unused]] u32 name_len = rp.Pop(); [[maybe_unused]] u32 out_buf_len = rp.Pop(); auto host_name = rp.PopStaticBuffer(); struct hostent* result = ::gethostbyname(reinterpret_cast(host_name.data())); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(ResultSuccess); s32 ret; if (!result) { rb.Push(ret = TranslateError(GET_ERRNO)); } else { rb.Push(ret = 0); rb.PushStaticBuffer(HostEntToHostByNameData(result), 0); } LOG_DEBUG(Service_SOC, "called, ret={}", static_cast(ret)); } void SOC_U::GetPeerName(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); const auto max_addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); sockaddr_storage dest_addr; socklen_t dest_addr_len = sizeof(dest_addr); const int ret = ::getpeername(holder.socket_fd, reinterpret_cast(&dest_addr), &dest_addr_len); CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); std::vector dest_addr_buff(sizeof(ctr_dest_addr)); std::memcpy(dest_addr_buff.data(), &ctr_dest_addr, sizeof(ctr_dest_addr)); int result = 0; if (ret != 0) { result = TranslateError(GET_ERRNO); } if (dest_addr_buff.size() > max_addr_len) { LOG_DEBUG(Service_SOC, "CTRSockAddr is too long, truncating data."); dest_addr_buff.resize(max_addr_len); } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(result)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(ResultSuccess); rb.Push(result); rb.PushStaticBuffer(std::move(dest_addr_buff), 0); } void SOC_U::Connect(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); const auto input_addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto input_addr_buf = rp.PopStaticBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); struct AsyncData { // Input SocketHolder* fd_info; std::pair input_addr; u32 socket_handle; u32 pid; // Output s32 ret{}; int connect_error; }; auto async_data = std::make_shared(); async_data->fd_info = &holder; async_data->pid = pid; CTRSockAddr ctr_input_addr; std::memcpy(&ctr_input_addr, input_addr_buf.data(), std::min(input_addr_len, sizeof(ctr_input_addr))); async_data->input_addr = CTRSockAddr::ToPlatform(ctr_input_addr); async_data->socket_handle = socket_handle; ctx.RunAsync( [async_data](Kernel::HLERequestContext& ctx) { async_data->ret = ::connect(async_data->fd_info->socket_fd, reinterpret_cast(&async_data->input_addr.first), async_data->input_addr.second); async_data->connect_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; return 0; }, [async_data](Kernel::HLERequestContext& ctx) { if (async_data->ret != 0) { async_data->ret = TranslateError(async_data->connect_error); } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->pid, async_data->socket_handle, static_cast(async_data->ret)); IPC::RequestBuilder rb(ctx, 0x06, 2, 0); rb.Push(ResultSuccess); rb.Push(async_data->ret); }); } void SOC_U::InitializeSockets(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); [[maybe_unused]] const auto memory_block_size = rp.Pop(); const u32 pid = rp.PopPID(); [[maybe_unused]] auto shared_memory = rp.PopObject(); Result res = ResultSuccess; if (initialized_processes.find(pid) == initialized_processes.end()) { initialized_processes.insert(pid); } else { res = ResultAlreadyInitialized; } LOG_DEBUG(Service_SOC, "called, pid={}, res={:#08X}", pid, res.raw); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(res); } void SOC_U::ShutdownSockets(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); // This service call does not provide a PID like the others, // instead SOC seems to fetch the PID from the current session. const u32 pid = ctx.ClientThread()->owner_process.lock()->process_id; if (initialized_processes.find(pid) == initialized_processes.end()) { LOG_DEBUG(Service_SOC, "Process not initialized: pid={}", pid); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultNotInitialized); return; } CloseAndDeleteAllSockets(pid); initialized_processes.erase(pid); LOG_DEBUG(Service_SOC, "called, pid={}", pid); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); const u32 level = rp.Pop(); const s32 optname = rp.Pop(); u32 optlen = rp.Pop(); const u32 pid = rp.PopPID(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); s32 err = 0; std::vector optval(optlen); if (optname < 0) { #ifdef _WIN32 err = WSAEINVAL; #else err = EINVAL; #endif } else { const auto level_opt = TranslateSockOpt(level, optname); std::vector platform_data( TranslateSockOptSizeToPlatform(level_opt.first, level_opt.second)); socklen_t platform_data_size = static_cast(platform_data.size()); err = ::getsockopt(holder.socket_fd, level_opt.first, level_opt.second, reinterpret_cast(platform_data.data()), &platform_data_size); if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } else { platform_data.resize(static_cast(platform_data_size)); TranslateSockOptDataFromPlatform(optval, platform_data, level_opt.first, level_opt.second); } } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(err)); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(ResultSuccess); rb.Push(err); rb.Push(static_cast(optval.size())); rb.PushStaticBuffer(std::move(optval), 0); } void SOC_U::SetSockOpt(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); const auto level = rp.Pop(); const auto optname = rp.Pop(); const auto optlen = rp.Pop(); const u32 pid = rp.PopPID(); auto optval = rp.PopStaticBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); optval.resize(optlen); s32 err = 0; if (optname < 0) { #ifdef _WIN32 err = WSAEINVAL; #else err = EINVAL; #endif } else { std::vector platform_data; const auto levelopt = TranslateSockOpt(level, optname); TranslateSockOptDataToPlatform(platform_data, optval, levelopt.first, levelopt.second); err = static_cast(::setsockopt(holder.socket_fd, levelopt.first, levelopt.second, reinterpret_cast(platform_data.data()), static_cast(platform_data.size()))); if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle, static_cast(err)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(err); } void SOC_U::GetNetworkOpt(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 level = rp.Pop(); u32 opt_name = rp.Pop(); u32 opt_len = rp.Pop(); std::vector opt_data(opt_len); u32 err = SOC_ERR_INAVLID_ENUM_VALUE; /// Only available level is SOC_SOL_CONFIG, any other value returns an error if (level == SOC_SOL_CONFIG) { switch (static_cast(opt_name)) { case NetworkOpt::NETOPT_MAC_ADDRESS: { if (opt_len >= 6) { std::array fake_mac = {0}; std::memcpy(opt_data.data(), fake_mac.data(), fake_mac.size()); } LOG_WARNING(Service_SOC, "(STUBBED) called, level={} opt_name={}", level, opt_name); err = 0; } break; case NetworkOpt::NETOPT_IP_INFO: { if (opt_len >= sizeof(InterfaceInfo)) { InterfaceInfo& out_info = *reinterpret_cast(opt_data.data()); auto info = GetDefaultInterfaceInfo(); if (info.has_value()) { out_info = info.value(); } } // Extra data not used normally, takes 0xC bytes more if (opt_len >= sizeof(InterfaceInfo) + 0xC) { LOG_ERROR(Service_SOC, "(STUBBED) called, level={} opt_name={} opt_len >= 24", level, opt_name); } err = 0; } break; default: LOG_ERROR(Service_SOC, "(STUBBED) called, level={} opt_name={}", level, opt_name); break; } } else { LOG_ERROR(Service_SOC, "Unknown level={}", level); } if (err != 0) { opt_data.resize(0); opt_len = 0; } IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(ResultSuccess); rb.Push(err); rb.Push(static_cast(opt_len)); rb.PushStaticBuffer(std::move(opt_data), 0); } void SOC_U::GetAddrInfoImpl(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 node_length = rp.Pop(); u32 service_length = rp.Pop(); u32 hints_size = rp.Pop(); u32 out_size = rp.Pop(); auto node = rp.PopStaticBuffer(); auto service = rp.PopStaticBuffer(); auto hints_buff = rp.PopStaticBuffer(); const char* node_data = node_length > 0 ? reinterpret_cast(node.data()) : nullptr; const char* service_data = service_length > 0 ? reinterpret_cast(service.data()) : nullptr; s32 ret = -1; addrinfo* out = nullptr; if (hints_size > 0) { CTRAddrInfo ctr_hints; std::memcpy(&ctr_hints, hints_buff.data(), hints_size); addrinfo hints = CTRAddrInfo::ToPlatform(ctr_hints); // Mimic what soc does in real hardware if (hints.ai_socktype != 0 && hints.ai_protocol == 0) { hints.ai_protocol = hints.ai_socktype == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP; } if (hints.ai_protocol != 0 && hints.ai_socktype == 0) { hints.ai_socktype = hints.ai_protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM; } ret = getaddrinfo(node_data, service_data, &hints, &out); } else { ret = getaddrinfo(node_data, service_data, nullptr, &out); } std::vector out_buff(out_size); u32 count = 0; if (ret == SOCKET_ERROR_VALUE) { ret = TranslateError(GET_ERRNO); out_buff.resize(0); } else { std::size_t pos = 0; addrinfo* cur = out; while (cur != nullptr) { if (pos <= out_size - sizeof(CTRAddrInfo)) { // According to 3dbrew, this function fills whatever it can and does not error even // if the buffer is not big enough. However the count returned is always correct. CTRAddrInfo ctr_addr = CTRAddrInfo::FromPlatform(*cur); std::memcpy(out_buff.data() + pos, &ctr_addr, sizeof(ctr_addr)); pos += sizeof(ctr_addr); } cur = cur->ai_next; count++; } if (out != nullptr) freeaddrinfo(out); } IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(ResultSuccess); rb.Push(ret); rb.Push(count); rb.PushStaticBuffer(std::move(out_buff), 0); } void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 socklen = rp.Pop(); u32 hostlen = rp.Pop(); u32 servlen = rp.Pop(); s32 flags = NameInfoFlagsToPlatform(rp.Pop()); auto sa_buff = rp.PopStaticBuffer(); CTRSockAddr ctr_sa; std::memcpy(&ctr_sa, sa_buff.data(), socklen); auto [sa, sa_len] = CTRSockAddr::ToPlatform(ctr_sa); std::vector host(hostlen); std::vector serv(servlen); char* host_data = hostlen > 0 ? reinterpret_cast(host.data()) : nullptr; char* serv_data = servlen > 0 ? reinterpret_cast(serv.data()) : nullptr; s32 ret = getnameinfo(reinterpret_cast(&sa), sa_len, host_data, hostlen, serv_data, servlen, flags); if (ret == SOCKET_ERROR_VALUE) { ret = TranslateError(GET_ERRNO); } IPC::RequestBuilder rb = rp.MakeBuilder(2, 4); rb.Push(ResultSuccess); rb.Push(ret); rb.PushStaticBuffer(std::move(host), 0); rb.PushStaticBuffer(std::move(serv), 1); } void SOC_U::SendToMultiple(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 socket_handle = rp.Pop(); u32 len = rp.Pop(); u32 flags = SendRecvFlagsToPlatform(rp.Pop()); u32 addr_len = rp.Pop(); u32 total_addr_len = rp.Pop(); const u32 pid = rp.PopPID(); auto input_buff = rp.PopStaticBuffer(); auto dest_addr_buff = rp.PopStaticBuffer(); auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); u32 count = total_addr_len / addr_len; s32 ret = 0; u32 i = 0; do { ret = SendToImpl(holder, len, flags, addr_len, input_buff, dest_addr_buff.data() + (i * addr_len)); i++; } while (i < count && ret >= 0); LOG_DEBUG(Service_SOC, "called, pid={}, fd_count={}, ret={}", pid, count, static_cast(ret)); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ret); } void SOC_U::CloseSockets(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 pid = rp.PopPID(); if (initialized_processes.find(pid) == initialized_processes.end()) { LOG_DEBUG(Service_SOC, "Process not initialized: pid={}", pid); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultNotInitialized); return; } CloseAndDeleteAllSockets(pid); LOG_DEBUG(Service_SOC, "called, pid={}", pid); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } void SOC_U::AddGlobalSocket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto socket_handle = rp.Pop(); // This service call does not provide a PID like the others, // instead SOC seems to fetch the PID from the current session. const u32 pid = ctx.ClientThread()->owner_process.lock()->process_id; auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp); if (!socket_holder_optional) { return; } SocketHolder& holder = socket_holder_optional->get(); holder.isGlobal = true; LOG_DEBUG(Service_SOC, "called, pid={}, fd={}", pid, socket_handle); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } SOC_U::SOC_U() : ServiceFramework("soc:U", 18) { static const FunctionInfo functions[] = { // clang-format off {0x0001, &SOC_U::InitializeSockets, "InitializeSockets"}, {0x0002, &SOC_U::Socket, "Socket"}, {0x0003, &SOC_U::Listen, "Listen"}, {0x0004, &SOC_U::Accept, "Accept"}, {0x0005, &SOC_U::Bind, "Bind"}, {0x0006, &SOC_U::Connect, "Connect"}, {0x0007, &SOC_U::RecvFromOther, "recvfrom_other"}, {0x0008, &SOC_U::RecvFrom, "RecvFrom"}, {0x0009, &SOC_U::SendToOther, "SendToOther"}, {0x000A, &SOC_U::SendToSingle, "SendToSingle"}, {0x000B, &SOC_U::Close, "Close"}, {0x000C, &SOC_U::Shutdown, "Shutdown"}, {0x000D, &SOC_U::GetHostByName, "GetHostByName"}, {0x000E, &SOC_U::GetHostByAddr, "GetHostByAddr"}, {0x000F, &SOC_U::GetAddrInfoImpl, "GetAddrInfo"}, {0x0010, &SOC_U::GetNameInfoImpl, "GetNameInfo"}, {0x0011, &SOC_U::GetSockOpt, "GetSockOpt"}, {0x0012, &SOC_U::SetSockOpt, "SetSockOpt"}, {0x0013, &SOC_U::Fcntl, "Fcntl"}, {0x0014, &SOC_U::Poll, "Poll"}, {0x0015, &SOC_U::SockAtMark, "SockAtMark"}, {0x0016, &SOC_U::GetHostId, "GetHostId"}, {0x0017, &SOC_U::GetSockName, "GetSockName"}, {0x0018, &SOC_U::GetPeerName, "GetPeerName"}, {0x0019, &SOC_U::ShutdownSockets, "ShutdownSockets"}, {0x001A, &SOC_U::GetNetworkOpt, "GetNetworkOpt"}, {0x001B, nullptr, "ICMPSocket"}, {0x001C, nullptr, "ICMPPing"}, {0x001D, nullptr, "ICMPCancel"}, {0x001E, nullptr, "ICMPClose"}, {0x001F, nullptr, "GetResolverInfo"}, {0x0020, &SOC_U::SendToMultiple, "SendToMultiple"}, {0x0021, &SOC_U::CloseSockets, "CloseSockets"}, {0x0023, &SOC_U::AddGlobalSocket, "AddGlobalSocket"}, // clang-format on }; RegisterHandlers(functions); #ifdef _WIN32 WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); #endif } SOC_U::~SOC_U() { CloseAndDeleteAllSockets(); #ifdef _WIN32 WSACleanup(); #endif } std::optional SOC_U::GetDefaultInterfaceInfo() { if (this->interface_info_cached) { return InterfaceInfo(this->interface_info); } InterfaceInfo ret; #ifdef _WIN32 SOCKET sock_fd = -1; #else int sock_fd = -1; #endif bool interface_found = false; struct sockaddr_in s_in = {.sin_family = AF_INET, .sin_port = htons(53), .sin_addr = {}}; s_in.sin_addr.s_addr = inet_addr("8.8.8.8"); socklen_t s_info_len = sizeof(struct sockaddr_in); sockaddr_in s_info; if ((sock_fd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) { return std::nullopt; } if (::connect(sock_fd, (struct sockaddr*)(&s_in), sizeof(struct sockaddr_in)) != 0) { closesocket(sock_fd); return std::nullopt; } if (::getsockname(sock_fd, (struct sockaddr*)&s_info, &s_info_len) != 0 || s_info_len != sizeof(struct sockaddr_in)) { closesocket(sock_fd); return std::nullopt; } closesocket(sock_fd); #ifdef _WIN32 sock_fd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); if (sock_fd == SOCKET_ERROR) { return std::nullopt; } const int max_interfaces = 100; std::vector interface_list_vec(max_interfaces); INTERFACE_INFO* interface_list = reinterpret_cast(interface_list_vec.data()); unsigned long bytes_used; if (WSAIoctl(sock_fd, SIO_GET_INTERFACE_LIST, 0, 0, interface_list, max_interfaces * sizeof(INTERFACE_INFO), &bytes_used, 0, 0) == SOCKET_ERROR) { closesocket(sock_fd); return std::nullopt; } closesocket(sock_fd); int num_interfaces = bytes_used / sizeof(INTERFACE_INFO); for (int i = 0; i < num_interfaces; i++) { if (((sockaddr*)&(interface_list[i].iiAddress))->sa_family == AF_INET && std::memcmp(&((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr.s_addr, &s_info.sin_addr.s_addr, sizeof(s_info.sin_addr.s_addr)) == 0) { ret.address = ((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr.s_addr; ret.netmask = ((sockaddr_in*)&(interface_list[i].iiNetmask))->sin_addr.s_addr; ret.broadcast = ((sockaddr_in*)&(interface_list[i].iiBroadcastAddress))->sin_addr.s_addr; interface_found = true; { char address[16] = {0}, netmask[16] = {0}, broadcast[16] = {0}; std::strncpy(address, inet_ntoa(((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr), sizeof(address) - 1); std::strncpy(netmask, inet_ntoa(((sockaddr_in*)&(interface_list[i].iiNetmask))->sin_addr), sizeof(netmask) - 1); std::strncpy( broadcast, inet_ntoa(((sockaddr_in*)&(interface_list[i].iiBroadcastAddress))->sin_addr), sizeof(broadcast) - 1); LOG_DEBUG(Service_SOC, "Found interface: (addr: {}, netmask: {}, broadcast: {})", address, netmask, broadcast); } break; } } #else struct ifaddrs* ifaddr; struct ifaddrs* ifa; if (getifaddrs(&ifaddr) == -1) { return std::nullopt; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* in_address = (struct sockaddr_in*)ifa->ifa_addr; struct sockaddr_in* in_netmask = (struct sockaddr_in*)ifa->ifa_netmask; struct sockaddr_in* in_broadcast = (struct sockaddr_in*)ifa->ifa_broadaddr; if (in_address->sin_addr.s_addr == s_info.sin_addr.s_addr) { ret.address = in_address->sin_addr.s_addr; ret.netmask = in_netmask->sin_addr.s_addr; ret.broadcast = in_broadcast->sin_addr.s_addr; interface_found = true; { char address[16] = {0}, netmask[16] = {0}, broadcast[16] = {0}; std::strncpy(address, inet_ntoa(in_address->sin_addr), sizeof(address) - 1); std::strncpy(netmask, inet_ntoa(in_netmask->sin_addr), sizeof(netmask) - 1); std::strncpy(broadcast, inet_ntoa(in_broadcast->sin_addr), sizeof(broadcast) - 1); LOG_DEBUG(Service_SOC, "Found interface: (addr: {}, netmask: {}, broadcast: {})", address, netmask, broadcast); } } } } freeifaddrs(ifaddr); #endif // _WIN32 if (interface_found) { this->interface_info = ret; this->interface_info_cached = true; return ret; } else { LOG_DEBUG(Service_SOC, "Interface not found"); return std::nullopt; } } std::shared_ptr GetService(Core::System& system) { return system.ServiceManager().GetService("soc:U"); } void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); std::make_shared()->InstallAsService(service_manager); } } // namespace Service::SOC