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 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 talk_base::NetworkManager* network_manager, 99 talk_base::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 talk_base::SocketAddress("stun.l.google.com", 19302)); 105 } 106 107 HttpPortAllocatorBase::HttpPortAllocatorBase( 108 talk_base::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 talk_base::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<talk_base::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 PortConfiguration* config = new PortConfiguration(stun_hosts_[0], 146 username(), 147 password()); 148 ConfigReady(config); 149 TryCreateRelaySession(); 150 } 151 152 void HttpPortAllocatorSessionBase::TryCreateRelaySession() { 153 if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) { 154 LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping."; 155 return; 156 } 157 158 if (attempts_ == HttpPortAllocator::kNumRetries) { 159 LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; " 160 << "giving up on relay."; 161 return; 162 } 163 164 if (relay_hosts_.size() == 0) { 165 LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured."; 166 return; 167 } 168 169 // Choose the next host to try. 170 std::string host = relay_hosts_[attempts_ % relay_hosts_.size()]; 171 attempts_++; 172 LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host; 173 if (relay_token_.empty()) { 174 LOG(LS_WARNING) << "No relay auth token found."; 175 } 176 177 SendSessionRequest(host, talk_base::HTTP_SECURE_PORT); 178 } 179 180 std::string HttpPortAllocatorSessionBase::GetSessionRequestUrl() { 181 std::string url = std::string(HttpPortAllocator::kCreateSessionURL); 182 if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHARED_UFRAG) { 183 ASSERT(!username().empty()); 184 ASSERT(!password().empty()); 185 url = url + "?username=" + talk_base::s_url_encode(username()) + 186 "&password=" + talk_base::s_url_encode(password()); 187 } 188 return url; 189 } 190 191 void HttpPortAllocatorSessionBase::ReceiveSessionResponse( 192 const std::string& response) { 193 194 StringMap map; 195 ParseMap(response, map); 196 197 if (!username().empty() && map["username"] != username()) { 198 LOG(LS_WARNING) << "Received unexpected username value from relay server."; 199 } 200 if (!password().empty() && map["password"] != password()) { 201 LOG(LS_WARNING) << "Received unexpected password value from relay server."; 202 } 203 204 std::string relay_ip = map["relay.ip"]; 205 std::string relay_udp_port = map["relay.udp_port"]; 206 std::string relay_tcp_port = map["relay.tcp_port"]; 207 std::string relay_ssltcp_port = map["relay.ssltcp_port"]; 208 209 PortConfiguration* config = new PortConfiguration(stun_hosts_[0], 210 map["username"], 211 map["password"]); 212 213 RelayServerConfig relay_config(RELAY_GTURN); 214 if (!relay_udp_port.empty()) { 215 talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str())); 216 relay_config.ports.push_back(ProtocolAddress(address, PROTO_UDP)); 217 } 218 if (!relay_tcp_port.empty()) { 219 talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str())); 220 relay_config.ports.push_back(ProtocolAddress(address, PROTO_TCP)); 221 } 222 if (!relay_ssltcp_port.empty()) { 223 talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str())); 224 relay_config.ports.push_back(ProtocolAddress(address, PROTO_SSLTCP)); 225 } 226 config->AddRelay(relay_config); 227 ConfigReady(config); 228 } 229 230 // HttpPortAllocator 231 232 HttpPortAllocator::HttpPortAllocator( 233 talk_base::NetworkManager* network_manager, 234 talk_base::PacketSocketFactory* socket_factory, 235 const std::string &user_agent) 236 : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) { 237 } 238 239 HttpPortAllocator::HttpPortAllocator( 240 talk_base::NetworkManager* network_manager, 241 const std::string &user_agent) 242 : HttpPortAllocatorBase(network_manager, user_agent) { 243 } 244 HttpPortAllocator::~HttpPortAllocator() {} 245 246 PortAllocatorSession* HttpPortAllocator::CreateSessionInternal( 247 const std::string& content_name, 248 int component, 249 const std::string& ice_ufrag, const std::string& ice_pwd) { 250 return new HttpPortAllocatorSession(this, content_name, component, 251 ice_ufrag, ice_pwd, stun_hosts(), 252 relay_hosts(), relay_token(), 253 user_agent()); 254 } 255 256 // HttpPortAllocatorSession 257 258 HttpPortAllocatorSession::HttpPortAllocatorSession( 259 HttpPortAllocator* allocator, 260 const std::string& content_name, 261 int component, 262 const std::string& ice_ufrag, 263 const std::string& ice_pwd, 264 const std::vector<talk_base::SocketAddress>& stun_hosts, 265 const std::vector<std::string>& relay_hosts, 266 const std::string& relay, 267 const std::string& agent) 268 : HttpPortAllocatorSessionBase(allocator, content_name, component, 269 ice_ufrag, ice_pwd, stun_hosts, 270 relay_hosts, relay, agent) { 271 } 272 273 HttpPortAllocatorSession::~HttpPortAllocatorSession() { 274 for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin(); 275 it != requests_.end(); ++it) { 276 (*it)->Destroy(true); 277 } 278 } 279 280 void HttpPortAllocatorSession::SendSessionRequest(const std::string& host, 281 int port) { 282 // Initiate an HTTP request to create a session through the chosen host. 283 talk_base::AsyncHttpRequest* request = 284 new talk_base::AsyncHttpRequest(user_agent()); 285 request->SignalWorkDone.connect(this, 286 &HttpPortAllocatorSession::OnRequestDone); 287 288 request->set_secure(port == talk_base::HTTP_SECURE_PORT); 289 request->set_proxy(allocator()->proxy()); 290 request->response().document.reset(new talk_base::MemoryStream); 291 request->request().verb = talk_base::HV_GET; 292 request->request().path = GetSessionRequestUrl(); 293 request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true); 294 request->request().addHeader("X-Stream-Type", "video_rtp", true); 295 request->set_host(host); 296 request->set_port(port); 297 request->Start(); 298 request->Release(); 299 300 requests_.push_back(request); 301 } 302 303 void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) { 304 talk_base::AsyncHttpRequest* request = 305 static_cast<talk_base::AsyncHttpRequest*>(data); 306 307 // Remove the request from the list of active requests. 308 std::list<talk_base::AsyncHttpRequest*>::iterator it = 309 std::find(requests_.begin(), requests_.end(), request); 310 if (it != requests_.end()) { 311 requests_.erase(it); 312 } 313 314 if (request->response().scode != 200) { 315 LOG(LS_WARNING) << "HTTPPortAllocator: request " 316 << " received error " << request->response().scode; 317 TryCreateRelaySession(); 318 return; 319 } 320 LOG(LS_INFO) << "HTTPPortAllocator: request succeeded"; 321 322 talk_base::MemoryStream* stream = 323 static_cast<talk_base::MemoryStream*>(request->response().document.get()); 324 stream->Rewind(); 325 size_t length; 326 stream->GetSize(&length); 327 std::string resp = std::string(stream->GetBuffer(), length); 328 ReceiveSessionResponse(resp); 329 } 330 331 } // namespace cricket 332