master
cpp 581 lines 22.5 KB
Raw
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 }