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