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/perftimer.h"
11 #include "base/threading/worker_pool.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 long to wait at maximum after we get results (a PAC file or
22 // knowledge that no PAC file is configured) from whichever network
23 // adapter finishes first.
24 const int kMaxWaitAfterFirstResultMs
= 400;
26 const int kGetAdaptersAddressesErrors
[] = {
27 ERROR_ADDRESS_NOT_ASSOCIATED
,
28 ERROR_BUFFER_OVERFLOW
,
29 ERROR_INVALID_PARAMETER
,
30 ERROR_NOT_ENOUGH_MEMORY
,
38 DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
39 URLRequestContext
* url_request_context
)
40 : state_(STATE_START
),
41 num_pending_fetchers_(0),
42 destination_string_(NULL
),
43 url_request_context_(url_request_context
) {
44 DCHECK(url_request_context_
);
47 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
48 // Count as user-initiated if we are not yet in STATE_DONE.
52 int DhcpProxyScriptFetcherWin::Fetch(base::string16
* utf16_text
,
53 const CompletionCallback
& callback
) {
54 DCHECK(CalledOnValidThread());
55 if (state_
!= STATE_START
&& state_
!= STATE_DONE
) {
57 return ERR_UNEXPECTED
;
60 fetch_start_time_
= base::TimeTicks::Now();
62 state_
= STATE_WAIT_ADAPTERS
;
64 destination_string_
= utf16_text
;
66 last_query_
= ImplCreateAdapterQuery();
67 base::WorkerPool::PostTaskAndReply(
70 &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames
,
73 &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone
,
78 return ERR_IO_PENDING
;
81 void DhcpProxyScriptFetcherWin::Cancel() {
82 DCHECK(CalledOnValidThread());
84 if (state_
!= STATE_DONE
) {
85 // We only count this stat if the cancel was explicitly initiated by
86 // our client, and if we weren't already in STATE_DONE.
87 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCancelTime",
88 base::TimeTicks::Now() - fetch_start_time_
);
94 void DhcpProxyScriptFetcherWin::CancelImpl() {
95 DCHECK(CalledOnValidThread());
97 if (state_
!= STATE_DONE
) {
102 for (FetcherVector::iterator it
= fetchers_
.begin();
103 it
!= fetchers_
.end();
112 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
113 scoped_refptr
<AdapterQuery
> query
) {
114 DCHECK(CalledOnValidThread());
116 // This can happen if this object is reused for multiple queries,
117 // and a previous query was cancelled before it completed.
118 if (query
.get() != last_query_
.get())
122 // Enable unit tests to wait for this to happen; in production this function
124 ImplOnGetCandidateAdapterNamesDone();
126 // We may have been cancelled.
127 if (state_
!= STATE_WAIT_ADAPTERS
)
130 state_
= STATE_NO_RESULTS
;
132 const std::set
<std::string
>& adapter_names
= query
->adapter_names();
134 if (adapter_names
.empty()) {
139 for (std::set
<std::string
>::const_iterator it
= adapter_names
.begin();
140 it
!= adapter_names
.end();
142 DhcpProxyScriptAdapterFetcher
* fetcher(ImplCreateAdapterFetcher());
144 *it
, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone
,
145 base::Unretained(this)));
146 fetchers_
.push_back(fetcher
);
148 num_pending_fetchers_
= fetchers_
.size();
151 std::string
DhcpProxyScriptFetcherWin::GetFetcherName() const {
152 DCHECK(CalledOnValidThread());
156 const GURL
& DhcpProxyScriptFetcherWin::GetPacURL() const {
157 DCHECK(CalledOnValidThread());
158 DCHECK_EQ(state_
, STATE_DONE
);
163 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result
) {
164 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
166 if (--num_pending_fetchers_
== 0) {
171 // If the only pending adapters are those less preferred than one
172 // with a valid PAC script, we do not need to wait any longer.
173 for (FetcherVector::iterator it
= fetchers_
.begin();
174 it
!= fetchers_
.end();
176 bool did_finish
= (*it
)->DidFinish();
177 int result
= (*it
)->GetResult();
178 if (did_finish
&& result
== OK
) {
182 if (!did_finish
|| result
!= ERR_PAC_NOT_IN_DHCP
) {
187 // Once we have a single result, we set a maximum on how long to wait
188 // for the rest of the results.
189 if (state_
== STATE_NO_RESULTS
) {
190 state_
= STATE_SOME_RESULTS
;
191 wait_timer_
.Start(FROM_HERE
,
192 ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer
);
196 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
197 DCHECK_EQ(state_
, STATE_SOME_RESULTS
);
199 // These are intended to help us understand whether our timeout may
200 // be too aggressive or not aggressive enough.
201 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumAdaptersAtWaitTimer",
203 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
204 num_pending_fetchers_
);
209 void DhcpProxyScriptFetcherWin::TransitionToDone() {
210 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
212 int result
= ERR_PAC_NOT_IN_DHCP
; // Default if no fetchers.
213 if (!fetchers_
.empty()) {
214 // Scan twice for the result; once through the whole list for success,
215 // then if no success, return result for most preferred network adapter,
216 // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
217 // Default to ERR_ABORTED if no fetcher completed.
218 result
= ERR_ABORTED
;
219 for (FetcherVector::iterator it
= fetchers_
.begin();
220 it
!= fetchers_
.end();
222 if ((*it
)->DidFinish() && (*it
)->GetResult() == OK
) {
224 *destination_string_
= (*it
)->GetPacScript();
225 pac_url_
= (*it
)->GetPacURL();
230 destination_string_
->clear();
231 for (FetcherVector::iterator it
= fetchers_
.begin();
232 it
!= fetchers_
.end();
234 if ((*it
)->DidFinish()) {
235 result
= (*it
)->GetResult();
236 if (result
!= ERR_PAC_NOT_IN_DHCP
) {
244 CompletionCallback callback
= callback_
;
246 DCHECK_EQ(state_
, STATE_DONE
);
247 DCHECK(fetchers_
.empty());
248 DCHECK(callback_
.is_null()); // Invariant of data.
250 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
251 base::TimeTicks::Now() - fetch_start_time_
);
254 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
255 "Net.DhcpWpadFetchError", std::abs(result
), GetAllErrorCodesForUma());
258 // We may be deleted re-entrantly within this outcall.
259 callback
.Run(result
);
262 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
263 return num_pending_fetchers_
;
266 URLRequestContext
* DhcpProxyScriptFetcherWin::url_request_context() const {
267 return url_request_context_
;
270 DhcpProxyScriptAdapterFetcher
*
271 DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
272 return new DhcpProxyScriptAdapterFetcher(url_request_context_
);
275 DhcpProxyScriptFetcherWin::AdapterQuery
*
276 DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
277 return new AdapterQuery();
280 base::TimeDelta
DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
281 return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs
);
284 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
285 std::set
<std::string
>* adapter_names
) {
286 DCHECK(adapter_names
);
287 adapter_names
->clear();
289 // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
290 // avoid reallocation.
291 ULONG adapters_size
= 15000;
292 scoped_ptr_malloc
<IP_ADAPTER_ADDRESSES
> adapters
;
293 ULONG error
= ERROR_SUCCESS
;
296 PerfTimer time_api_access
;
299 reinterpret_cast<IP_ADAPTER_ADDRESSES
*>(malloc(adapters_size
)));
300 // Return only unicast addresses, and skip information we do not need.
301 error
= GetAdaptersAddresses(AF_UNSPEC
,
302 GAA_FLAG_SKIP_ANYCAST
|
303 GAA_FLAG_SKIP_MULTICAST
|
304 GAA_FLAG_SKIP_DNS_SERVER
|
305 GAA_FLAG_SKIP_FRIENDLY_NAME
,
310 } while (error
== ERROR_BUFFER_OVERFLOW
&& num_tries
<= 3);
312 // This is primarily to validate our belief that the GetAdaptersAddresses API
313 // function is fast enough to call synchronously from the network thread.
314 UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
315 time_api_access
.Elapsed());
317 if (error
!= ERROR_SUCCESS
) {
318 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
319 "Net.DhcpWpadGetAdaptersAddressesError",
321 base::CustomHistogram::ArrayToCustomRanges(
322 kGetAdaptersAddressesErrors
,
323 arraysize(kGetAdaptersAddressesErrors
)));
326 if (error
== ERROR_NO_DATA
) {
327 // There are no adapters that we care about.
331 if (error
!= ERROR_SUCCESS
) {
332 LOG(WARNING
) << "Unexpected error retrieving WPAD configuration from DHCP.";
336 IP_ADAPTER_ADDRESSES
* adapter
= NULL
;
337 for (adapter
= adapters
.get(); adapter
; adapter
= adapter
->Next
) {
338 if (adapter
->IfType
== IF_TYPE_SOFTWARE_LOOPBACK
)
340 if ((adapter
->Flags
& IP_ADAPTER_DHCP_ENABLED
) == 0)
343 DCHECK(adapter
->AdapterName
);
344 adapter_names
->insert(adapter
->AdapterName
);
350 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
353 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
356 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
357 ImplGetCandidateAdapterNames(&adapter_names_
);
360 const std::set
<std::string
>&
361 DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
362 return adapter_names_
;
365 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
366 std::set
<std::string
>* adapter_names
) {
367 return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names
);