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
42namespace {
43
44const uint32 MSG_TIMEOUT = 100;  // must not conflict
45  // with BasicPortAllocator.cpp
46
47// Helper routine to remove whitespace from the ends of a string.
48void 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.
60typedef std::map<std::string, std::string> StringMap;
61void 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
92namespace cricket {
93
94// HttpPortAllocatorBase
95
96const int HttpPortAllocatorBase::kNumRetries = 5;
97
98const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
99
100HttpPortAllocatorBase::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
110HttpPortAllocatorBase::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
119HttpPortAllocatorBase::~HttpPortAllocatorBase() {
120}
121
122// HttpPortAllocatorSessionBase
123
124HttpPortAllocatorSessionBase::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
140HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
141
142void 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
155void 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
183std::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
194void 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
235HttpPortAllocator::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
242HttpPortAllocator::HttpPortAllocator(
243    talk_base::NetworkManager* network_manager,
244    const std::string &user_agent)
245    : HttpPortAllocatorBase(network_manager, user_agent) {
246}
247HttpPortAllocator::~HttpPortAllocator() {}
248
249PortAllocatorSession* 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
261HttpPortAllocatorSession::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
276HttpPortAllocatorSession::~HttpPortAllocatorSession() {
277  for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin();
278       it != requests_.end(); ++it) {
279    (*it)->Destroy(true);
280  }
281}
282
283void 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
306void 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