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.
5 #include "remoting/client/plugin/pepper_port_allocator.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"
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;
29 class PepperPortAllocatorSession
30 : public cricket::HttpPortAllocatorSessionBase
{
32 PepperPortAllocatorSession(
33 cricket::HttpPortAllocatorBase
* allocator
,
34 const std::string
& content_name
,
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();
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
;
50 void ResolveStunServerAddress();
51 void OnStunAddressResolved(int32_t result
);
53 void OnUrlOpened(int32_t result
);
54 void ReadResponseBody();
55 void OnResponseBodyRead(int32_t result
);
57 pp::InstanceHandle instance_
;
59 pp::HostResolver stun_address_resolver_
;
60 talk_base::SocketAddress stun_address_
;
63 scoped_ptr
<pp::URLLoader
> relay_url_loader_
;
64 std::vector
<char> relay_response_body_
;
65 bool relay_response_received_
;
67 pp::CompletionCallbackFactory
<PepperPortAllocatorSession
> callback_factory_
;
69 DISALLOW_COPY_AND_ASSIGN(PepperPortAllocatorSession
);
72 PepperPortAllocatorSession::PepperPortAllocatorSession(
73 cricket::HttpPortAllocatorBase
* allocator
,
74 const std::string
& content_name
,
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
,
85 ice_username_fragment
,
92 stun_address_resolver_(instance_
),
94 relay_response_received_(false),
95 callback_factory_(this) {
96 if (stun_hosts
.size() > 0) {
97 stun_address_
= stun_hosts
[0];
101 PepperPortAllocatorSession::~PepperPortAllocatorSession() {
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
109 if (stun_address_
.IsUnresolved()) {
110 config
->stun_address
.Clear();
112 config
->stun_address
= stun_address_
;
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
);
126 relay
->ports
= filtered_ports
;
128 cricket::BasicPortAllocatorSession::ConfigReady(config
);
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()));
137 ResolveStunServerAddress();
138 TryCreateRelaySession();
141 void PepperPortAllocatorSession::ResolveStunServerAddress() {
142 if (stun_address_
.IsNil()) {
146 if (!stun_address_
.IsUnresolved()) {
150 std::string hostname
= stun_address_
.hostname();
151 uint16 port
= stun_address_
.port();
153 PP_HostResolver_Hint hint
;
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(),
162 DCHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
165 void PepperPortAllocatorSession::OnStunAddressResolved(int32_t result
) {
167 LOG(ERROR
) << "Failed to resolve stun address "
168 << stun_address_
.hostname() << ": " << result
;
172 if (!stun_address_resolver_
.GetNetAddressCount()) {
173 LOG(WARNING
) << "Received 0 addresses for stun server "
174 << stun_address_
.hostname();
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();
185 PpNetAddressToSocketAddress(address
, &stun_address_
);
186 DCHECK(!stun_address_
.IsUnresolved());
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_|.
197 // TODO(sergeyu): Refactor HttpPortAllocatorSessionBase to fix this.
198 ReceiveSessionResponse(std::string(relay_response_body_
.begin(),
199 relay_response_body_
.end()));
201 ConfigReady(new cricket::PortConfiguration(
202 stun_address_
, std::string(), std::string()));
206 void PepperPortAllocatorSession::SendSessionRequest(
207 const std::string
& host
,
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());
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
);
227 void PepperPortAllocatorSession::OnUrlOpened(int32_t result
) {
228 if (result
== PP_ERROR_ABORTED
) {
233 LOG(WARNING
) << "URLLoader failed: " << result
;
234 // Retry creating session.
235 TryCreateRelaySession();
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();
248 relay_response_body_
.clear();
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
],
260 DCHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
263 void PepperPortAllocatorSession::OnResponseBodyRead(int32_t result
) {
264 if (result
== PP_ERROR_ABORTED
) {
269 LOG(WARNING
) << "Failed to read HTTP response body when "
270 "creating relay session: " << result
;
271 // Retry creating session.
272 TryCreateRelaySession();
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
);
282 relay_response_received_
= true;
283 ReceiveSessionResponse(std::string(relay_response_body_
.begin(),
284 relay_response_body_
.end()));
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();
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(),
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
319 set_flags(cricket::PORTALLOCATOR_DISABLE_TCP
|
320 cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG
|
321 cricket::PORTALLOCATOR_ENABLE_IPV6
);
324 PepperPortAllocator::~PepperPortAllocator() {
327 cricket::PortAllocatorSession
* PepperPortAllocator::CreateSessionInternal(
328 const std::string
& content_name
,
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_
);
337 } // namespace remoting