/src/mozilla-central/media/mtransport/test/turn_unittest.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | // Original author: ekr@rtfm.com |
8 | | |
9 | | // Some code copied from nICEr. License is: |
10 | | /* |
11 | | Copyright (c) 2007, Adobe Systems, Incorporated |
12 | | All rights reserved. |
13 | | |
14 | | Redistribution and use in source and binary forms, with or without |
15 | | modification, are permitted provided that the following conditions are |
16 | | met: |
17 | | |
18 | | * Redistributions of source code must retain the above copyright |
19 | | notice, this list of conditions and the following disclaimer. |
20 | | |
21 | | * Redistributions in binary form must reproduce the above copyright |
22 | | notice, this list of conditions and the following disclaimer in the |
23 | | documentation and/or other materials provided with the distribution. |
24 | | |
25 | | * Neither the name of Adobe Systems, Network Resonance nor the names of its |
26 | | contributors may be used to endorse or promote products derived from |
27 | | this software without specific prior written permission. |
28 | | |
29 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
30 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
31 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
32 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
33 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
34 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
35 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
36 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
37 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
38 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
39 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
40 | | */ |
41 | | |
42 | | #include <stdlib.h> |
43 | | #include <iostream> |
44 | | |
45 | | #include "sigslot.h" |
46 | | |
47 | | #include "logging.h" |
48 | | |
49 | | #include "nsThreadUtils.h" |
50 | | #include "nsXPCOM.h" |
51 | | |
52 | | #include "runnable_utils.h" |
53 | | |
54 | | #define GTEST_HAS_RTTI 0 |
55 | | #include "gtest/gtest.h" |
56 | | #include "gtest_utils.h" |
57 | | |
58 | | #define USE_TURN |
59 | | |
60 | | // nICEr includes |
61 | | extern "C" { |
62 | | #include "nr_api.h" |
63 | | #include "registry.h" |
64 | | #include "async_timer.h" |
65 | | #include "r_crc32.h" |
66 | | #include "ice_util.h" |
67 | | #include "transport_addr.h" |
68 | | #include "nr_crypto.h" |
69 | | #include "nr_socket.h" |
70 | | #include "nr_socket_local.h" |
71 | | #include "nr_socket_buffered_stun.h" |
72 | | #include "stun_client_ctx.h" |
73 | | #include "turn_client_ctx.h" |
74 | | } |
75 | | |
76 | | #include "nricemediastream.h" |
77 | | #include "nricectx.h" |
78 | | |
79 | | using namespace mozilla; |
80 | | |
81 | | static std::string kDummyTurnServer("192.0.2.1"); // From RFC 5737 |
82 | | |
83 | | class TurnClient : public MtransportTest { |
84 | | public: |
85 | | TurnClient() |
86 | | : MtransportTest(), |
87 | | real_socket_(nullptr), |
88 | | net_socket_(nullptr), |
89 | | buffered_socket_(nullptr), |
90 | | net_fd_(nullptr), |
91 | | turn_ctx_(nullptr), |
92 | | allocated_(false), |
93 | | received_(0), |
94 | 0 | protocol_(IPPROTO_UDP) { |
95 | 0 | } |
96 | | |
97 | 0 | ~TurnClient() { |
98 | 0 | } |
99 | | |
100 | 0 | static void SetUpTestCase() { |
101 | 0 | NrIceCtx::InitializeGlobals(false, false, false); |
102 | 0 | } |
103 | | |
104 | 0 | void SetTcp() { |
105 | 0 | protocol_ = IPPROTO_TCP; |
106 | 0 | } |
107 | | |
108 | 0 | void Init_s() { |
109 | 0 | int r; |
110 | 0 | nr_transport_addr addr; |
111 | 0 | r = nr_ip4_port_to_transport_addr(0, 0, protocol_, &addr); |
112 | 0 | ASSERT_EQ(0, r); |
113 | 0 |
|
114 | 0 | r = nr_socket_local_create(nullptr, &addr, &real_socket_); |
115 | 0 | ASSERT_EQ(0, r); |
116 | 0 |
|
117 | 0 | if (protocol_ == IPPROTO_TCP) { |
118 | 0 | int r = |
119 | 0 | nr_socket_buffered_stun_create(real_socket_, 100000, TURN_TCP_FRAMING, |
120 | 0 | &buffered_socket_); |
121 | 0 | ASSERT_EQ(0, r); |
122 | 0 | net_socket_ = buffered_socket_; |
123 | 0 | } else { |
124 | 0 | net_socket_ = real_socket_; |
125 | 0 | } |
126 | 0 |
|
127 | 0 | r = nr_str_port_to_transport_addr(turn_server_.c_str(), 3478, |
128 | 0 | protocol_, &addr); |
129 | 0 | ASSERT_EQ(0, r); |
130 | 0 |
|
131 | 0 | std::vector<unsigned char> password_vec( |
132 | 0 | turn_password_.begin(), turn_password_.end()); |
133 | 0 | Data password; |
134 | 0 | INIT_DATA(password, &password_vec[0], password_vec.size()); |
135 | 0 | r = nr_turn_client_ctx_create("test", net_socket_, |
136 | 0 | turn_user_.c_str(), |
137 | 0 | &password, |
138 | 0 | &addr, &turn_ctx_); |
139 | 0 | ASSERT_EQ(0, r); |
140 | 0 |
|
141 | 0 | r = nr_socket_getfd(net_socket_, &net_fd_); |
142 | 0 | ASSERT_EQ(0, r); |
143 | 0 |
|
144 | 0 | NR_ASYNC_WAIT(net_fd_, NR_ASYNC_WAIT_READ, socket_readable_cb, |
145 | 0 | (void *)this); |
146 | 0 | } |
147 | | |
148 | 0 | void TearDown_s() { |
149 | 0 | nr_turn_client_ctx_destroy(&turn_ctx_); |
150 | 0 | if (net_fd_) { |
151 | 0 | NR_ASYNC_CANCEL(net_fd_, NR_ASYNC_WAIT_READ); |
152 | 0 | } |
153 | 0 |
|
154 | 0 | nr_socket_destroy(&buffered_socket_); |
155 | 0 | } |
156 | | |
157 | 0 | void TearDown() { |
158 | 0 | RUN_ON_THREAD(test_utils_->sts_target(), |
159 | 0 | WrapRunnable(this, &TurnClient::TearDown_s), |
160 | 0 | NS_DISPATCH_SYNC); |
161 | 0 | } |
162 | | |
163 | 0 | void Allocate_s() { |
164 | 0 | Init_s(); |
165 | 0 | ASSERT_TRUE(turn_ctx_); |
166 | 0 |
|
167 | 0 | int r = nr_turn_client_allocate(turn_ctx_, |
168 | 0 | allocate_success_cb, |
169 | 0 | this); |
170 | 0 | ASSERT_EQ(0, r); |
171 | 0 | } |
172 | | |
173 | 0 | void Allocate(bool expect_success=true) { |
174 | 0 | RUN_ON_THREAD(test_utils_->sts_target(), |
175 | 0 | WrapRunnable(this, &TurnClient::Allocate_s), |
176 | 0 | NS_DISPATCH_SYNC); |
177 | 0 |
|
178 | 0 | if (expect_success) { |
179 | 0 | ASSERT_TRUE_WAIT(allocated_, 5000); |
180 | 0 | } |
181 | 0 | else { |
182 | 0 | PR_Sleep(10000); |
183 | 0 | ASSERT_FALSE(allocated_); |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | 0 | void Allocated() { |
188 | 0 | if (turn_ctx_->state!=NR_TURN_CLIENT_STATE_ALLOCATED) { |
189 | 0 | std::cerr << "Allocation failed" << std::endl; |
190 | 0 | return; |
191 | 0 | } |
192 | 0 | allocated_ = true; |
193 | 0 |
|
194 | 0 | int r; |
195 | 0 | nr_transport_addr addr; |
196 | 0 |
|
197 | 0 | r = nr_turn_client_get_relayed_address(turn_ctx_, &addr); |
198 | 0 | ASSERT_EQ(0, r); |
199 | 0 |
|
200 | 0 | relay_addr_ = addr.as_string; |
201 | 0 |
|
202 | 0 | std::cerr << "Allocation succeeded with addr=" << relay_addr_ << std::endl; |
203 | 0 | } |
204 | | |
205 | 0 | void Deallocate_s() { |
206 | 0 | ASSERT_TRUE(turn_ctx_); |
207 | 0 |
|
208 | 0 | std::cerr << "De-Allocating..." << std::endl; |
209 | 0 | int r = nr_turn_client_deallocate(turn_ctx_); |
210 | 0 | ASSERT_EQ(0, r); |
211 | 0 | } |
212 | | |
213 | 0 | void Deallocate() { |
214 | 0 | RUN_ON_THREAD(test_utils_->sts_target(), |
215 | 0 | WrapRunnable(this, &TurnClient::Deallocate_s), |
216 | 0 | NS_DISPATCH_SYNC); |
217 | 0 | } |
218 | | |
219 | 0 | void RequestPermission_s(const std::string& target) { |
220 | 0 | nr_transport_addr addr; |
221 | 0 | int r; |
222 | 0 |
|
223 | 0 | // Expected pattern here is "IP4:127.0.0.1:3487" |
224 | 0 | ASSERT_EQ(0, target.compare(0, 4, "IP4:")); |
225 | 0 |
|
226 | 0 | size_t offset = target.rfind(':'); |
227 | 0 | ASSERT_NE(std::string::npos, offset); |
228 | 0 |
|
229 | 0 | std::string host = target.substr(4, offset - 4); |
230 | 0 | std::string port = target.substr(offset + 1); |
231 | 0 |
|
232 | 0 | r = nr_str_port_to_transport_addr(host.c_str(), |
233 | 0 | atoi(port.c_str()), |
234 | 0 | IPPROTO_UDP, |
235 | 0 | &addr); |
236 | 0 | ASSERT_EQ(0, r); |
237 | 0 |
|
238 | 0 | r = nr_turn_client_ensure_perm(turn_ctx_, &addr); |
239 | 0 | ASSERT_EQ(0, r); |
240 | 0 | } |
241 | | |
242 | 0 | void RequestPermission(const std::string& target) { |
243 | 0 | RUN_ON_THREAD(test_utils_->sts_target(), |
244 | 0 | WrapRunnable(this, &TurnClient::RequestPermission_s, target), |
245 | 0 | NS_DISPATCH_SYNC); |
246 | 0 |
|
247 | 0 | } |
248 | | |
249 | 0 | void Readable(NR_SOCKET s, int how, void *arg) { |
250 | 0 | // Re-arm |
251 | 0 | std::cerr << "Socket is readable" << std::endl; |
252 | 0 | NR_ASYNC_WAIT(s, how, socket_readable_cb, arg); |
253 | 0 |
|
254 | 0 | UCHAR buf[8192]; |
255 | 0 | size_t len_s; |
256 | 0 | nr_transport_addr addr; |
257 | 0 |
|
258 | 0 | int r = nr_socket_recvfrom(net_socket_, buf, sizeof(buf), &len_s, 0, &addr); |
259 | 0 | if (r) { |
260 | 0 | std::cerr << "Error reading from socket" << std::endl; |
261 | 0 | return; |
262 | 0 | } |
263 | 0 | |
264 | 0 | ASSERT_LT(len_s, (size_t)INT_MAX); |
265 | 0 | int len = (int)len_s; |
266 | 0 |
|
267 | 0 | if (nr_is_stun_response_message(buf, len)) { |
268 | 0 | std::cerr << "STUN response" << std::endl; |
269 | 0 | r = nr_turn_client_process_response(turn_ctx_, buf, len, &addr); |
270 | 0 |
|
271 | 0 | if (r && r != R_REJECTED && r != R_RETRY) { |
272 | 0 | std::cerr << "Error processing STUN: " << r << std::endl; |
273 | 0 | } |
274 | 0 | } else if (nr_is_stun_indication_message(buf, len)) { |
275 | 0 | std::cerr << "STUN indication" << std::endl; |
276 | 0 |
|
277 | 0 | /* Process the indication */ |
278 | 0 | unsigned char data[NR_STUN_MAX_MESSAGE_SIZE]; |
279 | 0 | size_t datal; |
280 | 0 | nr_transport_addr remote_addr; |
281 | 0 |
|
282 | 0 | r = nr_turn_client_parse_data_indication(turn_ctx_, &addr, |
283 | 0 | buf, len, |
284 | 0 | data, &datal, sizeof(data), |
285 | 0 | &remote_addr); |
286 | 0 | ASSERT_EQ(0, r); |
287 | 0 | std::cerr << "Received " << datal << " bytes from " |
288 | 0 | << remote_addr.as_string << std::endl; |
289 | 0 |
|
290 | 0 | received_ += datal; |
291 | 0 |
|
292 | 0 | for (size_t i=0; i < datal; i++) { |
293 | 0 | ASSERT_EQ(i & 0xff, data[i]); |
294 | 0 | } |
295 | 0 | } |
296 | 0 | else { |
297 | 0 | if (nr_is_stun_message(buf, len)) { |
298 | 0 | std::cerr << "STUN message of unexpected type" << std::endl; |
299 | 0 | } else { |
300 | 0 | std::cerr << "Not a STUN message" << std::endl; |
301 | 0 | } |
302 | 0 | return; |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | 0 | void SendTo_s(const std::string& target, int expect_return) { |
307 | 0 | nr_transport_addr addr; |
308 | 0 | int r; |
309 | 0 |
|
310 | 0 | // Expected pattern here is "IP4:127.0.0.1:3487" |
311 | 0 | ASSERT_EQ(0, target.compare(0, 4, "IP4:")); |
312 | 0 |
|
313 | 0 | size_t offset = target.rfind(':'); |
314 | 0 | ASSERT_NE(std::string::npos, offset); |
315 | 0 |
|
316 | 0 | std::string host = target.substr(4, offset - 4); |
317 | 0 | std::string port = target.substr(offset + 1); |
318 | 0 |
|
319 | 0 | r = nr_str_port_to_transport_addr(host.c_str(), |
320 | 0 | atoi(port.c_str()), |
321 | 0 | IPPROTO_UDP, |
322 | 0 | &addr); |
323 | 0 | ASSERT_EQ(0, r); |
324 | 0 |
|
325 | 0 | unsigned char test[100]; |
326 | 0 | for (size_t i=0; i<sizeof(test); i++) { |
327 | 0 | test[i] = i & 0xff; |
328 | 0 | } |
329 | 0 |
|
330 | 0 | std::cerr << "Sending test message to " << target << " ..." << std::endl; |
331 | 0 |
|
332 | 0 | r = nr_turn_client_send_indication(turn_ctx_, |
333 | 0 | test, sizeof(test), 0, |
334 | 0 | &addr); |
335 | 0 | if (expect_return >= 0) { |
336 | 0 | ASSERT_EQ(expect_return, r); |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | 0 | void SendTo(const std::string& target, int expect_return=0) { |
341 | 0 | RUN_ON_THREAD(test_utils_->sts_target(), |
342 | 0 | WrapRunnable(this, &TurnClient::SendTo_s, target, |
343 | 0 | expect_return), |
344 | 0 | NS_DISPATCH_SYNC); |
345 | 0 | } |
346 | | |
347 | 0 | int received() const { return received_; } |
348 | | |
349 | 0 | static void socket_readable_cb(NR_SOCKET s, int how, void *arg) { |
350 | 0 | static_cast<TurnClient *>(arg)->Readable(s, how, arg); |
351 | 0 | } |
352 | | |
353 | 0 | static void allocate_success_cb(NR_SOCKET s, int how, void *arg){ |
354 | 0 | static_cast<TurnClient *>(arg)->Allocated(); |
355 | 0 | } |
356 | | |
357 | | protected: |
358 | | std::string turn_server_; |
359 | | nr_socket *real_socket_; |
360 | | nr_socket *net_socket_; |
361 | | nr_socket *buffered_socket_; |
362 | | NR_SOCKET net_fd_; |
363 | | nr_turn_client_ctx *turn_ctx_; |
364 | | std::string relay_addr_; |
365 | | bool allocated_; |
366 | | int received_; |
367 | | int protocol_; |
368 | | }; |
369 | | |
370 | 0 | TEST_F(TurnClient, Allocate) { |
371 | 0 | if (WarnIfTurnNotConfigured()) |
372 | 0 | return; |
373 | 0 | |
374 | 0 | Allocate(); |
375 | 0 | } |
376 | | |
377 | 0 | TEST_F(TurnClient, AllocateTcp) { |
378 | 0 | if (WarnIfTurnNotConfigured()) |
379 | 0 | return; |
380 | 0 | |
381 | 0 | SetTcp(); |
382 | 0 | Allocate(); |
383 | 0 | } |
384 | | |
385 | 0 | TEST_F(TurnClient, AllocateAndHold) { |
386 | 0 | if (WarnIfTurnNotConfigured()) |
387 | 0 | return; |
388 | 0 | |
389 | 0 | Allocate(); |
390 | 0 | PR_Sleep(20000); |
391 | 0 | ASSERT_TRUE(turn_ctx_->state == NR_TURN_CLIENT_STATE_ALLOCATED); |
392 | 0 | } |
393 | | |
394 | 0 | TEST_F(TurnClient, SendToSelf) { |
395 | 0 | if (WarnIfTurnNotConfigured()) |
396 | 0 | return; |
397 | 0 | |
398 | 0 | Allocate(); |
399 | 0 | SendTo(relay_addr_); |
400 | 0 | ASSERT_TRUE_WAIT(received() == 100, 5000); |
401 | 0 | SendTo(relay_addr_); |
402 | 0 | ASSERT_TRUE_WAIT(received() == 200, 1000); |
403 | 0 | } |
404 | | |
405 | | |
406 | 0 | TEST_F(TurnClient, SendToSelfTcp) { |
407 | 0 | if (WarnIfTurnNotConfigured()) |
408 | 0 | return; |
409 | 0 | |
410 | 0 | SetTcp(); |
411 | 0 | Allocate(); |
412 | 0 | SendTo(relay_addr_); |
413 | 0 | ASSERT_TRUE_WAIT(received() == 100, 5000); |
414 | 0 | SendTo(relay_addr_); |
415 | 0 | ASSERT_TRUE_WAIT(received() == 200, 1000); |
416 | 0 | } |
417 | | |
418 | 0 | TEST_F(TurnClient, PermissionDenied) { |
419 | 0 | if (WarnIfTurnNotConfigured()) |
420 | 0 | return; |
421 | 0 | |
422 | 0 | Allocate(); |
423 | 0 | RequestPermission(relay_addr_); |
424 | 0 | PR_Sleep(1000); |
425 | 0 |
|
426 | 0 | /* Fake a 403 response */ |
427 | 0 | nr_turn_permission *perm; |
428 | 0 | perm = STAILQ_FIRST(&turn_ctx_->permissions); |
429 | 0 | ASSERT_TRUE(perm); |
430 | 0 | while (perm) { |
431 | 0 | perm->stun->last_error_code = 403; |
432 | 0 | std::cerr << "Set 403's on permission" << std::endl; |
433 | 0 | perm = STAILQ_NEXT(perm, entry); |
434 | 0 | } |
435 | 0 |
|
436 | 0 | SendTo(relay_addr_, R_NOT_PERMITTED); |
437 | 0 | ASSERT_TRUE(received() == 0); |
438 | 0 |
|
439 | 0 | //TODO: We should check if we can still send to a second destination, but |
440 | 0 | // we would need a second TURN client as one client can only handle one |
441 | 0 | // allocation (maybe as part of bug 1128128 ?). |
442 | 0 | } |
443 | | |
444 | 0 | TEST_F(TurnClient, DeallocateReceiveFailure) { |
445 | 0 | if (WarnIfTurnNotConfigured()) |
446 | 0 | return; |
447 | 0 | |
448 | 0 | Allocate(); |
449 | 0 | SendTo(relay_addr_); |
450 | 0 | ASSERT_TRUE_WAIT(received() == 100, 5000); |
451 | 0 | Deallocate(); |
452 | 0 | turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED; |
453 | 0 | SendTo(relay_addr_); |
454 | 0 | PR_Sleep(1000); |
455 | 0 | ASSERT_TRUE(received() == 100); |
456 | 0 | } |
457 | | |
458 | 0 | TEST_F(TurnClient, DeallocateReceiveFailureTcp) { |
459 | 0 | if (WarnIfTurnNotConfigured()) |
460 | 0 | return; |
461 | 0 | |
462 | 0 | SetTcp(); |
463 | 0 | Allocate(); |
464 | 0 | SendTo(relay_addr_); |
465 | 0 | ASSERT_TRUE_WAIT(received() == 100, 5000); |
466 | 0 | Deallocate(); |
467 | 0 | turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED; |
468 | 0 | /* Either the connection got closed by the TURN server already, then the send |
469 | 0 | * is going to fail, which we simply ignore. Or the connection is still alive |
470 | 0 | * and we cand send the data, but it should not get forwarded to us. In either |
471 | 0 | * case we should not receive more data. */ |
472 | 0 | SendTo(relay_addr_, -1); |
473 | 0 | PR_Sleep(1000); |
474 | 0 | ASSERT_TRUE(received() == 100); |
475 | 0 | } |
476 | | |
477 | 0 | TEST_F(TurnClient, AllocateDummyServer) { |
478 | 0 | if (WarnIfTurnNotConfigured()) |
479 | 0 | return; |
480 | 0 | |
481 | 0 | turn_server_ = kDummyTurnServer; |
482 | 0 | Allocate(false); |
483 | 0 | } |