// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/protocol/jingle_session_manager.h" #include #include "base/bind.h" #include "base/message_loop_proxy.h" #include "base/string_util.h" #include "base/task.h" #include "remoting/base/constants.h" #include "remoting/jingle_glue/host_resolver.h" #include "remoting/jingle_glue/http_port_allocator.h" #include "remoting/jingle_glue/jingle_info_request.h" #include "remoting/jingle_glue/jingle_signaling_connector.h" #include "remoting/jingle_glue/signal_strategy.h" #include "third_party/libjingle/source/talk/base/basicpacketsocketfactory.h" #include "third_party/libjingle/source/talk/p2p/base/sessionmanager.h" #include "third_party/libjingle/source/talk/p2p/base/transport.h" #include "third_party/libjingle/source/talk/p2p/base/constants.h" #include "third_party/libjingle/source/talk/p2p/base/transport.h" #include "third_party/libjingle/source/talk/xmllite/xmlelement.h" using buzz::XmlElement; namespace remoting { namespace protocol { // static JingleSessionManager* JingleSessionManager::CreateNotSandboxed( base::MessageLoopProxy* message_loop) { return new JingleSessionManager(message_loop, NULL, NULL, NULL, NULL); } // static JingleSessionManager* JingleSessionManager::CreateSandboxed( base::MessageLoopProxy* message_loop, talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* port_allocator_session_factory) { return new JingleSessionManager(message_loop, network_manager, socket_factory, host_resolver_factory, port_allocator_session_factory); } JingleSessionManager::JingleSessionManager( base::MessageLoopProxy* message_loop, talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* port_allocator_session_factory) : message_loop_(message_loop), network_manager_(network_manager), socket_factory_(socket_factory), host_resolver_factory_(host_resolver_factory), port_allocator_session_factory_(port_allocator_session_factory), signal_strategy_(NULL), allow_nat_traversal_(false), allow_local_ips_(false), http_port_allocator_(NULL), closed_(false), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) { } JingleSessionManager::~JingleSessionManager() { Close(); } void JingleSessionManager::Init( const std::string& local_jid, SignalStrategy* signal_strategy, Listener* listener, crypto::RSAPrivateKey* private_key, const std::string& certificate, bool allow_nat_traversal) { DCHECK(CalledOnValidThread()); DCHECK(signal_strategy); DCHECK(listener); local_jid_ = local_jid; signal_strategy_ = signal_strategy; listener_ = listener; private_key_.reset(private_key); certificate_ = certificate; allow_nat_traversal_ = allow_nat_traversal; if (!network_manager_.get()) { VLOG(1) << "Creating talk_base::NetworkManager."; network_manager_.reset(new talk_base::BasicNetworkManager()); } if (!socket_factory_.get()) { VLOG(1) << "Creating talk_base::BasicPacketSocketFactory."; socket_factory_.reset(new talk_base::BasicPacketSocketFactory( talk_base::Thread::Current())); } // Initialize |port_allocator_|. // We always use PseudoTcp to provide a reliable channel. However // when it is used together with TCP the performance is very bad // so we explicitly disables TCP connections. int port_allocator_flags = cricket::PORTALLOCATOR_DISABLE_TCP; if (allow_nat_traversal) { http_port_allocator_ = new remoting::HttpPortAllocator( network_manager_.get(), socket_factory_.get(), port_allocator_session_factory_.get(), "transp2"); port_allocator_.reset(http_port_allocator_); } else { port_allocator_flags |= cricket::PORTALLOCATOR_DISABLE_STUN | cricket::PORTALLOCATOR_DISABLE_RELAY; port_allocator_.reset( new cricket::BasicPortAllocator( network_manager_.get(), socket_factory_.get())); } port_allocator_->set_flags(port_allocator_flags); // Initialize |cricket_session_manager_|. cricket_session_manager_.reset( new cricket::SessionManager(port_allocator_.get())); cricket_session_manager_->AddClient(kChromotingXmlNamespace, this); jingle_signaling_connector_.reset(new JingleSignalingConnector( signal_strategy_, cricket_session_manager_.get())); // If NAT traversal is enabled then we need to request STUN/Relay info. if (allow_nat_traversal) { jingle_info_request_.reset( new JingleInfoRequest(signal_strategy_->CreateIqRequest(), host_resolver_factory_.get())); jingle_info_request_->Send(base::Bind( &JingleSessionManager::OnJingleInfo, base::Unretained(this))); } else { listener_->OnSessionManagerInitialized(); } } void JingleSessionManager::Close() { DCHECK(CalledOnValidThread()); // Close() can be called only after all sessions are destroyed. DCHECK(sessions_.empty()); if (!closed_) { cricket_session_manager_->RemoveClient(kChromotingXmlNamespace); jingle_signaling_connector_.reset(); cricket_session_manager_.reset(); closed_ = true; } } void JingleSessionManager::set_allow_local_ips(bool allow_local_ips) { allow_local_ips_ = allow_local_ips; } Session* JingleSessionManager::Connect( const std::string& host_jid, const std::string& host_public_key, const std::string& receiver_token, CandidateSessionConfig* candidate_config, Session::StateChangeCallback* state_change_callback) { DCHECK(CalledOnValidThread()); // Can be called from any thread. JingleSession* jingle_session = JingleSession::CreateClientSession(this, host_public_key); jingle_session->set_candidate_config(candidate_config); jingle_session->set_receiver_token(receiver_token); cricket::Session* cricket_session = cricket_session_manager_->CreateSession( local_jid_, kChromotingXmlNamespace); // Initialize connection object before we send initiate stanza. jingle_session->SetStateChangeCallback(state_change_callback); jingle_session->Init(cricket_session); sessions_.push_back(jingle_session); cricket_session->Initiate(host_jid, CreateClientSessionDescription( jingle_session->candidate_config()->Clone(), receiver_token)); return jingle_session; } void JingleSessionManager::OnSessionCreate( cricket::Session* cricket_session, bool incoming) { DCHECK(CalledOnValidThread()); // Allow local connections if neccessary. cricket_session->set_allow_local_ips(allow_local_ips_); // If this is an incoming session, create a JingleSession on top of it. if (incoming) { DCHECK(!certificate_.empty()); DCHECK(private_key_.get()); JingleSession* jingle_session = JingleSession::CreateServerSession( this, certificate_, private_key_.get()); sessions_.push_back(jingle_session); jingle_session->Init(cricket_session); } } void JingleSessionManager::OnSessionDestroy(cricket::Session* cricket_session) { DCHECK(CalledOnValidThread()); std::list::iterator it; for (it = sessions_.begin(); it != sessions_.end(); ++it) { if ((*it)->HasSession(cricket_session)) { (*it)->ReleaseSession(); return; } } } bool JingleSessionManager::AcceptConnection( JingleSession* jingle_session, cricket::Session* cricket_session) { DCHECK(CalledOnValidThread()); // Reject connection if we are closed. if (closed_) { cricket_session->Reject(cricket::STR_TERMINATE_DECLINE); return false; } const cricket::SessionDescription* session_description = cricket_session->remote_description(); const cricket::ContentInfo* content = session_description->FirstContentByType(kChromotingXmlNamespace); CHECK(content); const ContentDescription* content_description = static_cast(content->description); jingle_session->set_candidate_config(content_description->config()->Clone()); jingle_session->set_initiator_token(content_description->auth_token()); // Always reject connection if there is no callback. IncomingSessionResponse response = protocol::SessionManager::DECLINE; // Use the callback to generate a response. listener_->OnIncomingSession(jingle_session, &response); switch (response) { case protocol::SessionManager::ACCEPT: { // Connection must be configured by the callback. CandidateSessionConfig* candidate_config = CandidateSessionConfig::CreateFrom(jingle_session->config()); cricket_session->Accept( CreateHostSessionDescription(candidate_config, jingle_session->local_certificate())); break; } case protocol::SessionManager::INCOMPATIBLE: { cricket_session->Reject(cricket::STR_TERMINATE_INCOMPATIBLE_PARAMETERS); return false; } case protocol::SessionManager::DECLINE: { cricket_session->Reject(cricket::STR_TERMINATE_DECLINE); return false; } default: { NOTREACHED(); } } return true; } void JingleSessionManager::SessionDestroyed(JingleSession* jingle_session) { std::list::iterator it = std::find(sessions_.begin(), sessions_.end(), jingle_session); CHECK(it != sessions_.end()); cricket::Session* cricket_session = jingle_session->ReleaseSession(); cricket_session_manager_->DestroySession(cricket_session); sessions_.erase(it); } void JingleSessionManager::OnJingleInfo( const std::string& token, const std::vector& relay_hosts, const std::vector& stun_hosts) { DCHECK(CalledOnValidThread()); if (http_port_allocator_) { // TODO(ajwong): Avoid string processing if log-level is low. std::string stun_servers; for (size_t i = 0; i < stun_hosts.size(); ++i) { stun_servers += stun_hosts[i].ToString() + "; "; } LOG(INFO) << "Configuring with relay token: " << token << ", relays: " << JoinString(relay_hosts, ';') << ", stun: " << stun_servers; http_port_allocator_->SetRelayToken(token); http_port_allocator_->SetStunHosts(stun_hosts); http_port_allocator_->SetRelayHosts(relay_hosts); } else { LOG(INFO) << "Jingle info found but no port allocator."; } listener_->OnSessionManagerInitialized(); } // Parse content description generated by WriteContent(). bool JingleSessionManager::ParseContent( cricket::SignalingProtocol protocol, const XmlElement* element, const cricket::ContentDescription** content, cricket::ParseError* error) { *content = ContentDescription::ParseXml(element); return *content != NULL; } bool JingleSessionManager::WriteContent( cricket::SignalingProtocol protocol, const cricket::ContentDescription* content, XmlElement** elem, cricket::WriteError* error) { const ContentDescription* desc = static_cast(content); *elem = desc->ToXml(); return true; } // static cricket::SessionDescription* JingleSessionManager::CreateClientSessionDescription( const CandidateSessionConfig* config, const std::string& auth_token) { cricket::SessionDescription* desc = new cricket::SessionDescription(); desc->AddContent( ContentDescription::kChromotingContentName, kChromotingXmlNamespace, new ContentDescription(config, auth_token, "")); return desc; } // static cricket::SessionDescription* JingleSessionManager::CreateHostSessionDescription( const CandidateSessionConfig* config, const std::string& certificate) { cricket::SessionDescription* desc = new cricket::SessionDescription(); desc->AddContent( ContentDescription::kChromotingContentName, kChromotingXmlNamespace, new ContentDescription(config, "", certificate)); return desc; } } // namespace protocol } // namespace remoting