blob: cd81c218f4e194cef84d88dd10c15004a5bf9eed [file] [log] [blame]
// 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 <limits>
#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<JingleSession*>::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<const ContentDescription*>(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<JingleSession*>::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<std::string>& relay_hosts,
const std::vector<talk_base::SocketAddress>& 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<const ContentDescription*>(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