Remove DnsTunnelingSocket flag from VirtioNetworking (#40443)

* Remove DnsTunnelingSocket flag from VirtioNetworking Drops the DnsTunnelingSocket virtio networking flag (and the dedicated DNS hvsocket plumbing it carried) in favor of the in-built DnsTunneling path. In virtio proxy mode, DNS queries are now always forwarded by the host virtio proxy itself: Linux's /etc/resolv.conf points at the eth0 gateway IP rather than the listener IP, no DNS hvsocket is opened, and the Linux init does not start a DnsTunnelingManager. Changes: - VirtioNetworking: remove DnsTunnelingSocket enum value, dnsHvsocket constructor parameter, m_dnsTunnelingResolver field, and the DnsResolver.h include. RefreshGuestConnection now uses only the in-built DnsTunneling path. - WslCoreVm: virtio proxy branch unconditionally sets the DnsTunneling flag when DNS tunneling is enabled. message->EnableDnsTunneling is suppressed for virtio proxy mode so Linux init won't open the DNS hvsocket. The pre-accepted dnsTunnelingSocket is dropped on the NAT->VirtioProxy fallback path. - HcsVirtualMachine: WSLC virtio proxy branch likewise switches to the DnsTunneling flag and discards the unused dnsSocketHandle. - Tests: add VirtioProxyTests::DnsTunnelingResolvConfUsesGateway which asserts resolv.conf contains the gateway IP and not the legacy listener IP, confirming the in-built path is in use. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Validate DNS hvsocket before DnsResolver support check Reorder ConfigureNetworking so the caller-provided DnsSocket is validated against the requested feature flag before the LoadDnsResolverMethods call may clear it. Avoids E_INVALIDARG in NAT mode when the support check fails. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Format VirtioNetworking constructor parameter list Wrap the constructor declaration/definition parameter list per the repo's BinPackParameters: false / 130-column style. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert manual wrapping; clang-format prefers single-line constructor Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed May 7, 2026 at 08:19 UTC 5f71bf7ec13da2618b356cf06e4be864d2795a2b
8 files changed +55 -47
src/windows/WslcSDK/wslcsdk.cpp
+1
@@ -442,6 +442,7 @@ try
442 }
443 runtimeSettings.FeatureFlags = ConvertFlags(internalType->featureFlags);
444 WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsVirtioFs);
445 + WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsDnsTunneling);
446
447 if (SUCCEEDED(errorInfoWrapper.CaptureResult(sessionManager->CreateSession(&runtimeSettings, WSLCSessionFlagsNone, &result->session))))
448 {
src/windows/common/VirtioNetworking.cpp
+1 -21
@@ -15,29 +15,13 @@ static constexpr auto c_eth0DeviceName = L"eth0";
15 static constexpr auto c_loopbackDeviceName = TEXT(LX_INIT_LOOPBACK_DEVICE_NAME);
16
17 VirtioNetworking::VirtioNetworking(
18 - GnsChannel&& gnsChannel,
19 - VirtioNetworkingFlags flags,
20 - LPCWSTR dnsOptions,
21 - std::shared_ptr<GuestDeviceManager> guestDeviceManager,
22 - wil::shared_handle userToken,
23 - wil::unique_socket&& dnsHvsocket) :
18 + GnsChannel&& gnsChannel, VirtioNetworkingFlags flags, LPCWSTR dnsOptions, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken) :
19 m_guestDeviceManager(std::move(guestDeviceManager)),
20 m_userToken(std::move(userToken)),
21 m_gnsChannel(std::move(gnsChannel)),
22 m_flags(flags),
23 m_dnsOptions(dnsOptions)
24 {
30 - THROW_HR_IF_MSG(
31 - E_INVALIDARG,
32 - ((!!dnsHvsocket != WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunnelingSocket)) ||
33 - (WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunnelingSocket) && WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunneling))),
34 - "Incompatible DNS settings");
35 -
36 - if (dnsHvsocket)
37 - {
38 - networking::DnsResolverFlags resolverFlags{};
39 - m_dnsTunnelingResolver.emplace(std::move(dnsHvsocket), resolverFlags);
40 - }
25 }
26
27 VirtioNetworking::~VirtioNetworking()
@@ -208,10 +192,6 @@ void VirtioNetworking::RefreshGuestConnection()
192 {
193 currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(default_route);
194 }
211 - else if (WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunnelingSocket))
212 - {
213 - currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(TEXT(LX_INIT_DNS_TUNNELING_IP_ADDRESS));
214 - }
195 else
196 {
197 wsl::core::networking::DnsSettingsFlags dnsFlags = networking::DnsSettingsFlags::IncludeVpn;
src/windows/common/VirtioNetworking.h
+1 -10
@@ -4,7 +4,6 @@
4
5 #include "INetworkingEngine.h"
6 #include "GnsChannel.h"
7 -#include "DnsResolver.h"
7 #include "WslCoreHostDnsInfo.h"
8 #include "GnsPortTrackerChannel.h"
9 #include "GuestDeviceManager.h"
@@ -17,20 +16,13 @@ enum class VirtioNetworkingFlags
16 LocalhostRelay = 0x1,
17 DnsTunneling = 0x2,
18 Ipv6 = 0x4,
20 - DnsTunnelingSocket = 0x8,
19 };
20 DEFINE_ENUM_FLAG_OPERATORS(VirtioNetworkingFlags);
21
22 class VirtioNetworking : public INetworkingEngine
23 {
24 public:
27 - VirtioNetworking(
28 - GnsChannel&& gnsChannel,
29 - VirtioNetworkingFlags flags,
30 - LPCWSTR dnsOptions,
31 - std::shared_ptr<GuestDeviceManager> guestDeviceManager,
32 - wil::shared_handle userToken,
33 - wil::unique_socket&& dnsHvsocket = {});
25 + VirtioNetworking(GnsChannel&& gnsChannel, VirtioNetworkingFlags flags, LPCWSTR dnsOptions, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken);
26
27 ~VirtioNetworking();
28
@@ -70,7 +62,6 @@ private:
62 std::shared_ptr<networking::NetworkSettings> m_networkSettings;
63 VirtioNetworkingFlags m_flags = VirtioNetworkingFlags::None;
64 LPCWSTR m_dnsOptions = nullptr;
73 - std::optional<networking::DnsResolver> m_dnsTunnelingResolver;
65 std::optional<GUID> m_localhostAdapterId;
66 std::optional<GUID> m_adapterId;
67
src/windows/service/exe/HcsVirtualMachine.cpp
+10 -10
@@ -372,24 +372,24 @@ try
372 // so we need our own copies to take ownership.
373 wil::unique_socket gnsSocketHandle{reinterpret_cast<SOCKET>(wslutil::DuplicateHandle(GnsSocket))};
374 wil::unique_socket dnsSocketHandle;
375 +
376 + // The DNS hvsocket is only allocated for NAT mode.
377 + THROW_HR_IF(E_INVALIDARG, (FeatureEnabled(WslcFeatureFlagsDnsTunneling) && m_networkingMode == WSLCNetworkingModeNAT) != (DnsSocket != nullptr));
378 +
379 + // The check still applies to virtio proxy because the host virtio proxy uses the same Windows DNS APIs.
380 if (FeatureEnabled(WslcFeatureFlagsDnsTunneling))
381 {
377 - THROW_HR_IF(E_INVALIDARG, DnsSocket == nullptr);
378 -
382 const auto result = wsl::core::networking::DnsResolver::LoadDnsResolverMethods();
383 if (FAILED(result))
384 {
385 LOG_HR_MSG(result, "Failed to load DNS resolver methods, DNS tunneling will be disabled");
386 WI_ClearFlag(m_featureFlags, WslcFeatureFlagsDnsTunneling);
387 }
385 - else
386 - {
387 - dnsSocketHandle.reset(reinterpret_cast<SOCKET>(wslutil::DuplicateHandle(*DnsSocket)));
388 - }
388 }
390 - else
389 +
390 + if (DnsSocket != nullptr && FeatureEnabled(WslcFeatureFlagsDnsTunneling))
391 {
392 - THROW_HR_IF(E_INVALIDARG, DnsSocket != nullptr);
392 + dnsSocketHandle.reset(reinterpret_cast<SOCKET>(wslutil::DuplicateHandle(*DnsSocket)));
393 }
394
395 if (m_networkingMode == WSLCNetworkingModeNAT)
@@ -425,11 +425,11 @@ try
425 wsl::core::VirtioNetworkingFlags flags = wsl::core::VirtioNetworkingFlags::Ipv6;
426 if (FeatureEnabled(WslcFeatureFlagsDnsTunneling))
427 {
428 - WI_SetFlag(flags, wsl::core::VirtioNetworkingFlags::DnsTunnelingSocket);
428 + WI_SetFlag(flags, wsl::core::VirtioNetworkingFlags::DnsTunneling);
429 }
430
431 m_networkEngine = std::make_unique<wsl::core::VirtioNetworking>(
432 - wsl::core::GnsChannel(std::move(gnsSocketHandle)), flags, nullptr, m_guestDeviceManager, m_userToken, std::move(dnsSocketHandle));
432 + wsl::core::GnsChannel(std::move(gnsSocketHandle)), flags, nullptr, m_guestDeviceManager, m_userToken);
433 }
434 else
435 {
src/windows/service/exe/WslCoreVm.cpp
+7 -4
@@ -512,7 +512,8 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
512 message->MemoryReclaimMode = static_cast<LX_MINI_INIT_MEMORY_RECLAIM_MODE>(m_vmConfig.MemoryReclaim);
513 message->EnableDebugShell = m_vmConfig.EnableDebugShell;
514 message->EnableSafeMode = m_vmConfig.EnableSafeMode;
515 - message->EnableDnsTunneling = m_vmConfig.EnableDnsTunneling;
515 + // Virtio proxy forwards DNS via the host proxy, so the dedicated DNS hvsocket is only used by NAT and Mirrored modes.
516 + message->EnableDnsTunneling = m_vmConfig.EnableDnsTunneling && m_vmConfig.NetworkingMode != NetworkingMode::VirtioProxy;
517 message->DefaultKernel = m_defaultKernel;
518 message->KernelModulesDeviceId = m_kernelModulesDeviceId;
519 message.WriteString(message->HostnameOffset, wsl::windows::common::filesystem::GetLinuxHostName());
@@ -571,9 +572,11 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
572 {
573 wsl::core::VirtioNetworkingFlags flags = wsl::core::VirtioNetworkingFlags::Ipv6;
574 WI_SetFlagIf(flags, wsl::core::VirtioNetworkingFlags::LocalhostRelay, m_vmConfig.EnableLocalhostRelay);
574 - WI_SetFlagIf(flags, wsl::core::VirtioNetworkingFlags::DnsTunnelingSocket, m_vmConfig.EnableDnsTunneling);
575 + WI_SetFlagIf(flags, wsl::core::VirtioNetworkingFlags::DnsTunneling, m_vmConfig.EnableDnsTunneling);
576 + // NAT may have fallen back to virtio proxy after the early-config message; drop the unused DNS hvsocket.
577 + dnsTunnelingSocket.reset();
578 m_networkingEngine = std::make_unique<wsl::core::VirtioNetworking>(
576 - std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken, std::move(dnsTunnelingSocket));
579 + std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken);
580 }
581 else if (m_vmConfig.NetworkingMode == NetworkingMode::Bridged)
582 {
@@ -2855,7 +2858,7 @@ void WslCoreVm::ValidateNetworkingMode()
2858 EMIT_USER_WARNING(Localization::MessageLocalhostForwardingNotSupportedMirroredMode());
2859 }
2860
2858 - // If DNS tunneling was requested, ensure it is supported by Windows.
2861 + // The DnsResolver support check still applies to virtio proxy because the host virtio proxy uses the same Windows DNS APIs.
2862 if (m_vmConfig.EnableDnsTunneling && !IsDnsTunnelingSupported())
2863 {
2864 // Since DNS tunneling is enabled by default, only show the warning if the user explicitly asked for it.
src/windows/wslcsession/WSLCVirtualMachine.cpp
+2 -1
@@ -341,7 +341,8 @@ void WSLCVirtualMachine::ConfigureNetworking()
341 std::vector<WSLCProcessFd> fds;
342 fds.emplace_back(WSLCProcessFd{.Fd = -1, .Type = WSLCFdType::WSLCFdTypeDefault});
343
344 - bool enableDnsTunneling = FeatureEnabled(WslcFeatureFlagsDnsTunneling);
344 + // Virtio proxy forwards DNS via the host proxy, so the DNS channel and /gns args are only needed for NAT mode.
345 + const bool enableDnsTunneling = FeatureEnabled(WslcFeatureFlagsDnsTunneling) && m_networkingMode != WSLCNetworkingModeVirtioProxy;
346 if (enableDnsTunneling)
347 {
348 fds.emplace_back(WSLCProcessFd{.Fd = -1, .Type = WSLCFdType::WSLCFdTypeDefault});
test/windows/NetworkTests.cpp
+17
@@ -5093,5 +5093,22 @@ class VirtioProxyTests
5093 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .dnsTunneling = true}));
5094 NetworkTests::VerifyDnsResolutionRecordTypes();
5095 }
5096 +
5097 + // Verifies that virtio proxy + dnsTunneling points resolv.conf at the gateway, not the hvsocket listener IP.
5098 + WSL2_TEST_METHOD(DnsTunnelingResolvConfUsesGateway)
5099 + {
5100 + VIRTIOPROXY_TEST_ONLY();
5101 + DNS_TUNNELING_TEST_ONLY();
5102 +
5103 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .dnsTunneling = true}));
5104 +
5105 + const auto state = NetworkTests::GetInterfaceState(L"eth0");
5106 + VERIFY_IS_TRUE(state.Gateway.has_value());
5107 +
5108 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep nameserver | grep -F " + state.Gateway.value()), static_cast<DWORD>(0));
5109 +
5110 + VERIFY_ARE_NOT_EQUAL(
5111 + LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep nameserver | grep -F " + c_dnsTunnelingDefaultIp), static_cast<DWORD>(0));
5112 + }
5113 };
5114 } // namespace NetworkTests
test/windows/WSLCTests.cpp
+16 -1
@@ -2900,7 +2900,22 @@ class WSLCTests
2900 {
2901 auto result = ExpectCommandResult(session.get(), {"/bin/grep", "-iF", "nameserver ", "/etc/resolv.conf"}, 0);
2902
2903 - VERIFY_ARE_EQUAL(result.Output[1], std::format("nameserver {}\n", LX_INIT_DNS_TUNNELING_IP_ADDRESS));
2903 + if (mode == WSLCNetworkingModeVirtioProxy)
2904 + {
2905 + // Virtio proxy points resolv.conf at the eth0 gateway.
2906 + ExpectCommandResult(
2907 + session.get(),
2908 + {"/bin/sh",
2909 + "-c",
2910 + "ns=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf); "
2911 + "gw=$(ip route show default | awk '{print $3; exit}'); "
2912 + "[ -n \"$ns\" ] && [ -n \"$gw\" ] && [ \"$ns\" = \"$gw\" ]"},
2913 + 0);
2914 + }
2915 + else
2916 + {
2917 + VERIFY_ARE_EQUAL(result.Output[1], std::format("nameserver {}\n", LX_INIT_DNS_TUNNELING_IP_ADDRESS));
2918 + }
2919 }
2920
2921 // Verify DNS resolution.