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/metrics/histogram.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "base/timer/elapsed_timer.h"
12 #include "net/base/net_errors.h"
13 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
17 #pragma comment(lib, "iphlpapi.lib")
21 // How many threads to use at maximum to do DHCP lookups. This is
22 // chosen based on the following UMA data:
23 // - When OnWaitTimer fires, ~99.8% of users have 6 or fewer network
24 // adapters enabled for DHCP in total.
25 // - At the same measurement point, ~99.7% of users have 3 or fewer pending
26 // DHCP adapter lookups.
27 // - There is however a very long and thin tail of users who have
28 // systems reporting up to 100+ adapters (this must be some very weird
29 // OS bug (?), probably the cause of http://crbug.com/240034).
31 // The maximum number of threads is chosen such that even systems that
32 // report a huge number of network adapters should not run out of
33 // memory from this number of threads, while giving a good chance of
34 // getting back results for any responsive adapters.
36 // The ~99.8% of systems that have 6 or fewer network adapters will
37 // not grow the thread pool to its maximum size (rather, they will
38 // grow it to 6 or fewer threads) so setting the limit lower would not
39 // improve performance or memory usage on those systems.
40 const int kMaxDhcpLookupThreads
= 12;
42 // How long to wait at maximum after we get results (a PAC file or
43 // knowledge that no PAC file is configured) from whichever network
44 // adapter finishes first.
45 const int kMaxWaitAfterFirstResultMs
= 400;
47 const int kGetAdaptersAddressesErrors
[] = {
48 ERROR_ADDRESS_NOT_ASSOCIATED
,
49 ERROR_BUFFER_OVERFLOW
,
50 ERROR_INVALID_PARAMETER
,
51 ERROR_NOT_ENOUGH_MEMORY
,
59 DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
60 URLRequestContext
* url_request_context
)
61 : state_(STATE_START
),
62 num_pending_fetchers_(0),
63 destination_string_(NULL
),
64 url_request_context_(url_request_context
) {
65 DCHECK(url_request_context_
);
67 worker_pool_
= new base::SequencedWorkerPool(kMaxDhcpLookupThreads
,
71 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
72 // Count as user-initiated if we are not yet in STATE_DONE.
75 worker_pool_
->Shutdown();
78 int DhcpProxyScriptFetcherWin::Fetch(base::string16
* utf16_text
,
79 const CompletionCallback
& callback
) {
80 DCHECK(CalledOnValidThread());
81 if (state_
!= STATE_START
&& state_
!= STATE_DONE
) {
83 return ERR_UNEXPECTED
;
86 fetch_start_time_
= base::TimeTicks::Now();
88 state_
= STATE_WAIT_ADAPTERS
;
90 destination_string_
= utf16_text
;
92 last_query_
= ImplCreateAdapterQuery();
93 GetTaskRunner()->PostTaskAndReply(
96 &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames
,
99 &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone
,
103 return ERR_IO_PENDING
;
106 void DhcpProxyScriptFetcherWin::Cancel() {
107 DCHECK(CalledOnValidThread());
109 if (state_
!= STATE_DONE
) {
110 // We only count this stat if the cancel was explicitly initiated by
111 // our client, and if we weren't already in STATE_DONE.
112 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCancelTime",
113 base::TimeTicks::Now() - fetch_start_time_
);
119 void DhcpProxyScriptFetcherWin::CancelImpl() {
120 DCHECK(CalledOnValidThread());
122 if (state_
!= STATE_DONE
) {
127 for (FetcherVector::iterator it
= fetchers_
.begin();
128 it
!= fetchers_
.end();
137 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
138 scoped_refptr
<AdapterQuery
> query
) {
139 DCHECK(CalledOnValidThread());
141 // This can happen if this object is reused for multiple queries,
142 // and a previous query was cancelled before it completed.
143 if (query
.get() != last_query_
.get())
147 // Enable unit tests to wait for this to happen; in production this function
149 ImplOnGetCandidateAdapterNamesDone();
151 // We may have been cancelled.
152 if (state_
!= STATE_WAIT_ADAPTERS
)
155 state_
= STATE_NO_RESULTS
;
157 const std::set
<std::string
>& adapter_names
= query
->adapter_names();
159 if (adapter_names
.empty()) {
164 for (std::set
<std::string
>::const_iterator it
= adapter_names
.begin();
165 it
!= adapter_names
.end();
167 DhcpProxyScriptAdapterFetcher
* fetcher(ImplCreateAdapterFetcher());
169 *it
, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone
,
170 base::Unretained(this)));
171 fetchers_
.push_back(fetcher
);
173 num_pending_fetchers_
= fetchers_
.size();
176 std::string
DhcpProxyScriptFetcherWin::GetFetcherName() const {
177 DCHECK(CalledOnValidThread());
181 const GURL
& DhcpProxyScriptFetcherWin::GetPacURL() const {
182 DCHECK(CalledOnValidThread());
183 DCHECK_EQ(state_
, STATE_DONE
);
188 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result
) {
189 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
191 if (--num_pending_fetchers_
== 0) {
196 // If the only pending adapters are those less preferred than one
197 // with a valid PAC script, we do not need to wait any longer.
198 for (FetcherVector::iterator it
= fetchers_
.begin();
199 it
!= fetchers_
.end();
201 bool did_finish
= (*it
)->DidFinish();
202 int result
= (*it
)->GetResult();
203 if (did_finish
&& result
== OK
) {
207 if (!did_finish
|| result
!= ERR_PAC_NOT_IN_DHCP
) {
212 // Once we have a single result, we set a maximum on how long to wait
213 // for the rest of the results.
214 if (state_
== STATE_NO_RESULTS
) {
215 state_
= STATE_SOME_RESULTS
;
216 wait_timer_
.Start(FROM_HERE
,
217 ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer
);
221 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
222 DCHECK_EQ(state_
, STATE_SOME_RESULTS
);
224 // These are intended to help us understand whether our timeout may
225 // be too aggressive or not aggressive enough.
226 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumAdaptersAtWaitTimer",
228 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
229 num_pending_fetchers_
);
234 void DhcpProxyScriptFetcherWin::TransitionToDone() {
235 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
237 int result
= ERR_PAC_NOT_IN_DHCP
; // Default if no fetchers.
238 if (!fetchers_
.empty()) {
239 // Scan twice for the result; once through the whole list for success,
240 // then if no success, return result for most preferred network adapter,
241 // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
242 // Default to ERR_ABORTED if no fetcher completed.
243 result
= ERR_ABORTED
;
244 for (FetcherVector::iterator it
= fetchers_
.begin();
245 it
!= fetchers_
.end();
247 if ((*it
)->DidFinish() && (*it
)->GetResult() == OK
) {
249 *destination_string_
= (*it
)->GetPacScript();
250 pac_url_
= (*it
)->GetPacURL();
255 destination_string_
->clear();
256 for (FetcherVector::iterator it
= fetchers_
.begin();
257 it
!= fetchers_
.end();
259 if ((*it
)->DidFinish()) {
260 result
= (*it
)->GetResult();
261 if (result
!= ERR_PAC_NOT_IN_DHCP
) {
269 CompletionCallback callback
= callback_
;
271 DCHECK_EQ(state_
, STATE_DONE
);
272 DCHECK(fetchers_
.empty());
273 DCHECK(callback_
.is_null()); // Invariant of data.
275 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
276 base::TimeTicks::Now() - fetch_start_time_
);
279 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
280 "Net.DhcpWpadFetchError", std::abs(result
), GetAllErrorCodesForUma());
283 // We may be deleted re-entrantly within this outcall.
284 callback
.Run(result
);
287 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
288 return num_pending_fetchers_
;
291 URLRequestContext
* DhcpProxyScriptFetcherWin::url_request_context() const {
292 return url_request_context_
;
295 scoped_refptr
<base::TaskRunner
> DhcpProxyScriptFetcherWin::GetTaskRunner() {
296 return worker_pool_
->GetTaskRunnerWithShutdownBehavior(
297 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN
);
300 DhcpProxyScriptAdapterFetcher
*
301 DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
302 return new DhcpProxyScriptAdapterFetcher(url_request_context_
,
306 DhcpProxyScriptFetcherWin::AdapterQuery
*
307 DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
308 return new AdapterQuery();
311 base::TimeDelta
DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
312 return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs
);
315 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
316 std::set
<std::string
>* adapter_names
) {
317 DCHECK(adapter_names
);
318 adapter_names
->clear();
320 // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
321 // avoid reallocation.
322 ULONG adapters_size
= 15000;
323 scoped_ptr
<IP_ADAPTER_ADDRESSES
, base::FreeDeleter
> adapters
;
324 ULONG error
= ERROR_SUCCESS
;
327 base::ElapsedTimer time_api_access
;
329 adapters
.reset(static_cast<IP_ADAPTER_ADDRESSES
*>(malloc(adapters_size
)));
330 // Return only unicast addresses, and skip information we do not need.
331 error
= GetAdaptersAddresses(AF_UNSPEC
,
332 GAA_FLAG_SKIP_ANYCAST
|
333 GAA_FLAG_SKIP_MULTICAST
|
334 GAA_FLAG_SKIP_DNS_SERVER
|
335 GAA_FLAG_SKIP_FRIENDLY_NAME
,
340 } while (error
== ERROR_BUFFER_OVERFLOW
&& num_tries
<= 3);
342 // This is primarily to validate our belief that the GetAdaptersAddresses API
343 // function is fast enough to call synchronously from the network thread.
344 UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
345 time_api_access
.Elapsed());
347 if (error
!= ERROR_SUCCESS
) {
348 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
349 "Net.DhcpWpadGetAdaptersAddressesError",
351 base::CustomHistogram::ArrayToCustomRanges(
352 kGetAdaptersAddressesErrors
,
353 arraysize(kGetAdaptersAddressesErrors
)));
356 if (error
== ERROR_NO_DATA
) {
357 // There are no adapters that we care about.
361 if (error
!= ERROR_SUCCESS
) {
362 LOG(WARNING
) << "Unexpected error retrieving WPAD configuration from DHCP.";
366 IP_ADAPTER_ADDRESSES
* adapter
= NULL
;
367 for (adapter
= adapters
.get(); adapter
; adapter
= adapter
->Next
) {
368 if (adapter
->IfType
== IF_TYPE_SOFTWARE_LOOPBACK
)
370 if ((adapter
->Flags
& IP_ADAPTER_DHCP_ENABLED
) == 0)
373 DCHECK(adapter
->AdapterName
);
374 adapter_names
->insert(adapter
->AdapterName
);
380 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
383 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
386 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
387 ImplGetCandidateAdapterNames(&adapter_names_
);
390 const std::set
<std::string
>&
391 DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
392 return adapter_names_
;
395 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
396 std::set
<std::string
>* adapter_names
) {
397 return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names
);