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