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/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"
24 // URL used to create a relay session.
25 const char kCreateRelaySessionURL
[] = "/create_session";
27 // Read buffer we allocate per read when reading response from
28 // URLLoader. Normally the response from URL loader is smaller than 1kB.
29 const int kReadSize
= 1024;
31 class PepperPortAllocatorSession
32 : public cricket::HttpPortAllocatorSessionBase
{
34 PepperPortAllocatorSession(
35 cricket::HttpPortAllocatorBase
* allocator
,
36 const std::string
& content_name
,
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();
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
;
52 void ResolveStunServerAddress();
53 void OnStunAddressResolved(int32_t result
);
55 void OnUrlOpened(int32_t result
);
56 void ReadResponseBody();
57 void OnResponseBodyRead(int32_t result
);
59 pp::InstanceHandle instance_
;
61 pp::HostResolverPrivate stun_address_resolver_
;
62 talk_base::SocketAddress stun_address_
;
65 scoped_ptr
<pp::URLLoader
> relay_url_loader_
;
66 std::vector
<char> relay_response_body_
;
67 bool relay_response_received_
;
69 // Used to safely cancel completion callbacks from PPAPI calls.
70 base::WeakPtrFactory
<PepperPortAllocatorSession
> weak_factory_
;
72 DISALLOW_COPY_AND_ASSIGN(PepperPortAllocatorSession
);
75 PepperPortAllocatorSession::PepperPortAllocatorSession(
76 cricket::HttpPortAllocatorBase
* allocator
,
77 const std::string
& content_name
,
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
, ""),
89 stun_address_resolver_(instance_
),
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];
98 PepperPortAllocatorSession::~PepperPortAllocatorSession() {
101 void PepperPortAllocatorSession::ConfigReady(
102 cricket::PortConfiguration
* config
) {
103 if (config
->stun_address
.IsUnresolved()) {
104 // Make sure that the address that we pass to ConfigReady() is
106 if (stun_address_
.IsUnresolved()) {
107 config
->stun_address
.Clear();
109 config
->stun_address
= stun_address_
;
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
);
123 relay
->ports
= filtered_ports
;
125 cricket::BasicPortAllocatorSession::ConfigReady(config
);
128 void 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(), "", ""));
134 ResolveStunServerAddress();
135 TryCreateRelaySession();
138 void PepperPortAllocatorSession::ResolveStunServerAddress() {
139 if (stun_address_
.IsNil()) {
143 if (!stun_address_
.IsUnresolved()) {
147 std::string hostname
= stun_address_
.hostname();
148 uint16 port
= stun_address_
.port();
150 PP_HostResolver_Private_Hint hint
;
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())));
159 DCHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
162 void PepperPortAllocatorSession::OnStunAddressResolved(int32_t result
) {
164 LOG(ERROR
) << "Failed to resolve stun address "
165 << stun_address_
.hostname() << ": " << result
;
169 if (!stun_address_resolver_
.GetSize()) {
170 LOG(WARNING
) << "Received 0 addresses for stun server "
171 << stun_address_
.hostname();
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();
183 DCHECK(!stun_address_
.IsUnresolved());
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_|.
194 // TODO(sergeyu): Refactor HttpPortAllocatorSessionBase to fix this.
195 ReceiveSessionResponse(std::string(relay_response_body_
.begin(),
196 relay_response_body_
.end()));
198 ConfigReady(new cricket::PortConfiguration(stun_address_
, "", ""));
202 void PepperPortAllocatorSession::SendSessionRequest(
203 const std::string
& host
,
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());
217 int result
= relay_url_loader_
->Open(
218 request_info
, PpCompletionCallback(base::Bind(
219 &PepperPortAllocatorSession::OnUrlOpened
,
220 weak_factory_
.GetWeakPtr())));
222 DCHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
225 void PepperPortAllocatorSession::OnUrlOpened(int32_t result
) {
226 if (result
== PP_ERROR_ABORTED
) {
231 LOG(WARNING
) << "URLLoader failed: " << result
;
232 // Retry creating session.
233 TryCreateRelaySession();
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();
246 relay_response_body_
.clear();
250 void 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
);
261 void PepperPortAllocatorSession::OnResponseBodyRead(int32_t result
) {
262 if (result
== PP_ERROR_ABORTED
) {
267 LOG(WARNING
) << "Failed to read HTTP response body when "
268 "creating relay session: " << result
;
269 // Retry creating session.
270 TryCreateRelaySession();
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
);
280 relay_response_received_
= true;
281 ReceiveSessionResponse(std::string(relay_response_body_
.begin(),
282 relay_response_body_
.end()));
292 scoped_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();
303 PepperPortAllocator::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(), ""),
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
315 set_flags(cricket::PORTALLOCATOR_DISABLE_TCP
|
316 cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG
);
319 PepperPortAllocator::~PepperPortAllocator() {
322 cricket::PortAllocatorSession
* PepperPortAllocator::CreateSessionInternal(
323 const std::string
& content_name
,
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_
);
332 } // namespace remoting