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