pepper_port_allocator.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 "remoting/client/plugin/pepper_port_allocator.h"
6
7#include "base/bind.h"
8#include "base/string_number_conversions.h"
9#include "net/base/net_util.h"
10#include "ppapi/c/pp_errors.h"
11#include "ppapi/cpp/completion_callback.h"
12#include "ppapi/cpp/private/host_resolver_private.h"
13#include "ppapi/cpp/url_loader.h"
14#include "ppapi/cpp/url_request_info.h"
15#include "ppapi/cpp/url_response_info.h"
16#include "remoting/client/plugin/pepper_network_manager.h"
17#include "remoting/client/plugin/pepper_packet_socket_factory.h"
18#include "remoting/client/plugin/pepper_util.h"
19
20namespace remoting {
21
22namespace {
23
24// URL used to create a relay session.
25const char kCreateRelaySessionURL[] = "/create_session";
26
27// Read buffer we allocate per read when reading response from
28// URLLoader. Normally the response from URL loader is smaller than 1kB.
29const int kReadSize = 1024;
30
31class PepperPortAllocatorSession
32    : public cricket::HttpPortAllocatorSessionBase {
33 public:
34  PepperPortAllocatorSession(
35      cricket::HttpPortAllocatorBase* allocator,
36      const std::string& content_name,
37      int component,
38      const std::string& ice_username_fragment,
39      const std::string& ice_password,
40      const std::vector<talk_base::SocketAddress>& stun_hosts,
41      const std::vector<std::string>& relay_hosts,
42      const std::string& relay_token,
43      const pp::InstanceHandle& instance);
44  virtual ~PepperPortAllocatorSession();
45
46  // cricket::HttpPortAllocatorBase overrides.
47  virtual void ConfigReady(cricket::PortConfiguration* config) OVERRIDE;
48  virtual void GetPortConfigurations() OVERRIDE;
49  virtual void SendSessionRequest(const std::string& host, int port) OVERRIDE;
50
51 private:
52  void ResolveStunServerAddress();
53  void OnStunAddressResolved(int32_t result);
54
55  void OnUrlOpened(int32_t result);
56  void ReadResponseBody();
57  void OnResponseBodyRead(int32_t result);
58
59  pp::InstanceHandle instance_;
60
61  pp::HostResolverPrivate stun_address_resolver_;
62  talk_base::SocketAddress stun_address_;
63  int stun_port_;
64
65  scoped_ptr<pp::URLLoader> relay_url_loader_;
66  std::vector<char> relay_response_body_;
67  bool relay_response_received_;
68
69  // Used to safely cancel completion callbacks from PPAPI calls.
70  base::WeakPtrFactory<PepperPortAllocatorSession> weak_factory_;
71
72  DISALLOW_COPY_AND_ASSIGN(PepperPortAllocatorSession);
73};
74
75PepperPortAllocatorSession::PepperPortAllocatorSession(
76    cricket::HttpPortAllocatorBase* allocator,
77    const std::string& content_name,
78    int component,
79    const std::string& ice_username_fragment,
80    const std::string& ice_password,
81    const std::vector<talk_base::SocketAddress>& stun_hosts,
82    const std::vector<std::string>& relay_hosts,
83    const std::string& relay_token,
84    const pp::InstanceHandle& instance)
85    : HttpPortAllocatorSessionBase(
86        allocator, content_name, component, ice_username_fragment, ice_password,
87        stun_hosts, relay_hosts, relay_token, ""),
88      instance_(instance),
89      stun_address_resolver_(instance_),
90      stun_port_(0),
91      relay_response_received_(false),
92      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
93  if (stun_hosts.size() > 0) {
94    stun_address_ = stun_hosts[0];
95  }
96}
97
98PepperPortAllocatorSession::~PepperPortAllocatorSession() {
99}
100
101void PepperPortAllocatorSession::ConfigReady(
102    cricket::PortConfiguration* config) {
103  if (config->stun_address.IsUnresolved()) {
104    // Make sure that the address that we pass to ConfigReady() is
105    // always resolved.
106    if (stun_address_.IsUnresolved()) {
107      config->stun_address.Clear();
108    } else {
109      config->stun_address = stun_address_;
110    }
111  }
112
113  // Filter out non-UDP relay ports, so that we don't try using TCP.
114  for (cricket::PortConfiguration::RelayList::iterator relay =
115           config->relays.begin(); relay != config->relays.end(); ++relay) {
116    cricket::PortList filtered_ports;
117    for (cricket::PortList::iterator port =
118             relay->ports.begin(); port != relay->ports.end(); ++port) {
119      if (port->proto == cricket::PROTO_UDP) {
120        filtered_ports.push_back(*port);
121      }
122    }
123    relay->ports = filtered_ports;
124  }
125  cricket::BasicPortAllocatorSession::ConfigReady(config);
126}
127
128void PepperPortAllocatorSession::GetPortConfigurations() {
129  // Add an empty configuration synchronously, so a local connection
130  // can be started immediately.
131  ConfigReady(new cricket::PortConfiguration(
132      talk_base::SocketAddress(), "", ""));
133
134  ResolveStunServerAddress();
135  TryCreateRelaySession();
136}
137
138void PepperPortAllocatorSession::ResolveStunServerAddress() {
139  if (stun_address_.IsNil()) {
140    return;
141  }
142
143  if (!stun_address_.IsUnresolved()) {
144    return;
145  }
146
147  std::string hostname = stun_address_.hostname();
148  uint16 port = stun_address_.port();
149
150  PP_HostResolver_Private_Hint hint;
151  hint.flags = 0;
152  hint.family = PP_NETADDRESSFAMILY_IPV4;
153  int result = stun_address_resolver_.Resolve(
154      hostname, port, hint,
155      PpCompletionCallback(base::Bind(
156          &PepperPortAllocatorSession::OnStunAddressResolved,
157          weak_factory_.GetWeakPtr())));
158
159  DCHECK_EQ(result, PP_OK_COMPLETIONPENDING);
160}
161
162void PepperPortAllocatorSession::OnStunAddressResolved(int32_t result) {
163  if (result < 0) {
164    LOG(ERROR) << "Failed to resolve stun address "
165               << stun_address_.hostname() << ": " << result;
166    return;
167  }
168
169  if (!stun_address_resolver_.GetSize()) {
170    LOG(WARNING) << "Received 0 addresses for stun server "
171               << stun_address_.hostname();
172    return;
173  }
174
175  PP_NetAddress_Private address;
176  if (!stun_address_resolver_.GetNetAddress(0, &address) ||
177      !PpAddressToSocketAddress(address, &stun_address_)) {
178    LOG(ERROR) << "Failed to get address for STUN server "
179               << stun_address_.hostname();
180    return;
181  }
182
183  DCHECK(!stun_address_.IsUnresolved());
184
185  if (relay_response_received_) {
186    // If we've finished reading the response, then resubmit it to
187    // HttpPortAllocatorSessionBase. This is necessary because STUN
188    // and Relay parameters are stored together in PortConfiguration
189    // and ReceiveSessionResponse() doesn't save relay session
190    // configuration for the case we resolve STUN address later. This
191    // method invokes overriden ConfigReady() which then submits
192    // resolved |stun_address_|.
193    //
194    // TODO(sergeyu): Refactor HttpPortAllocatorSessionBase to fix this.
195    ReceiveSessionResponse(std::string(relay_response_body_.begin(),
196                                       relay_response_body_.end()));
197  } else {
198    ConfigReady(new cricket::PortConfiguration(stun_address_, "", ""));
199  }
200}
201
202void PepperPortAllocatorSession::SendSessionRequest(
203    const std::string& host,
204    int port) {
205  relay_url_loader_.reset(new pp::URLLoader(instance_));
206  pp::URLRequestInfo request_info(instance_);
207  std::string url = "https://" + host + ":" + base::IntToString(port) +
208      GetSessionRequestUrl() + "&sn=1";
209  request_info.SetURL(url);
210  request_info.SetMethod("GET");
211  std::stringstream headers;
212  headers << "X-Talk-Google-Relay-Auth: " << relay_token() << "\n\r";
213  headers << "X-Google-Relay-Auth: " << relay_token() << "\n\r";
214  headers << "X-Stream-Type: " << "chromoting" << "\n\r";
215  request_info.SetHeaders(headers.str());
216
217  int result = relay_url_loader_->Open(
218      request_info, PpCompletionCallback(base::Bind(
219          &PepperPortAllocatorSession::OnUrlOpened,
220          weak_factory_.GetWeakPtr())));
221
222  DCHECK_EQ(result, PP_OK_COMPLETIONPENDING);
223}
224
225void PepperPortAllocatorSession::OnUrlOpened(int32_t result) {
226  if (result == PP_ERROR_ABORTED) {
227    return;
228  }
229
230  if (result < 0) {
231    LOG(WARNING) << "URLLoader failed: " << result;
232    // Retry creating session.
233    TryCreateRelaySession();
234    return;
235  }
236
237  pp::URLResponseInfo response = relay_url_loader_->GetResponseInfo();
238  DCHECK(!response.is_null());
239  if (response.GetStatusCode() != 200) {
240    LOG(WARNING) << "Received HTTP status code " << response.GetStatusCode();
241    // Retry creating session.
242    TryCreateRelaySession();
243    return;
244  }
245
246  relay_response_body_.clear();
247  ReadResponseBody();
248}
249
250void PepperPortAllocatorSession::ReadResponseBody() {
251  int pos = relay_response_body_.size();
252  relay_response_body_.resize(pos + kReadSize);
253  int result = relay_url_loader_->ReadResponseBody(
254      &relay_response_body_[pos], kReadSize,
255      PpCompletionCallback(base::Bind(
256          &PepperPortAllocatorSession::OnResponseBodyRead,
257          weak_factory_.GetWeakPtr())));
258  DCHECK_EQ(result, PP_OK_COMPLETIONPENDING);
259}
260
261void PepperPortAllocatorSession::OnResponseBodyRead(int32_t result) {
262  if (result == PP_ERROR_ABORTED) {
263    return;
264  }
265
266  if (result < 0) {
267    LOG(WARNING) << "Failed to read HTTP response body when "
268        "creating relay session: " << result;
269    // Retry creating session.
270    TryCreateRelaySession();
271    return;
272  }
273
274  // Resize the buffer in case we've read less than was requested.
275  CHECK_LE(result, kReadSize);
276  CHECK_GE(static_cast<int>(relay_response_body_.size()), kReadSize);
277  relay_response_body_.resize(relay_response_body_.size() - kReadSize + result);
278
279  if (result == 0) {
280    relay_response_received_ = true;
281    ReceiveSessionResponse(std::string(relay_response_body_.begin(),
282                                       relay_response_body_.end()));
283    return;
284  }
285
286  ReadResponseBody();
287}
288
289}  // namespace
290
291// static
292scoped_ptr<PepperPortAllocator> PepperPortAllocator::Create(
293    const pp::InstanceHandle& instance) {
294  scoped_ptr<talk_base::NetworkManager> network_manager(
295      new PepperNetworkManager(instance));
296  scoped_ptr<talk_base::PacketSocketFactory> socket_factory(
297      new PepperPacketSocketFactory(instance));
298  scoped_ptr<PepperPortAllocator> result(new PepperPortAllocator(
299      instance, network_manager.Pass(), socket_factory.Pass()));
300  return result.Pass();
301}
302
303PepperPortAllocator::PepperPortAllocator(
304    const pp::InstanceHandle& instance,
305    scoped_ptr<talk_base::NetworkManager> network_manager,
306    scoped_ptr<talk_base::PacketSocketFactory> socket_factory)
307    : HttpPortAllocatorBase(network_manager.get(), socket_factory.get(), ""),
308      instance_(instance),
309      network_manager_(network_manager.Pass()),
310      socket_factory_(socket_factory.Pass()) {
311  // TCP transport is disabled becase PseudoTCP works poorly over
312  // it. ENABLE_SHARED_UFRAG flag is specified so that the same
313  // username fragment is shared between all candidates for this
314  // channel.
315  set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
316            cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG);
317}
318
319PepperPortAllocator::~PepperPortAllocator() {
320}
321
322cricket::PortAllocatorSession* PepperPortAllocator::CreateSessionInternal(
323    const std::string& content_name,
324    int component,
325    const std::string& ice_username_fragment,
326    const std::string& ice_password) {
327   return new PepperPortAllocatorSession(
328       this, content_name, component, ice_username_fragment, ice_password,
329       stun_hosts(), relay_hosts(), relay_token(), instance_);
330}
331
332}  // namespace remoting
333