OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "remoting/protocol/libjingle_transport_factory.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/callback.h" | |
10 #include "base/callback_helpers.h" | |
11 #include "base/single_thread_task_runner.h" | |
12 #include "base/thread_task_runner_handle.h" | |
13 #include "base/timer/timer.h" | |
14 #include "jingle/glue/utils.h" | |
15 #include "net/base/net_errors.h" | |
16 #include "remoting/protocol/channel_socket_adapter.h" | |
17 #include "remoting/protocol/network_settings.h" | |
18 #include "remoting/signaling/jingle_info_request.h" | |
19 #include "third_party/webrtc/base/network.h" | |
20 #include "third_party/webrtc/p2p/base/constants.h" | |
21 #include "third_party/webrtc/p2p/base/p2ptransportchannel.h" | |
22 #include "third_party/webrtc/p2p/base/port.h" | |
23 #include "third_party/webrtc/p2p/client/httpportallocator.h" | |
24 | |
25 namespace remoting { | |
26 namespace protocol { | |
27 | |
28 namespace { | |
29 | |
30 // Try connecting ICE twice with timeout of 15 seconds for each attempt. | |
31 const int kMaxReconnectAttempts = 2; | |
32 const int kReconnectDelaySeconds = 15; | |
33 | |
34 // Get fresh STUN/Relay configuration every hour. | |
35 const int kJingleInfoUpdatePeriodSeconds = 3600; | |
36 | |
37 // Utility function to map a cricket::Candidate string type to a | |
38 // TransportRoute::RouteType enum value. | |
39 TransportRoute::RouteType CandidateTypeToTransportRouteType( | |
40 const std::string& candidate_type) { | |
41 if (candidate_type == "local") { | |
42 return TransportRoute::DIRECT; | |
43 } else if (candidate_type == "stun" || candidate_type == "prflx") { | |
44 return TransportRoute::STUN; | |
45 } else if (candidate_type == "relay") { | |
46 return TransportRoute::RELAY; | |
47 } else { | |
48 LOG(FATAL) << "Unknown candidate type: " << candidate_type; | |
49 return TransportRoute::DIRECT; | |
50 } | |
51 } | |
52 | |
53 class LibjingleTransport | |
54 : public Transport, | |
55 public base::SupportsWeakPtr<LibjingleTransport>, | |
56 public sigslot::has_slots<> { | |
57 public: | |
58 LibjingleTransport(cricket::PortAllocator* port_allocator, | |
59 const NetworkSettings& network_settings, | |
60 TransportRole role); | |
61 ~LibjingleTransport() override; | |
62 | |
63 // Called by JingleTransportFactory when it has fresh Jingle info. | |
64 void OnCanStart(); | |
65 | |
66 // Transport interface. | |
67 void Connect(const std::string& name, | |
68 Transport::EventHandler* event_handler, | |
69 const Transport::ConnectedCallback& callback) override; | |
70 void SetRemoteCredentials(const std::string& ufrag, | |
71 const std::string& password) override; | |
72 void AddRemoteCandidate(const cricket::Candidate& candidate) override; | |
73 const std::string& name() const override; | |
74 bool is_connected() const override; | |
75 | |
76 private: | |
77 void DoStart(); | |
78 void NotifyConnected(); | |
79 | |
80 // Signal handlers for cricket::TransportChannel. | |
81 void OnCandidateGathered(cricket::TransportChannelImpl* channel, | |
82 const cricket::Candidate& candidate); | |
83 void OnRouteChange(cricket::TransportChannel* channel, | |
84 const cricket::Candidate& candidate); | |
85 void OnReceivingState(cricket::TransportChannel* channel); | |
86 void OnWritableState(cricket::TransportChannel* channel); | |
87 | |
88 // Callback for TransportChannelSocketAdapter to notify when the socket is | |
89 // destroyed. | |
90 void OnChannelDestroyed(); | |
91 | |
92 void NotifyRouteChanged(); | |
93 | |
94 // Tries to connect by restarting ICE. Called by |reconnect_timer_|. | |
95 void TryReconnect(); | |
96 | |
97 cricket::PortAllocator* port_allocator_; | |
98 NetworkSettings network_settings_; | |
99 TransportRole role_; | |
100 | |
101 std::string name_; | |
102 EventHandler* event_handler_; | |
103 Transport::ConnectedCallback callback_; | |
104 std::string ice_username_fragment_; | |
105 | |
106 bool can_start_; | |
107 | |
108 std::string remote_ice_username_fragment_; | |
109 std::string remote_ice_password_; | |
110 std::list<cricket::Candidate> pending_candidates_; | |
111 scoped_ptr<cricket::P2PTransportChannel> channel_; | |
112 int connect_attempts_left_; | |
113 base::RepeatingTimer reconnect_timer_; | |
114 | |
115 base::WeakPtrFactory<LibjingleTransport> weak_factory_; | |
116 | |
117 DISALLOW_COPY_AND_ASSIGN(LibjingleTransport); | |
118 }; | |
119 | |
120 LibjingleTransport::LibjingleTransport(cricket::PortAllocator* port_allocator, | |
121 const NetworkSettings& network_settings, | |
122 TransportRole role) | |
123 : port_allocator_(port_allocator), | |
124 network_settings_(network_settings), | |
125 role_(role), | |
126 event_handler_(nullptr), | |
127 ice_username_fragment_( | |
128 rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH)), | |
129 can_start_(false), | |
130 connect_attempts_left_(kMaxReconnectAttempts), | |
131 weak_factory_(this) { | |
132 DCHECK(!ice_username_fragment_.empty()); | |
133 } | |
134 | |
135 LibjingleTransport::~LibjingleTransport() { | |
136 DCHECK(event_handler_); | |
137 | |
138 event_handler_->OnTransportDeleted(this); | |
139 | |
140 if (channel_.get()) { | |
141 base::ThreadTaskRunnerHandle::Get()->DeleteSoon( | |
142 FROM_HERE, channel_.release()); | |
143 } | |
144 } | |
145 | |
146 void LibjingleTransport::OnCanStart() { | |
147 DCHECK(CalledOnValidThread()); | |
148 | |
149 DCHECK(!can_start_); | |
150 can_start_ = true; | |
151 | |
152 // If Connect() has been called then start connection. | |
153 if (!callback_.is_null()) | |
154 DoStart(); | |
155 | |
156 // Pass pending ICE credentials and candidates to the channel. | |
157 if (!remote_ice_username_fragment_.empty()) { | |
158 channel_->SetRemoteIceCredentials(remote_ice_username_fragment_, | |
159 remote_ice_password_); | |
160 } | |
161 | |
162 while (!pending_candidates_.empty()) { | |
163 channel_->AddRemoteCandidate(pending_candidates_.front()); | |
164 pending_candidates_.pop_front(); | |
165 } | |
166 } | |
167 | |
168 void LibjingleTransport::Connect( | |
169 const std::string& name, | |
170 Transport::EventHandler* event_handler, | |
171 const Transport::ConnectedCallback& callback) { | |
172 DCHECK(CalledOnValidThread()); | |
173 DCHECK(!name.empty()); | |
174 DCHECK(event_handler); | |
175 DCHECK(!callback.is_null()); | |
176 | |
177 DCHECK(name_.empty()); | |
178 name_ = name; | |
179 event_handler_ = event_handler; | |
180 callback_ = callback; | |
181 | |
182 if (can_start_) | |
183 DoStart(); | |
184 } | |
185 | |
186 void LibjingleTransport::DoStart() { | |
187 DCHECK(!channel_.get()); | |
188 | |
189 // Create P2PTransportChannel, attach signal handlers and connect it. | |
190 // TODO(sergeyu): Specify correct component ID for the channel. | |
191 channel_.reset(new cricket::P2PTransportChannel( | |
192 std::string(), 0, nullptr, port_allocator_)); | |
193 std::string ice_password = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH); | |
194 channel_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); | |
195 channel_->SetIceRole((role_ == TransportRole::CLIENT) | |
196 ? cricket::ICEROLE_CONTROLLING | |
197 : cricket::ICEROLE_CONTROLLED); | |
198 event_handler_->OnTransportIceCredentials(this, ice_username_fragment_, | |
199 ice_password); | |
200 channel_->SetIceCredentials(ice_username_fragment_, ice_password); | |
201 channel_->SignalCandidateGathered.connect( | |
202 this, &LibjingleTransport::OnCandidateGathered); | |
203 channel_->SignalRouteChange.connect( | |
204 this, &LibjingleTransport::OnRouteChange); | |
205 channel_->SignalReceivingState.connect( | |
206 this, &LibjingleTransport::OnReceivingState); | |
207 channel_->SignalWritableState.connect( | |
208 this, &LibjingleTransport::OnWritableState); | |
209 channel_->set_incoming_only( | |
210 !(network_settings_.flags & NetworkSettings::NAT_TRAVERSAL_OUTGOING)); | |
211 | |
212 channel_->Connect(); | |
213 channel_->MaybeStartGathering(); | |
214 | |
215 --connect_attempts_left_; | |
216 | |
217 // Start reconnection timer. | |
218 reconnect_timer_.Start( | |
219 FROM_HERE, base::TimeDelta::FromSeconds(kReconnectDelaySeconds), | |
220 this, &LibjingleTransport::TryReconnect); | |
221 } | |
222 | |
223 void LibjingleTransport::NotifyConnected() { | |
224 // Create P2PDatagramSocket adapter for the P2PTransportChannel. | |
225 scoped_ptr<TransportChannelSocketAdapter> socket( | |
226 new TransportChannelSocketAdapter(channel_.get())); | |
227 socket->SetOnDestroyedCallback(base::Bind( | |
228 &LibjingleTransport::OnChannelDestroyed, base::Unretained(this))); | |
229 base::ResetAndReturn(&callback_).Run(socket.Pass()); | |
230 } | |
231 | |
232 void LibjingleTransport::SetRemoteCredentials(const std::string& ufrag, | |
233 const std::string& password) { | |
234 DCHECK(CalledOnValidThread()); | |
235 | |
236 remote_ice_username_fragment_ = ufrag; | |
237 remote_ice_password_ = password; | |
238 | |
239 if (channel_) | |
240 channel_->SetRemoteIceCredentials(ufrag, password); | |
241 } | |
242 | |
243 void LibjingleTransport::AddRemoteCandidate( | |
244 const cricket::Candidate& candidate) { | |
245 DCHECK(CalledOnValidThread()); | |
246 | |
247 // To enforce the no-relay setting, it's not enough to not produce relay | |
248 // candidates. It's also necessary to discard remote relay candidates. | |
249 bool relay_allowed = (network_settings_.flags & | |
250 NetworkSettings::NAT_TRAVERSAL_RELAY) != 0; | |
251 if (!relay_allowed && candidate.type() == cricket::RELAY_PORT_TYPE) | |
252 return; | |
253 | |
254 if (channel_) { | |
255 channel_->AddRemoteCandidate(candidate); | |
256 } else { | |
257 pending_candidates_.push_back(candidate); | |
258 } | |
259 } | |
260 | |
261 const std::string& LibjingleTransport::name() const { | |
262 DCHECK(CalledOnValidThread()); | |
263 return name_; | |
264 } | |
265 | |
266 bool LibjingleTransport::is_connected() const { | |
267 DCHECK(CalledOnValidThread()); | |
268 return callback_.is_null(); | |
269 } | |
270 | |
271 void LibjingleTransport::OnCandidateGathered( | |
272 cricket::TransportChannelImpl* channel, | |
273 const cricket::Candidate& candidate) { | |
274 DCHECK(CalledOnValidThread()); | |
275 event_handler_->OnTransportCandidate(this, candidate); | |
276 } | |
277 | |
278 void LibjingleTransport::OnRouteChange( | |
279 cricket::TransportChannel* channel, | |
280 const cricket::Candidate& candidate) { | |
281 // Ignore notifications if the channel is not writable. | |
282 if (channel_->writable()) | |
283 NotifyRouteChanged(); | |
284 } | |
285 | |
286 void LibjingleTransport::OnReceivingState(cricket::TransportChannel* channel) { | |
287 DCHECK_EQ(channel, static_cast<cricket::TransportChannel*>(channel_.get())); | |
288 | |
289 if (channel->receiving() && !callback_.is_null()) | |
290 NotifyConnected(); | |
291 } | |
292 | |
293 void LibjingleTransport::OnWritableState(cricket::TransportChannel* channel) { | |
294 DCHECK_EQ(channel, static_cast<cricket::TransportChannel*>(channel_.get())); | |
295 | |
296 if (channel->writable()) { | |
297 connect_attempts_left_ = kMaxReconnectAttempts; | |
298 reconnect_timer_.Stop(); | |
299 | |
300 // Route change notifications are ignored when the |channel_| is not | |
301 // writable. Notify the event handler about the current route once the | |
302 // channel is writable. | |
303 NotifyRouteChanged(); | |
304 } else { | |
305 reconnect_timer_.Reset(); | |
306 TryReconnect(); | |
307 } | |
308 } | |
309 | |
310 void LibjingleTransport::OnChannelDestroyed() { | |
311 // The connection socket is being deleted, so delete the transport too. | |
312 delete this; | |
313 } | |
314 | |
315 void LibjingleTransport::NotifyRouteChanged() { | |
316 TransportRoute route; | |
317 | |
318 DCHECK(channel_->best_connection()); | |
319 const cricket::Connection* connection = channel_->best_connection(); | |
320 | |
321 // A connection has both a local and a remote candidate. For our purposes, the | |
322 // route type is determined by the most indirect candidate type. For example: | |
323 // it's possible for the local candidate be a "relay" type, while the remote | |
324 // candidate is "local". In this case, we still want to report a RELAY route | |
325 // type. | |
326 static_assert(TransportRoute::DIRECT < TransportRoute::STUN && | |
327 TransportRoute::STUN < TransportRoute::RELAY, | |
328 "Route type enum values are ordered by 'indirectness'"); | |
329 route.type = std::max( | |
330 CandidateTypeToTransportRouteType(connection->local_candidate().type()), | |
331 CandidateTypeToTransportRouteType(connection->remote_candidate().type())); | |
332 | |
333 if (!jingle_glue::SocketAddressToIPEndPoint( | |
334 connection->remote_candidate().address(), &route.remote_address)) { | |
335 LOG(FATAL) << "Failed to convert peer IP address."; | |
336 } | |
337 | |
338 const cricket::Candidate& local_candidate = | |
339 channel_->best_connection()->local_candidate(); | |
340 if (!jingle_glue::SocketAddressToIPEndPoint( | |
341 local_candidate.address(), &route.local_address)) { | |
342 LOG(FATAL) << "Failed to convert local IP address."; | |
343 } | |
344 | |
345 event_handler_->OnTransportRouteChange(this, route); | |
346 } | |
347 | |
348 void LibjingleTransport::TryReconnect() { | |
349 DCHECK(!channel_->writable()); | |
350 | |
351 if (connect_attempts_left_ <= 0) { | |
352 reconnect_timer_.Stop(); | |
353 | |
354 // Notify the caller that ICE connection has failed - normally that will | |
355 // terminate Jingle connection (i.e. the transport will be destroyed). | |
356 event_handler_->OnTransportFailed(this); | |
357 return; | |
358 } | |
359 --connect_attempts_left_; | |
360 | |
361 // Restart ICE by resetting ICE password. | |
362 std::string ice_password = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH); | |
363 event_handler_->OnTransportIceCredentials(this, ice_username_fragment_, | |
364 ice_password); | |
365 channel_->SetIceCredentials(ice_username_fragment_, ice_password); | |
366 } | |
367 | |
368 } // namespace | |
369 | |
370 LibjingleTransportFactory::LibjingleTransportFactory( | |
371 SignalStrategy* signal_strategy, | |
372 scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator, | |
373 const NetworkSettings& network_settings, | |
374 TransportRole role) | |
375 : signal_strategy_(signal_strategy), | |
376 port_allocator_(port_allocator.Pass()), | |
377 network_settings_(network_settings), | |
378 role_(role) { | |
379 } | |
380 | |
381 LibjingleTransportFactory::~LibjingleTransportFactory() { | |
382 // This method may be called in response to a libjingle signal, so | |
383 // libjingle objects must be deleted asynchronously. | |
384 scoped_refptr<base::SingleThreadTaskRunner> task_runner = | |
385 base::ThreadTaskRunnerHandle::Get(); | |
386 task_runner->DeleteSoon(FROM_HERE, port_allocator_.release()); | |
387 } | |
388 | |
389 void LibjingleTransportFactory::PrepareTokens() { | |
390 EnsureFreshJingleInfo(); | |
391 } | |
392 | |
393 scoped_ptr<Transport> LibjingleTransportFactory::CreateTransport() { | |
394 scoped_ptr<LibjingleTransport> result( | |
395 new LibjingleTransport(port_allocator_.get(), network_settings_, role_)); | |
396 | |
397 EnsureFreshJingleInfo(); | |
398 | |
399 // If there is a pending |jingle_info_request_| delay starting the new | |
400 // transport until the request is finished. | |
401 if (jingle_info_request_) { | |
402 on_jingle_info_callbacks_.push_back( | |
403 base::Bind(&LibjingleTransport::OnCanStart, | |
404 result->AsWeakPtr())); | |
405 } else { | |
406 result->OnCanStart(); | |
407 } | |
408 | |
409 return result.Pass(); | |
410 } | |
411 | |
412 void LibjingleTransportFactory::EnsureFreshJingleInfo() { | |
413 uint32 stun_or_relay_flags = NetworkSettings::NAT_TRAVERSAL_STUN | | |
414 NetworkSettings::NAT_TRAVERSAL_RELAY; | |
415 if (!(network_settings_.flags & stun_or_relay_flags) || | |
416 jingle_info_request_) { | |
417 return; | |
418 } | |
419 | |
420 if (last_jingle_info_update_time_.is_null() || | |
421 base::TimeTicks::Now() - last_jingle_info_update_time_ > | |
422 base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds)) { | |
423 jingle_info_request_.reset(new JingleInfoRequest(signal_strategy_)); | |
424 jingle_info_request_->Send(base::Bind( | |
425 &LibjingleTransportFactory::OnJingleInfo, base::Unretained(this))); | |
426 } | |
427 } | |
428 | |
429 void LibjingleTransportFactory::OnJingleInfo( | |
430 const std::string& relay_token, | |
431 const std::vector<std::string>& relay_hosts, | |
432 const std::vector<rtc::SocketAddress>& stun_hosts) { | |
433 if (!relay_token.empty() && !relay_hosts.empty()) { | |
434 port_allocator_->SetRelayHosts(relay_hosts); | |
435 port_allocator_->SetRelayToken(relay_token); | |
436 } | |
437 if (!stun_hosts.empty()) { | |
438 port_allocator_->SetStunHosts(stun_hosts); | |
439 } | |
440 | |
441 jingle_info_request_.reset(); | |
442 if ((!relay_token.empty() && !relay_hosts.empty()) || !stun_hosts.empty()) | |
443 last_jingle_info_update_time_ = base::TimeTicks::Now(); | |
444 | |
445 while (!on_jingle_info_callbacks_.empty()) { | |
446 on_jingle_info_callbacks_.begin()->Run(); | |
447 on_jingle_info_callbacks_.pop_front(); | |
448 } | |
449 } | |
450 | |
451 } // namespace protocol | |
452 } // namespace remoting | |
OLD | NEW |