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