| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | WslMirroredNetworking.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains WSL mirrored networking function declarations. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #pragma once |
| 16 | #include <chrono> |
| 17 | #include <optional> |
| 18 | #include <string> |
| 19 | #include <vector> |
| 20 | |
| 21 | #include <mstcpip.h> |
| 22 | #include <ws2ipdef.h> |
| 23 | #include <netlistmgr.h> |
| 24 | #include <ComputeNetwork.h> |
| 25 | #include <wil/winrt.h> |
| 26 | |
| 27 | #include "WslCoreMessageQueue.h" |
| 28 | #include "WslCoreAdviseHandler.h" |
| 29 | #include "WslCoreNetworkEndpoint.h" |
| 30 | #include "WslCoreNetworkEndpointSettings.h" |
| 31 | #include "WslCoreNetworkingSupport.h" |
| 32 | #include "WslCoreTcpIpStateTracking.h" |
| 33 | #include "WslCoreHostDnsInfo.h" |
| 34 | #include "hcs.hpp" |
| 35 | #include "IMirroredNetworkManager.h" |
| 36 | |
| 37 | /// <summary> |
| 38 | /// Creates network-related information for WSL. |
| 39 | /// </summary> |
| 40 | namespace wsl::core::networking { |
| 41 | |
| 42 | class WslMirroredNetworkManager final : public wsl::core::networking::IMirroredNetworkManager |
| 43 | { |
| 44 | public: |
| 45 | WslMirroredNetworkManager( |
| 46 | HCS_SYSTEM hcsSystem, |
| 47 | const Config& config, |
| 48 | GnsMessageCallbackWithCallbackResult&& GnsMessageCallbackWithCallbackResult, |
| 49 | AddNetworkEndpointCallback&& addNetworkEndpointCallback, |
| 50 | const std::pair<uint16_t, uint16_t>& ephemeralPortRange); |
| 51 | |
| 52 | ~WslMirroredNetworkManager() noexcept override; |
| 53 | |
| 54 | // Disable copy and assign. |
| 55 | WslMirroredNetworkManager(const WslMirroredNetworkManager&) = delete; |
| 56 | WslMirroredNetworkManager& operator=(const WslMirroredNetworkManager&) = delete; |
| 57 | |
| 58 | // Disable move semantics. |
| 59 | WslMirroredNetworkManager(WslMirroredNetworkManager&&) noexcept = delete; |
| 60 | WslMirroredNetworkManager& operator=(WslMirroredNetworkManager&&) = delete; |
| 61 | |
| 62 | HnsStatus Stop() noexcept override; |
| 63 | |
| 64 | _Check_return_ HRESULT EnumerateNetworks(_Out_ std::vector<GUID>& NetworkIds) const noexcept override; |
| 65 | |
| 66 | void AddEndpoint(NetworkEndpoint&& newEndpoint, wsl::shared::hns::HNSEndpoint&& endpointProperties) noexcept override; |
| 67 | |
| 68 | void SendCreateNotificationsForInitialEndpoints() noexcept override; |
| 69 | |
| 70 | HRESULT WaitForMirroredGoalState() noexcept override; |
| 71 | |
| 72 | _Check_return_ bool DoesEndpointExist(GUID networkId) const noexcept override; |
| 73 | |
| 74 | void OnNetworkConnectivityHintChange() noexcept override; |
| 75 | void OnNetworkEndpointChange() noexcept override; |
| 76 | void OnDnsSuffixChange() noexcept override; |
| 77 | |
| 78 | void TunAdapterStateChanged(_In_ const std::string& interfaceName, _In_ bool up) noexcept override; |
| 79 | |
| 80 | // Client should call this if they detect the network is in a bad state and needs to be reconnected |
| 81 | void ReconnectGuestNetwork() override; |
| 82 | |
| 83 | std::shared_ptr<NetworkSettings> GetEndpointSettings(const wsl::shared::hns::HNSEndpoint& endpointProperties) const override; |
| 84 | |
| 85 | void TraceLoggingRundown() const override; |
| 86 | |
| 87 | private: |
| 88 | enum class State |
| 89 | { |
| 90 | Stopped = 0, |
| 91 | Started, |
| 92 | Starting, |
| 93 | }; |
| 94 | |
| 95 | static const char* StateToString(State state) noexcept; |
| 96 | |
| 97 | _Requires_lock_held_(m_networkLock) |
| 98 | std::vector<GUID> EnumerateMirroredNetworks() const noexcept; |
| 99 | |
| 100 | _Requires_lock_held_(m_networkLock) |
| 101 | _Check_return_ HRESULT AddNetwork(const GUID& networkId) noexcept; |
| 102 | |
| 103 | _Requires_lock_held_(m_networkLock) |
| 104 | _Check_return_ HRESULT RemoveNetwork(const GUID& networkId) noexcept; |
| 105 | |
| 106 | _Requires_lock_held_(m_networkLock) |
| 107 | _Check_return_ HRESULT RemoveEndpoint(const GUID& endpointId) noexcept; |
| 108 | |
| 109 | struct EndpointTracking |
| 110 | { |
| 111 | EndpointTracking(NetworkEndpoint&& networkEndpoint, wsl::shared::hns::HNSEndpoint&& hnsEndpoint, uint32_t retryCount) : |
| 112 | m_networkEndpoint{std::move(networkEndpoint)}, m_hnsEndpoint{std::move(hnsEndpoint)}, m_retryCount{retryCount} |
| 113 | { |
| 114 | } |
| 115 | ~EndpointTracking() noexcept = default; |
| 116 | EndpointTracking(const EndpointTracking&) = delete; |
| 117 | EndpointTracking& operator=(const EndpointTracking&) = delete; |
| 118 | EndpointTracking(EndpointTracking&&) = default; |
| 119 | EndpointTracking& operator=(EndpointTracking&&) = default; |
| 120 | |
| 121 | NetworkEndpoint m_networkEndpoint; |
| 122 | wsl::shared::hns::HNSEndpoint m_hnsEndpoint; |
| 123 | uint32_t m_retryCount = 0; |
| 124 | }; |
| 125 | |
| 126 | _Requires_lock_held_(m_networkLock) |
| 127 | void AddEndpointImpl(EndpointTracking&& endpointTrackingObject) noexcept; |
| 128 | |
| 129 | _Requires_lock_held_(m_networkLock) |
| 130 | void ProcessConnectivityChange(); |
| 131 | |
| 132 | _Requires_lock_held_(m_networkLock) |
| 133 | void ProcessInterfaceChange(); |
| 134 | |
| 135 | _Requires_lock_held_(m_networkLock) |
| 136 | void ProcessIpAddressChange(); |
| 137 | |
| 138 | _Requires_lock_held_(m_networkLock) |
| 139 | void ProcessRouteChange(); |
| 140 | |
| 141 | _Requires_lock_held_(m_networkLock) |
| 142 | void ProcessDNSChange(); |
| 143 | |
| 144 | _Requires_lock_held_(m_networkLock) |
| 145 | _Check_return_ HRESULT SendAddressRequestToGns( |
| 146 | const NetworkEndpoint& endpoint, const TrackedIpAddress& address, wsl::shared::hns::ModifyRequestType requestType) noexcept; |
| 147 | |
| 148 | _Requires_lock_held_(m_networkLock) |
| 149 | _Check_return_ HRESULT SendRouteRequestToGns(const NetworkEndpoint& endpoint, const TrackedRoute& route, wsl::shared::hns::ModifyRequestType requestType) noexcept; |
| 150 | |
| 151 | _Requires_lock_held_(m_networkLock) |
| 152 | _Check_return_ HRESULT SendLoopbackRequestToGns( |
| 153 | const NetworkEndpoint& endpoint, const TrackedIpAddress& address, wsl::shared::hns::OperationType operation) noexcept; |
| 154 | |
| 155 | _Requires_lock_held_(m_networkLock) |
| 156 | _Check_return_ HRESULT SendDnsRequestToGns(const NetworkEndpoint& endpoint, const DnsInfo& dnsInfo, wsl::shared::hns::ModifyRequestType requestType) noexcept; |
| 157 | |
| 158 | _Requires_lock_held_(m_networkLock) |
| 159 | _Check_return_ HRESULT SendInterfaceRequestToGns(const NetworkEndpoint& endpoint) noexcept; |
| 160 | |
| 161 | _Requires_lock_not_held_(m_networkLock) |
| 162 | void UpdateAllEndpoints(_In_ PCSTR sourceName) noexcept; |
| 163 | |
| 164 | _Requires_lock_held_(m_networkLock) |
| 165 | void UpdateAllEndpointsImpl(UpdateEndpointFlag updateFlag, _In_ PCSTR callingSource) noexcept; |
| 166 | |
| 167 | _Requires_lock_held_(m_networkLock) |
| 168 | _Check_return_ HRESULT UpdateHcnServiceTimer() noexcept; |
| 169 | |
| 170 | _Requires_lock_held_(m_networkLock) |
| 171 | _Check_return_ HRESULT ResetHcnServiceSession() noexcept; |
| 172 | |
| 173 | _Requires_lock_held_(m_networkLock) |
| 174 | _Check_return_ bool SyncIpStateWithLinux(NetworkEndpoint& endpoint); |
| 175 | |
| 176 | _Requires_lock_held_(m_networkLock) |
| 177 | NetworkSettings GetNetworkSettingsOfInterface(DWORD ifIndex) const; |
| 178 | |
| 179 | void TelemetryConnectionCallback(NLM_CONNECTIVITY hostConnectivity, uint32_t telemetryCounter) noexcept; |
| 180 | |
| 181 | // protects access to member variables as well as operations that generate callback messages |
| 182 | // methods which lead to GNS messages being sent must maintain the order in which the caller invoked them |
| 183 | // thus exclusive access will be guaranteed for these methods, even if we don't need write-protection to member variables |
| 184 | mutable wil::srwlock m_networkLock; |
| 185 | |
| 186 | // Member variables used to limit calls through UpdatePreferredEndpoint(UpdateEndpointFlag::Default) to every 350ms. |
| 187 | // This is because WslMirroredNetworkManager uses an eventing model where we will often see many 10s of events fired back-to-back |
| 188 | static constexpr uint32_t m_debounceUpdateAllEndpointsTimerMs = 350; |
| 189 | _Guarded_by_(m_networkLock) uint64_t m_lastUpdateAllEndpointsDefaultTime = 0; |
| 190 | bool m_IsDebounceUpdateAllEndpointsDefaultTimerSet = false; |
| 191 | _Requires_lock_held_(m_networkLock) |
| 192 | wil::unique_threadpool_timer m_debounceUpdateAllEndpointsDefaultTimer; |
| 193 | static void __stdcall DebounceUpdateAllEndpointsDefaultTimerFired(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER); |
| 194 | |
| 195 | // Member variables tracking resiliency attempts to create endpoints in the container for indicated networkIds from HNS |
| 196 | static constexpr uint32_t m_maxAddEndpointRetryCount = 3; |
| 197 | static constexpr uint32_t m_debounceCreateEndpointFailureTimerMs = 1000; |
| 198 | _Requires_lock_held_(m_networkLock) |
| 199 | std::vector<EndpointTracking> m_failedEndpointProperties; |
| 200 | _Requires_lock_held_(m_networkLock) |
| 201 | wil::unique_threadpool_timer m_debounceCreateEndpointFailureTimer; |
| 202 | static void __stdcall DebounceCreateEndpointFailureTimerFired(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER); |
| 203 | |
| 204 | // Member variables tracking the WinRT and COM networking APIs required |
| 205 | wsl::windows::common::helpers::unique_mta_cookie m_mtaCookie; |
| 206 | wil::com_ptr<ABI::Windows::Networking::Connectivity::INetworkInformationStatics> m_networkInformationStatics; |
| 207 | wil::com_ptr<INetworkListManager> m_netListManager; |
| 208 | wil::com_ptr<INetworkEvents> m_netListManagerEventSink; |
| 209 | WslCoreAdviseHandler m_netListManagerAdviseHandler; |
| 210 | |
| 211 | _Guarded_by_(m_networkLock) HnsStatus m_latestHnsStatus { HnsStatus::NoNetworkEverConnected }; |
| 212 | |
| 213 | // Members tracking all endpoints created in the container, and the current networks connected |
| 214 | _Guarded_by_(m_networkLock) std::vector<NetworkEndpoint> m_networkEndpoints; |
| 215 | _Guarded_by_(m_networkLock) std::set<GUID, wsl::windows::common::helpers::GuidLess> m_hostConnectedInterfaces; |
| 216 | |
| 217 | // Members tracking callback functors back through the parent |
| 218 | _Guarded_by_(m_networkLock) GnsMessageCallbackWithCallbackResult m_callbackForGnsMessage; |
| 219 | // the AddNetworkEndpointCallback is called through the m_gnsMessageQueue |
| 220 | // so we don't risk deadlocks if the callback chooses to call back into WslMirroredNetworkManager |
| 221 | _Guarded_by_(m_networkLock) AddNetworkEndpointCallback m_addNetworkEndpointCallback; |
| 222 | |
| 223 | // The DNS info synced into the guest |
| 224 | _Guarded_by_(m_networkLock) DnsInfo m_trackedDnsInfo; |
| 225 | // The current DNS info on the host |
| 226 | _Guarded_by_(m_networkLock) DnsInfo m_dnsInfo; |
| 227 | |
| 228 | std::wstring m_dnsTunnelingIpAddress; |
| 229 | |
| 230 | // Tracks whether we are in the mirrored goal state or not. |
| 231 | _Guarded_by_(m_networkLock) wil::unique_event m_inMirroredGoalState { wil::EventOptions::ManualReset }; |
| 232 | |
| 233 | ConnectivityTelemetry m_connectivityTelemetry; |
| 234 | |
| 235 | // Used for telemetry to see how long it takes to reach the mirrored goal state for the first time. |
| 236 | std::chrono::time_point<std::chrono::steady_clock> m_objectCreationTime = std::chrono::steady_clock::now(); |
| 237 | std::chrono::time_point<std::chrono::steady_clock> m_initialMirroredGoalStateEndTime; |
| 238 | |
| 239 | // Handle for the Hcn* Api. Owned by the caller (WslCoreVm), this is a non-owning copy |
| 240 | const HCS_SYSTEM m_hcsSystem{}; |
| 241 | |
| 242 | // Config of the WslCoreVm. |
| 243 | const Config& m_vmConfig; |
| 244 | |
| 245 | // Ephemeral port range allocated for the VM. |
| 246 | std::pair<uint16_t, uint16_t> m_ephemeralPortRange; |
| 247 | |
| 248 | // All guest related messages sent back through callbacks to Linux (GNS) |
| 249 | // must be queued in order into a single queue. |
| 250 | WslCoreMessageQueue m_gnsCallbackQueue; |
| 251 | |
| 252 | // All host-configuration messages, either back to the parent MirroredNetworking or to HNS/HCS |
| 253 | // must have their own queue as to not be blocked by Linux messages. |
| 254 | WslCoreMessageQueue m_hnsQueue; |
| 255 | |
| 256 | // Callback timer that is invoked when the HNS service goes down to attempt to re-establish contact |
| 257 | static void __stdcall HcnServiceConnectionTimerCallback(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER) noexcept; |
| 258 | wil::unique_threadpool_timer m_retryHcnServiceConnectionTimer; |
| 259 | _Guarded_by_(m_networkLock) DWORD m_retryHcnServiceConnectionDurationMs = 0; |
| 260 | |
| 261 | // Callback that is invoked by HNS when a service-wide notification |
| 262 | // is available (e.g. when a HNS network is created or deleted). |
| 263 | static void __stdcall HcnCallback(_In_ DWORD NotificationType, _In_opt_ void* Context, _In_ HRESULT NotificationStatus, _In_opt_ PCWSTR NotificationData) noexcept; |
| 264 | windows::common::hcs::unique_hcn_service_callback m_hcnCallback; |
| 265 | |
| 266 | _Guarded_by_(m_networkLock) State m_state { State::Stopped }; |
| 267 | |
| 268 | // Callback timer that is invoked when we want to retry syncing the latest Windows IP state with Linux |
| 269 | static void __stdcall RetryLinuxIpStateSyncTimerCallback(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER) noexcept; |
| 270 | wil::unique_threadpool_timer m_retryLinuxIpStateSyncTimer; |
| 271 | static constexpr uint32_t m_linuxIpStateRetryDebounceTimerMinMilliseconds = 100ul; |
| 272 | static constexpr uint32_t m_linuxIpStateRetryDebounceTimerMaxMilliseconds = 2000ul; |
| 273 | uint32_t m_linuxIpStateRetryDebounceTimerMilliseconds = m_linuxIpStateRetryDebounceTimerMinMilliseconds; |
| 274 | |
| 275 | class PublicNLMSink final : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, INetworkEvents> |
| 276 | { |
| 277 | WslMirroredNetworkManager* m_parent{}; |
| 278 | |
| 279 | public: |
| 280 | explicit PublicNLMSink(WslMirroredNetworkManager* parent) : m_parent(parent) |
| 281 | { |
| 282 | } |
| 283 | |
| 284 | ~PublicNLMSink() override = default; |
| 285 | |
| 286 | PublicNLMSink(const PublicNLMSink&) = delete; |
| 287 | PublicNLMSink& operator=(const PublicNLMSink&) = delete; |
| 288 | PublicNLMSink(PublicNLMSink&&) = delete; |
| 289 | PublicNLMSink& operator=(PublicNLMSink&&) = delete; |
| 290 | |
| 291 | // INetworkEvents |
| 292 | IFACEMETHODIMP NetworkAdded(GUID networkId) override |
| 293 | { |
| 294 | m_parent->UpdateAllEndpoints("INetworkEvents"); |
| 295 | return S_OK; |
| 296 | } |
| 297 | |
| 298 | IFACEMETHODIMP NetworkDeleted(GUID networkId) override |
| 299 | { |
| 300 | m_parent->UpdateAllEndpoints("INetworkEvents"); |
| 301 | return S_OK; |
| 302 | } |
| 303 | |
| 304 | IFACEMETHODIMP NetworkConnectivityChanged(GUID networkId, NLM_CONNECTIVITY connectivity) override |
| 305 | { |
| 306 | m_parent->UpdateAllEndpoints("INetworkEvents"); |
| 307 | return S_OK; |
| 308 | } |
| 309 | |
| 310 | IFACEMETHODIMP NetworkPropertyChanged(GUID networkId, NLM_NETWORK_PROPERTY_CHANGE property) override |
| 311 | { |
| 312 | m_parent->UpdateAllEndpoints("INetworkEvents"); |
| 313 | return S_OK; |
| 314 | } |
| 315 | }; |
| 316 | }; |
| 317 | |
| 318 | } // namespace wsl::core::networking |