port_allocator.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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 // TODO(ronghuawu): crbug/138185 add ourselves to the firewall list in browser 78 // process and then remove below line. 79 set_allow_tcp_listen(false); 80} 81 82P2PPortAllocator::~P2PPortAllocator() { 83} 84 85cricket::PortAllocatorSession* P2PPortAllocator::CreateSessionInternal( 86 const std::string& content_name, 87 int component, 88 const std::string& ice_username_fragment, 89 const std::string& ice_password) { 90 return new P2PPortAllocatorSession( 91 this, content_name, component, ice_username_fragment, ice_password); 92} 93 94P2PPortAllocatorSession::P2PPortAllocatorSession( 95 P2PPortAllocator* allocator, 96 const std::string& content_name, 97 int component, 98 const std::string& ice_username_fragment, 99 const std::string& ice_password) 100 : cricket::BasicPortAllocatorSession( 101 allocator, content_name, component, 102 ice_username_fragment, ice_password), 103 allocator_(allocator), 104 relay_session_attempts_(0), 105 relay_udp_port_(0), 106 relay_tcp_port_(0), 107 relay_ssltcp_port_(0) { 108} 109 110P2PPortAllocatorSession::~P2PPortAllocatorSession() { 111 if (stun_address_request_) 112 stun_address_request_->Cancel(); 113} 114 115void P2PPortAllocatorSession::didReceiveData( 116 WebURLLoader* loader, const char* data, 117 int data_length, int encoded_data_length) { 118 DCHECK_EQ(loader, relay_session_request_.get()); 119 if (static_cast<int>(relay_session_response_.size()) + data_length > 120 kMaximumRelayResponseSize) { 121 LOG(ERROR) << "Response received from the server is too big."; 122 loader->cancel(); 123 return; 124 } 125 relay_session_response_.append(data, data + data_length); 126} 127 128void P2PPortAllocatorSession::didFinishLoading(WebURLLoader* loader, 129 double finish_time) { 130 ParseRelayResponse(); 131} 132 133void P2PPortAllocatorSession::didFail(WebKit::WebURLLoader* loader, 134 const WebKit::WebURLError& error) { 135 DCHECK_EQ(loader, relay_session_request_.get()); 136 DCHECK_NE(error.reason, 0); 137 138 LOG(ERROR) << "Relay session request failed."; 139 140 // Retry the request. 141 AllocateLegacyRelaySession(); 142} 143 144void P2PPortAllocatorSession::GetPortConfigurations() { 145 if (!allocator_->config_.stun_server.empty() && 146 stun_server_address_.IsNil()) { 147 ResolveStunServerAddress(); 148 } else { 149 AddConfig(); 150 } 151 152 if (allocator_->config_.legacy_relay) { 153 AllocateLegacyRelaySession(); 154 } 155} 156 157void P2PPortAllocatorSession::ResolveStunServerAddress() { 158 if (stun_address_request_) 159 return; 160 161 stun_address_request_ = 162 new P2PHostAddressRequest(allocator_->socket_dispatcher_); 163 stun_address_request_->Request(allocator_->config_.stun_server, base::Bind( 164 &P2PPortAllocatorSession::OnStunServerAddress, 165 base::Unretained(this))); 166} 167 168void P2PPortAllocatorSession::OnStunServerAddress( 169 const net::IPAddressNumber& address) { 170 if (address.empty()) { 171 LOG(ERROR) << "Failed to resolve STUN server address " 172 << allocator_->config_.stun_server; 173 // Allocating local ports on stun failure. 174 AddConfig(); 175 return; 176 } 177 178 if (!jingle_glue::IPEndPointToSocketAddress( 179 net::IPEndPoint(address, allocator_->config_.stun_server_port), 180 &stun_server_address_)) { 181 return; 182 } 183 184 AddConfig(); 185} 186 187void P2PPortAllocatorSession::AllocateLegacyRelaySession() { 188 if (allocator_->config_.relay_server.empty()) 189 return; 190 191 if (relay_session_attempts_ > kRelaySessionRetries) 192 return; 193 relay_session_attempts_++; 194 195 relay_session_response_.clear(); 196 197 WebURLLoaderOptions options; 198 options.allowCredentials = false; 199 200 options.crossOriginRequestPolicy = 201 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; 202 203 relay_session_request_.reset( 204 allocator_->web_frame_->createAssociatedURLLoader(options)); 205 if (!relay_session_request_) { 206 LOG(ERROR) << "Failed to create URL loader."; 207 return; 208 } 209 210 std::string url = "https://" + allocator_->config_.relay_server + 211 kCreateRelaySessionURL + 212 "?username=" + net::EscapeUrlEncodedData(username(), true) + 213 "&password=" + net::EscapeUrlEncodedData(password(), true); 214 215 WebURLRequest request; 216 request.initialize(); 217 request.setURL(WebURL(GURL(url))); 218 request.setAllowStoredCredentials(false); 219 request.setCachePolicy(WebURLRequest::ReloadIgnoringCacheData); 220 request.setHTTPMethod("GET"); 221 request.addHTTPHeaderField( 222 WebString::fromUTF8("X-Talk-Google-Relay-Auth"), 223 WebString::fromUTF8(allocator_->config_.relay_password)); 224 request.addHTTPHeaderField( 225 WebString::fromUTF8("X-Google-Relay-Auth"), 226 WebString::fromUTF8(allocator_->config_.relay_password)); 227 request.addHTTPHeaderField(WebString::fromUTF8("X-Stream-Type"), 228 WebString::fromUTF8("chromoting")); 229 230 relay_session_request_->loadAsynchronously(request, this); 231} 232 233void P2PPortAllocatorSession::ParseRelayResponse() { 234 std::vector<std::pair<std::string, std::string> > value_pairs; 235 if (!base::SplitStringIntoKeyValuePairs(relay_session_response_, '=', '\n', 236 &value_pairs)) { 237 LOG(ERROR) << "Received invalid response from relay server"; 238 return; 239 } 240 241 relay_ip_.Clear(); 242 relay_udp_port_ = 0; 243 relay_tcp_port_ = 0; 244 relay_ssltcp_port_ = 0; 245 246 for (std::vector<std::pair<std::string, std::string> >::iterator 247 it = value_pairs.begin(); 248 it != value_pairs.end(); ++it) { 249 std::string key; 250 std::string value; 251 TrimWhitespaceASCII(it->first, TRIM_ALL, &key); 252 TrimWhitespaceASCII(it->second, TRIM_ALL, &value); 253 254 if (key == "username") { 255 if (value != username()) { 256 LOG(ERROR) << "When creating relay session received user name " 257 " that was different from the value specified in the query."; 258 return; 259 } 260 } else if (key == "password") { 261 if (value != password()) { 262 LOG(ERROR) << "When creating relay session received password " 263 "that was different from the value specified in the query."; 264 return; 265 } 266 } else if (key == "relay.ip") { 267 relay_ip_.SetIP(value); 268 if (relay_ip_.ip() == 0) { 269 LOG(ERROR) << "Received unresolved relay server address: " << value; 270 return; 271 } 272 } else if (key == "relay.udp_port") { 273 if (!ParsePortNumber(value, &relay_udp_port_)) 274 return; 275 } else if (key == "relay.tcp_port") { 276 if (!ParsePortNumber(value, &relay_tcp_port_)) 277 return; 278 } else if (key == "relay.ssltcp_port") { 279 if (!ParsePortNumber(value, &relay_ssltcp_port_)) 280 return; 281 } 282 } 283 284 AddConfig(); 285} 286 287void P2PPortAllocatorSession::AddConfig() { 288 cricket::PortConfiguration* config = new cricket::PortConfiguration( 289 stun_server_address_, std::string(), std::string()); 290 291 if (allocator_->config_.legacy_relay) { 292 // Passing empty credentials for legacy google relay. 293 cricket::RelayServerConfig gturn_config(cricket::RELAY_GTURN); 294 if (relay_ip_.ip() != 0) { 295 if (relay_udp_port_ > 0) { 296 talk_base::SocketAddress address(relay_ip_.ip(), relay_udp_port_); 297 gturn_config.ports.push_back(cricket::ProtocolAddress( 298 address, cricket::PROTO_UDP)); 299 } 300 if (relay_tcp_port_ > 0 && !allocator_->config_.disable_tcp_transport) { 301 talk_base::SocketAddress address(relay_ip_.ip(), relay_tcp_port_); 302 gturn_config.ports.push_back(cricket::ProtocolAddress( 303 address, cricket::PROTO_TCP)); 304 } 305 if (relay_ssltcp_port_ > 0 && 306 !allocator_->config_.disable_tcp_transport) { 307 talk_base::SocketAddress address(relay_ip_.ip(), relay_ssltcp_port_); 308 gturn_config.ports.push_back(cricket::ProtocolAddress( 309 address, cricket::PROTO_SSLTCP)); 310 } 311 if (!gturn_config.ports.empty()) { 312 config->AddRelay(gturn_config); 313 } 314 } 315 } else { 316 if (!(allocator_->config_.relay_username.empty() || 317 allocator_->config_.relay_server.empty())) { 318 // Adding TURN related information to config. 319 // As per TURN RFC, same turn server should be used for stun as well. 320 // Configuration should have same address for both stun and turn. 321 DCHECK_EQ(allocator_->config_.stun_server, 322 allocator_->config_.relay_server); 323 cricket::RelayServerConfig turn_config(cricket::RELAY_TURN); 324 cricket::RelayCredentials credentials( 325 allocator_->config_.relay_username, 326 allocator_->config_.relay_password); 327 turn_config.credentials = credentials; 328 // Using the stun resolved address if available for TURN. 329 turn_config.ports.push_back(cricket::ProtocolAddress( 330 stun_server_address_, cricket::PROTO_UDP)); 331 config->AddRelay(turn_config); 332 } 333 } 334 ConfigReady(config); 335} 336 337} // namespace content 338