2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
11 #include "webrtc/p2p/client/httpportallocator.h"
16 #include "webrtc/base/asynchttprequest.h"
17 #include "webrtc/base/basicdefs.h"
18 #include "webrtc/base/common.h"
19 #include "webrtc/base/helpers.h"
20 #include "webrtc/base/logging.h"
21 #include "webrtc/base/nethelpers.h"
22 #include "webrtc/base/signalthread.h"
23 #include "webrtc/base/stringencode.h"
27 // Helper routine to remove whitespace from the ends of a string.
28 void Trim(std::string& str) {
29 size_t first = str.find_first_not_of(" \t\r\n");
30 if (first == std::string::npos) {
35 ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos);
38 // Parses the lines in the result of the HTTP request that are of the form
39 // 'a=b' and returns them in a map.
40 typedef std::map<std::string, std::string> StringMap;
41 void ParseMap(const std::string& string, StringMap& map) {
42 size_t start_of_line = 0;
43 size_t end_of_line = 0;
45 for (;;) { // for each line
46 start_of_line = string.find_first_not_of("\r\n", end_of_line);
47 if (start_of_line == std::string::npos)
50 end_of_line = string.find_first_of("\r\n", start_of_line);
51 if (end_of_line == std::string::npos) {
52 end_of_line = string.length();
55 size_t equals = string.find('=', start_of_line);
56 if ((equals >= end_of_line) || (equals == std::string::npos))
59 std::string key(string, start_of_line, equals - start_of_line);
60 std::string value(string, equals + 1, end_of_line - equals - 1);
65 if ((key.size() > 0) && (value.size() > 0))
74 // HttpPortAllocatorBase
76 const int HttpPortAllocatorBase::kNumRetries = 5;
78 const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
80 HttpPortAllocatorBase::HttpPortAllocatorBase(
81 rtc::NetworkManager* network_manager,
82 rtc::PacketSocketFactory* socket_factory,
83 const std::string &user_agent)
84 : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) {
85 relay_hosts_.push_back("relay.google.com");
86 stun_hosts_.push_back(
87 rtc::SocketAddress("stun.l.google.com", 19302));
90 HttpPortAllocatorBase::HttpPortAllocatorBase(
91 rtc::NetworkManager* network_manager,
92 const std::string &user_agent)
93 : BasicPortAllocator(network_manager), agent_(user_agent) {
94 relay_hosts_.push_back("relay.google.com");
95 stun_hosts_.push_back(
96 rtc::SocketAddress("stun.l.google.com", 19302));
99 HttpPortAllocatorBase::~HttpPortAllocatorBase() {
102 // HttpPortAllocatorSessionBase
104 HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase(
105 HttpPortAllocatorBase* allocator,
106 const std::string& content_name,
108 const std::string& ice_ufrag,
109 const std::string& ice_pwd,
110 const std::vector<rtc::SocketAddress>& stun_hosts,
111 const std::vector<std::string>& relay_hosts,
112 const std::string& relay_token,
113 const std::string& user_agent)
114 : BasicPortAllocatorSession(allocator, content_name, component,
116 relay_hosts_(relay_hosts), stun_hosts_(stun_hosts),
117 relay_token_(relay_token), agent_(user_agent), attempts_(0) {
120 HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
122 void HttpPortAllocatorSessionBase::GetPortConfigurations() {
123 // Creating relay sessions can take time and is done asynchronously.
124 // Creating stun sessions could also take time and could be done aysnc also,
125 // but for now is done here and added to the initial config. Note any later
126 // configs will have unresolved stun ips and will be discarded by the
127 // AllocationSequence.
128 ServerAddresses hosts;
129 for (std::vector<rtc::SocketAddress>::iterator it = stun_hosts_.begin();
130 it != stun_hosts_.end(); ++it) {
134 PortConfiguration* config = new PortConfiguration(hosts,
138 TryCreateRelaySession();
141 void HttpPortAllocatorSessionBase::TryCreateRelaySession() {
142 if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) {
143 LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping.";
147 if (attempts_ == HttpPortAllocator::kNumRetries) {
148 LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; "
149 << "giving up on relay.";
153 if (relay_hosts_.size() == 0) {
154 LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured.";
158 // Choose the next host to try.
159 std::string host = relay_hosts_[attempts_ % relay_hosts_.size()];
161 LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host;
162 if (relay_token_.empty()) {
163 LOG(LS_WARNING) << "No relay auth token found.";
166 SendSessionRequest(host, rtc::HTTP_SECURE_PORT);
169 std::string HttpPortAllocatorSessionBase::GetSessionRequestUrl() {
170 std::string url = std::string(HttpPortAllocator::kCreateSessionURL);
171 if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHARED_UFRAG) {
172 ASSERT(!username().empty());
173 ASSERT(!password().empty());
174 url = url + "?username=" + rtc::s_url_encode(username()) +
175 "&password=" + rtc::s_url_encode(password());
180 void HttpPortAllocatorSessionBase::ReceiveSessionResponse(
181 const std::string& response) {
184 ParseMap(response, map);
186 if (!username().empty() && map["username"] != username()) {
187 LOG(LS_WARNING) << "Received unexpected username value from relay server.";
189 if (!password().empty() && map["password"] != password()) {
190 LOG(LS_WARNING) << "Received unexpected password value from relay server.";
193 std::string relay_ip = map["relay.ip"];
194 std::string relay_udp_port = map["relay.udp_port"];
195 std::string relay_tcp_port = map["relay.tcp_port"];
196 std::string relay_ssltcp_port = map["relay.ssltcp_port"];
198 ServerAddresses hosts;
199 for (std::vector<rtc::SocketAddress>::iterator it = stun_hosts_.begin();
200 it != stun_hosts_.end(); ++it) {
204 PortConfiguration* config = new PortConfiguration(hosts,
208 RelayServerConfig relay_config(RELAY_GTURN);
209 if (!relay_udp_port.empty()) {
210 rtc::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str()));
211 relay_config.ports.push_back(ProtocolAddress(address, PROTO_UDP));
213 if (!relay_tcp_port.empty()) {
214 rtc::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str()));
215 relay_config.ports.push_back(ProtocolAddress(address, PROTO_TCP));
217 if (!relay_ssltcp_port.empty()) {
218 rtc::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str()));
219 relay_config.ports.push_back(ProtocolAddress(address, PROTO_SSLTCP));
221 config->AddRelay(relay_config);
227 HttpPortAllocator::HttpPortAllocator(
228 rtc::NetworkManager* network_manager,
229 rtc::PacketSocketFactory* socket_factory,
230 const std::string &user_agent)
231 : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) {
234 HttpPortAllocator::HttpPortAllocator(
235 rtc::NetworkManager* network_manager,
236 const std::string &user_agent)
237 : HttpPortAllocatorBase(network_manager, user_agent) {
239 HttpPortAllocator::~HttpPortAllocator() {}
241 PortAllocatorSession* HttpPortAllocator::CreateSessionInternal(
242 const std::string& content_name,
244 const std::string& ice_ufrag, const std::string& ice_pwd) {
245 return new HttpPortAllocatorSession(this, content_name, component,
246 ice_ufrag, ice_pwd, stun_hosts(),
247 relay_hosts(), relay_token(),
251 // HttpPortAllocatorSession
253 HttpPortAllocatorSession::HttpPortAllocatorSession(
254 HttpPortAllocator* allocator,
255 const std::string& content_name,
257 const std::string& ice_ufrag,
258 const std::string& ice_pwd,
259 const std::vector<rtc::SocketAddress>& stun_hosts,
260 const std::vector<std::string>& relay_hosts,
261 const std::string& relay,
262 const std::string& agent)
263 : HttpPortAllocatorSessionBase(allocator, content_name, component,
264 ice_ufrag, ice_pwd, stun_hosts,
265 relay_hosts, relay, agent) {
268 HttpPortAllocatorSession::~HttpPortAllocatorSession() {
269 for (std::list<rtc::AsyncHttpRequest*>::iterator it = requests_.begin();
270 it != requests_.end(); ++it) {
271 (*it)->Destroy(true);
275 void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
277 // Initiate an HTTP request to create a session through the chosen host.
278 rtc::AsyncHttpRequest* request =
279 new rtc::AsyncHttpRequest(user_agent());
280 request->SignalWorkDone.connect(this,
281 &HttpPortAllocatorSession::OnRequestDone);
283 request->set_secure(port == rtc::HTTP_SECURE_PORT);
284 request->set_proxy(allocator()->proxy());
285 request->response().document.reset(new rtc::MemoryStream);
286 request->request().verb = rtc::HV_GET;
287 request->request().path = GetSessionRequestUrl();
288 request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true);
289 request->request().addHeader("X-Stream-Type", "video_rtp", true);
290 request->set_host(host);
291 request->set_port(port);
295 requests_.push_back(request);
298 void HttpPortAllocatorSession::OnRequestDone(rtc::SignalThread* data) {
299 rtc::AsyncHttpRequest* request =
300 static_cast<rtc::AsyncHttpRequest*>(data);
302 // Remove the request from the list of active requests.
303 std::list<rtc::AsyncHttpRequest*>::iterator it =
304 std::find(requests_.begin(), requests_.end(), request);
305 if (it != requests_.end()) {
309 if (request->response().scode != 200) {
310 LOG(LS_WARNING) << "HTTPPortAllocator: request "
311 << " received error " << request->response().scode;
312 TryCreateRelaySession();
315 LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
317 rtc::MemoryStream* stream =
318 static_cast<rtc::MemoryStream*>(request->response().document.get());
321 stream->GetSize(&length);
322 std::string resp = std::string(stream->GetBuffer(), length);
323 ReceiveSessionResponse(resp);
326 } // namespace cricket