| OLD | NEW |
| (Empty) |
| 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 <algorithm> | |
| 8 | |
| 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/httpportallocator.h" | |
| 24 | |
| 25 namespace remoting { | |
| 26 namespace protocol { | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 // Try connecting ICE twice with timeout of 15 seconds for each attempt. | |
| 31 const int kMaxReconnectAttempts = 2; | |
| 32 const int kReconnectDelaySeconds = 15; | |
| 33 | |
| 34 // Get fresh STUN/Relay configuration every hour. | |
| 35 const int kJingleInfoUpdatePeriodSeconds = 3600; | |
| 36 | |
| 37 // Utility function to map a cricket::Candidate string type to a | |
| 38 // TransportRoute::RouteType enum value. | |
| 39 TransportRoute::RouteType CandidateTypeToTransportRouteType( | |
| 40 const std::string& candidate_type) { | |
| 41 if (candidate_type == "local") { | |
| 42 return TransportRoute::DIRECT; | |
| 43 } else if (candidate_type == "stun" || candidate_type == "prflx") { | |
| 44 return TransportRoute::STUN; | |
| 45 } else if (candidate_type == "relay") { | |
| 46 return TransportRoute::RELAY; | |
| 47 } else { | |
| 48 LOG(FATAL) << "Unknown candidate type: " << candidate_type; | |
| 49 return TransportRoute::DIRECT; | |
| 50 } | |
| 51 } | |
| 52 | |
| 53 class LibjingleTransport | |
| 54 : public Transport, | |
| 55 public base::SupportsWeakPtr<LibjingleTransport>, | |
| 56 public sigslot::has_slots<> { | |
| 57 public: | |
| 58 LibjingleTransport(cricket::PortAllocator* port_allocator, | |
| 59 const NetworkSettings& network_settings, | |
| 60 TransportRole role); | |
| 61 ~LibjingleTransport() override; | |
| 62 | |
| 63 // Called by JingleTransportFactory when it has fresh Jingle info. | |
| 64 void OnCanStart(); | |
| 65 | |
| 66 // Transport interface. | |
| 67 void Connect(const std::string& name, | |
| 68 Transport::EventHandler* event_handler, | |
| 69 const Transport::ConnectedCallback& callback) override; | |
| 70 void SetRemoteCredentials(const std::string& ufrag, | |
| 71 const std::string& password) override; | |
| 72 void AddRemoteCandidate(const cricket::Candidate& candidate) override; | |
| 73 const std::string& name() const override; | |
| 74 bool is_connected() const override; | |
| 75 | |
| 76 private: | |
| 77 void DoStart(); | |
| 78 void NotifyConnected(); | |
| 79 | |
| 80 // Signal handlers for cricket::TransportChannel. | |
| 81 void OnCandidateGathered(cricket::TransportChannelImpl* channel, | |
| 82 const cricket::Candidate& candidate); | |
| 83 void OnRouteChange(cricket::TransportChannel* channel, | |
| 84 const cricket::Candidate& candidate); | |
| 85 void OnReceivingState(cricket::TransportChannel* channel); | |
| 86 void OnWritableState(cricket::TransportChannel* channel); | |
| 87 | |
| 88 // Callback for TransportChannelSocketAdapter to notify when the socket is | |
| 89 // destroyed. | |
| 90 void OnChannelDestroyed(); | |
| 91 | |
| 92 void NotifyRouteChanged(); | |
| 93 | |
| 94 // Tries to connect by restarting ICE. Called by |reconnect_timer_|. | |
| 95 void TryReconnect(); | |
| 96 | |
| 97 cricket::PortAllocator* port_allocator_; | |
| 98 NetworkSettings network_settings_; | |
| 99 TransportRole role_; | |
| 100 | |
| 101 std::string name_; | |
| 102 EventHandler* event_handler_; | |
| 103 Transport::ConnectedCallback callback_; | |
| 104 std::string ice_username_fragment_; | |
| 105 | |
| 106 bool can_start_; | |
| 107 | |
| 108 std::string remote_ice_username_fragment_; | |
| 109 std::string remote_ice_password_; | |
| 110 std::list<cricket::Candidate> pending_candidates_; | |
| 111 scoped_ptr<cricket::P2PTransportChannel> channel_; | |
| 112 int connect_attempts_left_; | |
| 113 base::RepeatingTimer reconnect_timer_; | |
| 114 | |
| 115 base::WeakPtrFactory<LibjingleTransport> weak_factory_; | |
| 116 | |
| 117 DISALLOW_COPY_AND_ASSIGN(LibjingleTransport); | |
| 118 }; | |
| 119 | |
| 120 LibjingleTransport::LibjingleTransport(cricket::PortAllocator* port_allocator, | |
| 121 const NetworkSettings& network_settings, | |
| 122 TransportRole role) | |
| 123 : port_allocator_(port_allocator), | |
| 124 network_settings_(network_settings), | |
| 125 role_(role), | |
| 126 event_handler_(nullptr), | |
| 127 ice_username_fragment_( | |
| 128 rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH)), | |
| 129 can_start_(false), | |
| 130 connect_attempts_left_(kMaxReconnectAttempts), | |
| 131 weak_factory_(this) { | |
| 132 DCHECK(!ice_username_fragment_.empty()); | |
| 133 } | |
| 134 | |
| 135 LibjingleTransport::~LibjingleTransport() { | |
| 136 DCHECK(event_handler_); | |
| 137 | |
| 138 event_handler_->OnTransportDeleted(this); | |
| 139 | |
| 140 if (channel_.get()) { | |
| 141 base::ThreadTaskRunnerHandle::Get()->DeleteSoon( | |
| 142 FROM_HERE, channel_.release()); | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 void LibjingleTransport::OnCanStart() { | |
| 147 DCHECK(CalledOnValidThread()); | |
| 148 | |
| 149 DCHECK(!can_start_); | |
| 150 can_start_ = true; | |
| 151 | |
| 152 // If Connect() has been called then start connection. | |
| 153 if (!callback_.is_null()) | |
| 154 DoStart(); | |
| 155 | |
| 156 // Pass pending ICE credentials and candidates to the channel. | |
| 157 if (!remote_ice_username_fragment_.empty()) { | |
| 158 channel_->SetRemoteIceCredentials(remote_ice_username_fragment_, | |
| 159 remote_ice_password_); | |
| 160 } | |
| 161 | |
| 162 while (!pending_candidates_.empty()) { | |
| 163 channel_->AddRemoteCandidate(pending_candidates_.front()); | |
| 164 pending_candidates_.pop_front(); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 void LibjingleTransport::Connect( | |
| 169 const std::string& name, | |
| 170 Transport::EventHandler* event_handler, | |
| 171 const Transport::ConnectedCallback& callback) { | |
| 172 DCHECK(CalledOnValidThread()); | |
| 173 DCHECK(!name.empty()); | |
| 174 DCHECK(event_handler); | |
| 175 DCHECK(!callback.is_null()); | |
| 176 | |
| 177 DCHECK(name_.empty()); | |
| 178 name_ = name; | |
| 179 event_handler_ = event_handler; | |
| 180 callback_ = callback; | |
| 181 | |
| 182 if (can_start_) | |
| 183 DoStart(); | |
| 184 } | |
| 185 | |
| 186 void LibjingleTransport::DoStart() { | |
| 187 DCHECK(!channel_.get()); | |
| 188 | |
| 189 // Create P2PTransportChannel, attach signal handlers and connect it. | |
| 190 // TODO(sergeyu): Specify correct component ID for the channel. | |
| 191 channel_.reset(new cricket::P2PTransportChannel( | |
| 192 std::string(), 0, nullptr, port_allocator_)); | |
| 193 std::string ice_password = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH); | |
| 194 channel_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); | |
| 195 channel_->SetIceRole((role_ == TransportRole::CLIENT) | |
| 196 ? cricket::ICEROLE_CONTROLLING | |
| 197 : cricket::ICEROLE_CONTROLLED); | |
| 198 event_handler_->OnTransportIceCredentials(this, ice_username_fragment_, | |
| 199 ice_password); | |
| 200 channel_->SetIceCredentials(ice_username_fragment_, ice_password); | |
| 201 channel_->SignalCandidateGathered.connect( | |
| 202 this, &LibjingleTransport::OnCandidateGathered); | |
| 203 channel_->SignalRouteChange.connect( | |
| 204 this, &LibjingleTransport::OnRouteChange); | |
| 205 channel_->SignalReceivingState.connect( | |
| 206 this, &LibjingleTransport::OnReceivingState); | |
| 207 channel_->SignalWritableState.connect( | |
| 208 this, &LibjingleTransport::OnWritableState); | |
| 209 channel_->set_incoming_only( | |
| 210 !(network_settings_.flags & NetworkSettings::NAT_TRAVERSAL_OUTGOING)); | |
| 211 | |
| 212 channel_->Connect(); | |
| 213 channel_->MaybeStartGathering(); | |
| 214 | |
| 215 --connect_attempts_left_; | |
| 216 | |
| 217 // Start reconnection timer. | |
| 218 reconnect_timer_.Start( | |
| 219 FROM_HERE, base::TimeDelta::FromSeconds(kReconnectDelaySeconds), | |
| 220 this, &LibjingleTransport::TryReconnect); | |
| 221 } | |
| 222 | |
| 223 void LibjingleTransport::NotifyConnected() { | |
| 224 // Create P2PDatagramSocket adapter for the P2PTransportChannel. | |
| 225 scoped_ptr<TransportChannelSocketAdapter> socket( | |
| 226 new TransportChannelSocketAdapter(channel_.get())); | |
| 227 socket->SetOnDestroyedCallback(base::Bind( | |
| 228 &LibjingleTransport::OnChannelDestroyed, base::Unretained(this))); | |
| 229 base::ResetAndReturn(&callback_).Run(socket.Pass()); | |
| 230 } | |
| 231 | |
| 232 void LibjingleTransport::SetRemoteCredentials(const std::string& ufrag, | |
| 233 const std::string& password) { | |
| 234 DCHECK(CalledOnValidThread()); | |
| 235 | |
| 236 remote_ice_username_fragment_ = ufrag; | |
| 237 remote_ice_password_ = password; | |
| 238 | |
| 239 if (channel_) | |
| 240 channel_->SetRemoteIceCredentials(ufrag, password); | |
| 241 } | |
| 242 | |
| 243 void LibjingleTransport::AddRemoteCandidate( | |
| 244 const cricket::Candidate& candidate) { | |
| 245 DCHECK(CalledOnValidThread()); | |
| 246 | |
| 247 // To enforce the no-relay setting, it's not enough to not produce relay | |
| 248 // candidates. It's also necessary to discard remote relay candidates. | |
| 249 bool relay_allowed = (network_settings_.flags & | |
| 250 NetworkSettings::NAT_TRAVERSAL_RELAY) != 0; | |
| 251 if (!relay_allowed && candidate.type() == cricket::RELAY_PORT_TYPE) | |
| 252 return; | |
| 253 | |
| 254 if (channel_) { | |
| 255 channel_->AddRemoteCandidate(candidate); | |
| 256 } else { | |
| 257 pending_candidates_.push_back(candidate); | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 const std::string& LibjingleTransport::name() const { | |
| 262 DCHECK(CalledOnValidThread()); | |
| 263 return name_; | |
| 264 } | |
| 265 | |
| 266 bool LibjingleTransport::is_connected() const { | |
| 267 DCHECK(CalledOnValidThread()); | |
| 268 return callback_.is_null(); | |
| 269 } | |
| 270 | |
| 271 void LibjingleTransport::OnCandidateGathered( | |
| 272 cricket::TransportChannelImpl* channel, | |
| 273 const cricket::Candidate& candidate) { | |
| 274 DCHECK(CalledOnValidThread()); | |
| 275 event_handler_->OnTransportCandidate(this, candidate); | |
| 276 } | |
| 277 | |
| 278 void LibjingleTransport::OnRouteChange( | |
| 279 cricket::TransportChannel* channel, | |
| 280 const cricket::Candidate& candidate) { | |
| 281 // Ignore notifications if the channel is not writable. | |
| 282 if (channel_->writable()) | |
| 283 NotifyRouteChanged(); | |
| 284 } | |
| 285 | |
| 286 void LibjingleTransport::OnReceivingState(cricket::TransportChannel* channel) { | |
| 287 DCHECK_EQ(channel, static_cast<cricket::TransportChannel*>(channel_.get())); | |
| 288 | |
| 289 if (channel->receiving() && !callback_.is_null()) | |
| 290 NotifyConnected(); | |
| 291 } | |
| 292 | |
| 293 void LibjingleTransport::OnWritableState(cricket::TransportChannel* channel) { | |
| 294 DCHECK_EQ(channel, static_cast<cricket::TransportChannel*>(channel_.get())); | |
| 295 | |
| 296 if (channel->writable()) { | |
| 297 connect_attempts_left_ = kMaxReconnectAttempts; | |
| 298 reconnect_timer_.Stop(); | |
| 299 | |
| 300 // Route change notifications are ignored when the |channel_| is not | |
| 301 // writable. Notify the event handler about the current route once the | |
| 302 // channel is writable. | |
| 303 NotifyRouteChanged(); | |
| 304 } else { | |
| 305 reconnect_timer_.Reset(); | |
| 306 TryReconnect(); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 void LibjingleTransport::OnChannelDestroyed() { | |
| 311 // The connection socket is being deleted, so delete the transport too. | |
| 312 delete this; | |
| 313 } | |
| 314 | |
| 315 void LibjingleTransport::NotifyRouteChanged() { | |
| 316 TransportRoute route; | |
| 317 | |
| 318 DCHECK(channel_->best_connection()); | |
| 319 const cricket::Connection* connection = channel_->best_connection(); | |
| 320 | |
| 321 // A connection has both a local and a remote candidate. For our purposes, the | |
| 322 // route type is determined by the most indirect candidate type. For example: | |
| 323 // it's possible for the local candidate be a "relay" type, while the remote | |
| 324 // candidate is "local". In this case, we still want to report a RELAY route | |
| 325 // type. | |
| 326 static_assert(TransportRoute::DIRECT < TransportRoute::STUN && | |
| 327 TransportRoute::STUN < TransportRoute::RELAY, | |
| 328 "Route type enum values are ordered by 'indirectness'"); | |
| 329 route.type = std::max( | |
| 330 CandidateTypeToTransportRouteType(connection->local_candidate().type()), | |
| 331 CandidateTypeToTransportRouteType(connection->remote_candidate().type())); | |
| 332 | |
| 333 if (!jingle_glue::SocketAddressToIPEndPoint( | |
| 334 connection->remote_candidate().address(), &route.remote_address)) { | |
| 335 LOG(FATAL) << "Failed to convert peer IP address."; | |
| 336 } | |
| 337 | |
| 338 const cricket::Candidate& local_candidate = | |
| 339 channel_->best_connection()->local_candidate(); | |
| 340 if (!jingle_glue::SocketAddressToIPEndPoint( | |
| 341 local_candidate.address(), &route.local_address)) { | |
| 342 LOG(FATAL) << "Failed to convert local IP address."; | |
| 343 } | |
| 344 | |
| 345 event_handler_->OnTransportRouteChange(this, route); | |
| 346 } | |
| 347 | |
| 348 void LibjingleTransport::TryReconnect() { | |
| 349 DCHECK(!channel_->writable()); | |
| 350 | |
| 351 if (connect_attempts_left_ <= 0) { | |
| 352 reconnect_timer_.Stop(); | |
| 353 | |
| 354 // Notify the caller that ICE connection has failed - normally that will | |
| 355 // terminate Jingle connection (i.e. the transport will be destroyed). | |
| 356 event_handler_->OnTransportFailed(this); | |
| 357 return; | |
| 358 } | |
| 359 --connect_attempts_left_; | |
| 360 | |
| 361 // Restart ICE by resetting ICE password. | |
| 362 std::string ice_password = rtc::CreateRandomString(cricket::ICE_PWD_LENGTH); | |
| 363 event_handler_->OnTransportIceCredentials(this, ice_username_fragment_, | |
| 364 ice_password); | |
| 365 channel_->SetIceCredentials(ice_username_fragment_, ice_password); | |
| 366 } | |
| 367 | |
| 368 } // namespace | |
| 369 | |
| 370 LibjingleTransportFactory::LibjingleTransportFactory( | |
| 371 SignalStrategy* signal_strategy, | |
| 372 scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator, | |
| 373 const NetworkSettings& network_settings, | |
| 374 TransportRole role) | |
| 375 : signal_strategy_(signal_strategy), | |
| 376 port_allocator_(port_allocator.Pass()), | |
| 377 network_settings_(network_settings), | |
| 378 role_(role) { | |
| 379 } | |
| 380 | |
| 381 LibjingleTransportFactory::~LibjingleTransportFactory() { | |
| 382 // This method may be called in response to a libjingle signal, so | |
| 383 // libjingle objects must be deleted asynchronously. | |
| 384 scoped_refptr<base::SingleThreadTaskRunner> task_runner = | |
| 385 base::ThreadTaskRunnerHandle::Get(); | |
| 386 task_runner->DeleteSoon(FROM_HERE, port_allocator_.release()); | |
| 387 } | |
| 388 | |
| 389 void LibjingleTransportFactory::PrepareTokens() { | |
| 390 EnsureFreshJingleInfo(); | |
| 391 } | |
| 392 | |
| 393 scoped_ptr<Transport> LibjingleTransportFactory::CreateTransport() { | |
| 394 scoped_ptr<LibjingleTransport> result( | |
| 395 new LibjingleTransport(port_allocator_.get(), network_settings_, role_)); | |
| 396 | |
| 397 EnsureFreshJingleInfo(); | |
| 398 | |
| 399 // If there is a pending |jingle_info_request_| delay starting the new | |
| 400 // transport until the request is finished. | |
| 401 if (jingle_info_request_) { | |
| 402 on_jingle_info_callbacks_.push_back( | |
| 403 base::Bind(&LibjingleTransport::OnCanStart, | |
| 404 result->AsWeakPtr())); | |
| 405 } else { | |
| 406 result->OnCanStart(); | |
| 407 } | |
| 408 | |
| 409 return result.Pass(); | |
| 410 } | |
| 411 | |
| 412 void LibjingleTransportFactory::EnsureFreshJingleInfo() { | |
| 413 uint32 stun_or_relay_flags = NetworkSettings::NAT_TRAVERSAL_STUN | | |
| 414 NetworkSettings::NAT_TRAVERSAL_RELAY; | |
| 415 if (!(network_settings_.flags & stun_or_relay_flags) || | |
| 416 jingle_info_request_) { | |
| 417 return; | |
| 418 } | |
| 419 | |
| 420 if (last_jingle_info_update_time_.is_null() || | |
| 421 base::TimeTicks::Now() - last_jingle_info_update_time_ > | |
| 422 base::TimeDelta::FromSeconds(kJingleInfoUpdatePeriodSeconds)) { | |
| 423 jingle_info_request_.reset(new JingleInfoRequest(signal_strategy_)); | |
| 424 jingle_info_request_->Send(base::Bind( | |
| 425 &LibjingleTransportFactory::OnJingleInfo, base::Unretained(this))); | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 void LibjingleTransportFactory::OnJingleInfo( | |
| 430 const std::string& relay_token, | |
| 431 const std::vector<std::string>& relay_hosts, | |
| 432 const std::vector<rtc::SocketAddress>& stun_hosts) { | |
| 433 if (!relay_token.empty() && !relay_hosts.empty()) { | |
| 434 port_allocator_->SetRelayHosts(relay_hosts); | |
| 435 port_allocator_->SetRelayToken(relay_token); | |
| 436 } | |
| 437 if (!stun_hosts.empty()) { | |
| 438 port_allocator_->SetStunHosts(stun_hosts); | |
| 439 } | |
| 440 | |
| 441 jingle_info_request_.reset(); | |
| 442 if ((!relay_token.empty() && !relay_hosts.empty()) || !stun_hosts.empty()) | |
| 443 last_jingle_info_update_time_ = base::TimeTicks::Now(); | |
| 444 | |
| 445 while (!on_jingle_info_callbacks_.empty()) { | |
| 446 on_jingle_info_callbacks_.begin()->Run(); | |
| 447 on_jingle_info_callbacks_.pop_front(); | |
| 448 } | |
| 449 } | |
| 450 | |
| 451 } // namespace protocol | |
| 452 } // namespace remoting | |
| OLD | NEW |