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