Upstreaming browser/ui/uikit_ui_util from iOS.
[chromium-blink-merge.git] / remoting / protocol / libjingle_transport_factory.cc
blob86537379d8e4cc4c97889f372863d975aaea08bd
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"
7 #include <algorithm>
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"
26 namespace remoting {
27 namespace protocol {
29 namespace {
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;
48 } else {
49 LOG(FATAL) << "Unknown candidate type: " << candidate_type;
50 return TransportRoute::DIRECT;
54 class LibjingleTransport
55 : public Transport,
56 public base::SupportsWeakPtr<LibjingleTransport>,
57 public sigslot::has_slots<> {
58 public:
59 LibjingleTransport(cricket::PortAllocator* port_allocator,
60 const NetworkSettings& network_settings,
61 TransportRole role);
62 ~LibjingleTransport() override;
64 // Called by JingleTransportFactory when it has fresh Jingle info.
65 void OnCanStart();
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;
77 private:
78 void DoStart();
79 void NotifyConnected();
81 // Signal handlers for cricket::TransportChannel.
82 void OnRequestSignaling(cricket::TransportChannelImpl* channel);
83 void OnCandidateReady(cricket::TransportChannelImpl* channel,
84 const cricket::Candidate& candidate);
85 void OnRouteChange(cricket::TransportChannel* channel,
86 const cricket::Candidate& candidate);
87 void OnWritableState(cricket::TransportChannel* channel);
89 // Callback for TransportChannelSocketAdapter to notify when the socket is
90 // destroyed.
91 void OnChannelDestroyed();
93 void NotifyRouteChanged();
95 // Tries to connect by restarting ICE. Called by |reconnect_timer_|.
96 void TryReconnect();
98 cricket::PortAllocator* port_allocator_;
99 NetworkSettings network_settings_;
100 TransportRole role_;
102 std::string name_;
103 EventHandler* event_handler_;
104 Transport::ConnectedCallback callback_;
105 std::string ice_username_fragment_;
107 bool can_start_;
109 std::string remote_ice_username_fragment_;
110 std::string remote_ice_password_;
111 std::list<cricket::Candidate> pending_candidates_;
112 scoped_ptr<cricket::P2PTransportChannel> channel_;
113 int connect_attempts_left_;
114 base::RepeatingTimer<LibjingleTransport> reconnect_timer_;
116 base::WeakPtrFactory<LibjingleTransport> weak_factory_;
118 DISALLOW_COPY_AND_ASSIGN(LibjingleTransport);
121 LibjingleTransport::LibjingleTransport(cricket::PortAllocator* port_allocator,
122 const NetworkSettings& network_settings,
123 TransportRole role)
124 : port_allocator_(port_allocator),
125 network_settings_(network_settings),
126 role_(role),
127 event_handler_(nullptr),
128 ice_username_fragment_(
129 rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH)),
130 can_start_(false),
131 connect_attempts_left_(kMaxReconnectAttempts),
132 weak_factory_(this) {
133 DCHECK(!ice_username_fragment_.empty());
136 LibjingleTransport::~LibjingleTransport() {
137 DCHECK(event_handler_);
139 event_handler_->OnTransportDeleted(this);
141 if (channel_.get()) {
142 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
143 FROM_HERE, channel_.release());
147 void LibjingleTransport::OnCanStart() {
148 DCHECK(CalledOnValidThread());
150 DCHECK(!can_start_);
151 can_start_ = true;
153 // If Connect() has been called then start connection.
154 if (!callback_.is_null())
155 DoStart();
157 // Pass pending ICE credentials and candidates to the channel.
158 if (!remote_ice_username_fragment_.empty()) {
159 channel_->SetRemoteIceCredentials(remote_ice_username_fragment_,
160 remote_ice_password_);
163 while (!pending_candidates_.empty()) {
164 channel_->OnCandidate(pending_candidates_.front());
165 pending_candidates_.pop_front();
169 void LibjingleTransport::Connect(
170 const std::string& name,
171 Transport::EventHandler* event_handler,
172 const Transport::ConnectedCallback& callback) {
173 DCHECK(CalledOnValidThread());
174 DCHECK(!name.empty());
175 DCHECK(event_handler);
176 DCHECK(!callback.is_null());
178 DCHECK(name_.empty());
179 name_ = name;
180 event_handler_ = event_handler;
181 callback_ = callback;
183 if (can_start_)
184 DoStart();
187 void LibjingleTransport::DoStart() {
188 DCHECK(!channel_.get());
190 // Create P2PTransportChannel, attach signal handlers and connect it.
191 // TODO(sergeyu): Specify correct component ID for the channel.
192 channel_.reset(new cricket::P2PTransportChannel(
193 std::string(), 0, nullptr, port_allocator_));
194 std::string ice_password = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH);
195 channel_->SetIceProtocolType(cricket::ICEPROTO_RFC5245);
196 channel_->SetIceRole((role_ == TransportRole::CLIENT)
197 ? cricket::ICEROLE_CONTROLLING
198 : cricket::ICEROLE_CONTROLLED);
199 event_handler_->OnTransportIceCredentials(this, ice_username_fragment_,
200 ice_password);
201 channel_->SetIceCredentials(ice_username_fragment_, ice_password);
202 channel_->SignalRequestSignaling.connect(
203 this, &LibjingleTransport::OnRequestSignaling);
204 channel_->SignalCandidateReady.connect(
205 this, &LibjingleTransport::OnCandidateReady);
206 channel_->SignalRouteChange.connect(
207 this, &LibjingleTransport::OnRouteChange);
208 channel_->SignalWritableState.connect(
209 this, &LibjingleTransport::OnWritableState);
210 channel_->set_incoming_only(
211 !(network_settings_.flags & NetworkSettings::NAT_TRAVERSAL_OUTGOING));
213 channel_->Connect();
215 --connect_attempts_left_;
217 // Start reconnection timer.
218 reconnect_timer_.Start(
219 FROM_HERE, base::TimeDelta::FromSeconds(kReconnectDelaySeconds),
220 this, &LibjingleTransport::TryReconnect);
222 base::ThreadTaskRunnerHandle::Get()->PostTask(
223 FROM_HERE, base::Bind(&LibjingleTransport::NotifyConnected,
224 weak_factory_.GetWeakPtr()));
227 void LibjingleTransport::NotifyConnected() {
228 // Create P2PDatagramSocket adapter for the P2PTransportChannel.
229 scoped_ptr<TransportChannelSocketAdapter> socket(
230 new TransportChannelSocketAdapter(channel_.get()));
231 socket->SetOnDestroyedCallback(base::Bind(
232 &LibjingleTransport::OnChannelDestroyed, base::Unretained(this)));
233 base::ResetAndReturn(&callback_).Run(socket.Pass());
236 void LibjingleTransport::SetRemoteCredentials(const std::string& ufrag,
237 const std::string& password) {
238 DCHECK(CalledOnValidThread());
240 remote_ice_username_fragment_ = ufrag;
241 remote_ice_password_ = password;
243 if (channel_)
244 channel_->SetRemoteIceCredentials(ufrag, password);
247 void LibjingleTransport::AddRemoteCandidate(
248 const cricket::Candidate& candidate) {
249 DCHECK(CalledOnValidThread());
251 // To enforce the no-relay setting, it's not enough to not produce relay
252 // candidates. It's also necessary to discard remote relay candidates.
253 bool relay_allowed = (network_settings_.flags &
254 NetworkSettings::NAT_TRAVERSAL_RELAY) != 0;
255 if (!relay_allowed && candidate.type() == cricket::RELAY_PORT_TYPE)
256 return;
258 if (channel_) {
259 channel_->OnCandidate(candidate);
260 } else {
261 pending_candidates_.push_back(candidate);
265 const std::string& LibjingleTransport::name() const {
266 DCHECK(CalledOnValidThread());
267 return name_;
270 bool LibjingleTransport::is_connected() const {
271 DCHECK(CalledOnValidThread());
272 return callback_.is_null();
275 void LibjingleTransport::OnRequestSignaling(
276 cricket::TransportChannelImpl* channel) {
277 DCHECK(CalledOnValidThread());
278 channel_->OnSignalingReady();
281 void LibjingleTransport::OnCandidateReady(
282 cricket::TransportChannelImpl* channel,
283 const cricket::Candidate& candidate) {
284 DCHECK(CalledOnValidThread());
285 event_handler_->OnTransportCandidate(this, candidate);
288 void LibjingleTransport::OnRouteChange(
289 cricket::TransportChannel* channel,
290 const cricket::Candidate& candidate) {
291 // Ignore notifications if the channel is not writable.
292 if (channel_->writable())
293 NotifyRouteChanged();
296 void LibjingleTransport::OnWritableState(
297 cricket::TransportChannel* channel) {
298 DCHECK_EQ(channel, channel_.get());
300 if (channel->writable()) {
301 connect_attempts_left_ = kMaxReconnectAttempts;
302 reconnect_timer_.Stop();
304 // Route change notifications are ignored when the |channel_| is not
305 // writable. Notify the event handler about the current route once the
306 // channel is writable.
307 NotifyRouteChanged();
308 } else {
309 reconnect_timer_.Reset();
310 TryReconnect();
314 void LibjingleTransport::OnChannelDestroyed() {
315 // The connection socket is being deleted, so delete the transport too.
316 delete this;
319 void LibjingleTransport::NotifyRouteChanged() {
320 TransportRoute route;
322 DCHECK(channel_->best_connection());
323 const cricket::Connection* connection = channel_->best_connection();
325 // A connection has both a local and a remote candidate. For our purposes, the
326 // route type is determined by the most indirect candidate type. For example:
327 // it's possible for the local candidate be a "relay" type, while the remote
328 // candidate is "local". In this case, we still want to report a RELAY route
329 // type.
330 static_assert(TransportRoute::DIRECT < TransportRoute::STUN &&
331 TransportRoute::STUN < TransportRoute::RELAY,
332 "Route type enum values are ordered by 'indirectness'");
333 route.type = std::max(
334 CandidateTypeToTransportRouteType(connection->local_candidate().type()),
335 CandidateTypeToTransportRouteType(connection->remote_candidate().type()));
337 if (!jingle_glue::SocketAddressToIPEndPoint(
338 connection->remote_candidate().address(), &route.remote_address)) {
339 LOG(FATAL) << "Failed to convert peer IP address.";
342 const cricket::Candidate& local_candidate =
343 channel_->best_connection()->local_candidate();
344 if (!jingle_glue::SocketAddressToIPEndPoint(
345 local_candidate.address(), &route.local_address)) {
346 LOG(FATAL) << "Failed to convert local IP address.";
349 event_handler_->OnTransportRouteChange(this, route);
352 void LibjingleTransport::TryReconnect() {
353 DCHECK(!channel_->writable());
355 if (connect_attempts_left_ <= 0) {
356 reconnect_timer_.Stop();
358 // Notify the caller that ICE connection has failed - normally that will
359 // terminate Jingle connection (i.e. the transport will be destroyed).
360 event_handler_->OnTransportFailed(this);
361 return;
363 --connect_attempts_left_;
365 // Restart ICE by resetting ICE password.
366 std::string ice_password = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH);
367 event_handler_->OnTransportIceCredentials(this, ice_username_fragment_,
368 ice_password);
369 channel_->SetIceCredentials(ice_username_fragment_, ice_password);
372 } // namespace
374 LibjingleTransportFactory::LibjingleTransportFactory(
375 SignalStrategy* signal_strategy,
376 scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator,
377 const NetworkSettings& network_settings,
378 TransportRole role)
379 : signal_strategy_(signal_strategy),
380 port_allocator_(port_allocator.Pass()),
381 network_settings_(network_settings),
382 role_(role) {
385 LibjingleTransportFactory::~LibjingleTransportFactory() {
386 // This method may be called in response to a libjingle signal, so
387 // libjingle objects must be deleted asynchronously.
388 scoped_refptr<base::SingleThreadTaskRunner> task_runner =
389 base::ThreadTaskRunnerHandle::Get();
390 task_runner->DeleteSoon(FROM_HERE, port_allocator_.release());
393 void LibjingleTransportFactory::PrepareTokens() {
394 EnsureFreshJingleInfo();
397 scoped_ptr<Transport> LibjingleTransportFactory::CreateTransport() {
398 scoped_ptr<LibjingleTransport> result(
399 new LibjingleTransport(port_allocator_.get(), network_settings_, role_));
401 EnsureFreshJingleInfo();
403 // If there is a pending |jingle_info_request_| delay starting the new
404 // transport until the request is finished.
405 if (jingle_info_request_) {
406 on_jingle_info_callbacks_.push_back(
407 base::Bind(&LibjingleTransport::OnCanStart,
408 result->AsWeakPtr()));
409 } else {
410 result->OnCanStart();
413 return result.Pass();
416 void LibjingleTransportFactory::EnsureFreshJingleInfo() {
417 uint32 stun_or_relay_flags = NetworkSettings::NAT_TRAVERSAL_STUN |
418 NetworkSettings::NAT_TRAVERSAL_RELAY;
419 if (!(network_settings_.flags & stun_or_relay_flags) ||
420 jingle_info_request_) {
421 return;
424 if (base::TimeTicks::Now() - last_jingle_info_update_time_ >
425 base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds)) {
426 jingle_info_request_.reset(new JingleInfoRequest(signal_strategy_));
427 jingle_info_request_->Send(base::Bind(
428 &LibjingleTransportFactory::OnJingleInfo, base::Unretained(this)));
432 void LibjingleTransportFactory::OnJingleInfo(
433 const std::string& relay_token,
434 const std::vector<std::string>& relay_hosts,
435 const std::vector<rtc::SocketAddress>& stun_hosts) {
436 if (!relay_token.empty() && !relay_hosts.empty()) {
437 port_allocator_->SetRelayHosts(relay_hosts);
438 port_allocator_->SetRelayToken(relay_token);
440 if (!stun_hosts.empty()) {
441 port_allocator_->SetStunHosts(stun_hosts);
444 jingle_info_request_.reset();
445 if ((!relay_token.empty() && !relay_hosts.empty()) || !stun_hosts.empty())
446 last_jingle_info_update_time_ = base::TimeTicks::Now();
448 while (!on_jingle_info_callbacks_.empty()) {
449 on_jingle_info_callbacks_.begin()->Run();
450 on_jingle_info_callbacks_.pop_front();
454 } // namespace protocol
455 } // namespace remoting