~ubuntu-branches/debian/experimental/kopete/experimental

« back to all changes in this revision

Viewing changes to protocols/jabber/libjingle/talk/p2p/client/connectivitychecker.cc

  • Committer: Package Import Robot
  • Author(s): Maximiliano Curia
  • Date: 2015-02-24 11:32:57 UTC
  • mfrom: (1.1.41 vivid)
  • Revision ID: package-import@ubuntu.com-20150224113257-gnupg4v7lzz18ij0
Tags: 4:14.12.2-1
* New upstream release (14.12.2).
* Bump Standards-Version to 3.9.6, no changes needed.

Show diffs side-by-side

added added

removed removed

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