From 45709aa9cdc368b805f5a3ea7d9938ba188abd0f Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Sat, 30 Jan 2021 13:11:39 -0800 Subject: [PATCH] dns: skeleton passthrough mitm --- .../impl/ams_system_thread_definitions.hpp | 3 + .../include/stratosphere/socket.hpp | 17 +++ .../stratosphere/socket/socket_types.hpp | 65 ++++++++++ .../source/amsmitm_module_management.cpp | 3 + .../source/dns_mitm/dnsmitm_debug.cpp | 76 ++++++++++++ .../source/dns_mitm/dnsmitm_debug.hpp | 25 ++++ .../source/dns_mitm/dnsmitm_module.cpp | 112 ++++++++++++++++++ .../source/dns_mitm/dnsmitm_module.hpp | 24 ++++ .../source/dns_mitm/dnsmitm_resolver_impl.cpp | 42 +++++++ .../source/dns_mitm/dnsmitm_resolver_impl.hpp | 48 ++++++++ 10 files changed, 415 insertions(+) create mode 100644 libraries/libstratosphere/include/stratosphere/socket.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp create mode 100644 stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp create mode 100644 stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp create mode 100644 stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp create mode 100644 stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.hpp create mode 100644 stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp create mode 100644 stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 06a1e0269..ff789a78d 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -113,6 +113,9 @@ namespace ams::impl { AMS_DEFINE_SYSTEM_THREAD(21, erpt, Main); AMS_DEFINE_SYSTEM_THREAD(21, erpt, IpcServer); + /* socket. */ + AMS_DEFINE_SYSTEM_THREAD(29, socket, ResolverIpcServer); + /* jpegdec. */ AMS_DEFINE_SYSTEM_THREAD(21, jpegdec, Main); diff --git a/libraries/libstratosphere/include/stratosphere/socket.hpp b/libraries/libstratosphere/include/stratosphere/socket.hpp new file mode 100644 index 000000000..d43f92384 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket.hpp @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp new file mode 100644 index 000000000..bac58080f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::socket { + + using InAddrT = u32; + using InPortT = u16; + using SockLenT = u32; + using NfdsT = u64; + using FdMask = u64; + + constexpr inline unsigned int FdSetSize = 0x400; + + template + constexpr inline InAddrT EncodeInAddr = (A << 24) | (B << 16) | (C << 8) | (D << 0); + + constexpr inline InAddrT InAddr_Any = EncodeInAddr< 0, 0, 0, 0>; + constexpr inline InAddrT InAddr_Broadcast = EncodeInAddr<255, 255, 255, 255>; + constexpr inline InAddrT InAddr_None = EncodeInAddr<255, 255, 255, 255>; + constexpr inline InAddrT InAddr_Loopback = EncodeInAddr<127, 0, 0, 1>; + + enum class Family : u8 { + Af_Unspec = 0, + Pf_Unspec = Af_Unspec, + + Af_Inet = 2, + Pf_Inet = Af_Inet, + + Af_Route = 17, + Pf_Route = Af_Route, + + Af_Link = 18, + Pf_Link = Af_Link, + + Af_Inet6 = 28, + Pf_Inet6 = Af_Inet6, + + Af_Max = 42, + Pf_Max = Af_Max + }; + + struct HostEnt { + char *h_name; + char **h_aliases; + Family h_addrtype; + int h_length; + char **h_addr_list; + }; + +} diff --git a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp index 1bd12f55f..436c95e43 100644 --- a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp @@ -22,6 +22,7 @@ #include "bpc_mitm/bpcmitm_module.hpp" #include "bpc_mitm/bpc_ams_module.hpp" #include "ns_mitm/nsmitm_module.hpp" +#include "dns_mitm/dnsmitm_module.hpp" #include "sysupdater/sysupdater_module.hpp" namespace ams::mitm { @@ -34,6 +35,7 @@ namespace ams::mitm { ModuleId_BpcMitm, ModuleId_BpcAms, ModuleId_NsMitm, + ModuleId_DnsMitm, ModuleId_Sysupdater, ModuleId_Count, @@ -66,6 +68,7 @@ namespace ams::mitm { GetModuleDefinition(), GetModuleDefinition(), GetModuleDefinition(), + GetModuleDefinition(), GetModuleDefinition(), }; diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp new file mode 100644 index 000000000..81d67a9f3 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "../amsmitm_fs_utils.hpp" +#include "dnsmitm_debug.hpp" + +namespace ams::mitm::socket::resolver { + + #if 1 + + namespace { + + ::FsFile g_log_file; + s64 g_log_ofs; + + os::SdkMutex g_log_mutex; + + char g_log_buf[0x400]; + + } + + void InitializeDebug() { + /* Create the log file. */ + mitm::fs::CreateAtmosphereSdFile("dns_log.txt", 0, ams::fs::CreateOption_None); + + /* Open the log file. */ + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(g_log_file), "dns_log.txt", ams::fs::OpenMode_ReadWrite | ams::fs::OpenMode_AllowAppend)); + + /* Get the current log offset. */ + R_ABORT_UNLESS(::fsFileGetSize(std::addressof(g_log_file), std::addressof(g_log_ofs))); + + /* Start a new log. */ + LogDebug("\n---\n"); + } + + void LogDebug(const char *fmt, ...) { + std::scoped_lock lk(g_log_mutex); + + int len = 0; + { + std::va_list vl; + va_start(vl, fmt); + len = util::VSNPrintf(g_log_buf, sizeof(g_log_buf), fmt, vl); + va_end(vl); + } + + R_ABORT_UNLESS(::fsFileWrite(std::addressof(g_log_file), g_log_ofs, g_log_buf, len, FsWriteOption_Flush)); + g_log_ofs += len; + } + + #else + + void InitializeDebug() { + /* ... */ + } + + void LogDebug(const char *fmt, ...) { + /* ... */ + } + + #endif + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp new file mode 100644 index 000000000..ce6848fdf --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::socket::resolver { + + void InitializeDebug(); + + void LogDebug(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp new file mode 100644 index 000000000..827019d17 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "../amsmitm_initialization.hpp" +#include "dnsmitm_module.hpp" +#include "dnsmitm_resolver_impl.hpp" +#include "dnsmitm_debug.hpp" + +namespace ams::mitm::socket::resolver { + + namespace { + + enum PortIndex { + PortIndex_Mitm, + PortIndex_Count, + }; + + constexpr sm::ServiceName DnsMitmServiceName = sm::ServiceName::Encode("sfdnsres"); + + constexpr size_t MaxSessions = 30; + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + + class ServerManager final : public sf::hipc::ServerManager { + private: + virtual Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + ServerManager g_server_manager; + + Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + /* Acknowledge the mitm session. */ + std::shared_ptr<::Service> fsrv; + sm::MitmProcessInfo client_info; + server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info)); + + switch (port_index) { + case PortIndex_Mitm: + return this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced(decltype(fsrv)(fsrv), client_info), fsrv); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr size_t TotalThreads = 8; + static_assert(TotalThreads >= 1, "TotalThreads"); + constexpr size_t NumExtraThreads = TotalThreads - 1; + constexpr size_t ThreadStackSize = mitm::ModuleTraits::StackSize; + alignas(os::MemoryPageSize) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize]; + + os::ThreadType g_extra_threads[NumExtraThreads]; + + void LoopServerThread(void *arg) { + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + + void ProcessForServerOnAllThreads() { + /* Initialize threads. */ + if constexpr (NumExtraThreads > 0) { + const s32 priority = os::GetThreadCurrentPriority(os::GetCurrentThread()); + for (size_t i = 0; i < NumExtraThreads; i++) { + R_ABORT_UNLESS(os::CreateThread(g_extra_threads + i, LoopServerThread, nullptr, g_extra_thread_stacks[i], ThreadStackSize, priority)); + } + } + + /* Start extra threads. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::StartThread(g_extra_threads + i); + } + } + + /* Loop this thread. */ + LoopServerThread(nullptr); + + /* Wait for extra threads to finish. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::WaitThread(g_extra_threads + i); + } + } + } + + } + + void MitmModule::ThreadFunction(void *arg) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* Initialize debug. */ + resolver::InitializeDebug(); + + /* Create mitm servers. */ + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer(PortIndex_Mitm, DnsMitmServiceName))); + + /* Loop forever, servicing our services. */ + ProcessForServerOnAllThreads(); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.hpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.hpp new file mode 100644 index 000000000..59756d319 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "../amsmitm_module.hpp" + +namespace ams::mitm::socket::resolver { + + DEFINE_MITM_MODULE_CLASS(0x2000, AMS_GET_SYSTEM_THREAD_PRIORITY(socket, ResolverIpcServer) - 1); + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp new file mode 100644 index 000000000..9ab72463f --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "dnsmitm_resolver_impl.hpp" +#include "dnsmitm_debug.hpp" + +namespace ams::mitm::socket::resolver { + + Result ResolverImpl::GetHostByNameRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &name, sf::Out out_host_error, sf::Out out_errno, const sf::OutBuffer &out_hostent, sf::Out out_size) { + LogDebug("[%016lx]: GetHostByNameRequest(%s)\n", this->client_info.program_id.value, reinterpret_cast(name.GetPointer())); + return sm::mitm::ResultShouldForwardToSession(); + } + + Result ResolverImpl::GetAddrInfoRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutBuffer &out_addrinfo, sf::Out out_errno, sf::Out out_retval, sf::Out out_size) { + LogDebug("[%016lx]: GetAddrInfoRequest(%s, %s)\n", this->client_info.program_id.value, reinterpret_cast(node.GetPointer()), reinterpret_cast(srv.GetPointer())); + return sm::mitm::ResultShouldForwardToSession(); + } + + Result ResolverImpl::GetHostByNameRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &name, const sf::OutAutoSelectBuffer &out_hostent, sf::Out out_size, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_host_error, sf::Out out_errno) { + LogDebug("[%016lx]: GetHostByNameRequestWithOptions(%s)\n", this->client_info.program_id.value, reinterpret_cast(name.GetPointer())); + return sm::mitm::ResultShouldForwardToSession(); + } + + Result ResolverImpl::GetAddrInfoRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out out_size, sf::Out out_retval, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_host_error, sf::Out out_errno) { + LogDebug("[%016lx]: GetAddrInfoRequestWithOptions(%s, %s)\n", this->client_info.program_id.value, reinterpret_cast(node.GetPointer()), reinterpret_cast(srv.GetPointer())); + return sm::mitm::ResultShouldForwardToSession(); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp new file mode 100644 index 000000000..90c472bc3 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#define AMS_DNS_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetHostByNameRequest, (u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &name, sf::Out out_host_error, sf::Out out_errno, const sf::OutBuffer &out_hostent, sf::Out out_size), (cancel_handle, client_pid, use_nsd_resolve, name, out_host_error, out_errno, out_hostent, out_size)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetAddrInfoRequest, (u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutBuffer &out_addrinfo, sf::Out out_errno, sf::Out out_retval, sf::Out out_size), (cancel_handle, client_pid, use_nsd_resolve, node, srv, serialized_hint, out_addrinfo, out_errno, out_retval, out_size)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetHostByNameRequestWithOptions, (const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &name, const sf::OutAutoSelectBuffer &out_hostent, sf::Out out_size, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_host_error, sf::Out out_errno), (client_pid, name, out_hostent, out_size, options_version, options, num_options, out_host_error, out_errno), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetAddrInfoRequestWithOptions, (const sf::ClientProcessId &client_pid, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out out_size, sf::Out out_retval, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_host_error, sf::Out out_errno), (client_pid, node, srv, serialized_hint, out_addrinfo, out_size, out_retval, options_version, options, num_options, out_host_error, out_errno), hos::Version_5_0_0) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::socket::resolver, IResolver, AMS_DNS_MITM_INTERFACE_INFO) + +namespace ams::mitm::socket::resolver { + + class ResolverImpl : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - everything. + */ + return true; + } + public: + /* Overridden commands. */ + Result GetHostByNameRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &name, sf::Out out_host_error, sf::Out out_errno, const sf::OutBuffer &out_hostent, sf::Out out_size); + Result GetAddrInfoRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutBuffer &out_addrinfo, sf::Out out_errno, sf::Out out_retval, sf::Out out_size); + Result GetHostByNameRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &name, const sf::OutAutoSelectBuffer &out_hostent, sf::Out out_size, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_host_error, sf::Out out_errno); + Result GetAddrInfoRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out out_size, sf::Out out_retval, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_host_error, sf::Out out_errno); + }; + static_assert(IsIResolver); + +}