• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/callback.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "base/timer/timer.h"
11 #include "jingle/glue/channel_socket_adapter.h"
12 #include "jingle/glue/pseudotcp_adapter.h"
13 #include "jingle/glue/utils.h"
14 #include "net/base/net_errors.h"
15 #include "remoting/base/constants.h"
16 #include "remoting/jingle_glue/jingle_info_request.h"
17 #include "remoting/jingle_glue/network_settings.h"
18 #include "remoting/protocol/channel_authenticator.h"
19 #include "third_party/libjingle/source/talk/base/network.h"
20 #include "third_party/libjingle/source/talk/p2p/base/constants.h"
21 #include "third_party/libjingle/source/talk/p2p/base/p2ptransportchannel.h"
22 #include "third_party/libjingle/source/talk/p2p/client/basicportallocator.h"
23 #include "third_party/libjingle/source/talk/p2p/client/httpportallocator.h"
24 
25 namespace remoting {
26 namespace protocol {
27 
28 namespace {
29 
30 // Value is chosen to balance the extra latency against the reduced
31 // load due to ACK traffic.
32 const int kTcpAckDelayMilliseconds = 10;
33 
34 // Values for the TCP send and receive buffer size. This should be tuned to
35 // accommodate high latency network but not backlog the decoding pipeline.
36 const int kTcpReceiveBufferSize = 256 * 1024;
37 const int kTcpSendBufferSize = kTcpReceiveBufferSize + 30 * 1024;
38 
39 // Try connecting ICE twice with timeout of 15 seconds for each attempt.
40 const int kMaxReconnectAttempts = 2;
41 const int kReconnectDelaySeconds = 15;
42 
43 // Get fresh STUN/Relay configuration every hour.
44 const int kJingleInfoUpdatePeriodSeconds = 3600;
45 
46 class LibjingleStreamTransport
47     : public StreamTransport,
48       public base::SupportsWeakPtr<LibjingleStreamTransport>,
49       public sigslot::has_slots<> {
50  public:
51   LibjingleStreamTransport(cricket::PortAllocator* port_allocator,
52                            const NetworkSettings& network_settings);
53   virtual ~LibjingleStreamTransport();
54 
55   // Called by JingleTransportFactory when it has fresh Jingle info.
56   void OnCanStart();
57 
58   // StreamTransport interface.
59   virtual void Initialize(
60       const std::string& name,
61       Transport::EventHandler* event_handler,
62       scoped_ptr<ChannelAuthenticator> authenticator) OVERRIDE;
63   virtual void Connect(
64       const StreamTransport::ConnectedCallback& callback) OVERRIDE;
65   virtual void AddRemoteCandidate(const cricket::Candidate& candidate) OVERRIDE;
66   virtual const std::string& name() const OVERRIDE;
67   virtual bool is_connected() const OVERRIDE;
68 
69  private:
70   void DoStart();
71 
72   // Signal handlers for cricket::TransportChannel.
73   void OnRequestSignaling(cricket::TransportChannelImpl* channel);
74   void OnCandidateReady(cricket::TransportChannelImpl* channel,
75                         const cricket::Candidate& candidate);
76   void OnRouteChange(cricket::TransportChannel* channel,
77                      const cricket::Candidate& candidate);
78   void OnWritableState(cricket::TransportChannel* channel);
79 
80   // Callback for PseudoTcpAdapter::Connect().
81   void OnTcpConnected(int result);
82 
83   // Callback for Authenticator::SecureAndAuthenticate();
84   void OnAuthenticationDone(net::Error error,
85                             scoped_ptr<net::StreamSocket> socket);
86 
87   // Callback for jingle_glue::TransportChannelSocketAdapter to notify when the
88   // socket is destroyed.
89   void OnChannelDestroyed();
90 
91   // Tries to connect by restarting ICE. Called by |reconnect_timer_|.
92   void TryReconnect();
93 
94   // Helper methods to call |callback_|.
95   void NotifyConnected(scoped_ptr<net::StreamSocket> socket);
96   void NotifyConnectFailed();
97 
98   cricket::PortAllocator* port_allocator_;
99   NetworkSettings network_settings_;
100 
101   std::string name_;
102   EventHandler* event_handler_;
103   StreamTransport::ConnectedCallback callback_;
104   scoped_ptr<ChannelAuthenticator> authenticator_;
105   std::string ice_username_fragment_;
106   std::string ice_password_;
107 
108   bool can_start_;
109 
110   std::list<cricket::Candidate> pending_candidates_;
111   scoped_ptr<cricket::P2PTransportChannel> channel_;
112   bool channel_was_writable_;
113   int connect_attempts_left_;
114   base::RepeatingTimer<LibjingleStreamTransport> reconnect_timer_;
115 
116   // We own |socket_| until it is connected.
117   scoped_ptr<jingle_glue::PseudoTcpAdapter> socket_;
118 
119   DISALLOW_COPY_AND_ASSIGN(LibjingleStreamTransport);
120 };
121 
LibjingleStreamTransport(cricket::PortAllocator * port_allocator,const NetworkSettings & network_settings)122 LibjingleStreamTransport::LibjingleStreamTransport(
123     cricket::PortAllocator* port_allocator,
124     const NetworkSettings& network_settings)
125     : port_allocator_(port_allocator),
126       network_settings_(network_settings),
127       event_handler_(NULL),
128       ice_username_fragment_(
129           talk_base::CreateRandomString(cricket::ICE_UFRAG_LENGTH)),
130       ice_password_(talk_base::CreateRandomString(cricket::ICE_PWD_LENGTH)),
131       can_start_(false),
132       channel_was_writable_(false),
133       connect_attempts_left_(kMaxReconnectAttempts) {
134   DCHECK(!ice_username_fragment_.empty());
135   DCHECK(!ice_password_.empty());
136 }
137 
~LibjingleStreamTransport()138 LibjingleStreamTransport::~LibjingleStreamTransport() {
139   DCHECK(event_handler_);
140   event_handler_->OnTransportDeleted(this);
141   // Channel should be already destroyed if we were connected.
142   DCHECK(!is_connected() || socket_.get() == NULL);
143 
144   if (channel_.get()) {
145     base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
146         FROM_HERE, channel_.release());
147   }
148 }
149 
OnCanStart()150 void LibjingleStreamTransport::OnCanStart() {
151   DCHECK(CalledOnValidThread());
152 
153   DCHECK(!can_start_);
154   can_start_ = true;
155 
156   // If Connect() has been called then start connection.
157   if (!callback_.is_null())
158     DoStart();
159 
160   while (!pending_candidates_.empty()) {
161     channel_->OnCandidate(pending_candidates_.front());
162     pending_candidates_.pop_front();
163   }
164 }
165 
Initialize(const std::string & name,Transport::EventHandler * event_handler,scoped_ptr<ChannelAuthenticator> authenticator)166 void LibjingleStreamTransport::Initialize(
167     const std::string& name,
168     Transport::EventHandler* event_handler,
169     scoped_ptr<ChannelAuthenticator> authenticator) {
170   DCHECK(CalledOnValidThread());
171 
172   DCHECK(!name.empty());
173   DCHECK(event_handler);
174 
175   // Can be initialized only once.
176   DCHECK(name_.empty());
177 
178   name_ = name;
179   event_handler_ = event_handler;
180   authenticator_ = authenticator.Pass();
181 }
182 
Connect(const StreamTransport::ConnectedCallback & callback)183 void LibjingleStreamTransport::Connect(
184     const StreamTransport::ConnectedCallback& callback) {
185   DCHECK(CalledOnValidThread());
186   callback_ = callback;
187 
188   if (can_start_)
189     DoStart();
190 }
191 
DoStart()192 void LibjingleStreamTransport::DoStart() {
193   DCHECK(!channel_.get());
194 
195   // Create P2PTransportChannel, attach signal handlers and connect it.
196   // TODO(sergeyu): Specify correct component ID for the channel.
197   channel_.reset(new cricket::P2PTransportChannel(
198       std::string(), 0, NULL, port_allocator_));
199   channel_->SetIceProtocolType(cricket::ICEPROTO_GOOGLE);
200   channel_->SetIceCredentials(ice_username_fragment_, ice_password_);
201   channel_->SignalRequestSignaling.connect(
202       this, &LibjingleStreamTransport::OnRequestSignaling);
203   channel_->SignalCandidateReady.connect(
204       this, &LibjingleStreamTransport::OnCandidateReady);
205   channel_->SignalRouteChange.connect(
206       this, &LibjingleStreamTransport::OnRouteChange);
207   channel_->SignalWritableState.connect(
208       this, &LibjingleStreamTransport::OnWritableState);
209   channel_->set_incoming_only(
210       !(network_settings_.flags & NetworkSettings::NAT_TRAVERSAL_OUTGOING));
211 
212   channel_->Connect();
213 
214   --connect_attempts_left_;
215 
216   // Start reconnection timer.
217   reconnect_timer_.Start(
218       FROM_HERE, base::TimeDelta::FromSeconds(kReconnectDelaySeconds),
219       this, &LibjingleStreamTransport::TryReconnect);
220 
221   // Create net::Socket adapter for the P2PTransportChannel.
222   scoped_ptr<jingle_glue::TransportChannelSocketAdapter> channel_adapter(
223       new jingle_glue::TransportChannelSocketAdapter(channel_.get()));
224 
225   channel_adapter->SetOnDestroyedCallback(base::Bind(
226       &LibjingleStreamTransport::OnChannelDestroyed, base::Unretained(this)));
227 
228   // Configure and connect PseudoTCP adapter.
229   socket_.reset(
230       new jingle_glue::PseudoTcpAdapter(channel_adapter.release()));
231   socket_->SetSendBufferSize(kTcpSendBufferSize);
232   socket_->SetReceiveBufferSize(kTcpReceiveBufferSize);
233   socket_->SetNoDelay(true);
234   socket_->SetAckDelay(kTcpAckDelayMilliseconds);
235 
236   // TODO(sergeyu): This is a hack to improve latency of the video
237   // channel. Consider removing it once we have better flow control
238   // implemented.
239   if (name_ == kVideoChannelName)
240     socket_->SetWriteWaitsForSend(true);
241 
242   int result = socket_->Connect(
243       base::Bind(&LibjingleStreamTransport::OnTcpConnected,
244                  base::Unretained(this)));
245   if (result != net::ERR_IO_PENDING)
246     OnTcpConnected(result);
247 }
248 
AddRemoteCandidate(const cricket::Candidate & candidate)249 void LibjingleStreamTransport::AddRemoteCandidate(
250     const cricket::Candidate& candidate) {
251   DCHECK(CalledOnValidThread());
252   if (channel_) {
253     channel_->OnCandidate(candidate);
254   } else {
255     pending_candidates_.push_back(candidate);
256   }
257 }
258 
name() const259 const std::string& LibjingleStreamTransport::name() const {
260   DCHECK(CalledOnValidThread());
261   return name_;
262 }
263 
is_connected() const264 bool LibjingleStreamTransport::is_connected() const {
265   DCHECK(CalledOnValidThread());
266   return callback_.is_null();
267 }
268 
OnRequestSignaling(cricket::TransportChannelImpl * channel)269 void LibjingleStreamTransport::OnRequestSignaling(
270     cricket::TransportChannelImpl* channel) {
271   DCHECK(CalledOnValidThread());
272   channel_->OnSignalingReady();
273 }
274 
OnCandidateReady(cricket::TransportChannelImpl * channel,const cricket::Candidate & candidate)275 void LibjingleStreamTransport::OnCandidateReady(
276     cricket::TransportChannelImpl* channel,
277     const cricket::Candidate& candidate) {
278   DCHECK(CalledOnValidThread());
279   event_handler_->OnTransportCandidate(this, candidate);
280 }
281 
OnRouteChange(cricket::TransportChannel * channel,const cricket::Candidate & candidate)282 void LibjingleStreamTransport::OnRouteChange(
283     cricket::TransportChannel* channel,
284     const cricket::Candidate& candidate) {
285   TransportRoute route;
286 
287   if (candidate.type() == "local") {
288     route.type = TransportRoute::DIRECT;
289   } else if (candidate.type() == "stun") {
290     route.type = TransportRoute::STUN;
291   } else if (candidate.type() == "relay") {
292     route.type = TransportRoute::RELAY;
293   } else {
294     LOG(FATAL) << "Unknown candidate type: " << candidate.type();
295   }
296 
297   if (!jingle_glue::SocketAddressToIPEndPoint(
298           candidate.address(), &route.remote_address)) {
299     LOG(FATAL) << "Failed to convert peer IP address.";
300   }
301 
302   DCHECK(channel_->best_connection());
303   const cricket::Candidate& local_candidate =
304       channel_->best_connection()->local_candidate();
305   if (!jingle_glue::SocketAddressToIPEndPoint(
306           local_candidate.address(), &route.local_address)) {
307     LOG(FATAL) << "Failed to convert local IP address.";
308   }
309 
310   event_handler_->OnTransportRouteChange(this, route);
311 }
312 
OnWritableState(cricket::TransportChannel * channel)313 void LibjingleStreamTransport::OnWritableState(
314     cricket::TransportChannel* channel) {
315   DCHECK_EQ(channel, channel_.get());
316 
317   if (channel->writable()) {
318     channel_was_writable_ = true;
319     connect_attempts_left_ = kMaxReconnectAttempts;
320     reconnect_timer_.Stop();
321   } else if (!channel->writable() && channel_was_writable_) {
322     reconnect_timer_.Reset();
323     TryReconnect();
324   }
325 }
326 
OnTcpConnected(int result)327 void LibjingleStreamTransport::OnTcpConnected(int result) {
328   DCHECK(CalledOnValidThread());
329 
330   if (result != net::OK) {
331     NotifyConnectFailed();
332     return;
333   }
334 
335   authenticator_->SecureAndAuthenticate(
336       socket_.PassAs<net::StreamSocket>(),
337       base::Bind(&LibjingleStreamTransport::OnAuthenticationDone,
338                  base::Unretained(this)));
339 }
340 
OnAuthenticationDone(net::Error error,scoped_ptr<net::StreamSocket> socket)341 void LibjingleStreamTransport::OnAuthenticationDone(
342     net::Error error,
343     scoped_ptr<net::StreamSocket> socket) {
344   if (error != net::OK) {
345     NotifyConnectFailed();
346     return;
347   }
348 
349   NotifyConnected(socket.Pass());
350 }
351 
OnChannelDestroyed()352 void LibjingleStreamTransport::OnChannelDestroyed() {
353   if (is_connected()) {
354     // The connection socket is being deleted, so delete the transport too.
355     delete this;
356   }
357 }
358 
TryReconnect()359 void LibjingleStreamTransport::TryReconnect() {
360   DCHECK(!channel_->writable());
361 
362   if (connect_attempts_left_ <= 0) {
363     reconnect_timer_.Stop();
364 
365     // Notify the caller that ICE connection has failed - normally that will
366     // terminate Jingle connection (i.e. the transport will be destroyed).
367     event_handler_->OnTransportFailed(this);
368     return;
369   }
370   --connect_attempts_left_;
371 
372   // Restart ICE by resetting ICE password.
373   ice_password_ = talk_base::CreateRandomString(cricket::ICE_PWD_LENGTH);
374   channel_->SetIceCredentials(ice_username_fragment_, ice_password_);
375 }
376 
NotifyConnected(scoped_ptr<net::StreamSocket> socket)377 void LibjingleStreamTransport::NotifyConnected(
378     scoped_ptr<net::StreamSocket> socket) {
379   DCHECK(!is_connected());
380   StreamTransport::ConnectedCallback callback = callback_;
381   callback_.Reset();
382   callback.Run(socket.Pass());
383 }
384 
NotifyConnectFailed()385 void LibjingleStreamTransport::NotifyConnectFailed() {
386   DCHECK(!is_connected());
387 
388   socket_.reset();
389 
390   // This method may be called in response to a libjingle signal, so
391   // libjingle objects must be deleted asynchronously.
392   if (channel_.get()) {
393     base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
394         FROM_HERE, channel_.release());
395   }
396 
397   authenticator_.reset();
398 
399   NotifyConnected(scoped_ptr<net::StreamSocket>());
400 }
401 
402 }  // namespace
403 
LibjingleTransportFactory(SignalStrategy * signal_strategy,scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator,const NetworkSettings & network_settings)404 LibjingleTransportFactory::LibjingleTransportFactory(
405     SignalStrategy* signal_strategy,
406     scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator,
407     const NetworkSettings& network_settings)
408     : signal_strategy_(signal_strategy),
409       port_allocator_(port_allocator.Pass()),
410       network_settings_(network_settings) {
411 }
412 
~LibjingleTransportFactory()413 LibjingleTransportFactory::~LibjingleTransportFactory() {
414   // This method may be called in response to a libjingle signal, so
415   // libjingle objects must be deleted asynchronously.
416   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
417       base::ThreadTaskRunnerHandle::Get();
418   task_runner->DeleteSoon(FROM_HERE, port_allocator_.release());
419 }
420 
PrepareTokens()421 void LibjingleTransportFactory::PrepareTokens() {
422   EnsureFreshJingleInfo();
423 }
424 
CreateStreamTransport()425 scoped_ptr<StreamTransport> LibjingleTransportFactory::CreateStreamTransport() {
426   scoped_ptr<LibjingleStreamTransport> result(
427       new LibjingleStreamTransport(port_allocator_.get(), network_settings_));
428 
429   EnsureFreshJingleInfo();
430 
431   // If there is a pending |jingle_info_request_| delay starting the new
432   // transport until the request is finished.
433   if (jingle_info_request_) {
434     on_jingle_info_callbacks_.push_back(
435         base::Bind(&LibjingleStreamTransport::OnCanStart,
436                    result->AsWeakPtr()));
437   } else {
438     result->OnCanStart();
439   }
440 
441   return result.PassAs<StreamTransport>();
442 }
443 
444 scoped_ptr<DatagramTransport>
CreateDatagramTransport()445 LibjingleTransportFactory::CreateDatagramTransport() {
446   NOTIMPLEMENTED();
447   return scoped_ptr<DatagramTransport>();
448 }
449 
EnsureFreshJingleInfo()450 void LibjingleTransportFactory::EnsureFreshJingleInfo() {
451   uint32 stun_or_relay_flags = NetworkSettings::NAT_TRAVERSAL_STUN |
452       NetworkSettings::NAT_TRAVERSAL_RELAY;
453   if (!(network_settings_.flags & stun_or_relay_flags) ||
454       jingle_info_request_) {
455     return;
456   }
457 
458   if (base::TimeTicks::Now() - last_jingle_info_update_time_ >
459       base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds)) {
460     jingle_info_request_.reset(new JingleInfoRequest(signal_strategy_));
461     jingle_info_request_->Send(base::Bind(
462         &LibjingleTransportFactory::OnJingleInfo, base::Unretained(this)));
463   }
464 }
465 
OnJingleInfo(const std::string & relay_token,const std::vector<std::string> & relay_hosts,const std::vector<talk_base::SocketAddress> & stun_hosts)466 void LibjingleTransportFactory::OnJingleInfo(
467     const std::string& relay_token,
468     const std::vector<std::string>& relay_hosts,
469     const std::vector<talk_base::SocketAddress>& stun_hosts) {
470   if (!relay_token.empty() && !relay_hosts.empty()) {
471     port_allocator_->SetRelayHosts(relay_hosts);
472     port_allocator_->SetRelayToken(relay_token);
473   }
474   if (!stun_hosts.empty()) {
475     port_allocator_->SetStunHosts(stun_hosts);
476   }
477 
478   jingle_info_request_.reset();
479   if ((!relay_token.empty() && !relay_hosts.empty()) || !stun_hosts.empty())
480     last_jingle_info_update_time_ = base::TimeTicks::Now();
481 
482   while (!on_jingle_info_callbacks_.empty()) {
483     on_jingle_info_callbacks_.begin()->Run();
484     on_jingle_info_callbacks_.pop_front();
485   }
486 }
487 
488 }  // namespace protocol
489 }  // namespace remoting
490