henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame^] | 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 | const uint32 MSG_TIMEOUT = 100; // must not conflict |
| 45 | // with BasicPortAllocator.cpp |
| 46 | |
| 47 | // Helper routine to remove whitespace from the ends of a string. |
| 48 | void 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. |
| 60 | typedef std::map<std::string, std::string> StringMap; |
| 61 | void 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 | |
| 92 | namespace cricket { |
| 93 | |
| 94 | // HttpPortAllocatorBase |
| 95 | |
| 96 | const int HttpPortAllocatorBase::kNumRetries = 5; |
| 97 | |
| 98 | const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session"; |
| 99 | |
| 100 | HttpPortAllocatorBase::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 | |
| 110 | HttpPortAllocatorBase::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 | |
| 119 | HttpPortAllocatorBase::~HttpPortAllocatorBase() { |
| 120 | } |
| 121 | |
| 122 | // HttpPortAllocatorSessionBase |
| 123 | |
| 124 | HttpPortAllocatorSessionBase::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 | |
| 140 | HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {} |
| 141 | |
| 142 | void 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 | |
| 155 | void 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 | |
| 183 | std::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 | |
| 194 | void 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 | |
| 235 | HttpPortAllocator::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 | |
| 242 | HttpPortAllocator::HttpPortAllocator( |
| 243 | talk_base::NetworkManager* network_manager, |
| 244 | const std::string &user_agent) |
| 245 | : HttpPortAllocatorBase(network_manager, user_agent) { |
| 246 | } |
| 247 | HttpPortAllocator::~HttpPortAllocator() {} |
| 248 | |
| 249 | PortAllocatorSession* 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 | |
| 261 | HttpPortAllocatorSession::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 | |
| 276 | HttpPortAllocatorSession::~HttpPortAllocatorSession() { |
| 277 | for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin(); |
| 278 | it != requests_.end(); ++it) { |
| 279 | (*it)->Destroy(true); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | void 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 | |
| 306 | void 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 |