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 blink::WebString;
25using blink::WebURL;
26using blink::WebURLLoader;
27using blink::WebURLLoaderOptions;
28using blink::WebURLRequest;
29using blink::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      legacy_relay(true),
58      disable_tcp_transport(false) {
59}
60
61P2PPortAllocator::Config::~Config() {
62}
63
64P2PPortAllocator::Config::RelayServerConfig::RelayServerConfig()
65    : port(0) {
66}
67
68P2PPortAllocator::Config::RelayServerConfig::~RelayServerConfig() {
69}
70
71P2PPortAllocator::P2PPortAllocator(
72    blink::WebFrame* web_frame,
73    P2PSocketDispatcher* socket_dispatcher,
74    talk_base::NetworkManager* network_manager,
75    talk_base::PacketSocketFactory* socket_factory,
76    const Config& config)
77    : cricket::BasicPortAllocator(network_manager, socket_factory),
78      web_frame_(web_frame),
79      socket_dispatcher_(socket_dispatcher),
80      config_(config) {
81  uint32 flags = 0;
82  if (config_.disable_tcp_transport)
83    flags |= cricket::PORTALLOCATOR_DISABLE_TCP;
84  set_flags(flags);
85  // TODO(ronghuawu): crbug/138185 add ourselves to the firewall list in browser
86  // process and then remove below line.
87  if (!CommandLine::ForCurrentProcess()->HasSwitch(
88          switches::kEnableWebRtcTcpServerSocket)) {
89    set_allow_tcp_listen(false);
90  }
91}
92
93P2PPortAllocator::~P2PPortAllocator() {
94}
95
96cricket::PortAllocatorSession* P2PPortAllocator::CreateSessionInternal(
97    const std::string& content_name,
98    int component,
99    const std::string& ice_username_fragment,
100    const std::string& ice_password) {
101  return new P2PPortAllocatorSession(
102      this, content_name, component, ice_username_fragment, ice_password);
103}
104
105P2PPortAllocatorSession::RelayServer::RelayServer() {
106}
107
108P2PPortAllocatorSession::RelayServer::~RelayServer() {
109}
110
111P2PPortAllocatorSession::P2PPortAllocatorSession(
112    P2PPortAllocator* allocator,
113    const std::string& content_name,
114    int component,
115    const std::string& ice_username_fragment,
116    const std::string& ice_password)
117    : cricket::BasicPortAllocatorSession(
118        allocator, content_name, component,
119        ice_username_fragment, ice_password),
120      allocator_(allocator),
121      relay_session_attempts_(0),
122      relay_udp_port_(0),
123      relay_tcp_port_(0),
124      relay_ssltcp_port_(0),
125      pending_relay_requests_(0) {
126}
127
128P2PPortAllocatorSession::~P2PPortAllocatorSession() {
129  if (stun_address_request_.get())
130    stun_address_request_->Cancel();
131
132  for (size_t i = 0; i < relay_info_.size(); ++i) {
133    if (relay_info_[i].relay_address_request.get())
134      relay_info_[i].relay_address_request->Cancel();
135  }
136}
137
138void P2PPortAllocatorSession::didReceiveData(
139    WebURLLoader* loader, const char* data,
140    int data_length, int encoded_data_length) {
141  DCHECK_EQ(loader, relay_session_request_.get());
142  if (static_cast<int>(relay_session_response_.size()) + data_length >
143      kMaximumRelayResponseSize) {
144    LOG(ERROR) << "Response received from the server is too big.";
145    loader->cancel();
146    return;
147  }
148  relay_session_response_.append(data, data + data_length);
149}
150
151void P2PPortAllocatorSession::didFinishLoading(WebURLLoader* loader,
152                                               double finish_time) {
153  ParseRelayResponse();
154}
155
156void P2PPortAllocatorSession::didFail(blink::WebURLLoader* loader,
157                                      const blink::WebURLError& error) {
158  DCHECK_EQ(loader, relay_session_request_.get());
159  DCHECK_NE(error.reason, 0);
160
161  LOG(ERROR) << "Relay session request failed.";
162
163  // Retry the request.
164  AllocateLegacyRelaySession();
165}
166
167void P2PPortAllocatorSession::GetPortConfigurations() {
168  // Resolve Stun and Relay server addresses.
169  if (!allocator_->config_.stun_server.empty() &&
170      stun_server_address_.IsNil()) {
171    ResolveStunServerAddress();
172  } else {
173    AddConfig();
174  }
175
176  if (allocator_->config_.legacy_relay) {
177    AllocateLegacyRelaySession();
178  } else {
179    ResolveRelayServerAddresses();
180  }
181}
182
183void P2PPortAllocatorSession::ResolveStunServerAddress() {
184  if (stun_address_request_.get())
185    return;
186
187  stun_address_request_ =
188      new P2PHostAddressRequest(allocator_->socket_dispatcher_);
189  stun_address_request_->Request(allocator_->config_.stun_server, base::Bind(
190      &P2PPortAllocatorSession::OnStunServerAddress,
191      base::Unretained(this)));
192}
193
194void P2PPortAllocatorSession::OnStunServerAddress(
195    const net::IPAddressNumber& address) {
196  if (address.empty()) {
197    LOG(ERROR) << "Failed to resolve STUN server address "
198               << allocator_->config_.stun_server;
199    // Allocating local ports on stun failure.
200    AddConfig();
201    return;
202  }
203
204  if (!jingle_glue::IPEndPointToSocketAddress(
205          net::IPEndPoint(address, allocator_->config_.stun_server_port),
206          &stun_server_address_)) {
207    return;
208  }
209  AddConfig();
210}
211
212void P2PPortAllocatorSession::ResolveRelayServerAddresses() {
213  for (size_t i = 0; i < allocator_->config_.relays.size(); ++i) {
214    scoped_refptr<P2PHostAddressRequest> relay_request =
215        new P2PHostAddressRequest(allocator_->socket_dispatcher_);
216    relay_request->Request(
217        allocator_->config_.relays[i].server_address,
218        base::Bind(&P2PPortAllocatorSession::OnRelayServerAddressResolved,
219                   base::Unretained(this), i));
220    // Copy relay configuration from alloctor and keeping it in a map.
221    RelayServer relay;
222    relay.config = allocator_->config_.relays[i];
223    relay.relay_address_request = relay_request;
224    relay_info_.push_back(relay);
225    ++pending_relay_requests_;
226  }
227}
228
229void P2PPortAllocatorSession::OnRelayServerAddressResolved(
230    size_t index, const net::IPAddressNumber& address) {
231  // Let's first decrement the pending requests count.
232  --pending_relay_requests_;
233  if (index > relay_info_.size()) {
234    NOTREACHED();
235    return;
236  }
237
238  if (address.empty()) {
239    LOG(ERROR) << "Failed to resolve Relay server address "
240               << relay_info_.at(index).config.server_address;
241  } else {
242    // Getting relay server info for which this resolved address belongs.
243    RelayServer& relay_server = relay_info_.at(index);
244
245    talk_base::SocketAddress socket_address;
246    if (!jingle_glue::IPEndPointToSocketAddress(
247            net::IPEndPoint(address, relay_server.config.port),
248            &socket_address)) {
249      NOTREACHED();
250    }
251    relay_server.resolved_relay_address = socket_address;
252  }
253
254  if (!pending_relay_requests_)
255    AddConfig();
256}
257
258void P2PPortAllocatorSession::AllocateLegacyRelaySession() {
259  if (allocator_->config_.relays.empty())
260    return;
261  // If we are using legacy relay, we will have only one entry in relay server
262  // list.
263  P2PPortAllocator::Config::RelayServerConfig relay_config =
264      allocator_->config_.relays[0];
265
266  if (relay_session_attempts_ > kRelaySessionRetries)
267    return;
268  relay_session_attempts_++;
269
270  relay_session_response_.clear();
271
272  WebURLLoaderOptions options;
273  options.allowCredentials = false;
274
275  options.crossOriginRequestPolicy =
276      WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
277
278  relay_session_request_.reset(
279      allocator_->web_frame_->createAssociatedURLLoader(options));
280  if (!relay_session_request_) {
281    LOG(ERROR) << "Failed to create URL loader.";
282    return;
283  }
284
285  std::string url = "https://" + relay_config.server_address +
286      kCreateRelaySessionURL +
287      "?username=" + net::EscapeUrlEncodedData(username(), true) +
288      "&password=" + net::EscapeUrlEncodedData(password(), true);
289
290  WebURLRequest request;
291  request.initialize();
292  request.setURL(WebURL(GURL(url)));
293  request.setAllowStoredCredentials(false);
294  request.setCachePolicy(WebURLRequest::ReloadIgnoringCacheData);
295  request.setHTTPMethod("GET");
296  request.addHTTPHeaderField(
297      WebString::fromUTF8("X-Talk-Google-Relay-Auth"),
298      WebString::fromUTF8(relay_config.password));
299  request.addHTTPHeaderField(
300      WebString::fromUTF8("X-Google-Relay-Auth"),
301      WebString::fromUTF8(relay_config.username));
302  request.addHTTPHeaderField(WebString::fromUTF8("X-Stream-Type"),
303                             WebString::fromUTF8("chromoting"));
304
305  relay_session_request_->loadAsynchronously(request, this);
306}
307
308void P2PPortAllocatorSession::ParseRelayResponse() {
309  std::vector<std::pair<std::string, std::string> > value_pairs;
310  if (!base::SplitStringIntoKeyValuePairs(relay_session_response_, '=', '\n',
311                                          &value_pairs)) {
312    LOG(ERROR) << "Received invalid response from relay server";
313    return;
314  }
315
316  relay_ip_.Clear();
317  relay_udp_port_ = 0;
318  relay_tcp_port_ = 0;
319  relay_ssltcp_port_ = 0;
320
321  for (std::vector<std::pair<std::string, std::string> >::iterator
322           it = value_pairs.begin();
323       it != value_pairs.end(); ++it) {
324    std::string key;
325    std::string value;
326    TrimWhitespaceASCII(it->first, TRIM_ALL, &key);
327    TrimWhitespaceASCII(it->second, TRIM_ALL, &value);
328
329    if (key == "username") {
330      if (value != username()) {
331        LOG(ERROR) << "When creating relay session received user name "
332            " that was different from the value specified in the query.";
333        return;
334      }
335    } else if (key == "password") {
336      if (value != password()) {
337        LOG(ERROR) << "When creating relay session received password "
338            "that was different from the value specified in the query.";
339        return;
340      }
341    } else if (key == "relay.ip") {
342      relay_ip_.SetIP(value);
343      if (relay_ip_.ip() == 0) {
344        LOG(ERROR) << "Received unresolved relay server address: " << value;
345        return;
346      }
347    } else if (key == "relay.udp_port") {
348      if (!ParsePortNumber(value, &relay_udp_port_))
349        return;
350    } else if (key == "relay.tcp_port") {
351      if (!ParsePortNumber(value, &relay_tcp_port_))
352        return;
353    } else if (key == "relay.ssltcp_port") {
354      if (!ParsePortNumber(value, &relay_ssltcp_port_))
355        return;
356    }
357  }
358
359  AddConfig();
360}
361
362void P2PPortAllocatorSession::AddConfig() {
363  cricket::PortConfiguration* port_config = new cricket::PortConfiguration(
364      stun_server_address_, std::string(), std::string());
365
366  if (!pending_relay_requests_) {
367    // Push all resolved addresses and transport port type to allocator.
368    for (size_t i = 0; i < relay_info_.size(); ++i) {
369      if (relay_info_[i].resolved_relay_address.IsNil())
370        continue;
371
372      RelayServer relay_info = relay_info_[i];
373      cricket::RelayCredentials credentials(relay_info.config.username,
374                                            relay_info.config.password);
375      cricket::RelayServerConfig relay_server(cricket::RELAY_TURN);
376      cricket::ProtocolType protocol;
377      if (!cricket::StringToProto(relay_info.config.transport_type.c_str(),
378                                  &protocol)) {
379        DLOG(WARNING) << "Ignoring TURN server "
380                      << relay_info.config.server_address << ". "
381                      << "Reason= Incorrect "
382                      << relay_info.config.transport_type
383                      << " transport parameter.";
384        continue;
385      }
386
387      relay_server.ports.push_back(cricket::ProtocolAddress(
388          relay_info.resolved_relay_address,
389          protocol,
390          relay_info.config.secure));
391      relay_server.credentials = credentials;
392      port_config->AddRelay(relay_server);
393    }
394  }
395  ConfigReady(port_config);
396}
397
398}  // namespace content
399