blob: f1f68c084a9c9af1733bdfe92705554020153430 [file] [log] [blame]
// Copyright (c) 2012 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 "content/renderer/p2p/port_allocator.h"
#include "base/bind.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "content/renderer/p2p/host_address_request.h"
#include "jingle/glue/utils.h"
#include "net/base/escape.h"
#include "net/base/ip_endpoint.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLError.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLLoader.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLRequest.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLResponse.h"
using WebKit::WebString;
using WebKit::WebURL;
using WebKit::WebURLLoader;
using WebKit::WebURLLoaderOptions;
using WebKit::WebURLRequest;
using WebKit::WebURLResponse;
namespace content {
namespace {
// URL used to create a relay session.
const char kCreateRelaySessionURL[] = "/create_session";
// Number of times we will try to request relay session.
const int kRelaySessionRetries = 3;
// Manimum relay server size we would try to parse.
const int kMaximumRelayResponseSize = 102400;
bool ParsePortNumber(
const std::string& string, int* value) {
if (!base::StringToInt(string, value) || *value <= 0 || *value >= 65536) {
LOG(ERROR) << "Received invalid port number from relay server: " << string;
return false;
}
return true;
}
} // namespace
P2PPortAllocator::Config::Config()
: stun_server_port(0),
relay_server_port(0),
legacy_relay(false),
disable_tcp_transport(false) {
}
P2PPortAllocator::Config::~Config() {
}
P2PPortAllocator::P2PPortAllocator(
WebKit::WebFrame* web_frame,
P2PSocketDispatcher* socket_dispatcher,
talk_base::NetworkManager* network_manager,
talk_base::PacketSocketFactory* socket_factory,
const Config& config)
: cricket::BasicPortAllocator(network_manager, socket_factory),
web_frame_(web_frame),
socket_dispatcher_(socket_dispatcher),
config_(config) {
uint32 flags = 0;
if (config_.disable_tcp_transport)
flags |= cricket::PORTALLOCATOR_DISABLE_TCP;
set_flags(flags);
}
P2PPortAllocator::~P2PPortAllocator() {
}
cricket::PortAllocatorSession* P2PPortAllocator::CreateSessionInternal(
const std::string& content_name,
int component,
const std::string& ice_username_fragment,
const std::string& ice_password) {
return new P2PPortAllocatorSession(
this, content_name, component, ice_username_fragment, ice_password);
}
P2PPortAllocatorSession::P2PPortAllocatorSession(
P2PPortAllocator* allocator,
const std::string& content_name,
int component,
const std::string& ice_username_fragment,
const std::string& ice_password)
: cricket::BasicPortAllocatorSession(
allocator, content_name, component,
ice_username_fragment, ice_password),
allocator_(allocator),
relay_session_attempts_(0),
relay_udp_port_(0),
relay_tcp_port_(0),
relay_ssltcp_port_(0) {
}
P2PPortAllocatorSession::~P2PPortAllocatorSession() {
if (stun_address_request_)
stun_address_request_->Cancel();
}
void P2PPortAllocatorSession::didReceiveData(
WebURLLoader* loader, const char* data,
int data_length, int encoded_data_length) {
DCHECK_EQ(loader, relay_session_request_.get());
if (static_cast<int>(relay_session_response_.size()) + data_length >
kMaximumRelayResponseSize) {
LOG(ERROR) << "Response received from the server is too big.";
loader->cancel();
return;
}
relay_session_response_.append(data, data + data_length);
}
void P2PPortAllocatorSession::didFinishLoading(WebURLLoader* loader,
double finish_time) {
ParseRelayResponse();
}
void P2PPortAllocatorSession::didFail(WebKit::WebURLLoader* loader,
const WebKit::WebURLError& error) {
DCHECK_EQ(loader, relay_session_request_.get());
DCHECK_NE(error.reason, 0);
LOG(ERROR) << "Relay session request failed.";
// Retry the request.
AllocateRelaySession();
}
void P2PPortAllocatorSession::GetPortConfigurations() {
// Add an empty configuration synchronously, so a local connection
// can be started immediately.
ConfigReady(new cricket::PortConfiguration(talk_base::SocketAddress(),
"", ""));
if (stun_server_address_.IsNil()) {
ResolveStunServerAddress();
} else {
AddConfig();
}
AllocateRelaySession();
}
void P2PPortAllocatorSession::ResolveStunServerAddress() {
if (allocator_->config_.stun_server.empty())
return;
if (stun_address_request_)
return;
stun_address_request_ =
new P2PHostAddressRequest(allocator_->socket_dispatcher_);
stun_address_request_->Request(allocator_->config_.stun_server, base::Bind(
&P2PPortAllocatorSession::OnStunServerAddress,
base::Unretained(this)));
}
void P2PPortAllocatorSession::OnStunServerAddress(
const net::IPAddressNumber& address) {
if (address.empty()) {
LOG(ERROR) << "Failed to resolve STUN server address "
<< allocator_->config_.stun_server;
return;
}
if (!jingle_glue::IPEndPointToSocketAddress(
net::IPEndPoint(address, allocator_->config_.stun_server_port),
&stun_server_address_)) {
return;
}
AddConfig();
}
void P2PPortAllocatorSession::AllocateRelaySession() {
if (allocator_->config_.relay_server.empty())
return;
if (!allocator_->config_.legacy_relay) {
NOTIMPLEMENTED() << " TURN support is not implemented yet.";
return;
}
if (relay_session_attempts_ > kRelaySessionRetries)
return;
relay_session_attempts_++;
relay_session_response_.clear();
WebURLLoaderOptions options;
options.allowCredentials = false;
options.crossOriginRequestPolicy =
WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
relay_session_request_.reset(
allocator_->web_frame_->createAssociatedURLLoader(options));
if (!relay_session_request_.get()) {
LOG(ERROR) << "Failed to create URL loader.";
return;
}
std::string url = "https://" + allocator_->config_.relay_server +
kCreateRelaySessionURL +
"?username=" + net::EscapeUrlEncodedData(username(), true) +
"&password=" + net::EscapeUrlEncodedData(password(), true);
WebURLRequest request;
request.initialize();
request.setURL(WebURL(GURL(url)));
request.setAllowStoredCredentials(false);
request.setCachePolicy(WebURLRequest::ReloadIgnoringCacheData);
request.setHTTPMethod("GET");
request.addHTTPHeaderField(
WebString::fromUTF8("X-Talk-Google-Relay-Auth"),
WebString::fromUTF8(allocator_->config_.relay_password));
request.addHTTPHeaderField(
WebString::fromUTF8("X-Google-Relay-Auth"),
WebString::fromUTF8(allocator_->config_.relay_password));
request.addHTTPHeaderField(WebString::fromUTF8("X-Stream-Type"),
WebString::fromUTF8("chromoting"));
relay_session_request_->loadAsynchronously(request, this);
}
void P2PPortAllocatorSession::ParseRelayResponse() {
std::vector<std::pair<std::string, std::string> > value_pairs;
if (!base::SplitStringIntoKeyValuePairs(relay_session_response_, '=', '\n',
&value_pairs)) {
LOG(ERROR) << "Received invalid response from relay server";
return;
}
relay_ip_.Clear();
relay_udp_port_ = 0;
relay_tcp_port_ = 0;
relay_ssltcp_port_ = 0;
for (std::vector<std::pair<std::string, std::string> >::iterator
it = value_pairs.begin();
it != value_pairs.end(); ++it) {
std::string key;
std::string value;
TrimWhitespaceASCII(it->first, TRIM_ALL, &key);
TrimWhitespaceASCII(it->second, TRIM_ALL, &value);
if (key == "username") {
if (value != username()) {
LOG(ERROR) << "When creating relay session received user name "
" that was different from the value specified in the query.";
return;
}
} else if (key == "password") {
if (value != password()) {
LOG(ERROR) << "When creating relay session received password "
"that was different from the value specified in the query.";
return;
}
} else if (key == "relay.ip") {
relay_ip_.SetIP(value);
if (relay_ip_.ip() == 0) {
LOG(ERROR) << "Received unresolved relay server address: " << value;
return;
}
} else if (key == "relay.udp_port") {
if (!ParsePortNumber(value, &relay_udp_port_))
return;
} else if (key == "relay.tcp_port") {
if (!ParsePortNumber(value, &relay_tcp_port_))
return;
} else if (key == "relay.ssltcp_port") {
if (!ParsePortNumber(value, &relay_ssltcp_port_))
return;
}
}
AddConfig();
}
void P2PPortAllocatorSession::AddConfig() {
cricket::PortConfiguration* config =
new cricket::PortConfiguration(stun_server_address_, "", "");
if (relay_ip_.ip() != 0) {
cricket::PortConfiguration::PortList ports;
if (relay_udp_port_ > 0) {
talk_base::SocketAddress address(relay_ip_.ip(), relay_udp_port_);
ports.push_back(cricket::ProtocolAddress(address, cricket::PROTO_UDP));
}
if (relay_tcp_port_ > 0 && !allocator_->config_.disable_tcp_transport) {
talk_base::SocketAddress address(relay_ip_.ip(), relay_tcp_port_);
ports.push_back(cricket::ProtocolAddress(address, cricket::PROTO_TCP));
}
if (relay_ssltcp_port_ > 0 && !allocator_->config_.disable_tcp_transport) {
talk_base::SocketAddress address(relay_ip_.ip(), relay_ssltcp_port_);
ports.push_back(cricket::ProtocolAddress(address, cricket::PROTO_SSLTCP));
}
if (!ports.empty())
config->AddRelay(ports, 0.0f);
}
ConfigReady(config);
}
} // namespace content