diff --git a/config_templates/system_settings.ini b/config_templates/system_settings.ini index 600d03c5f..77fac36b3 100644 --- a/config_templates/system_settings.ini +++ b/config_templates/system_settings.ini @@ -42,6 +42,13 @@ ; enabled or disabled. ; 0 = Disabled (not debug mode), 1 = Enabled (debug mode) ; enable_am_debug_mode = u8!0x0 +; Controls whether dns.mitm is enabled +; 0 = Disabled, 1 = Enabled +; atmosphere!enable_dns_mitm = u8!0x1 +; Controls whether dns.mitm uses the default redirections in addition to +; whatever is specified in the user's hosts file. +; 0 = Disabled (use hosts file contents), 1 = Enabled (use defaults and hosts file contents) +; atmosphere!add_defaults_to_dns_hosts = u8!0x0 [hbloader] ; Controls the size of the homebrew heap when running as applet. ; If set to zero, all available applet memory is used as heap. diff --git a/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp b/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp index c919abc1f..4597b2adf 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp @@ -22,6 +22,9 @@ namespace ams::emummc { /* Get whether emummc is active. */ bool IsActive(); + /* Get the active emummc id. */ + u32 GetActiveId(); + /* Get Nintendo redirection path. */ const char *GetNintendoDirPath(); diff --git a/libraries/libstratosphere/source/ams/ams_emummc_api.cpp b/libraries/libstratosphere/source/ams/ams_emummc_api.cpp index 02dd75f9c..2c82a12db 100644 --- a/libraries/libstratosphere/source/ams/ams_emummc_api.cpp +++ b/libraries/libstratosphere/source/ams/ams_emummc_api.cpp @@ -57,10 +57,10 @@ namespace ams::emummc { }; /* Globals. */ - os::Mutex g_lock(false); - ExosphereConfig g_exo_config; - bool g_is_emummc; - bool g_has_cached; + constinit os::SdkMutex g_lock; + constinit ExosphereConfig g_exo_config; + constinit bool g_is_emummc; + constinit bool g_has_cached; /* Helpers. */ void CacheValues() { @@ -110,6 +110,12 @@ namespace ams::emummc { return g_is_emummc; } + /* Get the active emummc id. */ + u32 GetActiveId() { + CacheValues(); + return g_exo_config.base_cfg.id; + } + /* Get Nintendo redirection path. */ const char *GetNintendoDirPath() { CacheValues(); diff --git a/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp b/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp index de7bc6eb0..f32f61abf 100644 --- a/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp @@ -47,6 +47,36 @@ namespace ams::mitm::fs { return fsFsCreateFile(&g_sd_filesystem, path, size, option); } + Result DeleteSdFile(const char *path) { + R_TRY(EnsureSdInitialized()); + return fsFsDeleteFile(&g_sd_filesystem, path); + } + + bool HasSdFile(const char *path) { + if (R_FAILED(EnsureSdInitialized())) { + return false; + } + + FsDirEntryType type; + if (R_FAILED(fsFsGetEntryType(&g_sd_filesystem, path, &type))) { + return false; + } + + return type == FsDirEntryType_File; + } + + bool HasAtmosphereSdFile(const char *path) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + return HasSdFile(fixed_path); + } + + Result DeleteAtmosphereSdFile(const char *path) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + return DeleteSdFile(fixed_path); + } + Result CreateAtmosphereSdFile(const char *path, s64 size, s32 option) { char fixed_path[ams::fs::EntryNameLengthMax + 1]; FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); diff --git a/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp b/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp index 974df3243..258c888bd 100644 --- a/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp +++ b/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp @@ -22,6 +22,7 @@ namespace ams::mitm::fs { void OpenGlobalSdCardFileSystem(); /* Utilities. */ + Result DeleteAtmosphereSdFile(const char *path); Result CreateSdFile(const char *path, s64 size, s32 option); Result CreateAtmosphereSdFile(const char *path, s64 size, s32 option); Result OpenSdFile(FsFile *out, const char *path, u32 mode); @@ -30,6 +31,9 @@ namespace ams::mitm::fs { Result OpenAtmosphereSdRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode); Result OpenAtmosphereRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs); + bool HasSdFile(const char *path); + bool HasAtmosphereSdFile(const char *path); + Result CreateSdDirectory(const char *path); Result CreateAtmosphereSdDirectory(const char *path); Result OpenSdDirectory(FsDir *out, const char *path, u32 mode); diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp index dcfbc5b67..f4ac939ce 100644 --- a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp @@ -17,14 +17,297 @@ #include "../amsmitm_fs_utils.hpp" #include "dnsmitm_debug.hpp" #include "dnsmitm_host_redirection.hpp" +#include "socket_allocator.hpp" namespace ams::mitm::socket::resolver { + namespace { + + constexpr const char DefaultHostsFile[] = + "# Nintendo telemetry servers\n" + "127.0.0.1 receive-lp1.dg.srv.nintendo.net\n" + "127.0.0.1 receive-lp1.er.srv.nintendo.net\n"; + + constinit os::SdkMutex g_redirection_lock; + std::unordered_map g_redirection_map; + + constinit char g_specific_emummc_hosts_path[0x40] = {}; + + void ParseHostsFile(const char *file_data) { + enum class State { + IgnoredLine, + BeginLine, + Ip1, + IpDot1, + Ip2, + IpDot2, + Ip3, + IpDot3, + Ip4, + WhiteSpace, + HostName, + }; + + ams::socket::InAddrT current_address; + char current_hostname[0x200]; + u32 work; + + State state = State::BeginLine; + for (const char *cur = file_data; *cur != '\x00'; ++cur) { + const char c = *cur; + switch (state) { + case State::IgnoredLine: + if (c == '\n') { + state = State::BeginLine; + } + break; + case State::BeginLine: + if (std::isdigit(static_cast(c))) { + current_address = 0; + work = static_cast(c - '0'); + state = State::Ip1; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip1: + if (std::isdigit(static_cast(c))) { + work *= 10; + work += static_cast(c - '0'); + } else if (c == '.') { + current_address |= (work & 0xFF) << 0; + work = 0; + state = State::IpDot1; + } else { + state = State::IgnoredLine; + } + break; + case State::IpDot1: + if (std::isdigit(static_cast(c))) { + work = static_cast(c - '0'); + state = State::Ip2; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip2: + if (std::isdigit(static_cast(c))) { + work *= 10; + work += static_cast(c - '0'); + } else if (c == '.') { + current_address |= (work & 0xFF) << 8; + work = 0; + state = State::IpDot2; + } else { + state = State::IgnoredLine; + } + break; + case State::IpDot2: + if (std::isdigit(static_cast(c))) { + work = static_cast(c - '0'); + state = State::Ip3; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip3: + if (std::isdigit(static_cast(c))) { + work *= 10; + work += static_cast(c - '0'); + } else if (c == '.') { + current_address |= (work & 0xFF) << 16; + work = 0; + state = State::IpDot3; + } else { + state = State::IgnoredLine; + } + break; + case State::IpDot3: + if (std::isdigit(static_cast(c))) { + work = static_cast(c - '0'); + state = State::Ip4; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip4: + if (std::isdigit(static_cast(c))) { + work *= 10; + work += static_cast(c - '0'); + } else if (c == ' ' || c == '\t') { + current_address |= (work & 0xFF) << 24; + work = 0; + state = State::WhiteSpace; + } else { + state = State::IgnoredLine; + } + break; + case State::WhiteSpace: + if (c == '\n') { + state = State::BeginLine; + } else if (c != ' ' && c != '\r' && c != '\t') { + current_hostname[0] = c; + work = 1; + state = State::HostName; + } + break; + case State::HostName: + if (c == ' ' || c == '\r' || c == '\n' || c == '\t') { + AMS_ABORT_UNLESS(work < sizeof(current_hostname)); + current_hostname[work] = '\x00'; + + g_redirection_map[static_cast(current_hostname)] = current_address; + + if (c == '\n') { + state = State::BeginLine; + } else { + state = State::WhiteSpace; + } + } else { + AMS_ABORT_UNLESS(work < sizeof(current_hostname) - 1); + current_hostname[work++] = c; + } + } + } + + if (state == State::HostName) { + AMS_ABORT_UNLESS(work < sizeof(current_hostname)); + current_hostname[work] = '\x00'; + + g_redirection_map[static_cast(current_hostname)] = current_address; + } + } + + void Log(::FsFile &f, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void Log(::FsFile &f, const char *fmt, ...) { + char log_buf[0x100]; + int len = 0; + { + std::va_list vl; + va_start(vl, fmt); + len = util::VSNPrintf(log_buf, sizeof(log_buf), fmt, vl); + va_end(vl); + } + + s64 ofs; + R_ABORT_UNLESS(::fsFileGetSize(std::addressof(f), std::addressof(ofs))); + R_ABORT_UNLESS(::fsFileWrite(std::addressof(f), ofs, log_buf, len, FsWriteOption_Flush)); + } + + const char *SelectHostsFile(::FsFile &log_file) { + Log(log_file, "Selecting hosts file...\n"); + const bool is_emummc = emummc::IsActive(); + const u32 emummc_id = emummc::GetActiveId(); + util::SNPrintf(g_specific_emummc_hosts_path, sizeof(g_specific_emummc_hosts_path), "/hosts/emummc_%04x", emummc_id); + + if (is_emummc) { + if (mitm::fs::HasAtmosphereSdFile(g_specific_emummc_hosts_path)) { + return g_specific_emummc_hosts_path; + } + Log(log_file, "Skipping %s because it does not exist...\n", g_specific_emummc_hosts_path); + + if (mitm::fs::HasAtmosphereSdFile("/hosts/emummc")) { + return "/hosts/emummc"; + } + Log(log_file, "Skipping %s because it does not exist...\n", "/hosts/emummc"); + } else { + if (mitm::fs::HasAtmosphereSdFile("/hosts/sysmmc")) { + return "/hosts/sysmmc"; + } + Log(log_file, "Skipping %s because it does not exist...\n", "/hosts/sysmmc"); + } + + return "/hosts/default"; + } + + } + + void InitializeResolverRedirections(bool add_defaults) { + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(g_redirection_lock); + + /* Clear the redirections map. */ + g_redirection_map.clear(); + + /* Open log file. */ + ::FsFile log_file; + mitm::fs::DeleteAtmosphereSdFile("/dns_mitm_startup.log"); + R_ABORT_UNLESS(mitm::fs::CreateAtmosphereSdFile("/dns_mitm_startup.log", 0, ams::fs::CreateOption_None)); + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(log_file), "/dns_mitm_startup.log", ams::fs::OpenMode_ReadWrite | ams::fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { ::fsFileClose(std::addressof(log_file)); }; + + Log(log_file, "DNS Mitm:\n"); + + /* If a default hosts file doesn't exist on the sd card, create one. */ + if (!mitm::fs::HasAtmosphereSdFile("/hosts/default")) { + Log(log_file, "Creating /hosts/default because it does not exist.\n"); + + mitm::fs::CreateAtmosphereSdDirectory("/hosts"); + R_ABORT_UNLESS(mitm::fs::CreateAtmosphereSdFile("/hosts/default", sizeof(DefaultHostsFile) - 1, ams::fs::CreateOption_None)); + + ::FsFile default_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(default_file), "/hosts/default", ams::fs::OpenMode_ReadWrite)); + R_ABORT_UNLESS(::fsFileWrite(std::addressof(default_file), 0, DefaultHostsFile, sizeof(DefaultHostsFile) - 1, ::FsWriteOption_Flush)); + ::fsFileClose(std::addressof(default_file)); + } + + /* If we should, add the defaults. */ + if (add_defaults) { + Log(log_file, "Adding defaults to redirection list.\n"); + ParseHostsFile(DefaultHostsFile); + } + + /* Select the hosts file. */ + const char *hosts_path = SelectHostsFile(log_file); + Log(log_file, "Selected %s\n", hosts_path); + + /* Load the hosts file. */ + { + char *hosts_file_data = nullptr; + ON_SCOPE_EXIT { if (hosts_file_data != nullptr) { ams::Free(hosts_file_data); } }; + { + ::FsFile hosts_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(hosts_file), hosts_path, ams::fs::OpenMode_Read)); + ON_SCOPE_EXIT { ::fsFileClose(std::addressof(hosts_file)); }; + + /* Get the hosts file size. */ + s64 hosts_size; + R_ABORT_UNLESS(::fsFileGetSize(std::addressof(hosts_file), std::addressof(hosts_size))); + + /* Validate we can read the file. */ + AMS_ABORT_UNLESS(0 <= hosts_size && hosts_size < 0x8000); + + /* Read the data. */ + hosts_file_data = static_cast(ams::Malloc(0x8000)); + AMS_ABORT_UNLESS(hosts_file_data != nullptr); + + u64 br; + R_ABORT_UNLESS(::fsFileRead(std::addressof(hosts_file), 0, hosts_file_data, hosts_size, ::FsReadOption_None, std::addressof(br))); + AMS_ABORT_UNLESS(br == static_cast(hosts_size)); + + /* Null-terminate. */ + hosts_file_data[hosts_size] = '\x00'; + } + + /* Parse the hosts file. */ + ParseHostsFile(hosts_file_data); + } + + /* Print the redirections. */ + Log(log_file, "Redirections:\n"); + for (const auto &[host, address] : g_redirection_map) { + Log(log_file, " `%s` -> %u.%u.%u.%u\n", host.c_str(), (address >> 0) & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF, (address >> 24) & 0xFF); + } + } + bool GetRedirectedHostByName(ams::socket::InAddrT *out, const char *hostname) { - /* TODO: Real implementation */ - if (std::strcmp(hostname, "receive-lp1.dg.srv.nintendo.net") == 0) { - *out = ams::socket::InAddr_Loopback; - return true; + std::scoped_lock lk(g_redirection_lock); + + for (const auto &[host, address] : g_redirection_map) { + if (std::strcmp(host.c_str(), hostname) == 0) { + *out = address; + return true; + } } return false; diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp index 2c339668e..33da75102 100644 --- a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp @@ -18,6 +18,8 @@ namespace ams::mitm::socket::resolver { + void InitializeResolverRedirections(bool add_defaults); + bool GetRedirectedHostByName(ams::socket::InAddrT *out, const char *hostname); } diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp index 827019d17..6bf18931d 100644 --- a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp @@ -16,8 +16,9 @@ #include #include "../amsmitm_initialization.hpp" #include "dnsmitm_module.hpp" -#include "dnsmitm_resolver_impl.hpp" #include "dnsmitm_debug.hpp" +#include "dnsmitm_resolver_impl.hpp" +#include "dnsmitm_host_redirection.hpp" namespace ams::mitm::socket::resolver { @@ -93,15 +94,39 @@ namespace ams::mitm::socket::resolver { } } + bool ShouldMitmDns() { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_dns_mitm") == sizeof(en)) { + return (en != 0); + } + return false; + } + + bool ShouldAddDefaultResolverRedirections() { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "add_defaults_to_dns_hosts") == sizeof(en)) { + return (en != 0); + } + return false; + } + } void MitmModule::ThreadFunction(void *arg) { /* Wait until initialization is complete. */ mitm::WaitInitialized(); + /* If we shouldn't mitm dns, don't do anything at all. */ + if (!ShouldMitmDns()) { + return; + } + /* Initialize debug. */ resolver::InitializeDebug(); + /* Initialize redirection map. */ + resolver::InitializeResolverRedirections(ShouldAddDefaultResolverRedirections()); + /* Create mitm servers. */ R_ABORT_UNLESS((g_server_manager.RegisterMitmServer(PortIndex_Mitm, DnsMitmServiceName))); diff --git a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp index 9e946c1ec..4733bde4a 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -349,6 +349,15 @@ namespace ams::settings::fwdbg { /* 0 = Disabled (not debug mode), 1 = Enabled (debug mode) */ R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_am_debug_mode", "u8!0x0")); + /* Controls whether dns.mitm is enabled. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_dns_mitm", "u8!0x1")); + + /* Controls whether dns.mitm uses the default redirections in addition to */ + /* whatever is specified in the user's hosts file. */ + /* 0 = Disabled (use hosts file contents), 1 = Enabled (use defaults and hosts file contents) */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "add_defaults_to_dns_hosts", "u8!0x0")); + /* Hbloader custom settings. */ /* Controls the size of the homebrew heap when running as applet. */