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