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/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/timer/timer.h"
13 #include "jingle/glue/channel_socket_adapter.h"
14 #include "jingle/glue/utils.h"
15 #include "net/base/net_errors.h"
16 #include "remoting/protocol/network_settings.h"
17 #include "remoting/signaling/jingle_info_request.h"
18 #include "third_party/webrtc/base/network.h"
19 #include "third_party/webrtc/p2p/base/constants.h"
20 #include "third_party/webrtc/p2p/base/p2ptransportchannel.h"
21 #include "third_party/webrtc/p2p/base/port.h"
22 #include "third_party/webrtc/p2p/client/basicportallocator.h"
23 #include "third_party/webrtc/p2p/client/httpportallocator.h"
30 // Try connecting ICE twice with timeout of 15 seconds for each attempt.
31 const int kMaxReconnectAttempts
= 2;
32 const int kReconnectDelaySeconds
= 15;
34 // Get fresh STUN/Relay configuration every hour.
35 const int kJingleInfoUpdatePeriodSeconds
= 3600;
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") {
44 return TransportRoute::STUN
;
45 } else if (candidate_type
== "relay") {
46 return TransportRoute::RELAY
;
48 LOG(FATAL
) << "Unknown candidate type: " << candidate_type
;
49 return TransportRoute::DIRECT
;
53 class LibjingleTransport
55 public base::SupportsWeakPtr
<LibjingleTransport
>,
56 public sigslot::has_slots
<> {
58 LibjingleTransport(cricket::PortAllocator
* port_allocator
,
59 const NetworkSettings
& network_settings
);
60 ~LibjingleTransport() override
;
62 // Called by JingleTransportFactory when it has fresh Jingle info.
65 // Transport interface.
66 void Connect(const std::string
& name
,
67 Transport::EventHandler
* event_handler
,
68 const Transport::ConnectedCallback
& callback
) override
;
69 void AddRemoteCandidate(const cricket::Candidate
& candidate
) override
;
70 const std::string
& name() const override
;
71 bool is_connected() const override
;
75 void NotifyConnected();
77 // Signal handlers for cricket::TransportChannel.
78 void OnRequestSignaling(cricket::TransportChannelImpl
* channel
);
79 void OnCandidateReady(cricket::TransportChannelImpl
* channel
,
80 const cricket::Candidate
& candidate
);
81 void OnRouteChange(cricket::TransportChannel
* channel
,
82 const cricket::Candidate
& candidate
);
83 void OnWritableState(cricket::TransportChannel
* channel
);
85 // Callback for jingle_glue::TransportChannelSocketAdapter to notify when the
86 // socket is destroyed.
87 void OnChannelDestroyed();
89 void NotifyRouteChanged();
91 // Tries to connect by restarting ICE. Called by |reconnect_timer_|.
94 cricket::PortAllocator
* port_allocator_
;
95 NetworkSettings network_settings_
;
98 EventHandler
* event_handler_
;
99 Transport::ConnectedCallback callback_
;
100 std::string ice_username_fragment_
;
101 std::string ice_password_
;
105 std::list
<cricket::Candidate
> pending_candidates_
;
106 scoped_ptr
<cricket::P2PTransportChannel
> channel_
;
107 bool channel_was_writable_
;
108 int connect_attempts_left_
;
109 base::RepeatingTimer
<LibjingleTransport
> reconnect_timer_
;
111 base::WeakPtrFactory
<LibjingleTransport
> weak_factory_
;
113 DISALLOW_COPY_AND_ASSIGN(LibjingleTransport
);
116 LibjingleTransport::LibjingleTransport(cricket::PortAllocator
* port_allocator
,
117 const NetworkSettings
& network_settings
)
118 : port_allocator_(port_allocator
),
119 network_settings_(network_settings
),
120 event_handler_(nullptr),
121 ice_username_fragment_(
122 rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH
)),
123 ice_password_(rtc::CreateRandomString(cricket::ICE_PWD_LENGTH
)),
125 channel_was_writable_(false),
126 connect_attempts_left_(kMaxReconnectAttempts
),
127 weak_factory_(this) {
128 DCHECK(!ice_username_fragment_
.empty());
129 DCHECK(!ice_password_
.empty());
132 LibjingleTransport::~LibjingleTransport() {
133 DCHECK(event_handler_
);
135 event_handler_
->OnTransportDeleted(this);
137 if (channel_
.get()) {
138 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
139 FROM_HERE
, channel_
.release());
143 void LibjingleTransport::OnCanStart() {
144 DCHECK(CalledOnValidThread());
149 // If Connect() has been called then start connection.
150 if (!callback_
.is_null())
153 while (!pending_candidates_
.empty()) {
154 channel_
->OnCandidate(pending_candidates_
.front());
155 pending_candidates_
.pop_front();
159 void LibjingleTransport::Connect(
160 const std::string
& name
,
161 Transport::EventHandler
* event_handler
,
162 const Transport::ConnectedCallback
& callback
) {
163 DCHECK(CalledOnValidThread());
164 DCHECK(!name
.empty());
165 DCHECK(event_handler
);
166 DCHECK(!callback
.is_null());
168 DCHECK(name_
.empty());
170 event_handler_
= event_handler
;
171 callback_
= callback
;
177 void LibjingleTransport::DoStart() {
178 DCHECK(!channel_
.get());
180 // Create P2PTransportChannel, attach signal handlers and connect it.
181 // TODO(sergeyu): Specify correct component ID for the channel.
182 channel_
.reset(new cricket::P2PTransportChannel(
183 std::string(), 0, nullptr, port_allocator_
));
184 channel_
->SetIceProtocolType(cricket::ICEPROTO_GOOGLE
);
185 channel_
->SetIceCredentials(ice_username_fragment_
, ice_password_
);
186 channel_
->SignalRequestSignaling
.connect(
187 this, &LibjingleTransport::OnRequestSignaling
);
188 channel_
->SignalCandidateReady
.connect(
189 this, &LibjingleTransport::OnCandidateReady
);
190 channel_
->SignalRouteChange
.connect(
191 this, &LibjingleTransport::OnRouteChange
);
192 channel_
->SignalWritableState
.connect(
193 this, &LibjingleTransport::OnWritableState
);
194 channel_
->set_incoming_only(
195 !(network_settings_
.flags
& NetworkSettings::NAT_TRAVERSAL_OUTGOING
));
199 --connect_attempts_left_
;
201 // Start reconnection timer.
202 reconnect_timer_
.Start(
203 FROM_HERE
, base::TimeDelta::FromSeconds(kReconnectDelaySeconds
),
204 this, &LibjingleTransport::TryReconnect
);
207 void LibjingleTransport::NotifyConnected() {
208 // Create net::Socket adapter for the P2PTransportChannel.
209 scoped_ptr
<jingle_glue::TransportChannelSocketAdapter
> socket(
210 new jingle_glue::TransportChannelSocketAdapter(channel_
.get()));
211 socket
->SetOnDestroyedCallback(base::Bind(
212 &LibjingleTransport::OnChannelDestroyed
, base::Unretained(this)));
214 Transport::ConnectedCallback callback
= callback_
;
216 callback
.Run(socket
.Pass());
219 void LibjingleTransport::AddRemoteCandidate(
220 const cricket::Candidate
& candidate
) {
221 DCHECK(CalledOnValidThread());
223 // To enforce the no-relay setting, it's not enough to not produce relay
224 // candidates. It's also necessary to discard remote relay candidates.
225 bool relay_allowed
= (network_settings_
.flags
&
226 NetworkSettings::NAT_TRAVERSAL_RELAY
) != 0;
227 if (!relay_allowed
&& candidate
.type() == cricket::RELAY_PORT_TYPE
)
231 channel_
->OnCandidate(candidate
);
233 pending_candidates_
.push_back(candidate
);
237 const std::string
& LibjingleTransport::name() const {
238 DCHECK(CalledOnValidThread());
242 bool LibjingleTransport::is_connected() const {
243 DCHECK(CalledOnValidThread());
244 return callback_
.is_null();
247 void LibjingleTransport::OnRequestSignaling(
248 cricket::TransportChannelImpl
* channel
) {
249 DCHECK(CalledOnValidThread());
250 channel_
->OnSignalingReady();
253 void LibjingleTransport::OnCandidateReady(
254 cricket::TransportChannelImpl
* channel
,
255 const cricket::Candidate
& candidate
) {
256 DCHECK(CalledOnValidThread());
257 event_handler_
->OnTransportCandidate(this, candidate
);
260 void LibjingleTransport::OnRouteChange(
261 cricket::TransportChannel
* channel
,
262 const cricket::Candidate
& candidate
) {
263 // Ignore notifications if the channel is not writable.
264 if (channel_
->writable())
265 NotifyRouteChanged();
268 void LibjingleTransport::OnWritableState(
269 cricket::TransportChannel
* channel
) {
270 DCHECK_EQ(channel
, channel_
.get());
272 if (channel
->writable()) {
273 if (!channel_was_writable_
) {
274 channel_was_writable_
= true;
275 base::ThreadTaskRunnerHandle::Get()->PostTask(
277 base::Bind(&LibjingleTransport::NotifyConnected
,
278 weak_factory_
.GetWeakPtr()));
280 connect_attempts_left_
= kMaxReconnectAttempts
;
281 reconnect_timer_
.Stop();
283 // Route change notifications are ignored when the |channel_| is not
284 // writable. Notify the event handler about the current route once the
285 // channel is writable.
286 NotifyRouteChanged();
287 } else if (!channel
->writable() && channel_was_writable_
) {
288 reconnect_timer_
.Reset();
293 void LibjingleTransport::OnChannelDestroyed() {
294 if (is_connected()) {
295 // The connection socket is being deleted, so delete the transport too.
300 void LibjingleTransport::NotifyRouteChanged() {
301 TransportRoute route
;
303 DCHECK(channel_
->best_connection());
304 const cricket::Connection
* connection
= channel_
->best_connection();
306 // A connection has both a local and a remote candidate. For our purposes, the
307 // route type is determined by the most indirect candidate type. For example:
308 // it's possible for the local candidate be a "relay" type, while the remote
309 // candidate is "local". In this case, we still want to report a RELAY route
311 static_assert(TransportRoute::DIRECT
< TransportRoute::STUN
&&
312 TransportRoute::STUN
< TransportRoute::RELAY
,
313 "Route type enum values are ordered by 'indirectness'");
314 route
.type
= std::max(
315 CandidateTypeToTransportRouteType(connection
->local_candidate().type()),
316 CandidateTypeToTransportRouteType(connection
->remote_candidate().type()));
318 if (!jingle_glue::SocketAddressToIPEndPoint(
319 connection
->remote_candidate().address(), &route
.remote_address
)) {
320 LOG(FATAL
) << "Failed to convert peer IP address.";
323 const cricket::Candidate
& local_candidate
=
324 channel_
->best_connection()->local_candidate();
325 if (!jingle_glue::SocketAddressToIPEndPoint(
326 local_candidate
.address(), &route
.local_address
)) {
327 LOG(FATAL
) << "Failed to convert local IP address.";
330 event_handler_
->OnTransportRouteChange(this, route
);
333 void LibjingleTransport::TryReconnect() {
334 DCHECK(!channel_
->writable());
336 if (connect_attempts_left_
<= 0) {
337 reconnect_timer_
.Stop();
339 // Notify the caller that ICE connection has failed - normally that will
340 // terminate Jingle connection (i.e. the transport will be destroyed).
341 event_handler_
->OnTransportFailed(this);
344 --connect_attempts_left_
;
346 // Restart ICE by resetting ICE password.
347 ice_password_
= rtc::CreateRandomString(cricket::ICE_PWD_LENGTH
);
348 channel_
->SetIceCredentials(ice_username_fragment_
, ice_password_
);
353 LibjingleTransportFactory::LibjingleTransportFactory(
354 SignalStrategy
* signal_strategy
,
355 scoped_ptr
<cricket::HttpPortAllocatorBase
> port_allocator
,
356 const NetworkSettings
& network_settings
)
357 : signal_strategy_(signal_strategy
),
358 port_allocator_(port_allocator
.Pass()),
359 network_settings_(network_settings
) {
362 LibjingleTransportFactory::~LibjingleTransportFactory() {
363 // This method may be called in response to a libjingle signal, so
364 // libjingle objects must be deleted asynchronously.
365 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
=
366 base::ThreadTaskRunnerHandle::Get();
367 task_runner
->DeleteSoon(FROM_HERE
, port_allocator_
.release());
370 void LibjingleTransportFactory::PrepareTokens() {
371 EnsureFreshJingleInfo();
374 scoped_ptr
<Transport
> LibjingleTransportFactory::CreateTransport() {
375 scoped_ptr
<LibjingleTransport
> result(
376 new LibjingleTransport(port_allocator_
.get(), network_settings_
));
378 EnsureFreshJingleInfo();
380 // If there is a pending |jingle_info_request_| delay starting the new
381 // transport until the request is finished.
382 if (jingle_info_request_
) {
383 on_jingle_info_callbacks_
.push_back(
384 base::Bind(&LibjingleTransport::OnCanStart
,
385 result
->AsWeakPtr()));
387 result
->OnCanStart();
390 return result
.Pass();
393 void LibjingleTransportFactory::EnsureFreshJingleInfo() {
394 uint32 stun_or_relay_flags
= NetworkSettings::NAT_TRAVERSAL_STUN
|
395 NetworkSettings::NAT_TRAVERSAL_RELAY
;
396 if (!(network_settings_
.flags
& stun_or_relay_flags
) ||
397 jingle_info_request_
) {
401 if (base::TimeTicks::Now() - last_jingle_info_update_time_
>
402 base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds
)) {
403 jingle_info_request_
.reset(new JingleInfoRequest(signal_strategy_
));
404 jingle_info_request_
->Send(base::Bind(
405 &LibjingleTransportFactory::OnJingleInfo
, base::Unretained(this)));
409 void LibjingleTransportFactory::OnJingleInfo(
410 const std::string
& relay_token
,
411 const std::vector
<std::string
>& relay_hosts
,
412 const std::vector
<rtc::SocketAddress
>& stun_hosts
) {
413 if (!relay_token
.empty() && !relay_hosts
.empty()) {
414 port_allocator_
->SetRelayHosts(relay_hosts
);
415 port_allocator_
->SetRelayToken(relay_token
);
417 if (!stun_hosts
.empty()) {
418 port_allocator_
->SetStunHosts(stun_hosts
);
421 jingle_info_request_
.reset();
422 if ((!relay_token
.empty() && !relay_hosts
.empty()) || !stun_hosts
.empty())
423 last_jingle_info_update_time_
= base::TimeTicks::Now();
425 while (!on_jingle_info_callbacks_
.empty()) {
426 on_jingle_info_callbacks_
.begin()->Run();
427 on_jingle_info_callbacks_
.pop_front();
431 } // namespace protocol
432 } // namespace remoting