| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | LxssHttpProxy.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains HTTP proxy related classes and helper functions for proxy queries. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "LxssHttpProxy.h" |
| 17 | |
| 18 | #include <winhttp.h> |
| 19 | #include <notifications.h> |
| 20 | |
| 21 | #include "WslCoreNetworkingSupport.h" |
| 22 | |
| 23 | using namespace wsl::windows::common; |
| 24 | |
| 25 | std::optional<LxssDynamicFunction<decltype(RegisterProxyChangeNotification)>> HttpProxyStateTracker::s_WinHttpRegisterProxyChangeNotification; |
| 26 | std::optional<LxssDynamicFunction<decltype(UnregisterProxyChangeNotification)>> HttpProxyStateTracker::s_WinHttpUnregisterProxyChangeNotification; |
| 27 | std::optional<LxssDynamicFunction<decltype(GetProxySettingsEx)>> HttpProxyStateTracker::s_WinHttpGetProxySettingsEx; |
| 28 | std::optional<LxssDynamicFunction<decltype(GetProxySettingsResultEx)>> HttpProxyStateTracker::s_WinHttpGetProxySettingsResultEx; |
| 29 | std::optional<LxssDynamicFunction<decltype(FreeProxySettingsEx)>> HttpProxyStateTracker::s_WinHttpFreeProxySettingsEx; |
| 30 | |
| 31 | // Helpers for using Winhttp's APIs |
| 32 | HRESULT HttpProxyStateTracker::s_LoadWinHttpProxyMethods() noexcept |
| 33 | try |
| 34 | { |
| 35 | static wil::shared_hmodule winHttpModule; |
| 36 | static std::once_flag winHttpLoadFlag; |
| 37 | |
| 38 | // Load Winhttp dll only once |
| 39 | std::call_once(winHttpLoadFlag, [&]() { |
| 40 | winHttpModule.reset(LoadLibraryEx(c_winhttpModuleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); |
| 41 | THROW_LAST_ERROR_IF(!winHttpModule); |
| 42 | }); |
| 43 | |
| 44 | // Initialize dynamic functions for the WinHttp Proxy OS APIs |
| 45 | // Not using the throwing constructor |
| 46 | // as failures should not show up in the Error logs as they can falsely flag failure to start the container |
| 47 | LxssDynamicFunction<decltype(RegisterProxyChangeNotification)> local_WinHttpRegisterProxyChangeNotification{DynamicFunctionErrorLogs::None}; |
| 48 | LxssDynamicFunction<decltype(UnregisterProxyChangeNotification)> local_WinHttpUnregisterProxyChangeNotification{DynamicFunctionErrorLogs::None}; |
| 49 | LxssDynamicFunction<decltype(GetProxySettingsEx)> local_WinHttpGetProxySettingsEx{DynamicFunctionErrorLogs::None}; |
| 50 | LxssDynamicFunction<decltype(GetProxySettingsResultEx)> local_WinHttpGetProxySettingsResultEx{DynamicFunctionErrorLogs::None}; |
| 51 | LxssDynamicFunction<decltype(FreeProxySettingsEx)> local_WinHttpFreeProxySettingsEx{DynamicFunctionErrorLogs::None}; |
| 52 | |
| 53 | // try to load each function - only save if all succeed |
| 54 | RETURN_IF_FAILED_EXPECTED( |
| 55 | local_WinHttpRegisterProxyChangeNotification.load(winHttpModule, "WinHttpRegisterProxyChangeNotification")); |
| 56 | RETURN_IF_FAILED_EXPECTED( |
| 57 | local_WinHttpUnregisterProxyChangeNotification.load(winHttpModule, "WinHttpUnregisterProxyChangeNotification")); |
| 58 | RETURN_IF_FAILED_EXPECTED(local_WinHttpGetProxySettingsEx.load(winHttpModule, "WinHttpGetProxySettingsEx")); |
| 59 | RETURN_IF_FAILED_EXPECTED(local_WinHttpGetProxySettingsResultEx.load(winHttpModule, "WinHttpGetProxySettingsResultEx")); |
| 60 | RETURN_IF_FAILED_EXPECTED(local_WinHttpFreeProxySettingsEx.load(winHttpModule, "WinHttpFreeProxySettingsEx")); |
| 61 | |
| 62 | s_WinHttpRegisterProxyChangeNotification.emplace(std::move(local_WinHttpRegisterProxyChangeNotification)); |
| 63 | s_WinHttpUnregisterProxyChangeNotification.emplace(std::move(local_WinHttpUnregisterProxyChangeNotification)); |
| 64 | s_WinHttpGetProxySettingsEx.emplace(std::move(local_WinHttpGetProxySettingsEx)); |
| 65 | s_WinHttpGetProxySettingsResultEx.emplace(std::move(local_WinHttpGetProxySettingsResultEx)); |
| 66 | s_WinHttpFreeProxySettingsEx.emplace(std::move(local_WinHttpFreeProxySettingsEx)); |
| 67 | return S_OK; |
| 68 | } |
| 69 | CATCH_RETURN() |
| 70 | |
| 71 | void FreeHttpProxySettings(WINHTTP_PROXY_SETTINGS_EX* proxySettings) noexcept |
| 72 | try |
| 73 | { |
| 74 | THROW_IF_WIN32_ERROR(HttpProxyStateTracker::s_WinHttpFreeProxySettingsEx.value()(WinHttpProxySettingsTypeWsl, proxySettings)); |
| 75 | } |
| 76 | CATCH_LOG() |
| 77 | |
| 78 | auto CallbackStatusToString(DWORD internetStatus) noexcept |
| 79 | { |
| 80 | switch (internetStatus) |
| 81 | { |
| 82 | case WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE: |
| 83 | return "WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE"; |
| 84 | case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: |
| 85 | return "WINHTTP_CALLBACK_STATUS_REQUEST_ERROR"; |
| 86 | case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: |
| 87 | return " WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING"; |
| 88 | default: |
| 89 | return "Invalid status"; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // struct to contain proxy specific settings |
| 94 | |
| 95 | void LogHttpProxySettings(const HttpProxySettings& settings) noexcept |
| 96 | try |
| 97 | { |
| 98 | WSL_LOG("OnProxyRequestComplete", TraceLoggingValue(settings.ToString().c_str(), "newProxySettings")); |
| 99 | } |
| 100 | CATCH_LOG() |
| 101 | |
| 102 | HttpProxySettings::HttpProxySettings(const WINHTTP_PROXY_SETTINGS_EX& proxySettings) |
| 103 | { |
| 104 | if (WI_IsFlagSet(proxySettings.ullFlags, WINHTTP_PROXY_TYPE_PROXY)) |
| 105 | { |
| 106 | Proxy = wsl::shared::string::WideToMultiByte(proxySettings.pcwszProxy); |
| 107 | SecureProxy = wsl::shared::string::WideToMultiByte(proxySettings.pcwszSecureProxy); |
| 108 | |
| 109 | const auto proxyBypasses = wil::make_range(proxySettings.rgpcwszProxyBypasses, proxySettings.cProxyBypasses); |
| 110 | std::transform(std::cbegin(proxyBypasses), std::cend(proxyBypasses), std::back_inserter(ProxyBypasses), [](const auto& proxyBypass) { |
| 111 | return wsl::shared::string::WideToMultiByte(proxyBypass); |
| 112 | }); |
| 113 | |
| 114 | if (!ProxyBypasses.empty()) |
| 115 | { |
| 116 | ProxyBypassesComma = std::accumulate( |
| 117 | std::next(std::cbegin(ProxyBypasses)), |
| 118 | std::cend(ProxyBypasses), |
| 119 | ProxyBypasses.front(), |
| 120 | [](std::string previous, const std::string& proxyBypass) { return std::move(previous) + "," + proxyBypass; }); |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | if (WI_IsFlagSet(proxySettings.ullFlags, WINHTTP_PROXY_TYPE_AUTO_PROXY_URL)) |
| 125 | { |
| 126 | PacUrl = wsl::shared::string::WideToMultiByte(proxySettings.pcwszAutoconfigUrl); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | std::string HttpProxySettings::ToString() const |
| 131 | { |
| 132 | std::ostringstream httpProxySettingsString{}; |
| 133 | httpProxySettingsString << "Proxy: " << Proxy << ", SecureProxy: " << SecureProxy << ", PacUrl: " << PacUrl |
| 134 | << ", ProxyBypasses: " << ProxyBypassesComma; |
| 135 | return httpProxySettingsString.str(); |
| 136 | } |
| 137 | |
| 138 | bool HttpProxySettings::HasSettingsConfigured() const |
| 139 | { |
| 140 | return !(Proxy.empty() && SecureProxy.empty() && PacUrl.empty()); |
| 141 | } |
| 142 | |
| 143 | void CALLBACK HttpProxyStateTracker::s_GetProxySettingsExCallback( |
| 144 | _In_ HINTERNET resolver, _In_ DWORD_PTR context, _In_ DWORD internetStatus, _In_ PVOID statusInformation, _In_ DWORD) noexcept |
| 145 | try |
| 146 | { |
| 147 | HttpProxyStateTracker* proxyTracker = reinterpret_cast<HttpProxyStateTracker*>(context); |
| 148 | const WINHTTP_ASYNC_RESULT* pAsyncResult = static_cast<WINHTTP_ASYNC_RESULT*>(statusInformation); |
| 149 | |
| 150 | if (!proxyTracker) |
| 151 | { |
| 152 | return; |
| 153 | } |
| 154 | |
| 155 | WSL_LOG( |
| 156 | "s_GetProxySettingsExCallback-CallbackInfo", TraceLoggingValue(CallbackStatusToString(internetStatus), "internetStatus")); |
| 157 | |
| 158 | // This is the last WinHttp callback for this request, received after the request handles were closed. |
| 159 | if (internetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) |
| 160 | { |
| 161 | proxyTracker->m_callbackQueue.submit([proxyTracker] { proxyTracker->RequestClosed(); }); |
| 162 | return; |
| 163 | } |
| 164 | |
| 165 | DWORD error = ERROR_SUCCESS; |
| 166 | PCSTR executionStep = ""; |
| 167 | unique_winhttp_proxy_settings proxySettings{}; |
| 168 | switch (internetStatus) |
| 169 | { |
| 170 | case WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE: |
| 171 | { |
| 172 | executionStep = "WinHttpGetProxySettingsResultEx"; |
| 173 | error = s_WinHttpGetProxySettingsResultEx.value()(resolver, &proxySettings); |
| 174 | break; |
| 175 | } |
| 176 | case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: |
| 177 | { |
| 178 | executionStep = "CallbackError"; |
| 179 | error = pAsyncResult->dwError; |
| 180 | break; |
| 181 | } |
| 182 | |
| 183 | default: |
| 184 | { |
| 185 | error = ERROR_INVALID_PARAMETER; |
| 186 | break; |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | if (SUCCEEDED_WIN32(error)) |
| 191 | { |
| 192 | WSL_LOG( |
| 193 | "s_GetProxySettingsExCallback-Results", |
| 194 | TraceLoggingValue(proxySettings.pcwszProxy, "pcwszProxy"), |
| 195 | TraceLoggingValue(proxySettings.pcwszSecureProxy, "pcwszSecureProxy"), |
| 196 | TraceLoggingValue(proxySettings.pcwszAutoconfigUrl, "pcwszAutoconfigUrl"), |
| 197 | TraceLoggingValue(proxySettings.cProxyBypasses, "cProxyBypasses")); |
| 198 | } |
| 199 | else |
| 200 | { |
| 201 | WSL_LOG( |
| 202 | "WinHttpGetProxySettingsExCallbackFailed", |
| 203 | TraceLoggingValue(error, "result"), |
| 204 | TraceLoggingValue(executionStep, "executionStep")); |
| 205 | } |
| 206 | LOG_IF_WIN32_ERROR(error); |
| 207 | |
| 208 | HttpProxySettings newProxySettings{proxySettings}; |
| 209 | proxyTracker->m_callbackQueue.submit([proxyTracker, error, movedProxySettings = std::move(newProxySettings)]() mutable { |
| 210 | proxyTracker->RequestCompleted(error, std::move(movedProxySettings)); |
| 211 | }); |
| 212 | } |
| 213 | CATCH_LOG() |
| 214 | |
| 215 | void HttpProxyStateTracker::RequestClosed() noexcept |
| 216 | try |
| 217 | { |
| 218 | WI_ASSERT(m_callbackQueue.isRunningInQueue()); |
| 219 | const auto requery = m_queryState == QueryState::PendingAndQueueAdditional; |
| 220 | m_queryState = QueryState::NoQuery; |
| 221 | if (requery) |
| 222 | { |
| 223 | LOG_IF_FAILED(wil::ResultFromException([&] { QueryProxySettingsAsync(); })); |
| 224 | } |
| 225 | |
| 226 | if (m_queryState == QueryState::NoQuery) |
| 227 | { |
| 228 | m_requestFinished.SetEvent(); |
| 229 | } |
| 230 | } |
| 231 | CATCH_LOG() |
| 232 | |
| 233 | bool HttpProxyStateTracker::AreProxyStringsIdentical(const HttpProxySettings& newSettings) const |
| 234 | { |
| 235 | if (!m_proxySettings.has_value()) |
| 236 | { |
| 237 | return false; |
| 238 | } |
| 239 | // note that we do not include the UnsupportedProxyDropReason intentionally here as if that is only change we don't want to trigger a toast |
| 240 | return ( |
| 241 | newSettings.Proxy == m_proxySettings->Proxy && newSettings.SecureProxy == m_proxySettings->SecureProxy && |
| 242 | newSettings.ProxyBypasses == m_proxySettings->ProxyBypasses && newSettings.PacUrl == m_proxySettings->PacUrl); |
| 243 | } |
| 244 | |
| 245 | void HttpProxyStateTracker::RequestCompleted(_In_ DWORD error, _In_ HttpProxySettings&& newProxySettings) noexcept |
| 246 | try |
| 247 | { |
| 248 | WI_ASSERT(m_callbackQueue.isRunningInQueue()); |
| 249 | if (SUCCEEDED_WIN32(error)) |
| 250 | { |
| 251 | auto dataLock = m_proxySettingsLock.lock(); |
| 252 | |
| 253 | FilterProxySettingsByNetworkConfiguration(newProxySettings, m_networkMode); |
| 254 | |
| 255 | if (!AreProxyStringsIdentical(newProxySettings)) |
| 256 | { |
| 257 | LogHttpProxySettings(newProxySettings); |
| 258 | m_proxySettings = std::move(newProxySettings); |
| 259 | |
| 260 | // If there was a setting changes, and this is not the initial proxy query, notify the user to restart WSL to get new proxy changes. |
| 261 | if (m_initialProxyQueryCompleted.is_signaled()) |
| 262 | { |
| 263 | notifications::DisplayProxyChangeNotification(m_localizedProxyChangeString); |
| 264 | } |
| 265 | } |
| 266 | else |
| 267 | { |
| 268 | // note that the DropReason is not included in AreProxyStringsIdentical as we don't want to toast if that is only change, |
| 269 | // but we still want to make sure the drop reason is updated; otherwise, we risk not reporting the correct drop reason to user |
| 270 | if (newProxySettings.UnsupportedProxyDropReason != m_proxySettings->UnsupportedProxyDropReason) |
| 271 | { |
| 272 | m_proxySettings->UnsupportedProxyDropReason = newProxySettings.UnsupportedProxyDropReason; |
| 273 | } |
| 274 | } |
| 275 | m_initialProxyQueryCompleted.SetEvent(); |
| 276 | } |
| 277 | |
| 278 | // It is guaranteed that after closing the handles, a callback with status WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING |
| 279 | // will be issued to indicate that the callback has been cleaned up. |
| 280 | { |
| 281 | const auto requestLock = m_requestLock.lock(); |
| 282 | m_resolver.reset(); |
| 283 | m_session.reset(); |
| 284 | } |
| 285 | } |
| 286 | CATCH_LOG() |
| 287 | |
| 288 | // Proxy query tracking. |
| 289 | void CALLBACK HttpProxyStateTracker::s_OnProxyChange(_In_ ULONGLONG flags, _In_ void* pContext) noexcept |
| 290 | try |
| 291 | { |
| 292 | WSL_LOG("OnProxyChange", TraceLoggingValue(flags, "flags")); |
| 293 | const auto proxyStateTracking = static_cast<HttpProxyStateTracker*>(pContext); |
| 294 | |
| 295 | // Ensure this is a change notification. |
| 296 | if (WI_IsFlagClear(flags, WINHTTP_PROXY_NOTIFY_CHANGE)) |
| 297 | { |
| 298 | return; |
| 299 | } |
| 300 | |
| 301 | proxyStateTracking->m_callbackQueue.submit([proxyStateTracking] { proxyStateTracking->QueryProxySettingsAsync(); }); |
| 302 | } |
| 303 | CATCH_LOG() |
| 304 | |
| 305 | UnsupportedProxyReason HttpProxyStateTracker::IsUnsupportedProxy(LPCWSTR proxyString, wsl::core::NetworkingMode configuration) noexcept |
| 306 | try |
| 307 | { |
| 308 | if (!proxyString) |
| 309 | { |
| 310 | return UnsupportedProxyReason::Supported; |
| 311 | } |
| 312 | |
| 313 | URL_COMPONENTS url{}; |
| 314 | url.dwStructSize = sizeof(url); // Required for WinHttpCrackUrl |
| 315 | url.dwHostNameLength = -1; // Indicates what we want cracked. |
| 316 | const DWORD proxyLength = static_cast<DWORD>(wcslen(proxyString)); |
| 317 | |
| 318 | if (proxyLength == 0) |
| 319 | { |
| 320 | return UnsupportedProxyReason::Supported; |
| 321 | } |
| 322 | |
| 323 | THROW_IF_WIN32_BOOL_FALSE(WinHttpCrackUrl(proxyString, proxyLength, 0, &url)); |
| 324 | |
| 325 | // lpszHostName name will still include <proxy>:port portion of proxy string http://<proxy>:port, but the hostNameLength truncates the port |
| 326 | std::wstring portRemoved{url.lpszHostName, url.dwHostNameLength}; |
| 327 | |
| 328 | // IPv6 strings can come in format http://[<IPv6 address>]:port |
| 329 | const auto openBracket = portRemoved.find_first_of(L"["); |
| 330 | if (openBracket != ::std::wstring::npos) |
| 331 | { |
| 332 | const auto closeBracket = portRemoved.find_first_of(L"]"); |
| 333 | if (closeBracket == ::std::wstring::npos || (openBracket + 1 >= closeBracket)) |
| 334 | { |
| 335 | // no other of below checks can contain brackets |
| 336 | return UnsupportedProxyReason::Supported; |
| 337 | } |
| 338 | portRemoved = portRemoved.substr(openBracket + 1, closeBracket - openBracket - 1); |
| 339 | } |
| 340 | |
| 341 | in6_addr addrV6{}; |
| 342 | PCWSTR pStringEnd{}; // not used by us but still required for *ToAddressW |
| 343 | if (SUCCEEDED_WIN32(RtlIpv6StringToAddressW(portRemoved.c_str(), &pStringEnd, &addrV6))) |
| 344 | { |
| 345 | if (configuration != wsl::core::NetworkingMode::Mirrored) |
| 346 | { |
| 347 | return UnsupportedProxyReason::Ipv6NotMirrored; // v6 is only supported in mirrored mode |
| 348 | } |
| 349 | if (IN6_IS_ADDR_LOOPBACK(&addrV6)) |
| 350 | { |
| 351 | return UnsupportedProxyReason::LoopbackV6; // v6 loopback is not supported in any network configuration |
| 352 | } |
| 353 | return UnsupportedProxyReason::Supported; |
| 354 | } |
| 355 | |
| 356 | // v4 loopback is only supported in mirrored mode |
| 357 | if (configuration != wsl::core::NetworkingMode::Mirrored) |
| 358 | { |
| 359 | in_addr addrV4{}; |
| 360 | if (SUCCEEDED_WIN32(RtlIpv4StringToAddressW(portRemoved.c_str(), true, &pStringEnd, &addrV4))) |
| 361 | { |
| 362 | if (IN4_IS_ADDR_LOOPBACK(&addrV4)) |
| 363 | { |
| 364 | return UnsupportedProxyReason::LoopbackNotMirrored; |
| 365 | } |
| 366 | return UnsupportedProxyReason::Supported; |
| 367 | } |
| 368 | |
| 369 | if (wsl::shared::string::IsEqual(portRemoved, c_loopback, true) || wsl::shared::string::IsEqual(portRemoved, c_localhost, true)) |
| 370 | { |
| 371 | return UnsupportedProxyReason::LoopbackNotMirrored; |
| 372 | } |
| 373 | |
| 374 | DWORD size = 0; |
| 375 | std::wstring computerName{}; |
| 376 | |
| 377 | if (!GetComputerNameW(nullptr, &size)) |
| 378 | { |
| 379 | const DWORD err = GetLastError(); |
| 380 | THROW_WIN32_IF(err, err != ERROR_BUFFER_OVERFLOW); |
| 381 | } |
| 382 | computerName.resize(size, L'\0'); |
| 383 | |
| 384 | THROW_IF_WIN32_BOOL_FALSE(GetComputerNameW(computerName.data(), &size)); |
| 385 | |
| 386 | // remove any embedded null characters |
| 387 | const auto offset = computerName.find_first_of(L'\0'); |
| 388 | if (offset != ::std::wstring::npos) |
| 389 | { |
| 390 | computerName.resize(offset); |
| 391 | } |
| 392 | |
| 393 | if (wsl::shared::string::IsEqual(computerName, portRemoved, true)) |
| 394 | { |
| 395 | return UnsupportedProxyReason::LoopbackNotMirrored; |
| 396 | } |
| 397 | } |
| 398 | return UnsupportedProxyReason::Supported; |
| 399 | } |
| 400 | catch (...) |
| 401 | { |
| 402 | LOG_CAUGHT_EXCEPTION(); |
| 403 | return UnsupportedProxyReason::UnsupportedError; |
| 404 | } |
| 405 | |
| 406 | void HttpProxyStateTracker::FilterProxySettingsByNetworkConfiguration(HttpProxySettings& settings, wsl::core::NetworkingMode mode) noexcept |
| 407 | try |
| 408 | { |
| 409 | const auto proxySupportState = IsUnsupportedProxy(wsl::shared::string::MultiByteToWide(settings.Proxy).c_str(), mode); |
| 410 | const auto secureProxySupportState = IsUnsupportedProxy(wsl::shared::string::MultiByteToWide(settings.SecureProxy).c_str(), mode); |
| 411 | if (proxySupportState != UnsupportedProxyReason::Supported) |
| 412 | { |
| 413 | settings.Proxy.clear(); |
| 414 | settings.UnsupportedProxyDropReason = proxySupportState; |
| 415 | } |
| 416 | |
| 417 | if (secureProxySupportState != UnsupportedProxyReason::Supported) |
| 418 | { |
| 419 | settings.SecureProxy.clear(); |
| 420 | settings.UnsupportedProxyDropReason = secureProxySupportState; |
| 421 | } |
| 422 | |
| 423 | // If we now have no proxy settings configured, we should clear the proxy bypasses too. |
| 424 | // Note that if one setting was cleared, but other was not, the proxy bypasses are still valid. |
| 425 | if (settings.Proxy.empty() && settings.SecureProxy.empty()) |
| 426 | { |
| 427 | settings.ProxyBypasses.clear(); |
| 428 | settings.ProxyBypassesComma.clear(); |
| 429 | } |
| 430 | |
| 431 | if (proxySupportState != UnsupportedProxyReason::Supported || secureProxySupportState != UnsupportedProxyReason::Supported) |
| 432 | { |
| 433 | WSL_LOG( |
| 434 | "AutoProxy-DropUnsupportedSetting", |
| 435 | TraceLoggingValue(wsl::core::ToString(mode), "InvalidNetworkConfiguration"), |
| 436 | TraceLoggingValue(ToString(proxySupportState), "DropHttpProxySetting"), |
| 437 | TraceLoggingValue(ToString(secureProxySupportState), "DropHttpsProxySetting")); |
| 438 | } |
| 439 | } |
| 440 | CATCH_LOG() |
| 441 | |
| 442 | void HttpProxyStateTracker::QueryProxySettingsAsync() |
| 443 | { |
| 444 | PCSTR executionStep = ""; |
| 445 | try |
| 446 | { |
| 447 | WI_ASSERT(m_callbackQueue.isRunningInQueue()); |
| 448 | const auto requestLock = m_requestLock.lock(); |
| 449 | if (m_stopping) |
| 450 | { |
| 451 | return; |
| 452 | } |
| 453 | |
| 454 | if (m_queryState == QueryState::PendingAndQueueAdditional) |
| 455 | { |
| 456 | return; |
| 457 | } |
| 458 | |
| 459 | if (m_queryState == QueryState::Pending) |
| 460 | { |
| 461 | m_queryState = QueryState::PendingAndQueueAdditional; |
| 462 | WSL_LOG("Run another http proxy query after current completes"); |
| 463 | return; |
| 464 | } |
| 465 | |
| 466 | executionStep = "impersonate_token"; |
| 467 | auto runAsUser = wil::impersonate_token(m_userToken.get()); |
| 468 | |
| 469 | executionStep = "WinHttpOpen"; |
| 470 | // |
| 471 | // Open session and setup resolver handle |
| 472 | // |
| 473 | wil::unique_winhttp_hinternet session(WinHttpOpen( |
| 474 | nullptr, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC)); |
| 475 | THROW_LAST_ERROR_IF(!session.is_valid()); |
| 476 | |
| 477 | executionStep = "WinHttpCreateProxyResolver"; |
| 478 | wil::unique_winhttp_hinternet resolver{}; |
| 479 | THROW_IF_WIN32_ERROR(WinHttpCreateProxyResolver(session.get(), &resolver)); |
| 480 | |
| 481 | executionStep = "WinHttpSetOption"; |
| 482 | DWORD_PTR context = reinterpret_cast<DWORD_PTR>(this); |
| 483 | THROW_IF_WIN32_BOOL_FALSE(WinHttpSetOption(resolver.get(), WINHTTP_OPTION_CONTEXT_VALUE, &context, sizeof(context))); |
| 484 | |
| 485 | executionStep = "WinHttpSetStatusCallback"; |
| 486 | // We need to set flag WINHTTP_CALLBACK_FLAG_HANDLES in order to get the WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING |
| 487 | // status callback when a handle is closed. |
| 488 | // Using those flags will always result in 2 callbacks, 1 on success/failure, 1 when closing the requests. |
| 489 | // Without these we risk race conditions while deconstructing the ProxyTracker. |
| 490 | THROW_LAST_ERROR_IF( |
| 491 | WinHttpSetStatusCallback( |
| 492 | resolver.get(), |
| 493 | s_GetProxySettingsExCallback, |
| 494 | WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE | WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | WINHTTP_CALLBACK_FLAG_HANDLES, |
| 495 | 0) == WINHTTP_INVALID_STATUS_CALLBACK); |
| 496 | |
| 497 | WINHTTP_PROXY_SETTINGS_PARAM ProxySettingsParam{0, nullptr, nullptr}; |
| 498 | |
| 499 | // Track the request before calling WinHttp because it can complete asynchronously before returning. |
| 500 | m_requestFinished.ResetEvent(); |
| 501 | m_queryState = QueryState::Pending; |
| 502 | |
| 503 | executionStep = "WinHttpGetProxySettingsEx"; |
| 504 | // Query the proxy settings |
| 505 | const DWORD dwError = s_WinHttpGetProxySettingsEx.value()(resolver.get(), WinHttpProxySettingsTypeWsl, &ProxySettingsParam, context); |
| 506 | |
| 507 | if (dwError != ERROR_IO_PENDING && dwError != ERROR_SUCCESS) |
| 508 | { |
| 509 | THROW_WIN32(dwError); |
| 510 | } |
| 511 | |
| 512 | // Transfer ownership of HTTP handles and track request |
| 513 | m_resolver = std::move(resolver); |
| 514 | m_session = std::move(session); |
| 515 | } |
| 516 | catch (...) |
| 517 | { |
| 518 | const auto hr = wil::ResultFromCaughtException(); |
| 519 | WSL_LOG("QueryProxySettingsFailed", TraceLoggingHResult(hr, "result"), TraceLoggingValue(executionStep, "executionStep")); |
| 520 | |
| 521 | throw; |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | HttpProxyStateTracker::HttpProxyStateTracker(int ProxyTimeout, HANDLE UserToken, wsl::core::NetworkingMode mode) : |
| 526 | m_networkMode{mode}, |
| 527 | m_initialQueryTimeout{ProxyTimeout}, |
| 528 | m_localizedProxyChangeString{wsl::shared::Localization::MessageHttpProxyChangeDetected()} |
| 529 | { |
| 530 | THROW_IF_WIN32_BOOL_FALSE(::DuplicateTokenEx(UserToken, MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenImpersonation, &m_userToken)); |
| 531 | THROW_IF_WIN32_ERROR(s_WinHttpRegisterProxyChangeNotification.value()(WINHTTP_PROXY_NOTIFY_CHANGE, s_OnProxyChange, this, &m_proxyRegistrationHandle)); |
| 532 | m_callbackQueue.submit([this] { QueryProxySettingsAsync(); }); |
| 533 | } |
| 534 | |
| 535 | HttpProxyStateTracker::~HttpProxyStateTracker() |
| 536 | { |
| 537 | // cancel Proxy change notifications, preventing queries from being triggered. |
| 538 | if (m_proxyRegistrationHandle != nullptr) |
| 539 | { |
| 540 | try |
| 541 | { |
| 542 | THROW_IF_WIN32_ERROR(s_WinHttpUnregisterProxyChangeNotification.value()(m_proxyRegistrationHandle)); |
| 543 | } |
| 544 | CATCH_LOG() |
| 545 | } |
| 546 | |
| 547 | { |
| 548 | const auto requestLock = m_requestLock.lock(); |
| 549 | m_stopping = true; |
| 550 | |
| 551 | // It is guaranteed that after closing the handles, a callback with status WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING |
| 552 | // will be issued and it will be the last callback for this request, making it safe to delete the ProxyStateTracker. |
| 553 | m_resolver.reset(); |
| 554 | m_session.reset(); |
| 555 | } |
| 556 | |
| 557 | // Wait for all requests to complete. At this point no new requests can be started since we unregistered for proxy change |
| 558 | // notifications. Without this we risk racing proxy callbacks and them calling into a cleaned up ProxyStateTracker. |
| 559 | m_requestFinished.wait(); |
| 560 | |
| 561 | // Cancel all pending work in the queue and prevent additional requests |
| 562 | m_callbackQueue.cancel(); |
| 563 | } |
| 564 | |
| 565 | std::optional<HttpProxySettings> HttpProxyStateTracker::WaitForInitialProxySettings() |
| 566 | { |
| 567 | m_initialProxyQueryCompleted.wait(m_initialQueryTimeout); |
| 568 | auto lock = m_proxySettingsLock.lock(); |
| 569 | return m_proxySettings; |
| 570 | } |
| 571 | |
| 572 | void HttpProxyStateTracker::ConfigureNetworkingMode(wsl::core::NetworkingMode mode) noexcept |
| 573 | { |
| 574 | auto lock = m_proxySettingsLock.lock(); |
| 575 | // if we fall back to NAT mode need to strip bad settings |
| 576 | if (m_proxySettings.has_value() && mode != m_networkMode) |
| 577 | { |
| 578 | FilterProxySettingsByNetworkConfiguration(m_proxySettings.value(), mode); |
| 579 | } |
| 580 | m_networkMode = mode; |
| 581 | } |