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

« back to all changes in this revision

Viewing changes to protocols/jabber/googletalk/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