/* * Copyright (c) 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" #include "dnsmitm_host_redirection.hpp" #include "serializer/serializer.hpp" #include "sfdnsres_shim.h" namespace ams::mitm::socket::resolver { namespace { struct AresAddrInfoQuery { std::string hostname; ams::socket::InAddrT redirect_addr; u16 redirect_port; const struct ams::socket::ares_addrinfo_hints *hint; }; std::map> g_ares_addr_info_queries; ssize_t g_current_ares_addr_info_query_id; } ssize_t SerializeRedirectedHostEnt(u8 * const dst, size_t dst_size, const char *hostname, ams::socket::InAddrT redirect_addr) { struct in_addr addr = { .s_addr = redirect_addr }; struct in_addr *addr_list[2] = { std::addressof(addr), nullptr }; struct hostent ent = { .h_name = const_cast(hostname), .h_aliases = nullptr, .h_addrtype = AF_INET, .h_length = sizeof(u32), .h_addr_list = (char **)addr_list, }; const auto result = serializer::DNSSerializer::ToBuffer(dst, dst_size, ent); AMS_ABORT_UNLESS(result >= 0); return result; } ssize_t SerializeRedirectedAddrInfo(u8 * const dst, size_t dst_size, const char *hostname, ams::socket::InAddrT redirect_addr, u16 redirect_port, const struct addrinfo *hint) { AMS_UNUSED(hostname); struct addrinfo ai = { .ai_flags = 0, .ai_family = AF_UNSPEC, .ai_socktype = 0, .ai_protocol = 0, .ai_addrlen = 0, .ai_canonname = nullptr, .ai_next = nullptr, }; if (hint != nullptr) { ai = *hint; } switch (ai.ai_family) { case AF_UNSPEC: ai.ai_family = AF_INET; break; case AF_INET: ai.ai_family = AF_INET; break; case AF_INET6: AMS_ABORT("Redirected INET6 not supported"); break; AMS_UNREACHABLE_DEFAULT_CASE(); } if (ai.ai_socktype == 0) { ai.ai_socktype = SOCK_STREAM; } if (ai.ai_protocol == 0) { ai.ai_protocol = IPPROTO_TCP; } const struct sockaddr_in sin = { .sin_family = AF_INET, .sin_port = ams::socket::InetHtons(redirect_port), .sin_addr = { .s_addr = redirect_addr }, .sin_zero = {}, }; ai.ai_addrlen = sizeof(sin); ai.ai_addr = (struct sockaddr *)(std::addressof(sin)); const auto result = serializer::DNSSerializer::ToBuffer(dst, dst_size, ai); AMS_ABORT_UNLESS(result >= 0); return result; } ssize_t QueueAresAddrInfoQuery(const char *hostname, ams::socket::InAddrT redirect_addr, u16 redirect_port, const struct ams::socket::ares_addrinfo_hints *hint) { ssize_t query_id = g_current_ares_addr_info_query_id; AresAddrInfoQuery query; query.hostname = hostname; query.redirect_addr = redirect_addr; query.redirect_port = redirect_port; query.hint = hint; g_ares_addr_info_queries.emplace(query_id, std::make_unique(query)); /* NOTE: This really just increases ad infinitum. Should we bother checking it? */ ++g_current_ares_addr_info_query_id; return query_id; } ssize_t DequeueAresAddrInfoQuery(u8 * const dst, size_t dst_size, u32 query_id) { AresAddrInfoQuery *query = nullptr; for (auto it = g_ares_addr_info_queries.begin(); it != g_ares_addr_info_queries.end(); ++it) { if (it->first == static_cast(query_id)) { query = it->second.get(); break; } } if (query == nullptr) { return 0; } struct ams::socket::ares_addrinfo_node node = { .ai_ttl = 0, .ai_flags = AF_UNSPEC, .ai_family = 0, .ai_socktype = 0, .ai_protocol = 0, .ai_addrlen = 0, .ai_next = nullptr, }; if (query->hint != nullptr) { std::memcpy(reinterpret_cast(std::addressof(node.ai_flags)), reinterpret_cast(query->hint), sizeof(struct ams::socket::ares_addrinfo_hints)); } switch (node.ai_family) { case AF_UNSPEC: node.ai_family = AF_INET; break; case AF_INET: node.ai_family = AF_INET; break; case AF_INET6: AMS_ABORT("Redirected INET6 not supported"); break; AMS_UNREACHABLE_DEFAULT_CASE(); } if (node.ai_socktype == 0) { node.ai_socktype = SOCK_STREAM; } if (node.ai_protocol == 0) { node.ai_protocol = IPPROTO_TCP; } const struct sockaddr_in sin = { .sin_family = AF_INET, .sin_port = ams::socket::InetHtons(query->redirect_port), .sin_addr = { .s_addr = query->redirect_addr }, .sin_zero = {}, }; node.ai_addrlen = sizeof(sin); node.ai_addr = (struct sockaddr *)(std::addressof(sin)); struct ams::socket::ares_addrinfo ai { .cnames = nullptr, .nodes = std::addressof(node), .name = const_cast(query->hostname.c_str()), }; const auto result = serializer::DNSSerializer::ToBuffer(dst, dst_size, ai); AMS_ABORT_UNLESS(result >= 0); g_ares_addr_info_queries.erase(query_id); return result; } void CancelAresAddrInfoQuery(u32 query_id) { for (auto it = g_ares_addr_info_queries.begin(); it != g_ares_addr_info_queries.end(); ++it) { if (it->first == static_cast(query_id)) { g_ares_addr_info_queries.erase(it); break; } } } 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) { AMS_UNUSED(cancel_handle, client_pid, use_nsd_resolve); const char *hostname = reinterpret_cast(name.GetPointer()); LogDebug("[%016lx]: GetHostByNameRequest(%s)\n", m_client_info.program_id.value, hostname); R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); ams::socket::InAddrT redirect_addr = {}; R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); LogDebug("[%016lx]: Redirecting %s to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); const auto size = SerializeRedirectedHostEnt(out_hostent.GetPointer(), out_hostent.GetSize(), hostname, redirect_addr); *out_host_error = 0; *out_errno = 0; *out_size = size; R_SUCCEED(); } 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) { AMS_UNUSED(cancel_handle, client_pid, use_nsd_resolve); const char *hostname = reinterpret_cast(node.GetPointer()); LogDebug("[%016lx]: GetAddrInfoRequest(%s, %s)\n", m_client_info.program_id.value, reinterpret_cast(node.GetPointer()), reinterpret_cast(srv.GetPointer())); R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); ams::socket::InAddrT redirect_addr = {}; R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); u16 port = 0; if (srv.GetPointer() != nullptr) { for (const char *cur = reinterpret_cast(srv.GetPointer()); *cur != 0; ++cur) { AMS_ABORT_UNLESS(std::isdigit(static_cast(*cur))); port *= 10; port += *cur - '0'; } } LogDebug("[%016lx]: Redirecting %s:%u to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); const bool use_hint = serialized_hint.GetPointer() != nullptr; struct addrinfo hint = {}; if (use_hint) { AMS_ABORT_UNLESS(serializer::DNSSerializer::FromBuffer(hint, serialized_hint.GetPointer(), serialized_hint.GetSize()) >= 0); } ON_SCOPE_EXIT { if (use_hint) { serializer::FreeAddrInfo(hint); } }; const auto size = SerializeRedirectedAddrInfo(out_addrinfo.GetPointer(), out_addrinfo.GetSize(), hostname, redirect_addr, port, use_hint ? std::addressof(hint) : nullptr); *out_retval = 0; *out_errno = 0; *out_size = size; R_SUCCEED(); } 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) { AMS_UNUSED(client_pid, options_version, options, num_options); const char *hostname = reinterpret_cast(name.GetPointer()); LogDebug("[%016lx]: GetHostByNameRequestWithOptions(%s)\n", m_client_info.program_id.value, hostname); R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); ams::socket::InAddrT redirect_addr = {}; R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); LogDebug("[%016lx]: Redirecting %s to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); const auto size = SerializeRedirectedHostEnt(out_hostent.GetPointer(), out_hostent.GetSize(), hostname, redirect_addr); *out_host_error = 0; *out_errno = 0; *out_size = size; R_SUCCEED(); } 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) { AMS_UNUSED(client_pid, options_version, options, num_options); const char *hostname = reinterpret_cast(node.GetPointer()); LogDebug("[%016lx]: GetAddrInfoRequestWithOptions(%s, %s)\n", m_client_info.program_id.value, hostname, reinterpret_cast(srv.GetPointer())); R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); ams::socket::InAddrT redirect_addr = {}; R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); u16 port = 0; if (srv.GetPointer() != nullptr) { for (const char *cur = reinterpret_cast(srv.GetPointer()); *cur != 0; ++cur) { AMS_ABORT_UNLESS(std::isdigit(static_cast(*cur))); port *= 10; port += *cur - '0'; } } LogDebug("[%016lx]: Redirecting %s:%u to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); const bool use_hint = serialized_hint.GetPointer() != nullptr; struct addrinfo hint = {}; if (use_hint) { AMS_ABORT_UNLESS(serializer::DNSSerializer::FromBuffer(hint, serialized_hint.GetPointer(), serialized_hint.GetSize()) >= 0); } ON_SCOPE_EXIT { if (use_hint) { serializer::FreeAddrInfo(hint); } }; const auto size = SerializeRedirectedAddrInfo(out_addrinfo.GetPointer(), out_addrinfo.GetSize(), hostname, redirect_addr, port, use_hint ? std::addressof(hint) : nullptr); *out_retval = 0; *out_host_error = 0; *out_errno = 0; *out_size = size; R_SUCCEED(); } Result ResolverImpl::ReportTelemetry(const sf::ClientProcessId &client_pid, const sf::InBuffer &telemetry, u32 version) { /* NOTE: As of 23.0.0 this command does nothing. We mitm it anyway for future proofing. */ AMS_UNUSED(client_pid, telemetry, version); R_SUCCEED(); } Result ResolverImpl::QueryAddrInfoRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &node, const sf::InAutoSelectBuffer &srv, const sf::InAutoSelectBuffer &serialized_hint, sf::Out out_query_id, u32 fd, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out out_retval, sf::Out out_errno) { AMS_UNUSED(client_pid, fd, options_version, options, num_options); const char *hostname = reinterpret_cast(node.GetPointer()); LogDebug("[%016lx]: QueryAddrInfoRequestWithOptions(%s, %s)\n", m_client_info.program_id.value, hostname, reinterpret_cast(srv.GetPointer())); R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); ams::socket::InAddrT redirect_addr = {}; R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); u16 port = 0; if (srv.GetPointer() != nullptr) { for (const char *cur = reinterpret_cast(srv.GetPointer()); *cur != 0; ++cur) { AMS_ABORT_UNLESS(std::isdigit(static_cast(*cur))); port *= 10; port += *cur - '0'; } } LogDebug("[%016lx]: Queueing redirection %s:%u to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); const bool use_hint = serialized_hint.GetPointer() != nullptr; struct ams::socket::ares_addrinfo_hints hint = {}; if (use_hint) { AMS_ABORT_UNLESS(serializer::DNSSerializer::FromBuffer(hint, serialized_hint.GetPointer(), serialized_hint.GetSize()) >= 0); } const auto query_id = QueueAresAddrInfoQuery(hostname, redirect_addr, port, use_hint ? std::addressof(hint) : nullptr); LogDebug("[%016lx]: Queued query with id 0x%lx\n", m_client_info.program_id.value, query_id); /* Forward the query. The provided socket EventFd must be signalled by having bsdsocket write 1 to the associated socket. */ /* This is fine though, we only care about replacing with our redirection during fetch. */ R_TRY(sfdnsresQueryAddrInfoRequestWithOptionsFwd(m_forward_service.get(), static_cast(client_pid.GetValue().value), node.GetPointer(), node.GetSize(), srv.GetPointer(), srv.GetSize(), serialized_hint.GetPointer(), serialized_hint.GetSize(), reinterpret_cast(out_query_id.GetPointer()), fd, options_version, options.GetPointer(), options.GetSize(), num_options, reinterpret_cast(out_retval.GetPointer()), reinterpret_cast(out_errno.GetPointer()))); /* Replace with our own query. */ *out_query_id = query_id; *out_retval = 0; *out_errno = 0; R_SUCCEED(); } Result ResolverImpl::FetchAddrInfoRequestWithOptions(const sf::ClientProcessId &client_pid, u32 query_id, u32 version, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out out_size, sf::Out out_retval, sf::Out out_errno) { AMS_UNUSED(client_pid, version); LogDebug("[%016lx]: FetchAddrInfoRequestWithOptions(%d)\n", m_client_info.program_id.value, query_id); const auto size = DequeueAresAddrInfoQuery(out_addrinfo.GetPointer(), out_addrinfo.GetSize(), query_id); LogDebug("[%016lx]: Dequeued query with size 0x%lx\n", m_client_info.program_id.value, size); *out_size = size; *out_retval = 0; *out_errno = 0; R_SUCCEED(); } Result ResolverImpl::CancelQuery(const sf::ClientProcessId &client_pid, u32 query_id) { AMS_UNUSED(client_pid); LogDebug("[%016lx]: CancelQuery(%d)\n", m_client_info.program_id.value, query_id); CancelAresAddrInfoQuery(query_id); R_SUCCEED(); } Result ResolverImpl::AtmosphereReloadHostsFile() { /* Perform a hosts file reload. */ InitializeResolverRedirections(); R_SUCCEED(); } }