Home | History | Annotate | Download | only in proxy
      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<IP_ADAPTER_ADDRESSES, base::FreeDeleter> adapters;
    324   ULONG error = ERROR_SUCCESS;
    325   int num_tries = 0;
    326 
    327   base::ElapsedTimer time_api_access;
    328   do {
    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,
    336                                  NULL,
    337                                  adapters.get(),
    338                                  &adapters_size);
    339     ++num_tries;
    340   } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
    341 
    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());
    346 
    347   if (error != ERROR_SUCCESS) {
    348     UMA_HISTOGRAM_CUSTOM_ENUMERATION(
    349         "Net.DhcpWpadGetAdaptersAddressesError",
    350         error,
    351         base::CustomHistogram::ArrayToCustomRanges(
    352             kGetAdaptersAddressesErrors,
    353             arraysize(kGetAdaptersAddressesErrors)));
    354   }
    355 
    356   if (error == ERROR_NO_DATA) {
    357     // There are no adapters that we care about.
    358     return true;
    359   }
    360 
    361   if (error != ERROR_SUCCESS) {
    362     LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
    363     return false;
    364   }
    365 
    366   IP_ADAPTER_ADDRESSES* adapter = NULL;
    367   for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
    368     if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
    369       continue;
    370     if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
    371       continue;
    372 
    373     DCHECK(adapter->AdapterName);
    374     adapter_names->insert(adapter->AdapterName);
    375   }
    376 
    377   return true;
    378 }
    379 
    380 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
    381 }
    382 
    383 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
    384 }
    385 
    386 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
    387   ImplGetCandidateAdapterNames(&adapter_names_);
    388 }
    389 
    390 const std::set<std::string>&
    391     DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
    392   return adapter_names_;
    393 }
    394 
    395 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
    396     std::set<std::string>* adapter_names) {
    397   return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
    398 }
    399 
    400 }  // namespace net
    401