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 
41 namespace {
42 
43 const uint32 MSG_TIMEOUT = 100;  // must not conflict
44   // with BasicPortAllocator.cpp
45 
46 // Helper routine to remove whitespace from the ends of a string.
Trim(std::string & str)47 void Trim(std::string& str) {
48   size_t first = str.find_first_not_of(" \t\r\n");
49   if (first == std::string::npos) {
50     str.clear();
51     return;
52   }
53 
54   ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos);
55 }
56 
57 // Parses the lines in the result of the HTTP request that are of the form
58 // 'a=b' and returns them in a map.
59 typedef std::map<std::string, std::string> StringMap;
ParseMap(const std::string & string,StringMap & map)60 void ParseMap(const std::string& string, StringMap& map) {
61   size_t start_of_line = 0;
62   size_t end_of_line = 0;
63 
64   for (;;) {  // for each line
65     start_of_line = string.find_first_not_of("\r\n", end_of_line);
66     if (start_of_line == std::string::npos)
67       break;
68 
69     end_of_line = string.find_first_of("\r\n", start_of_line);
70     if (end_of_line == std::string::npos) {
71       end_of_line = string.length();
72     }
73 
74     size_t equals = string.find('=', start_of_line);
75     if ((equals >= end_of_line) || (equals == std::string::npos))
76       continue;
77 
78     std::string key(string, start_of_line, equals - start_of_line);
79     std::string value(string, equals + 1, end_of_line - equals - 1);
80 
81     Trim(key);
82     Trim(value);
83 
84     if ((key.size() > 0) && (value.size() > 0))
85       map[key] = value;
86   }
87 }
88 
89 }  // namespace
90 
91 namespace cricket {
92 
93 // HttpPortAllocatorBase
94 
95 const int HttpPortAllocatorBase::kNumRetries = 5;
96 
97 const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
98 
HttpPortAllocatorBase(talk_base::NetworkManager * network_manager,talk_base::PacketSocketFactory * socket_factory,const std::string & user_agent)99 HttpPortAllocatorBase::HttpPortAllocatorBase(
100     talk_base::NetworkManager* network_manager,
101     talk_base::PacketSocketFactory* socket_factory,
102     const std::string &user_agent)
103     : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) {
104   relay_hosts_.push_back("relay.google.com");
105   stun_hosts_.push_back(
106       talk_base::SocketAddress("stun.l.google.com", 19302));
107 }
108 
HttpPortAllocatorBase(talk_base::NetworkManager * network_manager,const std::string & user_agent)109 HttpPortAllocatorBase::HttpPortAllocatorBase(
110     talk_base::NetworkManager* network_manager,
111     const std::string &user_agent)
112     : BasicPortAllocator(network_manager), agent_(user_agent) {
113   relay_hosts_.push_back("relay.google.com");
114   stun_hosts_.push_back(
115       talk_base::SocketAddress("stun.l.google.com", 19302));
116 }
117 
~HttpPortAllocatorBase()118 HttpPortAllocatorBase::~HttpPortAllocatorBase() {
119 }
120 
121 // HttpPortAllocatorSessionBase
122 
HttpPortAllocatorSessionBase(HttpPortAllocatorBase * allocator,const std::string & name,const std::string & session_type,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)123 HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase(
124     HttpPortAllocatorBase* allocator, const std::string &name,
125     const std::string& session_type,
126     const std::vector<talk_base::SocketAddress>& stun_hosts,
127     const std::vector<std::string>& relay_hosts,
128     const std::string& relay_token,
129     const std::string& user_agent)
130     : BasicPortAllocatorSession(allocator, name, session_type),
131       relay_hosts_(relay_hosts), stun_hosts_(stun_hosts),
132       relay_token_(relay_token), agent_(user_agent), attempts_(0) {
133 }
134 
~HttpPortAllocatorSessionBase()135 HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
136 
GetPortConfigurations()137 void HttpPortAllocatorSessionBase::GetPortConfigurations() {
138   // Creating relay sessions can take time and is done asynchronously.
139   // Creating stun sessions could also take time and could be done aysnc also,
140   // but for now is done here and added to the initial config.  Note any later
141   // configs will have unresolved stun ips and will be discarded by the
142   // AllocationSequence.
143   PortConfiguration* config = new PortConfiguration(stun_hosts_[0], "", "", "");
144   ConfigReady(config);
145   TryCreateRelaySession();
146 }
147 
TryCreateRelaySession()148 void HttpPortAllocatorSessionBase::TryCreateRelaySession() {
149   if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) {
150     LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping.";
151     return;
152   }
153 
154   if (attempts_ == HttpPortAllocator::kNumRetries) {
155     LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; "
156                   << "giving up on relay.";
157     return;
158   }
159 
160   if (relay_hosts_.size() == 0) {
161     LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured.";
162     return;
163   }
164 
165   // Choose the next host to try.
166   std::string host = relay_hosts_[attempts_ % relay_hosts_.size()];
167   attempts_++;
168   LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host;
169   if (relay_token_.empty()) {
170     LOG(LS_WARNING) << "No relay auth token found.";
171   }
172 
173   SendSessionRequest(host, talk_base::HTTP_SECURE_PORT);
174 }
175 
ReceiveSessionResponse(const std::string & response)176 void HttpPortAllocatorSessionBase::ReceiveSessionResponse(
177     const std::string& response) {
178 
179   StringMap map;
180   ParseMap(response, map);
181 
182   std::string username = map["username"];
183   std::string password = map["password"];
184   std::string magic_cookie = map["magic_cookie"];
185 
186   std::string relay_ip = map["relay.ip"];
187   std::string relay_udp_port = map["relay.udp_port"];
188   std::string relay_tcp_port = map["relay.tcp_port"];
189   std::string relay_ssltcp_port = map["relay.ssltcp_port"];
190 
191   PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
192                                                     username,
193                                                     password,
194                                                     magic_cookie);
195 
196   PortConfiguration::PortList ports;
197   if (!relay_udp_port.empty()) {
198     talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str()));
199     ports.push_back(ProtocolAddress(address, PROTO_UDP));
200   }
201   if (!relay_tcp_port.empty()) {
202     talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str()));
203     ports.push_back(ProtocolAddress(address, PROTO_TCP));
204   }
205   if (!relay_ssltcp_port.empty()) {
206     talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str()));
207     ports.push_back(ProtocolAddress(address, PROTO_SSLTCP));
208   }
209   config->AddRelay(ports, 0.0f);
210   ConfigReady(config);
211 }
212 
213 // HttpPortAllocator
214 
HttpPortAllocator(talk_base::NetworkManager * network_manager,talk_base::PacketSocketFactory * socket_factory,const std::string & user_agent)215 HttpPortAllocator::HttpPortAllocator(
216     talk_base::NetworkManager* network_manager,
217     talk_base::PacketSocketFactory* socket_factory,
218     const std::string &user_agent)
219     : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) {
220 }
221 
HttpPortAllocator(talk_base::NetworkManager * network_manager,const std::string & user_agent)222 HttpPortAllocator::HttpPortAllocator(
223     talk_base::NetworkManager* network_manager,
224     const std::string &user_agent)
225     : HttpPortAllocatorBase(network_manager, user_agent) {
226 }
~HttpPortAllocator()227 HttpPortAllocator::~HttpPortAllocator() {}
228 
CreateSession(const std::string & name,const std::string & session_type)229 PortAllocatorSession* HttpPortAllocator::CreateSession(
230     const std::string& name, const std::string& session_type) {
231   return new HttpPortAllocatorSession(this, name, session_type, stun_hosts(),
232       relay_hosts(), relay_token(), user_agent());
233 }
234 
235 // HttpPortAllocatorSession
236 
HttpPortAllocatorSession(HttpPortAllocator * allocator,const std::string & name,const std::string & session_type,const std::vector<talk_base::SocketAddress> & stun_hosts,const std::vector<std::string> & relay_hosts,const std::string & relay,const std::string & agent)237 HttpPortAllocatorSession::HttpPortAllocatorSession(
238     HttpPortAllocator* allocator,
239     const std::string& name,
240     const std::string& session_type,
241     const std::vector<talk_base::SocketAddress>& stun_hosts,
242     const std::vector<std::string>& relay_hosts,
243     const std::string& relay,
244     const std::string& agent)
245     : HttpPortAllocatorSessionBase(allocator, name, session_type, stun_hosts,
246                                    relay_hosts, relay, agent) {
247 }
248 
~HttpPortAllocatorSession()249 HttpPortAllocatorSession::~HttpPortAllocatorSession() {
250   for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin();
251        it != requests_.end(); ++it) {
252     (*it)->Destroy(true);
253   }
254 }
255 
SendSessionRequest(const std::string & host,int port)256 void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
257                                                   int port) {
258   // Initiate an HTTP request to create a session through the chosen host.
259   talk_base::AsyncHttpRequest* request =
260       new talk_base::AsyncHttpRequest(user_agent());
261   request->SignalWorkDone.connect(this,
262       &HttpPortAllocatorSession::OnRequestDone);
263 
264   request->set_secure(port == talk_base::HTTP_SECURE_PORT);
265   request->set_proxy(allocator()->proxy());
266   request->response().document.reset(new talk_base::MemoryStream);
267   request->request().verb = talk_base::HV_GET;
268   request->request().path = HttpPortAllocator::kCreateSessionURL;
269   request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true);
270   request->request().addHeader("X-Google-Relay-Auth", relay_token(), true);
271   request->request().addHeader("X-Session-Type", session_type(), true);
272   request->request().addHeader("X-Stream-Type", name(), true);
273   request->set_host(host);
274   request->set_port(port);
275   request->Start();
276   request->Release();
277   requests_.push_back(request);
278 }
279 
OnRequestDone(talk_base::SignalThread * data)280 void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
281   talk_base::AsyncHttpRequest* request =
282       static_cast<talk_base::AsyncHttpRequest*>(data);
283 
284   // Remove the request from the list of active requests.
285   std::list<talk_base::AsyncHttpRequest*>::iterator it =
286       std::find(requests_.begin(), requests_.end(), request);
287   if (it != requests_.end()) {
288     requests_.erase(it);
289   }
290 
291   if (request->response().scode != 200) {
292     LOG(LS_WARNING) << "HTTPPortAllocator: request "
293                     << " received error " << request->response().scode;
294     TryCreateRelaySession();
295     return;
296   }
297   LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
298 
299   talk_base::MemoryStream* stream =
300       static_cast<talk_base::MemoryStream*>(request->response().document.get());
301   stream->Rewind();
302   size_t length;
303   stream->GetSize(&length);
304   std::string resp = std::string(stream->GetBuffer(), length);
305   ReceiveSessionResponse(resp);
306 }
307 
308 }  // namespace cricket
309