• 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/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 
23 namespace remoting {
24 namespace protocol {
25 
26 namespace {
27 
28 // Try connecting ICE twice with timeout of 15 seconds for each attempt.
29 const int kMaxReconnectAttempts = 2;
30 const int kReconnectDelaySeconds = 15;
31 
32 // Get fresh STUN/Relay configuration every hour.
33 const int kJingleInfoUpdatePeriodSeconds = 3600;
34 
35 class 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 
LibjingleTransport(cricket::PortAllocator * port_allocator,const NetworkSettings & network_settings)97 LibjingleTransport::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 
~LibjingleTransport()113 LibjingleTransport::~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 
OnCanStart()124 void 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 
Connect(const std::string & name,Transport::EventHandler * event_handler,const Transport::ConnectedCallback & callback)140 void 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 
DoStart()158 void 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 
NotifyConnected()188 void 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 
AddRemoteCandidate(const cricket::Candidate & candidate)200 void 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 
name() const218 const std::string& LibjingleTransport::name() const {
219   DCHECK(CalledOnValidThread());
220   return name_;
221 }
222 
is_connected() const223 bool LibjingleTransport::is_connected() const {
224   DCHECK(CalledOnValidThread());
225   return callback_.is_null();
226 }
227 
OnRequestSignaling(cricket::TransportChannelImpl * channel)228 void LibjingleTransport::OnRequestSignaling(
229     cricket::TransportChannelImpl* channel) {
230   DCHECK(CalledOnValidThread());
231   channel_->OnSignalingReady();
232 }
233 
OnCandidateReady(cricket::TransportChannelImpl * channel,const cricket::Candidate & candidate)234 void LibjingleTransport::OnCandidateReady(
235     cricket::TransportChannelImpl* channel,
236     const cricket::Candidate& candidate) {
237   DCHECK(CalledOnValidThread());
238   event_handler_->OnTransportCandidate(this, candidate);
239 }
240 
OnRouteChange(cricket::TransportChannel * channel,const cricket::Candidate & candidate)241 void 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 
OnWritableState(cricket::TransportChannel * channel)272 void 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 
OnChannelDestroyed()292 void LibjingleTransport::OnChannelDestroyed() {
293   if (is_connected()) {
294     // The connection socket is being deleted, so delete the transport too.
295     delete this;
296   }
297 }
298 
TryReconnect()299 void 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 
LibjingleTransportFactory(SignalStrategy * signal_strategy,scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator,const NetworkSettings & network_settings)319 LibjingleTransportFactory::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 
~LibjingleTransportFactory()328 LibjingleTransportFactory::~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 
PrepareTokens()336 void LibjingleTransportFactory::PrepareTokens() {
337   EnsureFreshJingleInfo();
338 }
339 
CreateTransport()340 scoped_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 
EnsureFreshJingleInfo()359 void 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 
OnJingleInfo(const std::string & relay_token,const std::vector<std::string> & relay_hosts,const std::vector<rtc::SocketAddress> & stun_hosts)375 void 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