port_allocator.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "content/renderer/p2p/port_allocator.h" 6 7#include "base/bind.h" 8#include "base/string_number_conversions.h" 9#include "base/string_util.h" 10#include "base/strings/string_split.h" 11#include "content/renderer/p2p/host_address_request.h" 12#include "jingle/glue/utils.h" 13#include "net/base/escape.h" 14#include "net/base/ip_endpoint.h" 15#include "third_party/WebKit/Source/Platform/chromium/public/WebURLError.h" 16#include "third_party/WebKit/Source/Platform/chromium/public/WebURLLoader.h" 17#include "third_party/WebKit/Source/Platform/chromium/public/WebURLRequest.h" 18#include "third_party/WebKit/Source/Platform/chromium/public/WebURLResponse.h" 19#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" 20#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h" 21 22using WebKit::WebString; 23using WebKit::WebURL; 24using WebKit::WebURLLoader; 25using WebKit::WebURLLoaderOptions; 26using WebKit::WebURLRequest; 27using WebKit::WebURLResponse; 28 29namespace content { 30 31namespace { 32 33// URL used to create a relay session. 34const char kCreateRelaySessionURL[] = "/create_session"; 35 36// Number of times we will try to request relay session. 37const int kRelaySessionRetries = 3; 38 39// Manimum relay server size we would try to parse. 40const int kMaximumRelayResponseSize = 102400; 41 42bool ParsePortNumber( 43 const std::string& string, int* value) { 44 if (!base::StringToInt(string, value) || *value <= 0 || *value >= 65536) { 45 LOG(ERROR) << "Received invalid port number from relay server: " << string; 46 return false; 47 } 48 return true; 49} 50 51} // namespace 52 53P2PPortAllocator::Config::Config() 54 : stun_server_port(0), 55 relay_server_port(0), 56 legacy_relay(true), 57 disable_tcp_transport(false) { 58} 59 60P2PPortAllocator::Config::~Config() { 61} 62 63P2PPortAllocator::P2PPortAllocator( 64 WebKit::WebFrame* web_frame, 65 P2PSocketDispatcher* socket_dispatcher, 66 talk_base::NetworkManager* network_manager, 67 talk_base::PacketSocketFactory* socket_factory, 68 const Config& config) 69 : cricket::BasicPortAllocator(network_manager, socket_factory), 70 web_frame_(web_frame), 71 socket_dispatcher_(socket_dispatcher), 72 config_(config) { 73 uint32 flags = 0; 74 if (config_.disable_tcp_transport) 75 flags |= cricket::PORTALLOCATOR_DISABLE_TCP; 76 set_flags(flags); 77} 78 79P2PPortAllocator::~P2PPortAllocator() { 80} 81 82cricket::PortAllocatorSession* P2PPortAllocator::CreateSessionInternal( 83 const std::string& content_name, 84 int component, 85 const std::string& ice_username_fragment, 86 const std::string& ice_password) { 87 return new P2PPortAllocatorSession( 88 this, content_name, component, ice_username_fragment, ice_password); 89} 90 91P2PPortAllocatorSession::P2PPortAllocatorSession( 92 P2PPortAllocator* allocator, 93 const std::string& content_name, 94 int component, 95 const std::string& ice_username_fragment, 96 const std::string& ice_password) 97 : cricket::BasicPortAllocatorSession( 98 allocator, content_name, component, 99 ice_username_fragment, ice_password), 100 allocator_(allocator), 101 relay_session_attempts_(0), 102 relay_udp_port_(0), 103 relay_tcp_port_(0), 104 relay_ssltcp_port_(0) { 105} 106 107P2PPortAllocatorSession::~P2PPortAllocatorSession() { 108 if (stun_address_request_) 109 stun_address_request_->Cancel(); 110} 111 112void P2PPortAllocatorSession::didReceiveData( 113 WebURLLoader* loader, const char* data, 114 int data_length, int encoded_data_length) { 115 DCHECK_EQ(loader, relay_session_request_.get()); 116 if (static_cast<int>(relay_session_response_.size()) + data_length > 117 kMaximumRelayResponseSize) { 118 LOG(ERROR) << "Response received from the server is too big."; 119 loader->cancel(); 120 return; 121 } 122 relay_session_response_.append(data, data + data_length); 123} 124 125void P2PPortAllocatorSession::didFinishLoading(WebURLLoader* loader, 126 double finish_time) { 127 ParseRelayResponse(); 128} 129 130void P2PPortAllocatorSession::didFail(WebKit::WebURLLoader* loader, 131 const WebKit::WebURLError& error) { 132 DCHECK_EQ(loader, relay_session_request_.get()); 133 DCHECK_NE(error.reason, 0); 134 135 LOG(ERROR) << "Relay session request failed."; 136 137 // Retry the request. 138 AllocateLegacyRelaySession(); 139} 140 141void P2PPortAllocatorSession::GetPortConfigurations() { 142 if (!allocator_->config_.stun_server.empty() && 143 stun_server_address_.IsNil()) { 144 ResolveStunServerAddress(); 145 } else { 146 AddConfig(); 147 } 148 149 if (allocator_->config_.legacy_relay) { 150 AllocateLegacyRelaySession(); 151 } 152} 153 154void P2PPortAllocatorSession::ResolveStunServerAddress() { 155 if (stun_address_request_) 156 return; 157 158 stun_address_request_ = 159 new P2PHostAddressRequest(allocator_->socket_dispatcher_); 160 stun_address_request_->Request(allocator_->config_.stun_server, base::Bind( 161 &P2PPortAllocatorSession::OnStunServerAddress, 162 base::Unretained(this))); 163} 164 165void P2PPortAllocatorSession::OnStunServerAddress( 166 const net::IPAddressNumber& address) { 167 if (address.empty()) { 168 LOG(ERROR) << "Failed to resolve STUN server address " 169 << allocator_->config_.stun_server; 170 // Allocating local ports on stun failure. 171 AddConfig(); 172 return; 173 } 174 175 if (!jingle_glue::IPEndPointToSocketAddress( 176 net::IPEndPoint(address, allocator_->config_.stun_server_port), 177 &stun_server_address_)) { 178 return; 179 } 180 181 AddConfig(); 182} 183 184void P2PPortAllocatorSession::AllocateLegacyRelaySession() { 185 if (allocator_->config_.relay_server.empty()) 186 return; 187 188 if (relay_session_attempts_ > kRelaySessionRetries) 189 return; 190 relay_session_attempts_++; 191 192 relay_session_response_.clear(); 193 194 WebURLLoaderOptions options; 195 options.allowCredentials = false; 196 197 options.crossOriginRequestPolicy = 198 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; 199 200 relay_session_request_.reset( 201 allocator_->web_frame_->createAssociatedURLLoader(options)); 202 if (!relay_session_request_.get()) { 203 LOG(ERROR) << "Failed to create URL loader."; 204 return; 205 } 206 207 std::string url = "https://" + allocator_->config_.relay_server + 208 kCreateRelaySessionURL + 209 "?username=" + net::EscapeUrlEncodedData(username(), true) + 210 "&password=" + net::EscapeUrlEncodedData(password(), true); 211 212 WebURLRequest request; 213 request.initialize(); 214 request.setURL(WebURL(GURL(url))); 215 request.setAllowStoredCredentials(false); 216 request.setCachePolicy(WebURLRequest::ReloadIgnoringCacheData); 217 request.setHTTPMethod("GET"); 218 request.addHTTPHeaderField( 219 WebString::fromUTF8("X-Talk-Google-Relay-Auth"), 220 WebString::fromUTF8(allocator_->config_.relay_password)); 221 request.addHTTPHeaderField( 222 WebString::fromUTF8("X-Google-Relay-Auth"), 223 WebString::fromUTF8(allocator_->config_.relay_password)); 224 request.addHTTPHeaderField(WebString::fromUTF8("X-Stream-Type"), 225 WebString::fromUTF8("chromoting")); 226 227 relay_session_request_->loadAsynchronously(request, this); 228} 229 230void P2PPortAllocatorSession::ParseRelayResponse() { 231 std::vector<std::pair<std::string, std::string> > value_pairs; 232 if (!base::SplitStringIntoKeyValuePairs(relay_session_response_, '=', '\n', 233 &value_pairs)) { 234 LOG(ERROR) << "Received invalid response from relay server"; 235 return; 236 } 237 238 relay_ip_.Clear(); 239 relay_udp_port_ = 0; 240 relay_tcp_port_ = 0; 241 relay_ssltcp_port_ = 0; 242 243 for (std::vector<std::pair<std::string, std::string> >::iterator 244 it = value_pairs.begin(); 245 it != value_pairs.end(); ++it) { 246 std::string key; 247 std::string value; 248 TrimWhitespaceASCII(it->first, TRIM_ALL, &key); 249 TrimWhitespaceASCII(it->second, TRIM_ALL, &value); 250 251 if (key == "username") { 252 if (value != username()) { 253 LOG(ERROR) << "When creating relay session received user name " 254 " that was different from the value specified in the query."; 255 return; 256 } 257 } else if (key == "password") { 258 if (value != password()) { 259 LOG(ERROR) << "When creating relay session received password " 260 "that was different from the value specified in the query."; 261 return; 262 } 263 } else if (key == "relay.ip") { 264 relay_ip_.SetIP(value); 265 if (relay_ip_.ip() == 0) { 266 LOG(ERROR) << "Received unresolved relay server address: " << value; 267 return; 268 } 269 } else if (key == "relay.udp_port") { 270 if (!ParsePortNumber(value, &relay_udp_port_)) 271 return; 272 } else if (key == "relay.tcp_port") { 273 if (!ParsePortNumber(value, &relay_tcp_port_)) 274 return; 275 } else if (key == "relay.ssltcp_port") { 276 if (!ParsePortNumber(value, &relay_ssltcp_port_)) 277 return; 278 } 279 } 280 281 AddConfig(); 282} 283 284void P2PPortAllocatorSession::AddConfig() { 285 cricket::PortConfiguration* config = 286 new cricket::PortConfiguration(stun_server_address_, "", ""); 287 288 if (allocator_->config_.legacy_relay) { 289 // Passing empty credentials for legacy google relay. 290 cricket::RelayServerConfig gturn_config(cricket::RELAY_GTURN); 291 if (relay_ip_.ip() != 0) { 292 if (relay_udp_port_ > 0) { 293 talk_base::SocketAddress address(relay_ip_.ip(), relay_udp_port_); 294 gturn_config.ports.push_back(cricket::ProtocolAddress( 295 address, cricket::PROTO_UDP)); 296 } 297 if (relay_tcp_port_ > 0 && !allocator_->config_.disable_tcp_transport) { 298 talk_base::SocketAddress address(relay_ip_.ip(), relay_tcp_port_); 299 gturn_config.ports.push_back(cricket::ProtocolAddress( 300 address, cricket::PROTO_TCP)); 301 } 302 if (relay_ssltcp_port_ > 0 && 303 !allocator_->config_.disable_tcp_transport) { 304 talk_base::SocketAddress address(relay_ip_.ip(), relay_ssltcp_port_); 305 gturn_config.ports.push_back(cricket::ProtocolAddress( 306 address, cricket::PROTO_SSLTCP)); 307 } 308 if (!gturn_config.ports.empty()) { 309 config->AddRelay(gturn_config); 310 } 311 } 312 } else { 313 if (!(allocator_->config_.relay_username.empty() || 314 allocator_->config_.relay_server.empty())) { 315 // Adding TURN related information to config. 316 // As per TURN RFC, same turn server should be used for stun as well. 317 // Configuration should have same address for both stun and turn. 318 DCHECK_EQ(allocator_->config_.stun_server, 319 allocator_->config_.relay_server); 320 cricket::RelayServerConfig turn_config(cricket::RELAY_TURN); 321 cricket::RelayCredentials credentials( 322 allocator_->config_.relay_username, 323 allocator_->config_.relay_password); 324 turn_config.credentials = credentials; 325 // Using the stun resolved address if available for TURN. 326 turn_config.ports.push_back(cricket::ProtocolAddress( 327 stun_server_address_, cricket::PROTO_UDP)); 328 config->AddRelay(turn_config); 329 } 330 } 331 ConfigReady(config); 332} 333 334} // namespace content 335