// 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.h" #include "base/bind.h" #include "base/location.h" #include "base/message_loop_proxy.h" #include "base/rand_util.h" #include "base/stl_util.h" #include "crypto/hmac.h" #include "crypto/rsa_private_key.h" #include "net/base/net_errors.h" #include "net/socket/stream_socket.h" #include "remoting/base/constants.h" #include "remoting/protocol/jingle_datagram_connector.h" #include "remoting/protocol/jingle_session_manager.h" #include "remoting/protocol/jingle_stream_connector.h" #include "third_party/libjingle/source/talk/base/thread.h" #include "third_party/libjingle/source/talk/p2p/base/p2ptransportchannel.h" #include "third_party/libjingle/source/talk/p2p/base/session.h" #include "third_party/libjingle/source/talk/p2p/base/transport.h" using cricket::BaseSession; namespace remoting { namespace protocol { // static JingleSession* JingleSession::CreateClientSession( JingleSessionManager* manager, const std::string& host_public_key) { return new JingleSession(manager, "", NULL, host_public_key); } // static JingleSession* JingleSession::CreateServerSession( JingleSessionManager* manager, const std::string& certificate, crypto::RSAPrivateKey* key) { return new JingleSession(manager, certificate, key, ""); } JingleSession::JingleSession( JingleSessionManager* jingle_session_manager, const std::string& local_cert, crypto::RSAPrivateKey* local_private_key, const std::string& peer_public_key) : jingle_session_manager_(jingle_session_manager), local_cert_(local_cert), state_(INITIALIZING), error_(OK), closing_(false), cricket_session_(NULL), config_set_(false), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) { // TODO(hclam): Need a better way to clone a key. if (local_private_key) { std::vector key_bytes; CHECK(local_private_key->ExportPrivateKey(&key_bytes)); local_private_key_.reset( crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_bytes)); CHECK(local_private_key_.get()); } } JingleSession::~JingleSession() { // Reset the callback so that it's not called from Close(). state_change_callback_.Reset(); Close(); jingle_session_manager_->SessionDestroyed(this); } void JingleSession::Init(cricket::Session* cricket_session) { DCHECK(CalledOnValidThread()); cricket_session_ = cricket_session; jid_ = cricket_session_->remote_name(); cricket_session_->SignalState.connect( this, &JingleSession::OnSessionState); cricket_session_->SignalError.connect( this, &JingleSession::OnSessionError); } void JingleSession::CloseInternal(int result, bool failed) { DCHECK(CalledOnValidThread()); if (state_ != FAILED && state_ != CLOSED && !closing_) { closing_ = true; control_channel_socket_.reset(); event_channel_socket_.reset(); STLDeleteContainerPairSecondPointers(channel_connectors_.begin(), channel_connectors_.end()); // Tear down the cricket session, including the cricket transport channels. if (cricket_session_) { cricket_session_->Terminate(); cricket_session_->SignalState.disconnect(this); } // Inform the StateChangeCallback, so calling code knows not to // touch any channels. Needs to be done in the end because the // session may be deleted in response to this event. if (failed) SetState(FAILED); else SetState(CLOSED); } } bool JingleSession::HasSession(cricket::Session* cricket_session) { DCHECK(CalledOnValidThread()); return cricket_session_ == cricket_session; } cricket::Session* JingleSession::ReleaseSession() { DCHECK(CalledOnValidThread()); // Session may be destroyed only after it is closed. DCHECK(state_ == FAILED || state_ == CLOSED); cricket::Session* session = cricket_session_; if (cricket_session_) cricket_session_->SignalState.disconnect(this); cricket_session_ = NULL; return session; } void JingleSession::SetStateChangeCallback( const StateChangeCallback& callback) { DCHECK(CalledOnValidThread()); DCHECK(!callback.is_null()); state_change_callback_ = callback; } Session::Error JingleSession::error() { DCHECK(CalledOnValidThread()); return error_; } void JingleSession::CreateStreamChannel( const std::string& name, const StreamChannelCallback& callback) { DCHECK(CalledOnValidThread()); AddChannelConnector( name, new JingleStreamConnector(this, name, callback)); } void JingleSession::CreateDatagramChannel( const std::string& name, const DatagramChannelCallback& callback) { DCHECK(CalledOnValidThread()); AddChannelConnector( name, new JingleDatagramConnector(this, name, callback)); } net::Socket* JingleSession::control_channel() { DCHECK(CalledOnValidThread()); return control_channel_socket_.get(); } net::Socket* JingleSession::event_channel() { DCHECK(CalledOnValidThread()); return event_channel_socket_.get(); } const std::string& JingleSession::jid() { DCHECK(CalledOnValidThread()); return jid_; } const CandidateSessionConfig* JingleSession::candidate_config() { DCHECK(CalledOnValidThread()); DCHECK(candidate_config_.get()); return candidate_config_.get(); } void JingleSession::set_candidate_config( const CandidateSessionConfig* candidate_config) { DCHECK(CalledOnValidThread()); DCHECK(!candidate_config_.get()); DCHECK(candidate_config); candidate_config_.reset(candidate_config); } const std::string& JingleSession::local_certificate() const { DCHECK(CalledOnValidThread()); return local_cert_; } const SessionConfig& JingleSession::config() { DCHECK(CalledOnValidThread()); DCHECK(config_set_); return config_; } void JingleSession::set_config(const SessionConfig& config) { DCHECK(CalledOnValidThread()); DCHECK(!config_set_); config_ = config; config_set_ = true; } const std::string& JingleSession::initiator_token() { DCHECK(CalledOnValidThread()); return initiator_token_; } void JingleSession::set_initiator_token(const std::string& initiator_token) { DCHECK(CalledOnValidThread()); initiator_token_ = initiator_token; } const std::string& JingleSession::receiver_token() { DCHECK(CalledOnValidThread()); return receiver_token_; } void JingleSession::set_receiver_token(const std::string& receiver_token) { DCHECK(CalledOnValidThread()); receiver_token_ = receiver_token; } void JingleSession::set_shared_secret(const std::string& secret) { DCHECK(CalledOnValidThread()); shared_secret_ = secret; } const std::string& JingleSession::shared_secret() { DCHECK(CalledOnValidThread()); return shared_secret_; } void JingleSession::Close() { DCHECK(CalledOnValidThread()); CloseInternal(net::ERR_CONNECTION_CLOSED, false); } void JingleSession::OnSessionState( BaseSession* session, BaseSession::State state) { DCHECK(CalledOnValidThread()); DCHECK_EQ(cricket_session_, session); if (state_ == FAILED || state_ == CLOSED) { // Don't do anything if we already closed. return; } switch (state) { case cricket::Session::STATE_SENTINITIATE: case cricket::Session::STATE_RECEIVEDINITIATE: OnInitiate(); break; case cricket::Session::STATE_SENTACCEPT: case cricket::Session::STATE_RECEIVEDACCEPT: OnAccept(); break; case cricket::Session::STATE_SENTTERMINATE: case cricket::Session::STATE_RECEIVEDTERMINATE: case cricket::Session::STATE_SENTREJECT: case cricket::Session::STATE_RECEIVEDREJECT: OnTerminate(); break; case cricket::Session::STATE_DEINIT: // Close() must have been called before this. NOTREACHED(); break; default: // We don't care about other steates. break; } } void JingleSession::OnSessionError( BaseSession* session, BaseSession::Error error) { DCHECK(CalledOnValidThread()); DCHECK_EQ(cricket_session_, session); if (error != cricket::Session::ERROR_NONE) { CloseInternal(net::ERR_CONNECTION_ABORTED, true); } } void JingleSession::OnInitiate() { DCHECK(CalledOnValidThread()); jid_ = cricket_session_->remote_name(); if (!cricket_session_->initiator()) { const protocol::ContentDescription* content_description = static_cast( GetContentInfo()->description); CHECK(content_description); } if (cricket_session_->initiator()) { // Set state to CONNECTING if this is an outgoing message. We need // to post this task because channel creation works only after we // return from this method. This is because // JingleChannelConnector::Connect() needs to call // set_incoming_only() on P2PTransportChannel, but // P2PTransportChannel is created only after we return from this // method. // TODO(sergeyu): Add set_incoming_only() in TransportChannelProxy. jingle_session_manager_->message_loop_->PostTask( FROM_HERE, task_factory_.NewRunnableMethod( &JingleSession::SetState, CONNECTING)); } else { jingle_session_manager_->message_loop_->PostTask( FROM_HERE, task_factory_.NewRunnableMethod( &JingleSession::AcceptConnection)); } } bool JingleSession::InitializeConfigFromDescription( const cricket::SessionDescription* description) { // We should only be called after ParseContent has succeeded, in which // case there will always be a Chromoting session configuration. const cricket::ContentInfo* content = description->FirstContentByType(kChromotingXmlNamespace); CHECK(content); const protocol::ContentDescription* content_description = static_cast(content->description); CHECK(content_description); remote_cert_ = content_description->certificate(); if (remote_cert_.empty()) { LOG(ERROR) << "Connection response does not specify certificate"; return false; } SessionConfig config; if (!content_description->config()->GetFinalConfig(&config)) { LOG(ERROR) << "Connection response does not specify configuration"; return false; } if (!candidate_config()->IsSupported(config)) { LOG(ERROR) << "Connection response specifies an invalid configuration"; return false; } set_config(config); return true; } void JingleSession::OnAccept() { DCHECK(CalledOnValidThread()); // If we initiated the session, store the candidate configuration that the // host responded with, to refer to later. if (cricket_session_->initiator()) { if (!InitializeConfigFromDescription( cricket_session_->remote_description())) { CloseInternal(net::ERR_CONNECTION_FAILED, true); return; } } CreateChannels(); SetState(CONNECTED); } void JingleSession::OnTerminate() { DCHECK(CalledOnValidThread()); CloseInternal(net::ERR_CONNECTION_ABORTED, false); } void JingleSession::AcceptConnection() { SetState(CONNECTING); if (!jingle_session_manager_->AcceptConnection(this, cricket_session_)) { Close(); // Release session so that JingleSessionManager::SessionDestroyed() // doesn't try to call cricket::SessionManager::DestroySession() for it. ReleaseSession(); delete this; return; } } void JingleSession::AddChannelConnector( const std::string& name, JingleChannelConnector* connector) { DCHECK(channel_connectors_.find(name) == channel_connectors_.end()); const std::string& content_name = GetContentInfo()->name; cricket::TransportChannel* raw_channel = cricket_session_->CreateChannel(content_name, name); if (!jingle_session_manager_->allow_nat_traversal_ && !cricket_session_->initiator()) { // Don't make outgoing connections from the host to client when // NAT traversal is disabled. raw_channel->GetP2PChannel()->set_incoming_only(true); } channel_connectors_[name] = connector; connector->Connect(cricket_session_->initiator(), local_cert_, remote_cert_, local_private_key_.get(), raw_channel); // Workaround bug in libjingle - it doesn't connect channels if they // are created after the session is accepted. See crbug.com/89384. // TODO(sergeyu): Fix the bug and remove this line. cricket_session_->GetTransport(content_name)->ConnectChannels(); } void JingleSession::OnChannelConnectorFinished( const std::string& name, JingleChannelConnector* connector) { DCHECK(CalledOnValidThread()); DCHECK_EQ(channel_connectors_[name], connector); channel_connectors_[name] = NULL; } void JingleSession::CreateChannels() { CreateStreamChannel( kControlChannelName, base::Bind(&JingleSession::OnChannelConnected, base::Unretained(this), &control_channel_socket_)); CreateStreamChannel( kEventChannelName, base::Bind(&JingleSession::OnChannelConnected, base::Unretained(this), &event_channel_socket_)); } void JingleSession::OnChannelConnected( scoped_ptr* socket_container, net::StreamSocket* socket) { if (!socket) { LOG(ERROR) << "Failed to connect control or events channel. " << "Terminating connection"; CloseInternal(net::ERR_CONNECTION_CLOSED, true); return; } socket_container->reset(socket); if (control_channel_socket_.get() && event_channel_socket_.get()) SetState(CONNECTED_CHANNELS); } const cricket::ContentInfo* JingleSession::GetContentInfo() const { const cricket::SessionDescription* session_description; // If we initiate the session, we get to specify the content name. When // accepting one, the remote end specifies it. if (cricket_session_->initiator()) { session_description = cricket_session_->local_description(); } else { session_description = cricket_session_->remote_description(); } const cricket::ContentInfo* content = session_description->FirstContentByType(kChromotingXmlNamespace); CHECK(content); return content; } void JingleSession::SetState(State new_state) { DCHECK(CalledOnValidThread()); if (new_state != state_) { DCHECK_NE(state_, CLOSED); DCHECK_NE(state_, FAILED); state_ = new_state; if (!state_change_callback_.is_null()) state_change_callback_.Run(new_state); } } } // namespace protocol } // namespace remoting