1 /* 2 * libjingle 3 * Copyright 2011, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <string> 29 30 #include "talk/p2p/client/connectivitychecker.h" 31 32 #include "talk/p2p/base/candidate.h" 33 #include "talk/p2p/base/common.h" 34 #include "talk/p2p/base/constants.h" 35 #include "talk/p2p/base/port.h" 36 #include "talk/p2p/base/relayport.h" 37 #include "talk/p2p/base/stunport.h" 38 #include "webrtc/base/asynchttprequest.h" 39 #include "webrtc/base/autodetectproxy.h" 40 #include "webrtc/base/helpers.h" 41 #include "webrtc/base/httpcommon-inl.h" 42 #include "webrtc/base/httpcommon.h" 43 #include "webrtc/base/logging.h" 44 #include "webrtc/base/proxydetect.h" 45 #include "webrtc/base/thread.h" 46 47 namespace cricket { 48 49 static const char kDefaultStunHostname[] = "stun.l.google.com"; 50 static const int kDefaultStunPort = 19302; 51 52 // Default maximum time in milliseconds we will wait for connections. 53 static const uint32 kDefaultTimeoutMs = 3000; 54 55 enum { 56 MSG_START = 1, 57 MSG_STOP = 2, 58 MSG_TIMEOUT = 3, 59 MSG_SIGNAL_RESULTS = 4 60 }; 61 62 class TestHttpPortAllocator : public HttpPortAllocator { 63 public: 64 TestHttpPortAllocator(rtc::NetworkManager* network_manager, 65 const std::string& user_agent, 66 const std::string& relay_token) : 67 HttpPortAllocator(network_manager, user_agent) { 68 SetRelayToken(relay_token); 69 } 70 PortAllocatorSession* CreateSessionInternal( 71 const std::string& content_name, 72 int component, 73 const std::string& ice_ufrag, 74 const std::string& ice_pwd) { 75 return new TestHttpPortAllocatorSession(this, content_name, component, 76 ice_ufrag, ice_pwd, 77 stun_hosts(), relay_hosts(), 78 relay_token(), user_agent()); 79 } 80 }; 81 82 void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) { 83 SignalConfigReady(username(), password(), config, proxy_); 84 delete config; 85 } 86 87 void TestHttpPortAllocatorSession::OnRequestDone( 88 rtc::SignalThread* data) { 89 rtc::AsyncHttpRequest* request = 90 static_cast<rtc::AsyncHttpRequest*>(data); 91 92 // Tell the checker that the request is complete. 93 SignalRequestDone(request); 94 95 // Pass on the response to super class. 96 HttpPortAllocatorSession::OnRequestDone(data); 97 } 98 99 ConnectivityChecker::ConnectivityChecker( 100 rtc::Thread* worker, 101 const std::string& jid, 102 const std::string& session_id, 103 const std::string& user_agent, 104 const std::string& relay_token, 105 const std::string& connection) 106 : worker_(worker), 107 jid_(jid), 108 session_id_(session_id), 109 user_agent_(user_agent), 110 relay_token_(relay_token), 111 connection_(connection), 112 proxy_detect_(NULL), 113 timeout_ms_(kDefaultTimeoutMs), 114 stun_address_(kDefaultStunHostname, kDefaultStunPort), 115 started_(false) { 116 } 117 118 ConnectivityChecker::~ConnectivityChecker() { 119 if (started_) { 120 // We try to clear the TIMEOUT below. But worker may still handle it and 121 // cause SignalCheckDone to happen on main-thread. So we finally clear any 122 // pending SIGNAL_RESULTS. 123 worker_->Clear(this, MSG_TIMEOUT); 124 worker_->Send(this, MSG_STOP); 125 nics_.clear(); 126 main_->Clear(this, MSG_SIGNAL_RESULTS); 127 } 128 } 129 130 bool ConnectivityChecker::Initialize() { 131 network_manager_.reset(CreateNetworkManager()); 132 socket_factory_.reset(CreateSocketFactory(worker_)); 133 port_allocator_.reset(CreatePortAllocator(network_manager_.get(), 134 user_agent_, relay_token_)); 135 uint32 new_allocator_flags = port_allocator_->flags(); 136 new_allocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG; 137 port_allocator_->set_flags(new_allocator_flags); 138 return true; 139 } 140 141 void ConnectivityChecker::Start() { 142 main_ = rtc::Thread::Current(); 143 worker_->Post(this, MSG_START); 144 started_ = true; 145 } 146 147 void ConnectivityChecker::CleanUp() { 148 ASSERT(worker_ == rtc::Thread::Current()); 149 if (proxy_detect_) { 150 proxy_detect_->Release(); 151 proxy_detect_ = NULL; 152 } 153 154 for (uint32 i = 0; i < sessions_.size(); ++i) { 155 delete sessions_[i]; 156 } 157 sessions_.clear(); 158 for (uint32 i = 0; i < ports_.size(); ++i) { 159 delete ports_[i]; 160 } 161 ports_.clear(); 162 } 163 164 bool ConnectivityChecker::AddNic(const rtc::IPAddress& ip, 165 const rtc::SocketAddress& proxy_addr) { 166 NicMap::iterator i = nics_.find(NicId(ip, proxy_addr)); 167 if (i != nics_.end()) { 168 // Already have it. 169 return false; 170 } 171 uint32 now = rtc::Time(); 172 NicInfo info; 173 info.ip = ip; 174 info.proxy_info = GetProxyInfo(); 175 info.stun.start_time_ms = now; 176 nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info)); 177 return true; 178 } 179 180 void ConnectivityChecker::SetProxyInfo(const rtc::ProxyInfo& proxy_info) { 181 port_allocator_->set_proxy(user_agent_, proxy_info); 182 AllocatePorts(); 183 } 184 185 rtc::ProxyInfo ConnectivityChecker::GetProxyInfo() const { 186 rtc::ProxyInfo proxy_info; 187 if (proxy_detect_) { 188 proxy_info = proxy_detect_->proxy(); 189 } 190 return proxy_info; 191 } 192 193 void ConnectivityChecker::CheckNetworks() { 194 network_manager_->SignalNetworksChanged.connect( 195 this, &ConnectivityChecker::OnNetworksChanged); 196 network_manager_->StartUpdating(); 197 } 198 199 void ConnectivityChecker::OnMessage(rtc::Message *msg) { 200 switch (msg->message_id) { 201 case MSG_START: 202 ASSERT(worker_ == rtc::Thread::Current()); 203 worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT); 204 CheckNetworks(); 205 break; 206 case MSG_STOP: 207 // We're being stopped, free resources. 208 CleanUp(); 209 break; 210 case MSG_TIMEOUT: 211 // We need to signal results on the main thread. 212 main_->Post(this, MSG_SIGNAL_RESULTS); 213 break; 214 case MSG_SIGNAL_RESULTS: 215 ASSERT(main_ == rtc::Thread::Current()); 216 SignalCheckDone(this); 217 break; 218 default: 219 LOG(LS_ERROR) << "Unknown message: " << msg->message_id; 220 } 221 } 222 223 void ConnectivityChecker::OnProxyDetect(rtc::SignalThread* thread) { 224 ASSERT(worker_ == rtc::Thread::Current()); 225 if (proxy_detect_->proxy().type != rtc::PROXY_NONE) { 226 SetProxyInfo(proxy_detect_->proxy()); 227 } 228 } 229 230 void ConnectivityChecker::OnRequestDone(rtc::AsyncHttpRequest* request) { 231 ASSERT(worker_ == rtc::Thread::Current()); 232 // Since we don't know what nic were actually used for the http request, 233 // for now, just use the first one. 234 std::vector<rtc::Network*> networks; 235 network_manager_->GetNetworks(&networks); 236 if (networks.empty()) { 237 LOG(LS_ERROR) << "No networks while registering http start."; 238 return; 239 } 240 rtc::ProxyInfo proxy_info = request->proxy(); 241 NicMap::iterator i = 242 #ifdef USE_WEBRTC_DEV_BRANCH 243 nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address)); 244 #else // USE_WEBRTC_DEV_BRANCH 245 nics_.find(NicId(networks[0]->ip(), proxy_info.address)); 246 #endif // USE_WEBRTC_DEV_BRANCH 247 if (i != nics_.end()) { 248 int port = request->port(); 249 uint32 now = rtc::Time(); 250 NicInfo* nic_info = &i->second; 251 if (port == rtc::HTTP_DEFAULT_PORT) { 252 nic_info->http.rtt = now - nic_info->http.start_time_ms; 253 } else if (port == rtc::HTTP_SECURE_PORT) { 254 nic_info->https.rtt = now - nic_info->https.start_time_ms; 255 } else { 256 LOG(LS_ERROR) << "Got response with unknown port: " << port; 257 } 258 } else { 259 LOG(LS_ERROR) << "No nic info found while receiving response."; 260 } 261 } 262 263 void ConnectivityChecker::OnConfigReady( 264 const std::string& username, const std::string& password, 265 const PortConfiguration* config, const rtc::ProxyInfo& proxy_info) { 266 ASSERT(worker_ == rtc::Thread::Current()); 267 268 // Since we send requests on both HTTP and HTTPS we will get two 269 // configs per nic. Results from the second will overwrite the 270 // result from the first. 271 // TODO: Handle multiple pings on one nic. 272 CreateRelayPorts(username, password, config, proxy_info); 273 } 274 275 void ConnectivityChecker::OnRelayPortComplete(Port* port) { 276 ASSERT(worker_ == rtc::Thread::Current()); 277 RelayPort* relay_port = reinterpret_cast<RelayPort*>(port); 278 const ProtocolAddress* address = relay_port->ServerAddress(0); 279 #ifdef USE_WEBRTC_DEV_BRANCH 280 rtc::IPAddress ip = port->Network()->GetBestIP(); 281 #else // USE_WEBRTC_DEV_BRANCH 282 rtc::IPAddress ip = port->Network()->ip(); 283 #endif // USE_WEBRTC_DEV_BRANCH 284 NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); 285 if (i != nics_.end()) { 286 // We have it already, add the new information. 287 NicInfo* nic_info = &i->second; 288 ConnectInfo* connect_info = NULL; 289 if (address) { 290 switch (address->proto) { 291 case PROTO_UDP: 292 connect_info = &nic_info->udp; 293 break; 294 case PROTO_TCP: 295 connect_info = &nic_info->tcp; 296 break; 297 case PROTO_SSLTCP: 298 connect_info = &nic_info->ssltcp; 299 break; 300 default: 301 LOG(LS_ERROR) << " relay address with bad protocol added"; 302 } 303 if (connect_info) { 304 connect_info->rtt = 305 rtc::TimeSince(connect_info->start_time_ms); 306 } 307 } 308 } else { 309 LOG(LS_ERROR) << " got relay address for non-existing nic"; 310 } 311 } 312 313 void ConnectivityChecker::OnStunPortComplete(Port* port) { 314 ASSERT(worker_ == rtc::Thread::Current()); 315 const std::vector<Candidate> candidates = port->Candidates(); 316 Candidate c = candidates[0]; 317 #ifdef USE_WEBRTC_DEV_BRANCH 318 rtc::IPAddress ip = port->Network()->GetBestIP(); 319 #else // USE_WEBRTC_DEV_BRANCH 320 rtc::IPAddress ip = port->Network()->ip(); 321 #endif // USE_WEBRTC_DEV_BRANCH 322 NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); 323 if (i != nics_.end()) { 324 // We have it already, add the new information. 325 uint32 now = rtc::Time(); 326 NicInfo* nic_info = &i->second; 327 nic_info->external_address = c.address(); 328 329 nic_info->stun_server_addresses = 330 static_cast<StunPort*>(port)->server_addresses(); 331 nic_info->stun.rtt = now - nic_info->stun.start_time_ms; 332 } else { 333 LOG(LS_ERROR) << "Got stun address for non-existing nic"; 334 } 335 } 336 337 void ConnectivityChecker::OnStunPortError(Port* port) { 338 ASSERT(worker_ == rtc::Thread::Current()); 339 LOG(LS_ERROR) << "Stun address error."; 340 #ifdef USE_WEBRTC_DEV_BRANCH 341 rtc::IPAddress ip = port->Network()->GetBestIP(); 342 #else // USE_WEBRTC_DEV_BRANCH 343 rtc::IPAddress ip = port->Network()->ip(); 344 #endif // USE_WEBRTC_DEV_BRANCH 345 NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); 346 if (i != nics_.end()) { 347 // We have it already, add the new information. 348 NicInfo* nic_info = &i->second; 349 350 nic_info->stun_server_addresses = 351 static_cast<StunPort*>(port)->server_addresses(); 352 } 353 } 354 355 void ConnectivityChecker::OnRelayPortError(Port* port) { 356 ASSERT(worker_ == rtc::Thread::Current()); 357 LOG(LS_ERROR) << "Relay address error."; 358 } 359 360 void ConnectivityChecker::OnNetworksChanged() { 361 ASSERT(worker_ == rtc::Thread::Current()); 362 std::vector<rtc::Network*> networks; 363 network_manager_->GetNetworks(&networks); 364 if (networks.empty()) { 365 LOG(LS_ERROR) << "Machine has no networks; nothing to do"; 366 return; 367 } 368 AllocatePorts(); 369 } 370 371 HttpPortAllocator* ConnectivityChecker::CreatePortAllocator( 372 rtc::NetworkManager* network_manager, 373 const std::string& user_agent, 374 const std::string& relay_token) { 375 return new TestHttpPortAllocator(network_manager, user_agent, relay_token); 376 } 377 378 StunPort* ConnectivityChecker::CreateStunPort( 379 const std::string& username, const std::string& password, 380 const PortConfiguration* config, rtc::Network* network) { 381 return StunPort::Create(worker_, 382 socket_factory_.get(), 383 network, 384 #ifdef USE_WEBRTC_DEV_BRANCH 385 network->GetBestIP(), 386 #else // USE_WEBRTC_DEV_BRANCH 387 network->ip(), 388 #endif // USE_WEBRTC_DEV_BRANCH 389 0, 390 0, 391 username, 392 password, 393 config->stun_servers); 394 } 395 396 RelayPort* ConnectivityChecker::CreateRelayPort( 397 const std::string& username, const std::string& password, 398 const PortConfiguration* config, rtc::Network* network) { 399 return RelayPort::Create(worker_, 400 socket_factory_.get(), 401 network, 402 #ifdef USE_WEBRTC_DEV_BRANCH 403 network->GetBestIP(), 404 #else // USE_WEBRTC_DEV_BRANCH 405 network->ip(), 406 #endif // USE_WEBRTC_DEV_BRANCH 407 port_allocator_->min_port(), 408 port_allocator_->max_port(), 409 username, 410 password); 411 } 412 413 void ConnectivityChecker::CreateRelayPorts( 414 const std::string& username, const std::string& password, 415 const PortConfiguration* config, const rtc::ProxyInfo& proxy_info) { 416 PortConfiguration::RelayList::const_iterator relay; 417 std::vector<rtc::Network*> networks; 418 network_manager_->GetNetworks(&networks); 419 if (networks.empty()) { 420 LOG(LS_ERROR) << "Machine has no networks; no relay ports created."; 421 return; 422 } 423 for (relay = config->relays.begin(); 424 relay != config->relays.end(); ++relay) { 425 for (uint32 i = 0; i < networks.size(); ++i) { 426 NicMap::iterator iter = 427 #ifdef USE_WEBRTC_DEV_BRANCH 428 nics_.find(NicId(networks[i]->GetBestIP(), proxy_info.address)); 429 #else // USE_WEBRTC_DEV_BRANCH 430 nics_.find(NicId(networks[i]->ip(), proxy_info.address)); 431 #endif // USE_WEBRTC_DEV_BRANCH 432 if (iter != nics_.end()) { 433 // TODO: Now setting the same start time for all protocols. 434 // This might affect accuracy, but since we are mainly looking for 435 // connect failures or number that stick out, this is good enough. 436 uint32 now = rtc::Time(); 437 NicInfo* nic_info = &iter->second; 438 nic_info->udp.start_time_ms = now; 439 nic_info->tcp.start_time_ms = now; 440 nic_info->ssltcp.start_time_ms = now; 441 442 // Add the addresses of this protocol. 443 PortList::const_iterator relay_port; 444 for (relay_port = relay->ports.begin(); 445 relay_port != relay->ports.end(); 446 ++relay_port) { 447 RelayPort* port = CreateRelayPort(username, password, 448 config, networks[i]); 449 port->AddServerAddress(*relay_port); 450 port->AddExternalAddress(*relay_port); 451 452 nic_info->media_server_address = port->ServerAddress(0)->address; 453 454 // Listen to network events. 455 port->SignalPortComplete.connect( 456 this, &ConnectivityChecker::OnRelayPortComplete); 457 port->SignalPortError.connect( 458 this, &ConnectivityChecker::OnRelayPortError); 459 460 port->set_proxy(user_agent_, proxy_info); 461 462 // Start fetching an address for this port. 463 port->PrepareAddress(); 464 ports_.push_back(port); 465 } 466 } else { 467 LOG(LS_ERROR) << "Failed to find nic info when creating relay ports."; 468 } 469 } 470 } 471 } 472 473 void ConnectivityChecker::AllocatePorts() { 474 const std::string username = rtc::CreateRandomString(ICE_UFRAG_LENGTH); 475 const std::string password = rtc::CreateRandomString(ICE_PWD_LENGTH); 476 ServerAddresses stun_servers; 477 stun_servers.insert(stun_address_); 478 PortConfiguration config(stun_servers, username, password); 479 std::vector<rtc::Network*> networks; 480 network_manager_->GetNetworks(&networks); 481 if (networks.empty()) { 482 LOG(LS_ERROR) << "Machine has no networks; no ports will be allocated"; 483 return; 484 } 485 rtc::ProxyInfo proxy_info = GetProxyInfo(); 486 bool allocate_relay_ports = false; 487 for (uint32 i = 0; i < networks.size(); ++i) { 488 #ifdef USE_WEBRTC_DEV_BRANCH 489 if (AddNic(networks[i]->GetBestIP(), proxy_info.address)) { 490 #else // USE_WEBRTC_DEV_BRANCH 491 if (AddNic(networks[i]->ip(), proxy_info.address)) { 492 #endif // USE_WEBRTC_DEV_BRANCH 493 Port* port = CreateStunPort(username, password, &config, networks[i]); 494 if (port) { 495 496 // Listen to network events. 497 port->SignalPortComplete.connect( 498 this, &ConnectivityChecker::OnStunPortComplete); 499 port->SignalPortError.connect( 500 this, &ConnectivityChecker::OnStunPortError); 501 502 port->set_proxy(user_agent_, proxy_info); 503 port->PrepareAddress(); 504 ports_.push_back(port); 505 allocate_relay_ports = true; 506 } 507 } 508 } 509 510 // If any new ip/proxy combinations were added, send a relay allocate. 511 if (allocate_relay_ports) { 512 AllocateRelayPorts(); 513 } 514 515 // Initiate proxy detection. 516 InitiateProxyDetection(); 517 } 518 519 void ConnectivityChecker::InitiateProxyDetection() { 520 // Only start if we haven't been started before. 521 if (!proxy_detect_) { 522 proxy_detect_ = new rtc::AutoDetectProxy(user_agent_); 523 rtc::Url<char> host_url("/", "relay.google.com", 524 rtc::HTTP_DEFAULT_PORT); 525 host_url.set_secure(true); 526 proxy_detect_->set_server_url(host_url.url()); 527 proxy_detect_->SignalWorkDone.connect( 528 this, &ConnectivityChecker::OnProxyDetect); 529 proxy_detect_->Start(); 530 } 531 } 532 533 void ConnectivityChecker::AllocateRelayPorts() { 534 // Currently we are using the 'default' nic for http(s) requests. 535 TestHttpPortAllocatorSession* allocator_session = 536 reinterpret_cast<TestHttpPortAllocatorSession*>( 537 port_allocator_->CreateSessionInternal( 538 "connectivity checker test content", 539 ICE_CANDIDATE_COMPONENT_RTP, 540 rtc::CreateRandomString(ICE_UFRAG_LENGTH), 541 rtc::CreateRandomString(ICE_PWD_LENGTH))); 542 allocator_session->set_proxy(port_allocator_->proxy()); 543 allocator_session->SignalConfigReady.connect( 544 this, &ConnectivityChecker::OnConfigReady); 545 allocator_session->SignalRequestDone.connect( 546 this, &ConnectivityChecker::OnRequestDone); 547 548 // Try both http and https. 549 RegisterHttpStart(rtc::HTTP_SECURE_PORT); 550 allocator_session->SendSessionRequest("relay.l.google.com", 551 rtc::HTTP_SECURE_PORT); 552 RegisterHttpStart(rtc::HTTP_DEFAULT_PORT); 553 allocator_session->SendSessionRequest("relay.l.google.com", 554 rtc::HTTP_DEFAULT_PORT); 555 556 sessions_.push_back(allocator_session); 557 } 558 559 void ConnectivityChecker::RegisterHttpStart(int port) { 560 // Since we don't know what nic were actually used for the http request, 561 // for now, just use the first one. 562 std::vector<rtc::Network*> networks; 563 network_manager_->GetNetworks(&networks); 564 if (networks.empty()) { 565 LOG(LS_ERROR) << "No networks while registering http start."; 566 return; 567 } 568 rtc::ProxyInfo proxy_info = GetProxyInfo(); 569 NicMap::iterator i = 570 #ifdef USE_WEBRTC_DEV_BRANCH 571 nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address)); 572 #else // USE_WEBRTC_DEV_BRANCH 573 nics_.find(NicId(networks[0]->ip(), proxy_info.address)); 574 #endif // USE_WEBRTC_DEV_BRANCH 575 if (i != nics_.end()) { 576 uint32 now = rtc::Time(); 577 NicInfo* nic_info = &i->second; 578 if (port == rtc::HTTP_DEFAULT_PORT) { 579 nic_info->http.start_time_ms = now; 580 } else if (port == rtc::HTTP_SECURE_PORT) { 581 nic_info->https.start_time_ms = now; 582 } else { 583 LOG(LS_ERROR) << "Registering start time for unknown port: " << port; 584 } 585 } else { 586 LOG(LS_ERROR) << "Error, no nic info found while registering http start."; 587 } 588 } 589 590 } // namespace rtc 591