blob: 31315d254005561d18594cefe29f8d7ebe728af0 [file] [log] [blame]
Amna0b50a032024-02-13 14:42:26 -05001/*
2 * Copyright (C) 2021-2024 Savoir-faire Linux Inc.
3 * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#include <cppunit/TestAssert.h>
20#include <cppunit/TestFixture.h>
21#include <cppunit/extensions/HelperMacros.h>
22#include <regex>
23
24#include <condition_variable>
25#include <asio/executor_work_guard.hpp>
26#include <asio/io_context.hpp>
27
28#include "opendht/dhtrunner.h"
29#include "opendht/thread_pool.h"
30#include "test_runner.h"
31#include "upnp/upnp_context.h"
32#include "ice_transport.h"
33#include "ice_transport_factory.h"
34
35
36namespace dhtnet {
37namespace test {
38
39class IceTest : public CppUnit::TestFixture
40{
41public:
42 IceTest()
43 {
44
45 }
46 ~IceTest() {}
47 static std::string name() { return "Ice"; }
48 void setUp();
49 void tearDown();
50
51 // For future tests with publicIp
52 std::shared_ptr<dht::DhtRunner> dht_ {};
53 std::unique_ptr<dhtnet::IpAddr> turnV4_ {};
54
55 std::shared_ptr<asio::io_context> ioContext;
56 std::shared_ptr<std::thread> ioContextRunner;
57 std::shared_ptr<IceTransportFactory> factory;
58 std::shared_ptr<upnp::UPnPContext> upnpContext;
59
60private:
61 void testRawIceConnection();
62 void testTurnMasterIceConnection();
63 void testTurnSlaveIceConnection();
64 void testReceiveTooManyCandidates();
65 void testCompleteOnFailure();
66
67 CPPUNIT_TEST_SUITE(IceTest);
68 CPPUNIT_TEST(testRawIceConnection);
69 CPPUNIT_TEST(testTurnMasterIceConnection);
70 CPPUNIT_TEST(testTurnSlaveIceConnection);
71 CPPUNIT_TEST(testReceiveTooManyCandidates);
72 CPPUNIT_TEST(testCompleteOnFailure);
73 CPPUNIT_TEST_SUITE_END();
74};
75
76CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(IceTest, IceTest::name());
77
78void
79IceTest::setUp()
80{
81 if (!dht_) {
82 dht_ = std::make_shared<dht::DhtRunner>();
83 dht::DhtRunner::Config config {};
84 dht::DhtRunner::Context context {};
85 dht_->run(0, config, std::move(context));
86 dht_->bootstrap("bootstrap.jami.net:4222");
87 std::this_thread::sleep_for(std::chrono::seconds(5));
88 }
89 if (!turnV4_) {
90 turnV4_ = std::make_unique<dhtnet::IpAddr>("turn.jami.net", AF_INET);
91 }
92 if (!upnpContext) {
93 if (!ioContext) {
94 ioContext = std::make_shared<asio::io_context>();
95 ioContextRunner = std::make_shared<std::thread>([&] { ioContext->run(); });
96 }
97 upnpContext = std::make_shared<dhtnet::upnp::UPnPContext>(ioContext, nullptr);
98 }
99 if (!factory) {
100 factory = std::make_shared<IceTransportFactory>();
101 }
102}
103
104
105void
106IceTest::tearDown()
107{
108 upnpContext->shutdown();
109 ioContext->stop();
110 if (ioContextRunner && ioContextRunner->joinable()) {
111 ioContextRunner->join();
112 }
113 dht_.reset();
114 turnV4_.reset();
115}
116
117void
118IceTest::testRawIceConnection()
119{
120 dhtnet::IceTransportOptions ice_config;
121 ice_config.upnpEnable = true;
122 ice_config.tcpEnable = true;
123 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
124 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
125 std::unique_lock<std::mutex> lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
126 lk_init {mtx_init};
127 std::condition_variable cv, cv_create, cv_resp, cv_init;
128 std::string init = {};
129 std::string response = {};
130 bool iceMasterReady = false, iceSlaveReady = false;
131 ice_config.onInitDone = [&](bool ok) {
132 CPPUNIT_ASSERT(ok);
133 dht::ThreadPool::io().run([&] {
134 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
135 return ice_master != nullptr;
136 }));
137 auto iceAttributes = ice_master->getLocalAttributes();
138 std::stringstream icemsg;
139 icemsg << iceAttributes.ufrag << "\n";
140 icemsg << iceAttributes.pwd << "\n";
141 for (const auto& addr : ice_master->getLocalCandidates(1)) {
142 icemsg << addr << "\n";
143 fmt::print("Added local ICE candidate {}\n", addr);
144 }
145 init = icemsg.str();
146 cv_init.notify_one();
147 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
148 return !response.empty();
149 }));
150 auto sdp = ice_master->parseIceCandidates(response);
151 CPPUNIT_ASSERT(
152 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
153 });
154 };
155 ice_config.onNegoDone = [&](bool ok) {
156 iceMasterReady = ok;
157 cv.notify_one();
158 };
159 ice_config.master = true;
160 ice_config.streamsCount = 1;
161 ice_config.compCountPerStream = 1;
162 ice_config.upnpContext = upnpContext;
163 ice_config.factory = factory;
164
165 ice_master = factory->createTransport("master ICE");
166 ice_master->initIceInstance(ice_config);
167 cv_create.notify_all();
168 ice_config.onInitDone = [&](bool ok) {
169 CPPUNIT_ASSERT(ok);
170 dht::ThreadPool::io().run([&] {
171 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
172 return ice_slave != nullptr;
173 }));
174 auto iceAttributes = ice_slave->getLocalAttributes();
175 std::stringstream icemsg;
176 icemsg << iceAttributes.ufrag << "\n";
177 icemsg << iceAttributes.pwd << "\n";
178 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
179 icemsg << addr << "\n";
180 fmt::print("Added local ICE candidate {}\n", addr);
181 }
182 response = icemsg.str();
183 cv_resp.notify_one();
184 CPPUNIT_ASSERT(
185 cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
186 auto sdp = ice_slave->parseIceCandidates(init);
187 CPPUNIT_ASSERT(
188 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
189 });
190 };
191 ice_config.onNegoDone = [&](bool ok) {
192 iceSlaveReady = ok;
193 cv.notify_one();
194 };
195 ice_config.master = false;
196 ice_config.streamsCount = 1;
197 ice_config.compCountPerStream = 1;
198 ice_config.upnpContext = upnpContext;
199 ice_config.factory = factory;
200
201 ice_slave = factory->createTransport("slave ICE");
202 ice_slave->initIceInstance(ice_config);
203
204 cv_create.notify_all();
205 CPPUNIT_ASSERT(
206 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
207}
208
209void
210IceTest::testTurnMasterIceConnection()
211{
212 const auto& addr4 = dht_->getPublicAddress(AF_INET);
213 CPPUNIT_ASSERT(addr4.size() != 0);
214 CPPUNIT_ASSERT(turnV4_);
215 dhtnet::IceTransportOptions ice_config;
216 ice_config.upnpEnable = true;
217 ice_config.tcpEnable = true;
218 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
219 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
220 std::condition_variable cv, cv_create, cv_resp, cv_init;
221 std::string init = {};
222 std::string response = {};
223 bool iceMasterReady = false, iceSlaveReady = false;
224
225 // Master
226 ice_config.onInitDone = [&](bool ok) {
227 CPPUNIT_ASSERT(ok);
228 dht::ThreadPool::io().run([&] {
229 /*{
230 std::unique_lock<std::mutex> lk_create {mtx_create};
231 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
232 return ice_master != nullptr;
233 }));
234 }*/
235 auto iceAttributes = ice_master->getLocalAttributes();
236 std::stringstream icemsg;
237 icemsg << iceAttributes.ufrag << "\n";
238 icemsg << iceAttributes.pwd << "\n";
239
240 for (const auto& addr : ice_master->getLocalCandidates(1)) {
241
242 if (addr.find("host") == std::string::npos) {
243 // We only want to add relayed + public ip
244 icemsg << addr << "\n";
245 fmt::print("Added local ICE candidate {}\n", addr);
246 } else {
247 // Replace host by non existing IP (we still need host to not fail the start)
248 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
249 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
250 if (newaddr != addr)
251 icemsg << newaddr << "\n";
252 }
253 }
254 {
255 std::lock_guard lk {mtx_init};
256 init = icemsg.str();
257 cv_init.notify_one();
258 }
259 {
260 std::unique_lock<std::mutex> lk_resp {mtx_resp};
261 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
262 return !response.empty();
263 }));
264 auto sdp = ice_master->parseIceCandidates(response);
265 CPPUNIT_ASSERT(
266 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
267 }
268 });
269 };
270 ice_config.onNegoDone = [&](bool ok) {
271 std::lock_guard lk {mtx};
272 iceMasterReady = ok;
273 cv.notify_one();
274 };
275 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
276 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
277 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
278 .setUri(turnV4_->toString(true))
279 .setUsername("ring")
280 .setPassword("ring")
281 .setRealm("ring"));
282 ice_config.master = true;
283 ice_config.streamsCount = 1;
284 ice_config.compCountPerStream = 1;
285 ice_config.upnpContext = upnpContext;
286 ice_config.factory = factory;
287 {
288 std::unique_lock<std::mutex> lk_create {mtx_create};
289 ice_master = factory->createTransport("master ICE");
290 ice_master->initIceInstance(ice_config);
291 cv_create.notify_all();
292 }
293
294 // Slave
295 ice_config.turnServers = {};
296 ice_config.onInitDone = [&](bool ok) {
297 CPPUNIT_ASSERT(ok);
298 dht::ThreadPool::io().run([&] {
299 /*std::unique_lock<std::mutex> lk_create {mtx_create};
300 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
301 return ice_slave != nullptr;
302 }));*/
303 auto iceAttributes = ice_slave->getLocalAttributes();
304 std::stringstream icemsg;
305 icemsg << iceAttributes.ufrag << "\n";
306 icemsg << iceAttributes.pwd << "\n";
307 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
308 if (addr.find("host") == std::string::npos) {
309 // We only want to add relayed + public ip
310 icemsg << addr << "\n";
311 fmt::print("Added local ICE candidate {}\n", addr);
312 } else {
313 // Replace host by non existing IP (we still need host to not fail the start)
314 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
315 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
316 if (newaddr != addr)
317 icemsg << newaddr << "\n";
318 }
319 }
320 {
321 std::lock_guard lk {mtx_resp};
322 response = icemsg.str();
323 cv_resp.notify_one();
324 }
325 {
326 std::unique_lock lk {mtx_init};
327 CPPUNIT_ASSERT(
328 cv_init.wait_for(lk, std::chrono::seconds(10), [&] { return !init.empty(); }));
329 auto sdp = ice_slave->parseIceCandidates(init);
330 CPPUNIT_ASSERT(
331 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
332 }
333 });
334 };
335 ice_config.onNegoDone = [&](bool ok) {
336 std::lock_guard lk {mtx};
337 iceSlaveReady = ok;
338 cv.notify_one();
339 };
340 ice_config.master = false;
341 ice_config.streamsCount = 1;
342 ice_config.compCountPerStream = 1;
343 ice_config.upnpContext = upnpContext;
344 ice_config.factory = factory;
345 {
346 std::unique_lock<std::mutex> lk_create {mtx_create};
347 ice_slave = factory->createTransport("slave ICE");
348 ice_slave->initIceInstance(ice_config);
349 cv_create.notify_all();
350 }
351 std::unique_lock lk {mtx};
352 CPPUNIT_ASSERT(
353 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady; }));
354 CPPUNIT_ASSERT(
355 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceSlaveReady; }));
356
357 CPPUNIT_ASSERT(ice_master->getLocalAddress(1).toString(false) == turnV4_->toString(false));
358}
359
360void
361IceTest::testTurnSlaveIceConnection()
362{
363 const auto& addr4 = dht_->getPublicAddress(AF_INET);
364 CPPUNIT_ASSERT(addr4.size() != 0);
365 CPPUNIT_ASSERT(turnV4_);
366 dhtnet::IceTransportOptions ice_config;
367 ice_config.upnpEnable = true;
368 ice_config.tcpEnable = true;
369 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
370 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
371 std::unique_lock<std::mutex> lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
372 lk_init {mtx_init};
373 std::condition_variable cv, cv_create, cv_resp, cv_init;
374 std::string init = {};
375 std::string response = {};
376 bool iceMasterReady = false, iceSlaveReady = false;
377 ice_config.onInitDone = [&](bool ok) {
378 CPPUNIT_ASSERT(ok);
379 dht::ThreadPool::io().run([&] {
380 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
381 return ice_master != nullptr;
382 }));
383 auto iceAttributes = ice_master->getLocalAttributes();
384 std::stringstream icemsg;
385 icemsg << iceAttributes.ufrag << "\n";
386 icemsg << iceAttributes.pwd << "\n";
387 for (const auto& addr : ice_master->getLocalCandidates(1)) {
388 if (addr.find("host") == std::string::npos) {
389 // We only want to add relayed + public ip
390 icemsg << addr << "\n";
391 fmt::print("Added local ICE candidate {}\n", addr);
392 } else {
393 // Replace host by non existing IP (we still need host to not fail the start)
394 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
395 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
396 if (newaddr != addr)
397 icemsg << newaddr << "\n";
398 }
399 }
400 init = icemsg.str();
401 cv_init.notify_one();
402 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
403 return !response.empty();
404 }));
405 auto sdp = ice_master->parseIceCandidates(response);
406 CPPUNIT_ASSERT(
407 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
408 });
409 };
410 ice_config.onNegoDone = [&](bool ok) {
411 iceMasterReady = ok;
412 cv.notify_one();
413 };
414 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
415 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
416 ice_config.master = true;
417 ice_config.streamsCount = 1;
418 ice_config.compCountPerStream = 1;
419 ice_config.upnpContext = upnpContext;
420 ice_config.factory = factory;
421 ice_master = factory->createTransport("master ICE");
422 ice_master->initIceInstance(ice_config);
423 cv_create.notify_all();
424 ice_config.onInitDone = [&](bool ok) {
425 CPPUNIT_ASSERT(ok);
426 dht::ThreadPool::io().run([&] {
427 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
428 return ice_slave != nullptr;
429 }));
430 auto iceAttributes = ice_slave->getLocalAttributes();
431 std::stringstream icemsg;
432 icemsg << iceAttributes.ufrag << "\n";
433 icemsg << iceAttributes.pwd << "\n";
434 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
435 if (addr.find("host") == std::string::npos) {
436 // We only want to add relayed + public ip
437 icemsg << addr << "\n";
438 fmt::print("Added local ICE candidate {}\n", addr);
439 } else {
440 // Replace host by non existing IP (we still need host to not fail the start)
441 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
442 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
443 if (newaddr != addr)
444 icemsg << newaddr << "\n";
445 }
446 }
447 response = icemsg.str();
448 cv_resp.notify_one();
449 CPPUNIT_ASSERT(
450 cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
451 auto sdp = ice_slave->parseIceCandidates(init);
452 CPPUNIT_ASSERT(
453 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
454 });
455 };
456 ice_config.onNegoDone = [&](bool ok) {
457 iceSlaveReady = ok;
458 cv.notify_one();
459 };
460 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
461 .setUri(turnV4_->toString(true))
462 .setUsername("ring")
463 .setPassword("ring")
464 .setRealm("ring"));
465 ice_config.master = false;
466 ice_config.streamsCount = 1;
467 ice_config.compCountPerStream = 1;
468 ice_config.upnpContext = upnpContext;
469 ice_config.factory = factory;
470 ice_slave = factory->createTransport("slave ICE");
471 ice_slave->initIceInstance(ice_config);
472 cv_create.notify_all();
473 CPPUNIT_ASSERT(
474 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
475 CPPUNIT_ASSERT(ice_slave->getLocalAddress(1).toString(false) == turnV4_->toString(false));
476}
477
478void
479IceTest::testReceiveTooManyCandidates()
480{
481 const auto& addr4 = dht_->getPublicAddress(AF_INET);
482 CPPUNIT_ASSERT(addr4.size() != 0);
483 CPPUNIT_ASSERT(turnV4_);
484 dhtnet::IceTransportOptions ice_config;
485 ice_config.upnpEnable = true;
486 ice_config.tcpEnable = true;
487 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
488 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
489 std::condition_variable cv, cv_create, cv_resp, cv_init;
490 std::string init = {};
491 std::string response = {};
492 bool iceMasterReady = false, iceSlaveReady = false;
493 ice_config.onInitDone = [&](bool ok) {
494 CPPUNIT_ASSERT(ok);
495 dht::ThreadPool::io().run([&] {
496 {
497 std::unique_lock<std::mutex> lk_create {mtx_create};
498 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
499 return ice_master != nullptr;
500 }));
501 }
502 auto iceAttributes = ice_master->getLocalAttributes();
503 std::stringstream icemsg;
504 icemsg << iceAttributes.ufrag << "\n";
505 icemsg << iceAttributes.pwd << "\n";
506 for (const auto& addr : ice_master->getLocalCandidates(1)) {
507 icemsg << addr << "\n";
508 fmt::print("Added local ICE candidate {}\n", addr);
509 }
510 init = icemsg.str();
511 cv_init.notify_one();
512 {
513 std::unique_lock<std::mutex> lk_resp {mtx_resp};
514 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
515 return !response.empty();
516 }));
517 auto sdp = ice_master->parseIceCandidates(response);
518 CPPUNIT_ASSERT(
519 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
520 }
521 });
522 };
523 ice_config.onNegoDone = [&](bool ok) {
524 iceMasterReady = ok;
525 cv.notify_one();
526 };
527 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
528 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
529 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
530 .setUri(turnV4_->toString(true))
531 .setUsername("ring")
532 .setPassword("ring")
533 .setRealm("ring"));
534 ice_config.master = true;
535 ice_config.streamsCount = 1;
536 ice_config.compCountPerStream = 1;
537 ice_config.upnpContext = upnpContext;
538 ice_config.factory = factory;
539
540 ice_master = factory->createTransport("master ICE");
541 ice_master->initIceInstance(ice_config);
542 cv_create.notify_all();
543 ice_config.onInitDone = [&](bool ok) {
544 CPPUNIT_ASSERT(ok);
545 dht::ThreadPool::io().run([&] {
546 {
547 std::unique_lock<std::mutex> lk_create {mtx_create};
548 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
549 return ice_slave != nullptr;
550 }));
551 }
552 auto iceAttributes = ice_slave->getLocalAttributes();
553 std::stringstream icemsg;
554 icemsg << iceAttributes.ufrag << "\n";
555 icemsg << iceAttributes.pwd << "\n";
556 for (const auto& addr : ice_master->getLocalCandidates(1)) {
557 icemsg << addr << "\n";
558 fmt::print("Added local ICE candidate {}\n", addr);
559 }
560 for (auto i = 0; i < std::min(256, PJ_ICE_ST_MAX_CAND); ++i) {
561 icemsg << "Hc0a800a5 1 TCP 2130706431 192.168.0." << i
562 << " 43613 typ host tcptype passive"
563 << "\n";
564 icemsg << "Hc0a800a5 1 TCP 2130706431 192.168.0." << i
565 << " 9 typ host tcptype active"
566 << "\n";
567 }
568 {
569 std::lock_guard<std::mutex> lk_resp {mtx_resp};
570 response = icemsg.str();
571 cv_resp.notify_one();
572 }
573 std::unique_lock<std::mutex> lk_init {mtx_init};
574 CPPUNIT_ASSERT(
575 cv_init.wait_for(lk_init, std::chrono::seconds(10), [&] { return !init.empty(); }));
576 auto sdp = ice_slave->parseIceCandidates(init);
577 CPPUNIT_ASSERT(
578 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
579 });
580 };
581 ice_config.onNegoDone = [&](bool ok) {
582 iceSlaveReady = ok;
583 cv.notify_one();
584 };
585 ice_config.master = false;
586 ice_config.streamsCount = 1;
587 ice_config.compCountPerStream = 1;
588 ice_config.upnpContext = upnpContext;
589 ice_config.factory = factory;
590
591 ice_slave = factory->createTransport("slave ICE");
592 ice_slave->initIceInstance(ice_config);
593 cv_create.notify_all();
594
595 std::unique_lock<std::mutex> lk {mtx};
596 CPPUNIT_ASSERT(
597 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
598}
599
600void
601IceTest::testCompleteOnFailure()
602{
603 const auto& addr4 = dht_->getPublicAddress(AF_INET);
604 CPPUNIT_ASSERT(addr4.size() != 0);
605 CPPUNIT_ASSERT(turnV4_);
606 dhtnet::IceTransportOptions ice_config;
607 ice_config.upnpEnable = true;
608 ice_config.tcpEnable = true;
609 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
610 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
611 std::unique_lock<std::mutex> lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
612 lk_init {mtx_init};
613 std::condition_variable cv, cv_create, cv_resp, cv_init;
614 std::string init = {};
615 std::string response = {};
616 bool iceMasterNotReady = false, iceSlaveNotReady = false;
617 ice_config.onInitDone = [&](bool ok) {
618 CPPUNIT_ASSERT(ok);
619 dht::ThreadPool::io().run([&] {
620 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
621 return ice_master != nullptr;
622 }));
623 auto iceAttributes = ice_master->getLocalAttributes();
624 std::stringstream icemsg;
625 icemsg << iceAttributes.ufrag << "\n";
626 icemsg << iceAttributes.pwd << "\n";
627 for (const auto& addr : ice_master->getLocalCandidates(1)) {
628 if (addr.find("relay") != std::string::npos) {
629 // We only want to relayed and modify the rest (to have CONNREFUSED)
630 icemsg << addr << "\n";
631 fmt::print("Added local ICE candidate {}\n", addr);
632 } else {
633 // Replace host by non existing IP (we still need host to not fail the start)
634 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
635 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
636 if (newaddr != addr)
637 icemsg << newaddr << "\n";
638 }
639 }
640 init = icemsg.str();
641 cv_init.notify_one();
642 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
643 return !response.empty();
644 }));
645 auto sdp = ice_master->parseIceCandidates(response);
646 CPPUNIT_ASSERT(
647 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
648 });
649 };
650 ice_config.onNegoDone = [&](bool ok) {
651 iceMasterNotReady = !ok;
652 cv.notify_one();
653 };
654 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
655 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
656 ice_config.master = true;
657 ice_config.streamsCount = 1;
658 ice_config.compCountPerStream = 1;
659 ice_config.upnpContext = upnpContext;
660 ice_config.factory = factory;
661 ice_master = factory->createTransport("master ICE");
662 ice_master->initIceInstance(ice_config);
663 cv_create.notify_all();
664 ice_config.onInitDone = [&](bool ok) {
665 CPPUNIT_ASSERT(ok);
666 dht::ThreadPool::io().run([&] {
667 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
668 return ice_slave != nullptr;
669 }));
670 auto iceAttributes = ice_slave->getLocalAttributes();
671 std::stringstream icemsg;
672 icemsg << iceAttributes.ufrag << "\n";
673 icemsg << iceAttributes.pwd << "\n";
674 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
675 if (addr.find("relay") != std::string::npos) {
676 // We only want to relayed and modify the rest (to have CONNREFUSED)
677 icemsg << addr << "\n";
678 fmt::print("Added local ICE candidate {}\n", addr);
679 } else {
680 // Replace host by non existing IP (we still need host to not fail the start)
681 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
682 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
683 if (newaddr != addr)
684 icemsg << newaddr << "\n";
685 }
686 }
687 response = icemsg.str();
688 cv_resp.notify_one();
689 CPPUNIT_ASSERT(
690 cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
691 auto sdp = ice_slave->parseIceCandidates(init);
692 CPPUNIT_ASSERT(
693 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
694 });
695 };
696 ice_config.onNegoDone = [&](bool ok) {
697 iceSlaveNotReady = !ok;
698 cv.notify_one();
699 };
700 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
701 .setUri(turnV4_->toString(true))
702 .setUsername("ring")
703 .setPassword("ring")
704 .setRealm("ring"));
705 ice_config.master = false;
706 ice_config.streamsCount = 1;
707 ice_config.compCountPerStream = 1;
708 ice_config.upnpContext = upnpContext;
709 ice_config.factory = factory;
710 ice_slave = factory->createTransport("slave ICE");
711 ice_slave->initIceInstance(ice_config);
712 cv_create.notify_all();
713 // Check that nego failed and callback called
714 CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(120), [&] {
715 return iceMasterNotReady && iceSlaveNotReady;
716 }));
717}
718
719} // namespace test
720}
721
722JAMI_TEST_RUNNER(dhtnet::test::IceTest::name())