1
// Copyright 2011 Google Inc. All Rights Reserved.
6
#include "talk/p2p/client/connectivitychecker.h"
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"
25
static const char kSessionTypeVideo[] =
26
"http://www.google.com/session/video";
27
static const char kSessionNameRtp[] = "rtp";
29
static const char kDefaultStunHostname[] = "stun.l.google.com";
30
static const int kDefaultStunPort = 19302;
32
// Default maximum time in milliseconds we will wait for connections.
33
static const uint32 kDefaultTimeoutMs = 3000;
39
MSG_SIGNAL_RESULTS = 4
42
class TestHttpPortAllocator : public HttpPortAllocator {
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);
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());
58
void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
59
SignalConfigReady(config, proxy_);
62
void TestHttpPortAllocatorSession::OnRequestDone(
63
talk_base::SignalThread* data) {
64
talk_base::AsyncHttpRequest* request =
65
static_cast<talk_base::AsyncHttpRequest*>(data);
67
// Tell the checker that the request is complete.
68
SignalRequestDone(request);
70
// Pass on the response to super class.
71
HttpPortAllocatorSession::OnRequestDone(data);
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)
83
session_id_(session_id),
84
user_agent_(user_agent),
85
relay_token_(relay_token),
86
connection_(connection),
88
timeout_ms_(kDefaultTimeoutMs),
89
stun_address_(kDefaultStunHostname, kDefaultStunPort) {
92
ConnectivityChecker::~ConnectivityChecker() {
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_));
105
void ConnectivityChecker::Start() {
106
main_ = talk_base::Thread::Current();
107
worker_->Post(this, MSG_START);
110
void ConnectivityChecker::Stop() {
111
worker_->Post(this, MSG_STOP);
114
void ConnectivityChecker::CleanUp() {
115
ASSERT(worker_ == talk_base::Thread::Current());
116
worker_->Clear(this, MSG_TIMEOUT);
118
proxy_detect_->Release();
119
proxy_detect_ = NULL;
122
for (uint32 i = 0; i < sessions_.size(); ++i) {
126
for (uint32 i = 0; i < ports_.size(); ++i) {
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()) {
139
uint32 now = talk_base::Time();
142
info.proxy_info = GetProxyInfo();
143
info.stun.start_time_ms = now;
144
nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info));
148
void ConnectivityChecker::SetProxyInfo(const talk_base::ProxyInfo& proxy_info) {
149
port_allocator_->set_proxy(user_agent_, proxy_info);
153
talk_base::ProxyInfo ConnectivityChecker::GetProxyInfo() const {
154
talk_base::ProxyInfo proxy_info;
156
proxy_info = proxy_detect_->proxy();
161
void ConnectivityChecker::CheckNetworks() {
162
network_manager_->SignalNetworksChanged.connect(
163
this, &ConnectivityChecker::OnNetworksChanged);
164
network_manager_->StartUpdating();
167
void ConnectivityChecker::OnMessage(talk_base::Message *msg) {
168
switch (msg->message_id) {
170
ASSERT(worker_ == talk_base::Thread::Current());
171
worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT);
175
// We were stopped, just close down without signaling.
179
// Close down and signal results.
182
case MSG_SIGNAL_RESULTS:
183
ASSERT(main_ == talk_base::Thread::Current());
184
SignalCheckDone(this);
187
LOG(LS_ERROR) << "Unknown message: " << msg->message_id;
191
void ConnectivityChecker::OnCheckDone(bool signal_results) {
192
// Clean up memory allocated by the worker thread.
195
if (signal_results) {
196
main_->Post(this, MSG_SIGNAL_RESULTS);
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());
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.";
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;
228
LOG(LS_ERROR) << "Got response with unknown port: " << port;
231
LOG(LS_ERROR) << "No nic info found while receiving response.";
235
void ConnectivityChecker::OnConfigReady(
236
const PortConfiguration* config,
237
const talk_base::ProxyInfo& proxy_info) {
238
ASSERT(worker_ == talk_base::Thread::Current());
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);
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;
258
switch (address->proto) {
260
connect_info = &nic_info->udp;
263
connect_info = &nic_info->tcp;
266
connect_info = &nic_info->ssltcp;
269
LOG(LS_ERROR) << " relay address with bad protocol added";
273
talk_base::TimeSince(connect_info->start_time_ms);
277
LOG(LS_ERROR) << " got relay address for non-existing nic";
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;
295
LOG(LS_ERROR) << "Got stun address for non-existing nic";
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();
311
void ConnectivityChecker::OnRelayAddressError(Port* port) {
312
ASSERT(worker_ == talk_base::Thread::Current());
313
LOG(LS_ERROR) << "Relay address error.";
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";
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);
334
StunPort* ConnectivityChecker::CreateStunPort(
335
const PortConfiguration* config, talk_base::Network* network) {
336
return StunPort::Create(worker_,
337
socket_factory_.get(),
342
config->stun_address);
345
RelayPort* ConnectivityChecker::CreateRelayPort(
346
const PortConfiguration* config, talk_base::Network* network) {
347
return RelayPort::Create(worker_,
348
socket_factory_.get(),
351
port_allocator_->min_port(),
352
port_allocator_->max_port(),
355
config->magic_cookie);
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.";
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;
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();
388
RelayPort* port = CreateRelayPort(config, networks[i]);
389
port->AddServerAddress(*relay_port);
390
port->AddExternalAddress(*relay_port);
392
nic_info->media_server_address = port->ServerAddress(0)->address;
394
// Listen to network events.
395
port->SignalAddressReady.connect(
396
this, &ConnectivityChecker::OnRelayAddressReady);
397
port->SignalAddressError.connect(
398
this, &ConnectivityChecker::OnRelayAddressError);
400
port->set_proxy(user_agent_, proxy_info);
402
// Start fetching an address for this port.
403
port->PrepareAddress();
404
ports_.push_back(port);
407
LOG(LS_ERROR) << "Failed to find nic info when creating relay ports.";
413
void ConnectivityChecker::AllocatePorts() {
414
PortConfiguration config(stun_address_,
415
talk_base::CreateRandomString(16),
416
talk_base::CreateRandomString(16),
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";
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]);
430
// Listen to network events.
431
port->SignalAddressReady.connect(
432
this, &ConnectivityChecker::OnStunAddressReady);
433
port->SignalAddressError.connect(
434
this, &ConnectivityChecker::OnStunAddressError);
436
port->set_proxy(user_agent_, proxy_info);
437
port->PrepareAddress();
438
ports_.push_back(port);
439
allocate_relay_ports = true;
443
// If any new ip/proxy combinations were added, send a relay allocate.
444
if (allocate_relay_ports) {
445
AllocateRelayPorts();
448
// Initiate proxy detection.
449
InitiateProxyDetection();
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();
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);
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);
485
sessions_.push_back(allocator_session);
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.";
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;
507
LOG(LS_ERROR) << "Registering start time for unknown port: " << port;
510
LOG(LS_ERROR) << "Error, no nic info found while registering http start.";
514
} // namespace talk_base