| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #include "MirroredNetworking.h" |
| 4 | #include "Stringify.h" |
| 5 | #include "WslCoreFirewallSupport.h" |
| 6 | #include "WslCoreNetworkingSupport.h" |
| 7 | #include "WslMirroredNetworking.h" |
| 8 | #include "WslCoreVm.h" |
| 9 | |
| 10 | using wsl::core::MirroredNetworking; |
| 11 | using wsl::core::networking::NetworkEndpoint; |
| 12 | using namespace wsl::shared; |
| 13 | |
| 14 | MirroredNetworking::MirroredNetworking(HCS_SYSTEM system, GnsChannel&& gnsChannel, const Config& config, GUID runtimeId, wil::unique_socket&& dnsHvsocket) : |
| 15 | m_system(system), m_runtimeId(runtimeId), m_config(config), m_gnsChannel(std::move(gnsChannel)) |
| 16 | { |
| 17 | // ensure the MTA apartment stays alive for the lifetime of this object in this process for our callback |
| 18 | THROW_IF_FAILED(CoIncrementMTAUsage(&m_mtaCookie)); |
| 19 | |
| 20 | // Create the DNS resolver used for DNS tunneling |
| 21 | if (dnsHvsocket) |
| 22 | { |
| 23 | networking::DnsResolverFlags resolverFlags{}; |
| 24 | WI_SetFlagIf(resolverFlags, networking::DnsResolverFlags::BestEffortDnsParsing, m_config.BestEffortDnsParsing); |
| 25 | |
| 26 | m_dnsTunnelingResolver.emplace(std::move(dnsHvsocket), resolverFlags); |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | MirroredNetworking::~MirroredNetworking() |
| 31 | { |
| 32 | // Unblock GNSChannel if any calls are pended to unblock all the threadpools |
| 33 | // will also unblock m_networkManager, if that's waiting for calls through the GNS channel into Linux |
| 34 | m_gnsChannel.Stop(); |
| 35 | |
| 36 | // Stop DNS suffix change notifications before stopping m_networkManager and m_networkingQueue, as they can call into those objects. |
| 37 | m_dnsSuffixRegistryWatcher.reset(); |
| 38 | |
| 39 | // Gns must unregister all callbacks first (which could call into m_networkManager) |
| 40 | // Then we must shutdown the entire m_networkManager |
| 41 | // Accessing m_gnsRpcServer here is safe because it's only written to in the |
| 42 | // constructor, which is protected by m_instanceLock in LxssUserSessionImpl |
| 43 | if (m_gnsRpcServer) |
| 44 | { |
| 45 | // Unregister for GNS notifications |
| 46 | m_guestNetworkService.Stop(); |
| 47 | |
| 48 | if (m_networkManager) |
| 49 | { |
| 50 | m_networkManager->Stop(); |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | // stop the TCPIP network change notifications - then stop all queued network work |
| 55 | m_addressNotificationHandle.reset(); |
| 56 | m_routeNotificationHandle.reset(); |
| 57 | m_interfaceNotificationHandle.reset(); |
| 58 | m_networkNotificationHandle.reset(); |
| 59 | |
| 60 | m_gnsPortTrackerChannel.reset(); |
| 61 | m_networkingQueue.cancel(); |
| 62 | m_gnsMessageQueue.cancel(); |
| 63 | } |
| 64 | |
| 65 | // static |
| 66 | bool MirroredNetworking::IsHyperVFirewallSupported(const wsl::core::Config& vmConfig) noexcept |
| 67 | { |
| 68 | PCSTR executionStep = ""; |
| 69 | try |
| 70 | { |
| 71 | const auto hyperVFirewallSupport = wsl::core::networking::GetHyperVFirewallSupportVersion(vmConfig.FirewallConfig); |
| 72 | |
| 73 | if (hyperVFirewallSupport == wsl::core::networking::HyperVFirewallSupport::None) |
| 74 | { |
| 75 | WSL_LOG("IsHyperVFirewallSupported returning false: No Hyper-V Firewall API present"); |
| 76 | return false; |
| 77 | } |
| 78 | |
| 79 | if (hyperVFirewallSupport == wsl::core::networking::HyperVFirewallSupport::Version1) |
| 80 | { |
| 81 | // not allowing Hyper-V Firewall support with WSL with just the Version1 Hyper-V Firewall API |
| 82 | WSL_LOG("IsHyperVFirewallSupported returning false: WSL requires Hyper-V Firewall version2 but version1 is present"); |
| 83 | return false; |
| 84 | } |
| 85 | |
| 86 | executionStep = "HcnEnumerateNetworks"; |
| 87 | // Check to see if the network is already created without Hyper-V Firewall. |
| 88 | // HNS only supports one networking configuration per boot cycle, so if it was configured with the |
| 89 | // Mirrored flag but without the Hyper-V Firewall flag, then we MUST NOT attempt to enable Hyper-V Firewall. |
| 90 | for (const auto& id : wsl::core::networking::EnumerateNetworks()) |
| 91 | { |
| 92 | executionStep = "HcnOpenNetwork"; |
| 93 | auto network = wsl::core::networking::OpenNetwork(id); |
| 94 | |
| 95 | executionStep = "HcnQueryNetworkProperties"; |
| 96 | auto [networkProperties, propertiesString] = wsl::core::networking::QueryNetworkProperties(network.get()); |
| 97 | if (WI_IsFlagSet(static_cast<uint32_t>(networkProperties.Flags), WI_EnumValue(wsl::shared::hns::NetworkFlags::EnableFlowSteering)) && |
| 98 | !WI_IsFlagSet(static_cast<uint32_t>(networkProperties.Flags), WI_EnumValue(wsl::shared::hns::NetworkFlags::EnableFirewall))) |
| 99 | { |
| 100 | WSL_LOG( |
| 101 | "IsHyperVFirewallSupported returning false: HNS Mirrored-network already created without Hyper-V Firewall " |
| 102 | "support, cannot enable Hyper-V Firewall"); |
| 103 | return false; |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | return true; |
| 108 | } |
| 109 | catch (...) |
| 110 | { |
| 111 | const auto hr = wil::ResultFromCaughtException(); |
| 112 | WSL_LOG( |
| 113 | "IsHyperVFirewallSupportedFailed", |
| 114 | TraceLoggingHResult(hr, "result"), |
| 115 | TraceLoggingValue(executionStep, "executionStep"), |
| 116 | TraceLoggingValue("Mirrored", "networkingMode")); |
| 117 | |
| 118 | return false; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | // static |
| 123 | bool MirroredNetworking::IsExternalInterfaceConstrained(const HCN_NETWORK network) noexcept |
| 124 | { |
| 125 | try |
| 126 | { |
| 127 | // Read interface constraint |
| 128 | const auto lxssKey = windows::common::registry::OpenLxssMachineKey(KEY_READ); |
| 129 | const auto interfaceConstraint = |
| 130 | windows::common::registry::ReadString(lxssKey.get(), nullptr, networking::c_interfaceConstraintKey, L""); |
| 131 | |
| 132 | if (!interfaceConstraint.empty()) |
| 133 | { |
| 134 | // The user has configured an ExternalInterfaceConstraint |
| 135 | |
| 136 | // Use GetAdapterAddresses to obtain the InterfaceGuid of the interface corresponding to the constraint |
| 137 | constexpr auto GET_ADAPTER_ADDRESSES_BUFFER_SIZE_INITIAL = (15 * 1024); |
| 138 | constexpr auto GAA_FLAGS = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_DNS_SERVER; |
| 139 | |
| 140 | ULONG result = ERROR_SUCCESS; |
| 141 | ULONG bufferSize = GET_ADAPTER_ADDRESSES_BUFFER_SIZE_INITIAL; |
| 142 | std::vector<std::byte> buffer; |
| 143 | PIP_ADAPTER_ADDRESSES adapter; |
| 144 | |
| 145 | do |
| 146 | { |
| 147 | buffer.resize(bufferSize); |
| 148 | adapter = gslhelpers::get_struct<IP_ADAPTER_ADDRESSES>(gsl::make_span(buffer)); |
| 149 | result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAGS, nullptr, adapter, &bufferSize); |
| 150 | } while (result == ERROR_BUFFER_OVERFLOW); |
| 151 | |
| 152 | THROW_LAST_ERROR_IF_MSG(result != ERROR_SUCCESS, "GetAdaptersAddresses"); |
| 153 | |
| 154 | // Find the external interface constraint adapter (i.e. the adapter which has its friendly name matching the regkey value) |
| 155 | bool interfaceConstraintPresent = false; |
| 156 | while (adapter != nullptr) |
| 157 | { |
| 158 | if (wsl::shared::string::IsEqual(interfaceConstraint, adapter->FriendlyName, true)) |
| 159 | { |
| 160 | interfaceConstraintPresent = true; |
| 161 | break; |
| 162 | } |
| 163 | adapter = adapter->Next; |
| 164 | } |
| 165 | |
| 166 | if (interfaceConstraintPresent) |
| 167 | { |
| 168 | // Retrieve the interfaceGuid corresponding to this endpoint by querying the HNS network |
| 169 | GUID endpointInterfaceGuid{}; |
| 170 | wil::unique_cotaskmem_string error; |
| 171 | wil::unique_cotaskmem_string networkPropertiesString; |
| 172 | wsl::shared::hns::HNSNetwork networkProperties; |
| 173 | try |
| 174 | { |
| 175 | std::tie(networkProperties, networkPropertiesString) = wsl::core::networking::QueryNetworkProperties(network); |
| 176 | } |
| 177 | catch (...) |
| 178 | { |
| 179 | WSL_LOG( |
| 180 | "IsExternalInterfaceConstrainedFailed", |
| 181 | TraceLoggingHResult(wil::ResultFromCaughtException(), "result"), |
| 182 | TraceLoggingValue("HcnQueryNetworkProperties", "executionStep"), |
| 183 | TraceLoggingValue("Mirrored", "networkingMode")); |
| 184 | |
| 185 | return false; |
| 186 | } |
| 187 | |
| 188 | // Successfully read the interfaceGuid for this endpoint |
| 189 | endpointInterfaceGuid = networkProperties.InterfaceConstraint.InterfaceGuid; |
| 190 | |
| 191 | // Obtain ExternalInterfaceConstraint's interfaceGuid to compare against the endpoint's interfaceGuid |
| 192 | GUID externalInterfaceConstraintGuid{}; |
| 193 | THROW_IF_WIN32_ERROR(ConvertInterfaceLuidToGuid(&(adapter->Luid), &externalInterfaceConstraintGuid)); |
| 194 | |
| 195 | if (externalInterfaceConstraintGuid == endpointInterfaceGuid) |
| 196 | { |
| 197 | // This interface matches the one we are looking for. |
| 198 | // There is an external interface constraint configured, the constrained |
| 199 | // interface is present, and the interface in question is the ExternalInterfaceConstraint. |
| 200 | // This interface is allowed to operate normally and must not be constrained. |
| 201 | WSL_LOG( |
| 202 | "IsExternalInterfaceConstrainedInterface", |
| 203 | TraceLoggingValue(endpointInterfaceGuid, "InterfaceGuid"), |
| 204 | TraceLoggingValue( |
| 205 | "ExternalInterfaceConstraint is configured and this interface is the " |
| 206 | "ExternalInterfaceConstraint. This interface must NOT be constrained", |
| 207 | "state")); |
| 208 | return false; |
| 209 | } |
| 210 | |
| 211 | // There is an external interface constraint configured and the constrained |
| 212 | // interface is present, but this is not the ExternalInterfaceConstraint. |
| 213 | // Thus, this interface must be constrained. |
| 214 | WSL_LOG( |
| 215 | "IsExternalInterfaceConstrainedInterface", |
| 216 | TraceLoggingValue(endpointInterfaceGuid, "InterfaceGuid"), |
| 217 | TraceLoggingValue( |
| 218 | "ExternalInterfaceConstraint is configured and the ExternalInterfaceConstraint is " |
| 219 | "found. This interface must be constrained.", |
| 220 | "state")); |
| 221 | return true; |
| 222 | } |
| 223 | // There is an ExternalInterfaceConstraint configured, but it is not present/up. |
| 224 | // Thus, this interface must be constrained. |
| 225 | WSL_LOG( |
| 226 | "IsExternalInterfaceConstrainedInterface", |
| 227 | TraceLoggingValue( |
| 228 | "ExternalInterfaceConstraint is configured and the ExternalInterfaceConstraint is NOT " |
| 229 | "found. All interfaces must be constrained.", |
| 230 | "state")); |
| 231 | return true; |
| 232 | } |
| 233 | |
| 234 | // There is no ExternalInterfaceConstraint configured. |
| 235 | // This, this interface must NOT be constrained. |
| 236 | WSL_LOG( |
| 237 | "IsExternalInterfaceConstrainedInterface", |
| 238 | TraceLoggingValue("ExternalInterfaceConstraint is not configured. All interfaces must NOT be constrained.", "state")); |
| 239 | return false; |
| 240 | } |
| 241 | CATCH_LOG() |
| 242 | |
| 243 | // If we reached here, we hit caught an unexpected error. Default to non-constrained |
| 244 | return false; |
| 245 | } |
| 246 | |
| 247 | void MirroredNetworking::Initialize() |
| 248 | { |
| 249 | // Configure IPV6 before anything else happens (IPV6 configuration needs to be done early). |
| 250 | m_networkingQueue.submit([this] { |
| 251 | return wil::ResultFromException([&]() { m_gnsChannel.SendNetworkDeviceMessage(LxGnsMessageSetupIpv6, L"{}"); }); |
| 252 | }); |
| 253 | |
| 254 | m_gnsRpcServer = GnsRpcServer::GetOrCreate(); |
| 255 | m_guestNetworkService.CreateGuestNetworkService( |
| 256 | m_config.FirewallConfig.Enabled(), m_config.IgnoredPorts, m_runtimeId, m_gnsRpcServer->GetServerUuid(), s_GuestNetworkServiceCallback, this); |
| 257 | m_ephemeralPortRange = m_guestNetworkService.AllocateEphemeralPortRange(); |
| 258 | |
| 259 | networking::ConfigureHyperVFirewall(m_config.FirewallConfig, wsl::windows::common::wslutil::c_vmOwner); |
| 260 | |
| 261 | // must keep all m_networkManager interactions (including) creation queued |
| 262 | // also must queue GNS callbacks to keep them serialized |
| 263 | // the queue also prevents losing change notifications while we are still processing add notifications |
| 264 | // calling submit_with_results to get a WslThreadPoolWaitableResult so we can conditionally wait for this workitem to complete to determine if it succeeded |
| 265 | const auto workItemTracker = m_networkingQueue.submit_with_results<HRESULT>([this] { |
| 266 | try |
| 267 | { |
| 268 | auto addNetworkEndpointCallback = [this](const GUID& networkId) { |
| 269 | m_networkingQueue.submit([this, networkId] { this->AddNetworkEndpoint(networkId); }); |
| 270 | }; |
| 271 | |
| 272 | // Create and start the network manager. |
| 273 | // |
| 274 | // N.B. Mirrored networks may not yet exist and the NetworkManager c'tor will cause HCS to create them asynchronously. |
| 275 | // This is done by the query submitted to HcnEnumerateNetworks. |
| 276 | // Once the networks are created, the network change callback will be invoked and endpoints will be hot-added. |
| 277 | // implement wsl::core::networking::GnsMessageCallbackWithCallbackResult so WSL can serialize messages to Linux |
| 278 | auto networkManagerGnsMessageCallbackWithCallbackResult = [this]( |
| 279 | LX_MESSAGE_TYPE messageType, |
| 280 | const std::wstring& notificationString, |
| 281 | networking::GnsCallbackFlags callbackFlags, |
| 282 | _Out_opt_ int* returnedResult) -> HRESULT { |
| 283 | // NetworkManagerGnsMessageCallback queues the actual work to the m_gnsMessageQueue |
| 284 | return NetworkManagerGnsMessageCallback(messageType, notificationString, callbackFlags, returnedResult); |
| 285 | }; |
| 286 | |
| 287 | m_networkManager = std::make_unique<wsl::core::networking::WslMirroredNetworkManager>( |
| 288 | m_system, m_config, std::move(networkManagerGnsMessageCallbackWithCallbackResult), std::move(addNetworkEndpointCallback), m_ephemeralPortRange); |
| 289 | |
| 290 | // Register notifications for DNS suffix changes |
| 291 | m_dnsSuffixRegistryWatcher.emplace( |
| 292 | [this] { m_networkingQueue.submit([this] { m_networkManager->OnDnsSuffixChange(); }); }); |
| 293 | |
| 294 | // Send the requisite notifications for the required network devices |
| 295 | m_networkManager->SendCreateNotificationsForInitialEndpoints(); |
| 296 | |
| 297 | // HNS now has all host interfaces that will be mirrored mapped into NetworkIds |
| 298 | std::vector<GUID> networkIds; |
| 299 | THROW_IF_FAILED(m_networkManager->EnumerateNetworks(networkIds)); |
| 300 | |
| 301 | // Create an endpoint on each mirrored network. |
| 302 | for (auto& networkId : networkIds) |
| 303 | { |
| 304 | AddNetworkEndpoint(networkId); |
| 305 | } |
| 306 | |
| 307 | // At this point all endpoints are configured, mark the GuestNetworkService as 'Synchronized' |
| 308 | m_guestNetworkService.SetGuestNetworkServiceState(hns::GuestNetworkServiceState::Synchronized); |
| 309 | } |
| 310 | catch (...) |
| 311 | { |
| 312 | const auto hr = wil::ResultFromCaughtException(); |
| 313 | WSL_LOG( |
| 314 | "FailedToStartNetworkManager", |
| 315 | TraceLoggingValue(m_runtimeId, "vmId"), |
| 316 | TraceLoggingValue(hr, "error"), |
| 317 | TraceLoggingValue(ToString(m_config.NetworkingMode), "networkConfiguration")); |
| 318 | |
| 319 | return hr; |
| 320 | } |
| 321 | return S_OK; |
| 322 | }); |
| 323 | |
| 324 | // Wait for initial mirroring to give users a consistent experience. |
| 325 | // the wait should not timeout - we are waiting infinite |
| 326 | const auto waitResult = workItemTracker->wait(INFINITE); |
| 327 | WI_ASSERT(ERROR_SUCCESS == waitResult); |
| 328 | // now we can read the HRESULT returned from the work item |
| 329 | const auto hr = workItemTracker->read_result(); |
| 330 | if (SUCCEEDED(hr)) |
| 331 | { |
| 332 | // We must wait for the goal state to be reached outside of the queue, since operations |
| 333 | // required to reach the goal state require processing in the queue. |
| 334 | const auto goalStateHr = m_networkManager->WaitForMirroredGoalState(); |
| 335 | if (FAILED(goalStateHr)) |
| 336 | { |
| 337 | WSL_LOG( |
| 338 | "WaitForMirroredGoalStateFailed", |
| 339 | TraceLoggingHResult(goalStateHr, "hr"), |
| 340 | TraceLoggingValue(m_config.EnableDnsTunneling, "DnsTunnelingEnabled"), |
| 341 | TraceLoggingValue(m_config.FirewallConfig.Enabled(), "HyperVFirewallEnabled"), |
| 342 | TraceLoggingValue(m_config.EnableAutoProxy, "AutoProxyFeatureEnabled")); |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | THROW_IF_FAILED(hr); |
| 347 | // else we don't need to wait on the result |
| 348 | // it can safely go out of scope and we can exit (it's a shared_ptr) |
| 349 | } |
| 350 | |
| 351 | void MirroredNetworking::FillInitialConfiguration(LX_MINI_INIT_NETWORKING_CONFIGURATION& message) |
| 352 | { |
| 353 | message.NetworkingMode = LxMiniInitNetworkingModeMirrored; |
| 354 | |
| 355 | std::tie(message.EphemeralPortRangeStart, message.EphemeralPortRangeEnd) = m_ephemeralPortRange; |
| 356 | message.PortTrackerType = LxMiniInitPortTrackerTypeMirrored; |
| 357 | message.EnableDhcpClient = false; |
| 358 | message.DisableIpv6 = false; |
| 359 | } |
| 360 | |
| 361 | void MirroredNetworking::StartPortTracker(wil::unique_socket&& socket) |
| 362 | { |
| 363 | WI_ASSERT(!m_gnsPortTrackerChannel.has_value()); |
| 364 | |
| 365 | m_gnsPortTrackerChannel.emplace( |
| 366 | std::move(socket), |
| 367 | [&](const SOCKADDR_INET& Address, int Protocol, bool Allocate) { |
| 368 | return m_guestNetworkService.OnPortAllocationRequest(Address, Protocol, Allocate); |
| 369 | }, |
| 370 | [&](_In_ const std::string& InterfaceName, _In_ bool Up) { |
| 371 | m_networkingQueue.submit([=, this] { |
| 372 | if (m_networkManager) |
| 373 | { |
| 374 | m_networkManager->TunAdapterStateChanged(InterfaceName, Up); |
| 375 | } |
| 376 | }); |
| 377 | }); |
| 378 | } |
| 379 | |
| 380 | void MirroredNetworking::TraceLoggingRundown() noexcept |
| 381 | { |
| 382 | m_networkingQueue.submit([this] { |
| 383 | if (m_networkManager) |
| 384 | { |
| 385 | m_networkManager->TraceLoggingRundown(); |
| 386 | } |
| 387 | }); |
| 388 | } |
| 389 | |
| 390 | // must be called from m_networkingQueue - m_networkManager must be called only from that queue |
| 391 | void MirroredNetworking::AddNetworkEndpoint(const GUID& NetworkId) noexcept |
| 392 | { |
| 393 | PCSTR executionStep = ""; |
| 394 | try |
| 395 | { |
| 396 | WI_ASSERT(m_networkingQueue.isRunningInQueue()); |
| 397 | WI_ASSERT(m_networkManager); |
| 398 | |
| 399 | if (m_networkManager->DoesEndpointExist(NetworkId)) |
| 400 | { |
| 401 | WSL_LOG( |
| 402 | "MirroredNetworking::AddNetworkEndpoint - NetworkId already exists", TraceLoggingValue(NetworkId, "networkId")); |
| 403 | return; |
| 404 | } |
| 405 | |
| 406 | executionStep = "HcnOpenNetwork"; |
| 407 | auto network = wsl::core::networking::OpenNetwork(NetworkId); |
| 408 | WSL_LOG("MirroredNetworking::AddNetworkEndpoint [HcnOpenNetwork]", TraceLoggingValue(NetworkId, "networkId")); |
| 409 | |
| 410 | // Query the network properties for diagnostic purposes only. |
| 411 | wsl::shared::hns::HNSNetwork properties; |
| 412 | wil::unique_cotaskmem_string networkProperties; |
| 413 | executionStep = "HcnQueryNetworkProperties"; |
| 414 | std::tie(properties, networkProperties) = wsl::core::networking::QueryNetworkProperties(network.get()); |
| 415 | WSL_LOG( |
| 416 | "MirroredNetworking::AddNetworkEndpoint [HcnQueryNetworkProperties]", |
| 417 | TraceLoggingValue(NetworkId, "networkId"), |
| 418 | TraceLoggingValue(networkProperties.get(), "networkProperties")); |
| 419 | |
| 420 | // Create a network endpoint. |
| 421 | // first see if we have cached a prior endpoint-id that matches this network-id |
| 422 | GUID endpointId{}; |
| 423 | const auto existingEndpointValue = m_networkIdMappings.find(NetworkId); |
| 424 | if (existingEndpointValue != m_networkIdMappings.end()) |
| 425 | { |
| 426 | endpointId = existingEndpointValue->second; |
| 427 | WSL_LOG( |
| 428 | "MirroredNetworking::AddNetworkEndpoint [using existing endpoint id]", |
| 429 | TraceLoggingValue(NetworkId, "networkId"), |
| 430 | TraceLoggingValue(endpointId, "endpointId")); |
| 431 | } |
| 432 | else |
| 433 | { |
| 434 | executionStep = "CoCreateGuid"; |
| 435 | THROW_IF_FAILED(CoCreateGuid(&endpointId)); |
| 436 | } |
| 437 | |
| 438 | std::wstring endpointSettings; |
| 439 | NetworkEndpoint endpointInfo{}; |
| 440 | endpointInfo.NetworkId = NetworkId; |
| 441 | endpointInfo.EndpointId = endpointId; |
| 442 | |
| 443 | if (m_config.FirewallConfig.Enabled()) |
| 444 | { |
| 445 | // Create HNS firewall policy object for the endpoint |
| 446 | hns::HostComputeEndpoint hnsEndpoint{}; |
| 447 | hns::EndpointPolicy<hns::PortnameEndpointPolicySetting> endpointPortNamePolicy{}; |
| 448 | hns::EndpointPolicy<hns::FirewallPolicySetting> endpointFirewallPolicy{}; |
| 449 | |
| 450 | // Assemble the endpoint |
| 451 | hnsEndpoint.HostComputeNetwork = NetworkId; |
| 452 | hnsEndpoint.SchemaVersion.Major = 2; |
| 453 | hnsEndpoint.SchemaVersion.Minor = 16; |
| 454 | |
| 455 | // Port name policy |
| 456 | endpointPortNamePolicy.Type = hns::EndpointPolicyType::PortName; |
| 457 | hnsEndpoint.Policies.emplace_back(std::move(endpointPortNamePolicy)); |
| 458 | |
| 459 | // Firewall policy |
| 460 | hns::FirewallPolicySetting firewallPolicyObject{}; |
| 461 | firewallPolicyObject.VmCreatorId = m_config.FirewallConfig.VmCreatorId.value(); |
| 462 | |
| 463 | // Set firewall policy flags |
| 464 | // Currently, only the ConstrainedInterface flag is supported, which is set based on the user configuring an ExternalInterfaceConstraint. |
| 465 | firewallPolicyObject.PolicyFlags = IsExternalInterfaceConstrained(network.get()) ? hns::FirewallPolicyFlags::ConstrainedInterface |
| 466 | : hns::FirewallPolicyFlags::None; |
| 467 | |
| 468 | endpointFirewallPolicy.Settings = std::move(firewallPolicyObject); |
| 469 | endpointFirewallPolicy.Type = hns::EndpointPolicyType::Firewall; |
| 470 | hnsEndpoint.Policies.emplace_back(std::move(endpointFirewallPolicy)); |
| 471 | endpointSettings = ToJsonW(hnsEndpoint); |
| 472 | } |
| 473 | else |
| 474 | { |
| 475 | // If Hyper-V Firewall is not supported for this scenario, only configure the basic HNS endpoint object |
| 476 | wsl::shared::hns::HNSEndpoint settings{}; |
| 477 | settings.VirtualNetwork = NetworkId; |
| 478 | endpointSettings = ToJsonW(settings); |
| 479 | } |
| 480 | |
| 481 | // Create the endpoint |
| 482 | executionStep = "HcnCreateEndpoint"; |
| 483 | wil::unique_cotaskmem_string error; |
| 484 | auto result = HcnCreateEndpoint(network.get(), endpointInfo.EndpointId, endpointSettings.c_str(), &endpointInfo.Endpoint, &error); |
| 485 | |
| 486 | WSL_LOG( |
| 487 | "MirroredNetworking::AddNetworkEndpoint [HcnCreateEndpoint]", |
| 488 | TraceLoggingValue(NetworkId, "HNSEndpoint::NetworkId"), |
| 489 | TraceLoggingValue(result, "result"), |
| 490 | TraceLoggingValue(error.is_valid() ? error.get() : L"", "errorString")); |
| 491 | THROW_IF_FAILED_MSG(result, "HcnCreateEndpoint %ls", error.get()); |
| 492 | |
| 493 | wil::unique_cotaskmem_string propertiesString; |
| 494 | executionStep = "HcnQueryEndpointProperties"; |
| 495 | result = HcnQueryEndpointProperties(endpointInfo.Endpoint.get(), nullptr, &propertiesString, &error); |
| 496 | WSL_LOG( |
| 497 | "MirroredNetworking::AddNetworkEndpoint [HcnQueryEndpointProperties]", |
| 498 | TraceLoggingValue(endpointInfo.EndpointId, "endpointId"), |
| 499 | TraceLoggingValue(result, "result"), |
| 500 | TraceLoggingValue(error.is_valid() ? error.get() : L"", "errorString"), |
| 501 | TraceLoggingValue(propertiesString.is_valid() ? propertiesString.get() : L"", "propertiesString")); |
| 502 | THROW_IF_FAILED_MSG(result, "HcnQueryEndpointProperties %ls", error.get()); |
| 503 | |
| 504 | executionStep = "ParsingHcnQueryEndpointProperties"; |
| 505 | auto endpointProperties = FromJson<hns::HNSEndpoint>(propertiesString.get()); |
| 506 | |
| 507 | endpointInfo.Network = m_networkManager->GetEndpointSettings(endpointProperties); |
| 508 | endpointInfo.InterfaceGuid = endpointProperties.InterfaceConstraint.InterfaceGuid; |
| 509 | |
| 510 | WSL_LOG( |
| 511 | "MirroredNetworking::AddNetworkEndpoint", |
| 512 | TraceLoggingValue(endpointInfo.EndpointId, "endpointId"), |
| 513 | TraceLoggingValue(endpointInfo.InterfaceGuid, "endpointInterfaceGuid"), |
| 514 | TraceLoggingValue(endpointInfo.InterfaceLuid.Value, "endpointInterfaceLuid"), |
| 515 | TraceLoggingValue(endpointProperties.IPAddress.c_str(), "endpointIpAddress"), |
| 516 | TraceLoggingValue(endpointProperties.PortFriendlyName.c_str(), "endpointPortFriendlyName"), |
| 517 | TraceLoggingValue(endpointProperties.Name.c_str(), "endpointName"), |
| 518 | TraceLoggingValue(endpointProperties.VirtualNetwork, "endpointVirtualNetwork"), |
| 519 | TraceLoggingValue(endpointProperties.VirtualNetworkName.c_str(), "endpointVirtualNetworkName")); |
| 520 | |
| 521 | m_networkManager->AddEndpoint(std::move(endpointInfo), std::move(endpointProperties)); |
| 522 | |
| 523 | if (!m_networkNotificationHandle) |
| 524 | { |
| 525 | // Register for network connectivity change notifications to update the MTU. |
| 526 | LOG_IF_WIN32_ERROR(NotifyNetworkConnectivityHintChange( |
| 527 | [](PVOID context, NL_NETWORK_CONNECTIVITY_HINT hint) { |
| 528 | WSL_LOG( |
| 529 | "MirroredNetworking::NotifyNetworkConnectivityHintChange fired", |
| 530 | TraceLoggingValue(static_cast<uint32_t>(hint.ConnectivityLevel), "connectivityLevel"), |
| 531 | TraceLoggingValue(static_cast<uint32_t>(hint.ConnectivityCost), "connectivityCost")); |
| 532 | |
| 533 | auto* thisPtr = static_cast<MirroredNetworking*>(context); |
| 534 | thisPtr->m_networkingQueue.submit([thisPtr] { |
| 535 | if (thisPtr->m_networkManager) |
| 536 | { |
| 537 | thisPtr->m_networkManager->OnNetworkConnectivityHintChange(); |
| 538 | } |
| 539 | }); |
| 540 | }, |
| 541 | this, |
| 542 | TRUE, |
| 543 | &m_networkNotificationHandle)); |
| 544 | } |
| 545 | if (!m_interfaceNotificationHandle) |
| 546 | { |
| 547 | LOG_IF_WIN32_ERROR(NotifyIpInterfaceChange( |
| 548 | AF_UNSPEC, |
| 549 | [](PVOID context, PMIB_IPINTERFACE_ROW row, MIB_NOTIFICATION_TYPE) { |
| 550 | WSL_LOG( |
| 551 | "MirroredNetworking::NotifyIpInterfaceChange fired", |
| 552 | TraceLoggingValue(row->Family, "family"), |
| 553 | TraceLoggingValue(row->InterfaceIndex, "ifIndex")); |
| 554 | |
| 555 | auto* thisPtr = static_cast<MirroredNetworking*>(context); |
| 556 | thisPtr->m_networkingQueue.submit([thisPtr] { |
| 557 | if (thisPtr->m_networkManager) |
| 558 | { |
| 559 | thisPtr->m_networkManager->OnNetworkConnectivityHintChange(); |
| 560 | } |
| 561 | }); |
| 562 | }, |
| 563 | this, |
| 564 | FALSE, |
| 565 | &m_interfaceNotificationHandle)); |
| 566 | } |
| 567 | if (!m_routeNotificationHandle) |
| 568 | { |
| 569 | LOG_IF_WIN32_ERROR(NotifyRouteChange2( |
| 570 | AF_UNSPEC, |
| 571 | [](PVOID context, PMIB_IPFORWARD_ROW2 row, MIB_NOTIFICATION_TYPE) { |
| 572 | WSL_LOG("MirroredNetworking::NotifyRouteChange2 fired", TraceLoggingValue(row->InterfaceIndex, "ifIndex")); |
| 573 | |
| 574 | auto* thisPtr = static_cast<MirroredNetworking*>(context); |
| 575 | thisPtr->m_networkingQueue.submit([thisPtr] { |
| 576 | if (thisPtr->m_networkManager) |
| 577 | { |
| 578 | thisPtr->m_networkManager->OnNetworkConnectivityHintChange(); |
| 579 | } |
| 580 | }); |
| 581 | }, |
| 582 | this, |
| 583 | FALSE, |
| 584 | &m_routeNotificationHandle)); |
| 585 | } |
| 586 | if (!m_addressNotificationHandle) |
| 587 | { |
| 588 | LOG_IF_WIN32_ERROR(NotifyUnicastIpAddressChange( |
| 589 | AF_UNSPEC, |
| 590 | [](PVOID context, PMIB_UNICASTIPADDRESS_ROW row, MIB_NOTIFICATION_TYPE) { |
| 591 | WSL_LOG( |
| 592 | "MirroredNetworking::NotifyUnicastIpAddressChange fired", |
| 593 | TraceLoggingValue(row->InterfaceIndex, "ifIndex")); |
| 594 | |
| 595 | auto* thisPtr = static_cast<MirroredNetworking*>(context); |
| 596 | thisPtr->m_networkingQueue.submit([thisPtr] { |
| 597 | if (thisPtr->m_networkManager) |
| 598 | { |
| 599 | thisPtr->m_networkManager->OnNetworkConnectivityHintChange(); |
| 600 | } |
| 601 | }); |
| 602 | }, |
| 603 | this, |
| 604 | FALSE, |
| 605 | &m_addressNotificationHandle)); |
| 606 | } |
| 607 | |
| 608 | // we've successfully added a new endpoint - track that Id |
| 609 | if (existingEndpointValue == m_networkIdMappings.end()) |
| 610 | { |
| 611 | WSL_LOG( |
| 612 | "MirroredNetworking::AddNetworkEndpoint [tracking new endpoint]", |
| 613 | TraceLoggingValue(NetworkId, "networkId"), |
| 614 | TraceLoggingValue(endpointId, "endpointId")); |
| 615 | m_networkIdMappings[NetworkId] = endpointId; |
| 616 | } |
| 617 | } |
| 618 | catch (...) |
| 619 | { |
| 620 | WSL_LOG( |
| 621 | "AddNetworkEndpointFailure", |
| 622 | TraceLoggingHResult(wil::ResultFromCaughtException(), "result"), |
| 623 | TraceLoggingValue(executionStep, "executionStep"), |
| 624 | TraceLoggingValue("Mirrored", "networkingMode"), |
| 625 | TraceLoggingValue(m_config.EnableDnsTunneling, "DnsTunnelingEnabled"), |
| 626 | TraceLoggingValue(m_config.FirewallConfig.Enabled(), "HyperVFirewallEnabled"), |
| 627 | TraceLoggingValue(m_config.EnableAutoProxy, "AutoProxyFeatureEnabled") // the feature is enabled, but we don't know if proxy settings are actually configured |
| 628 | ); |
| 629 | } |
| 630 | } |
| 631 | |
| 632 | // must be called from m_networkingQueue so all GNS interactions are correctly serialized |
| 633 | // OnNetworkEndpointChange is called from GNS |
| 634 | HRESULT MirroredNetworking::OnNetworkEndpointChange(const GUID& EndpointId, _In_ LPCWSTR Settings) const noexcept |
| 635 | try |
| 636 | { |
| 637 | WI_ASSERT(m_networkingQueue.isRunningInQueue()); |
| 638 | |
| 639 | const auto notification = FromJson<hns::ModifyGuestEndpointSettingRequest<void>>(Settings); |
| 640 | |
| 641 | // not sending Neighbor updates into the container |
| 642 | if (notification.ResourceType == hns::GuestEndpointResourceType::Neighbor) |
| 643 | { |
| 644 | return E_NOTIMPL; |
| 645 | } |
| 646 | |
| 647 | // a network property changed on some interface that HNS is tracking |
| 648 | // we're using this notification as a trigger to rediscover the preferred interface |
| 649 | WSL_LOG( |
| 650 | "MirroredNetworking::OnNetworkEndpointChange [GNS server notification]", |
| 651 | TraceLoggingValue(wsl::shared::string::GuidToString<wchar_t>(EndpointId).c_str(), "Endpoint"), |
| 652 | TraceLoggingValue(Settings, "Payload")); |
| 653 | m_networkManager->OnNetworkEndpointChange(); |
| 654 | |
| 655 | return S_OK; |
| 656 | } |
| 657 | CATCH_RETURN() |
| 658 | |
| 659 | HRESULT MirroredNetworking::NetworkManagerGnsMessageCallback( |
| 660 | LX_MESSAGE_TYPE messageType, std::wstring notificationString, networking::GnsCallbackFlags callbackFlags, _Out_opt_ int* returnedValueFromGns) noexcept |
| 661 | try |
| 662 | { |
| 663 | // only pass the OUT returnedValueFromGns int* if the callback flags are set to wait |
| 664 | if (returnedValueFromGns) |
| 665 | { |
| 666 | *returnedValueFromGns = ERROR_FATAL_APP_EXIT; |
| 667 | WI_ASSERT(WI_IsFlagSet(callbackFlags, wsl::core::networking::GnsCallbackFlags::Wait)); |
| 668 | } |
| 669 | |
| 670 | auto sendGnsMessage = [this, messageType, capturedNotificationString = std::move(notificationString), callbackFlags, returnedValueFromGns]() mutable { |
| 671 | try |
| 672 | { |
| 673 | auto retryCount = 0ul; |
| 674 | auto sendMessage = [&]() { |
| 675 | const auto hr = wil::ResultFromException([&] { |
| 676 | if (returnedValueFromGns && WI_IsFlagSet(callbackFlags, wsl::core::networking::GnsCallbackFlags::Wait)) |
| 677 | { |
| 678 | *returnedValueFromGns = |
| 679 | m_gnsChannel.SendNetworkDeviceMessageReturnResult(messageType, capturedNotificationString.c_str()); |
| 680 | } |
| 681 | else |
| 682 | { |
| 683 | m_gnsChannel.SendNetworkDeviceMessage(messageType, capturedNotificationString.c_str()); |
| 684 | } |
| 685 | }); |
| 686 | const bool hasLinuxResult = returnedValueFromGns != nullptr; |
| 687 | const int linuxResultCode = hasLinuxResult ? *returnedValueFromGns : 0; |
| 688 | WSL_LOG( |
| 689 | "MirroredNetworking::NetworkManagerGnsMessageCallback", |
| 690 | TraceLoggingValue(ToString(messageType), "messageType"), |
| 691 | TraceLoggingValue(capturedNotificationString.c_str(), "notificationString"), |
| 692 | TraceLoggingValue(hr, "hr"), |
| 693 | TraceLoggingValue(hasLinuxResult, "hasLinuxResult"), |
| 694 | TraceLoggingValue(linuxResultCode, "linuxResultCode"), |
| 695 | TraceLoggingValue(retryCount, "retryCount")); |
| 696 | |
| 697 | ++retryCount; |
| 698 | return networking::GetGnsCallbackResult(messageType, hr, linuxResultCode); |
| 699 | }; |
| 700 | |
| 701 | return wsl::shared::retry::RetryWithTimeout<HRESULT>(sendMessage, std::chrono::milliseconds(100), std::chrono::seconds(3)); |
| 702 | } |
| 703 | CATCH_RETURN() |
| 704 | }; |
| 705 | |
| 706 | if (WI_IsFlagSet(callbackFlags, wsl::core::networking::GnsCallbackFlags::Wait)) |
| 707 | { |
| 708 | return m_gnsMessageQueue.submit_and_wait(std::move(sendGnsMessage)); |
| 709 | } |
| 710 | |
| 711 | m_gnsMessageQueue.submit(std::move(sendGnsMessage)); |
| 712 | return S_OK; |
| 713 | } |
| 714 | CATCH_RETURN() |
| 715 | |
| 716 | void MirroredNetworking::GuestNetworkServiceCallback(DWORD NotificationType, HRESULT NotificationStatus, _In_opt_ PCWSTR NotificationData) noexcept |
| 717 | try |
| 718 | { |
| 719 | WSL_LOG( |
| 720 | "MirroredNetworking::GuestNetworkServiceCallback", |
| 721 | TraceLoggingValue(wsl::windows::common::stringify::HcnNotificationsToString(NotificationType), "NotificationType"), |
| 722 | TraceLoggingValue(NotificationStatus, "NotificationStatus"), |
| 723 | TraceLoggingValue(NotificationData, "NotificationData")); |
| 724 | |
| 725 | WI_ASSERT(SUCCEEDED(NotificationStatus)); |
| 726 | |
| 727 | hns::NotificationBase data{}; |
| 728 | if (ARGUMENT_PRESENT(NotificationData)) |
| 729 | { |
| 730 | data = FromJson<hns::NotificationBase>(NotificationData); |
| 731 | } |
| 732 | |
| 733 | switch (NotificationType) |
| 734 | { |
| 735 | case HcnNotificationServiceDisconnect: |
| 736 | break; |
| 737 | |
| 738 | case HcnNotificationGuestNetworkServiceStateChanged: |
| 739 | break; |
| 740 | |
| 741 | case HcnNotificationGuestNetworkServiceInterfaceStateChanged: |
| 742 | break; |
| 743 | |
| 744 | default: |
| 745 | WI_ASSERT(false); |
| 746 | } |
| 747 | |
| 748 | return; |
| 749 | } |
| 750 | CATCH_LOG() |
| 751 | |
| 752 | void CALLBACK MirroredNetworking::s_GuestNetworkServiceCallback(DWORD NotificationType, _In_ void* Context, HRESULT NotificationStatus, _In_opt_ PCWSTR NotificationData) |
| 753 | { |
| 754 | static_cast<MirroredNetworking*>(Context)->GuestNetworkServiceCallback(NotificationType, NotificationStatus, NotificationData); |
| 755 | } |