diff --git a/.gitignore b/.gitignore index 78f859ac8..058c36740 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,5 @@ sept/sept-secondary/KEYS.py **/build_nintendo_nx_arm **/build_nintendo_nx_x64 **/build_nintendo_nx_x86 + +stratosphere/test/ diff --git a/config_templates/system_settings.ini b/config_templates/system_settings.ini index 600d03c5f..3059cb839 100644 --- a/config_templates/system_settings.ini +++ b/config_templates/system_settings.ini @@ -42,6 +42,16 @@ ; 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 +; 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) +; add_defaults_to_dns_hosts = u8!0x1 +; Controls whether dns.mitm logs to the sd card for debugging +; 0 = Disabled, 1 = Enabled +; enable_dns_mitm_debug_log = 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/docs/changelog.md b/docs/changelog.md index 69f0251b3..0fa705e14 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,4 +1,19 @@ # Changelog +## 0.18.0 ++ A new mitm module was added (`dns.mitm`). + + This provides a highly configurable mechanism for redirecting DNS resolution requests. + + By default atmosphère redirects resolution requests for official telemetry servers to a loopback address. + + Documentation on how to configure `dns.mitm` to meet your more specific needs may be found [here](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/features/dns_mitm.md). ++ The service framework API (`sf`) was refactored to be more accurate to official logic and greatly reduce memory requirements. + + The comparison of atmosphère module memory usage versus Nintendo's found [here](https://github.com/Atmosphere-NX/Atmosphere/wiki/Memory-Comparisons) was updated to reflect this. + + **Please Note**: If you are a developer using the libstratosphere service APIs, some updating may be required. Contact SciresM#0524 on discord for assistance if required. ++ A number of deprecations were removed, following a general codebase cleanup: + + The `sm` extension to not unregister services on connection close was superseded by official opt-in logic in 11.0.0, and has been removed in favor of official logic. + + This should have zero impact on users. + + The temporary `hid-mitm` added in 0.9.0 has finally been removed, following over a year of deprecation. + + There shouldn't be any homebrew in use still affected by this, but the situation will be monitored. + + If this is somehow still a real issue, an unaffiliated hid mitm sysmodule providing the same functionality can be created and released, separate from atmosphère itself. ++ Several issues were fixed, and usability and stability were improved. ## 0.17.1 + A number of atmosphère's modules were using more memory than their Nintendo equivalent's in 0.17.0; a number of code generatio tweaks have been applied to fix this across the board. + A detailed comparison of atmosphère module memory usage versus Nintendo's was made and can be found [here](https://github.com/Atmosphere-NX/Atmosphere/wiki/Memory-Comparisons). diff --git a/docs/components/modules/ams_mitm.md b/docs/components/modules/ams_mitm.md index 0f17c5d26..425e31b33 100644 --- a/docs/components/modules/ams_mitm.md +++ b/docs/components/modules/ams_mitm.md @@ -25,6 +25,12 @@ set_mitm enables intercepting requests to the system settings service. It curren + `ns` system module and games (to allow for overriding game locales) + All firmware debug settings requests (to allow modification of system settings not directly exposed to the user) +## dns_mitm + +dns_mitm enables intercepting requests to dns resolution services, to enable redirecting requests for specified hostnames. + +For documentation, see [here](../../features/dns_mitm.md). + ### Firmware Version set_mitm intercepts the `GetFirmwareVersion` command, if the requester is `qlaunch` or `maintenance`. It modifies the `display_version` field of the returned system version, causing the version to display diff --git a/docs/features/dns_mitm.md b/docs/features/dns_mitm.md new file mode 100644 index 000000000..e61f499c2 --- /dev/null +++ b/docs/features/dns_mitm.md @@ -0,0 +1,53 @@ +# DNS.mitm +As of 0.18.0, atmosphère provides a mechanism for redirecting DNS resolution requests. + +By default, atmosphère redirects resolution requests for official telemetry servers, redirecting them to a loopback address. + +## Hosts files + +DNS.mitm can be configured through the usage of a slightly-extended `hosts` file format, which is parsed only once on system startup. + +In particular, hosts files parsed by DNS.mitm have the following extensions to the usual format: ++ `*` is treated as a wildcard character, matching any collection of 0 or more characters wherever it occurs in a hostname. ++ `%` is treated as a stand-in for the value of `nsd!environment_identifier`. This is always `lp1`, on production devices. + +If multiple entries in a host file match a domain, the last-defined match is used. + +Please note that homebrew may trigger a hosts file re-parse by sending the extension IPC command 65000 ("AtmosphereReloadHostsFile") to a connected `sfdnsres` session. + +### Hosts file selection + +Atmosphère will try to read hosts from the following file paths, in order, stopping once it successfully performs a file read: + ++ (emummc only) `/atmosphere/hosts/emummc_%04lx.txt`, formatted with the emummc's id number (see `emummc.ini`). ++ (emummc only) `/atmosphere/hosts/emummc.txt`. ++ (sysmmc only) `/atmosphere/hosts/sysmmc.txt`. ++ `/atmosphere/hosts/default.txt` + +If `/atmosphere/hosts/default.txt` does not exist, atmosphère will create it to contain the defaults. + +### Atmosphère defaults + +By default, atmosphère's default redirections are parsed **in addition to** the contents of the loaded hosts file. + +This is equivalent to thinking of the loaded hosts file as having the atmosphère defaults prepended to it. + +This setting is considered desirable, because it minimizes the telemetry risks if a user forgets to update a custom hosts file on a system update which changes the telemetry servers. + +This behavior can be opted-out from by setting `atmosphere!add_defaults_to_dns_hosts = u8!0x0` in `system_settings.ini`. + +The current default redirections are: + +``` +# Nintendo telemetry servers +127.0.0.1 receive-%.dg.srv.nintendo.net receive-%.er.srv.nintendo.net +``` + +## Debugging + +On startup (or on hosts file re-parse), DNS.mitm will log both what hosts file it selected and the contents of all redirections it parses to `/atmosphere/logs/dns_mitm_startup.log`. + +In addition, if the user sets `atmosphere!enable_dns_mitm_debug_log = u8!0x1` in `system_settings.ini`, DNS.mitm will log all requests to GetHostByName/GetAddrInfo to `/atmosphere/logs/dns_mitm_debug.log`. All redirections will be noted when they occur. + +## Opting-out of DNS.mitm entirely +If you wish to disable DNS.mitm entirely, `system_settings.ini` can be edited to set `atmosphere!enable_dns_mitm = u8!0x0`. diff --git a/libraries/.gitrepo b/libraries/.gitrepo index 759e59bde..d525598ae 100644 --- a/libraries/.gitrepo +++ b/libraries/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/Atmosphere-NX/Atmosphere-libs branch = master - commit = 6c11c07e2a7f03952a4e70eb89b47bf528de39c6 - parent = 9e104bb83f1302e9f126542fbf57c7f666aae953 + commit = 17960517bad5d2d07effb28b744ac8d907d571e0 + parent = ee2e9d50fd93721b365daf0eae1eef17c8ba62e8 method = merge cmdver = 0.4.1 diff --git a/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp b/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp index 3bd1b054c..1b3469772 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp @@ -45,7 +45,21 @@ namespace ams::kern { - static_assert(cpu::NumCores <= static_cast(BITSIZEOF(u64))); - static_assert(util::size(cpu::VirtualToPhysicalCoreMap) == BITSIZEOF(u64)); + namespace cpu { + + static constexpr inline size_t NumVirtualCores = BITSIZEOF(u64); + + static constexpr inline u64 VirtualCoreMask = [] { + u64 mask = 0; + for (size_t i = 0; i < NumVirtualCores; ++i) { + mask |= (UINT64_C(1) << i); + } + return mask; + }(); + + } + + static_assert(cpu::NumCores <= cpu::NumVirtualCores); + static_assert(util::size(cpu::VirtualToPhysicalCoreMap) == cpu::NumVirtualCores); } diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp index df41785d5..783ef1904 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp @@ -314,12 +314,12 @@ namespace ams::kern::board::nintendo::nx { g_secure_applet_memory_used = false; } - u64 GetVersionIdentifier() { - u64 value = kern::GetTargetFirmware(); - value |= static_cast(ATMOSPHERE_RELEASE_VERSION_MICRO) << 32; - value |= static_cast(ATMOSPHERE_RELEASE_VERSION_MINOR) << 40; - value |= static_cast(ATMOSPHERE_RELEASE_VERSION_MAJOR) << 48; - value |= static_cast('M') << 56; + u32 GetVersionIdentifier() { + u32 value = 0; + value |= static_cast(ATMOSPHERE_RELEASE_VERSION_MICRO) << 0; + value |= static_cast(ATMOSPHERE_RELEASE_VERSION_MINOR) << 8; + value |= static_cast(ATMOSPHERE_RELEASE_VERSION_MAJOR) << 16; + value |= static_cast('M') << 24; return value; } @@ -584,8 +584,8 @@ namespace ams::kern::board::nintendo::nx { f_ctx->module_base = KMemoryLayout::GetKernelCodeRegionExtents().GetAddress(); /* Set afsr1. */ - f_ctx->afsr0 = 0; - f_ctx->afsr1 = GetVersionIdentifier(); + f_ctx->afsr0 = GetVersionIdentifier(); + f_ctx->afsr1 = static_cast(kern::GetTargetFirmware()); /* Set efsr/far. */ f_ctx->far = cpu::GetFarEl1(); diff --git a/libraries/libmesosphere/source/kern_k_capabilities.cpp b/libraries/libmesosphere/source/kern_k_capabilities.cpp index a8d2c550e..336ac46fc 100644 --- a/libraries/libmesosphere/source/kern_k_capabilities.cpp +++ b/libraries/libmesosphere/source/kern_k_capabilities.cpp @@ -22,7 +22,7 @@ namespace ams::kern { /* Most fields have already been cleared by our constructor. */ /* Initial processes may run on all cores. */ - m_core_mask = (1ul << cpu::NumCores) - 1; + m_core_mask = cpu::VirtualCoreMask; /* Initial processes may use any user priority they like. */ m_priority_mask = ~0xFul; @@ -55,18 +55,17 @@ namespace ams::kern { const auto max_prio = cap.Get(); const auto min_prio = cap.Get(); - R_UNLESS(min_core <= max_core, svc::ResultInvalidCombination()); - R_UNLESS(min_prio <= max_prio, svc::ResultInvalidCombination()); - R_UNLESS(max_core < cpu::NumCores, svc::ResultInvalidCoreId()); + R_UNLESS(min_core <= max_core, svc::ResultInvalidCombination()); + R_UNLESS(min_prio <= max_prio, svc::ResultInvalidCombination()); + R_UNLESS(max_core < cpu::NumVirtualCores, svc::ResultInvalidCoreId()); - MESOSPHERE_ASSERT(max_core < BITSIZEOF(u64)); MESOSPHERE_ASSERT(max_prio < BITSIZEOF(u64)); /* Set core mask. */ for (auto core_id = min_core; core_id <= max_core; core_id++) { m_core_mask |= (1ul << core_id); } - MESOSPHERE_ASSERT((m_core_mask & ((1ul << cpu::NumCores) - 1)) == m_core_mask); + MESOSPHERE_ASSERT((m_core_mask & cpu::VirtualCoreMask) == m_core_mask); /* Set priority mask. */ for (auto prio = min_prio; prio <= max_prio; prio++) { diff --git a/libraries/libmesosphere/source/svc/kern_svc_info.cpp b/libraries/libmesosphere/source/svc/kern_svc_info.cpp index fda723182..60a72fbc0 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_info.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_info.cpp @@ -216,7 +216,7 @@ namespace ams::kern::svc { case ams::svc::InfoType_ThreadTickCount: { /* Verify the requested core is valid. */ - const bool core_valid = (info_subtype == static_cast(-1ul)) || (info_subtype < util::size(cpu::VirtualToPhysicalCoreMap)); + const bool core_valid = (info_subtype == static_cast(-1ul)) || (info_subtype < cpu::NumVirtualCores); R_UNLESS(core_valid, svc::ResultInvalidCombination()); /* Get the thread from its handle. */ diff --git a/libraries/libmesosphere/source/svc/kern_svc_process.cpp b/libraries/libmesosphere/source/svc/kern_svc_process.cpp index 33c8d8926..7625cbf3b 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_process.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_process.cpp @@ -21,8 +21,8 @@ namespace ams::kern::svc { namespace { - constexpr bool IsValidCoreId(int32_t core_id) { - return (0 <= core_id && core_id < static_cast(cpu::NumCores)); + constexpr bool IsValidVirtualCoreId(int32_t core_id) { + return (0 <= core_id && core_id < static_cast(cpu::NumVirtualCores)); } void ExitProcess() { @@ -275,7 +275,7 @@ namespace ams::kern::svc { R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); /* Validate the core id. */ - R_UNLESS(IsValidCoreId(core_id), svc::ResultInvalidCoreId()); + R_UNLESS(IsValidVirtualCoreId(core_id), svc::ResultInvalidCoreId()); R_UNLESS(((1ul << core_id) & process->GetCoreMask()) != 0, svc::ResultInvalidCoreId()); /* Validate the priority. */ diff --git a/libraries/libmesosphere/source/svc/kern_svc_thread.cpp b/libraries/libmesosphere/source/svc/kern_svc_thread.cpp index 81a35a171..c887c5f56 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_thread.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_thread.cpp @@ -21,8 +21,8 @@ namespace ams::kern::svc { namespace { - constexpr bool IsValidCoreId(int32_t core_id) { - return (0 <= core_id && core_id < static_cast(cpu::NumCores)); + constexpr bool IsValidVirtualCoreId(int32_t core_id) { + return (0 <= core_id && core_id < static_cast(cpu::NumVirtualCores)); } Result CreateThread(ams::svc::Handle *out, ams::svc::ThreadFunc f, uintptr_t arg, uintptr_t stack_bottom, int32_t priority, int32_t core_id) { @@ -33,7 +33,7 @@ namespace ams::kern::svc { } /* Validate arguments. */ - R_UNLESS(IsValidCoreId(core_id), svc::ResultInvalidCoreId()); + R_UNLESS(IsValidVirtualCoreId(core_id), svc::ResultInvalidCoreId()); R_UNLESS(((1ul << core_id) & process.GetCoreMask()) != 0, svc::ResultInvalidCoreId()); R_UNLESS(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority, svc::ResultInvalidPriority()); @@ -168,7 +168,7 @@ namespace ams::kern::svc { R_UNLESS(affinity_mask != 0, svc::ResultInvalidCombination()); /* Validate the core id. */ - if (IsValidCoreId(core_id)) { + if (IsValidVirtualCoreId(core_id)) { R_UNLESS(((1ul << core_id) & affinity_mask) != 0, svc::ResultInvalidCombination()); } else { R_UNLESS(core_id == ams::svc::IdealCoreNoUpdate || core_id == ams::svc::IdealCoreDontCare, svc::ResultInvalidCoreId()); diff --git a/libraries/libstratosphere/include/stratosphere.hpp b/libraries/libstratosphere/include/stratosphere.hpp index f4653194f..afa38728c 100644 --- a/libraries/libstratosphere/include/stratosphere.hpp +++ b/libraries/libstratosphere/include/stratosphere.hpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,7 @@ #include #include #include +#include #include #include #include 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/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 303b390d8..872aa7623 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/nsd.hpp b/libraries/libstratosphere/include/stratosphere/nsd.hpp new file mode 100644 index 000000000..d0cfe3845 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/nsd.hpp @@ -0,0 +1,18 @@ +/* + * 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 diff --git a/libraries/libstratosphere/include/stratosphere/nsd/impl/device/nsd_device.hpp b/libraries/libstratosphere/include/stratosphere/nsd/impl/device/nsd_device.hpp new file mode 100644 index 000000000..38521e386 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/nsd/impl/device/nsd_device.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 + +namespace ams::nsd::impl::device { + + const EnvironmentIdentifier &GetEnvironmentIdentifierFromSettings(); + +} diff --git a/libraries/libstratosphere/include/stratosphere/nsd/nsd_types.hpp b/libraries/libstratosphere/include/stratosphere/nsd/nsd_types.hpp new file mode 100644 index 000000000..9f8ace67a --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/nsd/nsd_types.hpp @@ -0,0 +1,37 @@ +/* + * 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::nsd { + + struct EnvironmentIdentifier { + static constexpr size_t Size = 8; + char value[Size]; + + constexpr friend bool operator==(const EnvironmentIdentifier &lhs, const EnvironmentIdentifier &rhs) { + return util::Strncmp(lhs.value, rhs.value, Size) == 0; + } + + constexpr friend bool operator!=(const EnvironmentIdentifier &lhs, const EnvironmentIdentifier &rhs) { + return !(lhs == rhs); + } + }; + + constexpr inline const EnvironmentIdentifier EnvironmentIdentifierOfProductDevice = {"lp1"}; + constexpr inline const EnvironmentIdentifier EnvironmentIdentifierOfNotProductDevice = {"dd1"}; + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket.hpp b/libraries/libstratosphere/include/stratosphere/socket.hpp new file mode 100644 index 000000000..f42d359b9 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket.hpp @@ -0,0 +1,22 @@ +/* + * 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 +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp new file mode 100644 index 000000000..cc99f0aa8 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp @@ -0,0 +1,31 @@ +/* + * 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 +#include + +namespace ams::socket { + + Errno GetLastError(); + void SetLastError(Errno err); + + u32 InetHtonl(u32 host); + u16 InetHtons(u16 host); + u32 InetNtohl(u32 net); + u16 InetNtohs(u16 net); + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp new file mode 100644 index 000000000..faf8b2f9d --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_errno.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 + +namespace ams::socket { + + enum class Errno : u32 { + ESuccess = 0, + /* ... */ + ENoSpc = 28, + /* ... */ + }; + + enum class HErrno : s32 { + Netdb_Internal = -1, + Netdb_Success = 0, + Host_Not_Found = 1, + Try_Again = 2, + No_Recovery = 3, + No_Data = 4, + + No_Address = No_Data, + }; + + enum class AiErrno : u32 { + EAi_Success = 0, + /* ... */ + }; + + constexpr inline bool operator!(Errno e) { return e == Errno::ESuccess; } + constexpr inline bool operator!(HErrno e) { return e == HErrno::Netdb_Success; } + constexpr inline bool operator!(AiErrno e) { return e == AiErrno::EAi_Success; } + +} 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..c8bbb9ddf --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp @@ -0,0 +1,144 @@ +/* + * 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 = util::ConvertToBigEndian(InAddrT{(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 Protocol : s32 { + IpProto_Ip = 0, + IpProto_Icmp = 1, + + IpProto_Tcp = 6, + + IpProto_Udp = 17, + + IpProto_UdpLite = 136, + + IpProto_Raw = 255, + + IpProto_Max = 256, + }; + + enum class Type : u32 { + Sock_Default = 0, + Sock_Stream = 1, + Sock_Dgram = 2, + Sock_Raw = 3, + Sock_SeqPacket = 5, + + Sock_NonBlock = 0x20000000, + }; + + 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; + }; + + struct InAddr { + InAddrT s_addr; + }; + + enum class AddrInfoFlag : u32 { + Ai_None = (0 << 0), + Ai_Passive = (1 << 0), + Ai_CanonName = (1 << 1), + Ai_NumericHost = (1 << 2), + Ai_NumericServ = (1 << 3), + + Ai_AddrConfig = (1 << 10), + }; + + struct SockAddr { + u8 sa_len; + Family sa_family; + char sa_data[14]; + }; + + struct SockAddrIn { + u8 sin_len; + Family sin_family; + InPortT sin_port; + InAddr sin_addr; + u8 sin_zero[8]; + }; + static_assert(sizeof(SockAddr) == sizeof(SockAddrIn)); + + struct AddrInfo { + AddrInfoFlag ai_flags; + Family ai_family; + Type ai_socktype; + Protocol ai_protocol; + SockLenT ai_addrlen; + SockAddr *ai_addr; + char *ai_canonname; + AddrInfo *ai_next; + }; + + #define AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(__ENUM__) \ + constexpr inline __ENUM__ operator | (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast>(lhs) | static_cast>(rhs)); } \ + constexpr inline __ENUM__ operator |=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs | rhs; } \ + constexpr inline __ENUM__ operator & (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast>(lhs) & static_cast>(rhs)); } \ + constexpr inline __ENUM__ operator &=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs & rhs; } \ + constexpr inline __ENUM__ operator ^ (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast>(lhs) ^ static_cast>(rhs)); } \ + constexpr inline __ENUM__ operator ^=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs ^ rhs; } \ + constexpr inline __ENUM__ operator ~ (__ENUM__ e) { return static_cast<__ENUM__>(~static_cast>(e)); } + + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(Type) + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(AddrInfoFlag) + + #undef AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS + +} 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/libraries/libstratosphere/source/ams/ams_environment.cpp b/libraries/libstratosphere/source/ams/ams_environment.cpp index e18a8019b..a42776b23 100644 --- a/libraries/libstratosphere/source/ams/ams_environment.cpp +++ b/libraries/libstratosphere/source/ams/ams_environment.cpp @@ -73,11 +73,11 @@ namespace ams { ams_ctx.sp = ctx->sp.x; ams_ctx.pc = ctx->pc.x; ams_ctx.pstate = ctx->pstate; - ams_ctx.afsr0 = ctx->afsr0; - ams_ctx.afsr1 = (static_cast(::ams::exosphere::GetVersion(ATMOSPHERE_RELEASE_VERSION)) << 32) | static_cast(hos::GetVersion()); + ams_ctx.afsr0 = static_cast(::ams::exosphere::GetVersion(ATMOSPHERE_RELEASE_VERSION)); if (svc::IsKernelMesosphere()) { - ams_ctx.afsr1 |= (static_cast('M') << (BITSIZEOF(u64) - BITSIZEOF(u8))); + ams_ctx.afsr0 |= (static_cast('M') << (BITSIZEOF(u32) - BITSIZEOF(u8))); } + ams_ctx.afsr1 = static_cast(hos::GetVersion()); ams_ctx.far = ctx->far.x; ams_ctx.report_identifier = armGetSystemTick(); diff --git a/libraries/libstratosphere/source/hid/hid_api.cpp b/libraries/libstratosphere/source/hid/hid_api.cpp index ece8133f2..ba07cde57 100644 --- a/libraries/libstratosphere/source/hid/hid_api.cpp +++ b/libraries/libstratosphere/source/hid/hid_api.cpp @@ -40,14 +40,12 @@ namespace ams::hid { /* Helper. */ void InitializeHid() { - R_ABORT_UNLESS(smInitialize()); - ON_SCOPE_EXIT { smExit(); }; - { + sm::DoWithSession([&]() { R_ABORT_UNLESS(hidInitialize()); hidInitializeNpad(); R_ABORT_UNLESS(hidSetSupportedNpadIdType(NpadIdTypes, NumNpadIdTypes)); R_ABORT_UNLESS(hidSetSupportedNpadStyleSet(HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt)); - } + }); } Result EnsureHidInitialized() { diff --git a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp index 085e14fc7..0d44056e8 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp @@ -26,7 +26,7 @@ namespace ams::ncm { MakeContentPathFunction make_content_path_func; bool disabled; protected: - ContentStorageImplBase() { /* ... */ } + ContentStorageImplBase() : make_content_path_func(), disabled(false) { /* ... */ } protected: /* Helpers. */ Result EnsureEnabled() const { diff --git a/libraries/libstratosphere/source/nsd/impl/device/nsd_device.cpp b/libraries/libstratosphere/source/nsd/impl/device/nsd_device.cpp new file mode 100644 index 000000000..8d06b32b9 --- /dev/null +++ b/libraries/libstratosphere/source/nsd/impl/device/nsd_device.cpp @@ -0,0 +1,49 @@ +/* + * 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 + +namespace ams::nsd::impl::device { + + namespace { + + constexpr const char SettingsName[] = "nsd"; + constexpr const char SettingsKey[] = "environment_identifier"; + + constinit os::SdkMutex g_environment_identifier_lock; + constinit EnvironmentIdentifier g_environment_identifier = {}; + constinit bool g_determined_environment_identifier = false; + + } + + const EnvironmentIdentifier &GetEnvironmentIdentifierFromSettings() { + std::scoped_lock lk(g_environment_identifier_lock); + + if (!g_determined_environment_identifier) { + /* Check size. */ + AMS_ABORT_UNLESS(settings::fwdbg::GetSettingsItemValueSize(SettingsName, SettingsKey) != 0); + + /* Get value. */ + const size_t size = settings::fwdbg::GetSettingsItemValue(g_environment_identifier.value, EnvironmentIdentifier::Size, SettingsName, SettingsKey); + AMS_ABORT_UNLESS(size < EnvironmentIdentifier::Size); + AMS_ABORT_UNLESS(g_environment_identifier == EnvironmentIdentifierOfProductDevice || g_environment_identifier == EnvironmentIdentifierOfNotProductDevice); + + g_determined_environment_identifier = true; + } + + return g_environment_identifier; + } + +} diff --git a/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp b/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp new file mode 100644 index 000000000..d65838676 --- /dev/null +++ b/libraries/libstratosphere/source/socket/impl/socket_allocator.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::socket::impl { + + void *Alloc(size_t size); + void *Calloc(size_t num, size_t size); + void Free(void *ptr); + +} diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.hpp b/libraries/libstratosphere/source/socket/impl/socket_api.hpp new file mode 100644 index 000000000..292b4bdc9 --- /dev/null +++ b/libraries/libstratosphere/source/socket/impl/socket_api.hpp @@ -0,0 +1,28 @@ +/* + * 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 + +namespace ams::socket::impl { + + Errno GetLastError(); + void SetLastError(Errno err); + + u32 InetHtonl(u32 host); + u16 InetHtons(u16 host); + u32 InetNtohl(u32 net); + u16 InetNtohs(u16 net); + +} diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp new file mode 100644 index 000000000..ed4fa9873 --- /dev/null +++ b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp @@ -0,0 +1,83 @@ +/* + * 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 "socket_api.hpp" +#include "socket_allocator.hpp" + +namespace ams::socket::impl { + + void *Alloc(size_t size) { + /* TODO: expheap, heap generation. */ + return ams::Malloc(size); + } + + void *Calloc(size_t num, size_t size) { + if (void *ptr = Alloc(size * num); ptr != nullptr) { + std::memset(ptr, 0, size * num); + return ptr; + } else { + return nullptr; + } + } + + void Free(void *ptr) { + /* TODO: expheap, heap generation. */ + return ams::Free(ptr); + } + + Errno GetLastError() { + /* TODO: check that client library is initialized. */ + return static_cast(errno); + } + + void SetLastError(Errno err) { + /* TODO: check that client library is initialized. */ + errno = static_cast(err); + } + + u32 InetHtonl(u32 host) { + if constexpr (util::IsBigEndian()) { + return host; + } else { + return util::SwapBytes(host); + } + } + + u16 InetHtons(u16 host) { + if constexpr (util::IsBigEndian()) { + return host; + } else { + return util::SwapBytes(host); + } + } + + u32 InetNtohl(u32 net) { + if constexpr (util::IsBigEndian()) { + return net; + } else { + return util::SwapBytes(net); + } + } + + u16 InetNtohs(u16 net) { + if constexpr (util::IsBigEndian()) { + return net; + } else { + return util::SwapBytes(net); + } + } + +} diff --git a/libraries/libstratosphere/source/socket/socket_api.cpp b/libraries/libstratosphere/source/socket/socket_api.cpp new file mode 100644 index 000000000..f1171c2c3 --- /dev/null +++ b/libraries/libstratosphere/source/socket/socket_api.cpp @@ -0,0 +1,45 @@ +/* + * 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 "impl/socket_api.hpp" + +namespace ams::socket { + + Errno GetLastError() { + return impl::GetLastError(); + } + + void SetLastError(Errno err) { + return impl::SetLastError(err); + } + + u32 InetHtonl(u32 host) { + return impl::InetHtonl(host); + } + + u16 InetHtons(u16 host) { + return impl::InetHtons(host); + } + + u32 InetNtohl(u32 net) { + return impl::InetNtohl(net); + } + + u16 InetNtohs(u16 net) { + return impl::InetNtohs(net); + } + +} diff --git a/libraries/libvapours/include/vapours/ams/ams_api_version.h b/libraries/libvapours/include/vapours/ams/ams_api_version.h index 914853687..73750f8fa 100644 --- a/libraries/libvapours/include/vapours/ams/ams_api_version.h +++ b/libraries/libvapours/include/vapours/ams/ams_api_version.h @@ -16,8 +16,8 @@ #pragma once #define ATMOSPHERE_RELEASE_VERSION_MAJOR 0 -#define ATMOSPHERE_RELEASE_VERSION_MINOR 17 -#define ATMOSPHERE_RELEASE_VERSION_MICRO 1 +#define ATMOSPHERE_RELEASE_VERSION_MINOR 18 +#define ATMOSPHERE_RELEASE_VERSION_MICRO 0 #define ATMOSPHERE_RELEASE_VERSION ATMOSPHERE_RELEASE_VERSION_MAJOR, ATMOSPHERE_RELEASE_VERSION_MINOR, ATMOSPHERE_RELEASE_VERSION_MICRO diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index 70771d657..f0a9250e1 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -46,6 +46,9 @@ #include #include +#include +#include + #ifdef ATMOSPHERE_IS_STRATOSPHERE #include #endif diff --git a/libraries/libvapours/include/vapours/util/impl/util_available_index_finder.hpp b/libraries/libvapours/include/vapours/util/impl/util_available_index_finder.hpp new file mode 100644 index 000000000..8bc7967b2 --- /dev/null +++ b/libraries/libvapours/include/vapours/util/impl/util_available_index_finder.hpp @@ -0,0 +1,220 @@ +/* + * 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 +#include + +namespace ams::util::impl { + + class AvailableIndexFinder { + private: + static constexpr int MaxDepthOfBox = 5; + + struct BitFlags64 { + private: + u64 m_data; + public: + constexpr ALWAYS_INLINE bool GetFlag(int index) const { + AMS_ASSERT(index < 64); + return ((m_data >> index) & UINT64_C(1)) != 0; + } + + constexpr ALWAYS_INLINE void SetFlag(int index) { + AMS_ASSERT(index < 64); + m_data |= (UINT64_C(1) << index); + } + + constexpr ALWAYS_INLINE void ClearFlag(int index) { + AMS_ASSERT(index < 64); + m_data &= ~(UINT64_C(1) << index); + } + + constexpr ALWAYS_INLINE bool IsFull() const { + return m_data == ~(UINT64_C(0)); + } + + constexpr ALWAYS_INLINE int FindIndexOfBitZero() const { + AMS_ASSERT(!this->IsFull()); + return __builtin_ctzll(~m_data); + } + }; + private: + int *m_p_current_index; + int *m_p_map_index; + void *m_buffer; + int m_depth; + BitFlags64 *m_flags; + private: + static constexpr int Pow64(int e) { + switch (e) { + case 0: return 0x1; + case 1: return 0x40; + case 2: return 0x1000; + case 3: return 0x40000; + case 4: return 0x1000000; + case 5: return 0x40000000; + default: return -1; + } + } + + static constexpr u64 Roundup64(u64 value) { + return (value + (64 - 1)) / 64; + } + + static constexpr size_t GetRequiredNumOfBox(int depth, size_t num_elements) { + if (depth == 1) { + return Roundup64(num_elements); + } else if (depth == 2) { + return Roundup64(num_elements) + Pow64(0); + } else if (depth == 3) { + return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2); + } else if (depth == 4) { + return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2) + Pow64(3); + } else if (depth == 5) { + return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2) + Pow64(3) + Pow64(4); + } else { + return 0; + } + } + + static constexpr int CalcOffset(int *arr, int depth) { + int offset = 0; + for (auto i = 0; i < depth; ++i) { + offset += Pow64(i); + } + for (auto i = 0; i < depth; ++i) { + offset += Pow64(i) - arr[depth - 1 - i]; + } + return offset; + } + public: + static consteval int GetSignature() { + return static_cast(util::ReverseFourCC<'B', 'I', 'T', 'S'>::Code); + } + + static constexpr int GetNeedDepth(size_t num_elements) { + if (num_elements <= 0x40) { + return 1; + } else if (num_elements <= 0x1000) { + return 2; + } else if (num_elements <= 0x40000) { + return 3; + } else if (num_elements <= 0x1000000) { + return 4; + } else if (num_elements <= 0x40000000) { + return 5; + } else { + return -1; + } + } + + static constexpr size_t GetRequiredMemorySize(size_t num_elements) { + return sizeof(BitFlags64) * GetRequiredNumOfBox(GetNeedDepth(num_elements), num_elements); + } + public: + void Initialize(int *cur, int *map, u8 *buf) { + const size_t num_elements = static_cast(*map); + AMS_ASSERT(num_elements <= static_cast(Pow64(MaxDepthOfBox - 1))); + + /* Set fields. */ + m_p_current_index = cur; + m_p_map_index = map; + m_buffer = buf; + m_depth = GetNeedDepth(num_elements); + + /* Validate fields. */ + AMS_ASSERT(m_depth > 0); + + /* Setup memory. */ + std::memset(m_buffer, 0, GetRequiredMemorySize(num_elements)); + m_flags = reinterpret_cast(m_buffer); + } + + int AcquireIndex() { + /* Validate pre-conditions. */ + AMS_ASSERT(*m_p_current_index < *m_p_map_index); + + /* Build up arrays. */ + int table[MaxDepthOfBox]; + BitFlags64 *pos[MaxDepthOfBox]; + for (auto i = 0; i < m_depth; ++i) { + /* Determine the position. */ + pos[i] = std::addressof(m_flags[CalcOffset(table, i)]); + + /* Set table entry. */ + table[i] = pos[i]->FindIndexOfBitZero(); + AMS_ASSERT(table[i] != BITSIZEOF(u64)); + } + + /* Validate that the index is not acquired. */ + AMS_ASSERT(!pos[m_depth - 1]->GetFlag(table[m_depth - 1])); + + /* Acquire the index. */ + pos[m_depth - 1]->SetFlag(table[m_depth - 1]); + + /* Validate that the index was acquired. */ + AMS_ASSERT(pos[m_depth - 1]->GetFlag(table[m_depth - 1])); + + /* Update tracking flags. */ + for (auto i = m_depth - 1; i > 0; --i) { + if (pos[i]->IsFull()) { + pos[i - 1]->SetFlag(table[i - 1]); + } + } + + /* Calculate the index we acquired. */ + int index = 0, pow = 0; + for (auto i = m_depth; i > 0; --i, ++pow) { + index += Pow64(pow) * table[i - 1]; + } + + /* Increment current index. */ + ++(*m_p_current_index); + + return index; + } + + void ReleaseIndex(int index) { + /* Convert index to table. */ + int table[MaxDepthOfBox]; + for (auto i = 0; i < m_depth; ++i) { + table[m_depth - 1 - i] = index % BITSIZEOF(u64); + index /= BITSIZEOF(u64); + } + + /* Build up arrays. */ + BitFlags64 *pos[MaxDepthOfBox]; + for (auto i = 0; i < m_depth; ++i) { + /* Determine the position. */ + pos[i] = std::addressof(m_flags[CalcOffset(table, i)]); + } + + /* Validate that the flag is set. */ + AMS_ASSERT(pos[m_depth - 1]->GetFlag(table[m_depth - 1])); + + /* Clear the flags. */ + for (auto i = m_depth - 1; i >= 0; --i) { + pos[i]->ClearFlag(table[i]); + } + + /* Decrement current index. */ + --(*m_p_current_index); + } + }; + +} diff --git a/libraries/libvapours/include/vapours/util/util_fixed_map.hpp b/libraries/libvapours/include/vapours/util/util_fixed_map.hpp new file mode 100644 index 000000000..1e8058223 --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_fixed_map.hpp @@ -0,0 +1,69 @@ +/* + * 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 +#include + +namespace ams::util { + + template, size_t BufferAlignment = 8> + class FixedMap { + private: + using KeyValuePair = std::pair; + + struct LessTypeForMap { + constexpr ALWAYS_INLINE bool operator()(const KeyValuePair &lhs, const KeyValuePair &rhs) const { + return Compare{}(lhs.first, rhs.first); + } + }; + + using TreeType = ::ams::util::FixedTree; + + using iterator = typename TreeType::iterator; + using const_iterator = typename TreeType::const_iterator; + public: + static constexpr size_t GetRequiredMemorySize(size_t num_elements) { + return TreeType::GetRequiredMemorySize(num_elements); + } + private: + TreeType m_tree; + public: + FixedMap() : m_tree() { /* ... */ } + + void Initialize(size_t num_elements, void *buffer, size_t buffer_size) { + return m_tree.Initialize(num_elements, buffer, buffer_size); + } + + ALWAYS_INLINE iterator begin() { return m_tree.begin(); } + ALWAYS_INLINE const_iterator begin() const { return m_tree.begin(); } + + ALWAYS_INLINE iterator end() { return m_tree.end(); } + ALWAYS_INLINE const_iterator end() const { return m_tree.end(); } + + ALWAYS_INLINE bool erase(const Key &key) { const KeyValuePair pair(key, Value{}); return m_tree.erase(pair); } + + ALWAYS_INLINE iterator find(const Key &key) { const KeyValuePair pair(key, Value{}); return m_tree.find(pair); } + ALWAYS_INLINE const_iterator find(const Key &key) const { const KeyValuePair pair(key, Value{}); return m_tree.find(pair); } + + ALWAYS_INLINE std::pair insert(const KeyValuePair &pair) { return m_tree.insert(pair); } + + ALWAYS_INLINE size_t size() const { return m_tree.size(); } + + ALWAYS_INLINE void clear() { return m_tree.clear(); } + }; + +} diff --git a/libraries/libvapours/include/vapours/util/util_fixed_set.hpp b/libraries/libvapours/include/vapours/util/util_fixed_set.hpp new file mode 100644 index 000000000..f5436a2a8 --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_fixed_set.hpp @@ -0,0 +1,28 @@ +/* + * 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 +#include + +namespace ams::util { + + template, size_t BufferAlignment = 8> + class FixedSet : public ::ams::util::FixedTree { + /* ... */ + }; + +} diff --git a/libraries/libvapours/include/vapours/util/util_fixed_tree.hpp b/libraries/libvapours/include/vapours/util/util_fixed_tree.hpp new file mode 100644 index 000000000..b0f348ba7 --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_fixed_tree.hpp @@ -0,0 +1,998 @@ +/* + * 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 +#include +#include + +namespace ams::util { + + template requires std::convertible_to + class FixedTree { + private: + class IteratorBase; + friend class IteratorBase; + private: + enum class Color : u8 { + Red = 0, + Black = 1, + }; + + static constexpr inline int Index_Nil = -1; + static constexpr inline int Index_Leaf = -2; + static constexpr inline int Index_BeforeBegin = -3; + static constexpr inline int Index_AfterEnd = -4; + + static constexpr inline size_t max_size = 0x40000000; + + struct Header { + /* "Nintendo Red-Black tree" */ + static constexpr u32 Signature = util::ReverseFourCC<'N','N','R','B'>::Code; + + u32 header_size; + u32 header_signature; + u32 _08; + s32 max_elements; + s32 cur_elements; + s32 root_index; + s32 left_most_index; + s32 right_most_index; + s32 index_signature; + u32 buffer_size; + u32 node_size; + u32 element_size; + u32 _30; + u32 _34; + u32 _38; + u32 _3C; + u32 _40; + u32 _44; + u32 _48; + u32 _4C; + + void InitializeHeader(u32 _08, s32 max_e, s32 cur_e, u32 ind_sig, u32 buf_sz, u32 node_sz, u32 e_sz, u32 _30, u32 _34, u32 _38, u32 _3C, u32 _40, u32 _44) { + this->header_size = sizeof(Header); + this->header_signature = Signature; + this->_08 = _08; + this->max_elements = max_e; + this->cur_elements = cur_e; + this->root_index = Index_Nil; + this->left_most_index = Index_Nil; + this->right_most_index = Index_Nil; + this->index_signature = ind_sig; + this->buffer_size = buf_sz; + this->node_size = node_sz; + this->element_size = e_sz; + this->_30 = _30; + this->_34 = _34; + this->_38 = _38; + this->_3C = _3C; + this->_40 = _40; + this->_44 = _44; + this->_48 = 0; + this->_4C = 0; + } + }; + static_assert(sizeof(Header) == 0x50); + + struct IndexPair { + int first; + int last; + }; + + struct Node { + Member m_data; + int m_parent; + int m_right; + int m_left; + Color m_color; + + void SetLeft(int l, Node *n, int p) { + m_left = l; + n->m_parent = p; + } + + void SetRight(int r, Node *n, int p) { + m_right = r; + n->m_parent = p; + } + }; + + class Iterator; + class ConstIterator; + + class IteratorBase { + private: + friend class ConstIterator; + private: + const FixedTree *m_this; + int m_index; + protected: + constexpr ALWAYS_INLINE IteratorBase(const FixedTree *tree, int index) : m_this(tree), m_index(index) { /* ... */ } + + constexpr bool IsEqualImpl(const IteratorBase &rhs) const { + /* Validate pre-conditions. */ + AMS_ASSERT(m_this); + + /* Check for tree equality. */ + if (m_this != rhs.m_this) { + return false; + } + + /* Check for nil. */ + if (m_this->IsNil(m_index) && m_this->IsNil(rhs.m_index)) { + return true; + } + + /* Check for index equality. */ + return m_index == rhs.m_index; + } + + constexpr IteratorMember &DereferenceImpl() const { + /* Validate pre-conditions. */ + AMS_ASSERT(m_this); + + if (!m_this->IsNil(m_index)) { + return m_this->m_nodes[m_index].m_data; + } else { + AMS_ASSERT(false); + return m_this->GetNode(std::numeric_limits::max())->m_data; + } + } + + constexpr ALWAYS_INLINE IteratorBase &IncrementImpl() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_this); + + this->OperateIndex(true); + return *this; + } + + constexpr ALWAYS_INLINE IteratorBase &DecrementImpl() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_this); + + this->OperateIndex(false); + return *this; + } + + constexpr void OperateIndex(bool increment) { + if (increment) { + /* We're incrementing. */ + if (m_index == Index_BeforeBegin) { + m_index = 0; + } else { + m_index = m_this->UncheckedPP(m_index); + if (m_this->IsNil(m_index)) { + m_index = Index_AfterEnd; + } + } + } else { + /* We're decrementing. */ + if (m_index == Index_AfterEnd) { + m_index = static_cast(m_this->size()) - 1; + } else { + m_index = m_this->UncheckedMM(m_index); + if (m_this->IsNil(m_index)) { + m_index = Index_BeforeBegin; + } + } + } + } + }; + + class Iterator : public IteratorBase { + public: + constexpr ALWAYS_INLINE Iterator(const FixedTree &tree) : IteratorBase(std::addressof(tree), tree.size() ? tree.GetLMost() : Index_Leaf) { /* ... */ } + constexpr ALWAYS_INLINE Iterator(const FixedTree &tree, int index) : IteratorBase(std::addressof(tree), index) { /* ... */ } + + constexpr ALWAYS_INLINE Iterator(const Iterator &rhs) = default; + + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { + return this->IsEqualImpl(rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const Iterator &rhs) const { + return !(*this == rhs); + } + + constexpr ALWAYS_INLINE IteratorMember &operator*() const { + return static_cast(this->DereferenceImpl()); + } + + constexpr ALWAYS_INLINE IteratorMember *operator->() const { + return std::addressof(this->operator *()); + } + + constexpr ALWAYS_INLINE Iterator &operator++() { + return static_cast(this->IncrementImpl()); + } + + constexpr ALWAYS_INLINE Iterator &operator--() { + return static_cast(this->DecrementImpl()); + } + }; + + class ConstIterator : public IteratorBase { + public: + constexpr ALWAYS_INLINE ConstIterator(const FixedTree &tree) : IteratorBase(std::addressof(tree), tree.size() ? tree.GetLMost() : Index_Leaf) { /* ... */ } + constexpr ALWAYS_INLINE ConstIterator(const FixedTree &tree, int index) : IteratorBase(std::addressof(tree), index) { /* ... */ } + + constexpr ALWAYS_INLINE ConstIterator(const ConstIterator &rhs) = default; + constexpr ALWAYS_INLINE ConstIterator(const Iterator &rhs) : IteratorBase(rhs.m_this, rhs.m_index) { /* ... */ } + + constexpr ALWAYS_INLINE bool operator==(const ConstIterator &rhs) const { + return this->IsEqualImpl(rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const ConstIterator &rhs) const { + return !(*this == rhs); + } + + constexpr ALWAYS_INLINE const IteratorMember &operator*() const { + return static_cast(this->DereferenceImpl()); + } + + constexpr ALWAYS_INLINE const IteratorMember *operator->() const { + return std::addressof(this->operator *()); + } + + constexpr ALWAYS_INLINE ConstIterator &operator++() { + return static_cast(this->IncrementImpl()); + } + + constexpr ALWAYS_INLINE ConstIterator &operator--() { + return static_cast(this->DecrementImpl()); + } + }; + public: + using iterator = Iterator; + using const_iterator = ConstIterator; + private: + impl::AvailableIndexFinder m_index_finder; + Node m_dummy_leaf; + Node *m_p_dummy_leaf; + u8 *m_buffer; + Header *m_header; + Node *m_nodes; + iterator m_end_iterator; + public: + FixedTree() : m_end_iterator(*this, Index_Nil) { + this->SetDummyMemory(); + } + protected: + void InitializeImpl(int num_elements, void *buffer, size_t buffer_size) { + /* Check pre-conditions. */ + AMS_ASSERT(num_elements > 0); + AMS_ASSERT(static_cast(num_elements) <= max_size); + AMS_ASSERT(util::IsAligned(reinterpret_cast(buffer), BufferAlignment)); + AMS_ASSERT(buffer_size == GetRequiredMemorySize(num_elements)); + + /* Set buffer. */ + m_buffer = static_cast(buffer); + m_header = reinterpret_cast
(m_buffer); + + /* Setup memory. */ + this->InitializeMemory(num_elements, buffer_size, impl::AvailableIndexFinder::GetSignature()); + + /* Check that buffer was set up correctly. */ + AMS_ASSERT(static_cast(buffer_size) == m_header->buffer_size); + + /* Setup dummy leaf. */ + this->SetDummyMemory(); + } + public: + static constexpr size_t SizeOfNodes(size_t num_elements) { + return util::AlignUp(sizeof(Node) * num_elements, BufferAlignment); + } + + static constexpr size_t SizeOfIndex(size_t num_elements) { + return impl::AvailableIndexFinder::GetRequiredMemorySize(num_elements); + } + + static constexpr size_t GetRequiredMemorySize(size_t num_elements) { + return sizeof(Header) + SizeOfNodes(num_elements) + SizeOfIndex(num_elements); + } + private: + void SetDummyMemory() { + m_dummy_leaf.m_color = Color::Black; + m_dummy_leaf.m_parent = Index_Nil; + m_dummy_leaf.m_left = Index_Leaf; + m_dummy_leaf.m_right = Index_Leaf; + m_p_dummy_leaf = std::addressof(m_dummy_leaf); + } + + void InitializeMemory(int num_elements, u32 buffer_size, u32 signature) { + /* Initialize the header. */ + m_header->InitializeHeader(1, num_elements, 0, signature, buffer_size, sizeof(Node), sizeof(Member), 4, 4, 4, 4, 4, BufferAlignment); + + /* Setup index finder. */ + m_index_finder.Initialize(std::addressof(m_header->cur_elements), std::addressof(m_header->max_elements), m_buffer + sizeof(*m_header) + SizeOfNodes(num_elements)); + + /* Set nodes array. */ + m_nodes = reinterpret_cast(m_buffer + sizeof(*m_header)); + } + + Node *GetNode(int index) const { + if (index >= 0) { + return m_nodes + index; + } else { + return m_p_dummy_leaf; + } + } + + constexpr ALWAYS_INLINE bool IsNil(int index) const { + return index < 0; + } + + constexpr ALWAYS_INLINE bool IsLeaf(int index) const { + return index == Index_Leaf; + } + + int GetRoot() const { return m_header->root_index; } + void SetRoot(int index) { + if (index == Index_Leaf) { + index = Index_Nil; + } + + m_header->root_index = index; + } + + int GetLMost() const { return m_header->left_most_index; } + void SetLMost(int index) { m_header->left_most_index = index; } + + int GetRMost() const { return m_header->right_most_index; } + void SetRMost(int index) { m_header->right_most_index = index; } + + int GetParent(int index) const { + return this->GetNode(index)->m_parent; + } + + int AcquireIndex() { return m_index_finder.AcquireIndex(); } + void ReleaseIndex(int index) { return m_index_finder.ReleaseIndex(index); } + + int EraseByIndex(int target_index) { + /* Setup tracking variables. */ + const auto next_index = this->UncheckedPP(target_index); + auto *target_node = this->GetNode(target_index); + + auto a_index = Index_Leaf; + auto *a_node = this->GetNode(a_index); + auto b_index = Index_Leaf; + auto *b_node = this->GetNode(b_index); + auto cur_index = target_index; + auto *cur_node = this->GetNode(cur_index); + + if (cur_node->m_left == Index_Leaf) { + a_index = cur_node->m_right; + a_node = this->GetNode(a_index); + + m_p_dummy_leaf->m_parent = cur_index; + } else { + if (cur_node->m_right == Index_Leaf) { + a_index = cur_node->m_left; + } else { + cur_index = next_index; + cur_node = this->GetNode(cur_index); + a_index = cur_node->m_right; + } + a_node = this->GetNode(a_index); + + m_p_dummy_leaf->m_parent = cur_index; + } + + /* Ensure the a node is updated (redundant) */ + a_node = this->GetNode(a_index); + + /* Update relevant metrics/links. */ + if (cur_index == target_index) { + /* No left, but has right. */ + b_index = target_node->m_parent; + b_node = this->GetNode(b_index); + + if (a_index != Index_Leaf) { + a_node->m_parent = b_index; + } + + if (this->GetRoot() == target_index) { + this->SetRoot(a_index); + } else if (b_node->m_left == target_index) { + b_node->m_left = a_index; + } else { + b_node->m_right = a_index; + } + + if (this->GetLMost() == target_index) { + this->SetLMost((a_index != Index_Leaf) ? this->FindMinInSubtree(a_index) : b_index); + } + + if (this->GetRMost() == target_index) { + this->SetRMost((a_index != Index_Leaf) ? this->FindMaxInSubtree(a_index) : b_index); + } + } else { + /* Has left or doesn't have right. */ + + /* Fix left links. */ + this->GetNode(target_node->m_left)->m_parent = cur_index; + cur_node->m_left = target_node->m_left; + + if (cur_index == target_node->m_right) { + b_index = cur_index; + b_node = this->GetNode(b_index); + } else { + b_index = cur_node->m_parent; + b_node = this->GetNode(b_index); + + if (!this->IsNil(a_index)) { + a_node->m_parent = b_index; + } + + b_node->m_left = a_index; + cur_node->m_right = target_node->m_right; + + this->GetNode(target_node->m_right)->m_parent = cur_index; + } + + if (this->GetRoot() == target_index) { + this->SetRoot(cur_index); + } else { + if (this->GetNode(target_node->m_parent)->m_left == target_index) { + this->GetNode(target_node->m_parent)->m_left = cur_index; + } else { + this->GetNode(target_node->m_parent)->m_right = cur_index; + } + } + + cur_node->m_parent = target_node->m_parent; + std::swap(cur_node->m_color, target_node->m_color); + } + + /* Ensure the tree remains balanced. */ + if (target_node->m_color == Color::Black) { + while (true) { + if (a_index == this->GetRoot() || a_node->m_color != Color::Black) { + break; + } + + if (a_index == b_node->m_left) { + cur_index = b_node->m_right; + cur_node = this->GetNode(cur_index); + + if (cur_node->m_color == Color::Red) { + cur_node->m_color = Color::Black; + b_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateLeft(b_index); + + cur_index = b_node->m_right; + cur_node = this->GetNode(cur_index); + } + + if (this->IsNil(cur_index)) { + a_index = b_index; + a_node = b_node; + } else { + if (this->GetNode(cur_node->m_left)->m_color != Color::Black || this->GetNode(cur_node->m_right)->m_color != Color::Black) { + if (this->GetNode(cur_node->m_right)->m_color == Color::Black) { + this->GetNode(cur_node->m_left)->m_color = Color::Black; + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateRight(cur_index); + + cur_index = b_node->m_right; + cur_node = this->GetNode(cur_index); + } + + cur_node->m_color = b_node->m_color; + b_node->m_color = Color::Black; + + this->GetNode(cur_node->m_right)->m_color = Color::Black; + + this->RotateLeft(b_index); + + break; + } + + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + a_index = b_index; + a_node = b_node; + } + } else { + cur_index = b_node->m_left; + cur_node = this->GetNode(cur_index); + + if (cur_node->m_color == Color::Red) { + cur_node->m_color = Color::Black; + b_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateRight(b_index); + + cur_index = b_node->m_left; + cur_node = this->GetNode(cur_index); + } + + if (this->IsNil(cur_index)) { + a_index = b_index; + a_node = b_node; + } else { + if (this->GetNode(cur_node->m_right)->m_color != Color::Black || this->GetNode(cur_node->m_left)->m_color != Color::Black) { + if (this->GetNode(cur_node->m_left)->m_color == Color::Black) { + this->GetNode(cur_node->m_right)->m_color = Color::Black; + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateLeft(cur_index); + + cur_index = b_node->m_left; + cur_node = this->GetNode(cur_index); + } + + cur_node->m_color = b_node->m_color; + b_node->m_color = Color::Black; + + this->GetNode(cur_node->m_left)->m_color = Color::Black; + + this->RotateRight(b_index); + + break; + } + + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + a_index = b_index; + a_node = b_node; + } + } + + b_index = a_node->m_parent; + b_node = this->GetNode(b_index); + } + + a_node->m_color = Color::Black; + } + + /* Release the index. */ + this->ReleaseIndex(target_index); + return target_index; + } + + int FindIndex(const Member &elem) const { + return this->FindIndexSub(this->GetRoot(), elem); + } + + int FindIndexSub(int index, const Member &elem) const { + if (index != Index_Nil) { + auto *node = this->GetNode(index); + if (Compare{}(elem, node->m_data)) { + if (!this->IsLeaf(node->m_left)) { + return this->FindIndexSub(node->m_left, elem); + } + } else { + if (!Compare{}(node->m_data, elem)) { + return index; + } + + if (!this->IsLeaf(node->m_right)) { + return this->FindIndexSub(node->m_right, elem); + } + } + } + + return Index_Nil; + } + + int FindMaxInSubtree(int index) const { + int max = index; + for (auto *node = this->GetNode(index); !this->IsNil(node->m_right); node = this->GetNode(node->m_right)) { + max = node->m_right; + } + return max; + } + + int FindMinInSubtree(int index) const { + int min = index; + for (auto *node = this->GetNode(index); !this->IsNil(node->m_left); node = this->GetNode(node->m_left)) { + min = node->m_left; + } + return min; + } + + int InsertAt(bool before, int parent, const Member &elem) { + /* Get an index for the new element. */ + const auto index = this->AcquireIndex(); + + /* Create the node. */ + auto *node = this->GetNode(index); + node->m_color = Color::Red; + node->m_parent = parent; + node->m_right = Index_Leaf; + node->m_left = Index_Leaf; + std::memcpy(reinterpret_cast(std::addressof(node->m_data)), reinterpret_cast(std::addressof(elem)), sizeof(node->m_data)); + + /* Fix up the parent node. */ + auto *parent_node = this->GetNode(parent); + if (before) { + parent_node->m_left = index; + if (parent == this->GetLMost()) { + this->SetLMost(index); + } + } else { + parent_node->m_right = index; + if (parent == this->GetRMost()) { + this->SetRMost(index); + } + } + + /* Ensure the tree is balanced. */ + int cur_index = index; + while (true) { + auto *cur_node = this->GetNode(cur_index); + if (this->GetNode(cur_node->m_parent)->m_color != Color::Red) { + break; + } + + auto *p_node = this->GetNode(cur_node->m_parent); + auto *g_node = this->GetNode(p_node->m_parent); + if (cur_node->m_parent == g_node->m_left) { + if (auto *gr_node = this->GetNode(g_node->m_right); gr_node->m_color == Color::Red) { + p_node->m_color = Color::Black; + gr_node->m_color = Color::Black; + g_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + cur_index = p_node->m_parent; + continue; + } + + if (cur_index == p_node->m_right) { + cur_index = cur_node->m_parent; + cur_node = this->GetNode(cur_index); + this->RotateLeft(cur_index); + } + + p_node = this->GetNode(cur_node->m_parent); + p_node->m_color = Color::Black; + + g_node = this->GetNode(p_node->m_parent); + g_node->m_color = Color::Red; + + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + this->RotateRight(p_node->m_parent); + } else { + if (auto *gl_node = this->GetNode(g_node->m_left); gl_node->m_color == Color::Red) { + p_node->m_color = Color::Black; + gl_node->m_color = Color::Black; + g_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + cur_index = p_node->m_parent; + continue; + } + + if (cur_index == p_node->m_left) { + cur_index = cur_node->m_parent; + cur_node = this->GetNode(cur_index); + this->RotateRight(cur_index); + } + + p_node = this->GetNode(cur_node->m_parent); + p_node->m_color = Color::Black; + + g_node = this->GetNode(p_node->m_parent); + g_node->m_color = Color::Red; + + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + this->RotateLeft(p_node->m_parent); + } + } + + /* Set root color. */ + this->GetNode(this->GetRoot())->m_color = Color::Black; + + return index; + } + + int InsertNoHint(bool before, const Member &elem) { + int cur_index = this->GetRoot(); + int prev_index = Index_Nil; + bool less = true; + while (cur_index != Index_Nil && cur_index != Index_Leaf) { + auto *node = this->GetNode(cur_index); + prev_index = cur_index; + + if (before) { + less = Compare{}(node->m_data, elem); + } else { + less = Compare{}(elem, node->m_data); + } + + if (less) { + cur_index = node->m_left; + } else { + cur_index = node->m_right; + } + } + + if (cur_index == Index_Nil) { + /* Create a new node. */ + const auto index = this->AcquireIndex(); + auto *node = this->GetNode(index); + node->m_color = Color::Black; + node->m_parent = Index_Nil; + node->m_right = Index_Leaf; + node->m_left = Index_Leaf; + std::memcpy(reinterpret_cast(std::addressof(node->m_data)), reinterpret_cast(std::addressof(elem)), sizeof(node->m_data)); + + this->SetRoot(index); + this->SetLMost(index); + this->SetRMost(index); + + return index; + } else { + auto *compare_node = this->GetNode(prev_index); + if (less) { + if (prev_index == this->GetLMost()) { + return this->InsertAt(less, prev_index, elem); + } else { + compare_node = this->GetNode(this->UncheckedMM(prev_index)); + } + } + + if (Compare{}(compare_node->m_data, elem)) { + return this->InsertAt(less, prev_index, elem); + } else { + return Index_Nil; + } + } + + } + + void RotateLeft(int index) { + /* Determine indices. */ + const auto p_index = this->GetParent(index); + const auto r_index = this->GetNode(index)->m_right; + const auto l_index = this->GetNode(index)->m_left; + const auto rl_index = this->GetNode(r_index)->m_left; + const auto rr_index = this->GetNode(r_index)->m_right; + + /* Get nodes. */ + auto *node = this->GetNode(index); + auto *p_node = this->GetNode(p_index); + auto *r_node = this->GetNode(r_index); + auto *l_node = this->GetNode(l_index); + auto *rl_node = this->GetNode(rl_index); + auto *rr_node = this->GetNode(rr_index); + + /* Perform the rotation. */ + if (p_index == Index_Nil) { + r_node->m_parent = Index_Nil; + m_header->root_index = r_index; + } else if (p_node->m_left == index) { + p_node->SetLeft(r_index, r_node, p_index); + } else { + p_node->SetRight(r_index, r_node, p_index); + } + r_node->SetLeft(index, node, r_index); + r_node->SetRight(rr_index, rr_node, r_index); + node->SetLeft(l_index, l_node, index); + node->SetRight(rl_index, rl_node, index); + } + + void RotateRight(int index) { + /* Determine indices. */ + const auto p_index = this->GetParent(index); + const auto l_index = this->GetNode(index)->m_left; + const auto ll_index = this->GetNode(l_index)->m_left; + const auto lr_index = this->GetNode(l_index)->m_right; + const auto r_index = this->GetNode(index)->m_right; + + /* Get nodes. */ + auto *node = this->GetNode(index); + auto *p_node = this->GetNode(p_index); + auto *l_node = this->GetNode(l_index); + auto *ll_node = this->GetNode(ll_index); + auto *lr_node = this->GetNode(lr_index); + auto *r_node = this->GetNode(r_index); + + /* Perform the rotation. */ + if (p_index == Index_Nil) { + l_node->m_parent = Index_Nil; + m_header->root_index = l_index; + } else if (p_node->m_left == index) { + p_node->SetLeft(l_index, l_node, p_index); + } else { + p_node->SetRight(l_index, l_node, p_index); + } + l_node->SetLeft(ll_index, ll_node, l_index); + l_node->SetRight(index, node, l_index); + node->SetLeft(lr_index, lr_node, index); + node->SetRight(r_index, r_node, index); + } + + int UncheckedMM(int index) const { + auto *node = this->GetNode(index); + if (this->IsNil(index)) { + index = this->GetRMost(); + node = this->GetNode(index); + } else if (this->IsNil(node->m_left)) { + int parent = node->m_parent; + Node *p; + + for (p = this->GetNode(parent); !this->IsNil(parent) && index == p->m_left; p = this->GetNode(parent)) { + index = parent; + node = p; + parent = p->m_parent; + } + + if (!this->IsNil(index)) { + index = parent; + node = p; + } + } else { + index = this->FindMaxInSubtree(node->m_left); + node = this->GetNode(index); + } + + if (this->IsNil(index)) { + return Index_Leaf; + } else { + return index; + } + } + + int UncheckedPP(int index) const { + auto *node = this->GetNode(index); + + if (!this->IsNil(index)) { + if (this->IsNil(node->m_right)) { + int parent = node->m_parent; + Node *p; + + for (p = this->GetNode(parent); !this->IsNil(parent) && index == p->m_right; p = this->GetNode(parent)) { + index = parent; + node = p; + parent = p->m_parent; + } + + index = parent; + node = p; + } else { + index = this->FindMinInSubtree(node->m_right); + node = this->GetNode(index); + } + } + + if (this->IsNil(index)) { + return Index_Leaf; + } else { + return index; + } + } + public: + void Initialize(size_t num_elements, void *buffer, size_t buffer_size) { + AMS_ASSERT(num_elements <= max_size); + + return this->InitializeImpl(static_cast(num_elements), buffer, buffer_size); + } + + iterator begin() { return iterator(*this); } + const_iterator begin() const { return const_iterator(*this); } + + iterator end() { return m_end_iterator; } + const_iterator end() const { return m_end_iterator; } + + size_t size() const { return m_header->cur_elements; } + + void clear() { + const auto num_elements = m_header->max_elements; + const auto buffer_size = m_header->buffer_size; + AMS_ASSERT(buffer_size == static_cast(GetRequiredMemorySize(num_elements))); + + return this->InitializeMemory(num_elements, buffer_size, impl::AvailableIndexFinder::GetSignature()); + } + + bool erase(const Member &elem) { + const auto range = this->equal_range(elem); + if (range.first != range.last) { + this->EraseByIndex(range.first); + return true; + } else { + return false; + } + } + + iterator find(const Member &elem) { + if (const auto index = this->FindIndex(elem); index >= 0) { + return iterator(*this, index); + } else { + return this->end(); + } + } + + const_iterator find(const Member &elem) const { + if (const auto index = this->FindIndex(elem); index >= 0) { + return const_iterator(*this, index); + } else { + return this->end(); + } + } + + std::pair insert(const Member &elem) { + const auto index = this->InsertNoHint(false, elem); + const auto it = iterator(*this, index); + return std::make_pair(it, !this->IsNil(index)); + } + + IndexPair equal_range(const Member &elem) { + /* Get node to start iteration. */ + auto cur_index = this->GetRoot(); + auto cur_node = this->GetNode(cur_index); + + auto min_index = Index_Leaf; + auto min_node = this->GetNode(min_index); + + auto max_index = Index_Leaf; + auto max_node = this->GetNode(max_index); + + /* Iterate until current is leaf, to find min/max. */ + while (cur_index != Index_Leaf) { + if (Compare{}(cur_node->m_data, elem)) { + cur_index = cur_node->m_right; + cur_node = this->GetNode(cur_index); + } else { + if (max_index == Index_Leaf && Compare{}(elem, cur_node->m_data)) { + max_index = cur_index; + max_node = this->GetNode(max_index); + } + min_index = cur_index; + min_node = this->GetNode(min_index); + + cur_index = cur_node->m_left; + cur_node = this->GetNode(cur_index); + } + } + + /* Iterate again, to find correct range extent for max. */ + cur_index = (max_index == Index_Leaf) ? this->GetRoot() : max_node->m_left; + cur_node = this->GetNode(cur_index); + while (cur_index != Index_Leaf) { + if (Compare{}(elem, cur_node->m_data)) { + max_index = cur_index; + max_node = cur_node; + cur_index = cur_node->m_left; + } else { + cur_index = cur_node->m_right; + } + cur_node = this->GetNode(cur_index); + } + + AMS_UNUSED(min_node); + return IndexPair{min_index, max_index}; + } + }; + + + +} diff --git a/libraries/libvapours/include/vapours/util/util_scope_guard.hpp b/libraries/libvapours/include/vapours/util/util_scope_guard.hpp index def2186b6..60b9808f8 100644 --- a/libraries/libvapours/include/vapours/util/util_scope_guard.hpp +++ b/libraries/libvapours/include/vapours/util/util_scope_guard.hpp @@ -13,8 +13,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - - /* Scope guard logic lovingly taken from Andrei Alexandrescu's "Systemic Error Handling in C++" */ +/* Scope guard logic lovingly taken from Andrei Alexandrescu's "Systemic Error Handling in C++" */ #pragma once #include #include diff --git a/libraries/libvapours/include/vapours/util/util_string_util.hpp b/libraries/libvapours/include/vapours/util/util_string_util.hpp index d06d96a7d..9abe8aaab 100644 --- a/libraries/libvapours/include/vapours/util/util_string_util.hpp +++ b/libraries/libvapours/include/vapours/util/util_string_util.hpp @@ -31,7 +31,7 @@ namespace ams::util { } template - int Strncmp(const T *lhs, const T *rhs, int count) { + constexpr int Strncmp(const T *lhs, const T *rhs, int count) { AMS_ASSERT(lhs != nullptr); AMS_ASSERT(rhs != nullptr); AMS_ABORT_UNLESS(count >= 0); @@ -50,7 +50,7 @@ namespace ams::util { } template - int Strnicmp(const T *lhs, const T *rhs, int count) { + constexpr int Strnicmp(const T *lhs, const T *rhs, int count) { AMS_ASSERT(lhs != nullptr); AMS_ASSERT(rhs != nullptr); AMS_ABORT_UNLESS(count >= 0); 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/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..f5ac6fe4f --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp @@ -0,0 +1,77 @@ +/* + * 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 { + + namespace { + + constinit os::SdkMutex g_log_mutex; + constinit bool g_log_enabled; + + constinit ::FsFile g_log_file; + constinit s64 g_log_ofs; + + + constinit char g_log_buf[0x400]; + + } + + void InitializeDebug(bool enable_log) { + { + std::scoped_lock lk(g_log_mutex); + + g_log_enabled = enable_log; + + if (g_log_enabled) { + /* Create the logs directory. */ + mitm::fs::CreateAtmosphereSdDirectory("/logs"); + + /* Create the log file. */ + mitm::fs::CreateAtmosphereSdFile("/logs/dns_mitm_debug.log", 0, ams::fs::CreateOption_None); + + /* Open the log file. */ + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(g_log_file), "/logs/dns_mitm_debug.log", 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); + + if (g_log_enabled) { + 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; + } + } + +} 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..b1beef8da --- /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(bool enable_log); + + void LogDebug(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp new file mode 100644 index 000000000..c589dfcad --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp @@ -0,0 +1,399 @@ +/* + * 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" +#include "dnsmitm_host_redirection.hpp" +#include "socket_allocator.hpp" + +namespace ams::mitm::socket::resolver { + + namespace { + + /* https://github.com/clibs/wildcardcmp */ + constexpr int wildcardcmp(const char *pattern, const char *string) { + const char *w = nullptr; /* last `*` */ + const char *s = nullptr; /* last checked char */ + + /* malformed */ + if (!pattern || !string) return 0; + + /* loop 1 char at a time */ + while (1) { + if (!*string) { + if (!*pattern) return 1; + if ('*' == *pattern) return 1; + if (!*s) return 0; + string = s++; + pattern = w; + continue; + } else { + if (*pattern != *string) { + if ('*' == *pattern) { + w = ++pattern; + s = string; + /* "*" -> "foobar" */ + if (*pattern) continue; + return 1; + } else if (w) { + string++; + /* "*ooba*" -> "foobar" */ + continue; + } + return 0; + } + } + + string++; + pattern++; + } + + return 1; + } + + constexpr const char DefaultHostsFile[] = + "# Nintendo telemetry servers\n" + "127.0.0.1 receive-%.dg.srv.nintendo.net receive-%.er.srv.nintendo.net\n"; + + constinit os::SdkMutex g_redirection_lock; + std::vector> g_redirection_list; + + void RemoveRedirection(const char *hostname) { + for (auto it = g_redirection_list.begin(); it != g_redirection_list.end(); ++it) { + if (std::strcmp(it->first.c_str(), hostname) == 0) { + g_redirection_list.erase(it); + break; + } + } + } + + void AddRedirection(const char *hostname, ams::socket::InAddrT addr) { + RemoveRedirection(hostname); + g_redirection_list.emplace(g_redirection_list.begin(), std::string(hostname), addr); + } + + constinit char g_specific_emummc_hosts_path[0x40] = {}; + + void ParseHostsFile(const char *file_data) { + /* Get the environment identifier from settings. */ + const auto env = ams::nsd::impl::device::GetEnvironmentIdentifierFromSettings(); + const auto env_len = std::strlen(env.value); + + /* Parse the file. */ + 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 if (c == '\n') { + state = State::BeginLine; + } 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') { + if (c == '%') { + std::memcpy(current_hostname, env.value, env_len); + work = env_len; + } else { + 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'; + + AddRedirection(current_hostname, current_address); + work = 0; + + if (c == '\n') { + state = State::BeginLine; + } else { + state = State::WhiteSpace; + } + } else if (c == '%') { + AMS_ABORT_UNLESS(work < sizeof(current_hostname) - env_len); + std::memcpy(current_hostname + work, env.value, env_len); + work += env_len; + } 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'; + + AddRedirection(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.txt", 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.txt")) { + return "/hosts/emummc.txt"; + } + Log(log_file, "Skipping %s because it does not exist...\n", "/hosts/emummc.txt"); + } else { + if (mitm::fs::HasAtmosphereSdFile("/hosts/sysmmc.txt")) { + return "/hosts/sysmmc.txt"; + } + Log(log_file, "Skipping %s because it does not exist...\n", "/hosts/sysmmc.txt"); + } + + return "/hosts/default.txt"; + } + + 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 InitializeResolverRedirections() { + /* Get whether we should add defaults. */ + const bool add_defaults = ShouldAddDefaultResolverRedirections(); + + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(g_redirection_lock); + + /* Clear the redirections map. */ + g_redirection_list.clear(); + + /* Open log file. */ + ::FsFile log_file; + mitm::fs::DeleteAtmosphereSdFile("/logs/dns_mitm_startup.log"); + mitm::fs::CreateAtmosphereSdDirectory("/logs"); + R_ABORT_UNLESS(mitm::fs::CreateAtmosphereSdFile("/logs/dns_mitm_startup.log", 0, ams::fs::CreateOption_None)); + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(log_file), "/logs/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.txt")) { + Log(log_file, "Creating /hosts/default.txt because it does not exist.\n"); + + mitm::fs::CreateAtmosphereSdDirectory("/hosts"); + R_ABORT_UNLESS(mitm::fs::CreateAtmosphereSdFile("/hosts/default.txt", sizeof(DefaultHostsFile) - 1, ams::fs::CreateOption_None)); + + ::FsFile default_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(default_file), "/hosts/default.txt", 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_list) { + 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) { + std::scoped_lock lk(g_redirection_lock); + + for (const auto &[host, address] : g_redirection_list) { + if (wildcardcmp(host.c_str(), hostname)) { + *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 new file mode 100644 index 000000000..d492c50f5 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.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 InitializeResolverRedirections(); + + 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 new file mode 100644 index 000000000..2aeeb93e6 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp @@ -0,0 +1,137 @@ +/* + * 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_debug.hpp" +#include "dnsmitm_resolver_impl.hpp" +#include "dnsmitm_host_redirection.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); + } + } + } + + 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 ShouldEnableDebugLog() { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_dns_mitm_debug_log") == 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(ShouldEnableDebugLog()); + + /* Initialize redirection map. */ + resolver::InitializeResolverRedirections(); + + /* 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..a15bf5f1c --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp @@ -0,0 +1,200 @@ +/* + * 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" +#include "dnsmitm_host_redirection.hpp" +#include "serializer/serializer.hpp" +#include "sfdnsres_shim.h" + +namespace ams::mitm::socket::resolver { + + 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] = { &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) { + 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; + } + + 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) { + const char *hostname = reinterpret_cast(name.GetPointer()); + + LogDebug("[%016lx]: GetHostByNameRequest(%s)\n", this->client_info.program_id.value, hostname); + + 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", this->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; + + return ResultSuccess(); + } + + 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) { + const char *hostname = reinterpret_cast(node.GetPointer()); + + LogDebug("[%016lx]: GetAddrInfoRequest(%s, %s)\n", this->client_info.program_id.value, reinterpret_cast(node.GetPointer()), reinterpret_cast(srv.GetPointer())); + + 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", this->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; + + return ResultSuccess(); + } + + 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) { + const char *hostname = reinterpret_cast(name.GetPointer()); + + LogDebug("[%016lx]: GetHostByNameRequestWithOptions(%s)\n", this->client_info.program_id.value, hostname); + + 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", this->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; + + return ResultSuccess(); + } + + 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) { + const char *hostname = reinterpret_cast(node.GetPointer()); + + LogDebug("[%016lx]: GetAddrInfoRequestWithOptions(%s, %s)\n", this->client_info.program_id.value, hostname, reinterpret_cast(srv.GetPointer())); + + 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", this->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; + + return ResultSuccess(); + } + + Result ResolverImpl::AtmosphereReloadHostsFile() { + /* Perform a hosts file reload. */ + InitializeResolverRedirections(); + return ResultSuccess(); + } + +} 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..5b95af8af --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp @@ -0,0 +1,52 @@ +/* + * 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_METHOD_INFO(C, H, 65000, Result, AtmosphereReloadHostsFile, (), ()) + +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); + + /* Extension commands. */ + Result AtmosphereReloadHostsFile(); + }; + static_assert(IsIResolver); + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.cpp new file mode 100644 index 000000000..bf6efc567 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.cpp @@ -0,0 +1,49 @@ +/* + * 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_debug.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + ssize_t DNSSerializer::CheckToBufferArguments(const u8 *dst, size_t dst_size, size_t required, int error_id) { + if (dst == nullptr) { + return -1; + } else if (dst_size < required) { + return -1; + } + + return 0; + } + + u32 DNSSerializer::InternalHton(const u32 &v) { + return ams::socket::InetHtonl(v); + } + + u16 DNSSerializer::InternalHton(const u16 &v) { + return ams::socket::InetHtons(v); + } + + u32 DNSSerializer::InternalNtoh(const u32 &v) { + return ams::socket::InetNtohl(v); + } + + u16 DNSSerializer::InternalNtoh(const u16 &v) { + return ams::socket::InetNtohs(v); + } + + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.hpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.hpp new file mode 100644 index 000000000..36d329562 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.hpp @@ -0,0 +1,73 @@ +/* + * 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::serializer { + + class DNSSerializer { + public: + static ssize_t CheckToBufferArguments(const u8 *dst, size_t dst_size, size_t required, int error_id); + + static u32 InternalHton(const u32 &v); + static u16 InternalHton(const u16 &v); + static u32 InternalNtoh(const u32 &v); + static u16 InternalNtoh(const u16 &v); + public: + template + static size_t SizeOf(const T &in); + + template + static size_t SizeOf(const T *in); + + template + static size_t SizeOf(const T *in, size_t count); + + template + static size_t SizeOf(const T **arr, u32 &out_count); + + template + static ssize_t ToBuffer(u8 * const dst, size_t dst_size, const T &in); + + template + static ssize_t FromBuffer(T &out, const u8 *src, size_t src_size); + + template + static ssize_t ToBuffer(u8 * const dst, size_t dst_Size, T *in); + + template + static ssize_t ToBuffer(u8 * const dst, size_t dst_size, T **arr); + + template + static ssize_t FromBuffer(T *&out, const u8 *src, size_t src_size); + + template + static ssize_t FromBuffer(T **&out_arr, const u8 *src, size_t src_size); + + template + static ssize_t ToBuffer(u8 * const dst, size_t dst_size, const T * const arr, size_t count); + + template + static ssize_t FromBuffer(T * const arr, size_t arr_size, const u8 *src, size_t src_size, size_t count); + }; + + void FreeHostent(ams::socket::HostEnt &ent); + void FreeHostent(struct hostent &ent); + + void FreeAddrInfo(ams::socket::AddrInfo &addr_info); + void FreeAddrInfo(struct addrinfo &addr_info); + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_addrinfo.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_addrinfo.cpp new file mode 100644 index 000000000..f9f11479d --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_addrinfo.cpp @@ -0,0 +1,444 @@ +/* + * 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_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + constexpr inline u32 AddrInfoMagic = 0xBEEFCAFE; + + template + concept IsAddrInfo = std::same_as || std::same_as; + + template requires IsAddrInfo + using SockAddrType = typename std::conditional, ams::socket::SockAddr, struct sockaddr>::type; + + template requires IsAddrInfo + using SockAddrInType = typename std::conditional, ams::socket::SockAddrIn, struct sockaddr_in>::type; + + template requires IsAddrInfo + using SockAddrIn6Type = typename std::conditional, struct sockaddr_in6, struct sockaddr_in6>::type; + + template requires IsAddrInfo + constexpr bool IsAfInet(const auto ai_family) { + if constexpr (std::same_as) { + return ai_family == ams::socket::Family::Af_Inet; + } else { + return ai_family == AF_INET; + } + } + + template requires IsAddrInfo + constexpr bool IsAfInet6(const auto ai_family) { + if constexpr (std::same_as) { + return ai_family == ams::socket::Family::Af_Inet6; + } else { + return ai_family == AF_INET; + } + } + + template requires IsAddrInfo + void FreeAddrInfoImpl(T &addr_info) { + T *next = nullptr; + for (T *cur = std::addressof(addr_info); cur != nullptr; cur = next) { + next = cur->ai_next; + + if (cur->ai_addr != nullptr) { + if (IsAfInet(cur->ai_family)) { + ams::socket::impl::Free(reinterpret_cast *>(cur->ai_addr)); + } else if (IsAfInet6(cur->ai_family)) { + ams::socket::impl::Free(reinterpret_cast *>(cur->ai_addr)); + } else { + ams::socket::impl::Free(cur->ai_addr); + } + cur->ai_addr = nullptr; + } + + if (cur->ai_canonname != nullptr) { + ams::socket::impl::Free(cur->ai_canonname); + cur->ai_canonname = nullptr; + } + + if (cur != std::addressof(addr_info)) { + ams::socket::impl::Free(cur); + } + } + } + + template requires IsAddrInfo + size_t AddrInfoSingleSizeOf(const T *addr_info) { + size_t rc = 6 * sizeof(u32); + + if (addr_info->ai_addr == nullptr) { + rc += sizeof(u32); + } else if (IsAfInet(addr_info->ai_family)) { + rc += DNSSerializer::SizeOf(*reinterpret_cast *>(addr_info->ai_addr)); + } else if (IsAfInet6(addr_info->ai_family)) { + rc += DNSSerializer::SizeOf(*reinterpret_cast *>(addr_info->ai_addr)); + } else if (addr_info->ai_addrlen == 0) { + rc += sizeof(u32); + } else { + rc += addr_info->ai_addrlen; + } + + if (addr_info->ai_canonname != nullptr) { + rc += DNSSerializer::SizeOf(static_cast(addr_info->ai_canonname)); + } else { + rc += sizeof(u8); + } + + if (addr_info->ai_next == nullptr) { + rc += sizeof(u32); + } + + return rc; + } + + template requires IsAddrInfo + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + + for (const T *addr_info = std::addressof(in); addr_info != nullptr; addr_info = addr_info->ai_next) { + rc += AddrInfoSingleSizeOf(addr_info); + } + + return rc; + } + + template requires IsAddrInfo + ssize_t ToBufferInternalImpl(u8 * const dst, size_t dst_size, const T &addr_info) { + ssize_t rc = -1; + u8 *cur = dst; + + { + const u32 value = AddrInfoMagic; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast(addr_info.ai_flags); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast(addr_info.ai_family); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast(addr_info.ai_socktype); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast(addr_info.ai_protocol); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast(addr_info.ai_addrlen); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + if (addr_info.ai_addr == nullptr || addr_info.ai_addrlen == 0) { + const u32 value = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + } else if (IsAfInet(addr_info.ai_family)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), *reinterpret_cast *>(addr_info.ai_addr))) == -1) { + return rc; + } + } else if (IsAfInet6(addr_info.ai_family)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), *reinterpret_cast *>(addr_info.ai_addr))) == -1) { + return rc; + } + } else { + if (dst_size - (cur - dst) < addr_info.ai_addrlen) { + rc = -1; + return rc; + } + + /* NOTE: This is clearly a nintendo bug, see the accompanying note in FromBufferInternalImpl */ + std::memmove(cur, std::addressof(addr_info.ai_addr), addr_info.ai_addrlen); + rc = addr_info.ai_addrlen; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), addr_info.ai_canonname)) == -1) { + return rc; + } + cur += rc; + } + + if (addr_info.ai_next == nullptr) { + const u32 value = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + rc = cur - dst; + return rc; + } + + template requires IsAddrInfo + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + std::memset(dst, 0, dst_size); + + const size_t required = DNSSerializer::SizeOf(in); + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, required, __LINE__)) == -1) { + return rc; + } + + for (const T *addr_info = std::addressof(in); addr_info != nullptr; addr_info = addr_info->ai_next) { + if ((rc = ToBufferInternalImpl(cur, dst_size, *addr_info)) == -1) { + return rc; + } + cur += rc; + } + + rc = cur - dst; + return rc; + } + + template requires IsAddrInfo + ssize_t FromBufferInternalImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + std::memset(std::addressof(out), 0, sizeof(out)); + + ON_SCOPE_EXIT { if (rc < 0) { FreeAddrInfo(out); } }; + + u32 tmp_value; + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } else if (tmp_value != AddrInfoMagic) { + return rc; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_flags = static_cast(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_family = static_cast(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_socktype = static_cast(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_protocol = static_cast(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_addrlen = static_cast(tmp_value); + cur += rc; + } + + { + if (out.ai_addrlen == 0) { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + + if (tmp_value != 0) { + rc = -1; + return rc; + } + out.ai_addr = nullptr; + } else if (IsAfInet(out.ai_family)) { + out.ai_addr = static_cast *>(ams::socket::impl::Alloc(sizeof(SockAddrInType))); + if (out.ai_addr == nullptr) { + rc = -1; + return rc; + } + std::memset(out.ai_addr, 0, sizeof(SockAddrInType)); + + if ((rc = DNSSerializer::FromBuffer(*reinterpret_cast *>(out.ai_addr), cur, src_size - (cur - src))) == -1) { + return rc; + } + } else if (IsAfInet6(out.ai_family)) { + out.ai_addr = static_cast *>(ams::socket::impl::Alloc(sizeof(SockAddrIn6Type))); + if (out.ai_addr == nullptr) { + rc = -1; + return rc; + } + std::memset(out.ai_addr, 0, sizeof(SockAddrIn6Type)); + + if ((rc = DNSSerializer::FromBuffer(*reinterpret_cast *>(out.ai_addr), cur, src_size - (cur - src))) == -1) { + return rc; + } + } else { + out.ai_addr = static_cast(ams::socket::impl::Alloc(out.ai_addrlen)); + if (out.ai_addr == nullptr) { + rc = -1; + return rc; + } + + /* NOTE: This is *clearly* a nintendo bug. */ + /* They obviously intend to copy to the buffer they just allocated, but instead they copy to the addrinfo structure itself. */ + /* Probably &out.ai_addr instead of &out.ai_addr[0]? Either way, we'll implement what they do, but... */ + std::memcpy(std::addressof(out.ai_addr), cur, out.ai_addrlen); + rc = out.ai_addrlen; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(out.ai_canonname, cur, src_size - (cur - src))) == -1) { + return rc; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } else if (tmp_value == 0) { + out.ai_next = nullptr; + cur += rc; + } else if (tmp_value == AddrInfoMagic) { + out.ai_next = static_cast(ams::socket::impl::Alloc(sizeof(T))); + if (out.ai_next == nullptr) { + rc = -1; + return rc; + } + + std::memset(out.ai_next, 0, sizeof(T)); + } else { + rc = -1; + return rc; + } + } + + rc = cur - src; + return rc; + } + + template requires IsAddrInfo + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = 0; + const u8 *cur = src; + + const size_t required = DNSSerializer::SizeOf(out); + if (src_size < required) { + ams::socket::SetLastError(ams::socket::Errno::ENoSpc); + rc = -1; + return rc; + } + + for (T *addr_info = std::addressof(out); addr_info != nullptr; addr_info = addr_info->ai_next) { + if ((rc = FromBufferInternalImpl(*addr_info, cur, src_size - (cur - src))) == -1) { + rc = -1; + return rc; + } + cur += rc; + } + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct addrinfo &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::AddrInfo &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::AddrInfo &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct addrinfo &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(ams::socket::AddrInfo &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct addrinfo &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + void FreeAddrInfo(ams::socket::AddrInfo &addr_info) { + return FreeAddrInfoImpl(addr_info); + } + + void FreeAddrInfo(struct addrinfo &addr_info) { + return FreeAddrInfoImpl(addr_info); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_hostent.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_hostent.cpp new file mode 100644 index 000000000..b26991173 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_hostent.cpp @@ -0,0 +1,234 @@ +/* + * 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_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template + concept IsHostEnt = std::same_as || std::same_as; + + template requires IsHostEnt + using InAddrType = typename std::conditional, ams::socket::InAddr, struct in_addr>::type; + + template requires IsHostEnt + constexpr bool IsAfInet(const auto h_addrtype) { + if constexpr (std::same_as) { + return h_addrtype == ams::socket::Family::Af_Inet; + } else { + return h_addrtype == AF_INET; + } + } + + template requires IsHostEnt + constexpr bool IsAfInet6(const auto h_addrtype) { + if constexpr (std::same_as) { + return h_addrtype == ams::socket::Family::Af_Inet6; + } else { + return h_addrtype == AF_INET; + } + } + + template requires IsHostEnt + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + u32 dummy = 0; + + rc += DNSSerializer::SizeOf((const char *)(in.h_name)); + rc += DNSSerializer::SizeOf((const char **)(in.h_aliases), dummy); + rc += sizeof(u32); + rc += sizeof(u32); + rc += DNSSerializer::SizeOf((const InAddrType **)(in.h_addr_list), dummy); + + return rc; + } + + template requires IsHostEnt + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + const size_t required = DNSSerializer::SizeOf(in); + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, required, __LINE__)) == -1) { + return rc; + } + + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), in.h_name)) == -1) { + return rc; + } + cur += rc; + + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), in.h_aliases)) == -1) { + return rc; + } + cur += rc; + + const u16 h_addrtype = static_cast(in.h_addrtype); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), h_addrtype)) == -1) { + return rc; + } + cur += rc; + + const u16 h_length = in.h_length; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), h_length)) == -1) { + return rc; + } + cur += rc; + + if (IsAfInet(in.h_addrtype)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), (InAddrType **)(in.h_addr_list))) == -1) { + return rc; + } + } else if (IsAfInet6(in.h_addrtype)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), (InAddrType **)(in.h_addr_list))) == -1) { + return rc; + } + } else { + const u32 null = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), null)) == -1) { + return rc; + } + } + cur += rc; + + rc = cur - dst; + return rc; + } + + template requires IsHostEnt + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + std::memset(std::addressof(out), 0, sizeof(out)); + + ON_SCOPE_EXIT { + if (rc < 0) { + FreeHostent(out); + } + }; + + if ((rc = DNSSerializer::FromBuffer(out.h_name, cur, src_size - (cur - src))) == -1) { + return rc; + } + cur += rc; + + if ((rc = DNSSerializer::FromBuffer(out.h_aliases, cur, src_size - (cur - src))) == -1) { + return rc; + } + cur += rc; + + u16 h_addrtype = 0; + if ((rc = DNSSerializer::FromBuffer(h_addrtype, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.h_addrtype = static_cast(h_addrtype); + cur += rc; + + u16 h_length = 0; + if ((rc = DNSSerializer::FromBuffer(h_length, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.h_length = h_length; + cur += rc; + + InAddrType **addrs = nullptr; + if ((rc = DNSSerializer::FromBuffer(addrs, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.h_addr_list = (char **)addrs; + cur += rc; + + rc = cur - src; + return rc; + } + + template requires IsHostEnt + void FreeHostentImpl(T &ent) { + if (ent.h_name != nullptr) { + ams::socket::impl::Free(ent.h_name); + ent.h_name = nullptr; + } + + if (ent.h_aliases != nullptr) { + u32 i = 0; + for (char *str = ent.h_aliases[i]; str != nullptr; str = ent.h_aliases[++i]) { + ams::socket::impl::Free(str); + ent.h_aliases[i] = nullptr; + } + + ams::socket::impl::Free(ent.h_aliases); + ent.h_aliases = nullptr; + } + + if (ent.h_addr_list != nullptr) { + AMS_ASSERT(ent.h_length == sizeof(u32)); + if (ent.h_length == sizeof(u32)) { + auto **addr_list = reinterpret_cast **>(ent.h_addr_list); + + u32 i = 0; + for (auto *addr = addr_list[i]; addr != nullptr; addr = addr_list[++i]) { + ams::socket::impl::Free(addr); + addr_list[i] = nullptr; + } + + ams::socket::impl::Free(ent.h_addr_list); + ent.h_addr_list = nullptr; + } + } + + std::memset(std::addressof(ent), 0, sizeof(ent)); + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct hostent &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct hostent &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct hostent &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::HostEnt &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::HostEnt &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(ams::socket::HostEnt &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + void FreeHostent(ams::socket::HostEnt &ent) { + return FreeHostentImpl(ent); + } + + void FreeHostent(struct hostent &ent) { + return FreeHostentImpl(ent); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_in_addr.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_in_addr.cpp new file mode 100644 index 000000000..c990f2aad --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_in_addr.cpp @@ -0,0 +1,237 @@ +/* + * 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_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template + concept IsInAddr = std::same_as || std::same_as; + + template requires IsInAddr + size_t SizeOfImpl(const T &in) { + return sizeof(u32); + } + + template requires IsInAddr + size_t SizeOfImpl(const T **in, u32 &out_count) { + size_t rc = sizeof(u32); + out_count = 0; + + if (in != nullptr) { + for (const T ** cur_addr = in; *cur_addr != nullptr; ++cur_addr) { + ++out_count; + rc += sizeof(u32); + } + } + + return rc; + } + + template requires IsInAddr + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + const u32 val = DNSSerializer::InternalHton(in.s_addr); + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, sizeof(in), __LINE__)) == -1) { + return rc; + } + + std::memcpy(cur, std::addressof(val), sizeof(val)); + rc += sizeof(val); + + return rc; + } + + template requires IsInAddr + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + if (src_size < sizeof(out)) { + return rc; + } + + std::memset(std::addressof(out), 0, sizeof(out)); + out.s_addr = DNSSerializer::InternalNtoh(*reinterpret_cast(src)); + + rc = sizeof(u32); + return rc; + } + + template requires IsInAddr + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, T **arr) { + ssize_t rc = -1; + u8 *cur = dst; + + if (arr == nullptr && dst_size < sizeof(u32)) { + return rc; + } else if (arr == nullptr) { + const u32 null = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), null)) == -1) { + return rc; + } + cur += rc; + } else { + u32 count = 0; + for (auto *tmp = arr; *tmp != nullptr; ++tmp) { + ++count; + } + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, (count + 1) * sizeof(**arr), __LINE__)) == -1) { + return rc; + } + + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), count)) == -1) { + return rc; + } + cur += rc; + + rc = 0; + for (auto i = 0; arr[i] != nullptr; ++i) { + const T addr = *arr[i]; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), addr)) == -1) { + return rc; + } + + cur += rc; + } + + rc = cur - dst; + } + + return rc; + } + + template requires IsInAddr + ssize_t FromBufferImpl(T **&out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + out = nullptr; + + ON_SCOPE_EXIT { + if (rc == -1 && out != nullptr) { + for (auto i = 0; out[i] != nullptr; ++i) { + ams::socket::impl::Free(out[i]); + out[i] = nullptr; + } + + ams::socket::impl::Free(out); + out = nullptr; + } + }; + + if (src == nullptr) { + rc = 0; + return rc; + } else if (src_size == 0) { + rc = 0; + return rc; + } + + u32 count = 0; + if ((rc = DNSSerializer::FromBuffer(count, cur, src_size)) == -1) { + return rc; + } + cur += rc; + + if (count == 0) { + return rc; + } + + out = static_cast(ams::socket::impl::Alloc((count + 1) * sizeof(T *))); + if (out == nullptr) { + rc = -1; + return rc; + } + std::memset(out, 0, (count + 1) * sizeof(T *)); + + for (u32 i = 0; i < count; ++i) { + out[i] = static_cast(ams::socket::impl::Alloc(sizeof(T))); + if (out[i] == nullptr) { + rc = -1; + return rc; + } + + u32 s_addr = 0; + if ((rc = DNSSerializer::FromBuffer(s_addr, cur, src_size - (cur - src))) == -1) { + return rc; + } + out[i]->s_addr = s_addr; + cur += rc; + } + out[count] = nullptr; + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct in_addr &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::InAddr &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const struct in_addr **in, u32 &out_count) { + return SizeOfImpl(in, out_count); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::InAddr **in, u32 &out_count) { + return SizeOfImpl(in, out_count); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct in_addr &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::InAddr &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct in_addr &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct ams::socket::InAddr &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, struct in_addr **arr) { + return ToBufferImpl(dst, dst_size, arr); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, ams::socket::InAddr **arr) { + return ToBufferImpl(dst, dst_size, arr); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct in_addr **&out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct ams::socket::InAddr **&out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_ints.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_ints.cpp new file mode 100644 index 000000000..3077f7645 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_ints.cpp @@ -0,0 +1,74 @@ +/* + * 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_debug.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const u16 &in) { + /* Convert the value. */ + u8 *cur = dst; + const u16 val = InternalHton(in); + + /* Check arguments. */ + ssize_t rc = -1; + if ((rc = CheckToBufferArguments(cur, dst_size, sizeof(u16), __LINE__)) == -1) { + return rc; + } + + std::memcpy(cur, std::addressof(val), sizeof(u16)); + rc += sizeof(u16); + + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(u16 &out, const u8 *src, size_t src_size) { + if (src_size < sizeof(u16)) { + return -1; + } + + out = InternalNtoh(*reinterpret_cast(src)); + return sizeof(u16); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const u32 &in) { + /* Convert the value. */ + u8 *cur = dst; + const u32 val = InternalHton(in); + + /* Check arguments. */ + ssize_t rc = -1; + if ((rc = CheckToBufferArguments(cur, dst_size, sizeof(u32), __LINE__)) == -1) { + return rc; + } + + std::memcpy(cur, std::addressof(val), sizeof(u32)); + rc += sizeof(u32); + + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(u32 &out, const u8 *src, size_t src_size) { + if (src_size < sizeof(u32)) { + return -1; + } + + out = InternalNtoh(*reinterpret_cast(src)); + return sizeof(u32); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_4.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_4.cpp new file mode 100644 index 000000000..3c4cff1a0 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_4.cpp @@ -0,0 +1,141 @@ +/* + * 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_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template + concept IsSockAddrIn = std::same_as || std::same_as; + + template requires IsSockAddrIn + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + rc += sizeof(u16); + rc += sizeof(u16); + rc += DNSSerializer::SizeOf(in.sin_addr); + rc += sizeof(in.sin_zero); + return rc; + } + + template requires IsSockAddrIn + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, sizeof(in), __LINE__)) == -1) { + return rc; + } + + const u16 sin_family = static_cast(in.sin_family); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin_family)) == -1) { + return rc; + } + cur += rc; + + const u16 sin_port = static_cast(in.sin_port); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin_port)) == -1) { + return rc; + } + cur += rc; + + const u32 s_addr = static_cast(in.sin_addr.s_addr); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), s_addr)) == -1) { + return rc; + } + cur += rc; + + if (dst_size - (cur - dst) < sizeof(in.sin_zero)) { + rc = -1; + return rc; + } + + std::memcpy(cur, in.sin_zero, sizeof(in.sin_zero)); + cur += sizeof(in.sin_zero); + + rc = cur - dst; + return rc; + } + + template requires IsSockAddrIn + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + u16 sin_family; + if ((rc = DNSSerializer::FromBuffer(sin_family, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin_family = static_cast(sin_family); + cur += rc; + + u16 sin_port; + if ((rc = DNSSerializer::FromBuffer(sin_port, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin_port = static_cast(sin_port); + cur += rc; + + u32 s_addr; + if ((rc = DNSSerializer::FromBuffer(s_addr, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin_addr.s_addr = static_cast(s_addr); + cur += rc; + + if (src_size - (cur - src) < sizeof(out.sin_zero)) { + rc = -1; + return rc; + } + + std::memcpy(out.sin_zero, cur, sizeof(out.sin_zero)); + cur += sizeof(out.sin_zero); + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct sockaddr_in &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::SockAddrIn &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct sockaddr_in &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::SockAddrIn &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct sockaddr_in &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct ams::socket::SockAddrIn &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_6.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_6.cpp new file mode 100644 index 000000000..24f7990a0 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_6.cpp @@ -0,0 +1,133 @@ +/* + * 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_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template + concept IsSockAddrIn6 = std::same_as; + + template requires IsSockAddrIn6 + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + rc += sizeof(u16); + rc += sizeof(u16); + rc += sizeof(u32); + rc += DNSSerializer::SizeOf(in.sin6_addr); + rc += sizeof(u32); + return rc; + } + + template requires IsSockAddrIn6 + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, sizeof(in), __LINE__)) == -1) { + return rc; + } + + const u16 sin6_family = static_cast(in.sin6_family); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_family)) == -1) { + return rc; + } + cur += rc; + + const u16 sin6_port = static_cast(in.sin6_port); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_port)) == -1) { + return rc; + } + cur += rc; + + const u32 sin6_flowinfo = static_cast(in.sin6_flowinfo); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_flowinfo)) == -1) { + return rc; + } + cur += rc; + + std::memcpy(cur, std::addressof(in.sin6_addr), sizeof(in.sin6_addr)); + cur += sizeof(in.sin6_addr); + + const u32 sin6_scope_id = static_cast(in.sin6_scope_id); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_scope_id)) == -1) { + return rc; + } + cur += rc; + + rc = cur - dst; + return rc; + } + + template requires IsSockAddrIn6 + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + u16 sin6_family; + if ((rc = DNSSerializer::FromBuffer(sin6_family, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_family = static_cast(sin6_family); + cur += rc; + + u16 sin6_port; + if ((rc = DNSSerializer::FromBuffer(sin6_port, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_port = static_cast(sin6_port); + cur += rc; + + u32 sin6_flowinfo; + if ((rc = DNSSerializer::FromBuffer(sin6_flowinfo, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_flowinfo = static_cast(sin6_flowinfo); + cur += rc; + + std::memcpy(std::addressof(out.sin6_addr), cur, sizeof(out.sin6_addr)); + cur += sizeof(out.sin6_addr); + + u32 sin6_scope_id; + if ((rc = DNSSerializer::FromBuffer(sin6_scope_id, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_scope_id = static_cast(sin6_scope_id); + cur += rc; + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct sockaddr_in6 &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct sockaddr_in6 &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct sockaddr_in6 &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_string.cpp b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_string.cpp new file mode 100644 index 000000000..ab9f2c889 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_string.cpp @@ -0,0 +1,180 @@ +/* + * 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_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + template<> size_t DNSSerializer::SizeOf(const char *str) { + if (str == nullptr) { + return sizeof(char); + } + return std::strlen(str) + 1; + } + + template<> size_t DNSSerializer::SizeOf(const char **str, u32 &out_count) { + size_t rc = sizeof(u32); + out_count = 0; + + if (str != nullptr) { + for (const char **cur = str; *cur != nullptr; ++cur) { + ++out_count; + rc += SizeOf(*cur); + } + } + + return rc; + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, char *str) { + ssize_t rc = -1; + u8 *cur = dst; + + if (str == nullptr && dst_size == 0) { + return -1; + } else if (str == nullptr) { + *cur = '\x00'; + return 1; + } else if ((rc = SizeOf(static_cast(str))) == 0) { + *cur = '\x00'; + return 1; + } else if (CheckToBufferArguments(cur, dst_size, rc + 1, __LINE__) == -1) { + return -1; + } + + std::memmove(cur, str, rc); + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(char *&out, const u8 *src, size_t src_size) { + size_t len = 0; + + if (src == nullptr) { + return 0; + } else if (src_size == 0) { + return 0; + } else if (src_size < (len = SizeOf(reinterpret_cast(src)))) { + return 1; + } else if (src[0] == '\x00') { + return 1; + } + + out = static_cast(ams::socket::impl::Alloc(len)); + if (out == nullptr) { + return -1; + } + + std::memmove(out, src, len); + return len; + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, char **str) { + ssize_t rc = -1; + u8 *cur = dst; + u32 count = 0; + + if (dst_size == 0) { + return -1; + } + + const size_t total_size = SizeOf(const_cast(str), count); + AMS_UNUSED(total_size); + + if ((rc = CheckToBufferArguments(cur, dst_size, sizeof(u32), __LINE__)) == -1) { + return rc; + } else if ((rc = ToBuffer(cur, dst_size, count)) == -1) { + return rc; + } + cur += rc; + dst_size -= rc; + + if (str != nullptr) { + for (char **cur_str = str; *cur_str != nullptr; ++cur_str) { + const auto tmp = ToBuffer(cur, dst_size, *cur_str); + if (tmp == -1) { + return rc; + } + + cur += tmp; + dst_size -= tmp; + rc += tmp; + } + } + + rc = cur - dst; + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(char **&out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + u32 count = 0; + + out = nullptr; + + ON_SCOPE_EXIT { + if (rc < 0 && out != nullptr) { + u32 i = 0; + for (char *str = *out; str != nullptr; str = out[++i]) { + ams::socket::impl::Free(str); + } + ams::socket::impl::Free(out); + out = nullptr; + } + }; + + if (src == nullptr) { + rc = 0; + return rc; + } else if (src_size == 0) { + rc = 0; + return rc; + } else if ((rc = FromBuffer(count, cur, src_size)) == -1) { + rc = -1; + return rc; + } + cur += rc; + + out = static_cast(ams::socket::impl::Alloc((count + 1) * sizeof(char *))); + if (out == nullptr) { + rc = -1; + return rc; + } + std::memset(out, 0, (count + 1) * sizeof(char *)); + + u32 i; + for (i = 0; i < count; ++i) { + const size_t len = std::strlen(reinterpret_cast(cur)); + out[i] = static_cast(ams::socket::impl::Alloc(len + 1)); + if (out[i] == nullptr) { + rc = -1; + return rc; + } + + std::memmove(out[i], cur, len + 1); + cur += len + 1; + } + + out[i] = nullptr; + rc = cur - src; + + return rc; + } + + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.c b/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.c new file mode 100644 index 000000000..dbd9660c2 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.c @@ -0,0 +1,97 @@ +/* + * 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 "sfdnsres_shim.h" +#include + +/* Command forwarders. */ +Result sfdnsresGetHostByNameRequestWithOptionsFwd(Service *s, u64 process_id, const void *name, size_t name_size, void *out_hostent, size_t out_hostent_size, u32 *out_size, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno) { + const struct { + u32 options_version; + u32 num_options; + u64 process_id; + } in = { options_version, num_options, process_id }; + struct { + u32 size; + s32 host_error; + s32 errno; + } out; + + Result rc = serviceMitmDispatchInOut(s, 10, in, out, + .buffer_attrs = { + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In + }, + .buffers = { + { name, name_size }, + { out_hostent, out_hostent_size }, + { option, option_size } + }, + .in_send_pid = true, + .override_pid = process_id, + ); + + if (R_SUCCEEDED(rc)) { + if (out_size) *out_size = out.size; + if (out_host_error) *out_host_error = out.host_error; + if (out_errno) *out_errno = out.errno; + } + + return rc; +} + +Result sfdnsresGetAddrInfoRequestWithOptionsFwd(Service *s, u64 process_id, const void *node, size_t node_size, const void *srv, size_t srv_size, const void *hint, size_t hint_size, void *out_ai, size_t out_ai_size, u32 *out_size, s32 *out_rv, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno) { + const struct { + u32 options_version; + u32 num_options; + u64 process_id; + } in = { options_version, num_options, process_id }; + struct { + u32 size; + s32 rv; + s32 host_error; + s32 errno; + } out; + + Result rc = serviceMitmDispatchInOut(s, 12, in, out, + .buffer_attrs = { + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In + }, + .buffers = { + { node, node_size }, + { srv, srv_size }, + { hint, hint_size }, + { out_ai, out_ai_size }, + { option, option_size } + }, + .in_send_pid = true, + .override_pid = process_id, + ); + + if (R_SUCCEEDED(rc)) { + if (out_size) *out_size = out.size; + if (out_rv) *out_rv = out.rv; + if (out_host_error) *out_host_error = out.host_error; + if (out_errno) *out_errno = out.errno; + } + + return rc; +} + diff --git a/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.h b/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.h new file mode 100644 index 000000000..07f3fa94e --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.h @@ -0,0 +1,21 @@ +/** + * @file sfdnsres_shim.h + * @brief IPC wrapper for dns.mitm. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Command forwarders. */ +Result sfdnsresGetHostByNameRequestWithOptionsFwd(Service *s, u64 process_id, const void *name, size_t name_size, void *out_hostent, size_t out_hostent_size, u32 *out_size, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno); + +Result sfdnsresGetAddrInfoRequestWithOptionsFwd(Service *s, u64 process_id, const void *node, size_t node_size, const void *srv, size_t srv_size, const void *hint, size_t hint_size, void *out_ai, size_t out_ai_size, u32 *out_size, s32 *out_rv, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/stratosphere/ams_mitm/source/dns_mitm/socket_allocator.hpp b/stratosphere/ams_mitm/source/dns_mitm/socket_allocator.hpp new file mode 100644 index 000000000..d65838676 --- /dev/null +++ b/stratosphere/ams_mitm/source/dns_mitm/socket_allocator.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::socket::impl { + + void *Alloc(size_t size); + void *Calloc(size_t num, size_t size); + void Free(void *ptr); + +} 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..1ed8312e5 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,19 @@ 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!0x1")); + + /* Controls whether dns.mitm logs to the sd card for debugging. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_dns_mitm_debug_log", "u8!0x0")); + /* Hbloader custom settings. */ /* Controls the size of the homebrew heap when running as applet. */