master
cpp 508 lines 18.3 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2
3 #include <LxssDynamicFunction.h>
4 #include "precomp.h"
5
6 #include <algorithm>
7
8 #include <iphlpapi.h>
9 #include <ipifcons.h>
10
11 #include "WmiService.h"
12 #include "WslCoreHostDnsInfo.h"
13
14 static constexpr auto c_asciiNewLine = "\xa";
15
16 // Used for querying suffixes via WMI
17 static constexpr auto c_suffixSearchList = L"SuffixSearchList";
18 static constexpr auto c_connectionSpecificSuffix = L"ConnectionSpecificSuffix";
19 static constexpr auto c_connectionSpecificSuffixSearchList = L"ConnectionSpecificSuffixSearchList";
20 static constexpr auto c_interfaceIndex = L"InterfaceIndex";
21
22 static constexpr auto c_ipHelperModuleName = L"Iphlpapi.dll";
23
24 static std::optional<LxssDynamicFunction<decltype(GetInterfaceDnsSettings)>> g_getInterfaceDnsSettings;
25 static std::optional<LxssDynamicFunction<decltype(FreeInterfaceDnsSettings)>> g_freeInterfaceDnsSettings;
26
27 struct DnsRegistryPath
28 {
29 const wchar_t* registryPath;
30 const bool isRecursive;
31 };
32
33 // Registry paths that need to be monitored for DNS suffix changes
34 constexpr DnsRegistryPath c_dnsSuffixesRegistryPaths[] = {
35 {L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\InterfaceSpecificParameters", true},
36 {L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", true},
37 {L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces", true},
38 {L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", false},
39 {L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters", false},
40 {L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient", false},
41 {L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters", false}};
42
43 // Load GetInterfaceDnsSettings and FreeInterfaceDnsSettings, if available
44 static bool LoadIpHelperMethods() noexcept
45 try
46 {
47 static wil::shared_hmodule ipHelperModule;
48 static std::once_flag loadFlag;
49
50 std::call_once(loadFlag, [&]() {
51 ipHelperModule.reset(LoadLibraryEx(c_ipHelperModuleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
52 RETURN_LAST_ERROR_IF_EXPECTED(!ipHelperModule);
53
54 LxssDynamicFunction<decltype(GetInterfaceDnsSettings)> local_getInterfaceDnsSettings{DynamicFunctionErrorLogs::None};
55 RETURN_IF_FAILED_EXPECTED(local_getInterfaceDnsSettings.load(ipHelperModule, "GetInterfaceDnsSettings"));
56 LxssDynamicFunction<decltype(FreeInterfaceDnsSettings)> local_freeInterfaceDnsSettings{DynamicFunctionErrorLogs::None};
57 RETURN_IF_FAILED_EXPECTED(local_freeInterfaceDnsSettings.load(ipHelperModule, "FreeInterfaceDnsSettings"));
58
59 g_getInterfaceDnsSettings.emplace(std::move(local_getInterfaceDnsSettings));
60 g_freeInterfaceDnsSettings.emplace(std::move(local_freeInterfaceDnsSettings));
61 return S_OK;
62 });
63
64 if (g_getInterfaceDnsSettings.has_value() && g_freeInterfaceDnsSettings.has_value())
65 {
66 return true;
67 }
68
69 WSL_LOG("LoadIpHelperMethods (false): GetInterfaceDnsSettings is not present");
70 return false;
71 }
72 catch (...)
73 {
74 LOG_CAUGHT_EXCEPTION();
75 return false;
76 }
77
78 DWORD wsl::core::networking::GetBestInterface()
79 {
80 DWORD bestInterface = 0;
81 SOCKADDR_STORAGE address{};
82 IN4ADDR_SETANY((SOCKADDR_IN*)&address);
83 if (GetBestInterfaceEx((SOCKADDR*)&address, &bestInterface) != NO_ERROR)
84 {
85 IN6ADDR_SETANY((SOCKADDR_IN6*)&address);
86 if (GetBestInterfaceEx((SOCKADDR*)&address, &bestInterface) != NO_ERROR)
87 {
88 bestInterface = 0;
89 }
90 }
91
92 WSL_LOG("wsl::core::networking::GetBestInterface [GetBestInterfaceEx]", TraceLoggingValue(bestInterface, "bestInterface"));
93
94 return bestInterface;
95 }
96
97 wsl::core::networking::DnsInfo wsl::core::networking::HostDnsInfo::GetDnsTunnelingSettings(const std::wstring& dnsTunnelingNameserver)
98 {
99 DnsInfo dnsInfo;
100 dnsInfo.Servers.push_back(wsl::shared::string::WideToMultiByte(dnsTunnelingNameserver));
101
102 // All Windows DNS suffixes are configured in Linux when DNS tunneling is enabled
103 dnsInfo.Domains = GetAllDnsSuffixes(AdapterAddresses::GetCurrent());
104
105 return dnsInfo;
106 }
107
108 std::vector<std::string> wsl::core::networking::HostDnsInfo::GetDnsServerStrings(
109 _In_ const PIP_ADAPTER_DNS_SERVER_ADDRESS& FirstDnsServer, _In_ USHORT IpFamilyFilter, _In_ USHORT MaxValues)
110 {
111 std::vector<std::string> DnsServerStrings;
112 CHAR IpBuffer[46];
113
114 PIP_ADAPTER_DNS_SERVER_ADDRESS DnsServer = FirstDnsServer;
115 while ((DnsServer != nullptr) && (DnsServerStrings.size() < MaxValues))
116 {
117 PVOID IpAddress = nullptr;
118 const USHORT IpFamily = DnsServer->Address.lpSockaddr->sa_family;
119 if (IpFamily == AF_INET)
120 {
121 IpAddress = &((sockaddr_in*)DnsServer->Address.lpSockaddr)->sin_addr;
122 }
123 else if (IpFamily == AF_INET6)
124 {
125 IpAddress = &((sockaddr_in6*)DnsServer->Address.lpSockaddr)->sin6_addr;
126 }
127
128 DnsServer = DnsServer->Next;
129 if (IpFamily != IpFamilyFilter)
130 {
131 continue;
132 }
133
134 THROW_LAST_ERROR_IF_MSG(
135 (InetNtopA(IpFamily, IpAddress, IpBuffer, ARRAYSIZE(IpBuffer)) == NULL), "Failed to convert IP address");
136
137 DnsServerStrings.push_back(IpBuffer);
138 }
139
140 return DnsServerStrings;
141 }
142
143 std::vector<std::string> wsl::core::networking::HostDnsInfo::GetInterfaceDnsServers(const std::vector<IpAdapterAddress>& AdapterAddresses, _In_ DnsSettingsFlags Flags)
144 {
145 std::vector<std::string> DnsServers;
146
147 constexpr size_t MaxResolvConfDnsServers = 3;
148 for (const IpAdapterAddress& NextAddress : AdapterAddresses)
149 {
150 WI_ASSERT(DnsServers.size() < MaxResolvConfDnsServers);
151
152 USHORT MaxDnsServers = static_cast<USHORT>(MaxResolvConfDnsServers - DnsServers.size());
153
154 // Include only primary DNS VPN server.
155 if ((MaxDnsServers > 1) && (wsl::core::networking::IsInterfaceTypeVpn(NextAddress->IfType)))
156 {
157 MaxDnsServers = 1;
158 }
159
160 // Add DNS nameservers from the interface, with the IPv4 addresses first.
161 std::vector<std::string> ipv4Servers = GetDnsServerStrings(NextAddress->FirstDnsServerAddress, AF_INET, MaxDnsServers);
162 DnsServers.insert(DnsServers.end(), ipv4Servers.begin(), ipv4Servers.end());
163
164 WI_ASSERT(DnsServers.size() <= MaxResolvConfDnsServers);
165
166 if (WI_IsFlagSet(Flags, DnsSettingsFlags::IncludeIpv6Servers))
167 {
168 std::vector<std::string> ipv6Servers = GetDnsServerStrings(
169 NextAddress->FirstDnsServerAddress, AF_INET6, static_cast<USHORT>(MaxResolvConfDnsServers - DnsServers.size()));
170
171 DnsServers.insert(DnsServers.end(), ipv6Servers.begin(), ipv6Servers.end());
172 }
173
174 WI_ASSERT(DnsServers.size() <= MaxResolvConfDnsServers);
175
176 // Only the first three nameserver entries are used.
177 if (DnsServers.size() >= MaxResolvConfDnsServers)
178 {
179 break;
180 }
181 }
182
183 return DnsServers;
184 }
185
186 std::vector<std::string> wsl::core::networking::HostDnsInfo::GetInterfaceDnsSuffixes(const std::vector<IpAdapterAddress>& AdapterAddresses)
187 {
188 std::vector<std::string> DnsSuffixes;
189 std::set<std::wstring> UniqueDnsSuffixes;
190
191 PIP_ADAPTER_DNS_SUFFIX DnsSuffix;
192
193 auto AppendSuffix = [&](const std::wstring& NewSuffix) {
194 if (NewSuffix.empty())
195 {
196 return;
197 }
198
199 if (std::ranges::find_if(UniqueDnsSuffixes, [&](const std::wstring& Suffix) {
200 return wsl::shared::string::IsEqual(Suffix, NewSuffix, true);
201 }) == UniqueDnsSuffixes.end())
202 {
203 DnsSuffixes.emplace_back(wsl::shared::string::WideToMultiByte(NewSuffix));
204 UniqueDnsSuffixes.insert(NewSuffix);
205 }
206 };
207
208 for (const IpAdapterAddress& NextAddress : AdapterAddresses)
209 {
210 // Add any domain suffix information from the interface.
211 if ((NextAddress->DnsSuffix != nullptr) && (wcslen(NextAddress->DnsSuffix) > 0))
212 {
213 AppendSuffix(NextAddress->DnsSuffix);
214 }
215
216 DnsSuffix = NextAddress->FirstDnsSuffix;
217 while (DnsSuffix != nullptr)
218 {
219 AppendSuffix(DnsSuffix->String);
220
221 DnsSuffix = DnsSuffix->Next;
222 }
223 }
224
225 return DnsSuffixes;
226 }
227
228 wsl::core::networking::DnsInfo wsl::core::networking::HostDnsInfo::GetDnsSettings(_In_ DnsSettingsFlags Flags)
229 {
230 std::vector<IpAdapterAddress> Addresses = AdapterAddresses::GetCurrent();
231
232 auto RemoveFilter = [&](const IpAdapterAddress& Address) {
233 // Ignore interfaces that are not currently "up".
234 // Ignore loopback and tunneling interfaces.
235 // Ignore interfaces that have no IP address or no DNS addresses.
236 // Ignore hidden interfaces
237 if ((Address->OperStatus != IfOperStatusUp) || (Address->IfType == IF_TYPE_SOFTWARE_LOOPBACK) || (Address->IfType == IF_TYPE_TUNNEL) ||
238 (!WI_IsFlagSet(Flags, DnsSettingsFlags::IncludeVpn) && wsl::core::networking::IsInterfaceTypeVpn(Address->IfType)) ||
239 (Address->FirstUnicastAddress == nullptr) || (Address->FirstDnsServerAddress == nullptr) || IsInterfaceHidden(Address->IfIndex))
240 {
241 return true;
242 }
243 return false;
244 };
245
246 std::erase_if(Addresses, RemoveFilter);
247
248 // Find the recommended internet interface if one exists.
249 // First try VPN interface, then regular IPv4 and then IPv6.
250 const auto BestInterface = GetBestInterface();
251
252 // Sort the remaining network interfaces, with the most preferable at index 0.
253 std::sort(Addresses.begin(), Addresses.end(), [&](const IpAdapterAddress& First, const IpAdapterAddress& Second) {
254 // VPN interface takes precedence.
255 const bool FirstIsVpn = wsl::core::networking::IsInterfaceTypeVpn(First->IfType);
256 const bool SecondIsVpn = wsl::core::networking::IsInterfaceTypeVpn(Second->IfType);
257 if (FirstIsVpn ^ SecondIsVpn)
258 {
259 // Give precedence to the first VPN interface.
260 return FirstIsVpn;
261 }
262
263 // The identified 'best' internet connection interface should go right
264 // after VPN. Or if both networking interfaces are VPN interfaces,
265 // give preference to the one considered 'best'.
266 if (First->IfIndex == BestInterface)
267 {
268 return true;
269 }
270 if (Second->IfIndex == BestInterface)
271 {
272 return false;
273 }
274
275 // Check the first interface for IPv4 DNS servers.
276 bool FirstHasIpv4 = false;
277 auto DnsServer = First->FirstDnsServerAddress;
278 while (DnsServer != nullptr)
279 {
280 const USHORT IpFamily = DnsServer->Address.lpSockaddr->sa_family;
281 DnsServer = DnsServer->Next;
282 if (IpFamily == AF_INET)
283 {
284 FirstHasIpv4 = true;
285 break;
286 }
287 }
288
289 // Check the second interface for IPv4 DNS servers.
290 bool SecondHasIpv4 = false;
291 DnsServer = Second->FirstDnsServerAddress;
292 while (DnsServer != nullptr)
293 {
294 const USHORT IpFamily = DnsServer->Address.lpSockaddr->sa_family;
295 DnsServer = DnsServer->Next;
296 if (IpFamily == AF_INET)
297 {
298 SecondHasIpv4 = true;
299 break;
300 }
301 }
302
303 // Give precedence to interfaces that have IPv4 DNS servers; otherwise, give precedence to the lower interface index.
304 return (FirstHasIpv4 ^ SecondHasIpv4) ? FirstHasIpv4 : (First->IfIndex < Second->IfIndex);
305 });
306
307 DnsInfo DnsSettings{};
308
309 DnsSettings.Servers = GetInterfaceDnsServers(Addresses, Flags);
310
311 if (WI_IsFlagSet(Flags, DnsSettingsFlags::IncludeAllSuffixes))
312 {
313 DnsSettings.Domains = GetAllDnsSuffixes(Addresses);
314 }
315 else
316 {
317 DnsSettings.Domains = GetInterfaceDnsSuffixes(Addresses);
318 }
319
320 return DnsSettings;
321 }
322
323 std::string wsl::core::networking::GenerateResolvConf(_In_ const DnsInfo& Info)
324 {
325 std::string contents{};
326 if (!Info.Servers.empty())
327 {
328 // Add IP addresses of the DNS name servers.
329 for (const std::string& ip : Info.Servers)
330 {
331 contents += "nameserver ";
332 contents += ip;
333 contents += c_asciiNewLine;
334 }
335
336 // Add DNS suffix information using 'search' directive.
337 // Per resolv.conf(5): "The domain directive is an obsolete name for the search directive
338 // that handles one search list entry only."
339 // See: https://man7.org/linux/man-pages/man5/resolv.conf.5.html
340 if (!Info.Domains.empty())
341 {
342 contents += "search ";
343 std::for_each(Info.Domains.begin(), (Info.Domains.end() - 1), [&contents](const std::string& NextDomain) {
344 contents += NextDomain;
345 contents += " ";
346 });
347
348 contents += Info.Domains.back();
349 contents += c_asciiNewLine;
350 }
351 }
352
353 WSL_LOG("wsl::core::networking::GenerateResolvConf", TraceLoggingValue(contents.c_str(), "resolvConf"));
354
355 return contents;
356 }
357
358 std::vector<std::string> wsl::core::networking::GetAllDnsSuffixes(const std::vector<IpAdapterAddress>& AdapterAddresses)
359 {
360 const auto com = InitializeCOMState();
361 wsl::core::WmiService service(L"ROOT\\StandardCimv2");
362
363 // DNS suffixes will be configured in Linux in the following order, *similar* (not 100% the same) to the order in which Windows tries suffixes.
364 //
365 // 1) Global suffixes (can be configured manually or via group policy) - queried using WMI call equivalent with Get-DnsClientGlobalSetting
366 // 2) Supplemental search list, queried using GetInterfaceDnsSettings()
367 // 3) Per-interface suffixes, queried using WMI call equivalent with Get-DnsClient
368 std::vector<std::string> dnsSuffixes;
369 std::set<std::wstring> uniqueDnsSuffixes;
370
371 auto AppendSuffix = [&](const std::wstring& newSuffix) {
372 if (newSuffix.empty())
373 {
374 return;
375 }
376
377 if (std::ranges::find_if(uniqueDnsSuffixes, [&](const std::wstring& suffix) {
378 return wsl::shared::string::IsEqual(suffix, newSuffix, true);
379 }) == uniqueDnsSuffixes.end())
380 {
381 dnsSuffixes.emplace_back(wsl::shared::string::WideToMultiByte(newSuffix));
382 uniqueDnsSuffixes.insert(newSuffix);
383 }
384 };
385
386 // 1) Query global suffixes
387 wsl::core::WmiEnumerate enumDnsClientGlobalSetting(service);
388
389 for (const auto& instance : enumDnsClientGlobalSetting.query(L"SELECT * FROM MSFT_DnsClientGlobalSetting"))
390 {
391 std::vector<std::wstring> suffixSearchList;
392
393 instance.get(c_suffixSearchList, &suffixSearchList);
394
395 for (const auto& suffix : suffixSearchList)
396 {
397 AppendSuffix(suffix);
398 }
399 }
400
401 // 2) Query supplemental search list. Skip this step if the OS does not support the required APIs
402 if (LoadIpHelperMethods())
403 {
404 for (const auto& address : AdapterAddresses)
405 {
406 if (IsInterfaceHidden(address->IfIndex))
407 {
408 continue;
409 }
410
411 GUID interfaceGuid{};
412 if (FAILED_WIN32_LOG(ConvertInterfaceLuidToGuid(&address->Luid, &interfaceGuid)))
413 {
414 continue;
415 }
416
417 DNS_INTERFACE_SETTINGS_EX settings{};
418 settings.SettingsV1.Version = DNS_INTERFACE_SETTINGS_VERSION2;
419 settings.SettingsV1.Flags = DNS_SETTING_SUPPLEMENTAL_SEARCH_LIST;
420
421 if (FAILED_WIN32_LOG(g_getInterfaceDnsSettings.value()(interfaceGuid, reinterpret_cast<DNS_INTERFACE_SETTINGS*>(&settings))))
422 {
423 continue;
424 }
425
426 const auto freeSettings =
427 wil::scope_exit([&] { g_freeInterfaceDnsSettings.value()(reinterpret_cast<DNS_INTERFACE_SETTINGS*>(&settings)); });
428
429 if (settings.SupplementalSearchList != nullptr)
430 {
431 // The suffix list can be delimited by comma, space, tab
432 std::wstring separators = L", \t";
433
434 for (const auto& suffix :
435 wsl::shared::string::SplitByMultipleSeparators(std::wstring{settings.SupplementalSearchList}, separators))
436 {
437 AppendSuffix(suffix);
438 }
439 }
440 }
441 }
442
443 // 3) Query per-interface suffixes
444 wsl::core::WmiEnumerate enumDnsClient(service);
445
446 for (const auto& instance : enumDnsClient.query(L"SELECT * FROM MSFT_DnsClient"))
447 {
448 std::wstring connectionSuffix;
449 std::vector<std::wstring> connectionSuffixSearchList;
450 IF_INDEX interfaceIndex{};
451
452 instance.get(c_interfaceIndex, &interfaceIndex);
453 if (IsInterfaceHidden(interfaceIndex))
454 {
455 continue;
456 }
457
458 instance.get(c_connectionSpecificSuffix, &connectionSuffix);
459 instance.get(c_connectionSpecificSuffixSearchList, &connectionSuffixSearchList);
460
461 AppendSuffix(connectionSuffix);
462
463 for (const auto& suffix : connectionSuffixSearchList)
464 {
465 AppendSuffix(suffix);
466 }
467 }
468
469 return dnsSuffixes;
470 }
471
472 wsl::core::networking::DnsSuffixRegistryWatcher::DnsSuffixRegistryWatcher(RegistryChangeCallback&& reportRegistryChange) :
473 m_reportRegistryChange(std::move(reportRegistryChange))
474 {
475 std::vector<wistd::unique_ptr<wsl::windows::common::slim_registry_watcher>> localRegistryWatchers;
476
477 for (const auto& path : c_dnsSuffixesRegistryPaths)
478 {
479 auto watcher = wil::make_unique_nothrow<wsl::windows::common::slim_registry_watcher>();
480 THROW_HR_IF(E_OUTOFMEMORY, !watcher);
481
482 THROW_IF_FAILED(watcher->create(HKEY_LOCAL_MACHINE, path.registryPath, path.isRecursive, [this](wil::RegistryChangeKind changeKind) {
483 m_reportRegistryChange();
484 }));
485 localRegistryWatchers.emplace_back(std::move(watcher));
486 }
487
488 m_registryWatchers.swap(localRegistryWatchers);
489 }
490
491 wsl::shared::hns::DNS wsl::core::networking::BuildDnsNotification(const DnsInfo& settings, PCWSTR options)
492 {
493 wsl::shared::hns::DNS dnsNotification{};
494 if (options)
495 {
496 dnsNotification.Options = options;
497 }
498
499 dnsNotification.ServerList = wsl::shared::string::MultiByteToWide(wsl::shared::string::Join(settings.Servers, ','));
500
501 // Use 'search' entry for DNS suffix list.
502 // Per resolv.conf(5): "The domain directive is an obsolete name for the search directive
503 // that handles one search list entry only."
504 // See: https://man7.org/linux/man-pages/man5/resolv.conf.5.html
505 dnsNotification.Search = wsl::shared::string::MultiByteToWide(wsl::shared::string::Join(settings.Domains, ','));
506
507 return dnsNotification;
508 }