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