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 "net/base/escape.h"
14#include "net/base/ip_endpoint.h"
15#include "third_party/WebKit/public/platform/WebURLError.h"
16#include "third_party/WebKit/public/platform/WebURLLoader.h"
17#include "third_party/WebKit/public/platform/WebURLRequest.h"
18#include "third_party/WebKit/public/platform/WebURLResponse.h"
19#include "third_party/WebKit/public/web/WebFrame.h"
20#include "third_party/WebKit/public/web/WebURLLoaderOptions.h"
21
22using blink::WebString;
23using blink::WebURL;
24using blink::WebURLLoader;
25using blink::WebURLLoaderOptions;
26using blink::WebURLRequest;
27using blink::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      legacy_relay(true),
56      disable_tcp_transport(false) {
57}
58
59P2PPortAllocator::Config::~Config() {
60}
61
62P2PPortAllocator::Config::RelayServerConfig::RelayServerConfig()
63    : port(0) {
64}
65
66P2PPortAllocator::Config::RelayServerConfig::~RelayServerConfig() {
67}
68
69P2PPortAllocator::P2PPortAllocator(
70    blink::WebFrame* web_frame,
71    P2PSocketDispatcher* socket_dispatcher,
72    talk_base::NetworkManager* network_manager,
73    talk_base::PacketSocketFactory* socket_factory,
74    const Config& config)
75    : cricket::BasicPortAllocator(network_manager, socket_factory),
76      web_frame_(web_frame),
77      socket_dispatcher_(socket_dispatcher),
78      config_(config) {
79  uint32 flags = 0;
80  if (config_.disable_tcp_transport)
81    flags |= cricket::PORTALLOCATOR_DISABLE_TCP;
82  set_flags(flags);
83  set_allow_tcp_listen(false);
84}
85
86P2PPortAllocator::~P2PPortAllocator() {
87}
88
89cricket::PortAllocatorSession* P2PPortAllocator::CreateSessionInternal(
90    const std::string& content_name,
91    int component,
92    const std::string& ice_username_fragment,
93    const std::string& ice_password) {
94  return new P2PPortAllocatorSession(
95      this, content_name, component, ice_username_fragment, ice_password);
96}
97
98P2PPortAllocatorSession::P2PPortAllocatorSession(
99    P2PPortAllocator* allocator,
100    const std::string& content_name,
101    int component,
102    const std::string& ice_username_fragment,
103    const std::string& ice_password)
104    : cricket::BasicPortAllocatorSession(
105        allocator, content_name, component,
106        ice_username_fragment, ice_password),
107      allocator_(allocator),
108      relay_session_attempts_(0),
109      relay_udp_port_(0),
110      relay_tcp_port_(0),
111      relay_ssltcp_port_(0),
112      pending_relay_requests_(0) {
113}
114
115P2PPortAllocatorSession::~P2PPortAllocatorSession() {
116}
117
118void P2PPortAllocatorSession::didReceiveData(
119    WebURLLoader* loader, const char* data,
120    int data_length, int encoded_data_length) {
121  DCHECK_EQ(loader, relay_session_request_.get());
122  if (static_cast<int>(relay_session_response_.size()) + data_length >
123      kMaximumRelayResponseSize) {
124    LOG(ERROR) << "Response received from the server is too big.";
125    loader->cancel();
126    return;
127  }
128  relay_session_response_.append(data, data + data_length);
129}
130
131void P2PPortAllocatorSession::didFinishLoading(
132    WebURLLoader* loader, double finish_time,
133    int64_t total_encoded_data_length) {
134  ParseRelayResponse();
135}
136
137void P2PPortAllocatorSession::didFail(blink::WebURLLoader* loader,
138                                      const blink::WebURLError& error) {
139  DCHECK_EQ(loader, relay_session_request_.get());
140  DCHECK_NE(error.reason, 0);
141
142  LOG(ERROR) << "Relay session request failed.";
143
144  // Retry the request.
145  AllocateLegacyRelaySession();
146}
147
148void P2PPortAllocatorSession::GetPortConfigurations() {
149  if (allocator_->config_.legacy_relay) {
150    AllocateLegacyRelaySession();
151  }
152  AddConfig();
153}
154
155void P2PPortAllocatorSession::AllocateLegacyRelaySession() {
156  if (allocator_->config_.relays.empty())
157    return;
158  // If we are using legacy relay, we will have only one entry in relay server
159  // list.
160  P2PPortAllocator::Config::RelayServerConfig relay_config =
161      allocator_->config_.relays[0];
162
163  if (relay_session_attempts_ > kRelaySessionRetries)
164    return;
165  relay_session_attempts_++;
166
167  relay_session_response_.clear();
168
169  WebURLLoaderOptions options;
170  options.allowCredentials = false;
171
172  options.crossOriginRequestPolicy =
173      WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
174
175  relay_session_request_.reset(
176      allocator_->web_frame_->createAssociatedURLLoader(options));
177  if (!relay_session_request_) {
178    LOG(ERROR) << "Failed to create URL loader.";
179    return;
180  }
181
182  std::string url = "https://" + relay_config.server_address +
183      kCreateRelaySessionURL +
184      "?username=" + net::EscapeUrlEncodedData(username(), true) +
185      "&password=" + net::EscapeUrlEncodedData(password(), true);
186
187  WebURLRequest request;
188  request.initialize();
189  request.setURL(WebURL(GURL(url)));
190  request.setAllowStoredCredentials(false);
191  request.setCachePolicy(WebURLRequest::ReloadIgnoringCacheData);
192  request.setHTTPMethod("GET");
193  request.addHTTPHeaderField(
194      WebString::fromUTF8("X-Talk-Google-Relay-Auth"),
195      WebString::fromUTF8(relay_config.password));
196  request.addHTTPHeaderField(
197      WebString::fromUTF8("X-Google-Relay-Auth"),
198      WebString::fromUTF8(relay_config.username));
199  request.addHTTPHeaderField(WebString::fromUTF8("X-Stream-Type"),
200                             WebString::fromUTF8("chromoting"));
201
202  relay_session_request_->loadAsynchronously(request, this);
203}
204
205void P2PPortAllocatorSession::ParseRelayResponse() {
206  std::vector<std::pair<std::string, std::string> > value_pairs;
207  if (!base::SplitStringIntoKeyValuePairs(relay_session_response_, '=', '\n',
208                                          &value_pairs)) {
209    LOG(ERROR) << "Received invalid response from relay server";
210    return;
211  }
212
213  relay_ip_.Clear();
214  relay_udp_port_ = 0;
215  relay_tcp_port_ = 0;
216  relay_ssltcp_port_ = 0;
217
218  for (std::vector<std::pair<std::string, std::string> >::iterator
219           it = value_pairs.begin();
220       it != value_pairs.end(); ++it) {
221    std::string key;
222    std::string value;
223    base::TrimWhitespaceASCII(it->first, base::TRIM_ALL, &key);
224    base::TrimWhitespaceASCII(it->second, base::TRIM_ALL, &value);
225
226    if (key == "username") {
227      if (value != username()) {
228        LOG(ERROR) << "When creating relay session received user name "
229            " that was different from the value specified in the query.";
230        return;
231      }
232    } else if (key == "password") {
233      if (value != password()) {
234        LOG(ERROR) << "When creating relay session received password "
235            "that was different from the value specified in the query.";
236        return;
237      }
238    } else if (key == "relay.ip") {
239      relay_ip_.SetIP(value);
240      if (relay_ip_.ip() == 0) {
241        LOG(ERROR) << "Received unresolved relay server address: " << value;
242        return;
243      }
244    } else if (key == "relay.udp_port") {
245      if (!ParsePortNumber(value, &relay_udp_port_))
246        return;
247    } else if (key == "relay.tcp_port") {
248      if (!ParsePortNumber(value, &relay_tcp_port_))
249        return;
250    } else if (key == "relay.ssltcp_port") {
251      if (!ParsePortNumber(value, &relay_ssltcp_port_))
252        return;
253    }
254  }
255
256  AddConfig();
257}
258
259void P2PPortAllocatorSession::AddConfig() {
260  const P2PPortAllocator::Config& config = allocator_->config_;
261  cricket::PortConfiguration* port_config = new cricket::PortConfiguration(
262      talk_base::SocketAddress(config.stun_server, config.stun_server_port),
263      std::string(), std::string());
264
265  for (size_t i = 0; i < config.relays.size(); ++i) {
266    cricket::RelayCredentials credentials(config.relays[i].username,
267                                          config.relays[i].password);
268    cricket::RelayServerConfig relay_server(cricket::RELAY_TURN);
269    cricket::ProtocolType protocol;
270    if (!cricket::StringToProto(config.relays[i].transport_type.c_str(),
271                                &protocol)) {
272      DLOG(WARNING) << "Ignoring TURN server "
273                    << config.relays[i].server_address << ". "
274                    << "Reason= Incorrect "
275                    << config.relays[i].transport_type
276                    << " transport parameter.";
277      continue;
278    }
279
280    relay_server.ports.push_back(cricket::ProtocolAddress(
281        talk_base::SocketAddress(config.relays[i].server_address,
282                                 config.relays[i].port),
283        protocol,
284        config.relays[i].secure));
285    relay_server.credentials = credentials;
286    port_config->AddRelay(relay_server);
287  }
288  ConfigReady(port_config);
289}
290
291}  // namespace content
292