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.
Trim(std::string & str)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;
ParseMap(const std::string & string,StringMap & map)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
HttpPortAllocatorBase(rtc::NetworkManager * network_manager,rtc::PacketSocketFactory * socket_factory,const std::string & user_agent)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
HttpPortAllocatorBase(rtc::NetworkManager * network_manager,const std::string & user_agent)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
~HttpPortAllocatorBase()116 HttpPortAllocatorBase::~HttpPortAllocatorBase() {
117 }
118
119 // HttpPortAllocatorSessionBase
120
HttpPortAllocatorSessionBase(HttpPortAllocatorBase * allocator,const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd,const std::vector<rtc::SocketAddress> & stun_hosts,const std::vector<std::string> & relay_hosts,const std::string & relay_token,const std::string & user_agent)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
~HttpPortAllocatorSessionBase()137 HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
138
GetPortConfigurations()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
TryCreateRelaySession()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
GetSessionRequestUrl()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
ReceiveSessionResponse(const std::string & response)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
HttpPortAllocator(rtc::NetworkManager * network_manager,rtc::PacketSocketFactory * socket_factory,const std::string & user_agent)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
HttpPortAllocator(rtc::NetworkManager * network_manager,const std::string & user_agent)251 HttpPortAllocator::HttpPortAllocator(
252 rtc::NetworkManager* network_manager,
253 const std::string &user_agent)
254 : HttpPortAllocatorBase(network_manager, user_agent) {
255 }
~HttpPortAllocator()256 HttpPortAllocator::~HttpPortAllocator() {}
257
CreateSessionInternal(const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd)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
HttpPortAllocatorSession(HttpPortAllocator * allocator,const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd,const std::vector<rtc::SocketAddress> & stun_hosts,const std::vector<std::string> & relay_hosts,const std::string & relay,const std::string & agent)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
~HttpPortAllocatorSession()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
SendSessionRequest(const std::string & host,int port)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
OnRequestDone(rtc::SignalThread * data)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