3 * Copyright 2011, Google Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
30 #include "webrtc/p2p/client/connectivitychecker.h"
32 #include "webrtc/p2p/base/candidate.h"
33 #include "webrtc/p2p/base/common.h"
34 #include "webrtc/p2p/base/constants.h"
35 #include "webrtc/p2p/base/port.h"
36 #include "webrtc/p2p/base/relayport.h"
37 #include "webrtc/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"
49 static const char kDefaultStunHostname[] = "stun.l.google.com";
50 static const int kDefaultStunPort = 19302;
52 // Default maximum time in milliseconds we will wait for connections.
53 static const uint32 kDefaultTimeoutMs = 3000;
59 MSG_SIGNAL_RESULTS = 4
62 class TestHttpPortAllocator : public HttpPortAllocator {
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);
70 PortAllocatorSession* CreateSessionInternal(
71 const std::string& content_name,
73 const std::string& ice_ufrag,
74 const std::string& ice_pwd) {
75 return new TestHttpPortAllocatorSession(this, content_name, component,
77 stun_hosts(), relay_hosts(),
78 relay_token(), user_agent());
82 void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
83 SignalConfigReady(username(), password(), config, proxy_);
87 void TestHttpPortAllocatorSession::OnRequestDone(
88 rtc::SignalThread* data) {
89 rtc::AsyncHttpRequest* request =
90 static_cast<rtc::AsyncHttpRequest*>(data);
92 // Tell the checker that the request is complete.
93 SignalRequestDone(request);
95 // Pass on the response to super class.
96 HttpPortAllocatorSession::OnRequestDone(data);
99 ConnectivityChecker::ConnectivityChecker(
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)
108 session_id_(session_id),
109 user_agent_(user_agent),
110 relay_token_(relay_token),
111 connection_(connection),
113 timeout_ms_(kDefaultTimeoutMs),
114 stun_address_(kDefaultStunHostname, kDefaultStunPort),
118 ConnectivityChecker::~ConnectivityChecker() {
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);
126 main_->Clear(this, MSG_SIGNAL_RESULTS);
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);
141 void ConnectivityChecker::Start() {
142 main_ = rtc::Thread::Current();
143 worker_->Post(this, MSG_START);
147 void ConnectivityChecker::CleanUp() {
148 ASSERT(worker_ == rtc::Thread::Current());
150 proxy_detect_->Release();
151 proxy_detect_ = NULL;
154 for (uint32 i = 0; i < sessions_.size(); ++i) {
158 for (uint32 i = 0; i < ports_.size(); ++i) {
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()) {
171 uint32 now = rtc::Time();
174 info.proxy_info = GetProxyInfo();
175 info.stun.start_time_ms = now;
176 nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info));
180 void ConnectivityChecker::SetProxyInfo(const rtc::ProxyInfo& proxy_info) {
181 port_allocator_->set_proxy(user_agent_, proxy_info);
185 rtc::ProxyInfo ConnectivityChecker::GetProxyInfo() const {
186 rtc::ProxyInfo proxy_info;
188 proxy_info = proxy_detect_->proxy();
193 void ConnectivityChecker::CheckNetworks() {
194 network_manager_->SignalNetworksChanged.connect(
195 this, &ConnectivityChecker::OnNetworksChanged);
196 network_manager_->StartUpdating();
199 void ConnectivityChecker::OnMessage(rtc::Message *msg) {
200 switch (msg->message_id) {
202 ASSERT(worker_ == rtc::Thread::Current());
203 worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT);
207 // We're being stopped, free resources.
211 // We need to signal results on the main thread.
212 main_->Post(this, MSG_SIGNAL_RESULTS);
214 case MSG_SIGNAL_RESULTS:
215 ASSERT(main_ == rtc::Thread::Current());
216 SignalCheckDone(this);
219 LOG(LS_ERROR) << "Unknown message: " << msg->message_id;
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());
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.";
240 rtc::ProxyInfo proxy_info = request->proxy();
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;
256 LOG(LS_ERROR) << "Got response with unknown port: " << port;
259 LOG(LS_ERROR) << "No nic info found while receiving response.";
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());
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);
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;
290 switch (address->proto) {
292 connect_info = &nic_info->udp;
295 connect_info = &nic_info->tcp;
298 connect_info = &nic_info->ssltcp;
301 LOG(LS_ERROR) << " relay address with bad protocol added";
305 rtc::TimeSince(connect_info->start_time_ms);
309 LOG(LS_ERROR) << " got relay address for non-existing nic";
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();
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;
333 LOG(LS_ERROR) << "Got stun address for non-existing nic";
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;
350 nic_info->stun_server_addresses =
351 static_cast<StunPort*>(port)->server_addresses();
355 void ConnectivityChecker::OnRelayPortError(Port* port) {
356 ASSERT(worker_ == rtc::Thread::Current());
357 LOG(LS_ERROR) << "Relay address error.";
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";
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);
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(),
384 #ifdef USE_WEBRTC_DEV_BRANCH
385 network->GetBestIP(),
386 #else // USE_WEBRTC_DEV_BRANCH
388 #endif // USE_WEBRTC_DEV_BRANCH
393 config->stun_servers);
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(),
402 #ifdef USE_WEBRTC_DEV_BRANCH
403 network->GetBestIP(),
404 #else // USE_WEBRTC_DEV_BRANCH
406 #endif // USE_WEBRTC_DEV_BRANCH
407 port_allocator_->min_port(),
408 port_allocator_->max_port(),
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.";
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;
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();
447 RelayPort* port = CreateRelayPort(username, password,
448 config, networks[i]);
449 port->AddServerAddress(*relay_port);
450 port->AddExternalAddress(*relay_port);
452 nic_info->media_server_address = port->ServerAddress(0)->address;
454 // Listen to network events.
455 port->SignalPortComplete.connect(
456 this, &ConnectivityChecker::OnRelayPortComplete);
457 port->SignalPortError.connect(
458 this, &ConnectivityChecker::OnRelayPortError);
460 port->set_proxy(user_agent_, proxy_info);
462 // Start fetching an address for this port.
463 port->PrepareAddress();
464 ports_.push_back(port);
467 LOG(LS_ERROR) << "Failed to find nic info when creating relay ports.";
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";
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]);
496 // Listen to network events.
497 port->SignalPortComplete.connect(
498 this, &ConnectivityChecker::OnStunPortComplete);
499 port->SignalPortError.connect(
500 this, &ConnectivityChecker::OnStunPortError);
502 port->set_proxy(user_agent_, proxy_info);
503 port->PrepareAddress();
504 ports_.push_back(port);
505 allocate_relay_ports = true;
510 // If any new ip/proxy combinations were added, send a relay allocate.
511 if (allocate_relay_ports) {
512 AllocateRelayPorts();
515 // Initiate proxy detection.
516 InitiateProxyDetection();
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();
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);
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);
556 sessions_.push_back(allocator_session);
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.";
568 rtc::ProxyInfo proxy_info = GetProxyInfo();
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;
583 LOG(LS_ERROR) << "Registering start time for unknown port: " << port;
586 LOG(LS_ERROR) << "Error, no nic info found while registering http start.";