2 * Copyright 2011 The WebRTC Project Authors. All rights reserved.
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
13 #include "webrtc/p2p/client/connectivitychecker.h"
15 #include "webrtc/p2p/base/candidate.h"
16 #include "webrtc/p2p/base/common.h"
17 #include "webrtc/p2p/base/constants.h"
18 #include "webrtc/p2p/base/port.h"
19 #include "webrtc/p2p/base/relayport.h"
20 #include "webrtc/p2p/base/stunport.h"
21 #include "webrtc/base/asynchttprequest.h"
22 #include "webrtc/base/autodetectproxy.h"
23 #include "webrtc/base/helpers.h"
24 #include "webrtc/base/httpcommon-inl.h"
25 #include "webrtc/base/httpcommon.h"
26 #include "webrtc/base/logging.h"
27 #include "webrtc/base/proxydetect.h"
28 #include "webrtc/base/thread.h"
32 static const char kDefaultStunHostname[] = "stun.l.google.com";
33 static const int kDefaultStunPort = 19302;
35 // Default maximum time in milliseconds we will wait for connections.
36 static const uint32 kDefaultTimeoutMs = 3000;
42 MSG_SIGNAL_RESULTS = 4
45 class TestHttpPortAllocator : public HttpPortAllocator {
47 TestHttpPortAllocator(rtc::NetworkManager* network_manager,
48 const std::string& user_agent,
49 const std::string& relay_token) :
50 HttpPortAllocator(network_manager, user_agent) {
51 SetRelayToken(relay_token);
53 PortAllocatorSession* CreateSessionInternal(
54 const std::string& content_name,
56 const std::string& ice_ufrag,
57 const std::string& ice_pwd) {
58 return new TestHttpPortAllocatorSession(this, content_name, component,
60 stun_hosts(), relay_hosts(),
61 relay_token(), user_agent());
65 void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
66 SignalConfigReady(username(), password(), config, proxy_);
70 void TestHttpPortAllocatorSession::OnRequestDone(
71 rtc::SignalThread* data) {
72 rtc::AsyncHttpRequest* request =
73 static_cast<rtc::AsyncHttpRequest*>(data);
75 // Tell the checker that the request is complete.
76 SignalRequestDone(request);
78 // Pass on the response to super class.
79 HttpPortAllocatorSession::OnRequestDone(data);
82 ConnectivityChecker::ConnectivityChecker(
84 const std::string& jid,
85 const std::string& session_id,
86 const std::string& user_agent,
87 const std::string& relay_token,
88 const std::string& connection)
91 session_id_(session_id),
92 user_agent_(user_agent),
93 relay_token_(relay_token),
94 connection_(connection),
96 timeout_ms_(kDefaultTimeoutMs),
97 stun_address_(kDefaultStunHostname, kDefaultStunPort),
101 ConnectivityChecker::~ConnectivityChecker() {
103 // We try to clear the TIMEOUT below. But worker may still handle it and
104 // cause SignalCheckDone to happen on main-thread. So we finally clear any
105 // pending SIGNAL_RESULTS.
106 worker_->Clear(this, MSG_TIMEOUT);
107 worker_->Send(this, MSG_STOP);
109 main_->Clear(this, MSG_SIGNAL_RESULTS);
113 bool ConnectivityChecker::Initialize() {
114 network_manager_.reset(CreateNetworkManager());
115 socket_factory_.reset(CreateSocketFactory(worker_));
116 port_allocator_.reset(CreatePortAllocator(network_manager_.get(),
117 user_agent_, relay_token_));
118 uint32 new_allocator_flags = port_allocator_->flags();
119 new_allocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
120 port_allocator_->set_flags(new_allocator_flags);
124 void ConnectivityChecker::Start() {
125 main_ = rtc::Thread::Current();
126 worker_->Post(this, MSG_START);
130 void ConnectivityChecker::CleanUp() {
131 ASSERT(worker_ == rtc::Thread::Current());
133 proxy_detect_->Release();
134 proxy_detect_ = NULL;
137 for (uint32 i = 0; i < sessions_.size(); ++i) {
141 for (uint32 i = 0; i < ports_.size(); ++i) {
147 bool ConnectivityChecker::AddNic(const rtc::IPAddress& ip,
148 const rtc::SocketAddress& proxy_addr) {
149 NicMap::iterator i = nics_.find(NicId(ip, proxy_addr));
150 if (i != nics_.end()) {
154 uint32 now = rtc::Time();
157 info.proxy_info = GetProxyInfo();
158 info.stun.start_time_ms = now;
159 nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info));
163 void ConnectivityChecker::SetProxyInfo(const rtc::ProxyInfo& proxy_info) {
164 port_allocator_->set_proxy(user_agent_, proxy_info);
168 rtc::ProxyInfo ConnectivityChecker::GetProxyInfo() const {
169 rtc::ProxyInfo proxy_info;
171 proxy_info = proxy_detect_->proxy();
176 void ConnectivityChecker::CheckNetworks() {
177 network_manager_->SignalNetworksChanged.connect(
178 this, &ConnectivityChecker::OnNetworksChanged);
179 network_manager_->StartUpdating();
182 void ConnectivityChecker::OnMessage(rtc::Message *msg) {
183 switch (msg->message_id) {
185 ASSERT(worker_ == rtc::Thread::Current());
186 worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT);
190 // We're being stopped, free resources.
194 // We need to signal results on the main thread.
195 main_->Post(this, MSG_SIGNAL_RESULTS);
197 case MSG_SIGNAL_RESULTS:
198 ASSERT(main_ == rtc::Thread::Current());
199 SignalCheckDone(this);
202 LOG(LS_ERROR) << "Unknown message: " << msg->message_id;
206 void ConnectivityChecker::OnProxyDetect(rtc::SignalThread* thread) {
207 ASSERT(worker_ == rtc::Thread::Current());
208 if (proxy_detect_->proxy().type != rtc::PROXY_NONE) {
209 SetProxyInfo(proxy_detect_->proxy());
213 void ConnectivityChecker::OnRequestDone(rtc::AsyncHttpRequest* request) {
214 ASSERT(worker_ == rtc::Thread::Current());
215 // Since we don't know what nic were actually used for the http request,
216 // for now, just use the first one.
217 std::vector<rtc::Network*> networks;
218 network_manager_->GetNetworks(&networks);
219 if (networks.empty()) {
220 LOG(LS_ERROR) << "No networks while registering http start.";
223 rtc::ProxyInfo proxy_info = request->proxy();
225 #ifdef USE_WEBRTC_DEV_BRANCH
226 nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address));
227 #else // USE_WEBRTC_DEV_BRANCH
228 nics_.find(NicId(networks[0]->ip(), proxy_info.address));
229 #endif // USE_WEBRTC_DEV_BRANCH
230 if (i != nics_.end()) {
231 int port = request->port();
232 uint32 now = rtc::Time();
233 NicInfo* nic_info = &i->second;
234 if (port == rtc::HTTP_DEFAULT_PORT) {
235 nic_info->http.rtt = now - nic_info->http.start_time_ms;
236 } else if (port == rtc::HTTP_SECURE_PORT) {
237 nic_info->https.rtt = now - nic_info->https.start_time_ms;
239 LOG(LS_ERROR) << "Got response with unknown port: " << port;
242 LOG(LS_ERROR) << "No nic info found while receiving response.";
246 void ConnectivityChecker::OnConfigReady(
247 const std::string& username, const std::string& password,
248 const PortConfiguration* config, const rtc::ProxyInfo& proxy_info) {
249 ASSERT(worker_ == rtc::Thread::Current());
251 // Since we send requests on both HTTP and HTTPS we will get two
252 // configs per nic. Results from the second will overwrite the
253 // result from the first.
254 // TODO: Handle multiple pings on one nic.
255 CreateRelayPorts(username, password, config, proxy_info);
258 void ConnectivityChecker::OnRelayPortComplete(Port* port) {
259 ASSERT(worker_ == rtc::Thread::Current());
260 RelayPort* relay_port = reinterpret_cast<RelayPort*>(port);
261 const ProtocolAddress* address = relay_port->ServerAddress(0);
262 #ifdef USE_WEBRTC_DEV_BRANCH
263 rtc::IPAddress ip = port->Network()->GetBestIP();
264 #else // USE_WEBRTC_DEV_BRANCH
265 rtc::IPAddress ip = port->Network()->ip();
266 #endif // USE_WEBRTC_DEV_BRANCH
267 NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
268 if (i != nics_.end()) {
269 // We have it already, add the new information.
270 NicInfo* nic_info = &i->second;
271 ConnectInfo* connect_info = NULL;
273 switch (address->proto) {
275 connect_info = &nic_info->udp;
278 connect_info = &nic_info->tcp;
281 connect_info = &nic_info->ssltcp;
284 LOG(LS_ERROR) << " relay address with bad protocol added";
288 rtc::TimeSince(connect_info->start_time_ms);
292 LOG(LS_ERROR) << " got relay address for non-existing nic";
296 void ConnectivityChecker::OnStunPortComplete(Port* port) {
297 ASSERT(worker_ == rtc::Thread::Current());
298 const std::vector<Candidate> candidates = port->Candidates();
299 Candidate c = candidates[0];
300 #ifdef USE_WEBRTC_DEV_BRANCH
301 rtc::IPAddress ip = port->Network()->GetBestIP();
302 #else // USE_WEBRTC_DEV_BRANCH
303 rtc::IPAddress ip = port->Network()->ip();
304 #endif // USE_WEBRTC_DEV_BRANCH
305 NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
306 if (i != nics_.end()) {
307 // We have it already, add the new information.
308 uint32 now = rtc::Time();
309 NicInfo* nic_info = &i->second;
310 nic_info->external_address = c.address();
312 nic_info->stun_server_addresses =
313 static_cast<StunPort*>(port)->server_addresses();
314 nic_info->stun.rtt = now - nic_info->stun.start_time_ms;
316 LOG(LS_ERROR) << "Got stun address for non-existing nic";
320 void ConnectivityChecker::OnStunPortError(Port* port) {
321 ASSERT(worker_ == rtc::Thread::Current());
322 LOG(LS_ERROR) << "Stun address error.";
323 #ifdef USE_WEBRTC_DEV_BRANCH
324 rtc::IPAddress ip = port->Network()->GetBestIP();
325 #else // USE_WEBRTC_DEV_BRANCH
326 rtc::IPAddress ip = port->Network()->ip();
327 #endif // USE_WEBRTC_DEV_BRANCH
328 NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
329 if (i != nics_.end()) {
330 // We have it already, add the new information.
331 NicInfo* nic_info = &i->second;
333 nic_info->stun_server_addresses =
334 static_cast<StunPort*>(port)->server_addresses();
338 void ConnectivityChecker::OnRelayPortError(Port* port) {
339 ASSERT(worker_ == rtc::Thread::Current());
340 LOG(LS_ERROR) << "Relay address error.";
343 void ConnectivityChecker::OnNetworksChanged() {
344 ASSERT(worker_ == rtc::Thread::Current());
345 std::vector<rtc::Network*> networks;
346 network_manager_->GetNetworks(&networks);
347 if (networks.empty()) {
348 LOG(LS_ERROR) << "Machine has no networks; nothing to do";
354 HttpPortAllocator* ConnectivityChecker::CreatePortAllocator(
355 rtc::NetworkManager* network_manager,
356 const std::string& user_agent,
357 const std::string& relay_token) {
358 return new TestHttpPortAllocator(network_manager, user_agent, relay_token);
361 StunPort* ConnectivityChecker::CreateStunPort(
362 const std::string& username, const std::string& password,
363 const PortConfiguration* config, rtc::Network* network) {
364 return StunPort::Create(worker_,
365 socket_factory_.get(),
367 #ifdef USE_WEBRTC_DEV_BRANCH
368 network->GetBestIP(),
369 #else // USE_WEBRTC_DEV_BRANCH
371 #endif // USE_WEBRTC_DEV_BRANCH
376 config->stun_servers);
379 RelayPort* ConnectivityChecker::CreateRelayPort(
380 const std::string& username, const std::string& password,
381 const PortConfiguration* config, rtc::Network* network) {
382 return RelayPort::Create(worker_,
383 socket_factory_.get(),
385 #ifdef USE_WEBRTC_DEV_BRANCH
386 network->GetBestIP(),
387 #else // USE_WEBRTC_DEV_BRANCH
389 #endif // USE_WEBRTC_DEV_BRANCH
390 port_allocator_->min_port(),
391 port_allocator_->max_port(),
396 void ConnectivityChecker::CreateRelayPorts(
397 const std::string& username, const std::string& password,
398 const PortConfiguration* config, const rtc::ProxyInfo& proxy_info) {
399 PortConfiguration::RelayList::const_iterator relay;
400 std::vector<rtc::Network*> networks;
401 network_manager_->GetNetworks(&networks);
402 if (networks.empty()) {
403 LOG(LS_ERROR) << "Machine has no networks; no relay ports created.";
406 for (relay = config->relays.begin();
407 relay != config->relays.end(); ++relay) {
408 for (uint32 i = 0; i < networks.size(); ++i) {
409 NicMap::iterator iter =
410 #ifdef USE_WEBRTC_DEV_BRANCH
411 nics_.find(NicId(networks[i]->GetBestIP(), proxy_info.address));
412 #else // USE_WEBRTC_DEV_BRANCH
413 nics_.find(NicId(networks[i]->ip(), proxy_info.address));
414 #endif // USE_WEBRTC_DEV_BRANCH
415 if (iter != nics_.end()) {
416 // TODO: Now setting the same start time for all protocols.
417 // This might affect accuracy, but since we are mainly looking for
418 // connect failures or number that stick out, this is good enough.
419 uint32 now = rtc::Time();
420 NicInfo* nic_info = &iter->second;
421 nic_info->udp.start_time_ms = now;
422 nic_info->tcp.start_time_ms = now;
423 nic_info->ssltcp.start_time_ms = now;
425 // Add the addresses of this protocol.
426 PortList::const_iterator relay_port;
427 for (relay_port = relay->ports.begin();
428 relay_port != relay->ports.end();
430 RelayPort* port = CreateRelayPort(username, password,
431 config, networks[i]);
432 port->AddServerAddress(*relay_port);
433 port->AddExternalAddress(*relay_port);
435 nic_info->media_server_address = port->ServerAddress(0)->address;
437 // Listen to network events.
438 port->SignalPortComplete.connect(
439 this, &ConnectivityChecker::OnRelayPortComplete);
440 port->SignalPortError.connect(
441 this, &ConnectivityChecker::OnRelayPortError);
443 port->set_proxy(user_agent_, proxy_info);
445 // Start fetching an address for this port.
446 port->PrepareAddress();
447 ports_.push_back(port);
450 LOG(LS_ERROR) << "Failed to find nic info when creating relay ports.";
456 void ConnectivityChecker::AllocatePorts() {
457 const std::string username = rtc::CreateRandomString(ICE_UFRAG_LENGTH);
458 const std::string password = rtc::CreateRandomString(ICE_PWD_LENGTH);
459 ServerAddresses stun_servers;
460 stun_servers.insert(stun_address_);
461 PortConfiguration config(stun_servers, username, password);
462 std::vector<rtc::Network*> networks;
463 network_manager_->GetNetworks(&networks);
464 if (networks.empty()) {
465 LOG(LS_ERROR) << "Machine has no networks; no ports will be allocated";
468 rtc::ProxyInfo proxy_info = GetProxyInfo();
469 bool allocate_relay_ports = false;
470 for (uint32 i = 0; i < networks.size(); ++i) {
471 #ifdef USE_WEBRTC_DEV_BRANCH
472 if (AddNic(networks[i]->GetBestIP(), proxy_info.address)) {
473 #else // USE_WEBRTC_DEV_BRANCH
474 if (AddNic(networks[i]->ip(), proxy_info.address)) {
475 #endif // USE_WEBRTC_DEV_BRANCH
476 Port* port = CreateStunPort(username, password, &config, networks[i]);
479 // Listen to network events.
480 port->SignalPortComplete.connect(
481 this, &ConnectivityChecker::OnStunPortComplete);
482 port->SignalPortError.connect(
483 this, &ConnectivityChecker::OnStunPortError);
485 port->set_proxy(user_agent_, proxy_info);
486 port->PrepareAddress();
487 ports_.push_back(port);
488 allocate_relay_ports = true;
493 // If any new ip/proxy combinations were added, send a relay allocate.
494 if (allocate_relay_ports) {
495 AllocateRelayPorts();
498 // Initiate proxy detection.
499 InitiateProxyDetection();
502 void ConnectivityChecker::InitiateProxyDetection() {
503 // Only start if we haven't been started before.
504 if (!proxy_detect_) {
505 proxy_detect_ = new rtc::AutoDetectProxy(user_agent_);
506 rtc::Url<char> host_url("/", "relay.google.com",
507 rtc::HTTP_DEFAULT_PORT);
508 host_url.set_secure(true);
509 proxy_detect_->set_server_url(host_url.url());
510 proxy_detect_->SignalWorkDone.connect(
511 this, &ConnectivityChecker::OnProxyDetect);
512 proxy_detect_->Start();
516 void ConnectivityChecker::AllocateRelayPorts() {
517 // Currently we are using the 'default' nic for http(s) requests.
518 TestHttpPortAllocatorSession* allocator_session =
519 reinterpret_cast<TestHttpPortAllocatorSession*>(
520 port_allocator_->CreateSessionInternal(
521 "connectivity checker test content",
522 ICE_CANDIDATE_COMPONENT_RTP,
523 rtc::CreateRandomString(ICE_UFRAG_LENGTH),
524 rtc::CreateRandomString(ICE_PWD_LENGTH)));
525 allocator_session->set_proxy(port_allocator_->proxy());
526 allocator_session->SignalConfigReady.connect(
527 this, &ConnectivityChecker::OnConfigReady);
528 allocator_session->SignalRequestDone.connect(
529 this, &ConnectivityChecker::OnRequestDone);
531 // Try both http and https.
532 RegisterHttpStart(rtc::HTTP_SECURE_PORT);
533 allocator_session->SendSessionRequest("relay.l.google.com",
534 rtc::HTTP_SECURE_PORT);
535 RegisterHttpStart(rtc::HTTP_DEFAULT_PORT);
536 allocator_session->SendSessionRequest("relay.l.google.com",
537 rtc::HTTP_DEFAULT_PORT);
539 sessions_.push_back(allocator_session);
542 void ConnectivityChecker::RegisterHttpStart(int port) {
543 // Since we don't know what nic were actually used for the http request,
544 // for now, just use the first one.
545 std::vector<rtc::Network*> networks;
546 network_manager_->GetNetworks(&networks);
547 if (networks.empty()) {
548 LOG(LS_ERROR) << "No networks while registering http start.";
551 rtc::ProxyInfo proxy_info = GetProxyInfo();
553 #ifdef USE_WEBRTC_DEV_BRANCH
554 nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address));
555 #else // USE_WEBRTC_DEV_BRANCH
556 nics_.find(NicId(networks[0]->ip(), proxy_info.address));
557 #endif // USE_WEBRTC_DEV_BRANCH
558 if (i != nics_.end()) {
559 uint32 now = rtc::Time();
560 NicInfo* nic_info = &i->second;
561 if (port == rtc::HTTP_DEFAULT_PORT) {
562 nic_info->http.start_time_ms = now;
563 } else if (port == rtc::HTTP_SECURE_PORT) {
564 nic_info->https.start_time_ms = now;
566 LOG(LS_ERROR) << "Registering start time for unknown port: " << port;
569 LOG(LS_ERROR) << "Error, no nic info found while registering http start.";