| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | LxssHttpProxy.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains HTTP proxy related classes and helper functions for proxy queries. Based on implementation in WSA. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #pragma once |
| 16 | |
| 17 | #include <windows.h> |
| 18 | #include <winhttp.h> |
| 19 | |
| 20 | #include <wil/resource.h> |
| 21 | |
| 22 | #include <string> |
| 23 | #include <vector> |
| 24 | #include <optional> |
| 25 | |
| 26 | #include "WslCoreConfig.h" |
| 27 | #include "WslCoreMessageQueue.h" |
| 28 | |
| 29 | static constexpr auto c_winhttpModuleName = L"Winhttp.dll"; |
| 30 | static constexpr auto c_httpProxyLower = "http_proxy"; |
| 31 | static constexpr auto c_httpProxyUpper = "HTTP_PROXY"; |
| 32 | static constexpr auto c_httpsProxyLower = "https_proxy"; |
| 33 | static constexpr auto c_httpsProxyUpper = "HTTPS_PROXY"; |
| 34 | static constexpr auto c_proxyBypassLower = "no_proxy"; |
| 35 | static constexpr auto c_proxyBypassUpper = "NO_PROXY"; |
| 36 | static constexpr auto c_pacProxy = "WSL_PAC_URL"; |
| 37 | static constexpr auto c_loopback = L"loopback"; |
| 38 | static constexpr auto c_localhost = L"localhost"; |
| 39 | |
| 40 | void FreeHttpProxySettings(WINHTTP_PROXY_SETTINGS_EX* proxySettings) noexcept; |
| 41 | using unique_winhttp_proxy_settings = wil::unique_struct<WINHTTP_PROXY_SETTINGS_EX, decltype(&FreeHttpProxySettings), FreeHttpProxySettings>; |
| 42 | |
| 43 | // Function declarations used for dynamically loading in Winhttp APIs. |
| 44 | WINHTTPAPI |
| 45 | DWORD |
| 46 | WINAPI |
| 47 | RegisterProxyChangeNotification(_In_ ULONGLONG ullFlags, _In_ WINHTTP_PROXY_CHANGE_CALLBACK pfnCallback, _In_ PVOID pvContext, _Out_ WINHTTP_PROXY_CHANGE_REGISTRATION_HANDLE* hRegistration); |
| 48 | |
| 49 | WINHTTPAPI |
| 50 | DWORD |
| 51 | WINAPI |
| 52 | UnregisterProxyChangeNotification(_In_ WINHTTP_PROXY_CHANGE_REGISTRATION_HANDLE hRegistration); |
| 53 | |
| 54 | WINHTTPAPI |
| 55 | DWORD |
| 56 | WINAPI |
| 57 | GetProxySettingsEx( |
| 58 | _In_ HINTERNET hResolver, |
| 59 | _In_ WINHTTP_PROXY_SETTINGS_TYPE ProxySettingsType, |
| 60 | _In_opt_ PWINHTTP_PROXY_SETTINGS_PARAM pProxySettingsParam, |
| 61 | _In_opt_ DWORD_PTR pContext); |
| 62 | |
| 63 | WINHTTPAPI |
| 64 | DWORD |
| 65 | WINAPI |
| 66 | GetProxySettingsResultEx(_In_ HINTERNET hResolver, _Out_ PVOID pProxySettingsEx); |
| 67 | |
| 68 | WINHTTPAPI |
| 69 | DWORD |
| 70 | WINAPI |
| 71 | FreeProxySettingsEx(_In_ WINHTTP_PROXY_SETTINGS_TYPE ProxySettingsType, _In_ PVOID pProxySettingsEx); |
| 72 | |
| 73 | enum class UnsupportedProxyReason |
| 74 | { |
| 75 | Supported, |
| 76 | LoopbackNotMirrored, |
| 77 | Ipv6NotMirrored, |
| 78 | LoopbackV6, |
| 79 | UnsupportedError |
| 80 | }; |
| 81 | |
| 82 | constexpr auto ToString(UnsupportedProxyReason config) noexcept |
| 83 | { |
| 84 | switch (config) |
| 85 | { |
| 86 | case UnsupportedProxyReason::Supported: |
| 87 | return "Supported"; |
| 88 | case UnsupportedProxyReason::LoopbackNotMirrored: |
| 89 | return "LoopbackNotMirrored"; |
| 90 | case UnsupportedProxyReason::Ipv6NotMirrored: |
| 91 | return "Ipv6NotMirrored"; |
| 92 | case UnsupportedProxyReason::LoopbackV6: |
| 93 | return "LoopbackV6"; |
| 94 | case UnsupportedProxyReason::UnsupportedError: |
| 95 | return "UnsupportedError"; |
| 96 | default: |
| 97 | return "<unknown UnsupportedProxyReason>"; |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | struct HttpProxySettings |
| 102 | { |
| 103 | HttpProxySettings() = default; |
| 104 | HttpProxySettings(const WINHTTP_PROXY_SETTINGS_EX& ProxySettings); |
| 105 | HttpProxySettings(const HttpProxySettings&) = default; |
| 106 | HttpProxySettings(HttpProxySettings&&) = default; |
| 107 | HttpProxySettings& operator=(const HttpProxySettings&) = default; |
| 108 | HttpProxySettings& operator=(HttpProxySettings&&) = default; |
| 109 | |
| 110 | std::string PacUrl{}; |
| 111 | std::string Proxy{}; |
| 112 | std::string SecureProxy{}; |
| 113 | std::vector<std::string> ProxyBypasses{}; |
| 114 | std::string ProxyBypassesComma{}; |
| 115 | UnsupportedProxyReason UnsupportedProxyDropReason = UnsupportedProxyReason::Supported; |
| 116 | |
| 117 | std::string ToString() const; |
| 118 | bool HasSettingsConfigured() const; |
| 119 | }; |
| 120 | |
| 121 | class HttpProxyStateTracker |
| 122 | { |
| 123 | enum class QueryState |
| 124 | { |
| 125 | NoQuery, |
| 126 | Pending, |
| 127 | PendingAndQueueAdditional |
| 128 | }; |
| 129 | |
| 130 | public: |
| 131 | HttpProxyStateTracker(int ProxyTimeout, HANDLE UserToken, wsl::core::NetworkingMode configuration); |
| 132 | ~HttpProxyStateTracker(); |
| 133 | |
| 134 | HttpProxyStateTracker(const HttpProxyStateTracker&) = delete; |
| 135 | HttpProxyStateTracker(HttpProxyStateTracker&&) = delete; |
| 136 | HttpProxyStateTracker& operator=(const HttpProxyStateTracker&) = delete; |
| 137 | HttpProxyStateTracker& operator=(HttpProxyStateTracker&&) = delete; |
| 138 | |
| 139 | /// <summary> |
| 140 | /// If no proxy queries have completed, wait for timeout for result. |
| 141 | /// Otherwise, return the proxy settings. |
| 142 | /// </summary> |
| 143 | std::optional<HttpProxySettings> WaitForInitialProxySettings(); |
| 144 | |
| 145 | /// <summary> |
| 146 | /// This needs to be called after the VM is created so actual selected configuration is set. |
| 147 | /// </summary> |
| 148 | void ConfigureNetworkingMode(wsl::core::NetworkingMode mode) noexcept; |
| 149 | |
| 150 | /// <summary> |
| 151 | /// Loads necessary WinHttpProxy APIs into static dynamic functions from DLL if they exist. |
| 152 | /// </summary> |
| 153 | static HRESULT s_LoadWinHttpProxyMethods() noexcept; |
| 154 | |
| 155 | /// Static dynamic function for loading in necessary WinHttpProxy APIs |
| 156 | static std::optional<LxssDynamicFunction<decltype(FreeProxySettingsEx)>> s_WinHttpFreeProxySettingsEx; |
| 157 | |
| 158 | private: |
| 159 | /// <summary> |
| 160 | /// Invoked via WslCoreMessageQueue. Uses WinHttpProxy APIs to start proxy query. |
| 161 | /// </summary> |
| 162 | void QueryProxySettingsAsync(); |
| 163 | |
| 164 | /// <summary> |
| 165 | /// Invoked via WslCoreMessageQueue when a proxy request completes. |
| 166 | /// </summary> |
| 167 | /// <param name="error"> Error status of request. </param> |
| 168 | /// <param name="proxySettings"> The current proxy settings. </param> |
| 169 | void RequestCompleted(_In_ DWORD error, _In_ HttpProxySettings&& newProxySettings) noexcept; |
| 170 | |
| 171 | /// <summary> |
| 172 | /// Invoked via WslCoreMessageQueue when a proxy request closes. |
| 173 | /// </summary> |
| 174 | void RequestClosed() noexcept; |
| 175 | |
| 176 | /// <summary> |
| 177 | /// Checks if two proxy settings are identical. |
| 178 | /// </summary> |
| 179 | bool AreProxyStringsIdentical(const HttpProxySettings& newSettings) const; |
| 180 | |
| 181 | /// <summary> |
| 182 | /// Memory barrier for reading/writing to m_proxySettings. |
| 183 | /// </summary> |
| 184 | wil::critical_section m_proxySettingsLock{}; |
| 185 | |
| 186 | /// <summary> |
| 187 | /// Current http proxy settings. If no queries have completed it is std::nullopt. |
| 188 | /// </summary> |
| 189 | _Guarded_by_(m_proxySettingsLock) std::optional<HttpProxySettings> m_proxySettings {}; |
| 190 | |
| 191 | /// <summary> |
| 192 | /// Current network mode. Used to determine some cases when we should send toast notification. |
| 193 | /// </summary> |
| 194 | _Guarded_by_(m_proxySettingsLock) wsl::core::NetworkingMode m_networkMode = wsl::core::NetworkingMode::Nat; |
| 195 | |
| 196 | /// <summary> |
| 197 | /// Indicates if we need to start another query after current one. |
| 198 | /// </summary> |
| 199 | QueryState m_queryState{QueryState::NoQuery}; |
| 200 | |
| 201 | /// <summary> |
| 202 | /// Synchronizes request startup and teardown. |
| 203 | /// </summary> |
| 204 | wil::critical_section m_requestLock{}; |
| 205 | _Guarded_by_(m_requestLock) bool m_stopping = false; |
| 206 | |
| 207 | /// <summary> |
| 208 | /// Used to impersonate user, as it is required for the proxy queries to run as the user; otherwise, the results will be incorrect. |
| 209 | /// </summary> |
| 210 | wil::unique_handle m_userToken{}; |
| 211 | |
| 212 | /// <summary> |
| 213 | /// Handle for tracking http proxy setting changes. |
| 214 | /// </summary> |
| 215 | WINHTTP_PROXY_CHANGE_REGISTRATION_HANDLE m_proxyRegistrationHandle{}; |
| 216 | |
| 217 | /// <summary> |
| 218 | /// Amount of time WSL will wait for proxy settings if no proxy settings have been detected by time we attempt to launch process. |
| 219 | /// </summary> |
| 220 | const int m_initialQueryTimeout = 1000; |
| 221 | |
| 222 | /// <summary> |
| 223 | /// We resolve and store the localized proxy change string for notifications to this object, as we can't resolve it in callback from proxy query. |
| 224 | /// </summary> |
| 225 | const std::wstring m_localizedProxyChangeString{}; |
| 226 | |
| 227 | /// <summary> |
| 228 | /// Event that is set when m_proxySettings has a value. |
| 229 | /// </summary> |
| 230 | wil::slim_event_manual_reset m_initialProxyQueryCompleted{false}; |
| 231 | |
| 232 | /// <summary> |
| 233 | /// Event that is set when all tracked requests have completed. |
| 234 | /// </summary> |
| 235 | wil::slim_event_manual_reset m_requestFinished{true}; |
| 236 | |
| 237 | // Handles associated with the request |
| 238 | _Guarded_by_(m_requestLock) wil::unique_winhttp_hinternet m_session {}; |
| 239 | _Guarded_by_(m_requestLock) wil::unique_winhttp_hinternet m_resolver {}; |
| 240 | |
| 241 | /// <summary> |
| 242 | /// Single-threaded queue to trigger work from winhttp callbacks. |
| 243 | /// </summary> |
| 244 | wsl::core::WslCoreMessageQueue m_callbackQueue{}; |
| 245 | |
| 246 | /// Static dynamic functions for loading in necessary WinHttpProxy APIs |
| 247 | static std::optional<LxssDynamicFunction<decltype(GetProxySettingsEx)>> s_WinHttpGetProxySettingsEx; |
| 248 | static std::optional<LxssDynamicFunction<decltype(GetProxySettingsResultEx)>> s_WinHttpGetProxySettingsResultEx; |
| 249 | static std::optional<LxssDynamicFunction<decltype(RegisterProxyChangeNotification)>> s_WinHttpRegisterProxyChangeNotification; |
| 250 | static std::optional<LxssDynamicFunction<decltype(UnregisterProxyChangeNotification)>> s_WinHttpUnregisterProxyChangeNotification; |
| 251 | |
| 252 | /// <summary> |
| 253 | /// Callback that returns results from proxy queries. |
| 254 | /// </summary> |
| 255 | /// <param name="resolver"> Resolver associated with this callback. </param> |
| 256 | /// <param name="context"> Pointer to ProxyCallbackContext associated with callback. </param> |
| 257 | /// <param name="internetStatus"> Status of callback. </param> |
| 258 | /// <param name="statusInformation"> Pointer to WINHTTP_ASYNC_RESULT used for error info. </param> |
| 259 | static void CALLBACK s_GetProxySettingsExCallback( |
| 260 | _In_ HINTERNET resolver, _In_ DWORD_PTR context, _In_ DWORD internetStatus, _In_ PVOID statusInformation, _In_ DWORD) noexcept; |
| 261 | |
| 262 | /// <summary> |
| 263 | /// Callback that notifies that an Http proxy setting change has been detected. |
| 264 | /// </summary> |
| 265 | /// <param name="flags"> Flags used to verify the type of callback received. </param> |
| 266 | /// <param name="pContext"> Pointer to ProxyStateTracker associated with callback. </param> |
| 267 | static void CALLBACK s_OnProxyChange(_In_ ULONGLONG flags, _In_ void* pContext) noexcept; |
| 268 | |
| 269 | /// <summary> |
| 270 | /// Determines if a proxy setting string is localhost. |
| 271 | /// </summary> |
| 272 | /// <param name="proxyString"> ProxyString to check if it is localhost. </param> |
| 273 | /// <param name="configuration"> Current network configuration. </param> |
| 274 | static UnsupportedProxyReason IsUnsupportedProxy(LPCWSTR proxyString, wsl::core::NetworkingMode mode) noexcept; |
| 275 | |
| 276 | /// <summary> |
| 277 | /// Remove invalid proxy configurations depending on network mode. |
| 278 | /// </summary> |
| 279 | /// <param name="settings"> HttpProxySettings to be filtered. </param> |
| 280 | /// <param name="configuration"> Current network configuration. </param> |
| 281 | static void FilterProxySettingsByNetworkConfiguration(HttpProxySettings& settings, wsl::core::NetworkingMode mode) noexcept; |
| 282 | }; |