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/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 
     17 namespace {
     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.
     38 const 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.
     43 const int kMaxWaitAfterFirstResultMs = 400;
     44 
     45 const 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 
     55 namespace net {
     56 
     57 DhcpProxyScriptFetcherWin::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 
     69 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
     70   // Count as user-initiated if we are not yet in STATE_DONE.
     71   Cancel();
     72 
     73   worker_pool_->Shutdown();
     74 }
     75 
     76 int 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 
    102 void DhcpProxyScriptFetcherWin::Cancel() {
    103   DCHECK(CalledOnValidThread());
    104 
    105   CancelImpl();
    106 }
    107 
    108 void 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 
    126 void 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 
    165 std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
    166   DCHECK(CalledOnValidThread());
    167   return "win";
    168 }
    169 
    170 const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
    171   DCHECK(CalledOnValidThread());
    172   DCHECK_EQ(state_, STATE_DONE);
    173 
    174   return pac_url_;
    175 }
    176 
    177 void 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 
    210 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
    211   DCHECK_EQ(state_, STATE_SOME_RESULTS);
    212 
    213   TransitionToDone();
    214 }
    215 
    216 void 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 
    261 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
    262   return num_pending_fetchers_;
    263 }
    264 
    265 URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
    266   return url_request_context_;
    267 }
    268 
    269 scoped_refptr<base::TaskRunner> DhcpProxyScriptFetcherWin::GetTaskRunner() {
    270   return worker_pool_->GetTaskRunnerWithShutdownBehavior(
    271       base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
    272 }
    273 
    274 DhcpProxyScriptAdapterFetcher*
    275     DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
    276   return new DhcpProxyScriptAdapterFetcher(url_request_context_,
    277                                            GetTaskRunner());
    278 }
    279 
    280 DhcpProxyScriptFetcherWin::AdapterQuery*
    281     DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
    282   return new AdapterQuery();
    283 }
    284 
    285 base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
    286   return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
    287 }
    288 
    289 bool 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 
    339 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
    340 }
    341 
    342 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
    343 }
    344 
    345 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
    346   ImplGetCandidateAdapterNames(&adapter_names_);
    347 }
    348 
    349 const std::set<std::string>&
    350     DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
    351   return adapter_names_;
    352 }
    353 
    354 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
    355     std::set<std::string>* adapter_names) {
    356   return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
    357 }
    358 
    359 }  // namespace net
    360