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
47namespace cricket {
48
49static const char kDefaultStunHostname[] = "stun.l.google.com";
50static const int kDefaultStunPort = 19302;
51
52// Default maximum time in milliseconds we will wait for connections.
53static const uint32 kDefaultTimeoutMs = 3000;
54
55enum {
56  MSG_START = 1,
57  MSG_STOP = 2,
58  MSG_TIMEOUT = 3,
59  MSG_SIGNAL_RESULTS = 4
60};
61
62class 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
82void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
83  SignalConfigReady(username(), password(), config, proxy_);
84  delete config;
85}
86
87void 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
99ConnectivityChecker::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
118ConnectivityChecker::~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
130bool 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
141void ConnectivityChecker::Start() {
142  main_ = rtc::Thread::Current();
143  worker_->Post(this, MSG_START);
144  started_ = true;
145}
146
147void 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
164bool 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
180void ConnectivityChecker::SetProxyInfo(const rtc::ProxyInfo& proxy_info) {
181  port_allocator_->set_proxy(user_agent_, proxy_info);
182  AllocatePorts();
183}
184
185rtc::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
193void ConnectivityChecker::CheckNetworks() {
194  network_manager_->SignalNetworksChanged.connect(
195      this, &ConnectivityChecker::OnNetworksChanged);
196  network_manager_->StartUpdating();
197}
198
199void 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
223void 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
230void 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
263void 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
275void 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
313void 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
337void 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
355void ConnectivityChecker::OnRelayPortError(Port* port) {
356  ASSERT(worker_ == rtc::Thread::Current());
357  LOG(LS_ERROR) << "Relay address error.";
358}
359
360void 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
371HttpPortAllocator* 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
378StunPort* 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
396RelayPort* 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
413void 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
473void 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
519void 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
533void 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
559void 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