- add sources.
[platform/framework/web/crosswalk.git] / src / net / proxy / dhcp_proxy_script_fetcher_win.cc
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.
4
5 #include "net/proxy/dhcp_proxy_script_fetcher_win.h"
6
7 #include "base/bind.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"
14
15 #include <winsock2.h>
16 #include <iphlpapi.h>
17 #pragma comment(lib, "iphlpapi.lib")
18
19 namespace {
20
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).
30 //
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.
35 //
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;
41
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;
46
47 const int kGetAdaptersAddressesErrors[] = {
48   ERROR_ADDRESS_NOT_ASSOCIATED,
49   ERROR_BUFFER_OVERFLOW,
50   ERROR_INVALID_PARAMETER,
51   ERROR_NOT_ENOUGH_MEMORY,
52   ERROR_NO_DATA,
53 };
54
55 }  // namespace
56
57 namespace net {
58
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_);
66
67   worker_pool_ = new base::SequencedWorkerPool(kMaxDhcpLookupThreads,
68                                                "PacDhcpLookup");
69 }
70
71 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
72   // Count as user-initiated if we are not yet in STATE_DONE.
73   Cancel();
74
75   worker_pool_->Shutdown();
76 }
77
78 int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text,
79                                      const CompletionCallback& callback) {
80   DCHECK(CalledOnValidThread());
81   if (state_ != STATE_START && state_ != STATE_DONE) {
82     NOTREACHED();
83     return ERR_UNEXPECTED;
84   }
85
86   fetch_start_time_ = base::TimeTicks::Now();
87
88   state_ = STATE_WAIT_ADAPTERS;
89   callback_ = callback;
90   destination_string_ = utf16_text;
91
92   last_query_ = ImplCreateAdapterQuery();
93   GetTaskRunner()->PostTaskAndReply(
94       FROM_HERE,
95       base::Bind(
96           &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames,
97           last_query_.get()),
98       base::Bind(
99           &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone,
100           AsWeakPtr(),
101           last_query_));
102
103   return ERR_IO_PENDING;
104 }
105
106 void DhcpProxyScriptFetcherWin::Cancel() {
107   DCHECK(CalledOnValidThread());
108
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_);
114   }
115
116   CancelImpl();
117 }
118
119 void DhcpProxyScriptFetcherWin::CancelImpl() {
120   DCHECK(CalledOnValidThread());
121
122   if (state_ != STATE_DONE) {
123     callback_.Reset();
124     wait_timer_.Stop();
125     state_ = STATE_DONE;
126
127     for (FetcherVector::iterator it = fetchers_.begin();
128          it != fetchers_.end();
129          ++it) {
130       (*it)->Cancel();
131     }
132
133     fetchers_.clear();
134   }
135 }
136
137 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
138     scoped_refptr<AdapterQuery> query) {
139   DCHECK(CalledOnValidThread());
140
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())
144     return;
145   last_query_ = NULL;
146
147   // Enable unit tests to wait for this to happen; in production this function
148   // call is a no-op.
149   ImplOnGetCandidateAdapterNamesDone();
150
151   // We may have been cancelled.
152   if (state_ != STATE_WAIT_ADAPTERS)
153     return;
154
155   state_ = STATE_NO_RESULTS;
156
157   const std::set<std::string>& adapter_names = query->adapter_names();
158
159   if (adapter_names.empty()) {
160     TransitionToDone();
161     return;
162   }
163
164   for (std::set<std::string>::const_iterator it = adapter_names.begin();
165        it != adapter_names.end();
166        ++it) {
167     DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher());
168     fetcher->Fetch(
169         *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone,
170                         base::Unretained(this)));
171     fetchers_.push_back(fetcher);
172   }
173   num_pending_fetchers_ = fetchers_.size();
174 }
175
176 std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
177   DCHECK(CalledOnValidThread());
178   return "win";
179 }
180
181 const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
182   DCHECK(CalledOnValidThread());
183   DCHECK_EQ(state_, STATE_DONE);
184
185   return pac_url_;
186 }
187
188 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) {
189   DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
190
191   if (--num_pending_fetchers_ == 0) {
192     TransitionToDone();
193     return;
194   }
195
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();
200        ++it) {
201     bool did_finish = (*it)->DidFinish();
202     int result = (*it)->GetResult();
203     if (did_finish && result == OK) {
204       TransitionToDone();
205       return;
206     }
207     if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) {
208       break;
209     }
210   }
211
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);
218   }
219 }
220
221 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
222   DCHECK_EQ(state_, STATE_SOME_RESULTS);
223
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",
227                            fetchers_.size());
228   UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
229                            num_pending_fetchers_);
230
231   TransitionToDone();
232 }
233
234 void DhcpProxyScriptFetcherWin::TransitionToDone() {
235   DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
236
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();
246          ++it) {
247       if ((*it)->DidFinish() && (*it)->GetResult() == OK) {
248         result = OK;
249         *destination_string_ = (*it)->GetPacScript();
250         pac_url_ = (*it)->GetPacURL();
251         break;
252       }
253     }
254     if (result != OK) {
255       destination_string_->clear();
256       for (FetcherVector::iterator it = fetchers_.begin();
257            it != fetchers_.end();
258            ++it) {
259         if ((*it)->DidFinish()) {
260           result = (*it)->GetResult();
261           if (result != ERR_PAC_NOT_IN_DHCP) {
262             break;
263           }
264         }
265       }
266     }
267   }
268
269   CompletionCallback callback = callback_;
270   CancelImpl();
271   DCHECK_EQ(state_, STATE_DONE);
272   DCHECK(fetchers_.empty());
273   DCHECK(callback_.is_null());  // Invariant of data.
274
275   UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
276                       base::TimeTicks::Now() - fetch_start_time_);
277
278   if (result != OK) {
279     UMA_HISTOGRAM_CUSTOM_ENUMERATION(
280         "Net.DhcpWpadFetchError", std::abs(result), GetAllErrorCodesForUma());
281   }
282
283   // We may be deleted re-entrantly within this outcall.
284   callback.Run(result);
285 }
286
287 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
288   return num_pending_fetchers_;
289 }
290
291 URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
292   return url_request_context_;
293 }
294
295 scoped_refptr<base::TaskRunner> DhcpProxyScriptFetcherWin::GetTaskRunner() {
296   return worker_pool_->GetTaskRunnerWithShutdownBehavior(
297       base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
298 }
299
300 DhcpProxyScriptAdapterFetcher*
301     DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
302   return new DhcpProxyScriptAdapterFetcher(url_request_context_,
303                                            GetTaskRunner());
304 }
305
306 DhcpProxyScriptFetcherWin::AdapterQuery*
307     DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
308   return new AdapterQuery();
309 }
310
311 base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
312   return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
313 }
314
315 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
316     std::set<std::string>* adapter_names) {
317   DCHECK(adapter_names);
318   adapter_names->clear();
319
320   // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
321   // avoid reallocation.
322   ULONG adapters_size = 15000;
323   scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> adapters;
324   ULONG error = ERROR_SUCCESS;
325   int num_tries = 0;
326
327   base::ElapsedTimer time_api_access;
328   do {
329     adapters.reset(
330         reinterpret_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,
337                                  NULL,
338                                  adapters.get(),
339                                  &adapters_size);
340     ++num_tries;
341   } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
342
343   // This is primarily to validate our belief that the GetAdaptersAddresses API
344   // function is fast enough to call synchronously from the network thread.
345   UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
346                       time_api_access.Elapsed());
347
348   if (error != ERROR_SUCCESS) {
349     UMA_HISTOGRAM_CUSTOM_ENUMERATION(
350         "Net.DhcpWpadGetAdaptersAddressesError",
351         error,
352         base::CustomHistogram::ArrayToCustomRanges(
353             kGetAdaptersAddressesErrors,
354             arraysize(kGetAdaptersAddressesErrors)));
355   }
356
357   if (error == ERROR_NO_DATA) {
358     // There are no adapters that we care about.
359     return true;
360   }
361
362   if (error != ERROR_SUCCESS) {
363     LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
364     return false;
365   }
366
367   IP_ADAPTER_ADDRESSES* adapter = NULL;
368   for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
369     if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
370       continue;
371     if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
372       continue;
373
374     DCHECK(adapter->AdapterName);
375     adapter_names->insert(adapter->AdapterName);
376   }
377
378   return true;
379 }
380
381 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
382 }
383
384 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
385 }
386
387 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
388   ImplGetCandidateAdapterNames(&adapter_names_);
389 }
390
391 const std::set<std::string>&
392     DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
393   return adapter_names_;
394 }
395
396 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
397     std::set<std::string>* adapter_names) {
398   return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
399 }
400
401 }  // namespace net