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.
5 #include "remoting/protocol/libjingle_transport_factory.h"
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/basicportallocator.h"
24 #include "third_party/webrtc/p2p/client/httpportallocator.h"
31 // Try connecting ICE twice with timeout of 15 seconds for each attempt.
32 const int kMaxReconnectAttempts
= 2;
33 const int kReconnectDelaySeconds
= 15;
35 // Get fresh STUN/Relay configuration every hour.
36 const int kJingleInfoUpdatePeriodSeconds
= 3600;
38 // Utility function to map a cricket::Candidate string type to a
39 // TransportRoute::RouteType enum value.
40 TransportRoute::RouteType
CandidateTypeToTransportRouteType(
41 const std::string
& candidate_type
) {
42 if (candidate_type
== "local") {
43 return TransportRoute::DIRECT
;
44 } else if (candidate_type
== "stun" || candidate_type
== "prflx") {
45 return TransportRoute::STUN
;
46 } else if (candidate_type
== "relay") {
47 return TransportRoute::RELAY
;
49 LOG(FATAL
) << "Unknown candidate type: " << candidate_type
;
50 return TransportRoute::DIRECT
;
54 class LibjingleTransport
56 public base::SupportsWeakPtr
<LibjingleTransport
>,
57 public sigslot::has_slots
<> {
59 LibjingleTransport(cricket::PortAllocator
* port_allocator
,
60 const NetworkSettings
& network_settings
,
62 ~LibjingleTransport() override
;
64 // Called by JingleTransportFactory when it has fresh Jingle info.
67 // Transport interface.
68 void Connect(const std::string
& name
,
69 Transport::EventHandler
* event_handler
,
70 const Transport::ConnectedCallback
& callback
) override
;
71 void SetRemoteCredentials(const std::string
& ufrag
,
72 const std::string
& password
) override
;
73 void AddRemoteCandidate(const cricket::Candidate
& candidate
) override
;
74 const std::string
& name() const override
;
75 bool is_connected() const override
;
76 void SetUseStandardIce(bool use_standard_ice
) override
;
80 void NotifyConnected();
82 // Signal handlers for cricket::TransportChannel.
83 void OnRequestSignaling(cricket::TransportChannelImpl
* channel
);
84 void OnCandidateReady(cricket::TransportChannelImpl
* channel
,
85 const cricket::Candidate
& candidate
);
86 void OnRouteChange(cricket::TransportChannel
* channel
,
87 const cricket::Candidate
& candidate
);
88 void OnWritableState(cricket::TransportChannel
* channel
);
90 // Callback for TransportChannelSocketAdapter to notify when the socket is
92 void OnChannelDestroyed();
94 void NotifyRouteChanged();
96 // Tries to connect by restarting ICE. Called by |reconnect_timer_|.
99 cricket::PortAllocator
* port_allocator_
;
100 NetworkSettings network_settings_
;
103 bool use_standard_ice_
= true;
106 EventHandler
* event_handler_
;
107 Transport::ConnectedCallback callback_
;
108 std::string ice_username_fragment_
;
112 std::string remote_ice_username_fragment_
;
113 std::string remote_ice_password_
;
114 std::list
<cricket::Candidate
> pending_candidates_
;
115 scoped_ptr
<cricket::P2PTransportChannel
> channel_
;
116 int connect_attempts_left_
;
117 base::RepeatingTimer
<LibjingleTransport
> reconnect_timer_
;
119 base::WeakPtrFactory
<LibjingleTransport
> weak_factory_
;
121 DISALLOW_COPY_AND_ASSIGN(LibjingleTransport
);
124 LibjingleTransport::LibjingleTransport(cricket::PortAllocator
* port_allocator
,
125 const NetworkSettings
& network_settings
,
127 : port_allocator_(port_allocator
),
128 network_settings_(network_settings
),
130 event_handler_(nullptr),
131 ice_username_fragment_(
132 rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH
)),
134 connect_attempts_left_(kMaxReconnectAttempts
),
135 weak_factory_(this) {
136 DCHECK(!ice_username_fragment_
.empty());
139 LibjingleTransport::~LibjingleTransport() {
140 DCHECK(event_handler_
);
142 event_handler_
->OnTransportDeleted(this);
144 if (channel_
.get()) {
145 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
146 FROM_HERE
, channel_
.release());
150 void LibjingleTransport::OnCanStart() {
151 DCHECK(CalledOnValidThread());
156 // If Connect() has been called then start connection.
157 if (!callback_
.is_null())
160 // Pass pending ICE credentials and candidates to the channel.
161 if (!remote_ice_username_fragment_
.empty()) {
162 channel_
->SetRemoteIceCredentials(remote_ice_username_fragment_
,
163 remote_ice_password_
);
166 while (!pending_candidates_
.empty()) {
167 if (!use_standard_ice_
) {
168 channel_
->SetRemoteIceCredentials(pending_candidates_
.front().username(),
169 pending_candidates_
.front().password());
171 channel_
->OnCandidate(pending_candidates_
.front());
172 pending_candidates_
.pop_front();
176 void LibjingleTransport::Connect(
177 const std::string
& name
,
178 Transport::EventHandler
* event_handler
,
179 const Transport::ConnectedCallback
& callback
) {
180 DCHECK(CalledOnValidThread());
181 DCHECK(!name
.empty());
182 DCHECK(event_handler
);
183 DCHECK(!callback
.is_null());
185 DCHECK(name_
.empty());
187 event_handler_
= event_handler
;
188 callback_
= callback
;
194 void LibjingleTransport::DoStart() {
195 DCHECK(!channel_
.get());
197 // Create P2PTransportChannel, attach signal handlers and connect it.
198 // TODO(sergeyu): Specify correct component ID for the channel.
199 channel_
.reset(new cricket::P2PTransportChannel(
200 std::string(), 0, nullptr, port_allocator_
));
201 std::string ice_password
= rtc::CreateRandomString(cricket::ICE_PWD_LENGTH
);
202 if (use_standard_ice_
) {
203 channel_
->SetIceProtocolType(cricket::ICEPROTO_RFC5245
);
204 channel_
->SetIceRole((role_
== TransportRole::CLIENT
)
205 ? cricket::ICEROLE_CONTROLLING
206 : cricket::ICEROLE_CONTROLLED
);
207 event_handler_
->OnTransportIceCredentials(this, ice_username_fragment_
,
210 channel_
->SetIceProtocolType(cricket::ICEPROTO_GOOGLE
);
212 channel_
->SetIceCredentials(ice_username_fragment_
, ice_password
);
213 channel_
->SignalRequestSignaling
.connect(
214 this, &LibjingleTransport::OnRequestSignaling
);
215 channel_
->SignalCandidateReady
.connect(
216 this, &LibjingleTransport::OnCandidateReady
);
217 channel_
->SignalRouteChange
.connect(
218 this, &LibjingleTransport::OnRouteChange
);
219 channel_
->SignalWritableState
.connect(
220 this, &LibjingleTransport::OnWritableState
);
221 channel_
->set_incoming_only(
222 !(network_settings_
.flags
& NetworkSettings::NAT_TRAVERSAL_OUTGOING
));
226 --connect_attempts_left_
;
228 // Start reconnection timer.
229 reconnect_timer_
.Start(
230 FROM_HERE
, base::TimeDelta::FromSeconds(kReconnectDelaySeconds
),
231 this, &LibjingleTransport::TryReconnect
);
233 base::ThreadTaskRunnerHandle::Get()->PostTask(
234 FROM_HERE
, base::Bind(&LibjingleTransport::NotifyConnected
,
235 weak_factory_
.GetWeakPtr()));
238 void LibjingleTransport::NotifyConnected() {
239 // Create P2PDatagramSocket adapter for the P2PTransportChannel.
240 scoped_ptr
<TransportChannelSocketAdapter
> socket(
241 new TransportChannelSocketAdapter(channel_
.get()));
242 socket
->SetOnDestroyedCallback(base::Bind(
243 &LibjingleTransport::OnChannelDestroyed
, base::Unretained(this)));
244 base::ResetAndReturn(&callback_
).Run(socket
.Pass());
247 void LibjingleTransport::SetRemoteCredentials(const std::string
& ufrag
,
248 const std::string
& password
) {
249 DCHECK(CalledOnValidThread());
251 remote_ice_username_fragment_
= ufrag
;
252 remote_ice_password_
= password
;
255 channel_
->SetRemoteIceCredentials(ufrag
, password
);
258 void LibjingleTransport::AddRemoteCandidate(
259 const cricket::Candidate
& candidate
) {
260 DCHECK(CalledOnValidThread());
262 // To enforce the no-relay setting, it's not enough to not produce relay
263 // candidates. It's also necessary to discard remote relay candidates.
264 bool relay_allowed
= (network_settings_
.flags
&
265 NetworkSettings::NAT_TRAVERSAL_RELAY
) != 0;
266 if (!relay_allowed
&& candidate
.type() == cricket::RELAY_PORT_TYPE
)
270 if (!use_standard_ice_
) {
271 channel_
->SetRemoteIceCredentials(candidate
.username(),
272 candidate
.password());
274 channel_
->OnCandidate(candidate
);
276 pending_candidates_
.push_back(candidate
);
280 const std::string
& LibjingleTransport::name() const {
281 DCHECK(CalledOnValidThread());
285 bool LibjingleTransport::is_connected() const {
286 DCHECK(CalledOnValidThread());
287 return callback_
.is_null();
290 void LibjingleTransport::SetUseStandardIce(bool use_standard_ice
) {
291 DCHECK(CalledOnValidThread());
293 use_standard_ice_
= use_standard_ice
;
296 void LibjingleTransport::OnRequestSignaling(
297 cricket::TransportChannelImpl
* channel
) {
298 DCHECK(CalledOnValidThread());
299 channel_
->OnSignalingReady();
302 void LibjingleTransport::OnCandidateReady(
303 cricket::TransportChannelImpl
* channel
,
304 const cricket::Candidate
& candidate
) {
305 DCHECK(CalledOnValidThread());
306 event_handler_
->OnTransportCandidate(this, candidate
);
309 void LibjingleTransport::OnRouteChange(
310 cricket::TransportChannel
* channel
,
311 const cricket::Candidate
& candidate
) {
312 // Ignore notifications if the channel is not writable.
313 if (channel_
->writable())
314 NotifyRouteChanged();
317 void LibjingleTransport::OnWritableState(
318 cricket::TransportChannel
* channel
) {
319 DCHECK_EQ(channel
, channel_
.get());
321 if (channel
->writable()) {
322 connect_attempts_left_
= kMaxReconnectAttempts
;
323 reconnect_timer_
.Stop();
325 // Route change notifications are ignored when the |channel_| is not
326 // writable. Notify the event handler about the current route once the
327 // channel is writable.
328 NotifyRouteChanged();
330 reconnect_timer_
.Reset();
335 void LibjingleTransport::OnChannelDestroyed() {
336 // The connection socket is being deleted, so delete the transport too.
340 void LibjingleTransport::NotifyRouteChanged() {
341 TransportRoute route
;
343 DCHECK(channel_
->best_connection());
344 const cricket::Connection
* connection
= channel_
->best_connection();
346 // A connection has both a local and a remote candidate. For our purposes, the
347 // route type is determined by the most indirect candidate type. For example:
348 // it's possible for the local candidate be a "relay" type, while the remote
349 // candidate is "local". In this case, we still want to report a RELAY route
351 static_assert(TransportRoute::DIRECT
< TransportRoute::STUN
&&
352 TransportRoute::STUN
< TransportRoute::RELAY
,
353 "Route type enum values are ordered by 'indirectness'");
354 route
.type
= std::max(
355 CandidateTypeToTransportRouteType(connection
->local_candidate().type()),
356 CandidateTypeToTransportRouteType(connection
->remote_candidate().type()));
358 if (!jingle_glue::SocketAddressToIPEndPoint(
359 connection
->remote_candidate().address(), &route
.remote_address
)) {
360 LOG(FATAL
) << "Failed to convert peer IP address.";
363 const cricket::Candidate
& local_candidate
=
364 channel_
->best_connection()->local_candidate();
365 if (!jingle_glue::SocketAddressToIPEndPoint(
366 local_candidate
.address(), &route
.local_address
)) {
367 LOG(FATAL
) << "Failed to convert local IP address.";
370 event_handler_
->OnTransportRouteChange(this, route
);
373 void LibjingleTransport::TryReconnect() {
374 DCHECK(!channel_
->writable());
376 if (connect_attempts_left_
<= 0) {
377 reconnect_timer_
.Stop();
379 // Notify the caller that ICE connection has failed - normally that will
380 // terminate Jingle connection (i.e. the transport will be destroyed).
381 event_handler_
->OnTransportFailed(this);
384 --connect_attempts_left_
;
386 // Restart ICE by resetting ICE password.
387 std::string ice_password
= rtc::CreateRandomString(cricket::ICE_PWD_LENGTH
);
388 event_handler_
->OnTransportIceCredentials(this, ice_username_fragment_
,
390 channel_
->SetIceCredentials(ice_username_fragment_
, ice_password
);
395 LibjingleTransportFactory::LibjingleTransportFactory(
396 SignalStrategy
* signal_strategy
,
397 scoped_ptr
<cricket::HttpPortAllocatorBase
> port_allocator
,
398 const NetworkSettings
& network_settings
,
400 : signal_strategy_(signal_strategy
),
401 port_allocator_(port_allocator
.Pass()),
402 network_settings_(network_settings
),
406 LibjingleTransportFactory::~LibjingleTransportFactory() {
407 // This method may be called in response to a libjingle signal, so
408 // libjingle objects must be deleted asynchronously.
409 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
=
410 base::ThreadTaskRunnerHandle::Get();
411 task_runner
->DeleteSoon(FROM_HERE
, port_allocator_
.release());
414 void LibjingleTransportFactory::PrepareTokens() {
415 EnsureFreshJingleInfo();
418 scoped_ptr
<Transport
> LibjingleTransportFactory::CreateTransport() {
419 scoped_ptr
<LibjingleTransport
> result(
420 new LibjingleTransport(port_allocator_
.get(), network_settings_
, role_
));
422 EnsureFreshJingleInfo();
424 // If there is a pending |jingle_info_request_| delay starting the new
425 // transport until the request is finished.
426 if (jingle_info_request_
) {
427 on_jingle_info_callbacks_
.push_back(
428 base::Bind(&LibjingleTransport::OnCanStart
,
429 result
->AsWeakPtr()));
431 result
->OnCanStart();
434 return result
.Pass();
437 void LibjingleTransportFactory::EnsureFreshJingleInfo() {
438 uint32 stun_or_relay_flags
= NetworkSettings::NAT_TRAVERSAL_STUN
|
439 NetworkSettings::NAT_TRAVERSAL_RELAY
;
440 if (!(network_settings_
.flags
& stun_or_relay_flags
) ||
441 jingle_info_request_
) {
445 if (base::TimeTicks::Now() - last_jingle_info_update_time_
>
446 base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds
)) {
447 jingle_info_request_
.reset(new JingleInfoRequest(signal_strategy_
));
448 jingle_info_request_
->Send(base::Bind(
449 &LibjingleTransportFactory::OnJingleInfo
, base::Unretained(this)));
453 void LibjingleTransportFactory::OnJingleInfo(
454 const std::string
& relay_token
,
455 const std::vector
<std::string
>& relay_hosts
,
456 const std::vector
<rtc::SocketAddress
>& stun_hosts
) {
457 if (!relay_token
.empty() && !relay_hosts
.empty()) {
458 port_allocator_
->SetRelayHosts(relay_hosts
);
459 port_allocator_
->SetRelayToken(relay_token
);
461 if (!stun_hosts
.empty()) {
462 port_allocator_
->SetStunHosts(stun_hosts
);
465 jingle_info_request_
.reset();
466 if ((!relay_token
.empty() && !relay_hosts
.empty()) || !stun_hosts
.empty())
467 last_jingle_info_update_time_
= base::TimeTicks::Now();
469 while (!on_jingle_info_callbacks_
.empty()) {
470 on_jingle_info_callbacks_
.begin()->Run();
471 on_jingle_info_callbacks_
.pop_front();
475 } // namespace protocol
476 } // namespace remoting