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.
51 // The WeakPtr we passed to the worker thread may be destroyed on the
52 // worker thread. This detaches any outstanding WeakPtr state from
53 // the current thread.
54 base::SupportsWeakPtr
<DhcpProxyScriptFetcherWin
>::DetachFromThread();
57 int DhcpProxyScriptFetcherWin::Fetch(string16
* utf16_text
,
58 const CompletionCallback
& callback
) {
59 DCHECK(CalledOnValidThread());
60 if (state_
!= STATE_START
&& state_
!= STATE_DONE
) {
62 return ERR_UNEXPECTED
;
65 fetch_start_time_
= base::TimeTicks::Now();
67 state_
= STATE_WAIT_ADAPTERS
;
69 destination_string_
= utf16_text
;
71 last_query_
= ImplCreateAdapterQuery();
72 base::WorkerPool::PostTaskAndReply(
75 &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames
,
78 &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone
,
83 return ERR_IO_PENDING
;
86 void DhcpProxyScriptFetcherWin::Cancel() {
87 DCHECK(CalledOnValidThread());
89 if (state_
!= STATE_DONE
) {
90 // We only count this stat if the cancel was explicitly initiated by
91 // our client, and if we weren't already in STATE_DONE.
92 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCancelTime",
93 base::TimeTicks::Now() - fetch_start_time_
);
99 void DhcpProxyScriptFetcherWin::CancelImpl() {
100 DCHECK(CalledOnValidThread());
102 if (state_
!= STATE_DONE
) {
107 for (FetcherVector::iterator it
= fetchers_
.begin();
108 it
!= fetchers_
.end();
117 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
118 scoped_refptr
<AdapterQuery
> query
) {
119 DCHECK(CalledOnValidThread());
121 // This can happen if this object is reused for multiple queries,
122 // and a previous query was cancelled before it completed.
123 if (query
.get() != last_query_
.get())
127 // Enable unit tests to wait for this to happen; in production this function
129 ImplOnGetCandidateAdapterNamesDone();
131 // We may have been cancelled.
132 if (state_
!= STATE_WAIT_ADAPTERS
)
135 state_
= STATE_NO_RESULTS
;
137 const std::set
<std::string
>& adapter_names
= query
->adapter_names();
139 if (adapter_names
.empty()) {
144 for (std::set
<std::string
>::const_iterator it
= adapter_names
.begin();
145 it
!= adapter_names
.end();
147 DhcpProxyScriptAdapterFetcher
* fetcher(ImplCreateAdapterFetcher());
149 *it
, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone
,
150 base::Unretained(this)));
151 fetchers_
.push_back(fetcher
);
153 num_pending_fetchers_
= fetchers_
.size();
156 std::string
DhcpProxyScriptFetcherWin::GetFetcherName() const {
157 DCHECK(CalledOnValidThread());
161 const GURL
& DhcpProxyScriptFetcherWin::GetPacURL() const {
162 DCHECK(CalledOnValidThread());
163 DCHECK_EQ(state_
, STATE_DONE
);
168 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result
) {
169 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
171 if (--num_pending_fetchers_
== 0) {
176 // If the only pending adapters are those less preferred than one
177 // with a valid PAC script, we do not need to wait any longer.
178 for (FetcherVector::iterator it
= fetchers_
.begin();
179 it
!= fetchers_
.end();
181 bool did_finish
= (*it
)->DidFinish();
182 int result
= (*it
)->GetResult();
183 if (did_finish
&& result
== OK
) {
187 if (!did_finish
|| result
!= ERR_PAC_NOT_IN_DHCP
) {
192 // Once we have a single result, we set a maximum on how long to wait
193 // for the rest of the results.
194 if (state_
== STATE_NO_RESULTS
) {
195 state_
= STATE_SOME_RESULTS
;
196 wait_timer_
.Start(FROM_HERE
,
197 ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer
);
201 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
202 DCHECK_EQ(state_
, STATE_SOME_RESULTS
);
204 // These are intended to help us understand whether our timeout may
205 // be too aggressive or not aggressive enough.
206 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumAdaptersAtWaitTimer",
208 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
209 num_pending_fetchers_
);
214 void DhcpProxyScriptFetcherWin::TransitionToDone() {
215 DCHECK(state_
== STATE_NO_RESULTS
|| state_
== STATE_SOME_RESULTS
);
217 int result
= ERR_PAC_NOT_IN_DHCP
; // Default if no fetchers.
218 if (!fetchers_
.empty()) {
219 // Scan twice for the result; once through the whole list for success,
220 // then if no success, return result for most preferred network adapter,
221 // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
222 // Default to ERR_ABORTED if no fetcher completed.
223 result
= ERR_ABORTED
;
224 for (FetcherVector::iterator it
= fetchers_
.begin();
225 it
!= fetchers_
.end();
227 if ((*it
)->DidFinish() && (*it
)->GetResult() == OK
) {
229 *destination_string_
= (*it
)->GetPacScript();
230 pac_url_
= (*it
)->GetPacURL();
235 destination_string_
->clear();
236 for (FetcherVector::iterator it
= fetchers_
.begin();
237 it
!= fetchers_
.end();
239 if ((*it
)->DidFinish()) {
240 result
= (*it
)->GetResult();
241 if (result
!= ERR_PAC_NOT_IN_DHCP
) {
249 CompletionCallback callback
= callback_
;
251 DCHECK_EQ(state_
, STATE_DONE
);
252 DCHECK(fetchers_
.empty());
253 DCHECK(callback_
.is_null()); // Invariant of data.
255 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
256 base::TimeTicks::Now() - fetch_start_time_
);
259 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
260 "Net.DhcpWpadFetchError", std::abs(result
), GetAllErrorCodesForUma());
263 // We may be deleted re-entrantly within this outcall.
264 callback
.Run(result
);
267 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
268 return num_pending_fetchers_
;
271 URLRequestContext
* DhcpProxyScriptFetcherWin::url_request_context() const {
272 return url_request_context_
;
275 DhcpProxyScriptAdapterFetcher
*
276 DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
277 return new DhcpProxyScriptAdapterFetcher(url_request_context_
);
280 DhcpProxyScriptFetcherWin::AdapterQuery
*
281 DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
282 return new AdapterQuery();
285 base::TimeDelta
DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
286 return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs
);
289 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
290 std::set
<std::string
>* adapter_names
) {
291 DCHECK(adapter_names
);
292 adapter_names
->clear();
294 // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
295 // avoid reallocation.
296 ULONG adapters_size
= 15000;
297 scoped_ptr_malloc
<IP_ADAPTER_ADDRESSES
> adapters
;
298 ULONG error
= ERROR_SUCCESS
;
301 PerfTimer time_api_access
;
304 reinterpret_cast<IP_ADAPTER_ADDRESSES
*>(malloc(adapters_size
)));
305 // Return only unicast addresses, and skip information we do not need.
306 error
= GetAdaptersAddresses(AF_UNSPEC
,
307 GAA_FLAG_SKIP_ANYCAST
|
308 GAA_FLAG_SKIP_MULTICAST
|
309 GAA_FLAG_SKIP_DNS_SERVER
|
310 GAA_FLAG_SKIP_FRIENDLY_NAME
,
315 } while (error
== ERROR_BUFFER_OVERFLOW
&& num_tries
<= 3);
317 // This is primarily to validate our belief that the GetAdaptersAddresses API
318 // function is fast enough to call synchronously from the network thread.
319 UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
320 time_api_access
.Elapsed());
322 if (error
!= ERROR_SUCCESS
) {
323 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
324 "Net.DhcpWpadGetAdaptersAddressesError",
326 base::CustomHistogram::ArrayToCustomRanges(
327 kGetAdaptersAddressesErrors
,
328 arraysize(kGetAdaptersAddressesErrors
)));
331 if (error
== ERROR_NO_DATA
) {
332 // There are no adapters that we care about.
336 if (error
!= ERROR_SUCCESS
) {
337 LOG(WARNING
) << "Unexpected error retrieving WPAD configuration from DHCP.";
341 IP_ADAPTER_ADDRESSES
* adapter
= NULL
;
342 for (adapter
= adapters
.get(); adapter
; adapter
= adapter
->Next
) {
343 if (adapter
->IfType
== IF_TYPE_SOFTWARE_LOOPBACK
)
345 if ((adapter
->Flags
& IP_ADAPTER_DHCP_ENABLED
) == 0)
348 DCHECK(adapter
->AdapterName
);
349 adapter_names
->insert(adapter
->AdapterName
);
355 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
358 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
361 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
362 ImplGetCandidateAdapterNames(&adapter_names_
);
365 const std::set
<std::string
>&
366 DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
367 return adapter_names_
;
370 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
371 std::set
<std::string
>* adapter_names
) {
372 return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names
);