diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d3146c9ee..c44febbc28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,8 @@ option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF) CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) +CMAKE_DEPENDENT_OPTION(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ON "NOT WIN32" OFF) + # On Android, fetch and compile libcxx before doing anything else if (ANDROID) set(CMAKE_SKIP_INSTALL_RULES ON) @@ -322,6 +324,10 @@ if (MINGW) find_library(MSWSOCK_LIBRARY mswsock REQUIRED) endif() +if(ENABLE_OPENSSL) + find_package(OpenSSL 3.0.0 REQUIRED) +endif() + # Please consider this as a stub if(ENABLE_QT6 AND Qt6_LOCATION) list(APPEND CMAKE_PREFIX_PATH "${Qt6_LOCATION}") diff --git a/src/common/socket_types.h b/src/common/socket_types.h index 0a801a4433..18ad6ac95e 100644 --- a/src/common/socket_types.h +++ b/src/common/socket_types.h @@ -5,15 +5,19 @@ #include "common/common_types.h" +#include + namespace Network { /// Address families enum class Domain : u8 { - INET, ///< Address family for IPv4 + Unspecified, ///< Can be 0 in getaddrinfo hints + INET, ///< Address family for IPv4 }; /// Socket types enum class Type { + Unspecified, ///< Can be 0 in getaddrinfo hints STREAM, DGRAM, RAW, @@ -22,6 +26,7 @@ enum class Type { /// Protocol values for sockets enum class Protocol : u8 { + Unspecified, ICMP, TCP, UDP, @@ -48,4 +53,13 @@ constexpr u32 FLAG_MSG_PEEK = 0x2; constexpr u32 FLAG_MSG_DONTWAIT = 0x80; constexpr u32 FLAG_O_NONBLOCK = 0x800; +/// Cross-platform addrinfo structure +struct AddrInfo { + Domain family; + Type socket_type; + Protocol protocol; + SockAddrIn addr; + std::optional canon_name; +}; + } // namespace Network diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 227c431bcb..d95d2fe014 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -723,6 +723,7 @@ add_library(core STATIC hle/service/spl/spl_types.h hle/service/ssl/ssl.cpp hle/service/ssl/ssl.h + hle/service/ssl/ssl_backend.h hle/service/time/clock_types.h hle/service/time/ephemeral_network_system_clock_context_writer.h hle/service/time/ephemeral_network_system_clock_core.h @@ -864,6 +865,19 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) target_link_libraries(core PRIVATE dynarmic::dynarmic) endif() +if(ENABLE_OPENSSL) + target_sources(core PRIVATE + hle/service/ssl/ssl_backend_openssl.cpp) + target_link_libraries(core PRIVATE OpenSSL::SSL) +elseif (WIN32) + target_sources(core PRIVATE + hle/service/ssl/ssl_backend_schannel.cpp) + target_link_libraries(core PRIVATE Secur32) +else() + target_sources(core PRIVATE + hle/service/ssl/ssl_backend_none.cpp) +endif() + if (YUZU_USE_PRECOMPILED_HEADERS) target_precompile_headers(core PRIVATE precompiled_headers.h) endif() diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index bce45d3219..6677689dc8 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -20,6 +20,9 @@ #include "core/internal_network/sockets.h" #include "network/network.h" +using Common::Expected; +using Common::Unexpected; + namespace Service::Sockets { namespace { @@ -265,16 +268,19 @@ void BSD::GetSockOpt(HLERequestContext& ctx) { const u32 level = rp.Pop(); const auto optname = static_cast(rp.Pop()); - LOG_WARNING(Service, "(STUBBED) called. fd={} level={} optname=0x{:x}", fd, level, optname); - std::vector optval(ctx.GetWriteBufferSize()); + LOG_WARNING(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname, + optval.size()); + + Errno err = GetSockOptImpl(fd, level, optname, optval); + ctx.WriteBuffer(optval); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(-1); - rb.PushEnum(Errno::NOTCONN); + rb.Push(err == Errno::SUCCESS ? 0 : -1); + rb.PushEnum(err); rb.Push(static_cast(optval.size())); } @@ -436,6 +442,18 @@ void BSD::Close(HLERequestContext& ctx) { BuildErrnoResponse(ctx, CloseImpl(fd)); } +void BSD::DuplicateSocket(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + [[maybe_unused]] const u64 unused = rp.Pop(); + + Common::Expected res = DuplicateSocketImpl(fd); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(res.value_or(0)); // ret + rb.Push(res ? 0 : static_cast(res.error())); // bsd errno +} + void BSD::EventFd(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 initval = rp.Pop(); @@ -477,12 +495,12 @@ std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protoco auto room_member = room_network.GetRoomMember().lock(); if (room_member && room_member->IsConnected()) { - descriptor.socket = std::make_unique(room_network); + descriptor.socket = std::make_shared(room_network); } else { - descriptor.socket = std::make_unique(); + descriptor.socket = std::make_shared(); } - descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol)); + descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); descriptor.is_connection_based = IsConnectionBased(type); return {fd, Errno::SUCCESS}; @@ -538,7 +556,7 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::spansocket.get(); - result.events = TranslatePollEventsToHost(pollfd.events); + result.events = Translate(pollfd.events); result.revents = Network::PollEvents{}; return result; }); @@ -547,7 +565,7 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::span& write_buffer) { } const SockAddrIn guest_addrin = Translate(addr_in); - ASSERT(write_buffer.size() == sizeof(guest_addrin)); + ASSERT(write_buffer.size() >= sizeof(guest_addrin)); + write_buffer.resize(sizeof(guest_addrin)); std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); return Translate(bsd_errno); } @@ -633,7 +652,8 @@ Errno BSD::GetSockNameImpl(s32 fd, std::vector& write_buffer) { } const SockAddrIn guest_addrin = Translate(addr_in); - ASSERT(write_buffer.size() == sizeof(guest_addrin)); + ASSERT(write_buffer.size() >= sizeof(guest_addrin)); + write_buffer.resize(sizeof(guest_addrin)); std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); return Translate(bsd_errno); } @@ -671,13 +691,47 @@ std::pair BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { } } -Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { - UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET - +Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (level != static_cast(SocketLevel::SOCKET)) { + UNIMPLEMENTED_MSG("Unknown getsockopt level"); + return Errno::SUCCESS; + } + + Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); + + switch (optname) { + case OptName::ERROR_: { + auto [pending_err, getsockopt_err] = socket->GetPendingError(); + if (getsockopt_err == Network::Errno::SUCCESS) { + Errno translated_pending_err = Translate(pending_err); + ASSERT_OR_EXECUTE_MSG( + optval.size() == sizeof(Errno), { return Errno::INVAL; }, + "Incorrect getsockopt option size"); + optval.resize(sizeof(Errno)); + memcpy(optval.data(), &translated_pending_err, sizeof(Errno)); + } + return Translate(getsockopt_err); + } + default: + UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); + return Errno::SUCCESS; + } +} + +Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + if (level != static_cast(SocketLevel::SOCKET)) { + UNIMPLEMENTED_MSG("Unknown setsockopt level"); + return Errno::SUCCESS; + } + Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); if (optname == OptName::LINGER) { @@ -711,6 +765,9 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con return Translate(socket->SetSndTimeo(value)); case OptName::RCVTIMEO: return Translate(socket->SetRcvTimeo(value)); + case OptName::NOSIGPIPE: + LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value); + return Errno::SUCCESS; default: UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); return Errno::SUCCESS; @@ -841,6 +898,28 @@ Errno BSD::CloseImpl(s32 fd) { return bsd_errno; } +Expected BSD::DuplicateSocketImpl(s32 fd) { + if (!IsFileDescriptorValid(fd)) { + return Unexpected(Errno::BADF); + } + + const s32 new_fd = FindFreeFileDescriptorHandle(); + if (new_fd < 0) { + LOG_ERROR(Service, "No more file descriptors available"); + return Unexpected(Errno::MFILE); + } + + file_descriptors[new_fd] = file_descriptors[fd]; + return new_fd; +} + +std::optional> BSD::GetSocket(s32 fd) { + if (!IsFileDescriptorValid(fd)) { + return std::nullopt; + } + return file_descriptors[fd]->socket; +} + s32 BSD::FindFreeFileDescriptorHandle() noexcept { for (s32 fd = 0; fd < static_cast(file_descriptors.size()); ++fd) { if (!file_descriptors[fd]) { @@ -911,7 +990,7 @@ BSD::BSD(Core::System& system_, const char* name) {24, &BSD::Write, "Write"}, {25, &BSD::Read, "Read"}, {26, &BSD::Close, "Close"}, - {27, nullptr, "DuplicateSocket"}, + {27, &BSD::DuplicateSocket, "DuplicateSocket"}, {28, nullptr, "GetResourceStatistics"}, {29, nullptr, "RecvMMsg"}, {30, nullptr, "SendMMsg"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 30ae9c1400..430edb97ca 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -8,6 +8,7 @@ #include #include "common/common_types.h" +#include "common/expected.h" #include "common/socket_types.h" #include "core/hle/service/service.h" #include "core/hle/service/sockets/sockets.h" @@ -29,12 +30,19 @@ public: explicit BSD(Core::System& system_, const char* name); ~BSD() override; + // These methods are called from SSL; the first two are also called from + // this class for the corresponding IPC methods. + // On the real device, the SSL service makes IPC calls to this service. + Common::Expected DuplicateSocketImpl(s32 fd); + Errno CloseImpl(s32 fd); + std::optional> GetSocket(s32 fd); + private: /// Maximum number of file descriptors static constexpr size_t MAX_FD = 128; struct FileDescriptor { - std::unique_ptr socket; + std::shared_ptr socket; s32 flags = 0; bool is_connection_based = false; }; @@ -138,6 +146,7 @@ private: void Write(HLERequestContext& ctx); void Read(HLERequestContext& ctx); void Close(HLERequestContext& ctx); + void DuplicateSocket(HLERequestContext& ctx); void EventFd(HLERequestContext& ctx); template @@ -153,6 +162,7 @@ private: Errno GetSockNameImpl(s32 fd, std::vector& write_buffer); Errno ListenImpl(s32 fd, s32 backlog); std::pair FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg); + Errno GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval); Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval); Errno ShutdownImpl(s32 fd, s32 how); std::pair RecvImpl(s32 fd, u32 flags, std::vector& message); @@ -161,7 +171,6 @@ private: std::pair SendImpl(s32 fd, u32 flags, std::span message); std::pair SendToImpl(s32 fd, u32 flags, std::span message, std::span addr); - Errno CloseImpl(s32 fd); s32 FindFreeFileDescriptorHandle() noexcept; bool IsFileDescriptorValid(s32 fd) const noexcept; diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 6491a73be5..22c3a31a0a 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -1,10 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/sockets/nsd.h" +#include "common/string_util.h" + namespace Service::Sockets { +constexpr Result ResultOverflow{ErrorModule::NSD, 6}; + NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { @@ -15,8 +20,8 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na {13, nullptr, "DeleteSettings"}, {14, nullptr, "ImportSettings"}, {15, nullptr, "SetChangeEnvironmentIdentifierDisabled"}, - {20, nullptr, "Resolve"}, - {21, nullptr, "ResolveEx"}, + {20, &NSD::Resolve, "Resolve"}, + {21, &NSD::ResolveEx, "ResolveEx"}, {30, nullptr, "GetNasServiceSetting"}, {31, nullptr, "GetNasServiceSettingEx"}, {40, nullptr, "GetNasRequestFqdn"}, @@ -40,6 +45,55 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na RegisterHandlers(functions); } +static ResultVal ResolveImpl(const std::string& fqdn_in) { + // The real implementation makes various substitutions. + // For now we just return the string as-is, which is good enough when not + // connecting to real Nintendo servers. + LOG_WARNING(Service, "(STUBBED) called({})", fqdn_in); + return fqdn_in; +} + +static Result ResolveCommon(const std::string& fqdn_in, std::array& fqdn_out) { + const auto res = ResolveImpl(fqdn_in); + if (res.Failed()) { + return res.Code(); + } + if (res->size() >= fqdn_out.size()) { + return ResultOverflow; + } + std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1); + return ResultSuccess; +} + +void NSD::Resolve(HLERequestContext& ctx) { + const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); + + std::array fqdn_out{}; + Result res = ResolveCommon(fqdn_in, fqdn_out); + + ctx.WriteBuffer(fqdn_out); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void NSD::ResolveEx(HLERequestContext& ctx) { + const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); + + std::array fqdn_out; + Result res = ResolveCommon(fqdn_in, fqdn_out); + + if (res.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + return; + } + + ctx.WriteBuffer(fqdn_out); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(ResultSuccess); +} + NSD::~NSD() = default; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index 5cc12b8555..a7379a8a9b 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -15,6 +15,10 @@ class NSD final : public ServiceFramework { public: explicit NSD(Core::System& system_, const char* name); ~NSD() override; + +private: + void Resolve(HLERequestContext& ctx); + void ResolveEx(HLERequestContext& ctx); }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 132dd57973..1196fb86c9 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -10,27 +10,18 @@ #include "core/core.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/sockets/sfdnsres.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/hle/service/sockets/sockets_translate.h" +#include "core/internal_network/network.h" #include "core/memory.h" -#ifdef _WIN32 -#include -#elif YUZU_UNIX -#include -#include -#include -#include -#ifndef EAI_NODATA -#define EAI_NODATA EAI_NONAME -#endif -#endif - namespace Service::Sockets { SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} { static const FunctionInfo functions[] = { {0, nullptr, "SetDnsAddressesPrivateRequest"}, {1, nullptr, "GetDnsAddressPrivateRequest"}, - {2, nullptr, "GetHostByNameRequest"}, + {2, &SFDNSRES::GetHostByNameRequest, "GetHostByNameRequest"}, {3, nullptr, "GetHostByAddrRequest"}, {4, nullptr, "GetHostStringErrorRequest"}, {5, nullptr, "GetGaiStringErrorRequest"}, @@ -38,11 +29,11 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres" {7, nullptr, "GetNameInfoRequest"}, {8, nullptr, "RequestCancelHandleRequest"}, {9, nullptr, "CancelRequest"}, - {10, nullptr, "GetHostByNameRequestWithOptions"}, + {10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"}, {11, nullptr, "GetHostByAddrRequestWithOptions"}, {12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"}, {13, nullptr, "GetNameInfoRequestWithOptions"}, - {14, nullptr, "ResolverSetOptionRequest"}, + {14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"}, {15, nullptr, "ResolverGetOptionRequest"}, }; RegisterHandlers(functions); @@ -59,188 +50,246 @@ enum class NetDbError : s32 { NoData = 4, }; -static NetDbError AddrInfoErrorToNetDbError(s32 result) { - // Best effort guess to map errors +static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) { + // These combinations have been verified on console (but are not + // exhaustive). switch (result) { - case 0: + case GetAddrInfoError::SUCCESS: return NetDbError::Success; - case EAI_AGAIN: + case GetAddrInfoError::AGAIN: return NetDbError::TryAgain; - case EAI_NODATA: - return NetDbError::NoData; + case GetAddrInfoError::NODATA: + return NetDbError::HostNotFound; + case GetAddrInfoError::SERVICE: + return NetDbError::Success; default: return NetDbError::HostNotFound; } } -static std::vector SerializeAddrInfo(const addrinfo* addrinfo, s32 result_code, - std::string_view host) { - // Adapted from - // https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190 - std::vector data; - - auto* current = addrinfo; - while (current != nullptr) { - struct SerializedResponseHeader { - u32 magic; - s32 flags; - s32 family; - s32 socket_type; - s32 protocol; - u32 address_length; - }; - static_assert(sizeof(SerializedResponseHeader) == 0x18, - "Response header size must be 0x18 bytes"); - - constexpr auto header_size = sizeof(SerializedResponseHeader); - const auto addr_size = - current->ai_addr && current->ai_addrlen > 0 ? current->ai_addrlen : 4; - const auto canonname_size = current->ai_canonname ? strlen(current->ai_canonname) + 1 : 1; - - const auto last_size = data.size(); - data.resize(last_size + header_size + addr_size + canonname_size); - - // Header in network byte order - SerializedResponseHeader header{}; - - constexpr auto HEADER_MAGIC = 0xBEEFCAFE; - header.magic = htonl(HEADER_MAGIC); - header.family = htonl(current->ai_family); - header.flags = htonl(current->ai_flags); - header.socket_type = htonl(current->ai_socktype); - header.protocol = htonl(current->ai_protocol); - header.address_length = current->ai_addr ? htonl((u32)current->ai_addrlen) : 0; - - auto* header_ptr = data.data() + last_size; - std::memcpy(header_ptr, &header, header_size); - - if (header.address_length == 0) { - std::memset(header_ptr + header_size, 0, 4); - } else { - switch (current->ai_family) { - case AF_INET: { - struct SockAddrIn { - s16 sin_family; - u16 sin_port; - u32 sin_addr; - u8 sin_zero[8]; - }; - - SockAddrIn serialized_addr{}; - const auto addr = *reinterpret_cast(current->ai_addr); - serialized_addr.sin_port = htons(addr.sin_port); - serialized_addr.sin_family = htons(addr.sin_family); - serialized_addr.sin_addr = htonl(addr.sin_addr.s_addr); - std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn)); - - char addr_string_buf[64]{}; - inet_ntop(AF_INET, &addr.sin_addr, addr_string_buf, std::size(addr_string_buf)); - LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, addr_string_buf); - break; - } - case AF_INET6: { - struct SockAddrIn6 { - s16 sin6_family; - u16 sin6_port; - u32 sin6_flowinfo; - u8 sin6_addr[16]; - u32 sin6_scope_id; - }; - - SockAddrIn6 serialized_addr{}; - const auto addr = *reinterpret_cast(current->ai_addr); - serialized_addr.sin6_family = htons(addr.sin6_family); - serialized_addr.sin6_port = htons(addr.sin6_port); - serialized_addr.sin6_flowinfo = htonl(addr.sin6_flowinfo); - serialized_addr.sin6_scope_id = htonl(addr.sin6_scope_id); - std::memcpy(serialized_addr.sin6_addr, &addr.sin6_addr, - sizeof(SockAddrIn6::sin6_addr)); - std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn6)); - - char addr_string_buf[64]{}; - inet_ntop(AF_INET6, &addr.sin6_addr, addr_string_buf, std::size(addr_string_buf)); - LOG_INFO(Service, "Resolved host '{}' to IPv6 address {}", host, addr_string_buf); - break; - } - default: - std::memcpy(header_ptr + header_size, current->ai_addr, addr_size); - break; - } - } - if (current->ai_canonname) { - std::memcpy(header_ptr + addr_size, current->ai_canonname, canonname_size); - } else { - *(header_ptr + header_size + addr_size) = 0; - } - - current = current->ai_next; +static Errno GetAddrInfoErrorToErrno(GetAddrInfoError result) { + // These combinations have been verified on console (but are not + // exhaustive). + switch (result) { + case GetAddrInfoError::SUCCESS: + // Note: Sometimes a successful lookup sets errno to EADDRNOTAVAIL for + // some reason, but that doesn't seem useful to implement. + return Errno::SUCCESS; + case GetAddrInfoError::AGAIN: + return Errno::SUCCESS; + case GetAddrInfoError::NODATA: + return Errno::SUCCESS; + case GetAddrInfoError::SERVICE: + return Errno::INVAL; + default: + return Errno::SUCCESS; } +} - // 4-byte sentinel value - data.push_back(0); - data.push_back(0); - data.push_back(0); - data.push_back(0); +template +static void Append(std::vector& vec, T t) { + size_t off = vec.size(); + vec.resize(off + sizeof(T)); + std::memcpy(vec.data() + off, &t, sizeof(T)); +} +static void AppendNulTerminated(std::vector& vec, std::string_view str) { + size_t off = vec.size(); + vec.resize(off + str.size() + 1); + std::memcpy(vec.data() + off, str.data(), str.size()); +} + +// We implement gethostbyname using the host's getaddrinfo rather than the +// host's gethostbyname, because it simplifies portability: e.g., getaddrinfo +// behaves the same on Unix and Windows, unlike gethostbyname where Windows +// doesn't implement h_errno. +static std::vector SerializeAddrInfoAsHostEnt(const std::vector& vec, + std::string_view host) { + + std::vector data; + // h_name: use the input hostname (append nul-terminated) + AppendNulTerminated(data, host); + // h_aliases: leave empty + + Append(data, 0); // count of h_aliases + // (If the count were nonzero, the aliases would be appended as nul-terminated here.) + Append(data, static_cast(Domain::INET)); // h_addrtype + Append(data, sizeof(Network::IPv4Address)); // h_length + // h_addr_list: + size_t count = vec.size(); + ASSERT(count <= UINT32_MAX); + Append(data, static_cast(count)); + for (const Network::AddrInfo& addrinfo : vec) { + // On the Switch, this is passed through htonl despite already being + // big-endian, so it ends up as little-endian. + Append(data, Network::IPv4AddressToInteger(addrinfo.addr.ip)); + + LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, + Network::IPv4AddressToString(addrinfo.addr.ip)); + } return data; } -static std::pair GetAddrInfoRequestImpl(HLERequestContext& ctx) { +static std::pair GetHostByNameRequestImpl(HLERequestContext& ctx) { struct Parameters { u8 use_nsd_resolve; - u32 unknown; + u32 cancel_handle; u64 process_id; }; IPC::RequestParser rp{ctx}; const auto parameters = rp.PopRaw(); - LOG_WARNING(Service, - "called with ignored parameters: use_nsd_resolve={}, unknown={}, process_id={}", - parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); + LOG_WARNING( + Service, + "called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}", + parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id); + + const auto host_buffer = ctx.ReadBuffer(0); + const std::string host = Common::StringFromBuffer(host_buffer); + // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. + + auto res = Network::GetAddrInfo(host, /*service*/ std::nullopt); + if (!res.has_value()) { + return {0, Translate(res.error())}; + } + + std::vector data = SerializeAddrInfoAsHostEnt(res.value(), host); + u32 data_size = static_cast(data.size()); + ctx.WriteBuffer(data, 0); + + return {data_size, GetAddrInfoError::SUCCESS}; +} + +void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) { + auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(ResultSuccess); + rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno + rb.Push(data_size); // serialized size +} + +void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) { + auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(ResultSuccess); + rb.Push(data_size); // serialized size + rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno +} + +static std::vector SerializeAddrInfo(const std::vector& vec, + std::string_view host) { + // Adapted from + // https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190 + std::vector data; + + for (const Network::AddrInfo& addrinfo : vec) { + // serialized addrinfo: + Append(data, 0xBEEFCAFE); // magic + Append(data, 0); // ai_flags + Append(data, static_cast(Translate(addrinfo.family))); // ai_family + Append(data, static_cast(Translate(addrinfo.socket_type))); // ai_socktype + Append(data, static_cast(Translate(addrinfo.protocol))); // ai_protocol + Append(data, sizeof(SockAddrIn)); // ai_addrlen + // ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size + + // ai_addr: + Append(data, static_cast(Translate(addrinfo.addr.family))); // sin_family + // On the Switch, the following fields are passed through htonl despite + // already being big-endian, so they end up as little-endian. + Append(data, addrinfo.addr.portno); // sin_port + Append(data, Network::IPv4AddressToInteger(addrinfo.addr.ip)); // sin_addr + data.resize(data.size() + 8, 0); // sin_zero + + if (addrinfo.canon_name.has_value()) { + AppendNulTerminated(data, *addrinfo.canon_name); + } else { + data.push_back(0); + } + + LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, + Network::IPv4AddressToString(addrinfo.addr.ip)); + } + + data.resize(data.size() + 4, 0); // 4-byte sentinel value + + return data; +} + +static std::pair GetAddrInfoRequestImpl(HLERequestContext& ctx) { + struct Parameters { + u8 use_nsd_resolve; + u32 cancel_handle; + u64 process_id; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw(); + + LOG_WARNING( + Service, + "called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}", + parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id); + + // TODO: If use_nsd_resolve is true, pass the name through NSD::Resolve + // before looking up. const auto host_buffer = ctx.ReadBuffer(0); const std::string host = Common::StringFromBuffer(host_buffer); - const auto service_buffer = ctx.ReadBuffer(1); - const std::string service = Common::StringFromBuffer(service_buffer); - - addrinfo* addrinfo; - // Pass null for hints. Serialized hints are also passed in a buffer, but are ignored for now - s32 result_code = getaddrinfo(host.c_str(), service.c_str(), nullptr, &addrinfo); - - u32 data_size = 0; - if (result_code == 0 && addrinfo != nullptr) { - const std::vector& data = SerializeAddrInfo(addrinfo, result_code, host); - data_size = static_cast(data.size()); - freeaddrinfo(addrinfo); - - ctx.WriteBuffer(data, 0); + std::optional service = std::nullopt; + if (ctx.CanReadBuffer(1)) { + std::span service_buffer = ctx.ReadBuffer(1); + service = Common::StringFromBuffer(service_buffer); } - return std::make_pair(data_size, result_code); + // Serialized hints are also passed in a buffer, but are ignored for now. + + auto res = Network::GetAddrInfo(host, service); + if (!res.has_value()) { + return {0, Translate(res.error())}; + } + + std::vector data = SerializeAddrInfo(res.value(), host); + u32 data_size = static_cast(data.size()); + ctx.WriteBuffer(data, 0); + + return {data_size, GetAddrInfoError::SUCCESS}; } void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) { - auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx); + auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(static_cast(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode - rb.Push(result_code); // errno - rb.Push(data_size); // serialized size + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno + rb.Push(static_cast(emu_gai_err)); // getaddrinfo error code + rb.Push(data_size); // serialized size } void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) { // Additional options are ignored - auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx); + auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); - IPC::ResponseBuilder rb{ctx, 5}; + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.Push(data_size); // serialized size - rb.Push(result_code); // errno - rb.Push(static_cast(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode - rb.Push(0); + rb.Push(data_size); // serialized size + rb.Push(static_cast(emu_gai_err)); // getaddrinfo error code + rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno +} + +void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(ResultSuccess); + rb.Push(0); // bsd errno } } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index 18e3cd60c9..d99a9d5601 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -17,8 +17,11 @@ public: ~SFDNSRES() override; private: + void GetHostByNameRequest(HLERequestContext& ctx); + void GetHostByNameRequestWithOptions(HLERequestContext& ctx); void GetAddrInfoRequest(HLERequestContext& ctx); void GetAddrInfoRequestWithOptions(HLERequestContext& ctx); + void ResolverSetOptionRequest(HLERequestContext& ctx); }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index acd2dae7b0..77426c46e3 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -22,13 +22,35 @@ enum class Errno : u32 { CONNRESET = 104, NOTCONN = 107, TIMEDOUT = 110, + INPROGRESS = 115, +}; + +enum class GetAddrInfoError : s32 { + SUCCESS = 0, + ADDRFAMILY = 1, + AGAIN = 2, + BADFLAGS = 3, + FAIL = 4, + FAMILY = 5, + MEMORY = 6, + NODATA = 7, + NONAME = 8, + SERVICE = 9, + SOCKTYPE = 10, + SYSTEM = 11, + BADHINTS = 12, + PROTOCOL = 13, + OVERFLOW_ = 14, // avoid name collision with Windows macro + OTHER = 15, }; enum class Domain : u32 { + Unspecified = 0, INET = 2, }; enum class Type : u32 { + Unspecified = 0, STREAM = 1, DGRAM = 2, RAW = 3, @@ -36,12 +58,16 @@ enum class Type : u32 { }; enum class Protocol : u32 { - UNSPECIFIED = 0, + Unspecified = 0, ICMP = 1, TCP = 6, UDP = 17, }; +enum class SocketLevel : u32 { + SOCKET = 0xffff, // i.e. SOL_SOCKET +}; + enum class OptName : u32 { REUSEADDR = 0x4, KEEPALIVE = 0x8, @@ -51,6 +77,8 @@ enum class OptName : u32 { RCVBUF = 0x1002, SNDTIMEO = 0x1005, RCVTIMEO = 0x1006, + ERROR_ = 0x1007, // avoid name collision with Windows macro + NOSIGPIPE = 0x800, // at least according to libnx }; enum class ShutdownHow : s32 { @@ -80,6 +108,9 @@ enum class PollEvents : u16 { Err = 1 << 3, Hup = 1 << 4, Nval = 1 << 5, + RdNorm = 1 << 6, + RdBand = 1 << 7, + WrBand = 1 << 8, }; DECLARE_ENUM_FLAG_OPERATORS(PollEvents); diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index 594e58f90f..2f9a0e39c3 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -29,6 +29,8 @@ Errno Translate(Network::Errno value) { return Errno::TIMEDOUT; case Network::Errno::CONNRESET: return Errno::CONNRESET; + case Network::Errno::INPROGRESS: + return Errno::INPROGRESS; default: UNIMPLEMENTED_MSG("Unimplemented errno={}", value); return Errno::SUCCESS; @@ -39,8 +41,50 @@ std::pair Translate(std::pair value) { return {value.first, Translate(value.second)}; } +GetAddrInfoError Translate(Network::GetAddrInfoError error) { + switch (error) { + case Network::GetAddrInfoError::SUCCESS: + return GetAddrInfoError::SUCCESS; + case Network::GetAddrInfoError::ADDRFAMILY: + return GetAddrInfoError::ADDRFAMILY; + case Network::GetAddrInfoError::AGAIN: + return GetAddrInfoError::AGAIN; + case Network::GetAddrInfoError::BADFLAGS: + return GetAddrInfoError::BADFLAGS; + case Network::GetAddrInfoError::FAIL: + return GetAddrInfoError::FAIL; + case Network::GetAddrInfoError::FAMILY: + return GetAddrInfoError::FAMILY; + case Network::GetAddrInfoError::MEMORY: + return GetAddrInfoError::MEMORY; + case Network::GetAddrInfoError::NODATA: + return GetAddrInfoError::NODATA; + case Network::GetAddrInfoError::NONAME: + return GetAddrInfoError::NONAME; + case Network::GetAddrInfoError::SERVICE: + return GetAddrInfoError::SERVICE; + case Network::GetAddrInfoError::SOCKTYPE: + return GetAddrInfoError::SOCKTYPE; + case Network::GetAddrInfoError::SYSTEM: + return GetAddrInfoError::SYSTEM; + case Network::GetAddrInfoError::BADHINTS: + return GetAddrInfoError::BADHINTS; + case Network::GetAddrInfoError::PROTOCOL: + return GetAddrInfoError::PROTOCOL; + case Network::GetAddrInfoError::OVERFLOW_: + return GetAddrInfoError::OVERFLOW_; + case Network::GetAddrInfoError::OTHER: + return GetAddrInfoError::OTHER; + default: + UNIMPLEMENTED_MSG("Unimplemented GetAddrInfoError={}", error); + return GetAddrInfoError::OTHER; + } +} + Network::Domain Translate(Domain domain) { switch (domain) { + case Domain::Unspecified: + return Network::Domain::Unspecified; case Domain::INET: return Network::Domain::INET; default: @@ -51,6 +95,8 @@ Network::Domain Translate(Domain domain) { Domain Translate(Network::Domain domain) { switch (domain) { + case Network::Domain::Unspecified: + return Domain::Unspecified; case Network::Domain::INET: return Domain::INET; default: @@ -61,39 +107,69 @@ Domain Translate(Network::Domain domain) { Network::Type Translate(Type type) { switch (type) { + case Type::Unspecified: + return Network::Type::Unspecified; case Type::STREAM: return Network::Type::STREAM; case Type::DGRAM: return Network::Type::DGRAM; + case Type::RAW: + return Network::Type::RAW; + case Type::SEQPACKET: + return Network::Type::SEQPACKET; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return Network::Type{}; } } -Network::Protocol Translate(Type type, Protocol protocol) { +Type Translate(Network::Type type) { + switch (type) { + case Network::Type::Unspecified: + return Type::Unspecified; + case Network::Type::STREAM: + return Type::STREAM; + case Network::Type::DGRAM: + return Type::DGRAM; + case Network::Type::RAW: + return Type::RAW; + case Network::Type::SEQPACKET: + return Type::SEQPACKET; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return Type{}; + } +} + +Network::Protocol Translate(Protocol protocol) { switch (protocol) { - case Protocol::UNSPECIFIED: - LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type"); - switch (type) { - case Type::DGRAM: - return Network::Protocol::UDP; - case Type::STREAM: - return Network::Protocol::TCP; - default: - return Network::Protocol::TCP; - } + case Protocol::Unspecified: + return Network::Protocol::Unspecified; case Protocol::TCP: return Network::Protocol::TCP; case Protocol::UDP: return Network::Protocol::UDP; default: UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); - return Network::Protocol::TCP; + return Network::Protocol::Unspecified; } } -Network::PollEvents TranslatePollEventsToHost(PollEvents flags) { +Protocol Translate(Network::Protocol protocol) { + switch (protocol) { + case Network::Protocol::Unspecified: + return Protocol::Unspecified; + case Network::Protocol::TCP: + return Protocol::TCP; + case Network::Protocol::UDP: + return Protocol::UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); + return Protocol::Unspecified; + } +} + +Network::PollEvents Translate(PollEvents flags) { Network::PollEvents result{}; const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) { if (True(flags & from)) { @@ -107,12 +183,15 @@ Network::PollEvents TranslatePollEventsToHost(PollEvents flags) { translate(PollEvents::Err, Network::PollEvents::Err); translate(PollEvents::Hup, Network::PollEvents::Hup); translate(PollEvents::Nval, Network::PollEvents::Nval); + translate(PollEvents::RdNorm, Network::PollEvents::RdNorm); + translate(PollEvents::RdBand, Network::PollEvents::RdBand); + translate(PollEvents::WrBand, Network::PollEvents::WrBand); UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); return result; } -PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) { +PollEvents Translate(Network::PollEvents flags) { PollEvents result{}; const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) { if (True(flags & from)) { @@ -127,13 +206,18 @@ PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) { translate(Network::PollEvents::Err, PollEvents::Err); translate(Network::PollEvents::Hup, PollEvents::Hup); translate(Network::PollEvents::Nval, PollEvents::Nval); + translate(Network::PollEvents::RdNorm, PollEvents::RdNorm); + translate(Network::PollEvents::RdBand, PollEvents::RdBand); + translate(Network::PollEvents::WrBand, PollEvents::WrBand); UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); return result; } Network::SockAddrIn Translate(SockAddrIn value) { - ASSERT(value.len == 0 || value.len == sizeof(value)); + // Note: 6 is incorrect, but can be passed by homebrew (because libnx sets + // sin_len to 6 when deserializing getaddrinfo results). + ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6); return { .family = Translate(static_cast(value.family)), diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h index c93291d3ee..694868b37e 100644 --- a/src/core/hle/service/sockets/sockets_translate.h +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -17,6 +17,9 @@ Errno Translate(Network::Errno value); /// Translate abstract return value errno pair to guest return value errno pair std::pair Translate(std::pair value); +/// Translate abstract getaddrinfo error to guest getaddrinfo error +GetAddrInfoError Translate(Network::GetAddrInfoError value); + /// Translate guest domain to abstract domain Network::Domain Translate(Domain domain); @@ -26,14 +29,20 @@ Domain Translate(Network::Domain domain); /// Translate guest type to abstract type Network::Type Translate(Type type); -/// Translate guest protocol to abstract protocol -Network::Protocol Translate(Type type, Protocol protocol); +/// Translate abstract type to guest type +Type Translate(Network::Type type); -/// Translate abstract poll event flags to guest poll event flags -Network::PollEvents TranslatePollEventsToHost(PollEvents flags); +/// Translate guest protocol to abstract protocol +Network::Protocol Translate(Protocol protocol); + +/// Translate abstract protocol to guest protocol +Protocol Translate(Network::Protocol protocol); /// Translate guest poll event flags to abstract poll event flags -PollEvents TranslatePollEventsToGuest(Network::PollEvents flags); +Network::PollEvents Translate(PollEvents flags); + +/// Translate abstract poll event flags to guest poll event flags +PollEvents Translate(Network::PollEvents flags); /// Translate guest socket address structure to abstract socket address structure Network::SockAddrIn Translate(SockAddrIn value); diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 2b99dd7ac9..a3b54c7f0f 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -1,10 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/string_util.h" + +#include "core/core.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" +#include "core/hle/service/sockets/bsd.h" #include "core/hle/service/ssl/ssl.h" +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" namespace Service::SSL { @@ -20,6 +28,18 @@ enum class ContextOption : u32 { CrlImportDateCheckEnable = 1, }; +// This is nn::ssl::Connection::IoMode +enum class IoMode : u32 { + Blocking = 1, + NonBlocking = 2, +}; + +// This is nn::ssl::sf::OptionType +enum class OptionType : u32 { + DoNotCloseSocket = 0, + GetServerCertChain = 1, +}; + // This is nn::ssl::sf::SslVersion struct SslVersion { union { @@ -34,35 +54,42 @@ struct SslVersion { }; }; +struct SslContextSharedData { + u32 connection_count = 0; +}; + class ISslConnection final : public ServiceFramework { public: - explicit ISslConnection(Core::System& system_, SslVersion version) - : ServiceFramework{system_, "ISslConnection"}, ssl_version{version} { + explicit ISslConnection(Core::System& system_, SslVersion version, + std::shared_ptr& shared_data, + std::unique_ptr&& backend) + : ServiceFramework{system_, "ISslConnection"}, ssl_version{version}, + shared_data_{shared_data}, backend_{std::move(backend)} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "SetSocketDescriptor"}, - {1, nullptr, "SetHostName"}, - {2, nullptr, "SetVerifyOption"}, - {3, nullptr, "SetIoMode"}, + {0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"}, + {1, &ISslConnection::SetHostName, "SetHostName"}, + {2, &ISslConnection::SetVerifyOption, "SetVerifyOption"}, + {3, &ISslConnection::SetIoMode, "SetIoMode"}, {4, nullptr, "GetSocketDescriptor"}, {5, nullptr, "GetHostName"}, {6, nullptr, "GetVerifyOption"}, {7, nullptr, "GetIoMode"}, - {8, nullptr, "DoHandshake"}, - {9, nullptr, "DoHandshakeGetServerCert"}, - {10, nullptr, "Read"}, - {11, nullptr, "Write"}, - {12, nullptr, "Pending"}, + {8, &ISslConnection::DoHandshake, "DoHandshake"}, + {9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"}, + {10, &ISslConnection::Read, "Read"}, + {11, &ISslConnection::Write, "Write"}, + {12, &ISslConnection::Pending, "Pending"}, {13, nullptr, "Peek"}, {14, nullptr, "Poll"}, {15, nullptr, "GetVerifyCertError"}, {16, nullptr, "GetNeededServerCertBufferSize"}, - {17, nullptr, "SetSessionCacheMode"}, + {17, &ISslConnection::SetSessionCacheMode, "SetSessionCacheMode"}, {18, nullptr, "GetSessionCacheMode"}, {19, nullptr, "FlushSessionCache"}, {20, nullptr, "SetRenegotiationMode"}, {21, nullptr, "GetRenegotiationMode"}, - {22, nullptr, "SetOption"}, + {22, &ISslConnection::SetOption, "SetOption"}, {23, nullptr, "GetOption"}, {24, nullptr, "GetVerifyCertErrors"}, {25, nullptr, "GetCipherInfo"}, @@ -80,21 +107,295 @@ public: // clang-format on RegisterHandlers(functions); + + shared_data->connection_count++; + } + + ~ISslConnection() { + shared_data_->connection_count--; + if (fd_to_close_.has_value()) { + s32 fd = *fd_to_close_; + if (!do_not_close_socket_) { + LOG_ERROR(Service_SSL, + "do_not_close_socket was changed after setting socket; is this right?"); + } else { + auto bsd = system.ServiceManager().GetService("bsd:u"); + if (bsd) { + auto err = bsd->CloseImpl(fd); + if (err != Service::Sockets::Errno::SUCCESS) { + LOG_ERROR(Service_SSL, "failed to close duplicated socket: {}", err); + } + } + } + } } private: SslVersion ssl_version; + std::shared_ptr shared_data_; + std::unique_ptr backend_; + std::optional fd_to_close_; + bool do_not_close_socket_ = false; + bool get_server_cert_chain_ = false; + std::shared_ptr socket_; + bool did_set_host_name_ = false; + bool did_handshake_ = false; + + ResultVal SetSocketDescriptorImpl(s32 fd) { + LOG_DEBUG(Service_SSL, "called, fd={}", fd); + ASSERT(!did_handshake_); + auto bsd = system.ServiceManager().GetService("bsd:u"); + ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); + s32 ret_fd; + // Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor + if (do_not_close_socket_) { + auto res = bsd->DuplicateSocketImpl(fd); + if (!res.has_value()) { + LOG_ERROR(Service_SSL, "failed to duplicate socket"); + return ResultInvalidSocket; + } + fd = *res; + fd_to_close_ = fd; + ret_fd = fd; + } else { + ret_fd = -1; + } + std::optional> sock = bsd->GetSocket(fd); + if (!sock.has_value()) { + LOG_ERROR(Service_SSL, "invalid socket fd {}", fd); + return ResultInvalidSocket; + } + socket_ = std::move(*sock); + backend_->SetSocket(socket_); + return ret_fd; + } + + Result SetHostNameImpl(const std::string& hostname) { + LOG_DEBUG(Service_SSL, "SetHostNameImpl({})", hostname); + ASSERT(!did_handshake_); + Result res = backend_->SetHostName(hostname); + if (res == ResultSuccess) { + did_set_host_name_ = true; + } + return res; + } + + Result SetVerifyOptionImpl(u32 option) { + ASSERT(!did_handshake_); + LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option); + return ResultSuccess; + } + + Result SetIOModeImpl(u32 _mode) { + auto mode = static_cast(_mode); + ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); + ASSERT_OR_EXECUTE(socket_, { return ResultNoSocket; }); + + bool non_block = mode == IoMode::NonBlocking; + Network::Errno e = socket_->SetNonBlock(non_block); + if (e != Network::Errno::SUCCESS) { + LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); + } + return ResultSuccess; + } + + Result SetSessionCacheModeImpl(u32 mode) { + ASSERT(!did_handshake_); + LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode); + return ResultSuccess; + } + + Result DoHandshakeImpl() { + ASSERT_OR_EXECUTE(!did_handshake_ && socket_, { return ResultNoSocket; }); + ASSERT_OR_EXECUTE_MSG( + did_set_host_name_, { return ResultInternalError; }, + "Expected SetHostName before DoHandshake"); + Result res = backend_->DoHandshake(); + did_handshake_ = res.IsSuccess(); + return res; + } + + std::vector SerializeServerCerts(const std::vector>& certs) { + struct Header { + u64 magic; + u32 count; + u32 pad; + }; + struct EntryHeader { + u32 size; + u32 offset; + }; + if (!get_server_cert_chain_) { + // Just return the first one, unencoded. + ASSERT_OR_EXECUTE_MSG( + !certs.empty(), { return {}; }, "Should be at least one server cert"); + return certs[0]; + } + std::vector ret; + Header header{0x4E4D684374726543, static_cast(certs.size()), 0}; + ret.insert(ret.end(), reinterpret_cast(&header), reinterpret_cast(&header + 1)); + size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader); + for (auto& cert : certs) { + EntryHeader entry_header{static_cast(cert.size()), static_cast(data_offset)}; + data_offset += cert.size(); + ret.insert(ret.end(), reinterpret_cast(&entry_header), + reinterpret_cast(&entry_header + 1)); + } + for (auto& cert : certs) { + ret.insert(ret.end(), cert.begin(), cert.end()); + } + return ret; + } + + ResultVal> ReadImpl(size_t size) { + ASSERT_OR_EXECUTE(did_handshake_, { return ResultInternalError; }); + std::vector res(size); + ResultVal actual = backend_->Read(res); + if (actual.Failed()) { + return actual.Code(); + } + res.resize(*actual); + return res; + } + + ResultVal WriteImpl(std::span data) { + ASSERT_OR_EXECUTE(did_handshake_, { return ResultInternalError; }); + return backend_->Write(data); + } + + ResultVal PendingImpl() { + LOG_WARNING(Service_SSL, "(STUBBED) called."); + return 0; + } + + void SetSocketDescriptor(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + const ResultVal res = SetSocketDescriptorImpl(fd); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + rb.Push(res.ValueOr(-1)); + } + + void SetHostName(HLERequestContext& ctx) { + const std::string hostname = Common::StringFromBuffer(ctx.ReadBuffer()); + const Result res = SetHostNameImpl(hostname); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void SetVerifyOption(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 option = rp.Pop(); + const Result res = SetVerifyOptionImpl(option); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void SetIoMode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 mode = rp.Pop(); + const Result res = SetIOModeImpl(mode); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void DoHandshake(HLERequestContext& ctx) { + const Result res = DoHandshakeImpl(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void DoHandshakeGetServerCert(HLERequestContext& ctx) { + Result res = DoHandshakeImpl(); + u32 certs_count = 0; + u32 certs_size = 0; + if (res == ResultSuccess) { + auto certs = backend_->GetServerCerts(); + if (certs.Succeeded()) { + std::vector certs_buf = SerializeServerCerts(*certs); + ctx.WriteBuffer(certs_buf); + certs_count = static_cast(certs->size()); + certs_size = static_cast(certs_buf.size()); + } + } + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(res); + rb.Push(certs_size); + rb.Push(certs_count); + } + + void Read(HLERequestContext& ctx) { + const ResultVal> res = ReadImpl(ctx.GetWriteBufferSize()); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + if (res.Succeeded()) { + rb.Push(static_cast(res->size())); + ctx.WriteBuffer(*res); + } else { + rb.Push(static_cast(0)); + } + } + + void Write(HLERequestContext& ctx) { + const ResultVal res = WriteImpl(ctx.ReadBuffer()); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + rb.Push(static_cast(res.ValueOr(0))); + } + + void Pending(HLERequestContext& ctx) { + const ResultVal res = PendingImpl(); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + rb.Push(res.ValueOr(0)); + } + + void SetSessionCacheMode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 mode = rp.Pop(); + const Result res = SetSessionCacheModeImpl(mode); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void SetOption(HLERequestContext& ctx) { + struct Parameters { + OptionType option; + s32 value; + }; + static_assert(sizeof(Parameters) == 0x8, "Parameters is an invalid size"); + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw(); + + switch (parameters.option) { + case OptionType::DoNotCloseSocket: + do_not_close_socket_ = static_cast(parameters.value); + break; + case OptionType::GetServerCertChain: + get_server_cert_chain_ = static_cast(parameters.value); + break; + default: + LOG_WARNING(Service_SSL, "unrecognized option={}, value={}", parameters.option, + parameters.value); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } }; class ISslContext final : public ServiceFramework { public: explicit ISslContext(Core::System& system_, SslVersion version) - : ServiceFramework{system_, "ISslContext"}, ssl_version{version} { + : ServiceFramework{system_, "ISslContext"}, ssl_version{version}, + shared_data_{std::make_shared()} { static const FunctionInfo functions[] = { {0, &ISslContext::SetOption, "SetOption"}, {1, nullptr, "GetOption"}, {2, &ISslContext::CreateConnection, "CreateConnection"}, - {3, nullptr, "GetConnectionCount"}, + {3, &ISslContext::GetConnectionCount, "GetConnectionCount"}, {4, &ISslContext::ImportServerPki, "ImportServerPki"}, {5, &ISslContext::ImportClientPki, "ImportClientPki"}, {6, nullptr, "RemoveServerPki"}, @@ -111,6 +412,7 @@ public: private: SslVersion ssl_version; + std::shared_ptr shared_data_; void SetOption(HLERequestContext& ctx) { struct Parameters { @@ -130,11 +432,24 @@ private: } void CreateConnection(HLERequestContext& ctx) { - LOG_WARNING(Service_SSL, "(STUBBED) called"); + LOG_WARNING(Service_SSL, "called"); + + auto backend_res = CreateSSLConnectionBackend(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(backend_res.Code()); + if (backend_res.Succeeded()) { + rb.PushIpcInterface(system, ssl_version, shared_data_, + std::move(*backend_res)); + } + } + + void GetConnectionCount(HLERequestContext& ctx) { + LOG_WARNING(Service_SSL, "connection_count={}", shared_data_->connection_count); + + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, ssl_version); + rb.Push(shared_data_->connection_count); } void ImportServerPki(HLERequestContext& ctx) { diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h new file mode 100644 index 0000000000..624e07d41e --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" + +#include "common/common_types.h" + +#include +#include +#include +#include + +namespace Network { +class SocketBase; +} + +namespace Service::SSL { + +constexpr Result ResultNoSocket{ErrorModule::SSLSrv, 103}; +constexpr Result ResultInvalidSocket{ErrorModule::SSLSrv, 106}; +constexpr Result ResultTimeout{ErrorModule::SSLSrv, 205}; +constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up + +constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204}; +// ^ ResultWouldBlock is returned from Read and Write, and oddly, DoHandshake, +// with no way in the latter case to distinguish whether the client should poll +// for read or write. The one official client I've seen handles this by always +// polling for read (with a timeout). + +class SSLConnectionBackend { +public: + virtual void SetSocket(std::shared_ptr socket) = 0; + virtual Result SetHostName(const std::string& hostname) = 0; + virtual Result DoHandshake() = 0; + virtual ResultVal Read(std::span data) = 0; + virtual ResultVal Write(std::span data) = 0; + virtual ResultVal>> GetServerCerts() = 0; +}; + +ResultVal> CreateSSLConnectionBackend(); + +} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp new file mode 100644 index 0000000000..eb01561e24 --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_none.cpp @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" + +#include "common/logging/log.h" + +namespace Service::SSL { + +ResultVal> CreateSSLConnectionBackend() { + LOG_ERROR(Service_SSL, "No SSL backend on this platform"); + return ResultInternalError; +} + +} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp new file mode 100644 index 0000000000..cf9b904ac1 --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -0,0 +1,342 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" + +#include "common/fs/file.h" +#include "common/hex_util.h" +#include "common/string_util.h" + +#include + +#include +#include +#include +#include + +using namespace Common::FS; + +namespace Service::SSL { + +// Import OpenSSL's `SSL` type into the namespace. This is needed because the +// namespace is also named `SSL`. +using ::SSL; + +namespace { + +std::once_flag one_time_init_flag; +bool one_time_init_success = false; + +SSL_CTX* ssl_ctx; +IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment +BIO_METHOD* bio_meth; + +Result CheckOpenSSLErrors(); +void OneTimeInit(); +void OneTimeInitLogFile(); +bool OneTimeInitBIO(); + +} // namespace + +class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend { +public: + Result Init() { + std::call_once(one_time_init_flag, OneTimeInit); + + if (!one_time_init_success) { + LOG_ERROR(Service_SSL, + "Can't create SSL connection because OpenSSL one-time initialization failed"); + return ResultInternalError; + } + + ssl_ = SSL_new(ssl_ctx); + if (!ssl_) { + LOG_ERROR(Service_SSL, "SSL_new failed"); + return CheckOpenSSLErrors(); + } + + SSL_set_connect_state(ssl_); + + bio_ = BIO_new(bio_meth); + if (!bio_) { + LOG_ERROR(Service_SSL, "BIO_new failed"); + return CheckOpenSSLErrors(); + } + + BIO_set_data(bio_, this); + BIO_set_init(bio_, 1); + SSL_set_bio(ssl_, bio_, bio_); + + return ResultSuccess; + } + + void SetSocket(std::shared_ptr socket) override { + socket_ = socket; + } + + Result SetHostName(const std::string& hostname) override { + if (!SSL_set1_host(ssl_, hostname.c_str())) { // hostname for verification + LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); + return CheckOpenSSLErrors(); + } + if (!SSL_set_tlsext_host_name(ssl_, hostname.c_str())) { // hostname for SNI + LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); + return CheckOpenSSLErrors(); + } + return ResultSuccess; + } + + Result DoHandshake() override { + SSL_set_verify_result(ssl_, X509_V_OK); + int ret = SSL_do_handshake(ssl_); + long verify_result = SSL_get_verify_result(ssl_); + if (verify_result != X509_V_OK) { + LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}", + X509_verify_cert_error_string(verify_result)); + return CheckOpenSSLErrors(); + } + if (ret <= 0) { + int ssl_err = SSL_get_error(ssl_, ret); + if (ssl_err == SSL_ERROR_ZERO_RETURN || + (ssl_err == SSL_ERROR_SYSCALL && got_read_eof_)) { + LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); + return ResultInternalError; + } + } + return HandleReturn("SSL_do_handshake", 0, ret).Code(); + } + + ResultVal Read(std::span data) override { + size_t actual; + int ret = SSL_read_ex(ssl_, data.data(), data.size(), &actual); + return HandleReturn("SSL_read_ex", actual, ret); + } + + ResultVal Write(std::span data) override { + size_t actual; + int ret = SSL_write_ex(ssl_, data.data(), data.size(), &actual); + return HandleReturn("SSL_write_ex", actual, ret); + } + + ResultVal HandleReturn(const char* what, size_t actual, int ret) { + int ssl_err = SSL_get_error(ssl_, ret); + CheckOpenSSLErrors(); + switch (ssl_err) { + case SSL_ERROR_NONE: + return actual; + case SSL_ERROR_ZERO_RETURN: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); + // DoHandshake special-cases this, but for Read and Write: + return size_t(0); + case SSL_ERROR_WANT_READ: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what); + return ResultWouldBlock; + case SSL_ERROR_WANT_WRITE: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what); + return ResultWouldBlock; + default: + if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof_) { + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); + return size_t(0); + } + LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); + return ResultInternalError; + } + } + + ResultVal>> GetServerCerts() override { + STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl_); + if (!chain) { + LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr"); + return ResultInternalError; + } + std::vector> ret; + int count = sk_X509_num(chain); + ASSERT(count >= 0); + for (int i = 0; i < count; i++) { + X509* x509 = sk_X509_value(chain, i); + ASSERT_OR_EXECUTE(x509 != nullptr, { continue; }); + unsigned char* buf = nullptr; + int len = i2d_X509(x509, &buf); + ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; }); + ret.emplace_back(buf, buf + len); + OPENSSL_free(buf); + } + return ret; + } + + ~SSLConnectionBackendOpenSSL() { + // these are null-tolerant: + SSL_free(ssl_); + BIO_free(bio_); + } + + static void KeyLogCallback(const SSL* ssl, const char* line) { + std::string str(line); + str.push_back('\n'); + // Do this in a single WriteString for atomicity if multiple instances + // are running on different threads (though that can't currently + // happen). + if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) { + LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE"); + } + LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line); + } + + static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) { + auto self = static_cast(BIO_get_data(bio)); + ASSERT_OR_EXECUTE_MSG( + self->socket_, { return 0; }, "OpenSSL asked to send but we have no socket"); + BIO_clear_retry_flags(bio); + auto [actual, err] = self->socket_->Send({reinterpret_cast(buf), len}, 0); + switch (err) { + case Network::Errno::SUCCESS: + *actual_p = actual; + return 1; + case Network::Errno::AGAIN: + BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); + return 0; + default: + LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); + return -1; + } + } + + static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) { + auto self = static_cast(BIO_get_data(bio)); + ASSERT_OR_EXECUTE_MSG( + self->socket_, { return 0; }, "OpenSSL asked to recv but we have no socket"); + BIO_clear_retry_flags(bio); + auto [actual, err] = self->socket_->Recv(0, {reinterpret_cast(buf), len}); + switch (err) { + case Network::Errno::SUCCESS: + *actual_p = actual; + if (actual == 0) { + self->got_read_eof_ = true; + } + return actual ? 1 : 0; + case Network::Errno::AGAIN: + BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); + return 0; + default: + LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); + return -1; + } + } + + static long CtrlCallback(BIO* bio, int cmd, long larg, void* parg) { + switch (cmd) { + case BIO_CTRL_FLUSH: + // Nothing to flush. + return 1; + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + case BIO_CTRL_GET_KTLS_SEND: + case BIO_CTRL_GET_KTLS_RECV: + // We don't support these operations, but don't bother logging them + // as they're nothing unusual. + return 0; + default: + LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, larg, parg); + return 0; + } + } + + SSL* ssl_ = nullptr; + BIO* bio_ = nullptr; + bool got_read_eof_ = false; + + std::shared_ptr socket_; +}; + +ResultVal> CreateSSLConnectionBackend() { + auto conn = std::make_unique(); + Result res = conn->Init(); + if (res.IsFailure()) { + return res; + } + return conn; +} + +namespace { + +Result CheckOpenSSLErrors() { + unsigned long rc; + const char* file; + int line; + const char* func; + const char* data; + int flags; + while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags))) { + std::string msg; + msg.resize(1024, '\0'); + ERR_error_string_n(rc, msg.data(), msg.size()); + msg.resize(strlen(msg.data()), '\0'); + if (flags & ERR_TXT_STRING) { + msg.append(" | "); + msg.append(data); + } + Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error, + Common::Log::TrimSourcePath(file), line, func, "OpenSSL: {}", + msg); + } + return ResultInternalError; +} + +void OneTimeInit() { + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + LOG_ERROR(Service_SSL, "SSL_CTX_new failed"); + CheckOpenSSLErrors(); + return; + } + + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); + + if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { + LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed"); + CheckOpenSSLErrors(); + return; + } + + OneTimeInitLogFile(); + + if (!OneTimeInitBIO()) { + return; + } + + one_time_init_success = true; +} + +void OneTimeInitLogFile() { + const char* logfile = getenv("SSLKEYLOGFILE"); + if (logfile) { + key_log_file.Open(logfile, FileAccessMode::Append, FileType::TextFile, + FileShareFlag::ShareWriteOnly); + if (key_log_file.IsOpen()) { + SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackendOpenSSL::KeyLogCallback); + } else { + LOG_CRITICAL(Service_SSL, + "SSLKEYLOGFILE was set but file could not be opened; not logging keys!"); + } + } +} + +bool OneTimeInitBIO() { + bio_meth = + BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackendOpenSSL"); + if (!bio_meth || + !BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackendOpenSSL::WriteCallback) || + !BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackendOpenSSL::ReadCallback) || + !BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackendOpenSSL::CtrlCallback)) { + LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD"); + return false; + } + return true; +} + +} // namespace + +} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp new file mode 100644 index 0000000000..0a326b536c --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -0,0 +1,529 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" + +#include "common/error.h" +#include "common/fs/file.h" +#include "common/hex_util.h" +#include "common/string_util.h" + +#include + +#define SECURITY_WIN32 +#include +#include + +namespace { + +std::once_flag one_time_init_flag; +bool one_time_init_success = false; + +SCHANNEL_CRED schannel_cred{ + .dwVersion = SCHANNEL_CRED_VERSION, + .dwFlags = SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols + SCH_CRED_AUTO_CRED_VALIDATION | // validate certs + SCH_CRED_NO_DEFAULT_CREDS, // don't automatically present a client certificate + // ^ I'm assuming that nobody would want to connect Yuzu to a + // service that requires some OS-provided corporate client + // certificate, and presenting one to some arbitrary server + // might be a privacy concern? Who knows, though. +}; + +CredHandle cred_handle; + +static void OneTimeInit() { + SECURITY_STATUS ret = + AcquireCredentialsHandle(nullptr, const_cast(UNISP_NAME), SECPKG_CRED_OUTBOUND, + nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr); + if (ret != SEC_E_OK) { + // SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString. + LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}", + Common::NativeErrorToString(ret)); + return; + } + + one_time_init_success = true; +} + +} // namespace + +namespace Service::SSL { + +class SSLConnectionBackendSchannel final : public SSLConnectionBackend { +public: + Result Init() { + std::call_once(one_time_init_flag, OneTimeInit); + + if (!one_time_init_success) { + LOG_ERROR( + Service_SSL, + "Can't create SSL connection because Schannel one-time initialization failed"); + return ResultInternalError; + } + + return ResultSuccess; + } + + void SetSocket(std::shared_ptr socket) override { + socket_ = socket; + } + + Result SetHostName(const std::string& hostname) override { + hostname_ = hostname; + return ResultSuccess; + } + + Result DoHandshake() override { + while (1) { + Result r; + switch (handshake_state_) { + case HandshakeState::Initial: + if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || + (r = CallInitializeSecurityContext()) != ResultSuccess) { + return r; + } + // CallInitializeSecurityContext updated `handshake_state_`. + continue; + case HandshakeState::ContinueNeeded: + case HandshakeState::IncompleteMessage: + if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || + (r = FillCiphertextReadBuf()) != ResultSuccess) { + return r; + } + if (ciphertext_read_buf_.empty()) { + LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); + return ResultInternalError; + } + if ((r = CallInitializeSecurityContext()) != ResultSuccess) { + return r; + } + // CallInitializeSecurityContext updated `handshake_state_`. + continue; + case HandshakeState::DoneAfterFlush: + if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) { + return r; + } + handshake_state_ = HandshakeState::Connected; + return ResultSuccess; + case HandshakeState::Connected: + LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook"); + return ResultInternalError; + case HandshakeState::Error: + return ResultInternalError; + } + } + } + + Result FillCiphertextReadBuf() { + size_t fill_size = read_buf_fill_size_ ? read_buf_fill_size_ : 4096; + read_buf_fill_size_ = 0; + // This unnecessarily zeroes the buffer; oh well. + size_t offset = ciphertext_read_buf_.size(); + ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; }); + ciphertext_read_buf_.resize(offset + fill_size, 0); + auto read_span = std::span(ciphertext_read_buf_).subspan(offset, fill_size); + auto [actual, err] = socket_->Recv(0, read_span); + switch (err) { + case Network::Errno::SUCCESS: + ASSERT(static_cast(actual) <= fill_size); + ciphertext_read_buf_.resize(offset + actual); + return ResultSuccess; + case Network::Errno::AGAIN: + ciphertext_read_buf_.resize(offset); + return ResultWouldBlock; + default: + ciphertext_read_buf_.resize(offset); + LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); + return ResultInternalError; + } + } + + // Returns success if the write buffer has been completely emptied. + Result FlushCiphertextWriteBuf() { + while (!ciphertext_write_buf_.empty()) { + auto [actual, err] = socket_->Send(ciphertext_write_buf_, 0); + switch (err) { + case Network::Errno::SUCCESS: + ASSERT(static_cast(actual) <= ciphertext_write_buf_.size()); + ciphertext_write_buf_.erase(ciphertext_write_buf_.begin(), + ciphertext_write_buf_.begin() + actual); + break; + case Network::Errno::AGAIN: + return ResultWouldBlock; + default: + LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); + return ResultInternalError; + } + } + return ResultSuccess; + } + + Result CallInitializeSecurityContext() { + unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | + ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM | + ISC_REQ_USE_SUPPLIED_CREDS; + unsigned long attr; + // https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel + std::array input_buffers{{ + // only used if `initial_call_done` + { + // [0] + .cbBuffer = static_cast(ciphertext_read_buf_.size()), + .BufferType = SECBUFFER_TOKEN, + .pvBuffer = ciphertext_read_buf_.data(), + }, + { + // [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is + // returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the + // whole buffer wasn't used) + .BufferType = SECBUFFER_EMPTY, + }, + }}; + std::array output_buffers{{ + { + .BufferType = SECBUFFER_TOKEN, + }, // [0] + { + .BufferType = SECBUFFER_ALERT, + }, // [1] + }}; + SecBufferDesc input_desc{ + .ulVersion = SECBUFFER_VERSION, + .cBuffers = static_cast(input_buffers.size()), + .pBuffers = input_buffers.data(), + }; + SecBufferDesc output_desc{ + .ulVersion = SECBUFFER_VERSION, + .cBuffers = static_cast(output_buffers.size()), + .pBuffers = output_buffers.data(), + }; + ASSERT_OR_EXECUTE_MSG( + input_buffers[0].cbBuffer == ciphertext_read_buf_.size(), + { return ResultInternalError; }, "read buffer too large"); + + bool initial_call_done = handshake_state_ != HandshakeState::Initial; + if (initial_call_done) { + LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext", + ciphertext_read_buf_.size()); + } + + SECURITY_STATUS ret = + InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt_ : nullptr, + // Caller ensured we have set a hostname: + const_cast(hostname_.value().c_str()), req, + 0, // Reserved1 + 0, // TargetDataRep not used with Schannel + initial_call_done ? &input_desc : nullptr, + 0, // Reserved2 + initial_call_done ? nullptr : &ctxt_, &output_desc, &attr, + nullptr); // ptsExpiry + + if (output_buffers[0].pvBuffer) { + std::span span(static_cast(output_buffers[0].pvBuffer), + output_buffers[0].cbBuffer); + ciphertext_write_buf_.insert(ciphertext_write_buf_.end(), span.begin(), span.end()); + FreeContextBuffer(output_buffers[0].pvBuffer); + } + + if (output_buffers[1].pvBuffer) { + std::span span(static_cast(output_buffers[1].pvBuffer), + output_buffers[1].cbBuffer); + // The documentation doesn't explain what format this data is in. + LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(), + Common::HexToString(span)); + } + + switch (ret) { + case SEC_I_CONTINUE_NEEDED: + LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED"); + if (input_buffers[1].BufferType == SECBUFFER_EXTRA) { + LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer); + ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf_.size()); + ciphertext_read_buf_.erase(ciphertext_read_buf_.begin(), + ciphertext_read_buf_.end() - input_buffers[1].cbBuffer); + } else { + ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY); + ciphertext_read_buf_.clear(); + } + handshake_state_ = HandshakeState::ContinueNeeded; + return ResultSuccess; + case SEC_E_INCOMPLETE_MESSAGE: + LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE"); + ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING); + read_buf_fill_size_ = input_buffers[1].cbBuffer; + handshake_state_ = HandshakeState::IncompleteMessage; + return ResultSuccess; + case SEC_E_OK: + LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK"); + ciphertext_read_buf_.clear(); + handshake_state_ = HandshakeState::DoneAfterFlush; + return GrabStreamSizes(); + default: + LOG_ERROR(Service_SSL, + "InitializeSecurityContext failed (probably certificate/protocol issue): {}", + Common::NativeErrorToString(ret)); + handshake_state_ = HandshakeState::Error; + return ResultInternalError; + } + } + + Result GrabStreamSizes() { + SECURITY_STATUS ret = + QueryContextAttributes(&ctxt_, SECPKG_ATTR_STREAM_SIZES, &stream_sizes_); + if (ret != SEC_E_OK) { + LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}", + Common::NativeErrorToString(ret)); + handshake_state_ = HandshakeState::Error; + return ResultInternalError; + } + return ResultSuccess; + } + + ResultVal Read(std::span data) override { + if (handshake_state_ != HandshakeState::Connected) { + LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake"); + return ResultInternalError; + } + if (data.size() == 0 || got_read_eof_) { + return size_t(0); + } + while (1) { + if (!cleartext_read_buf_.empty()) { + size_t read_size = std::min(cleartext_read_buf_.size(), data.size()); + std::memcpy(data.data(), cleartext_read_buf_.data(), read_size); + cleartext_read_buf_.erase(cleartext_read_buf_.begin(), + cleartext_read_buf_.begin() + read_size); + return read_size; + } + if (!ciphertext_read_buf_.empty()) { + std::array buffers{{ + { + .cbBuffer = static_cast(ciphertext_read_buf_.size()), + .BufferType = SECBUFFER_DATA, + .pvBuffer = ciphertext_read_buf_.data(), + }, + { + .BufferType = SECBUFFER_EMPTY, + }, + { + .BufferType = SECBUFFER_EMPTY, + }, + { + .BufferType = SECBUFFER_EMPTY, + }, + }}; + ASSERT_OR_EXECUTE_MSG( + buffers[0].cbBuffer == ciphertext_read_buf_.size(), + { return ResultInternalError; }, "read buffer too large"); + SecBufferDesc desc{ + .ulVersion = SECBUFFER_VERSION, + .cBuffers = static_cast(buffers.size()), + .pBuffers = buffers.data(), + }; + SECURITY_STATUS ret = + DecryptMessage(&ctxt_, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr); + switch (ret) { + case SEC_E_OK: + ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER, + { return ResultInternalError; }); + ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA, + { return ResultInternalError; }); + ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER, + { return ResultInternalError; }); + cleartext_read_buf_.assign(static_cast(buffers[1].pvBuffer), + static_cast(buffers[1].pvBuffer) + + buffers[1].cbBuffer); + if (buffers[3].BufferType == SECBUFFER_EXTRA) { + ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf_.size()); + ciphertext_read_buf_.erase(ciphertext_read_buf_.begin(), + ciphertext_read_buf_.end() - + buffers[3].cbBuffer); + } else { + ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY); + ciphertext_read_buf_.clear(); + } + continue; + case SEC_E_INCOMPLETE_MESSAGE: + break; + case SEC_I_CONTEXT_EXPIRED: + // Server hung up by sending close_notify. + got_read_eof_ = true; + return size_t(0); + default: + LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", + Common::NativeErrorToString(ret)); + return ResultInternalError; + } + } + Result r = FillCiphertextReadBuf(); + if (r != ResultSuccess) { + return r; + } + if (ciphertext_read_buf_.empty()) { + got_read_eof_ = true; + return size_t(0); + } + } + } + + ResultVal Write(std::span data) override { + if (handshake_state_ != HandshakeState::Connected) { + LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); + return ResultInternalError; + } + if (data.size() == 0) { + return size_t(0); + } + data = data.subspan(0, std::min(data.size(), stream_sizes_.cbMaximumMessage)); + if (!cleartext_write_buf_.empty()) { + // Already in the middle of a write. It wouldn't make sense to not + // finish sending the entire buffer since TLS has + // header/MAC/padding/etc. + if (data.size() != cleartext_write_buf_.size() || + std::memcmp(data.data(), cleartext_write_buf_.data(), data.size())) { + LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer"); + return ResultInternalError; + } + return WriteAlreadyEncryptedData(); + } else { + cleartext_write_buf_.assign(data.begin(), data.end()); + } + + std::vector header_buf(stream_sizes_.cbHeader, 0); + std::vector tmp_data_buf = cleartext_write_buf_; + std::vector trailer_buf(stream_sizes_.cbTrailer, 0); + + std::array buffers{{ + { + .cbBuffer = stream_sizes_.cbHeader, + .BufferType = SECBUFFER_STREAM_HEADER, + .pvBuffer = header_buf.data(), + }, + { + .cbBuffer = static_cast(tmp_data_buf.size()), + .BufferType = SECBUFFER_DATA, + .pvBuffer = tmp_data_buf.data(), + }, + { + .cbBuffer = stream_sizes_.cbTrailer, + .BufferType = SECBUFFER_STREAM_TRAILER, + .pvBuffer = trailer_buf.data(), + }, + }}; + ASSERT_OR_EXECUTE_MSG( + buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; }, + "temp buffer too large"); + SecBufferDesc desc{ + .ulVersion = SECBUFFER_VERSION, + .cBuffers = static_cast(buffers.size()), + .pBuffers = buffers.data(), + }; + + SECURITY_STATUS ret = EncryptMessage(&ctxt_, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0); + if (ret != SEC_E_OK) { + LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret)); + return ResultInternalError; + } + ciphertext_write_buf_.insert(ciphertext_write_buf_.end(), header_buf.begin(), + header_buf.end()); + ciphertext_write_buf_.insert(ciphertext_write_buf_.end(), tmp_data_buf.begin(), + tmp_data_buf.end()); + ciphertext_write_buf_.insert(ciphertext_write_buf_.end(), trailer_buf.begin(), + trailer_buf.end()); + return WriteAlreadyEncryptedData(); + } + + ResultVal WriteAlreadyEncryptedData() { + Result r = FlushCiphertextWriteBuf(); + if (r != ResultSuccess) { + return r; + } + // write buf is empty + size_t cleartext_bytes_written = cleartext_write_buf_.size(); + cleartext_write_buf_.clear(); + return cleartext_bytes_written; + } + + ResultVal>> GetServerCerts() override { + PCCERT_CONTEXT returned_cert = nullptr; + SECURITY_STATUS ret = + QueryContextAttributes(&ctxt_, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); + if (ret != SEC_E_OK) { + LOG_ERROR(Service_SSL, + "QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}", + Common::NativeErrorToString(ret)); + return ResultInternalError; + } + PCCERT_CONTEXT some_cert = nullptr; + std::vector> certs; + while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { + certs.emplace_back(static_cast(some_cert->pbCertEncoded), + static_cast(some_cert->pbCertEncoded) + + some_cert->cbCertEncoded); + } + std::reverse(certs.begin(), + certs.end()); // Windows returns certs in reverse order from what we want + CertFreeCertificateContext(returned_cert); + return certs; + } + + ~SSLConnectionBackendSchannel() { + if (handshake_state_ != HandshakeState::Initial) { + DeleteSecurityContext(&ctxt_); + } + } + + enum class HandshakeState { + // Haven't called anything yet. + Initial, + // `SEC_I_CONTINUE_NEEDED` was returned by + // `InitializeSecurityContext`; must finish sending data (if any) in + // the write buffer, then read at least one byte before calling + // `InitializeSecurityContext` again. + ContinueNeeded, + // `SEC_E_INCOMPLETE_MESSAGE` was returned by + // `InitializeSecurityContext`; hopefully the write buffer is empty; + // must read at least one byte before calling + // `InitializeSecurityContext` again. + IncompleteMessage, + // `SEC_E_OK` was returned by `InitializeSecurityContext`; must + // finish sending data in the write buffer before having `DoHandshake` + // report success. + DoneAfterFlush, + // We finished the above and are now connected. At this point, writing + // and reading are separate 'state machines' represented by the + // nonemptiness of the ciphertext and cleartext read and write buffers. + Connected, + // Another error was returned and we shouldn't allow initialization + // to continue. + Error, + } handshake_state_ = HandshakeState::Initial; + + CtxtHandle ctxt_; + SecPkgContext_StreamSizes stream_sizes_; + + std::shared_ptr socket_; + std::optional hostname_; + + std::vector ciphertext_read_buf_; + std::vector ciphertext_write_buf_; + std::vector cleartext_read_buf_; + std::vector cleartext_write_buf_; + + bool got_read_eof_ = false; + size_t read_buf_fill_size_ = 0; +}; + +ResultVal> CreateSSLConnectionBackend() { + auto conn = std::make_unique(); + Result res = conn->Init(); + if (res.IsFailure()) { + return res; + } + return conn; +} + +} // namespace Service::SSL diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 75ac10a9c3..39381e06e6 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -121,6 +121,8 @@ Errno TranslateNativeError(int e) { return Errno::MSGSIZE; case WSAETIMEDOUT: return Errno::TIMEDOUT; + case WSAEINPROGRESS: + return Errno::INPROGRESS; default: UNIMPLEMENTED_MSG("Unimplemented errno={}", e); return Errno::OTHER; @@ -195,6 +197,8 @@ bool EnableNonBlock(int fd, bool enable) { Errno TranslateNativeError(int e) { switch (e) { + case 0: + return Errno::SUCCESS; case EBADF: return Errno::BADF; case EINVAL: @@ -219,8 +223,10 @@ Errno TranslateNativeError(int e) { return Errno::MSGSIZE; case ETIMEDOUT: return Errno::TIMEDOUT; + case EINPROGRESS: + return Errno::INPROGRESS; default: - UNIMPLEMENTED_MSG("Unimplemented errno={}", e); + UNIMPLEMENTED_MSG("Unimplemented errno={} ({})", e, strerror(e)); return Errno::OTHER; } } @@ -234,15 +240,84 @@ Errno GetAndLogLastError() { int e = errno; #endif const Errno err = TranslateNativeError(e); - if (err == Errno::AGAIN || err == Errno::TIMEDOUT) { + if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { + // These happen during normal operation, so only log them at debug level. + LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); return err; } LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); return err; } -int TranslateDomain(Domain domain) { +GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) { + switch (gai_err) { + case 0: + return GetAddrInfoError::SUCCESS; +#ifdef EAI_ADDRFAMILY + case EAI_ADDRFAMILY: + return GetAddrInfoError::ADDRFAMILY; +#endif + case EAI_AGAIN: + return GetAddrInfoError::AGAIN; + case EAI_BADFLAGS: + return GetAddrInfoError::BADFLAGS; + case EAI_FAIL: + return GetAddrInfoError::FAIL; + case EAI_FAMILY: + return GetAddrInfoError::FAMILY; + case EAI_MEMORY: + return GetAddrInfoError::MEMORY; + case EAI_NONAME: + return GetAddrInfoError::NONAME; + case EAI_SERVICE: + return GetAddrInfoError::SERVICE; + case EAI_SOCKTYPE: + return GetAddrInfoError::SOCKTYPE; + // These codes may not be defined on all systems: +#ifdef EAI_SYSTEM + case EAI_SYSTEM: + return GetAddrInfoError::SYSTEM; +#endif +#ifdef EAI_BADHINTS + case EAI_BADHINTS: + return GetAddrInfoError::BADHINTS; +#endif +#ifdef EAI_PROTOCOL + case EAI_PROTOCOL: + return GetAddrInfoError::PROTOCOL; +#endif +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: + return GetAddrInfoError::OVERFLOW_; +#endif + default: +#ifdef EAI_NODATA + // This can't be a case statement because it would create a duplicate + // case on Windows where EAI_NODATA is an alias for EAI_NONAME. + if (gai_err == EAI_NODATA) { + return GetAddrInfoError::NODATA; + } +#endif + return GetAddrInfoError::OTHER; + } +} + +Domain TranslateDomainFromNative(int domain) { switch (domain) { + case 0: + return Domain::Unspecified; + case AF_INET: + return Domain::INET; + default: + UNIMPLEMENTED_MSG("Unhandled domain={}", domain); + return Domain::INET; + } +} + +int TranslateDomainToNative(Domain domain) { + switch (domain) { + case Domain::Unspecified: + return 0; case Domain::INET: return AF_INET; default: @@ -251,20 +326,58 @@ int TranslateDomain(Domain domain) { } } -int TranslateType(Type type) { +Type TranslateTypeFromNative(int type) { switch (type) { + case 0: + return Type::Unspecified; + case SOCK_STREAM: + return Type::STREAM; + case SOCK_DGRAM: + return Type::DGRAM; + case SOCK_RAW: + return Type::RAW; + case SOCK_SEQPACKET: + return Type::SEQPACKET; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return Type::STREAM; + } +} + +int TranslateTypeToNative(Type type) { + switch (type) { + case Type::Unspecified: + return 0; case Type::STREAM: return SOCK_STREAM; case Type::DGRAM: return SOCK_DGRAM; + case Type::RAW: + return SOCK_RAW; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return 0; } } -int TranslateProtocol(Protocol protocol) { +Protocol TranslateProtocolFromNative(int protocol) { switch (protocol) { + case 0: + return Protocol::Unspecified; + case IPPROTO_TCP: + return Protocol::TCP; + case IPPROTO_UDP: + return Protocol::UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); + return Protocol::Unspecified; + } +} + +int TranslateProtocolToNative(Protocol protocol) { + switch (protocol) { + case Protocol::Unspecified: + return 0; case Protocol::TCP: return IPPROTO_TCP; case Protocol::UDP: @@ -275,21 +388,10 @@ int TranslateProtocol(Protocol protocol) { } } -SockAddrIn TranslateToSockAddrIn(sockaddr input_) { - sockaddr_in input; - std::memcpy(&input, &input_, sizeof(input)); - +SockAddrIn TranslateToSockAddrIn(sockaddr_in input, size_t input_len) { SockAddrIn result; - switch (input.sin_family) { - case AF_INET: - result.family = Domain::INET; - break; - default: - UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family); - result.family = Domain::INET; - break; - } + result.family = TranslateDomainFromNative(input.sin_family); result.portno = ntohs(input.sin_port); @@ -301,22 +403,28 @@ SockAddrIn TranslateToSockAddrIn(sockaddr input_) { 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; + const auto translate = [&result, &events](PollEvents guest, short host) { + if (True(events & guest)) { + events &= ~guest; + result |= host; + } + }; + + translate(PollEvents::In, POLLIN); + translate(PollEvents::Pri, POLLPRI); + translate(PollEvents::Out, POLLOUT); + translate(PollEvents::Err, POLLERR); + translate(PollEvents::Hup, POLLHUP); + translate(PollEvents::Nval, POLLNVAL); + translate(PollEvents::RdNorm, POLLRDNORM); + translate(PollEvents::RdBand, POLLRDBAND); + translate(PollEvents::WrBand, POLLWRBAND); + #ifdef _WIN32 + if (True(events & PollEvents::Pri)) { 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); @@ -337,6 +445,10 @@ PollEvents TranslatePollRevents(short revents) { translate(POLLOUT, PollEvents::Out); translate(POLLERR, PollEvents::Err); translate(POLLHUP, PollEvents::Hup); + translate(POLLNVAL, PollEvents::Nval); + translate(POLLRDNORM, PollEvents::RdNorm); + translate(POLLRDBAND, PollEvents::RdBand); + translate(POLLWRBAND, PollEvents::WrBand); UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents); @@ -360,12 +472,53 @@ std::optional GetHostIPv4Address() { return {}; } - std::array ip_addr = {}; - ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) != - nullptr); return TranslateIPv4(network_interface->ip_address); } +std::string IPv4AddressToString(IPv4Address ip_addr) { + std::array buf = {}; + ASSERT(inet_ntop(AF_INET, &ip_addr, buf.data(), sizeof(buf)) == buf.data()); + return std::string(buf.data()); +} + +u32 IPv4AddressToInteger(IPv4Address ip_addr) { + return static_cast(ip_addr[0]) << 24 | static_cast(ip_addr[1]) << 16 | + static_cast(ip_addr[2]) << 8 | static_cast(ip_addr[3]); +} + +#undef GetAddrInfo // Windows defines it as a macro + +Common::Expected, GetAddrInfoError> GetAddrInfo( + const std::string& host, const std::optional& service) { + addrinfo hints{}; + hints.ai_family = AF_INET; // Switch only supports IPv4. + addrinfo* addrinfo; + s32 gai_err = getaddrinfo(host.c_str(), service.has_value() ? service->c_str() : nullptr, + &hints, &addrinfo); + if (gai_err != 0) { + return Common::Unexpected(TranslateGetAddrInfoErrorFromNative(gai_err)); + } + std::vector ret; + for (auto* current = addrinfo; current; current = current->ai_next) { + // We should only get AF_INET results due to the hints value. + ASSERT_OR_EXECUTE(addrinfo->ai_family == AF_INET && + addrinfo->ai_addrlen == sizeof(sockaddr_in), + continue;); + + AddrInfo& out = ret.emplace_back(); + out.family = TranslateDomainFromNative(current->ai_family); + out.socket_type = TranslateTypeFromNative(current->ai_socktype); + out.protocol = TranslateProtocolFromNative(current->ai_protocol); + out.addr = TranslateToSockAddrIn(*reinterpret_cast(current->ai_addr), + current->ai_addrlen); + if (current->ai_canonname != nullptr) { + out.canon_name = current->ai_canonname; + } + } + freeaddrinfo(addrinfo); + return ret; +} + std::pair Poll(std::vector& pollfds, s32 timeout) { const size_t num = pollfds.size(); @@ -410,6 +563,18 @@ Socket::Socket(Socket&& rhs) noexcept { fd = std::exchange(rhs.fd, INVALID_SOCKET); } +template +std::pair Socket::GetSockOpt(SOCKET fd_, int option) { + T value{}; + socklen_t len = sizeof(value); + const int result = getsockopt(fd_, SOL_SOCKET, option, reinterpret_cast(&value), &len); + if (result != SOCKET_ERROR) { + ASSERT(len == sizeof(value)); + return {value, Errno::SUCCESS}; + } + return {value, GetAndLogLastError()}; +} + template Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) { const int result = @@ -421,7 +586,8 @@ Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) { } Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { - fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol)); + fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type), + TranslateProtocolToNative(protocol)); if (fd != INVALID_SOCKET) { return Errno::SUCCESS; } @@ -430,19 +596,17 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { } std::pair Socket::Accept() { - sockaddr addr; + sockaddr_in addr; socklen_t addrlen = sizeof(addr); - const SOCKET new_socket = accept(fd, &addr, &addrlen); + const SOCKET new_socket = accept(fd, reinterpret_cast(&addr), &addrlen); if (new_socket == INVALID_SOCKET) { return {AcceptResult{}, GetAndLogLastError()}; } - ASSERT(addrlen == sizeof(sockaddr_in)); - AcceptResult result{ .socket = std::make_unique(new_socket), - .sockaddr_in = TranslateToSockAddrIn(addr), + .sockaddr_in = TranslateToSockAddrIn(addr, addrlen), }; return {std::move(result), Errno::SUCCESS}; @@ -458,25 +622,23 @@ Errno Socket::Connect(SockAddrIn addr_in) { } std::pair Socket::GetPeerName() { - sockaddr addr; + sockaddr_in addr; socklen_t addrlen = sizeof(addr); - if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { + if (getpeername(fd, reinterpret_cast(&addr), &addrlen) == SOCKET_ERROR) { return {SockAddrIn{}, GetAndLogLastError()}; } - ASSERT(addrlen == sizeof(sockaddr_in)); - return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; + return {TranslateToSockAddrIn(addr, addrlen), Errno::SUCCESS}; } std::pair Socket::GetSockName() { - sockaddr addr; + sockaddr_in addr; socklen_t addrlen = sizeof(addr); - if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { + if (getsockname(fd, reinterpret_cast(&addr), &addrlen) == SOCKET_ERROR) { return {SockAddrIn{}, GetAndLogLastError()}; } - ASSERT(addrlen == sizeof(sockaddr_in)); - return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; + return {TranslateToSockAddrIn(addr, addrlen), Errno::SUCCESS}; } Errno Socket::Bind(SockAddrIn addr) { @@ -519,7 +681,7 @@ Errno Socket::Shutdown(ShutdownHow how) { return GetAndLogLastError(); } -std::pair Socket::Recv(int flags, std::vector& message) { +std::pair Socket::Recv(int flags, std::span message) { ASSERT(flags == 0); ASSERT(message.size() < static_cast(std::numeric_limits::max())); @@ -532,21 +694,20 @@ std::pair Socket::Recv(int flags, std::vector& message) { return {-1, GetAndLogLastError()}; } -std::pair Socket::RecvFrom(int flags, std::vector& message, SockAddrIn* addr) { +std::pair Socket::RecvFrom(int flags, std::span message, SockAddrIn* addr) { ASSERT(flags == 0); ASSERT(message.size() < static_cast(std::numeric_limits::max())); - sockaddr addr_in{}; + sockaddr_in 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; + sockaddr* const p_addr_in = addr ? reinterpret_cast(&addr_in) : nullptr; const auto result = recvfrom(fd, reinterpret_cast(message.data()), static_cast(message.size()), 0, p_addr_in, p_addrlen); if (result != SOCKET_ERROR) { if (addr) { - ASSERT(addrlen == sizeof(addr_in)); - *addr = TranslateToSockAddrIn(addr_in); + *addr = TranslateToSockAddrIn(addr_in, addrlen); } return {static_cast(result), Errno::SUCCESS}; } @@ -597,6 +758,11 @@ Errno Socket::Close() { return Errno::SUCCESS; } +std::pair Socket::GetPendingError() { + auto [pending_err, getsockopt_err] = GetSockOpt(fd, SO_ERROR); + return {TranslateNativeError(pending_err), getsockopt_err}; +} + Errno Socket::SetLinger(bool enable, u32 linger) { return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger)); } diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index 1e09a007a1..96319bfc8d 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -16,6 +16,11 @@ #include #endif +namespace Common { +template +class Expected; +} + namespace Network { class SocketBase; @@ -36,6 +41,26 @@ enum class Errno { NETUNREACH, TIMEDOUT, MSGSIZE, + INPROGRESS, + OTHER, +}; + +enum class GetAddrInfoError { + SUCCESS, + ADDRFAMILY, + AGAIN, + BADFLAGS, + FAIL, + FAMILY, + MEMORY, + NODATA, + NONAME, + SERVICE, + SOCKTYPE, + SYSTEM, + BADHINTS, + PROTOCOL, + OVERFLOW_, OTHER, }; @@ -49,6 +74,9 @@ enum class PollEvents : u16 { Err = 1 << 3, Hup = 1 << 4, Nval = 1 << 5, + RdNorm = 1 << 6, + RdBand = 1 << 7, + WrBand = 1 << 8, }; DECLARE_ENUM_FLAG_OPERATORS(PollEvents); @@ -82,4 +110,10 @@ constexpr IPv4Address TranslateIPv4(in_addr addr) { /// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array std::optional GetHostIPv4Address(); +std::string IPv4AddressToString(IPv4Address ip_addr); +u32 IPv4AddressToInteger(IPv4Address ip_addr); + +Common::Expected, GetAddrInfoError> GetAddrInfo( + const std::string& host, const std::optional& service); + } // namespace Network diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index 7a77171c29..44e9e30937 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -98,7 +98,7 @@ Errno ProxySocket::Shutdown(ShutdownHow how) { return Errno::SUCCESS; } -std::pair ProxySocket::Recv(int flags, std::vector& message) { +std::pair ProxySocket::Recv(int flags, std::span message) { LOG_WARNING(Network, "(STUBBED) called"); ASSERT(flags == 0); ASSERT(message.size() < static_cast(std::numeric_limits::max())); @@ -106,7 +106,7 @@ std::pair ProxySocket::Recv(int flags, std::vector& message) { return {static_cast(0), Errno::SUCCESS}; } -std::pair ProxySocket::RecvFrom(int flags, std::vector& message, SockAddrIn* addr) { +std::pair ProxySocket::RecvFrom(int flags, std::span message, SockAddrIn* addr) { ASSERT(flags == 0); ASSERT(message.size() < static_cast(std::numeric_limits::max())); @@ -140,8 +140,8 @@ std::pair ProxySocket::RecvFrom(int flags, std::vector& message, } } -std::pair ProxySocket::ReceivePacket(int flags, std::vector& message, - SockAddrIn* addr, std::size_t max_length) { +std::pair ProxySocket::ReceivePacket(int flags, std::span message, SockAddrIn* addr, + std::size_t max_length) { ProxyPacket& packet = received_packets.front(); if (addr) { addr->family = Domain::INET; @@ -153,10 +153,7 @@ std::pair ProxySocket::ReceivePacket(int flags, std::vector& mes std::size_t read_bytes; if (packet.data.size() > max_length) { read_bytes = max_length; - message.clear(); - std::copy(packet.data.begin(), packet.data.begin() + read_bytes, - std::back_inserter(message)); - message.resize(max_length); + memcpy(message.data(), packet.data.data(), max_length); if (protocol == Protocol::UDP) { if (!peek) { @@ -171,9 +168,7 @@ std::pair ProxySocket::ReceivePacket(int flags, std::vector& mes } } else { read_bytes = packet.data.size(); - message.clear(); - std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message)); - message.resize(max_length); + memcpy(message.data(), packet.data.data(), read_bytes); if (!peek) { received_packets.pop(); } @@ -293,6 +288,11 @@ Errno ProxySocket::SetNonBlock(bool enable) { return Errno::SUCCESS; } +std::pair ProxySocket::GetPendingError() { + LOG_DEBUG(Network, "(STUBBED) called"); + return {Errno::SUCCESS, Errno::SUCCESS}; +} + bool ProxySocket::IsOpened() const { return fd != INVALID_SOCKET; } diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h index 6e991fa386..e12c413d1e 100644 --- a/src/core/internal_network/socket_proxy.h +++ b/src/core/internal_network/socket_proxy.h @@ -39,11 +39,11 @@ public: Errno Shutdown(ShutdownHow how) override; - std::pair Recv(int flags, std::vector& message) override; + std::pair Recv(int flags, std::span message) override; - std::pair RecvFrom(int flags, std::vector& message, SockAddrIn* addr) override; + std::pair RecvFrom(int flags, std::span message, SockAddrIn* addr) override; - std::pair ReceivePacket(int flags, std::vector& message, SockAddrIn* addr, + std::pair ReceivePacket(int flags, std::span message, SockAddrIn* addr, std::size_t max_length); std::pair Send(std::span message, int flags) override; @@ -74,6 +74,8 @@ public: template Errno SetSockOpt(SOCKET fd, int option, T value); + std::pair GetPendingError() override; + bool IsOpened() const override; private: diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h index 11e479e509..46a53ef795 100644 --- a/src/core/internal_network/sockets.h +++ b/src/core/internal_network/sockets.h @@ -59,10 +59,9 @@ public: virtual Errno Shutdown(ShutdownHow how) = 0; - virtual std::pair Recv(int flags, std::vector& message) = 0; + virtual std::pair Recv(int flags, std::span message) = 0; - virtual std::pair RecvFrom(int flags, std::vector& message, - SockAddrIn* addr) = 0; + virtual std::pair RecvFrom(int flags, std::span message, SockAddrIn* addr) = 0; virtual std::pair Send(std::span message, int flags) = 0; @@ -87,6 +86,8 @@ public: virtual Errno SetNonBlock(bool enable) = 0; + virtual std::pair GetPendingError() = 0; + virtual bool IsOpened() const = 0; virtual void HandleProxyPacket(const ProxyPacket& packet) = 0; @@ -126,9 +127,9 @@ public: Errno Shutdown(ShutdownHow how) override; - std::pair Recv(int flags, std::vector& message) override; + std::pair Recv(int flags, std::span message) override; - std::pair RecvFrom(int flags, std::vector& message, SockAddrIn* addr) override; + std::pair RecvFrom(int flags, std::span message, SockAddrIn* addr) override; std::pair Send(std::span message, int flags) override; @@ -156,6 +157,11 @@ public: template Errno SetSockOpt(SOCKET fd, int option, T value); + std::pair GetPendingError() override; + + template + std::pair GetSockOpt(SOCKET fd, int option); + bool IsOpened() const override; void HandleProxyPacket(const ProxyPacket& packet) override;