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