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