| 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 |