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