1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/proxy/dhcp_proxy_script_fetcher_win.h"
8 #include "base/bind_helpers.h"
9 #include "base/profiler/scoped_tracker.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "net/base/net_errors.h"
12 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
16 #pragma comment(lib, "iphlpapi.lib")
20 // How many threads to use at maximum to do DHCP lookups. This is
21 // chosen based on the following UMA data:
22 // - When OnWaitTimer fires, ~99.8% of users have 6 or fewer network
23 // adapters enabled for DHCP in total.
24 // - At the same measurement point, ~99.7% of users have 3 or fewer pending
25 // DHCP adapter lookups.
26 // - There is however a very long and thin tail of users who have
27 // systems reporting up to 100+ adapters (this must be some very weird
28 // OS bug (?), probably the cause of http://crbug.com/240034).
30 // The maximum number of threads is chosen such that even systems that
31 // report a huge number of network adapters should not run out of
32 // memory from this number of threads, while giving a good chance of
33 // getting back results for any responsive adapters.
35 // The ~99.8% of systems that have 6 or fewer network adapters will
36 // not grow the thread pool to its maximum size (rather, they will
37 // grow it to 6 or fewer threads) so setting the limit lower would not
38 // improve performance or memory usage on those systems.
39 const int kMaxDhcpLookupThreads
= 12;
41 // How long to wait at maximum after we get results (a PAC file or
42 // knowledge that no PAC file is configured) from whichever network
43 // adapter finishes first.
44 const int kMaxWaitAfterFirstResultMs
= 400;
46 const int kGetAdaptersAddressesErrors
[] = {
47 ERROR_ADDRESS_NOT_ASSOCIATED
,
48 ERROR_BUFFER_OVERFLOW
,
49 ERROR_INVALID_PARAMETER
,
50 ERROR_NOT_ENOUGH_MEMORY
,
58 DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
59 URLRequestContext
* url_request_context
)
60 : state_(STATE_START
),
61 num_pending_fetchers_(0),
62 destination_string_(NULL
),
63 url_request_context_(url_request_context
) {
64 DCHECK(url_request_context_
);
66 worker_pool_
= new base::SequencedWorkerPool(kMaxDhcpLookupThreads
,
70 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
71 // Count as user-initiated if we are not yet in STATE_DONE.
74 worker_pool_
->Shutdown();
77 int DhcpProxyScriptFetcherWin::Fetch(base::string16
* utf16_text
,
78 const CompletionCallback
& callback
) {
79 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
80 tracked_objects::ScopedTracker
tracking_profile1(
81 FROM_HERE_WITH_EXPLICIT_FUNCTION(
82 "476182 DhcpProxyScriptFetcherWin::Fetch 1"));
84 DCHECK(CalledOnValidThread());
85 if (state_
!= STATE_START
&& state_
!= STATE_DONE
) {
87 return ERR_UNEXPECTED
;
90 state_
= STATE_WAIT_ADAPTERS
;
92 destination_string_
= utf16_text
;
94 last_query_
= ImplCreateAdapterQuery();
95 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
96 tracked_objects::ScopedTracker
tracking_profile2(
97 FROM_HERE_WITH_EXPLICIT_FUNCTION(
98 "476182 DhcpProxyScriptFetcherWin::Fetch 2"));
99 GetTaskRunner()->PostTaskAndReply(
102 &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames
,
105 &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone
,
109 return ERR_IO_PENDING
;
112 void DhcpProxyScriptFetcherWin::Cancel() {
113 DCHECK(CalledOnValidThread());
118 void DhcpProxyScriptFetcherWin::CancelImpl() {
119 DCHECK(CalledOnValidThread());
121 if (state_
!= STATE_DONE
) {
126 for (FetcherVector::iterator it
= fetchers_
.begin();
127 it
!= fetchers_
.end();
136 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
137 scoped_refptr
<AdapterQuery
> query
) {
138 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
139 tracked_objects::ScopedTracker
tracking_profile1(
140 FROM_HERE_WITH_EXPLICIT_FUNCTION(
142 "DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone 1"));
144 DCHECK(CalledOnValidThread());
146 // This can happen if this object is reused for multiple queries,
147 // and a previous query was cancelled before it completed.
148 if (query
.get() != last_query_
.get())
152 // Enable unit tests to wait for this to happen; in production this function
154 ImplOnGetCandidateAdapterNamesDone();
156 // We may have been cancelled.
157 if (state_
!= STATE_WAIT_ADAPTERS
)
160 state_
= STATE_NO_RESULTS
;
162 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
163 tracked_objects::ScopedTracker
tracking_profile2(
164 FROM_HERE_WITH_EXPLICIT_FUNCTION(
166 "DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone 2"));
168 const std::set
<std::string
>& adapter_names
= query
->adapter_names();
170 if (adapter_names
.empty()) {
175 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
176 tracked_objects::ScopedTracker
tracking_profile3(
177 FROM_HERE_WITH_EXPLICIT_FUNCTION(
179 "DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone 3"));
181 for (std::set
<std::string
>::const_iterator it
= adapter_names
.begin();
182 it
!= adapter_names
.end();
184 DhcpProxyScriptAdapterFetcher
* fetcher(ImplCreateAdapterFetcher());
186 *it
, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone
,
187 base::Unretained(this)));
188 fetchers_
.push_back(fetcher
);
190 num_pending_fetchers_
= fetchers_
.size();
193 std::string
DhcpProxyScriptFetcherWin::GetFetcherName() const {
194 DCHECK(CalledOnValidThread());
198 const GURL
& DhcpProxyScriptFetcherWin::GetPacURL() const {
199 DCHECK(CalledOnValidThread());
200 DCHECK_EQ(state_
, STATE_DONE
);
205 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result
) {
206 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
208 if (--num_pending_fetchers_
== 0) {
213 // If the only pending adapters are those less preferred than one
214 // with a valid PAC script, we do not need to wait any longer.
215 for (FetcherVector::iterator it
= fetchers_
.begin();
216 it
!= fetchers_
.end();
218 bool did_finish
= (*it
)->DidFinish();
219 int result
= (*it
)->GetResult();
220 if (did_finish
&& result
== OK
) {
224 if (!did_finish
|| result
!= ERR_PAC_NOT_IN_DHCP
) {
229 // Once we have a single result, we set a maximum on how long to wait
230 // for the rest of the results.
231 if (state_
== STATE_NO_RESULTS
) {
232 state_
= STATE_SOME_RESULTS
;
233 wait_timer_
.Start(FROM_HERE
,
234 ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer
);
238 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
239 DCHECK_EQ(state_
, STATE_SOME_RESULTS
);
244 void DhcpProxyScriptFetcherWin::TransitionToDone() {
245 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
247 int result
= ERR_PAC_NOT_IN_DHCP
; // Default if no fetchers.
248 if (!fetchers_
.empty()) {
249 // Scan twice for the result; once through the whole list for success,
250 // then if no success, return result for most preferred network adapter,
251 // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
252 // Default to ERR_ABORTED if no fetcher completed.
253 result
= ERR_ABORTED
;
254 for (FetcherVector::iterator it
= fetchers_
.begin();
255 it
!= fetchers_
.end();
257 if ((*it
)->DidFinish() && (*it
)->GetResult() == OK
) {
259 *destination_string_
= (*it
)->GetPacScript();
260 pac_url_
= (*it
)->GetPacURL();
265 destination_string_
->clear();
266 for (FetcherVector::iterator it
= fetchers_
.begin();
267 it
!= fetchers_
.end();
269 if ((*it
)->DidFinish()) {
270 result
= (*it
)->GetResult();
271 if (result
!= ERR_PAC_NOT_IN_DHCP
) {
279 CompletionCallback callback
= callback_
;
281 DCHECK_EQ(state_
, STATE_DONE
);
282 DCHECK(fetchers_
.empty());
283 DCHECK(callback_
.is_null()); // Invariant of data.
285 // We may be deleted re-entrantly within this outcall.
286 callback
.Run(result
);
289 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
290 return num_pending_fetchers_
;
293 URLRequestContext
* DhcpProxyScriptFetcherWin::url_request_context() const {
294 return url_request_context_
;
297 scoped_refptr
<base::TaskRunner
> DhcpProxyScriptFetcherWin::GetTaskRunner() {
298 return worker_pool_
->GetTaskRunnerWithShutdownBehavior(
299 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN
);
302 DhcpProxyScriptAdapterFetcher
*
303 DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
304 return new DhcpProxyScriptAdapterFetcher(url_request_context_
,
308 DhcpProxyScriptFetcherWin::AdapterQuery
*
309 DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
310 return new AdapterQuery();
313 base::TimeDelta
DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
314 return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs
);
317 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
318 std::set
<std::string
>* adapter_names
) {
319 DCHECK(adapter_names
);
320 adapter_names
->clear();
322 // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
323 // avoid reallocation.
324 ULONG adapters_size
= 15000;
325 scoped_ptr
<IP_ADAPTER_ADDRESSES
, base::FreeDeleter
> adapters
;
326 ULONG error
= ERROR_SUCCESS
;
330 adapters
.reset(static_cast<IP_ADAPTER_ADDRESSES
*>(malloc(adapters_size
)));
331 // Return only unicast addresses, and skip information we do not need.
332 error
= GetAdaptersAddresses(AF_UNSPEC
,
333 GAA_FLAG_SKIP_ANYCAST
|
334 GAA_FLAG_SKIP_MULTICAST
|
335 GAA_FLAG_SKIP_DNS_SERVER
|
336 GAA_FLAG_SKIP_FRIENDLY_NAME
,
341 } while (error
== ERROR_BUFFER_OVERFLOW
&& num_tries
<= 3);
343 if (error
== ERROR_NO_DATA
) {
344 // There are no adapters that we care about.
348 if (error
!= ERROR_SUCCESS
) {
349 LOG(WARNING
) << "Unexpected error retrieving WPAD configuration from DHCP.";
353 IP_ADAPTER_ADDRESSES
* adapter
= NULL
;
354 for (adapter
= adapters
.get(); adapter
; adapter
= adapter
->Next
) {
355 if (adapter
->IfType
== IF_TYPE_SOFTWARE_LOOPBACK
)
357 if ((adapter
->Flags
& IP_ADAPTER_DHCP_ENABLED
) == 0)
360 DCHECK(adapter
->AdapterName
);
361 adapter_names
->insert(adapter
->AdapterName
);
367 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
370 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
371 ImplGetCandidateAdapterNames(&adapter_names_
);
374 const std::set
<std::string
>&
375 DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
376 return adapter_names_
;
379 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
380 std::set
<std::string
>* adapter_names
) {
381 return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names
);
384 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {