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