blob: 1b599430ed1be1e79035b49b20c29624106fb334 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001// 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/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/constants.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
23namespace cricket {
24
henrike@webrtc.org28e20752013-07-10 00:45:36 +000025static const char kDefaultStunHostname[] = "stun.l.google.com";
26static const int kDefaultStunPort = 19302;
27
28// Default maximum time in milliseconds we will wait for connections.
29static const uint32 kDefaultTimeoutMs = 3000;
30
31enum {
32 MSG_START = 1,
33 MSG_STOP = 2,
34 MSG_TIMEOUT = 3,
35 MSG_SIGNAL_RESULTS = 4
36};
37
38class TestHttpPortAllocator : public HttpPortAllocator {
39 public:
40 TestHttpPortAllocator(talk_base::NetworkManager* network_manager,
41 const std::string& user_agent,
42 const std::string& relay_token) :
43 HttpPortAllocator(network_manager, user_agent) {
44 SetRelayToken(relay_token);
45 }
46 PortAllocatorSession* CreateSessionInternal(
47 const std::string& content_name,
48 int component,
49 const std::string& ice_ufrag,
50 const std::string& ice_pwd) {
51 return new TestHttpPortAllocatorSession(this, content_name, component,
52 ice_ufrag, ice_pwd,
53 stun_hosts(), relay_hosts(),
54 relay_token(), user_agent());
55 }
56};
57
58void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
59 SignalConfigReady(username(), password(), config, proxy_);
henrike@webrtc.org1e09a712013-07-26 19:17:59 +000060 delete config;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000061}
62
63void TestHttpPortAllocatorSession::OnRequestDone(
64 talk_base::SignalThread* data) {
65 talk_base::AsyncHttpRequest* request =
66 static_cast<talk_base::AsyncHttpRequest*>(data);
67
68 // Tell the checker that the request is complete.
69 SignalRequestDone(request);
70
71 // Pass on the response to super class.
72 HttpPortAllocatorSession::OnRequestDone(data);
73}
74
75ConnectivityChecker::ConnectivityChecker(
76 talk_base::Thread* worker,
77 const std::string& jid,
78 const std::string& session_id,
79 const std::string& user_agent,
80 const std::string& relay_token,
81 const std::string& connection)
82 : worker_(worker),
83 jid_(jid),
84 session_id_(session_id),
85 user_agent_(user_agent),
86 relay_token_(relay_token),
87 connection_(connection),
88 proxy_detect_(NULL),
89 timeout_ms_(kDefaultTimeoutMs),
90 stun_address_(kDefaultStunHostname, kDefaultStunPort),
91 started_(false) {
92}
93
94ConnectivityChecker::~ConnectivityChecker() {
95 if (started_) {
96 // We try to clear the TIMEOUT below. But worker may still handle it and
97 // cause SignalCheckDone to happen on main-thread. So we finally clear any
98 // pending SIGNAL_RESULTS.
99 worker_->Clear(this, MSG_TIMEOUT);
100 worker_->Send(this, MSG_STOP);
101 nics_.clear();
102 main_->Clear(this, MSG_SIGNAL_RESULTS);
103 }
104}
105
106bool ConnectivityChecker::Initialize() {
107 network_manager_.reset(CreateNetworkManager());
108 socket_factory_.reset(CreateSocketFactory(worker_));
109 port_allocator_.reset(CreatePortAllocator(network_manager_.get(),
110 user_agent_, relay_token_));
111 uint32 new_allocator_flags = port_allocator_->flags();
112 new_allocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
113 port_allocator_->set_flags(new_allocator_flags);
114 return true;
115}
116
117void ConnectivityChecker::Start() {
118 main_ = talk_base::Thread::Current();
119 worker_->Post(this, MSG_START);
120 started_ = true;
121}
122
123void ConnectivityChecker::CleanUp() {
124 ASSERT(worker_ == talk_base::Thread::Current());
125 if (proxy_detect_) {
126 proxy_detect_->Release();
127 proxy_detect_ = NULL;
128 }
129
130 for (uint32 i = 0; i < sessions_.size(); ++i) {
131 delete sessions_[i];
132 }
133 sessions_.clear();
134 for (uint32 i = 0; i < ports_.size(); ++i) {
135 delete ports_[i];
136 }
137 ports_.clear();
138}
139
140bool ConnectivityChecker::AddNic(const talk_base::IPAddress& ip,
141 const talk_base::SocketAddress& proxy_addr) {
142 NicMap::iterator i = nics_.find(NicId(ip, proxy_addr));
143 if (i != nics_.end()) {
144 // Already have it.
145 return false;
146 }
147 uint32 now = talk_base::Time();
148 NicInfo info;
149 info.ip = ip;
150 info.proxy_info = GetProxyInfo();
151 info.stun.start_time_ms = now;
152 nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info));
153 return true;
154}
155
156void ConnectivityChecker::SetProxyInfo(const talk_base::ProxyInfo& proxy_info) {
157 port_allocator_->set_proxy(user_agent_, proxy_info);
158 AllocatePorts();
159}
160
161talk_base::ProxyInfo ConnectivityChecker::GetProxyInfo() const {
162 talk_base::ProxyInfo proxy_info;
163 if (proxy_detect_) {
164 proxy_info = proxy_detect_->proxy();
165 }
166 return proxy_info;
167}
168
169void ConnectivityChecker::CheckNetworks() {
170 network_manager_->SignalNetworksChanged.connect(
171 this, &ConnectivityChecker::OnNetworksChanged);
172 network_manager_->StartUpdating();
173}
174
175void ConnectivityChecker::OnMessage(talk_base::Message *msg) {
176 switch (msg->message_id) {
177 case MSG_START:
178 ASSERT(worker_ == talk_base::Thread::Current());
179 worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT);
180 CheckNetworks();
181 break;
182 case MSG_STOP:
183 // We're being stopped, free resources.
184 CleanUp();
185 break;
186 case MSG_TIMEOUT:
187 // We need to signal results on the main thread.
188 main_->Post(this, MSG_SIGNAL_RESULTS);
189 break;
190 case MSG_SIGNAL_RESULTS:
191 ASSERT(main_ == talk_base::Thread::Current());
192 SignalCheckDone(this);
193 break;
194 default:
195 LOG(LS_ERROR) << "Unknown message: " << msg->message_id;
196 }
197}
198
199void 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
206void 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
234void ConnectivityChecker::OnConfigReady(
235 const std::string& username, const std::string& password,
236 const PortConfiguration* config, 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(username, password, config, proxy_info);
244}
245
246void ConnectivityChecker::OnRelayPortComplete(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
280void ConnectivityChecker::OnStunPortComplete(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
298void ConnectivityChecker::OnStunPortError(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
310void ConnectivityChecker::OnRelayPortError(Port* port) {
311 ASSERT(worker_ == talk_base::Thread::Current());
312 LOG(LS_ERROR) << "Relay address error.";
313}
314
315void 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
326HttpPortAllocator* 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
333StunPort* ConnectivityChecker::CreateStunPort(
334 const std::string& username, const std::string& password,
335 const PortConfiguration* config, talk_base::Network* network) {
336 return StunPort::Create(worker_, socket_factory_.get(),
337 network, network->ip(), 0, 0,
338 username, password, config->stun_address);
339}
340
341RelayPort* ConnectivityChecker::CreateRelayPort(
342 const std::string& username, const std::string& password,
343 const PortConfiguration* config, talk_base::Network* network) {
344 return RelayPort::Create(worker_, socket_factory_.get(),
345 network, network->ip(),
346 port_allocator_->min_port(),
347 port_allocator_->max_port(),
348 username, password);
349}
350
351void ConnectivityChecker::CreateRelayPorts(
352 const std::string& username, const std::string& password,
353 const PortConfiguration* config, const talk_base::ProxyInfo& proxy_info) {
354 PortConfiguration::RelayList::const_iterator relay;
355 std::vector<talk_base::Network*> networks;
356 network_manager_->GetNetworks(&networks);
357 if (networks.empty()) {
358 LOG(LS_ERROR) << "Machine has no networks; no relay ports created.";
359 return;
360 }
361 for (relay = config->relays.begin();
362 relay != config->relays.end(); ++relay) {
363 for (uint32 i = 0; i < networks.size(); ++i) {
364 NicMap::iterator iter = nics_.find(NicId(networks[i]->ip(),
365 proxy_info.address));
366 if (iter != nics_.end()) {
367 // TODO: Now setting the same start time for all protocols.
368 // This might affect accuracy, but since we are mainly looking for
369 // connect failures or number that stick out, this is good enough.
370 uint32 now = talk_base::Time();
371 NicInfo* nic_info = &iter->second;
372 nic_info->udp.start_time_ms = now;
373 nic_info->tcp.start_time_ms = now;
374 nic_info->ssltcp.start_time_ms = now;
375
376 // Add the addresses of this protocol.
377 PortList::const_iterator relay_port;
378 for (relay_port = relay->ports.begin();
379 relay_port != relay->ports.end();
380 ++relay_port) {
381 RelayPort* port = CreateRelayPort(username, password,
382 config, networks[i]);
383 port->AddServerAddress(*relay_port);
384 port->AddExternalAddress(*relay_port);
385
386 nic_info->media_server_address = port->ServerAddress(0)->address;
387
388 // Listen to network events.
389 port->SignalPortComplete.connect(
390 this, &ConnectivityChecker::OnRelayPortComplete);
391 port->SignalPortError.connect(
392 this, &ConnectivityChecker::OnRelayPortError);
393
394 port->set_proxy(user_agent_, proxy_info);
395
396 // Start fetching an address for this port.
397 port->PrepareAddress();
398 ports_.push_back(port);
399 }
400 } else {
401 LOG(LS_ERROR) << "Failed to find nic info when creating relay ports.";
402 }
403 }
404 }
405}
406
407void ConnectivityChecker::AllocatePorts() {
408 const std::string username = talk_base::CreateRandomString(ICE_UFRAG_LENGTH);
409 const std::string password = talk_base::CreateRandomString(ICE_PWD_LENGTH);
410 PortConfiguration config(stun_address_, username, password);
411 std::vector<talk_base::Network*> networks;
412 network_manager_->GetNetworks(&networks);
413 if (networks.empty()) {
414 LOG(LS_ERROR) << "Machine has no networks; no ports will be allocated";
415 return;
416 }
417 talk_base::ProxyInfo proxy_info = GetProxyInfo();
418 bool allocate_relay_ports = false;
419 for (uint32 i = 0; i < networks.size(); ++i) {
420 if (AddNic(networks[i]->ip(), proxy_info.address)) {
421 Port* port = CreateStunPort(username, password, &config, networks[i]);
422 if (port) {
423
424 // Listen to network events.
425 port->SignalPortComplete.connect(
426 this, &ConnectivityChecker::OnStunPortComplete);
427 port->SignalPortError.connect(
428 this, &ConnectivityChecker::OnStunPortError);
429
430 port->set_proxy(user_agent_, proxy_info);
431 port->PrepareAddress();
432 ports_.push_back(port);
433 allocate_relay_ports = true;
434 }
435 }
436 }
437
438 // If any new ip/proxy combinations were added, send a relay allocate.
439 if (allocate_relay_ports) {
440 AllocateRelayPorts();
441 }
442
443 // Initiate proxy detection.
444 InitiateProxyDetection();
445}
446
447void ConnectivityChecker::InitiateProxyDetection() {
448 // Only start if we haven't been started before.
449 if (!proxy_detect_) {
450 proxy_detect_ = new talk_base::AutoDetectProxy(user_agent_);
451 talk_base::Url<char> host_url("/", "relay.google.com",
452 talk_base::HTTP_DEFAULT_PORT);
453 host_url.set_secure(true);
454 proxy_detect_->set_server_url(host_url.url());
455 proxy_detect_->SignalWorkDone.connect(
456 this, &ConnectivityChecker::OnProxyDetect);
457 proxy_detect_->Start();
458 }
459}
460
461void ConnectivityChecker::AllocateRelayPorts() {
462 // Currently we are using the 'default' nic for http(s) requests.
463 TestHttpPortAllocatorSession* allocator_session =
464 reinterpret_cast<TestHttpPortAllocatorSession*>(
465 port_allocator_->CreateSessionInternal(
466 "connectivity checker test content",
467 ICE_CANDIDATE_COMPONENT_RTP,
468 talk_base::CreateRandomString(ICE_UFRAG_LENGTH),
469 talk_base::CreateRandomString(ICE_PWD_LENGTH)));
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
487void 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