Note: We no longer publish the latest version of our code here. We primarily use a kumc-bmi github organization. The heron ETL repository, in particular, is not public. Peers in the informatics community should see MultiSiteDev for details on requesting access.

source: webrtc/talk/p2p/client/connectivitychecker.cc @ 0:4bda6873e34c

pub_scrub_3792 tip
Last change on this file since 0:4bda6873e34c was 0:4bda6873e34c, checked in by Michael Prittie <mprittie@…>, 6 years ago

Scrubbed password for publication.

File size: 17.7 KB
Line 
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
23namespace cricket {
24
25static const char kSessionTypeVideo[] =
26    "http://www.google.com/session/video";
27static const char kSessionNameRtp[] = "rtp";
28
29static const char kDefaultStunHostname[] = "stun.l.google.com";
30static const int kDefaultStunPort = 19302;
31
32// Default maximum time in milliseconds we will wait for connections.
33static const uint32 kDefaultTimeoutMs = 3000;
34
35enum {
36  MSG_START = 1,
37  MSG_STOP = 2,
38  MSG_TIMEOUT = 3,
39  MSG_SIGNAL_RESULTS = 4
40};
41
42class 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
62void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
63  SignalConfigReady(username(), password(), config, proxy_);
64  delete config;
65}
66
67void 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
79ConnectivityChecker::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
98ConnectivityChecker::~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
110bool 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
121void ConnectivityChecker::Start() {
122  main_ = talk_base::Thread::Current();
123  worker_->Post(this, MSG_START);
124  started_ = true;
125}
126
127void 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
144bool 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
160void ConnectivityChecker::SetProxyInfo(const talk_base::ProxyInfo& proxy_info) {
161  port_allocator_->set_proxy(user_agent_, proxy_info);
162  AllocatePorts();
163}
164
165talk_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
173void ConnectivityChecker::CheckNetworks() {
174  network_manager_->SignalNetworksChanged.connect(
175      this, &ConnectivityChecker::OnNetworksChanged);
176  network_manager_->StartUpdating();
177}
178
179void 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
203void 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
210void 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
238void 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
250void 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
284void 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
302void 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
314void ConnectivityChecker::OnRelayPortError(Port* port) {
315  ASSERT(worker_ == talk_base::Thread::Current());
316  LOG(LS_ERROR) << "Relay address error.";
317}
318
319void 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
330HttpPortAllocator* 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
337StunPort* 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
345RelayPort* 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
355void 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
411void 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
451void 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
465void 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
491void 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
Note: See TracBrowser for help on using the repository browser.