blob: 32928af1e437188c6aa4a07702128b3bf0f2d645 [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>();
François-Simon Fauteux-Chapleau0f825472024-03-27 16:19:37 -040095 ioContextRunner = std::make_shared<std::thread>([&] {
96 auto work = asio::make_work_guard(*ioContext);
97 ioContext->run();
98 });
Amna0b50a032024-02-13 14:42:26 -050099 }
100 upnpContext = std::make_shared<dhtnet::upnp::UPnPContext>(ioContext, nullptr);
101 }
102 if (!factory) {
103 factory = std::make_shared<IceTransportFactory>();
104 }
105}
106
107
108void
109IceTest::tearDown()
110{
111 upnpContext->shutdown();
112 ioContext->stop();
113 if (ioContextRunner && ioContextRunner->joinable()) {
114 ioContextRunner->join();
115 }
116 dht_.reset();
117 turnV4_.reset();
118}
119
120void
121IceTest::testRawIceConnection()
122{
123 dhtnet::IceTransportOptions ice_config;
124 ice_config.upnpEnable = true;
125 ice_config.tcpEnable = true;
126 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
127 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
Adrien Béraud024c46f2024-03-02 23:53:18 -0500128 std::unique_lock lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
Amna0b50a032024-02-13 14:42:26 -0500129 lk_init {mtx_init};
130 std::condition_variable cv, cv_create, cv_resp, cv_init;
131 std::string init = {};
132 std::string response = {};
133 bool iceMasterReady = false, iceSlaveReady = false;
134 ice_config.onInitDone = [&](bool ok) {
135 CPPUNIT_ASSERT(ok);
136 dht::ThreadPool::io().run([&] {
137 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
138 return ice_master != nullptr;
139 }));
140 auto iceAttributes = ice_master->getLocalAttributes();
141 std::stringstream icemsg;
142 icemsg << iceAttributes.ufrag << "\n";
143 icemsg << iceAttributes.pwd << "\n";
144 for (const auto& addr : ice_master->getLocalCandidates(1)) {
145 icemsg << addr << "\n";
146 fmt::print("Added local ICE candidate {}\n", addr);
147 }
148 init = icemsg.str();
149 cv_init.notify_one();
150 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
151 return !response.empty();
152 }));
153 auto sdp = ice_master->parseIceCandidates(response);
154 CPPUNIT_ASSERT(
155 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
156 });
157 };
158 ice_config.onNegoDone = [&](bool ok) {
159 iceMasterReady = ok;
160 cv.notify_one();
161 };
162 ice_config.master = true;
163 ice_config.streamsCount = 1;
164 ice_config.compCountPerStream = 1;
165 ice_config.upnpContext = upnpContext;
166 ice_config.factory = factory;
167
168 ice_master = factory->createTransport("master ICE");
169 ice_master->initIceInstance(ice_config);
170 cv_create.notify_all();
171 ice_config.onInitDone = [&](bool ok) {
172 CPPUNIT_ASSERT(ok);
173 dht::ThreadPool::io().run([&] {
174 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
175 return ice_slave != nullptr;
176 }));
177 auto iceAttributes = ice_slave->getLocalAttributes();
178 std::stringstream icemsg;
179 icemsg << iceAttributes.ufrag << "\n";
180 icemsg << iceAttributes.pwd << "\n";
181 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
182 icemsg << addr << "\n";
183 fmt::print("Added local ICE candidate {}\n", addr);
184 }
185 response = icemsg.str();
186 cv_resp.notify_one();
187 CPPUNIT_ASSERT(
188 cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
189 auto sdp = ice_slave->parseIceCandidates(init);
190 CPPUNIT_ASSERT(
191 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
192 });
193 };
194 ice_config.onNegoDone = [&](bool ok) {
195 iceSlaveReady = ok;
196 cv.notify_one();
197 };
198 ice_config.master = false;
199 ice_config.streamsCount = 1;
200 ice_config.compCountPerStream = 1;
201 ice_config.upnpContext = upnpContext;
202 ice_config.factory = factory;
203
204 ice_slave = factory->createTransport("slave ICE");
205 ice_slave->initIceInstance(ice_config);
206
207 cv_create.notify_all();
208 CPPUNIT_ASSERT(
209 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
210}
211
212void
213IceTest::testTurnMasterIceConnection()
214{
215 const auto& addr4 = dht_->getPublicAddress(AF_INET);
216 CPPUNIT_ASSERT(addr4.size() != 0);
217 CPPUNIT_ASSERT(turnV4_);
218 dhtnet::IceTransportOptions ice_config;
219 ice_config.upnpEnable = true;
220 ice_config.tcpEnable = true;
221 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
222 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
223 std::condition_variable cv, cv_create, cv_resp, cv_init;
224 std::string init = {};
225 std::string response = {};
226 bool iceMasterReady = false, iceSlaveReady = false;
227
228 // Master
229 ice_config.onInitDone = [&](bool ok) {
230 CPPUNIT_ASSERT(ok);
231 dht::ThreadPool::io().run([&] {
232 /*{
Adrien Béraud024c46f2024-03-02 23:53:18 -0500233 std::unique_lock lk_create {mtx_create};
Amna0b50a032024-02-13 14:42:26 -0500234 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
235 return ice_master != nullptr;
236 }));
237 }*/
238 auto iceAttributes = ice_master->getLocalAttributes();
239 std::stringstream icemsg;
240 icemsg << iceAttributes.ufrag << "\n";
241 icemsg << iceAttributes.pwd << "\n";
242
243 for (const auto& addr : ice_master->getLocalCandidates(1)) {
244
245 if (addr.find("host") == std::string::npos) {
246 // We only want to add relayed + public ip
247 icemsg << addr << "\n";
248 fmt::print("Added local ICE candidate {}\n", addr);
249 } else {
250 // Replace host by non existing IP (we still need host to not fail the start)
251 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
252 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
253 if (newaddr != addr)
254 icemsg << newaddr << "\n";
255 }
256 }
257 {
258 std::lock_guard lk {mtx_init};
259 init = icemsg.str();
260 cv_init.notify_one();
261 }
262 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500263 std::unique_lock lk_resp {mtx_resp};
Amna0b50a032024-02-13 14:42:26 -0500264 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
265 return !response.empty();
266 }));
267 auto sdp = ice_master->parseIceCandidates(response);
268 CPPUNIT_ASSERT(
269 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
270 }
271 });
272 };
273 ice_config.onNegoDone = [&](bool ok) {
274 std::lock_guard lk {mtx};
275 iceMasterReady = ok;
276 cv.notify_one();
277 };
278 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
279 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
280 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
281 .setUri(turnV4_->toString(true))
282 .setUsername("ring")
283 .setPassword("ring")
284 .setRealm("ring"));
285 ice_config.master = true;
286 ice_config.streamsCount = 1;
287 ice_config.compCountPerStream = 1;
288 ice_config.upnpContext = upnpContext;
289 ice_config.factory = factory;
290 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500291 std::unique_lock lk_create {mtx_create};
Amna0b50a032024-02-13 14:42:26 -0500292 ice_master = factory->createTransport("master ICE");
293 ice_master->initIceInstance(ice_config);
294 cv_create.notify_all();
295 }
296
297 // Slave
298 ice_config.turnServers = {};
299 ice_config.onInitDone = [&](bool ok) {
300 CPPUNIT_ASSERT(ok);
301 dht::ThreadPool::io().run([&] {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500302 /*std::unique_lock lk_create {mtx_create};
Amna0b50a032024-02-13 14:42:26 -0500303 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
304 return ice_slave != nullptr;
305 }));*/
306 auto iceAttributes = ice_slave->getLocalAttributes();
307 std::stringstream icemsg;
308 icemsg << iceAttributes.ufrag << "\n";
309 icemsg << iceAttributes.pwd << "\n";
310 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
311 if (addr.find("host") == std::string::npos) {
312 // We only want to add relayed + public ip
313 icemsg << addr << "\n";
314 fmt::print("Added local ICE candidate {}\n", addr);
315 } else {
316 // Replace host by non existing IP (we still need host to not fail the start)
317 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
318 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
319 if (newaddr != addr)
320 icemsg << newaddr << "\n";
321 }
322 }
323 {
324 std::lock_guard lk {mtx_resp};
325 response = icemsg.str();
326 cv_resp.notify_one();
327 }
328 {
329 std::unique_lock lk {mtx_init};
330 CPPUNIT_ASSERT(
331 cv_init.wait_for(lk, std::chrono::seconds(10), [&] { return !init.empty(); }));
332 auto sdp = ice_slave->parseIceCandidates(init);
333 CPPUNIT_ASSERT(
334 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
335 }
336 });
337 };
338 ice_config.onNegoDone = [&](bool ok) {
339 std::lock_guard lk {mtx};
340 iceSlaveReady = ok;
341 cv.notify_one();
342 };
343 ice_config.master = false;
344 ice_config.streamsCount = 1;
345 ice_config.compCountPerStream = 1;
346 ice_config.upnpContext = upnpContext;
347 ice_config.factory = factory;
348 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500349 std::unique_lock lk_create {mtx_create};
Amna0b50a032024-02-13 14:42:26 -0500350 ice_slave = factory->createTransport("slave ICE");
351 ice_slave->initIceInstance(ice_config);
352 cv_create.notify_all();
353 }
354 std::unique_lock lk {mtx};
355 CPPUNIT_ASSERT(
356 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady; }));
357 CPPUNIT_ASSERT(
358 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceSlaveReady; }));
359
360 CPPUNIT_ASSERT(ice_master->getLocalAddress(1).toString(false) == turnV4_->toString(false));
361}
362
363void
364IceTest::testTurnSlaveIceConnection()
365{
366 const auto& addr4 = dht_->getPublicAddress(AF_INET);
367 CPPUNIT_ASSERT(addr4.size() != 0);
368 CPPUNIT_ASSERT(turnV4_);
369 dhtnet::IceTransportOptions ice_config;
370 ice_config.upnpEnable = true;
371 ice_config.tcpEnable = true;
372 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
373 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
Adrien Béraud024c46f2024-03-02 23:53:18 -0500374 std::unique_lock lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
Amna0b50a032024-02-13 14:42:26 -0500375 lk_init {mtx_init};
376 std::condition_variable cv, cv_create, cv_resp, cv_init;
377 std::string init = {};
378 std::string response = {};
379 bool iceMasterReady = false, iceSlaveReady = false;
380 ice_config.onInitDone = [&](bool ok) {
381 CPPUNIT_ASSERT(ok);
382 dht::ThreadPool::io().run([&] {
383 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
384 return ice_master != nullptr;
385 }));
386 auto iceAttributes = ice_master->getLocalAttributes();
387 std::stringstream icemsg;
388 icemsg << iceAttributes.ufrag << "\n";
389 icemsg << iceAttributes.pwd << "\n";
390 for (const auto& addr : ice_master->getLocalCandidates(1)) {
391 if (addr.find("host") == std::string::npos) {
392 // We only want to add relayed + public ip
393 icemsg << addr << "\n";
394 fmt::print("Added local ICE candidate {}\n", addr);
395 } else {
396 // Replace host by non existing IP (we still need host to not fail the start)
397 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
398 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
399 if (newaddr != addr)
400 icemsg << newaddr << "\n";
401 }
402 }
403 init = icemsg.str();
404 cv_init.notify_one();
405 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
406 return !response.empty();
407 }));
408 auto sdp = ice_master->parseIceCandidates(response);
409 CPPUNIT_ASSERT(
410 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
411 });
412 };
413 ice_config.onNegoDone = [&](bool ok) {
414 iceMasterReady = ok;
415 cv.notify_one();
416 };
417 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
418 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
419 ice_config.master = true;
420 ice_config.streamsCount = 1;
421 ice_config.compCountPerStream = 1;
422 ice_config.upnpContext = upnpContext;
423 ice_config.factory = factory;
424 ice_master = factory->createTransport("master ICE");
425 ice_master->initIceInstance(ice_config);
426 cv_create.notify_all();
427 ice_config.onInitDone = [&](bool ok) {
428 CPPUNIT_ASSERT(ok);
429 dht::ThreadPool::io().run([&] {
430 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
431 return ice_slave != nullptr;
432 }));
433 auto iceAttributes = ice_slave->getLocalAttributes();
434 std::stringstream icemsg;
435 icemsg << iceAttributes.ufrag << "\n";
436 icemsg << iceAttributes.pwd << "\n";
437 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
438 if (addr.find("host") == std::string::npos) {
439 // We only want to add relayed + public ip
440 icemsg << addr << "\n";
441 fmt::print("Added local ICE candidate {}\n", addr);
442 } else {
443 // Replace host by non existing IP (we still need host to not fail the start)
444 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
445 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
446 if (newaddr != addr)
447 icemsg << newaddr << "\n";
448 }
449 }
450 response = icemsg.str();
451 cv_resp.notify_one();
452 CPPUNIT_ASSERT(
453 cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
454 auto sdp = ice_slave->parseIceCandidates(init);
455 CPPUNIT_ASSERT(
456 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
457 });
458 };
459 ice_config.onNegoDone = [&](bool ok) {
460 iceSlaveReady = ok;
461 cv.notify_one();
462 };
463 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
464 .setUri(turnV4_->toString(true))
465 .setUsername("ring")
466 .setPassword("ring")
467 .setRealm("ring"));
468 ice_config.master = false;
469 ice_config.streamsCount = 1;
470 ice_config.compCountPerStream = 1;
471 ice_config.upnpContext = upnpContext;
472 ice_config.factory = factory;
473 ice_slave = factory->createTransport("slave ICE");
474 ice_slave->initIceInstance(ice_config);
475 cv_create.notify_all();
476 CPPUNIT_ASSERT(
477 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
478 CPPUNIT_ASSERT(ice_slave->getLocalAddress(1).toString(false) == turnV4_->toString(false));
479}
480
481void
482IceTest::testReceiveTooManyCandidates()
483{
484 const auto& addr4 = dht_->getPublicAddress(AF_INET);
485 CPPUNIT_ASSERT(addr4.size() != 0);
486 CPPUNIT_ASSERT(turnV4_);
487 dhtnet::IceTransportOptions ice_config;
488 ice_config.upnpEnable = true;
489 ice_config.tcpEnable = true;
490 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
491 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
492 std::condition_variable cv, cv_create, cv_resp, cv_init;
493 std::string init = {};
494 std::string response = {};
495 bool iceMasterReady = false, iceSlaveReady = false;
496 ice_config.onInitDone = [&](bool ok) {
497 CPPUNIT_ASSERT(ok);
498 dht::ThreadPool::io().run([&] {
499 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500500 std::unique_lock lk_create {mtx_create};
Amna0b50a032024-02-13 14:42:26 -0500501 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
502 return ice_master != nullptr;
503 }));
504 }
505 auto iceAttributes = ice_master->getLocalAttributes();
506 std::stringstream icemsg;
507 icemsg << iceAttributes.ufrag << "\n";
508 icemsg << iceAttributes.pwd << "\n";
509 for (const auto& addr : ice_master->getLocalCandidates(1)) {
510 icemsg << addr << "\n";
511 fmt::print("Added local ICE candidate {}\n", addr);
512 }
513 init = icemsg.str();
514 cv_init.notify_one();
515 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500516 std::unique_lock lk_resp {mtx_resp};
Amna0b50a032024-02-13 14:42:26 -0500517 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
518 return !response.empty();
519 }));
520 auto sdp = ice_master->parseIceCandidates(response);
521 CPPUNIT_ASSERT(
522 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
523 }
524 });
525 };
526 ice_config.onNegoDone = [&](bool ok) {
527 iceMasterReady = ok;
528 cv.notify_one();
529 };
530 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
531 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
532 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
533 .setUri(turnV4_->toString(true))
534 .setUsername("ring")
535 .setPassword("ring")
536 .setRealm("ring"));
537 ice_config.master = true;
538 ice_config.streamsCount = 1;
539 ice_config.compCountPerStream = 1;
540 ice_config.upnpContext = upnpContext;
541 ice_config.factory = factory;
542
543 ice_master = factory->createTransport("master ICE");
544 ice_master->initIceInstance(ice_config);
545 cv_create.notify_all();
546 ice_config.onInitDone = [&](bool ok) {
547 CPPUNIT_ASSERT(ok);
548 dht::ThreadPool::io().run([&] {
549 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500550 std::unique_lock lk_create {mtx_create};
Amna0b50a032024-02-13 14:42:26 -0500551 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
552 return ice_slave != nullptr;
553 }));
554 }
555 auto iceAttributes = ice_slave->getLocalAttributes();
556 std::stringstream icemsg;
557 icemsg << iceAttributes.ufrag << "\n";
558 icemsg << iceAttributes.pwd << "\n";
559 for (const auto& addr : ice_master->getLocalCandidates(1)) {
560 icemsg << addr << "\n";
561 fmt::print("Added local ICE candidate {}\n", addr);
562 }
563 for (auto i = 0; i < std::min(256, PJ_ICE_ST_MAX_CAND); ++i) {
564 icemsg << "Hc0a800a5 1 TCP 2130706431 192.168.0." << i
565 << " 43613 typ host tcptype passive"
566 << "\n";
567 icemsg << "Hc0a800a5 1 TCP 2130706431 192.168.0." << i
568 << " 9 typ host tcptype active"
569 << "\n";
570 }
571 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500572 std::lock_guard lk_resp {mtx_resp};
Amna0b50a032024-02-13 14:42:26 -0500573 response = icemsg.str();
574 cv_resp.notify_one();
575 }
Adrien Béraud024c46f2024-03-02 23:53:18 -0500576 std::unique_lock lk_init {mtx_init};
Amna0b50a032024-02-13 14:42:26 -0500577 CPPUNIT_ASSERT(
578 cv_init.wait_for(lk_init, std::chrono::seconds(10), [&] { return !init.empty(); }));
579 auto sdp = ice_slave->parseIceCandidates(init);
580 CPPUNIT_ASSERT(
581 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
582 });
583 };
584 ice_config.onNegoDone = [&](bool ok) {
585 iceSlaveReady = ok;
586 cv.notify_one();
587 };
588 ice_config.master = false;
589 ice_config.streamsCount = 1;
590 ice_config.compCountPerStream = 1;
591 ice_config.upnpContext = upnpContext;
592 ice_config.factory = factory;
593
594 ice_slave = factory->createTransport("slave ICE");
595 ice_slave->initIceInstance(ice_config);
596 cv_create.notify_all();
597
Adrien Béraud024c46f2024-03-02 23:53:18 -0500598 std::unique_lock lk {mtx};
Amna0b50a032024-02-13 14:42:26 -0500599 CPPUNIT_ASSERT(
600 cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
601}
602
603void
604IceTest::testCompleteOnFailure()
605{
606 const auto& addr4 = dht_->getPublicAddress(AF_INET);
607 CPPUNIT_ASSERT(addr4.size() != 0);
608 CPPUNIT_ASSERT(turnV4_);
609 dhtnet::IceTransportOptions ice_config;
610 ice_config.upnpEnable = true;
611 ice_config.tcpEnable = true;
612 std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
613 std::mutex mtx, mtx_create, mtx_resp, mtx_init;
Adrien Béraud024c46f2024-03-02 23:53:18 -0500614 std::unique_lock lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
Amna0b50a032024-02-13 14:42:26 -0500615 lk_init {mtx_init};
616 std::condition_variable cv, cv_create, cv_resp, cv_init;
617 std::string init = {};
618 std::string response = {};
619 bool iceMasterNotReady = false, iceSlaveNotReady = false;
620 ice_config.onInitDone = [&](bool ok) {
621 CPPUNIT_ASSERT(ok);
622 dht::ThreadPool::io().run([&] {
623 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
624 return ice_master != nullptr;
625 }));
626 auto iceAttributes = ice_master->getLocalAttributes();
627 std::stringstream icemsg;
628 icemsg << iceAttributes.ufrag << "\n";
629 icemsg << iceAttributes.pwd << "\n";
630 for (const auto& addr : ice_master->getLocalCandidates(1)) {
631 if (addr.find("relay") != std::string::npos) {
632 // We only want to relayed and modify the rest (to have CONNREFUSED)
633 icemsg << addr << "\n";
634 fmt::print("Added local ICE candidate {}\n", addr);
635 } else {
636 // Replace host by non existing IP (we still need host to not fail the start)
637 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
638 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
639 if (newaddr != addr)
640 icemsg << newaddr << "\n";
641 }
642 }
643 init = icemsg.str();
644 cv_init.notify_one();
645 CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
646 return !response.empty();
647 }));
648 auto sdp = ice_master->parseIceCandidates(response);
649 CPPUNIT_ASSERT(
650 ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
651 });
652 };
653 ice_config.onNegoDone = [&](bool ok) {
654 iceMasterNotReady = !ok;
655 cv.notify_one();
656 };
657 ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
658 ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
659 ice_config.master = true;
660 ice_config.streamsCount = 1;
661 ice_config.compCountPerStream = 1;
662 ice_config.upnpContext = upnpContext;
663 ice_config.factory = factory;
664 ice_master = factory->createTransport("master ICE");
665 ice_master->initIceInstance(ice_config);
666 cv_create.notify_all();
667 ice_config.onInitDone = [&](bool ok) {
668 CPPUNIT_ASSERT(ok);
669 dht::ThreadPool::io().run([&] {
670 CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
671 return ice_slave != nullptr;
672 }));
673 auto iceAttributes = ice_slave->getLocalAttributes();
674 std::stringstream icemsg;
675 icemsg << iceAttributes.ufrag << "\n";
676 icemsg << iceAttributes.pwd << "\n";
677 for (const auto& addr : ice_slave->getLocalCandidates(1)) {
678 if (addr.find("relay") != std::string::npos) {
679 // We only want to relayed and modify the rest (to have CONNREFUSED)
680 icemsg << addr << "\n";
681 fmt::print("Added local ICE candidate {}\n", addr);
682 } else {
683 // Replace host by non existing IP (we still need host to not fail the start)
684 std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
685 auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
686 if (newaddr != addr)
687 icemsg << newaddr << "\n";
688 }
689 }
690 response = icemsg.str();
691 cv_resp.notify_one();
692 CPPUNIT_ASSERT(
693 cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
694 auto sdp = ice_slave->parseIceCandidates(init);
695 CPPUNIT_ASSERT(
696 ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
697 });
698 };
699 ice_config.onNegoDone = [&](bool ok) {
700 iceSlaveNotReady = !ok;
701 cv.notify_one();
702 };
703 ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
704 .setUri(turnV4_->toString(true))
705 .setUsername("ring")
706 .setPassword("ring")
707 .setRealm("ring"));
708 ice_config.master = false;
709 ice_config.streamsCount = 1;
710 ice_config.compCountPerStream = 1;
711 ice_config.upnpContext = upnpContext;
712 ice_config.factory = factory;
713 ice_slave = factory->createTransport("slave ICE");
714 ice_slave->initIceInstance(ice_config);
715 cv_create.notify_all();
716 // Check that nego failed and callback called
717 CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(120), [&] {
718 return iceMasterNotReady && iceSlaveNotReady;
719 }));
720}
721
722} // namespace test
723}
724
725JAMI_TEST_RUNNER(dhtnet::test::IceTest::name())