master
h 679 lines 23.3 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2
3 #pragma once
4 #include <optional>
5 #include <variant>
6 #include <vector>
7
8 #include <windows.h>
9 #include <ComputeNetwork.h>
10 #include <mstcpip.h>
11 #include <netioapi.h>
12 #include <netlistmgr.h>
13 #include <nldef.h>
14 #include <wlantypes.h>
15 #include <ws2ipdef.h>
16
17 #include "WslCoreConfig.h"
18 #include "WslTelemetry.h"
19 #include "hcs.hpp"
20
21 #include <wil/resource.h>
22
23 // Global operator overloads
24 // enabling usage of common Networking data structures within STL containers
25
26 inline bool operator==(const DOT11_SSID& lhs, const DOT11_SSID& rhs) noexcept
27 {
28 if (lhs.uSSIDLength == rhs.uSSIDLength)
29 {
30 return (0 == memcmp(lhs.ucSSID, rhs.ucSSID, lhs.uSSIDLength));
31 }
32 return false;
33 }
34
35 inline bool operator!=(const DOT11_SSID& lhs, const DOT11_SSID& rhs) noexcept
36 {
37 return !(lhs == rhs);
38 }
39
40 inline bool operator==(const NL_NETWORK_CONNECTIVITY_HINT& lhs, const NL_NETWORK_CONNECTIVITY_HINT& rhs) noexcept
41 {
42 return lhs.ApproachingDataLimit == rhs.ApproachingDataLimit && lhs.ConnectivityCost == rhs.ConnectivityCost &&
43 lhs.ConnectivityLevel == rhs.ConnectivityLevel && lhs.OverDataLimit == rhs.OverDataLimit && lhs.Roaming == rhs.Roaming;
44 }
45
46 inline bool operator!=(const NL_NETWORK_CONNECTIVITY_HINT& lhs, const NL_NETWORK_CONNECTIVITY_HINT& rhs) noexcept
47 {
48 return !(lhs == rhs);
49 }
50
51 inline bool operator==(const SOCKADDR_INET& lhs, const SOCKADDR_INET& rhs) noexcept
52 {
53 // not using INETADDR_ISEQUAL, because we can't compare the scopeId value from the v6 address
54 // that's the interface index on the host
55
56 if (lhs.si_family != rhs.si_family)
57 {
58 return false;
59 }
60 if (lhs.si_family == AF_INET)
61 {
62 return IN4_ADDR_EQUAL(&lhs.Ipv4.sin_addr, &rhs.Ipv4.sin_addr);
63 }
64 return IN6_ADDR_EQUAL(&lhs.Ipv6.sin6_addr, &rhs.Ipv6.sin6_addr);
65 }
66
67 inline bool operator<(const SOCKADDR_INET& lhs, const SOCKADDR_INET& rhs) noexcept
68 {
69 if (lhs.si_family == rhs.si_family)
70 {
71 if (lhs.si_family == AF_INET)
72 {
73 return lhs.Ipv4.sin_addr.S_un.S_addr < rhs.Ipv4.sin_addr.S_un.S_addr;
74 }
75
76 // implementing the comparison operation following the shortcut from mstcpip.h IN6_ADDR_EQUAL
77 const __int64 UNALIGNED* lhsRawPointer = (__int64 UNALIGNED*)(&lhs.Ipv6.sin6_addr);
78 const __int64 UNALIGNED* rhsRawPointer = (__int64 UNALIGNED*)(&rhs.Ipv6.sin6_addr);
79 if (lhsRawPointer[0] == rhsRawPointer[0])
80 {
81 return lhsRawPointer[1] < rhsRawPointer[1];
82 }
83 return lhsRawPointer[0] < rhsRawPointer[0];
84 }
85 return lhs.si_family < rhs.si_family;
86 }
87
88 inline bool operator>(const SOCKADDR_INET& lhs, const SOCKADDR_INET& rhs) noexcept
89 {
90 if (lhs.si_family == rhs.si_family)
91 {
92 if (lhs.si_family == AF_INET)
93 {
94 return lhs.Ipv4.sin_addr.S_un.S_addr > rhs.Ipv4.sin_addr.S_un.S_addr;
95 }
96
97 // implementing the comparison operation following the shortcut from mstcpip.h IN6_ADDR_EQUAL
98 const __int64 UNALIGNED* lhsRawPointer = (__int64 UNALIGNED*)(&lhs.Ipv6.sin6_addr);
99 const __int64 UNALIGNED* rhsRawPointer = (__int64 UNALIGNED*)(&rhs.Ipv6.sin6_addr);
100 if (lhsRawPointer[0] == rhsRawPointer[0])
101 {
102 return lhsRawPointer[1] > rhsRawPointer[1];
103 }
104 return lhsRawPointer[0] > rhsRawPointer[0];
105 }
106 return lhs.si_family > rhs.si_family;
107 }
108
109 inline bool operator==(const IP_ADDRESS_PREFIX& lhs, const IP_ADDRESS_PREFIX& rhs) noexcept
110 {
111 return lhs.PrefixLength == rhs.PrefixLength && lhs.Prefix == rhs.Prefix;
112 }
113
114 inline bool operator<(const IP_ADDRESS_PREFIX& lhs, const IP_ADDRESS_PREFIX& rhs) noexcept
115 {
116 if (lhs.PrefixLength == rhs.PrefixLength)
117 {
118 return lhs.Prefix < rhs.Prefix;
119 }
120 return lhs.PrefixLength < rhs.PrefixLength;
121 }
122
123 inline bool operator>(const IP_ADDRESS_PREFIX& lhs, const IP_ADDRESS_PREFIX& rhs) noexcept
124 {
125 if (lhs.PrefixLength == rhs.PrefixLength)
126 {
127 return lhs.Prefix > rhs.Prefix;
128 }
129 return lhs.PrefixLength > rhs.PrefixLength;
130 }
131
132 namespace wsl::core::networking {
133
134 inline constexpr auto* c_ipv4TestRequestTarget = L"www.msftconnecttest.com";
135 inline constexpr auto* c_ipv4TestRequestTargetA = "www.msftconnecttest.com";
136 inline constexpr auto* c_ipv6TestRequestTarget = L"ipv6.msftconnecttest.com";
137 inline constexpr auto* c_ipv6TestRequestTargetA = "ipv6.msftconnecttest.com";
138
139 inline HRESULT GetGnsCallbackResult(LX_MESSAGE_TYPE messageType, HRESULT transportResult, int linuxResultCode) noexcept
140 {
141 if (FAILED(transportResult) || messageType == LxGnsMessageConnectTestRequest || linuxResultCode == 0)
142 {
143 return transportResult;
144 }
145
146 return E_FAIL;
147 }
148
149 inline constexpr GUID c_wslFirewallVmCreatorId = {0x40E0AC32, 0x46A5, 0x438A, {0xA0, 0xB2, 0x2B, 0x47, 0x9E, 0x8F, 0x2E, 0x90}};
150
151 inline constexpr auto c_networkAdapterPrefix = L"VirtualMachine/Devices/NetworkAdapters/";
152 inline constexpr auto c_interfaceConstraintKey = L"ExternalInterfaceConstraint";
153
154 // RAII types to manage resources returned from NetIO APIs
155 using unique_notify_handle = wil::unique_any<HANDLE, decltype(CancelMibChangeNotify2), &CancelMibChangeNotify2>;
156 using unique_interface_table = wil::unique_any<PMIB_IPINTERFACE_TABLE, decltype(FreeMibTable), &FreeMibTable>;
157 using unique_address_table = wil::unique_any<PMIB_UNICASTIPADDRESS_TABLE, decltype(FreeMibTable), &FreeMibTable>;
158 using unique_forward_table = wil::unique_any<PMIB_IPFORWARD_TABLE2, decltype(FreeMibTable), &FreeMibTable>;
159 using unique_ifstack_table = wil::unique_any<PMIB_IFSTACK_TABLE, decltype(FreeMibTable), &FreeMibTable>;
160
161 // Ensures COM is initialized on the current thread. Tolerates the case where
162 // COM is already initialized (even with a different apartment type or security) since this
163 // can happen on RPC threads or callback threads.
164 inline wil::unique_couninitialize_call InitializeCOMState()
165 {
166 wil::unique_couninitialize_call coInit;
167 auto hr = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
168 if (SUCCEEDED(hr))
169 {
170 // Ignore error if CoInitializeSecurity has already been invoked
171 hr = CoInitializeSecurity(
172 nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_STATIC_CLOAKING, nullptr);
173
174 if (hr == RPC_E_TOO_LATE)
175 {
176 hr = S_OK;
177 }
178 }
179 else if (hr == RPC_E_CHANGED_MODE)
180 {
181 // COM already initialized by someone else - disarm so we don't uninitialize their COM
182 coInit.release();
183 hr = S_OK;
184 }
185
186 THROW_IF_FAILED(hr);
187 return coInit;
188 }
189
190 inline bool IsInterfaceTypeVpn(IFTYPE type) noexcept
191 {
192 return type == IF_TYPE_PPP || type == IF_TYPE_PROP_VIRTUAL;
193 }
194
195 inline bool IsInterfaceHidden(IF_INDEX InterfaceIndex)
196 {
197 NL_NETWORK_CONNECTIVITY_HINT ConnectivityHint;
198
199 // Return true if we fail to retrieve the interface information
200 if (GetNetworkConnectivityHintForInterface(InterfaceIndex, &ConnectivityHint) != NO_ERROR)
201 {
202 return true;
203 }
204 return ConnectivityHint.ConnectivityLevel == NetworkConnectivityLevelHintHidden;
205 }
206
207 inline bool IsMulticastOrBroadcastIpAddress(const SOCKADDR_INET& address)
208 {
209 switch (address.si_family)
210 {
211 case AF_INET:
212 return IN4_IS_ADDR_MULTICAST(&address.Ipv4.sin_addr) || IN4_IS_ADDR_BROADCAST(&address.Ipv4.sin_addr);
213 case AF_INET6:
214 return IN6_IS_ADDR_MULTICAST(&address.Ipv6.sin6_addr);
215 }
216 return false;
217 }
218
219 inline bool IsLoopbackIpAddress(const SOCKADDR_INET& address)
220 {
221 switch (address.si_family)
222 {
223 case AF_INET:
224 return IN4_IS_ADDR_LOOPBACK(&address.Ipv4.sin_addr);
225 case AF_INET6:
226 return IN6_IS_ADDR_LOOPBACK(&address.Ipv6.sin6_addr);
227 }
228 return false;
229 }
230
231 inline bool IsNetworkErrorForMissingServices(HRESULT hr) noexcept
232 {
233 switch (hr)
234 {
235 case HCS_E_SERVICE_NOT_AVAILABLE:
236 case HRESULT_FROM_WIN32(RPC_S_CALL_FAILED):
237 case HRESULT_FROM_WIN32(EPT_S_NOT_REGISTERED):
238 case HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_FOUND):
239 case HRESULT_FROM_WIN32(ERROR_SERVICE_DOES_NOT_EXIST):
240 case HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED):
241 return true;
242 }
243 return false;
244 }
245
246 inline std::string ToString(const NLM_CONNECTIVITY& nlmConnectivity)
247 {
248 if (nlmConnectivity == NLM_CONNECTIVITY_DISCONNECTED)
249 {
250 return "Disconnected";
251 }
252
253 std::string returnString;
254 if (nlmConnectivity & NLM_CONNECTIVITY_IPV4_NOTRAFFIC)
255 {
256 returnString += " IPv4NoTraffic";
257 }
258 if (nlmConnectivity & NLM_CONNECTIVITY_IPV6_NOTRAFFIC)
259 {
260 returnString += " IPv6NoTraffic";
261 }
262 if (nlmConnectivity & NLM_CONNECTIVITY_IPV4_SUBNET)
263 {
264 returnString += " IPv4Subnet";
265 }
266 if (nlmConnectivity & NLM_CONNECTIVITY_IPV4_LOCALNETWORK)
267 {
268 returnString += " IPv4Local";
269 }
270 if (nlmConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET)
271 {
272 returnString += " IPv4Internet";
273 }
274 if (nlmConnectivity & NLM_CONNECTIVITY_IPV6_SUBNET)
275 {
276 returnString += " IPv6Subnet";
277 }
278 if (nlmConnectivity & NLM_CONNECTIVITY_IPV6_LOCALNETWORK)
279 {
280 returnString += " IPv6Local";
281 }
282 if (nlmConnectivity & NLM_CONNECTIVITY_IPV6_INTERNET)
283 {
284 returnString += " IPv6Internet";
285 }
286
287 return returnString;
288 }
289
290 enum class UpdateEndpointFlag
291 {
292 None,
293 Default,
294 ResendInitialUpdate,
295 ForceUpdate,
296 ForceIpUpdate,
297 BlockClientUpdates,
298 };
299
300 inline PCSTR ToString(UpdateEndpointFlag flag) noexcept
301 {
302 switch (flag)
303 {
304 case UpdateEndpointFlag::None:
305 return "None";
306 case UpdateEndpointFlag::Default:
307 return "Default";
308 case UpdateEndpointFlag::ResendInitialUpdate:
309 return "ResendInitialUpdate";
310 case UpdateEndpointFlag::ForceUpdate:
311 return "ForceUpdate";
312 case UpdateEndpointFlag::ForceIpUpdate:
313 return "ForceIpUpdate";
314 case UpdateEndpointFlag::BlockClientUpdates:
315 return "BlockClientUpdates";
316 default:
317 return "<unknown UpdateEndpointFlag>";
318 }
319 }
320
321 // mapping wsl::shared::hns::* structures to the corresponding message type to send to GNS
322 constexpr LX_MESSAGE_TYPE GnsMessageType(const wsl::shared::hns::VmNicCreatedNotification&) noexcept
323 {
324 return LxGnsMessageVmNicCreatedNotification;
325 }
326
327 constexpr LX_MESSAGE_TYPE GnsMessageType(const wsl::shared::hns::CreateDeviceRequest&) noexcept
328 {
329 return LxGnsMessageCreateDeviceRequest;
330 }
331
332 constexpr LX_MESSAGE_TYPE GnsMessageType(const wsl::shared::hns::LoopbackRoutesRequest&) noexcept
333 {
334 return LxGnsMessageLoopbackRoutesRequest;
335 }
336
337 constexpr LX_MESSAGE_TYPE GnsMessageType(const wsl::shared::hns::ModifyGuestDeviceSettingRequest&) noexcept
338 {
339 return LxGnsMessageModifyGuestDeviceSettingRequest;
340 }
341
342 constexpr LX_MESSAGE_TYPE GnsMessageType(const wsl::shared::hns::InitialIpConfigurationNotification&) noexcept
343 {
344 return LxGnsMessageInitialIpConfigurationNotification;
345 }
346
347 inline bool IsInterfaceIndexOfGelnic(DWORD InterfaceIndex) noexcept
348 {
349 // Currently the GELNIC is indicated from HNS as an endpoint with interface index 0.
350 static constexpr DWORD c_InterfaceIndexGelnic = 0;
351 return InterfaceIndex == c_InterfaceIndexGelnic;
352 }
353
354 struct CurrentInterfaceInformation
355 {
356 CurrentInterfaceInformation() = default;
357
358 CurrentInterfaceInformation(
359 const GUID& preferredGuid, const NET_LUID& preferredLuid, IFTYPE preferredType, std::wstring preferredName, std::wstring interfaceDescription, bool metered) :
360 m_interfaceType(preferredType),
361 m_interfaceName(std::move(preferredName)),
362 m_interfaceDescription(std::move(interfaceDescription)),
363 m_interfaceGuid(preferredGuid),
364 m_interfaceLuid(preferredLuid),
365 m_metered(metered)
366 {
367 }
368
369 IFTYPE m_interfaceType{IF_TYPE_OTHER}; // == 1 == minimum iftype
370 std::wstring m_interfaceName;
371 std::wstring m_interfaceDescription;
372 std::optional<GUID> m_interfaceGuid{std::nullopt};
373 std::optional<NET_LUID> m_interfaceLuid{std::nullopt};
374 bool m_metered{false};
375 };
376
377 inline std::vector<GUID> EnumerateNetworks(std::optional<wsl::shared::hns::NetworkFlags> queryFlags = {})
378 {
379 std::wstring queryString;
380 if (queryFlags)
381 {
382 wsl::shared::hns::HostComputeQuery query{};
383 query.Filter = std::format("{{\"Flags\": {}}}", static_cast<uint32_t>(queryFlags.value()));
384 queryString = wsl::shared::ToJsonW(query);
385 }
386
387 wil::unique_cotaskmem_string response;
388 wil::unique_cotaskmem_string error;
389 const auto result = ::HcnEnumerateNetworks(queryString.empty() ? nullptr : queryString.c_str(), &response, &error);
390 THROW_IF_FAILED_MSG(result, "HcnEnumerateNetworks(%ls) %ls", queryString.empty() ? nullptr : queryString.c_str(), error.get());
391
392 return wsl::shared::FromJson<std::vector<GUID>>(response.get());
393 }
394
395 inline std::vector<GUID> EnumerateEndpointsByNetworkId(const GUID& networkId)
396 {
397 const std::wstring queryString = std::format(
398 L"{{\"Filter\": \"{{\\\"VirtualNetwork\\\": \\\"{}\\\"}}\"}}",
399 wsl::shared::string::GuidToString<wchar_t>(networkId, wsl::shared::string::GuidToStringFlags::None));
400
401 wil::unique_cotaskmem_string endpointsJson;
402 wil::unique_cotaskmem_string errorJson;
403 const auto result = HcnEnumerateEndpoints(queryString.c_str(), &endpointsJson, &errorJson);
404 THROW_IF_FAILED_MSG(result, "HcnEnumerateEndpoints failed: %ls, query: '%ls'", errorJson.get(), queryString.c_str());
405 return wsl::shared::FromJson<std::vector<GUID>>(endpointsJson.get());
406 }
407
408 inline std::vector<GUID> EnumerateMirroredNetworksAndHyperVFirewall(bool enableFirewall)
409 {
410 auto flags = wsl::shared::hns::NetworkFlags::EnableNonPersistent | wsl::shared::hns::NetworkFlags::EnableFlowSteering;
411 WI_SetFlagIf(flags, wsl::shared::hns::NetworkFlags::EnableFirewall, enableFirewall);
412 std::vector<GUID> networkIds = EnumerateNetworks(flags);
413 for (auto& id : networkIds)
414 {
415 WSL_LOG(
416 "EnumerateMirroredNetworksAndHyperVFirewall",
417 TraceLoggingValue(static_cast<uint32_t>(flags), "flags"),
418 TraceLoggingValue(id, "networkId"));
419 }
420
421 return networkIds;
422 }
423
424 inline wsl::windows::common::hcs::unique_hcn_network OpenNetwork(const GUID& networkId)
425 {
426 wsl::windows::common::hcs::unique_hcn_network network;
427 wil::unique_cotaskmem_string error;
428 const auto result = ::HcnOpenNetwork(networkId, &network, &error);
429 THROW_IF_FAILED_MSG(result, "HcnOpenNetwork %ls", error.get());
430
431 return network;
432 }
433
434 inline std::pair<wsl::shared::hns::HNSNetwork, wil::unique_cotaskmem_string> QueryNetworkProperties(HCN_NETWORK network)
435 {
436 wil::unique_cotaskmem_string properties;
437 wil::unique_cotaskmem_string error;
438 const auto result = ::HcnQueryNetworkProperties(network, nullptr, &properties, &error);
439 THROW_IF_FAILED_MSG(result, "HcnQueryNetworkProperties %ls", error.get());
440
441 auto parsed = wsl::shared::FromJson<wsl::shared::hns::HNSNetwork>(properties.get());
442 return {std::move(parsed), std::move(properties)};
443 }
444
445 struct EphemeralHcnEndpoint
446 {
447 EphemeralHcnEndpoint()
448 {
449 THROW_IF_FAILED(CoCreateGuid(&Id));
450 }
451
452 EphemeralHcnEndpoint(const EphemeralHcnEndpoint&) = delete;
453 EphemeralHcnEndpoint(EphemeralHcnEndpoint&&) = default;
454
455 EphemeralHcnEndpoint& operator=(const EphemeralHcnEndpoint&) = delete;
456 EphemeralHcnEndpoint& operator=(EphemeralHcnEndpoint&&) = default;
457
458 windows::common::hcs::unique_hcn_endpoint Endpoint;
459 GUID Id{};
460
461 ~EphemeralHcnEndpoint()
462 {
463 if (Endpoint)
464 {
465 wil::unique_cotaskmem_string error;
466 const auto result = HcnDeleteEndpoint(Id, &error);
467 LOG_IF_FAILED_MSG(result, "HcnDeleteEndpoint failed: %ls", error.get());
468 }
469 }
470 };
471
472 /// <summary>
473 /// Returns true if the host supports flow steering.
474 /// </summary>
475 bool IsFlowSteeringSupportedByHns() noexcept;
476
477 EphemeralHcnEndpoint CreateEphemeralHcnEndpoint(HCN_NETWORK network, const wsl::shared::hns::HostComputeEndpoint& endpointSettings);
478
479 std::vector<wsl::core::networking::CurrentInterfaceInformation> EnumerateConnectedInterfaces();
480
481 bool IsMetered(ABI::Windows::Networking::Connectivity::NetworkCostType cost) noexcept;
482
483 /// <summary>
484 /// Gets the minimum MTU across all connected network interfaces.
485 /// </summary>
486 std::optional<ULONG> GetMinimumConnectedInterfaceMtu() noexcept;
487
488 /// <summary>
489 /// This instance acts as an IP_ADAPTER_ADDRESS pointer.
490 /// </summary>
491 class AdapterAddresses;
492
493 /// <summary>
494 /// IP_ADAPTER_ADDRESSES wrapper that maintains a reference to the buffer
495 /// returned by GetAdaptersAddresses such that this instance always points to
496 /// valid data.
497 /// </summary>
498 class IpAdapterAddress
499 {
500 public:
501 /// <summary>
502 /// Instance constructor.
503 /// </summary>
504 IpAdapterAddress(const std::shared_ptr<AdapterAddresses>& AddressContainer, const IP_ADAPTER_ADDRESSES* Address) :
505 m_container(AddressContainer), m_address(Address)
506 {
507 }
508
509 /// <summary>
510 /// This instance acts as an IP_ADAPTER_ADDRESS pointer.
511 /// </summary>
512 const IP_ADAPTER_ADDRESSES* operator->() const noexcept
513 {
514 return m_address;
515 }
516
517 private:
518 /// <summary>
519 /// Reference to the buffer holding the IP_ADAPTER_ADDRESSES data.
520 /// </summary>
521 std::shared_ptr<AdapterAddresses> m_container{};
522
523 /// <summary>
524 /// Pointer into the buffer where this specific IP_ADAPTER_ADDRESSES
525 /// instance begins.
526 /// </summary>
527 const IP_ADAPTER_ADDRESSES* m_address{};
528 };
529
530 /// <summary>
531 /// GetAdaptersAddresses wrapper
532 /// </summary>
533 class AdapterAddresses : public std::enable_shared_from_this<AdapterAddresses>
534 {
535 public:
536 /// <summary>
537 /// Calls GetAdaptersAddresses and returns wrapped results.
538 /// </summary>
539 static std::vector<IpAdapterAddress> GetCurrent()
540 {
541 const std::shared_ptr<AdapterAddresses> newInstance(new AdapterAddresses());
542 return newInstance->Initialize();
543 }
544
545 private:
546 /// <summary>
547 /// Default constructor is private as an instance is not directly created
548 /// by callers.
549 /// </summary>
550 AdapterAddresses() = default;
551
552 /// <summary>
553 /// Copy constructor is not allowed.
554 /// </summary>
555 AdapterAddresses(const AdapterAddresses&) = delete;
556
557 /// <summary>
558 /// Internal function to do the interesting work.
559 /// </summary>
560 std::vector<IpAdapterAddress> Initialize()
561 {
562 // N.B. MSDN recommends starting with a 15K buffer as that will be sufficient on
563 // most systems and the call to GetAdaptersAddresses is expensive.
564 ULONG Result;
565 ULONG BufferSize = (15 * 1024);
566 do
567 {
568 m_buffer.resize(BufferSize);
569 Result = GetAdaptersAddresses(
570 AF_UNSPEC,
571 (GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS),
572 nullptr,
573 (PIP_ADAPTER_ADDRESSES)m_buffer.data(),
574 &BufferSize);
575 } while (Result == ERROR_BUFFER_OVERFLOW);
576
577 THROW_LAST_ERROR_IF_MSG((Result != ERROR_SUCCESS), "GetAdaptersAddresses");
578 m_buffer.resize(BufferSize);
579 auto AddressBuffer = (PIP_ADAPTER_ADDRESSES)m_buffer.data();
580 std::vector<IpAdapterAddress> addresses;
581 while (AddressBuffer != nullptr)
582 {
583 addresses.emplace_back(IpAdapterAddress(shared_from_this(), AddressBuffer));
584 AddressBuffer = AddressBuffer->Next;
585 }
586
587 return addresses;
588 }
589
590 /// <summary>
591 /// Buffer to hold the results of GetAdaptersAddresses.
592 /// </summary>
593 std::vector<BYTE> m_buffer{};
594 };
595
596 class ConnectivityTelemetry
597 {
598 public:
599 ConnectivityTelemetry() = default;
600 ~ConnectivityTelemetry() = default;
601 ConnectivityTelemetry(const ConnectivityTelemetry&) = delete;
602 ConnectivityTelemetry& operator=(const ConnectivityTelemetry&) = delete;
603 ConnectivityTelemetry(ConnectivityTelemetry&&) = delete;
604 ConnectivityTelemetry& operator=(ConnectivityTelemetry&&) = delete;
605
606 void StartTimer(std::function<void(NLM_CONNECTIVITY, uint32_t)>&& callback)
607 {
608 m_callback = std::move(callback);
609 m_telemetryConnectionTimer.reset(CreateThreadpoolTimer(TelemetryConnectionTimerCallback, this, nullptr));
610 THROW_IF_NULL_ALLOC(m_telemetryConnectionTimer);
611 }
612
613 void UpdateTimer() const noexcept
614 {
615 if (m_telemetryConnectionTimer)
616 {
617 FILETIME dueTime =
618 wil::filetime::from_int64(static_cast<ULONGLONG>(-1 * wil::filetime_duration::one_millisecond * m_backoffTimeMs));
619 SetThreadpoolTimer(m_telemetryConnectionTimer.get(), &dueTime, 0, 1000);
620 }
621 }
622
623 void Reset() noexcept
624 {
625 m_telemetryConnectionTimer.reset();
626 }
627
628 static uint32_t LinuxIPv4ConnCheckResult(uint32_t returnedLinuxLevel) noexcept
629 {
630 // v4 is the lower-16 bits
631 return returnedLinuxLevel & 0xffff;
632 }
633 static uint32_t LinuxIPv6ConnCheckResult(uint32_t returnedLinuxLevel) noexcept
634 {
635 // v6 is the higher-16 bits
636 return returnedLinuxLevel >> 16;
637 }
638 static uint32_t WindowsIPv4NlmConnectivityLevel(NLM_CONNECTIVITY hostConnectivity) noexcept
639 {
640 if (hostConnectivity == NLM_CONNECTIVITY_DISCONNECTED)
641 {
642 return NLM_CONNECTIVITY_DISCONNECTED;
643 }
644
645 return (hostConnectivity & NLM_CONNECTIVITY_IPV4_NOTRAFFIC) | (hostConnectivity & NLM_CONNECTIVITY_IPV4_SUBNET) |
646 (hostConnectivity & NLM_CONNECTIVITY_IPV4_LOCALNETWORK) | (hostConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET);
647 }
648 static uint32_t WindowsIPv6NlmConnectivityLevel(NLM_CONNECTIVITY hostConnectivity) noexcept
649 {
650 if (hostConnectivity == NLM_CONNECTIVITY_DISCONNECTED)
651 {
652 return NLM_CONNECTIVITY_DISCONNECTED;
653 }
654
655 return (hostConnectivity & NLM_CONNECTIVITY_IPV6_NOTRAFFIC) | (hostConnectivity & NLM_CONNECTIVITY_IPV6_SUBNET) |
656 (hostConnectivity & NLM_CONNECTIVITY_IPV6_LOCALNETWORK) | (hostConnectivity & NLM_CONNECTIVITY_IPV6_INTERNET);
657 }
658
659 private:
660 const uint32_t m_backoffTimeMs = 5000;
661 std::function<void(NLM_CONNECTIVITY, uint32_t)> m_callback;
662 wil::unique_threadpool_timer m_telemetryConnectionTimer;
663 uint32_t m_telemetryCounter = 0;
664
665 static void __stdcall TelemetryConnectionTimerCallback(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID context, _Inout_ PTP_TIMER) noexcept
666 try
667 {
668 const auto coInit = wil::CoInitializeEx();
669 const wil::com_ptr<INetworkListManager> networkListManager = wil::CoCreateInstance<NetworkListManager, INetworkListManager>();
670
671 NLM_CONNECTIVITY hostConnectivity{};
672 THROW_IF_FAILED(networkListManager->GetConnectivity(&hostConnectivity));
673
674 const auto updatedCounter = ++static_cast<ConnectivityTelemetry*>(context)->m_telemetryCounter;
675 static_cast<ConnectivityTelemetry*>(context)->m_callback(hostConnectivity, updatedCounter);
676 }
677 CATCH_LOG()
678 };
679 } // namespace wsl::core::networking