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/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"
     14 
     15 #include <winsock2.h>
     16 #include <iphlpapi.h>
     17 #pragma comment(lib, "iphlpapi.lib")
     18 
     19 namespace {
     20 
     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;
     25 
     26 const int kGetAdaptersAddressesErrors[] = {
     27   ERROR_ADDRESS_NOT_ASSOCIATED,
     28   ERROR_BUFFER_OVERFLOW,
     29   ERROR_INVALID_PARAMETER,
     30   ERROR_NOT_ENOUGH_MEMORY,
     31   ERROR_NO_DATA,
     32 };
     33 
     34 }  // namespace
     35 
     36 namespace net {
     37 
     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_);
     45 }
     46 
     47 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
     48   // Count as user-initiated if we are not yet in STATE_DONE.
     49   Cancel();
     50 }
     51 
     52 int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text,
     53                                      const CompletionCallback& callback) {
     54   DCHECK(CalledOnValidThread());
     55   if (state_ != STATE_START && state_ != STATE_DONE) {
     56     NOTREACHED();
     57     return ERR_UNEXPECTED;
     58   }
     59 
     60   fetch_start_time_ = base::TimeTicks::Now();
     61 
     62   state_ = STATE_WAIT_ADAPTERS;
     63   callback_ = callback;
     64   destination_string_ = utf16_text;
     65 
     66   last_query_ = ImplCreateAdapterQuery();
     67   base::WorkerPool::PostTaskAndReply(
     68       FROM_HERE,
     69       base::Bind(
     70           &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames,
     71           last_query_.get()),
     72       base::Bind(
     73           &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone,
     74           AsWeakPtr(),
     75           last_query_),
     76       true);
     77 
     78   return ERR_IO_PENDING;
     79 }
     80 
     81 void DhcpProxyScriptFetcherWin::Cancel() {
     82   DCHECK(CalledOnValidThread());
     83 
     84   if (state_ != STATE_DONE) {
     85     // We only count this stat if the cancel was explicitly initiated by
     86     // our client, and if we weren't already in STATE_DONE.
     87     UMA_HISTOGRAM_TIMES("Net.DhcpWpadCancelTime",
     88                         base::TimeTicks::Now() - fetch_start_time_);
     89   }
     90 
     91   CancelImpl();
     92 }
     93 
     94 void DhcpProxyScriptFetcherWin::CancelImpl() {
     95   DCHECK(CalledOnValidThread());
     96 
     97   if (state_ != STATE_DONE) {
     98     callback_.Reset();
     99     wait_timer_.Stop();
    100     state_ = STATE_DONE;
    101 
    102     for (FetcherVector::iterator it = fetchers_.begin();
    103          it != fetchers_.end();
    104          ++it) {
    105       (*it)->Cancel();
    106     }
    107 
    108     fetchers_.clear();
    109   }
    110 }
    111 
    112 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
    113     scoped_refptr<AdapterQuery> query) {
    114   DCHECK(CalledOnValidThread());
    115 
    116   // This can happen if this object is reused for multiple queries,
    117   // and a previous query was cancelled before it completed.
    118   if (query.get() != last_query_.get())
    119     return;
    120   last_query_ = NULL;
    121 
    122   // Enable unit tests to wait for this to happen; in production this function
    123   // call is a no-op.
    124   ImplOnGetCandidateAdapterNamesDone();
    125 
    126   // We may have been cancelled.
    127   if (state_ != STATE_WAIT_ADAPTERS)
    128     return;
    129 
    130   state_ = STATE_NO_RESULTS;
    131 
    132   const std::set<std::string>& adapter_names = query->adapter_names();
    133 
    134   if (adapter_names.empty()) {
    135     TransitionToDone();
    136     return;
    137   }
    138 
    139   for (std::set<std::string>::const_iterator it = adapter_names.begin();
    140        it != adapter_names.end();
    141        ++it) {
    142     DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher());
    143     fetcher->Fetch(
    144         *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone,
    145                         base::Unretained(this)));
    146     fetchers_.push_back(fetcher);
    147   }
    148   num_pending_fetchers_ = fetchers_.size();
    149 }
    150 
    151 std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
    152   DCHECK(CalledOnValidThread());
    153   return "win";
    154 }
    155 
    156 const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
    157   DCHECK(CalledOnValidThread());
    158   DCHECK_EQ(state_, STATE_DONE);
    159 
    160   return pac_url_;
    161 }
    162 
    163 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) {
    164   DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
    165 
    166   if (--num_pending_fetchers_ == 0) {
    167     TransitionToDone();
    168     return;
    169   }
    170 
    171   // If the only pending adapters are those less preferred than one
    172   // with a valid PAC script, we do not need to wait any longer.
    173   for (FetcherVector::iterator it = fetchers_.begin();
    174        it != fetchers_.end();
    175        ++it) {
    176     bool did_finish = (*it)->DidFinish();
    177     int result = (*it)->GetResult();
    178     if (did_finish && result == OK) {
    179       TransitionToDone();
    180       return;
    181     }
    182     if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) {
    183       break;
    184     }
    185   }
    186 
    187   // Once we have a single result, we set a maximum on how long to wait
    188   // for the rest of the results.
    189   if (state_ == STATE_NO_RESULTS) {
    190     state_ = STATE_SOME_RESULTS;
    191     wait_timer_.Start(FROM_HERE,
    192         ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer);
    193   }
    194 }
    195 
    196 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
    197   DCHECK_EQ(state_, STATE_SOME_RESULTS);
    198 
    199   // These are intended to help us understand whether our timeout may
    200   // be too aggressive or not aggressive enough.
    201   UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumAdaptersAtWaitTimer",
    202                            fetchers_.size());
    203   UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
    204                            num_pending_fetchers_);
    205 
    206   TransitionToDone();
    207 }
    208 
    209 void DhcpProxyScriptFetcherWin::TransitionToDone() {
    210   DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
    211 
    212   int result = ERR_PAC_NOT_IN_DHCP;  // Default if no fetchers.
    213   if (!fetchers_.empty()) {
    214     // Scan twice for the result; once through the whole list for success,
    215     // then if no success, return result for most preferred network adapter,
    216     // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
    217     // Default to ERR_ABORTED if no fetcher completed.
    218     result = ERR_ABORTED;
    219     for (FetcherVector::iterator it = fetchers_.begin();
    220          it != fetchers_.end();
    221          ++it) {
    222       if ((*it)->DidFinish() && (*it)->GetResult() == OK) {
    223         result = OK;
    224         *destination_string_ = (*it)->GetPacScript();
    225         pac_url_ = (*it)->GetPacURL();
    226         break;
    227       }
    228     }
    229     if (result != OK) {
    230       destination_string_->clear();
    231       for (FetcherVector::iterator it = fetchers_.begin();
    232            it != fetchers_.end();
    233            ++it) {
    234         if ((*it)->DidFinish()) {
    235           result = (*it)->GetResult();
    236           if (result != ERR_PAC_NOT_IN_DHCP) {
    237             break;
    238           }
    239         }
    240       }
    241     }
    242   }
    243 
    244   CompletionCallback callback = callback_;
    245   CancelImpl();
    246   DCHECK_EQ(state_, STATE_DONE);
    247   DCHECK(fetchers_.empty());
    248   DCHECK(callback_.is_null());  // Invariant of data.
    249 
    250   UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
    251                       base::TimeTicks::Now() - fetch_start_time_);
    252 
    253   if (result != OK) {
    254     UMA_HISTOGRAM_CUSTOM_ENUMERATION(
    255         "Net.DhcpWpadFetchError", std::abs(result), GetAllErrorCodesForUma());
    256   }
    257 
    258   // We may be deleted re-entrantly within this outcall.
    259   callback.Run(result);
    260 }
    261 
    262 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
    263   return num_pending_fetchers_;
    264 }
    265 
    266 URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
    267   return url_request_context_;
    268 }
    269 
    270 DhcpProxyScriptAdapterFetcher*
    271     DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
    272   return new DhcpProxyScriptAdapterFetcher(url_request_context_);
    273 }
    274 
    275 DhcpProxyScriptFetcherWin::AdapterQuery*
    276     DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
    277   return new AdapterQuery();
    278 }
    279 
    280 base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
    281   return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
    282 }
    283 
    284 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
    285     std::set<std::string>* adapter_names) {
    286   DCHECK(adapter_names);
    287   adapter_names->clear();
    288 
    289   // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
    290   // avoid reallocation.
    291   ULONG adapters_size = 15000;
    292   scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> adapters;
    293   ULONG error = ERROR_SUCCESS;
    294   int num_tries = 0;
    295 
    296   PerfTimer time_api_access;
    297   do {
    298     adapters.reset(
    299         reinterpret_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size)));
    300     // Return only unicast addresses, and skip information we do not need.
    301     error = GetAdaptersAddresses(AF_UNSPEC,
    302                                  GAA_FLAG_SKIP_ANYCAST |
    303                                  GAA_FLAG_SKIP_MULTICAST |
    304                                  GAA_FLAG_SKIP_DNS_SERVER |
    305                                  GAA_FLAG_SKIP_FRIENDLY_NAME,
    306                                  NULL,
    307                                  adapters.get(),
    308                                  &adapters_size);
    309     ++num_tries;
    310   } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
    311 
    312   // This is primarily to validate our belief that the GetAdaptersAddresses API
    313   // function is fast enough to call synchronously from the network thread.
    314   UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
    315                       time_api_access.Elapsed());
    316 
    317   if (error != ERROR_SUCCESS) {
    318     UMA_HISTOGRAM_CUSTOM_ENUMERATION(
    319         "Net.DhcpWpadGetAdaptersAddressesError",
    320         error,
    321         base::CustomHistogram::ArrayToCustomRanges(
    322             kGetAdaptersAddressesErrors,
    323             arraysize(kGetAdaptersAddressesErrors)));
    324   }
    325 
    326   if (error == ERROR_NO_DATA) {
    327     // There are no adapters that we care about.
    328     return true;
    329   }
    330 
    331   if (error != ERROR_SUCCESS) {
    332     LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
    333     return false;
    334   }
    335 
    336   IP_ADAPTER_ADDRESSES* adapter = NULL;
    337   for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
    338     if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
    339       continue;
    340     if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
    341       continue;
    342 
    343     DCHECK(adapter->AdapterName);
    344     adapter_names->insert(adapter->AdapterName);
    345   }
    346 
    347   return true;
    348 }
    349 
    350 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
    351 }
    352 
    353 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
    354 }
    355 
    356 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
    357   ImplGetCandidateAdapterNames(&adapter_names_);
    358 }
    359 
    360 const std::set<std::string>&
    361     DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
    362   return adapter_names_;
    363 }
    364 
    365 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
    366     std::set<std::string>* adapter_names) {
    367   return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
    368 }
    369 
    370 }  // namespace net
    371