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/threading/sequenced_worker_pool.h"
10#include "net/base/net_errors.h"
11#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
12
13#include <winsock2.h>
14#include <iphlpapi.h>
15#pragma comment(lib, "iphlpapi.lib")
16
17namespace {
18
19// How many threads to use at maximum to do DHCP lookups. This is
20// chosen based on the following UMA data:
21// - When OnWaitTimer fires, ~99.8% of users have 6 or fewer network
22//   adapters enabled for DHCP in total.
23// - At the same measurement point, ~99.7% of users have 3 or fewer pending
24//   DHCP adapter lookups.
25// - There is however a very long and thin tail of users who have
26//   systems reporting up to 100+ adapters (this must be some very weird
27//   OS bug (?), probably the cause of http://crbug.com/240034).
28//
29// The maximum number of threads is chosen such that even systems that
30// report a huge number of network adapters should not run out of
31// memory from this number of threads, while giving a good chance of
32// getting back results for any responsive adapters.
33//
34// The ~99.8% of systems that have 6 or fewer network adapters will
35// not grow the thread pool to its maximum size (rather, they will
36// grow it to 6 or fewer threads) so setting the limit lower would not
37// improve performance or memory usage on those systems.
38const int kMaxDhcpLookupThreads = 12;
39
40// How long to wait at maximum after we get results (a PAC file or
41// knowledge that no PAC file is configured) from whichever network
42// adapter finishes first.
43const int kMaxWaitAfterFirstResultMs = 400;
44
45const int kGetAdaptersAddressesErrors[] = {
46  ERROR_ADDRESS_NOT_ASSOCIATED,
47  ERROR_BUFFER_OVERFLOW,
48  ERROR_INVALID_PARAMETER,
49  ERROR_NOT_ENOUGH_MEMORY,
50  ERROR_NO_DATA,
51};
52
53}  // namespace
54
55namespace net {
56
57DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
58    URLRequestContext* url_request_context)
59    : state_(STATE_START),
60      num_pending_fetchers_(0),
61      destination_string_(NULL),
62      url_request_context_(url_request_context) {
63  DCHECK(url_request_context_);
64
65  worker_pool_ = new base::SequencedWorkerPool(kMaxDhcpLookupThreads,
66                                               "PacDhcpLookup");
67}
68
69DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
70  // Count as user-initiated if we are not yet in STATE_DONE.
71  Cancel();
72
73  worker_pool_->Shutdown();
74}
75
76int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text,
77                                     const CompletionCallback& callback) {
78  DCHECK(CalledOnValidThread());
79  if (state_ != STATE_START && state_ != STATE_DONE) {
80    NOTREACHED();
81    return ERR_UNEXPECTED;
82  }
83
84  state_ = STATE_WAIT_ADAPTERS;
85  callback_ = callback;
86  destination_string_ = utf16_text;
87
88  last_query_ = ImplCreateAdapterQuery();
89  GetTaskRunner()->PostTaskAndReply(
90      FROM_HERE,
91      base::Bind(
92          &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames,
93          last_query_.get()),
94      base::Bind(
95          &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone,
96          AsWeakPtr(),
97          last_query_));
98
99  return ERR_IO_PENDING;
100}
101
102void DhcpProxyScriptFetcherWin::Cancel() {
103  DCHECK(CalledOnValidThread());
104
105  CancelImpl();
106}
107
108void DhcpProxyScriptFetcherWin::CancelImpl() {
109  DCHECK(CalledOnValidThread());
110
111  if (state_ != STATE_DONE) {
112    callback_.Reset();
113    wait_timer_.Stop();
114    state_ = STATE_DONE;
115
116    for (FetcherVector::iterator it = fetchers_.begin();
117         it != fetchers_.end();
118         ++it) {
119      (*it)->Cancel();
120    }
121
122    fetchers_.clear();
123  }
124}
125
126void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
127    scoped_refptr<AdapterQuery> query) {
128  DCHECK(CalledOnValidThread());
129
130  // This can happen if this object is reused for multiple queries,
131  // and a previous query was cancelled before it completed.
132  if (query.get() != last_query_.get())
133    return;
134  last_query_ = NULL;
135
136  // Enable unit tests to wait for this to happen; in production this function
137  // call is a no-op.
138  ImplOnGetCandidateAdapterNamesDone();
139
140  // We may have been cancelled.
141  if (state_ != STATE_WAIT_ADAPTERS)
142    return;
143
144  state_ = STATE_NO_RESULTS;
145
146  const std::set<std::string>& adapter_names = query->adapter_names();
147
148  if (adapter_names.empty()) {
149    TransitionToDone();
150    return;
151  }
152
153  for (std::set<std::string>::const_iterator it = adapter_names.begin();
154       it != adapter_names.end();
155       ++it) {
156    DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher());
157    fetcher->Fetch(
158        *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone,
159                        base::Unretained(this)));
160    fetchers_.push_back(fetcher);
161  }
162  num_pending_fetchers_ = fetchers_.size();
163}
164
165std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
166  DCHECK(CalledOnValidThread());
167  return "win";
168}
169
170const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
171  DCHECK(CalledOnValidThread());
172  DCHECK_EQ(state_, STATE_DONE);
173
174  return pac_url_;
175}
176
177void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) {
178  DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
179
180  if (--num_pending_fetchers_ == 0) {
181    TransitionToDone();
182    return;
183  }
184
185  // If the only pending adapters are those less preferred than one
186  // with a valid PAC script, we do not need to wait any longer.
187  for (FetcherVector::iterator it = fetchers_.begin();
188       it != fetchers_.end();
189       ++it) {
190    bool did_finish = (*it)->DidFinish();
191    int result = (*it)->GetResult();
192    if (did_finish && result == OK) {
193      TransitionToDone();
194      return;
195    }
196    if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) {
197      break;
198    }
199  }
200
201  // Once we have a single result, we set a maximum on how long to wait
202  // for the rest of the results.
203  if (state_ == STATE_NO_RESULTS) {
204    state_ = STATE_SOME_RESULTS;
205    wait_timer_.Start(FROM_HERE,
206        ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer);
207  }
208}
209
210void DhcpProxyScriptFetcherWin::OnWaitTimer() {
211  DCHECK_EQ(state_, STATE_SOME_RESULTS);
212
213  TransitionToDone();
214}
215
216void DhcpProxyScriptFetcherWin::TransitionToDone() {
217  DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
218
219  int result = ERR_PAC_NOT_IN_DHCP;  // Default if no fetchers.
220  if (!fetchers_.empty()) {
221    // Scan twice for the result; once through the whole list for success,
222    // then if no success, return result for most preferred network adapter,
223    // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
224    // Default to ERR_ABORTED if no fetcher completed.
225    result = ERR_ABORTED;
226    for (FetcherVector::iterator it = fetchers_.begin();
227         it != fetchers_.end();
228         ++it) {
229      if ((*it)->DidFinish() && (*it)->GetResult() == OK) {
230        result = OK;
231        *destination_string_ = (*it)->GetPacScript();
232        pac_url_ = (*it)->GetPacURL();
233        break;
234      }
235    }
236    if (result != OK) {
237      destination_string_->clear();
238      for (FetcherVector::iterator it = fetchers_.begin();
239           it != fetchers_.end();
240           ++it) {
241        if ((*it)->DidFinish()) {
242          result = (*it)->GetResult();
243          if (result != ERR_PAC_NOT_IN_DHCP) {
244            break;
245          }
246        }
247      }
248    }
249  }
250
251  CompletionCallback callback = callback_;
252  CancelImpl();
253  DCHECK_EQ(state_, STATE_DONE);
254  DCHECK(fetchers_.empty());
255  DCHECK(callback_.is_null());  // Invariant of data.
256
257  // We may be deleted re-entrantly within this outcall.
258  callback.Run(result);
259}
260
261int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
262  return num_pending_fetchers_;
263}
264
265URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
266  return url_request_context_;
267}
268
269scoped_refptr<base::TaskRunner> DhcpProxyScriptFetcherWin::GetTaskRunner() {
270  return worker_pool_->GetTaskRunnerWithShutdownBehavior(
271      base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
272}
273
274DhcpProxyScriptAdapterFetcher*
275    DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
276  return new DhcpProxyScriptAdapterFetcher(url_request_context_,
277                                           GetTaskRunner());
278}
279
280DhcpProxyScriptFetcherWin::AdapterQuery*
281    DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
282  return new AdapterQuery();
283}
284
285base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
286  return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
287}
288
289bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
290    std::set<std::string>* adapter_names) {
291  DCHECK(adapter_names);
292  adapter_names->clear();
293
294  // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
295  // avoid reallocation.
296  ULONG adapters_size = 15000;
297  scoped_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> adapters;
298  ULONG error = ERROR_SUCCESS;
299  int num_tries = 0;
300
301  do {
302    adapters.reset(static_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size)));
303    // Return only unicast addresses, and skip information we do not need.
304    error = GetAdaptersAddresses(AF_UNSPEC,
305                                 GAA_FLAG_SKIP_ANYCAST |
306                                 GAA_FLAG_SKIP_MULTICAST |
307                                 GAA_FLAG_SKIP_DNS_SERVER |
308                                 GAA_FLAG_SKIP_FRIENDLY_NAME,
309                                 NULL,
310                                 adapters.get(),
311                                 &adapters_size);
312    ++num_tries;
313  } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
314
315  if (error == ERROR_NO_DATA) {
316    // There are no adapters that we care about.
317    return true;
318  }
319
320  if (error != ERROR_SUCCESS) {
321    LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
322    return false;
323  }
324
325  IP_ADAPTER_ADDRESSES* adapter = NULL;
326  for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
327    if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
328      continue;
329    if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
330      continue;
331
332    DCHECK(adapter->AdapterName);
333    adapter_names->insert(adapter->AdapterName);
334  }
335
336  return true;
337}
338
339DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
340}
341
342DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
343}
344
345void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
346  ImplGetCandidateAdapterNames(&adapter_names_);
347}
348
349const std::set<std::string>&
350    DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
351  return adapter_names_;
352}
353
354bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
355    std::set<std::string>* adapter_names) {
356  return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
357}
358
359}  // namespace net
360