diff --git a/src/windows/common/VirtioNetworking.cpp b/src/windows/common/VirtioNetworking.cpp index 652f64987..fd631ea15 100644 --- a/src/windows/common/VirtioNetworking.cpp +++ b/src/windows/common/VirtioNetworking.cpp @@ -15,13 +15,29 @@ static constexpr auto c_eth0DeviceName = L"eth0"; static constexpr auto c_loopbackDeviceName = TEXT(LX_INIT_LOOPBACK_DEVICE_NAME); VirtioNetworking::VirtioNetworking( - GnsChannel&& gnsChannel, VirtioNetworkingFlags flags, LPCWSTR dnsOptions, std::shared_ptr guestDeviceManager, wil::shared_handle userToken) : + GnsChannel&& gnsChannel, + VirtioNetworkingFlags flags, + LPCWSTR dnsOptions, + std::shared_ptr guestDeviceManager, + wil::shared_handle userToken, + wil::unique_socket&& dnsHvsocket) : m_guestDeviceManager(std::move(guestDeviceManager)), m_userToken(std::move(userToken)), m_gnsChannel(std::move(gnsChannel)), m_flags(flags), m_dnsOptions(dnsOptions) { + THROW_HR_IF_MSG( + E_INVALIDARG, + ((!!dnsHvsocket != WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunnelingSocket)) || + (WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunnelingSocket) && WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunneling))), + "Incompatible DNS settings"); + + if (dnsHvsocket) + { + networking::DnsResolverFlags resolverFlags{}; + m_dnsTunnelingResolver.emplace(std::move(dnsHvsocket), resolverFlags); + } } VirtioNetworking::~VirtioNetworking() @@ -196,6 +212,10 @@ try { currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(default_route); } + else if (WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunnelingSocket)) + { + currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(TEXT(LX_INIT_DNS_TUNNELING_IP_ADDRESS)); + } else { wsl::core::networking::DnsSettingsFlags dnsFlags = networking::DnsSettingsFlags::IncludeVpn; diff --git a/src/windows/common/VirtioNetworking.h b/src/windows/common/VirtioNetworking.h index c0fdb27b7..010f57dbd 100644 --- a/src/windows/common/VirtioNetworking.h +++ b/src/windows/common/VirtioNetworking.h @@ -4,6 +4,7 @@ #include "INetworkingEngine.h" #include "GnsChannel.h" +#include "DnsResolver.h" #include "WslCoreHostDnsInfo.h" #include "GnsPortTrackerChannel.h" #include "GuestDeviceManager.h" @@ -16,13 +17,21 @@ enum class VirtioNetworkingFlags LocalhostRelay = 0x1, DnsTunneling = 0x2, Ipv6 = 0x4, + DnsTunnelingSocket = 0x8, }; DEFINE_ENUM_FLAG_OPERATORS(VirtioNetworkingFlags); class VirtioNetworking : public INetworkingEngine { public: - VirtioNetworking(GnsChannel&& gnsChannel, VirtioNetworkingFlags flags, LPCWSTR dnsOptions, std::shared_ptr guestDeviceManager, wil::shared_handle userToken); + VirtioNetworking( + GnsChannel&& gnsChannel, + VirtioNetworkingFlags flags, + LPCWSTR dnsOptions, + std::shared_ptr guestDeviceManager, + wil::shared_handle userToken, + wil::unique_socket&& dnsHvsocket = {}); + ~VirtioNetworking(); // Note: This class cannot be moved because m_networkNotifyHandle captures a 'this' pointer. @@ -61,6 +70,7 @@ class VirtioNetworking : public INetworkingEngine std::shared_ptr m_networkSettings; VirtioNetworkingFlags m_flags = VirtioNetworkingFlags::None; LPCWSTR m_dnsOptions = nullptr; + std::optional m_dnsTunnelingResolver; std::optional m_localhostAdapterId; std::optional m_adapterId; diff --git a/src/windows/common/WslCoreConfig.cpp b/src/windows/common/WslCoreConfig.cpp index ee8b780aa..3b9572183 100644 --- a/src/windows/common/WslCoreConfig.cpp +++ b/src/windows/common/WslCoreConfig.cpp @@ -419,26 +419,14 @@ void wsl::core::Config::Initialize(_In_opt_ HANDLE UserToken) { try { - // Open a handle to the service control manager and check if the inbox service is registered. - const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE)}; - THROW_LAST_ERROR_IF(!manager); - - // Check if the service is running. - const wil::unique_schandle service{OpenServiceW(manager.get(), L"GlobalSecureAccessTunnelingService", SERVICE_QUERY_STATUS)}; - if (service) + if (wsl::windows::common::helpers::IsServiceRunning(L"GlobalSecureAccessTunnelingService")) { - SERVICE_STATUS status; - THROW_IF_WIN32_BOOL_FALSE(QueryServiceStatus(service.get(), &status)); - - if (status.dwCurrentState != SERVICE_STOPPED) + if (DnsTunnelingConfigPresence == ConfigKeyPresence::Present) { - if (DnsTunnelingConfigPresence == ConfigKeyPresence::Present) - { - EMIT_USER_WARNING(wsl::shared::Localization::MessageDnsTunnelingDisabled()); - } - - EnableDnsTunneling = false; + EMIT_USER_WARNING(wsl::shared::Localization::MessageDnsTunnelingDisabled()); } + + EnableDnsTunneling = false; } } CATCH_LOG() @@ -474,9 +462,12 @@ void wsl::core::Config::Initialize(_In_opt_ HANDLE UserToken) EnableVirtio9p = false; } - if (NetworkingMode != NetworkingMode::Nat && NetworkingMode != NetworkingMode::Mirrored) + if (NetworkingMode != NetworkingMode::Nat && NetworkingMode != NetworkingMode::Mirrored && NetworkingMode != NetworkingMode::VirtioProxy) { - VALIDATE_CONFIG_OPTION((NetworkingMode != NetworkingMode::Nat && NetworkingMode != NetworkingMode::Mirrored), EnableDnsTunneling, false); + VALIDATE_CONFIG_OPTION( + (NetworkingMode != NetworkingMode::Nat && NetworkingMode != NetworkingMode::Mirrored && NetworkingMode != NetworkingMode::VirtioProxy), + EnableDnsTunneling, + false); } if (!EnableDnsTunneling) diff --git a/src/windows/common/helpers.cpp b/src/windows/common/helpers.cpp index 762b63c4e..f9542635d 100644 --- a/src/windows/common/helpers.cpp +++ b/src/windows/common/helpers.cpp @@ -475,6 +475,29 @@ bool wsl::windows::common::helpers::IsServicePresent(_In_ LPCWSTR ServiceName) return !!service; } +bool wsl::windows::common::helpers::IsServiceRunning(_In_ LPCWSTR ServiceName) +{ + const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)}; + if (!manager) + { + return false; + } + + const wil::unique_schandle service{OpenServiceW(manager.get(), ServiceName, SERVICE_QUERY_STATUS)}; + if (!service) + { + return false; + } + + SERVICE_STATUS status; + if (!QueryServiceStatus(service.get(), &status)) + { + return false; + } + + return status.dwCurrentState != SERVICE_STOPPED; +} + bool wsl::windows::common::helpers::IsVirtioSerialConsoleSupported() { // See if the Windows version has the required platform change. diff --git a/src/windows/common/helpers.hpp b/src/windows/common/helpers.hpp index 42ce25e56..ae9172c3c 100644 --- a/src/windows/common/helpers.hpp +++ b/src/windows/common/helpers.hpp @@ -153,6 +153,8 @@ bool IsPackageInstalled(_In_ LPCWSTR PackageFamilyName); bool IsServicePresent(_In_ LPCWSTR ServiceName); +bool IsServiceRunning(_In_ LPCWSTR ServiceName); + bool IsVirtioSerialConsoleSupported(); bool IsVmemmSuffixSupported(); diff --git a/src/windows/service/exe/HcsVirtualMachine.cpp b/src/windows/service/exe/HcsVirtualMachine.cpp index 7b4962661..b554128f0 100644 --- a/src/windows/service/exe/HcsVirtualMachine.cpp +++ b/src/windows/service/exe/HcsVirtualMachine.cpp @@ -43,7 +43,7 @@ HcsVirtualMachine::HcsVirtualMachine(_In_ const WSLCSessionSettings* Settings) THROW_IF_FAILED(CoCreateGuid(&m_vmId)); m_vmIdString = wsl::shared::string::GuidToString(m_vmId, wsl::shared::string::GuidToStringFlags::Uppercase); - m_featureFlags = static_cast(Settings->FeatureFlags); + m_featureFlags = Settings->FeatureFlags; m_networkingMode = Settings->NetworkingMode; m_bootTimeoutMs = Settings->BootTimeoutMs; @@ -369,11 +369,17 @@ try if (FeatureEnabled(WslcFeatureFlagsDnsTunneling)) { THROW_HR_IF(E_INVALIDARG, DnsSocket == nullptr); - THROW_HR_IF_MSG( - E_NOTIMPL, m_networkingMode == WSLCNetworkingModeVirtioProxy, "DNS tunneling not supported for VirtioProxy"); - THROW_IF_FAILED(wsl::core::networking::DnsResolver::LoadDnsResolverMethods()); - dnsSocketHandle.reset(reinterpret_cast(wslutil::DuplicateHandle(*DnsSocket))); + const auto result = wsl::core::networking::DnsResolver::LoadDnsResolverMethods(); + if (FAILED(result)) + { + LOG_HR_MSG(result, "Failed to load DNS resolver methods, DNS tunneling will be disabled"); + WI_ClearFlag(m_featureFlags, WslcFeatureFlagsDnsTunneling); + } + else + { + dnsSocketHandle.reset(reinterpret_cast(wslutil::DuplicateHandle(*DnsSocket))); + } } else { @@ -383,34 +389,41 @@ try if (m_networkingMode == WSLCNetworkingModeNAT) { // TODO: refactor this to avoid using wsl config - wsl::core::Config config(nullptr); - if (!wsl::core::NatNetworking::IsHyperVFirewallSupported(config)) + m_natConfig.emplace(nullptr); + if (!wsl::core::NatNetworking::IsHyperVFirewallSupported(*m_natConfig)) { - config.FirewallConfig.reset(); + m_natConfig->FirewallConfig.reset(); } // Enable DNS tunneling if a DNS socket was provided if (FeatureEnabled(WslcFeatureFlagsDnsTunneling)) { - config.EnableDnsTunneling = true; + WI_ASSERT(dnsSocketHandle); + + m_natConfig->EnableDnsTunneling = true; in_addr address{}; WI_VERIFY(inet_pton(AF_INET, LX_INIT_DNS_TUNNELING_IP_ADDRESS, &address) == 1); - config.DnsTunnelingIpAddress = address.S_un.S_addr; + m_natConfig->DnsTunnelingIpAddress = address.S_un.S_addr; } m_networkEngine = std::make_unique( m_computeSystem.get(), - wsl::core::NatNetworking::CreateNetwork(config), + wsl::core::NatNetworking::CreateNetwork(*m_natConfig), wsl::core::GnsChannel(std::move(gnsSocketHandle)), - config, + *m_natConfig, std::move(dnsSocketHandle), nullptr); } else if (m_networkingMode == WSLCNetworkingModeVirtioProxy) { wsl::core::VirtioNetworkingFlags flags = wsl::core::VirtioNetworkingFlags::Ipv6; + if (FeatureEnabled(WslcFeatureFlagsDnsTunneling)) + { + WI_SetFlag(flags, wsl::core::VirtioNetworkingFlags::DnsTunnelingSocket); + } + m_networkEngine = std::make_unique( - wsl::core::GnsChannel(std::move(gnsSocketHandle)), flags, nullptr, m_guestDeviceManager, m_userToken); + wsl::core::GnsChannel(std::move(gnsSocketHandle)), flags, nullptr, m_guestDeviceManager, m_userToken, std::move(dnsSocketHandle)); } else { diff --git a/src/windows/service/exe/HcsVirtualMachine.h b/src/windows/service/exe/HcsVirtualMachine.h index 4d4fd83f8..5f6cdf36d 100644 --- a/src/windows/service/exe/HcsVirtualMachine.h +++ b/src/windows/service/exe/HcsVirtualMachine.h @@ -20,6 +20,7 @@ Module Name: #include "GuestDeviceManager.h" #include "Dmesg.h" #include "INetworkingEngine.h" +#include "WslCoreConfig.h" #include #include @@ -79,6 +80,7 @@ class HcsVirtualMachine wil::unique_socket m_listenSocket; std::shared_ptr m_dmesgCollector; std::shared_ptr m_guestDeviceManager; + std::optional m_natConfig; std::unique_ptr m_networkEngine; wil::unique_event m_vmExitEvent{wil::EventOptions::ManualReset}; diff --git a/src/windows/service/exe/WslCoreVm.cpp b/src/windows/service/exe/WslCoreVm.cpp index 7553a9574..76a598bba 100644 --- a/src/windows/service/exe/WslCoreVm.cpp +++ b/src/windows/service/exe/WslCoreVm.cpp @@ -571,8 +571,9 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken { wsl::core::VirtioNetworkingFlags flags = wsl::core::VirtioNetworkingFlags::Ipv6; WI_SetFlagIf(flags, wsl::core::VirtioNetworkingFlags::LocalhostRelay, m_vmConfig.EnableLocalhostRelay); + WI_SetFlagIf(flags, wsl::core::VirtioNetworkingFlags::DnsTunnelingSocket, m_vmConfig.EnableDnsTunneling); m_networkingEngine = std::make_unique( - std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken); + std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken, std::move(dnsTunnelingSocket)); } else if (m_vmConfig.NetworkingMode == NetworkingMode::Bridged) { @@ -1890,7 +1891,9 @@ bool WslCoreVm::InitializeDrvFsLockHeld(_In_ HANDLE UserToken) bool WslCoreVm::IsDnsTunnelingSupported() const { - WI_ASSERT(m_vmConfig.NetworkingMode == NetworkingMode::Nat || m_vmConfig.NetworkingMode == NetworkingMode::Mirrored); + WI_ASSERT( + m_vmConfig.NetworkingMode == NetworkingMode::Nat || m_vmConfig.NetworkingMode == NetworkingMode::Mirrored || + m_vmConfig.NetworkingMode == NetworkingMode::VirtioProxy); return SUCCEEDED_LOG(wsl::core::networking::DnsResolver::LoadDnsResolverMethods()); } diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index 6f528c48a..9eb670041 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -42,6 +42,11 @@ SessionOptions::SessionOptions() { WI_SetFlag(m_sessionSettings.FeatureFlags, WslcFeatureFlagsVirtioFs); } + + if (settings::User().Get()) + { + WI_SetFlag(m_sessionSettings.FeatureFlags, WslcFeatureFlagsDnsTunneling); + } } bool SessionOptions::IsElevated() diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 36f4e1b46..0e4ca688d 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -114,6 +114,11 @@ namespace details { return std::nullopt; } + WSLC_VALIDATE_SETTING(SessionDnsTunneling) + { + return value; + } + #undef WSLC_VALIDATE_SETTING } // namespace details diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index f32185f14..a58b93ab4 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -41,6 +41,7 @@ enum class Setting : size_t SessionStoragePath, SessionNetworkingMode, SessionHostFileShareMode, + SessionDnsTunneling, Max }; @@ -81,6 +82,7 @@ namespace details { DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") DEFINE_SETTING_MAPPING(SessionNetworkingMode, std::string, WSLCNetworkingMode, WSLCNetworkingModeVirtioProxy, "session.networkingMode") DEFINE_SETTING_MAPPING(SessionHostFileShareMode, std::string, HostFileShareMode, HostFileShareMode::VirtioFs, "session.hostFileShareMode") + DEFINE_SETTING_MAPPING(SessionDnsTunneling, bool, bool, true, "session.dnsTunneling") #undef DEFINE_SETTING_MAPPING // clang-format on diff --git a/test/windows/NetworkTests.cpp b/test/windows/NetworkTests.cpp index b8a85ab5f..cc90e6890 100644 --- a/test/windows/NetworkTests.cpp +++ b/test/windows/NetworkTests.cpp @@ -5041,5 +5041,32 @@ class VirtioProxyTests m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .dnsTunneling = false})); NetworkTests::VerifyDnsResolutionRecordTypes(); } + + TEST_METHOD(DnsResolutionBasicDnsTunneling) + { + VIRTIOPROXY_TEST_ONLY(); + DNS_TUNNELING_TEST_ONLY(); + + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .dnsTunneling = true})); + NetworkTests::VerifyDnsResolutionBasic(); + } + + TEST_METHOD(DnsResolutionDigDnsTunneling) + { + VIRTIOPROXY_TEST_ONLY(); + DNS_TUNNELING_TEST_ONLY(); + + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .dnsTunneling = true})); + NetworkTests::VerifyDnsResolutionDig(); + } + + TEST_METHOD(DnsResolutionRecordTypesDnsTunneling) + { + VIRTIOPROXY_TEST_ONLY(); + DNS_TUNNELING_TEST_ONLY(); + + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .dnsTunneling = true})); + NetworkTests::VerifyDnsResolutionRecordTypes(); + } }; } // namespace NetworkTests diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 00bbba7d8..c6a915564 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -2326,6 +2326,12 @@ class WSLCTests VERIFY_ARE_EQUAL(result.Output[1], std::format("nameserver {}\n", LX_INIT_DNS_TUNNELING_IP_ADDRESS)); } + + // Verify DNS resolution. + // Note: without DNS tunneling, NAT mode uses the ICS SharedAccess DNS proxy which only supports UDP. + // TCP DNS queries (dig +tcp) will time out without tunneling. + VerifyDigDnsResolution(session.get(), "getent ahosts bing.com"); + VerifyDnsQueries(session.get(), mode, enableDnsTunneling); } TEST_METHOD(NATNetworking) @@ -2344,6 +2350,53 @@ class WSLCTests ValidateNetworking(WSLCNetworkingModeVirtioProxy); } + TEST_METHOD(VirtioProxyNetworkingWithDnsTunneling) + { + WINDOWS_11_TEST_ONLY(); + ValidateNetworking(WSLCNetworkingModeVirtioProxy, true); + } + + // DNS test helpers + + void VerifyDigDnsResolution(IWSLCSession* session, const std::string& digCommandLine) + { + auto result = ExpectCommandResult(session, {"/bin/sh", "-c", digCommandLine}, 0); + VERIFY_IS_FALSE(result.Output[1].empty()); + } + + void VerifyDnsQueries(IWSLCSession* session, WSLCNetworkingMode mode, bool enableDnsTunneling) + { + // TCP DNS works except for NAT without tunneling (ICS SharedAccess DNS proxy is UDP-only). + const bool includeTcp = (mode != WSLCNetworkingModeNAT) || enableDnsTunneling; + + // UDP queries for all record types + VerifyDigDnsResolution(session, "dig +short +time=5 A bing.com"); + VerifyDigDnsResolution(session, "dig +short +time=5 AAAA bing.com"); + VerifyDigDnsResolution(session, "dig +short +time=5 MX bing.com"); + VerifyDigDnsResolution(session, "dig +short +time=5 NS bing.com"); + VerifyDigDnsResolution(session, "dig +short +time=5 -x 8.8.8.8"); + VerifyDigDnsResolution(session, "dig +short +time=5 SOA bing.com"); + VerifyDigDnsResolution(session, "dig +short +time=5 TXT bing.com"); + VerifyDigDnsResolution(session, "dig +time=5 CNAME bing.com"); + VerifyDigDnsResolution(session, "dig +time=5 SRV bing.com"); + + if (includeTcp) + { + // ANY - dig expects a large response so it queries directly over TCP + VerifyDigDnsResolution(session, "dig +short +time=5 ANY bing.com"); + + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 A bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 AAAA bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 MX bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 NS bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 -x 8.8.8.8"); + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 SOA bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +short +time=5 TXT bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +time=5 CNAME bing.com"); + VerifyDigDnsResolution(session, "dig +tcp +time=5 SRV bing.com"); + } + } + void ValidatePortMapping(WSLCNetworkingMode networkingMode) { WSL2_TEST_ONLY();