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/utils.h"
13#include "net/base/net_errors.h"
14#include "remoting/protocol/network_settings.h"
15#include "remoting/signaling/jingle_info_request.h"
16#include "third_party/libjingle/source/talk/p2p/base/constants.h"
17#include "third_party/libjingle/source/talk/p2p/base/p2ptransportchannel.h"
18#include "third_party/libjingle/source/talk/p2p/base/port.h"
19#include "third_party/libjingle/source/talk/p2p/client/basicportallocator.h"
20#include "third_party/libjingle/source/talk/p2p/client/httpportallocator.h"
21#include "third_party/webrtc/base/network.h"
22
23namespace remoting {
24namespace protocol {
25
26namespace {
27
28// Try connecting ICE twice with timeout of 15 seconds for each attempt.
29const int kMaxReconnectAttempts = 2;
30const int kReconnectDelaySeconds = 15;
31
32// Get fresh STUN/Relay configuration every hour.
33const int kJingleInfoUpdatePeriodSeconds = 3600;
34
35class LibjingleTransport
36    : public Transport,
37      public base::SupportsWeakPtr<LibjingleTransport>,
38      public sigslot::has_slots<> {
39 public:
40  LibjingleTransport(cricket::PortAllocator* port_allocator,
41                           const NetworkSettings& network_settings);
42  virtual ~LibjingleTransport();
43
44  // Called by JingleTransportFactory when it has fresh Jingle info.
45  void OnCanStart();
46
47  // Transport interface.
48  virtual void Connect(
49      const std::string& name,
50      Transport::EventHandler* event_handler,
51      const Transport::ConnectedCallback& callback) OVERRIDE;
52  virtual void AddRemoteCandidate(const cricket::Candidate& candidate) OVERRIDE;
53  virtual const std::string& name() const OVERRIDE;
54  virtual bool is_connected() const OVERRIDE;
55
56 private:
57  void DoStart();
58  void NotifyConnected();
59
60  // Signal handlers for cricket::TransportChannel.
61  void OnRequestSignaling(cricket::TransportChannelImpl* channel);
62  void OnCandidateReady(cricket::TransportChannelImpl* channel,
63                        const cricket::Candidate& candidate);
64  void OnRouteChange(cricket::TransportChannel* channel,
65                     const cricket::Candidate& candidate);
66  void OnWritableState(cricket::TransportChannel* channel);
67
68  // Callback for jingle_glue::TransportChannelSocketAdapter to notify when the
69  // socket is destroyed.
70  void OnChannelDestroyed();
71
72  // Tries to connect by restarting ICE. Called by |reconnect_timer_|.
73  void TryReconnect();
74
75  cricket::PortAllocator* port_allocator_;
76  NetworkSettings network_settings_;
77
78  std::string name_;
79  EventHandler* event_handler_;
80  Transport::ConnectedCallback callback_;
81  std::string ice_username_fragment_;
82  std::string ice_password_;
83
84  bool can_start_;
85
86  std::list<cricket::Candidate> pending_candidates_;
87  scoped_ptr<cricket::P2PTransportChannel> channel_;
88  bool channel_was_writable_;
89  int connect_attempts_left_;
90  base::RepeatingTimer<LibjingleTransport> reconnect_timer_;
91
92  base::WeakPtrFactory<LibjingleTransport> weak_factory_;
93
94  DISALLOW_COPY_AND_ASSIGN(LibjingleTransport);
95};
96
97LibjingleTransport::LibjingleTransport(cricket::PortAllocator* port_allocator,
98                                       const NetworkSettings& network_settings)
99    : port_allocator_(port_allocator),
100      network_settings_(network_settings),
101      event_handler_(NULL),
102      ice_username_fragment_(
103          rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH)),
104      ice_password_(rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)),
105      can_start_(false),
106      channel_was_writable_(false),
107      connect_attempts_left_(kMaxReconnectAttempts),
108      weak_factory_(this) {
109  DCHECK(!ice_username_fragment_.empty());
110  DCHECK(!ice_password_.empty());
111}
112
113LibjingleTransport::~LibjingleTransport() {
114  DCHECK(event_handler_);
115
116  event_handler_->OnTransportDeleted(this);
117
118  if (channel_.get()) {
119    base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
120        FROM_HERE, channel_.release());
121  }
122}
123
124void LibjingleTransport::OnCanStart() {
125  DCHECK(CalledOnValidThread());
126
127  DCHECK(!can_start_);
128  can_start_ = true;
129
130  // If Connect() has been called then start connection.
131  if (!callback_.is_null())
132    DoStart();
133
134  while (!pending_candidates_.empty()) {
135    channel_->OnCandidate(pending_candidates_.front());
136    pending_candidates_.pop_front();
137  }
138}
139
140void LibjingleTransport::Connect(
141    const std::string& name,
142    Transport::EventHandler* event_handler,
143    const Transport::ConnectedCallback& callback) {
144  DCHECK(CalledOnValidThread());
145  DCHECK(!name.empty());
146  DCHECK(event_handler);
147  DCHECK(!callback.is_null());
148
149  DCHECK(name_.empty());
150  name_ = name;
151  event_handler_ = event_handler;
152  callback_ = callback;
153
154  if (can_start_)
155    DoStart();
156}
157
158void LibjingleTransport::DoStart() {
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_->SetIceProtocolType(cricket::ICEPROTO_GOOGLE);
166  channel_->SetIceCredentials(ice_username_fragment_, ice_password_);
167  channel_->SignalRequestSignaling.connect(
168      this, &LibjingleTransport::OnRequestSignaling);
169  channel_->SignalCandidateReady.connect(
170      this, &LibjingleTransport::OnCandidateReady);
171  channel_->SignalRouteChange.connect(
172      this, &LibjingleTransport::OnRouteChange);
173  channel_->SignalWritableState.connect(
174      this, &LibjingleTransport::OnWritableState);
175  channel_->set_incoming_only(
176      !(network_settings_.flags & NetworkSettings::NAT_TRAVERSAL_OUTGOING));
177
178  channel_->Connect();
179
180  --connect_attempts_left_;
181
182  // Start reconnection timer.
183  reconnect_timer_.Start(
184      FROM_HERE, base::TimeDelta::FromSeconds(kReconnectDelaySeconds),
185      this, &LibjingleTransport::TryReconnect);
186}
187
188void LibjingleTransport::NotifyConnected() {
189  // Create net::Socket adapter for the P2PTransportChannel.
190  scoped_ptr<jingle_glue::TransportChannelSocketAdapter> socket(
191      new jingle_glue::TransportChannelSocketAdapter(channel_.get()));
192  socket->SetOnDestroyedCallback(base::Bind(
193      &LibjingleTransport::OnChannelDestroyed, base::Unretained(this)));
194
195  Transport::ConnectedCallback callback = callback_;
196  callback_.Reset();
197  callback.Run(socket.PassAs<net::Socket>());
198}
199
200void LibjingleTransport::AddRemoteCandidate(
201    const cricket::Candidate& candidate) {
202  DCHECK(CalledOnValidThread());
203
204  // To enforce the no-relay setting, it's not enough to not produce relay
205  // candidates. It's also necessary to discard remote relay candidates.
206  bool relay_allowed = (network_settings_.flags &
207                        NetworkSettings::NAT_TRAVERSAL_RELAY) != 0;
208  if (!relay_allowed && candidate.type() == cricket::RELAY_PORT_TYPE)
209    return;
210
211  if (channel_) {
212    channel_->OnCandidate(candidate);
213  } else {
214    pending_candidates_.push_back(candidate);
215  }
216}
217
218const std::string& LibjingleTransport::name() const {
219  DCHECK(CalledOnValidThread());
220  return name_;
221}
222
223bool LibjingleTransport::is_connected() const {
224  DCHECK(CalledOnValidThread());
225  return callback_.is_null();
226}
227
228void LibjingleTransport::OnRequestSignaling(
229    cricket::TransportChannelImpl* channel) {
230  DCHECK(CalledOnValidThread());
231  channel_->OnSignalingReady();
232}
233
234void LibjingleTransport::OnCandidateReady(
235    cricket::TransportChannelImpl* channel,
236    const cricket::Candidate& candidate) {
237  DCHECK(CalledOnValidThread());
238  event_handler_->OnTransportCandidate(this, candidate);
239}
240
241void LibjingleTransport::OnRouteChange(
242    cricket::TransportChannel* channel,
243    const cricket::Candidate& candidate) {
244  TransportRoute route;
245
246  if (candidate.type() == "local") {
247    route.type = TransportRoute::DIRECT;
248  } else if (candidate.type() == "stun") {
249    route.type = TransportRoute::STUN;
250  } else if (candidate.type() == "relay") {
251    route.type = TransportRoute::RELAY;
252  } else {
253    LOG(FATAL) << "Unknown candidate type: " << candidate.type();
254  }
255
256  if (!jingle_glue::SocketAddressToIPEndPoint(
257          candidate.address(), &route.remote_address)) {
258    LOG(FATAL) << "Failed to convert peer IP address.";
259  }
260
261  DCHECK(channel_->best_connection());
262  const cricket::Candidate& local_candidate =
263      channel_->best_connection()->local_candidate();
264  if (!jingle_glue::SocketAddressToIPEndPoint(
265          local_candidate.address(), &route.local_address)) {
266    LOG(FATAL) << "Failed to convert local IP address.";
267  }
268
269  event_handler_->OnTransportRouteChange(this, route);
270}
271
272void LibjingleTransport::OnWritableState(
273    cricket::TransportChannel* channel) {
274  DCHECK_EQ(channel, channel_.get());
275
276  if (channel->writable()) {
277    if (!channel_was_writable_) {
278      channel_was_writable_ = true;
279      base::ThreadTaskRunnerHandle::Get()->PostTask(
280          FROM_HERE,
281          base::Bind(&LibjingleTransport::NotifyConnected,
282                     weak_factory_.GetWeakPtr()));
283    }
284    connect_attempts_left_ = kMaxReconnectAttempts;
285    reconnect_timer_.Stop();
286  } else if (!channel->writable() && channel_was_writable_) {
287    reconnect_timer_.Reset();
288    TryReconnect();
289  }
290}
291
292void LibjingleTransport::OnChannelDestroyed() {
293  if (is_connected()) {
294    // The connection socket is being deleted, so delete the transport too.
295    delete this;
296  }
297}
298
299void LibjingleTransport::TryReconnect() {
300  DCHECK(!channel_->writable());
301
302  if (connect_attempts_left_ <= 0) {
303    reconnect_timer_.Stop();
304
305    // Notify the caller that ICE connection has failed - normally that will
306    // terminate Jingle connection (i.e. the transport will be destroyed).
307    event_handler_->OnTransportFailed(this);
308    return;
309  }
310  --connect_attempts_left_;
311
312  // Restart ICE by resetting ICE password.
313  ice_password_ = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH);
314  channel_->SetIceCredentials(ice_username_fragment_, ice_password_);
315}
316
317}  // namespace
318
319LibjingleTransportFactory::LibjingleTransportFactory(
320    SignalStrategy* signal_strategy,
321    scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator,
322    const NetworkSettings& network_settings)
323    : signal_strategy_(signal_strategy),
324      port_allocator_(port_allocator.Pass()),
325      network_settings_(network_settings) {
326}
327
328LibjingleTransportFactory::~LibjingleTransportFactory() {
329  // This method may be called in response to a libjingle signal, so
330  // libjingle objects must be deleted asynchronously.
331  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
332      base::ThreadTaskRunnerHandle::Get();
333  task_runner->DeleteSoon(FROM_HERE, port_allocator_.release());
334}
335
336void LibjingleTransportFactory::PrepareTokens() {
337  EnsureFreshJingleInfo();
338}
339
340scoped_ptr<Transport> LibjingleTransportFactory::CreateTransport() {
341  scoped_ptr<LibjingleTransport> result(
342      new LibjingleTransport(port_allocator_.get(), network_settings_));
343
344  EnsureFreshJingleInfo();
345
346  // If there is a pending |jingle_info_request_| delay starting the new
347  // transport until the request is finished.
348  if (jingle_info_request_) {
349    on_jingle_info_callbacks_.push_back(
350        base::Bind(&LibjingleTransport::OnCanStart,
351                   result->AsWeakPtr()));
352  } else {
353    result->OnCanStart();
354  }
355
356  return result.PassAs<Transport>();
357}
358
359void LibjingleTransportFactory::EnsureFreshJingleInfo() {
360  uint32 stun_or_relay_flags = NetworkSettings::NAT_TRAVERSAL_STUN |
361      NetworkSettings::NAT_TRAVERSAL_RELAY;
362  if (!(network_settings_.flags & stun_or_relay_flags) ||
363      jingle_info_request_) {
364    return;
365  }
366
367  if (base::TimeTicks::Now() - last_jingle_info_update_time_ >
368      base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds)) {
369    jingle_info_request_.reset(new JingleInfoRequest(signal_strategy_));
370    jingle_info_request_->Send(base::Bind(
371        &LibjingleTransportFactory::OnJingleInfo, base::Unretained(this)));
372  }
373}
374
375void LibjingleTransportFactory::OnJingleInfo(
376    const std::string& relay_token,
377    const std::vector<std::string>& relay_hosts,
378    const std::vector<rtc::SocketAddress>& stun_hosts) {
379  if (!relay_token.empty() && !relay_hosts.empty()) {
380    port_allocator_->SetRelayHosts(relay_hosts);
381    port_allocator_->SetRelayToken(relay_token);
382  }
383  if (!stun_hosts.empty()) {
384    port_allocator_->SetStunHosts(stun_hosts);
385  }
386
387  jingle_info_request_.reset();
388  if ((!relay_token.empty() && !relay_hosts.empty()) || !stun_hosts.empty())
389    last_jingle_info_update_time_ = base::TimeTicks::Now();
390
391  while (!on_jingle_info_callbacks_.empty()) {
392    on_jingle_info_callbacks_.begin()->Run();
393    on_jingle_info_callbacks_.pop_front();
394  }
395}
396
397}  // namespace protocol
398}  // namespace remoting
399