Home | History | Annotate | Download | only in client
      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 
     42 namespace {
     43 
     44 // Helper routine to remove whitespace from the ends of a string.
     45 void 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.
     57 typedef std::map<std::string, std::string> StringMap;
     58 void 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 
     89 namespace cricket {
     90 
     91 // HttpPortAllocatorBase
     92 
     93 const int HttpPortAllocatorBase::kNumRetries = 5;
     94 
     95 const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
     96 
     97 HttpPortAllocatorBase::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 
    107 HttpPortAllocatorBase::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 
    116 HttpPortAllocatorBase::~HttpPortAllocatorBase() {
    117 }
    118 
    119 // HttpPortAllocatorSessionBase
    120 
    121 HttpPortAllocatorSessionBase::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 
    137 HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
    138 
    139 void 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 
    158 void 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 
    186 std::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 
    197 void 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 
    244 HttpPortAllocator::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 
    251 HttpPortAllocator::HttpPortAllocator(
    252     rtc::NetworkManager* network_manager,
    253     const std::string &user_agent)
    254     : HttpPortAllocatorBase(network_manager, user_agent) {
    255 }
    256 HttpPortAllocator::~HttpPortAllocator() {}
    257 
    258 PortAllocatorSession* 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 
    270 HttpPortAllocatorSession::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 
    285 HttpPortAllocatorSession::~HttpPortAllocatorSession() {
    286   for (std::list<rtc::AsyncHttpRequest*>::iterator it = requests_.begin();
    287        it != requests_.end(); ++it) {
    288     (*it)->Destroy(true);
    289   }
    290 }
    291 
    292 void 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 
    315 void 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