- add sources.
[platform/framework/web/crosswalk.git] / src / content / renderer / p2p / port_allocator.cc
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
24 using WebKit::WebString;
25 using WebKit::WebURL;
26 using WebKit::WebURLLoader;
27 using WebKit::WebURLLoaderOptions;
28 using WebKit::WebURLRequest;
29 using WebKit::WebURLResponse;
30
31 namespace content {
32
33 namespace {
34
35 // URL used to create a relay session.
36 const char kCreateRelaySessionURL[] = "/create_session";
37
38 // Number of times we will try to request relay session.
39 const int kRelaySessionRetries = 3;
40
41 // Manimum relay server size we would try to parse.
42 const int kMaximumRelayResponseSize = 102400;
43
44 bool 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
55 P2PPortAllocator::Config::Config()
56     : stun_server_port(0),
57       legacy_relay(true),
58       disable_tcp_transport(false) {
59 }
60
61 P2PPortAllocator::Config::~Config() {
62 }
63
64 P2PPortAllocator::Config::RelayServerConfig::RelayServerConfig()
65     : port(0) {
66 }
67
68 P2PPortAllocator::Config::RelayServerConfig::~RelayServerConfig() {
69 }
70
71 P2PPortAllocator::P2PPortAllocator(
72     WebKit::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
93 P2PPortAllocator::~P2PPortAllocator() {
94 }
95
96 cricket::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
105 P2PPortAllocatorSession::RelayServer::RelayServer() {
106 }
107
108 P2PPortAllocatorSession::RelayServer::~RelayServer() {
109 }
110
111 P2PPortAllocatorSession::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
128 P2PPortAllocatorSession::~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
138 void 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
151 void P2PPortAllocatorSession::didFinishLoading(WebURLLoader* loader,
152                                                double finish_time) {
153   ParseRelayResponse();
154 }
155
156 void P2PPortAllocatorSession::didFail(WebKit::WebURLLoader* loader,
157                                       const WebKit::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
167 void 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
183 void 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
194 void 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
212 void 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
229 void 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
258 void 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
308 void 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
362 void 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