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