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