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