• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libjingle
3  * Copyright 2004--2008, Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "talk/p2p/client/httpportallocator.h"
29 
30 #include <algorithm>
31 #include <map>
32 
33 #include "talk/base/asynchttprequest.h"
34 #include "talk/base/basicdefs.h"
35 #include "talk/base/common.h"
36 #include "talk/base/helpers.h"
37 #include "talk/base/logging.h"
38 #include "talk/base/nethelpers.h"
39 #include "talk/base/signalthread.h"
40 #include "talk/base/stringencode.h"
41 
42 namespace {
43 
44 const uint32 MSG_TIMEOUT = 100;  // must not conflict
45   // with BasicPortAllocator.cpp
46 
47 // Helper routine to remove whitespace from the ends of a string.
Trim(std::string & str)48 void Trim(std::string& str) {
49   size_t first = str.find_first_not_of(" \t\r\n");
50   if (first == std::string::npos) {
51     str.clear();
52     return;
53   }
54 
55   ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos);
56 }
57 
58 // Parses the lines in the result of the HTTP request that are of the form
59 // 'a=b' and returns them in a map.
60 typedef std::map<std::string, std::string> StringMap;
ParseMap(const std::string & string,StringMap & map)61 void ParseMap(const std::string& string, StringMap& map) {
62   size_t start_of_line = 0;
63   size_t end_of_line = 0;
64 
65   for (;;) {  // for each line
66     start_of_line = string.find_first_not_of("\r\n", end_of_line);
67     if (start_of_line == std::string::npos)
68       break;
69 
70     end_of_line = string.find_first_of("\r\n", start_of_line);
71     if (end_of_line == std::string::npos) {
72       end_of_line = string.length();
73     }
74 
75     size_t equals = string.find('=', start_of_line);
76     if ((equals >= end_of_line) || (equals == std::string::npos))
77       continue;
78 
79     std::string key(string, start_of_line, equals - start_of_line);
80     std::string value(string, equals + 1, end_of_line - equals - 1);
81 
82     Trim(key);
83     Trim(value);
84 
85     if ((key.size() > 0) && (value.size() > 0))
86       map[key] = value;
87   }
88 }
89 
90 }  // namespace
91 
92 namespace cricket {
93 
94 // HttpPortAllocatorBase
95 
96 const int HttpPortAllocatorBase::kNumRetries = 5;
97 
98 const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
99 
HttpPortAllocatorBase(talk_base::NetworkManager * network_manager,talk_base::PacketSocketFactory * socket_factory,const std::string & user_agent)100 HttpPortAllocatorBase::HttpPortAllocatorBase(
101     talk_base::NetworkManager* network_manager,
102     talk_base::PacketSocketFactory* socket_factory,
103     const std::string &user_agent)
104     : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) {
105   relay_hosts_.push_back("relay.google.com");
106   stun_hosts_.push_back(
107       talk_base::SocketAddress("stun.l.google.com", 19302));
108 }
109 
HttpPortAllocatorBase(talk_base::NetworkManager * network_manager,const std::string & user_agent)110 HttpPortAllocatorBase::HttpPortAllocatorBase(
111     talk_base::NetworkManager* network_manager,
112     const std::string &user_agent)
113     : BasicPortAllocator(network_manager), agent_(user_agent) {
114   relay_hosts_.push_back("relay.google.com");
115   stun_hosts_.push_back(
116       talk_base::SocketAddress("stun.l.google.com", 19302));
117 }
118 
~HttpPortAllocatorBase()119 HttpPortAllocatorBase::~HttpPortAllocatorBase() {
120 }
121 
122 // HttpPortAllocatorSessionBase
123 
HttpPortAllocatorSessionBase(HttpPortAllocatorBase * allocator,const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd,const std::vector<talk_base::SocketAddress> & stun_hosts,const std::vector<std::string> & relay_hosts,const std::string & relay_token,const std::string & user_agent)124 HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase(
125     HttpPortAllocatorBase* allocator,
126     const std::string& content_name,
127     int component,
128     const std::string& ice_ufrag,
129     const std::string& ice_pwd,
130     const std::vector<talk_base::SocketAddress>& stun_hosts,
131     const std::vector<std::string>& relay_hosts,
132     const std::string& relay_token,
133     const std::string& user_agent)
134     : BasicPortAllocatorSession(allocator, content_name, component,
135                                 ice_ufrag, ice_pwd),
136       relay_hosts_(relay_hosts), stun_hosts_(stun_hosts),
137       relay_token_(relay_token), agent_(user_agent), attempts_(0) {
138 }
139 
~HttpPortAllocatorSessionBase()140 HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
141 
GetPortConfigurations()142 void HttpPortAllocatorSessionBase::GetPortConfigurations() {
143   // Creating relay sessions can take time and is done asynchronously.
144   // Creating stun sessions could also take time and could be done aysnc also,
145   // but for now is done here and added to the initial config.  Note any later
146   // configs will have unresolved stun ips and will be discarded by the
147   // AllocationSequence.
148   PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
149                                                     username(),
150                                                     password());
151   ConfigReady(config);
152   TryCreateRelaySession();
153 }
154 
TryCreateRelaySession()155 void HttpPortAllocatorSessionBase::TryCreateRelaySession() {
156   if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) {
157     LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping.";
158     return;
159   }
160 
161   if (attempts_ == HttpPortAllocator::kNumRetries) {
162     LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; "
163                   << "giving up on relay.";
164     return;
165   }
166 
167   if (relay_hosts_.size() == 0) {
168     LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured.";
169     return;
170   }
171 
172   // Choose the next host to try.
173   std::string host = relay_hosts_[attempts_ % relay_hosts_.size()];
174   attempts_++;
175   LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host;
176   if (relay_token_.empty()) {
177     LOG(LS_WARNING) << "No relay auth token found.";
178   }
179 
180   SendSessionRequest(host, talk_base::HTTP_SECURE_PORT);
181 }
182 
GetSessionRequestUrl()183 std::string HttpPortAllocatorSessionBase::GetSessionRequestUrl() {
184   std::string url = std::string(HttpPortAllocator::kCreateSessionURL);
185   if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHARED_UFRAG) {
186     ASSERT(!username().empty());
187     ASSERT(!password().empty());
188     url = url + "?username=" + talk_base::s_url_encode(username()) +
189         "&password=" + talk_base::s_url_encode(password());
190   }
191   return url;
192 }
193 
ReceiveSessionResponse(const std::string & response)194 void HttpPortAllocatorSessionBase::ReceiveSessionResponse(
195     const std::string& response) {
196 
197   StringMap map;
198   ParseMap(response, map);
199 
200   if (!username().empty() && map["username"] != username()) {
201     LOG(LS_WARNING) << "Received unexpected username value from relay server.";
202   }
203   if (!password().empty() && map["password"] != password()) {
204     LOG(LS_WARNING) << "Received unexpected password value from relay server.";
205   }
206 
207   std::string relay_ip = map["relay.ip"];
208   std::string relay_udp_port = map["relay.udp_port"];
209   std::string relay_tcp_port = map["relay.tcp_port"];
210   std::string relay_ssltcp_port = map["relay.ssltcp_port"];
211 
212   PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
213                                                     map["username"],
214                                                     map["password"]);
215 
216   RelayServerConfig relay_config(RELAY_GTURN);
217   if (!relay_udp_port.empty()) {
218     talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str()));
219     relay_config.ports.push_back(ProtocolAddress(address, PROTO_UDP));
220   }
221   if (!relay_tcp_port.empty()) {
222     talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str()));
223     relay_config.ports.push_back(ProtocolAddress(address, PROTO_TCP));
224   }
225   if (!relay_ssltcp_port.empty()) {
226     talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str()));
227     relay_config.ports.push_back(ProtocolAddress(address, PROTO_SSLTCP));
228   }
229   config->AddRelay(relay_config);
230   ConfigReady(config);
231 }
232 
233 // HttpPortAllocator
234 
HttpPortAllocator(talk_base::NetworkManager * network_manager,talk_base::PacketSocketFactory * socket_factory,const std::string & user_agent)235 HttpPortAllocator::HttpPortAllocator(
236     talk_base::NetworkManager* network_manager,
237     talk_base::PacketSocketFactory* socket_factory,
238     const std::string &user_agent)
239     : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) {
240 }
241 
HttpPortAllocator(talk_base::NetworkManager * network_manager,const std::string & user_agent)242 HttpPortAllocator::HttpPortAllocator(
243     talk_base::NetworkManager* network_manager,
244     const std::string &user_agent)
245     : HttpPortAllocatorBase(network_manager, user_agent) {
246 }
~HttpPortAllocator()247 HttpPortAllocator::~HttpPortAllocator() {}
248 
CreateSessionInternal(const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd)249 PortAllocatorSession* HttpPortAllocator::CreateSessionInternal(
250     const std::string& content_name,
251     int component,
252     const std::string& ice_ufrag, const std::string& ice_pwd) {
253   return new HttpPortAllocatorSession(this, content_name, component,
254                                       ice_ufrag, ice_pwd, stun_hosts(),
255                                       relay_hosts(), relay_token(),
256                                       user_agent());
257 }
258 
259 // HttpPortAllocatorSession
260 
HttpPortAllocatorSession(HttpPortAllocator * allocator,const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd,const std::vector<talk_base::SocketAddress> & stun_hosts,const std::vector<std::string> & relay_hosts,const std::string & relay,const std::string & agent)261 HttpPortAllocatorSession::HttpPortAllocatorSession(
262     HttpPortAllocator* allocator,
263     const std::string& content_name,
264     int component,
265     const std::string& ice_ufrag,
266     const std::string& ice_pwd,
267     const std::vector<talk_base::SocketAddress>& stun_hosts,
268     const std::vector<std::string>& relay_hosts,
269     const std::string& relay,
270     const std::string& agent)
271     : HttpPortAllocatorSessionBase(allocator, content_name, component,
272                                    ice_ufrag, ice_pwd, stun_hosts,
273                                    relay_hosts, relay, agent) {
274 }
275 
~HttpPortAllocatorSession()276 HttpPortAllocatorSession::~HttpPortAllocatorSession() {
277   for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin();
278        it != requests_.end(); ++it) {
279     (*it)->Destroy(true);
280   }
281 }
282 
SendSessionRequest(const std::string & host,int port)283 void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
284                                                   int port) {
285   // Initiate an HTTP request to create a session through the chosen host.
286   talk_base::AsyncHttpRequest* request =
287       new talk_base::AsyncHttpRequest(user_agent());
288   request->SignalWorkDone.connect(this,
289       &HttpPortAllocatorSession::OnRequestDone);
290 
291   request->set_secure(port == talk_base::HTTP_SECURE_PORT);
292   request->set_proxy(allocator()->proxy());
293   request->response().document.reset(new talk_base::MemoryStream);
294   request->request().verb = talk_base::HV_GET;
295   request->request().path = GetSessionRequestUrl();
296   request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true);
297   request->request().addHeader("X-Stream-Type", "video_rtp", true);
298   request->set_host(host);
299   request->set_port(port);
300   request->Start();
301   request->Release();
302 
303   requests_.push_back(request);
304 }
305 
OnRequestDone(talk_base::SignalThread * data)306 void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
307   talk_base::AsyncHttpRequest* request =
308       static_cast<talk_base::AsyncHttpRequest*>(data);
309 
310   // Remove the request from the list of active requests.
311   std::list<talk_base::AsyncHttpRequest*>::iterator it =
312       std::find(requests_.begin(), requests_.end(), request);
313   if (it != requests_.end()) {
314     requests_.erase(it);
315   }
316 
317   if (request->response().scode != 200) {
318     LOG(LS_WARNING) << "HTTPPortAllocator: request "
319                     << " received error " << request->response().scode;
320     TryCreateRelaySession();
321     return;
322   }
323   LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
324 
325   talk_base::MemoryStream* stream =
326       static_cast<talk_base::MemoryStream*>(request->response().document.get());
327   stream->Rewind();
328   size_t length;
329   stream->GetSize(&length);
330   std::string resp = std::string(stream->GetBuffer(), length);
331   ReceiveSessionResponse(resp);
332 }
333 
334 }  // namespace cricket
335