blob: c2c13042c7f2589c6fe7edbaa35733fef0ba4a26 [file] [log] [blame]
/*
* Copyright (C) 2021-2024 Savoir-faire Linux Inc.
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <cppunit/TestAssert.h>
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <regex>
#include <condition_variable>
#include <asio/executor_work_guard.hpp>
#include <asio/io_context.hpp>
#include "opendht/dhtrunner.h"
#include "opendht/sockaddr.h"
#include "opendht/thread_pool.h"
#include "test_runner.h"
#include "upnp/upnp_context.h"
#include "ice_transport.h"
#include "ice_transport_factory.h"
namespace dhtnet {
namespace test {
class IceTest : public CppUnit::TestFixture
{
public:
IceTest()
{
}
~IceTest() {}
static std::string name() { return "Ice"; }
void setUp();
void tearDown();
// For future tests with publicIp
std::shared_ptr<dht::DhtRunner> dht_ {};
std::unique_ptr<dhtnet::IpAddr> turnV4_ {};
std::shared_ptr<asio::io_context> ioContext;
std::shared_ptr<std::thread> ioContextRunner;
std::shared_ptr<IceTransportFactory> factory;
std::shared_ptr<upnp::UPnPContext> upnpContext;
private:
void testRawIceConnection();
void testTurnMasterIceConnection();
void testTurnSlaveIceConnection();
void testReceiveTooManyCandidates();
void testCompleteOnFailure();
CPPUNIT_TEST_SUITE(IceTest);
CPPUNIT_TEST(testRawIceConnection);
CPPUNIT_TEST(testTurnMasterIceConnection);
CPPUNIT_TEST(testTurnSlaveIceConnection);
CPPUNIT_TEST(testReceiveTooManyCandidates);
CPPUNIT_TEST(testCompleteOnFailure);
CPPUNIT_TEST_SUITE_END();
};
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(IceTest, IceTest::name());
void
IceTest::setUp()
{
if (!dht_) {
dht_ = std::make_shared<dht::DhtRunner>();
dht::DhtRunner::Config config {};
dht::DhtRunner::Context context {};
std::mutex mtx;
std::unique_lock lk(mtx);
std::condition_variable cv;
context.publicAddressChangedCb = [&](std::vector<dht::SockAddr> addr) {
if (addr.size() != 0)
cv.notify_all();
};
dht_->run(0, config, std::move(context));
dht_->bootstrap("bootstrap.jami.net:4222");
// Wait for the DHT's public address to be available, otherwise the assertion that
// `addr4.size() != 0` at the beginning of several of the tests will fail.
cv.wait_for(lk, std::chrono::seconds(5), [&] {
return dht_->getPublicAddress().size() != 0;
});
}
if (!turnV4_) {
turnV4_ = std::make_unique<dhtnet::IpAddr>("turn.jami.net", AF_INET);
}
if (!upnpContext) {
if (!ioContext) {
ioContext = std::make_shared<asio::io_context>();
ioContextRunner = std::make_shared<std::thread>([&] {
auto work = asio::make_work_guard(*ioContext);
ioContext->run();
});
}
upnpContext = std::make_shared<dhtnet::upnp::UPnPContext>(ioContext, nullptr);
}
if (!factory) {
factory = std::make_shared<IceTransportFactory>();
}
}
void
IceTest::tearDown()
{
upnpContext->shutdown();
ioContext->stop();
if (ioContextRunner && ioContextRunner->joinable()) {
ioContextRunner->join();
}
dht_.reset();
turnV4_.reset();
}
void
IceTest::testRawIceConnection()
{
dhtnet::IceTransportOptions ice_config;
ice_config.upnpEnable = true;
ice_config.tcpEnable = true;
std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
std::mutex mtx, mtx_create, mtx_resp, mtx_init;
std::unique_lock lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
lk_init {mtx_init};
std::condition_variable cv, cv_create, cv_resp, cv_init;
std::string init = {};
std::string response = {};
bool iceMasterReady = false, iceSlaveReady = false;
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_master != nullptr;
}));
auto iceAttributes = ice_master->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_master->getLocalCandidates(1)) {
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
}
init = icemsg.str();
cv_init.notify_one();
CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
return !response.empty();
}));
auto sdp = ice_master->parseIceCandidates(response);
CPPUNIT_ASSERT(
ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceMasterReady = ok;
cv.notify_one();
};
ice_config.master = true;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_master = factory->createTransport("master ICE");
ice_master->initIceInstance(ice_config);
cv_create.notify_all();
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_slave != nullptr;
}));
auto iceAttributes = ice_slave->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_slave->getLocalCandidates(1)) {
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
}
response = icemsg.str();
cv_resp.notify_one();
CPPUNIT_ASSERT(
cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
auto sdp = ice_slave->parseIceCandidates(init);
CPPUNIT_ASSERT(
ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceSlaveReady = ok;
cv.notify_one();
};
ice_config.master = false;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_slave = factory->createTransport("slave ICE");
ice_slave->initIceInstance(ice_config);
cv_create.notify_all();
CPPUNIT_ASSERT(
cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
}
void
IceTest::testTurnMasterIceConnection()
{
const auto& addr4 = dht_->getPublicAddress(AF_INET);
CPPUNIT_ASSERT(addr4.size() != 0);
CPPUNIT_ASSERT(turnV4_);
dhtnet::IceTransportOptions ice_config;
ice_config.upnpEnable = true;
ice_config.tcpEnable = true;
std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
std::mutex mtx, mtx_create, mtx_resp, mtx_init;
std::condition_variable cv, cv_create, cv_resp, cv_init;
std::string init = {};
std::string response = {};
bool iceMasterReady = false, iceSlaveReady = false;
// Master
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
/*{
std::unique_lock lk_create {mtx_create};
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_master != nullptr;
}));
}*/
auto iceAttributes = ice_master->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_master->getLocalCandidates(1)) {
if (addr.find("host") == std::string::npos) {
// We only want to add relayed + public ip
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
} else {
// Replace host by non existing IP (we still need host to not fail the start)
std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
if (newaddr != addr)
icemsg << newaddr << "\n";
}
}
{
std::lock_guard lk {mtx_init};
init = icemsg.str();
cv_init.notify_one();
}
{
std::unique_lock lk_resp {mtx_resp};
CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
return !response.empty();
}));
auto sdp = ice_master->parseIceCandidates(response);
CPPUNIT_ASSERT(
ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
}
});
};
ice_config.onNegoDone = [&](bool ok) {
std::lock_guard lk {mtx};
iceMasterReady = ok;
cv.notify_one();
};
ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
.setUri(turnV4_->toString(true))
.setUsername("ring")
.setPassword("ring")
.setRealm("ring"));
ice_config.master = true;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
{
std::unique_lock lk_create {mtx_create};
ice_master = factory->createTransport("master ICE");
ice_master->initIceInstance(ice_config);
cv_create.notify_all();
}
// Slave
ice_config.turnServers = {};
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
/*std::unique_lock lk_create {mtx_create};
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_slave != nullptr;
}));*/
auto iceAttributes = ice_slave->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_slave->getLocalCandidates(1)) {
if (addr.find("host") == std::string::npos) {
// We only want to add relayed + public ip
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
} else {
// Replace host by non existing IP (we still need host to not fail the start)
std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
if (newaddr != addr)
icemsg << newaddr << "\n";
}
}
{
std::lock_guard lk {mtx_resp};
response = icemsg.str();
cv_resp.notify_one();
}
{
std::unique_lock lk {mtx_init};
CPPUNIT_ASSERT(
cv_init.wait_for(lk, std::chrono::seconds(10), [&] { return !init.empty(); }));
auto sdp = ice_slave->parseIceCandidates(init);
CPPUNIT_ASSERT(
ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
}
});
};
ice_config.onNegoDone = [&](bool ok) {
std::lock_guard lk {mtx};
iceSlaveReady = ok;
cv.notify_one();
};
ice_config.master = false;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
{
std::unique_lock lk_create {mtx_create};
ice_slave = factory->createTransport("slave ICE");
ice_slave->initIceInstance(ice_config);
cv_create.notify_all();
}
std::unique_lock lk {mtx};
CPPUNIT_ASSERT(
cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady; }));
CPPUNIT_ASSERT(
cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceSlaveReady; }));
CPPUNIT_ASSERT(ice_master->getLocalAddress(1).toString(false) == turnV4_->toString(false));
}
void
IceTest::testTurnSlaveIceConnection()
{
const auto& addr4 = dht_->getPublicAddress(AF_INET);
CPPUNIT_ASSERT(addr4.size() != 0);
CPPUNIT_ASSERT(turnV4_);
dhtnet::IceTransportOptions ice_config;
ice_config.upnpEnable = true;
ice_config.tcpEnable = true;
std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
std::mutex mtx, mtx_create, mtx_resp, mtx_init;
std::unique_lock lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
lk_init {mtx_init};
std::condition_variable cv, cv_create, cv_resp, cv_init;
std::string init = {};
std::string response = {};
bool iceMasterReady = false, iceSlaveReady = false;
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_master != nullptr;
}));
auto iceAttributes = ice_master->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_master->getLocalCandidates(1)) {
if (addr.find("host") == std::string::npos) {
// We only want to add relayed + public ip
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
} else {
// Replace host by non existing IP (we still need host to not fail the start)
std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
if (newaddr != addr)
icemsg << newaddr << "\n";
}
}
init = icemsg.str();
cv_init.notify_one();
CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
return !response.empty();
}));
auto sdp = ice_master->parseIceCandidates(response);
CPPUNIT_ASSERT(
ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceMasterReady = ok;
cv.notify_one();
};
ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
ice_config.master = true;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_master = factory->createTransport("master ICE");
ice_master->initIceInstance(ice_config);
cv_create.notify_all();
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_slave != nullptr;
}));
auto iceAttributes = ice_slave->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_slave->getLocalCandidates(1)) {
if (addr.find("host") == std::string::npos) {
// We only want to add relayed + public ip
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
} else {
// Replace host by non existing IP (we still need host to not fail the start)
std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
if (newaddr != addr)
icemsg << newaddr << "\n";
}
}
response = icemsg.str();
cv_resp.notify_one();
CPPUNIT_ASSERT(
cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
auto sdp = ice_slave->parseIceCandidates(init);
CPPUNIT_ASSERT(
ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceSlaveReady = ok;
cv.notify_one();
};
ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
.setUri(turnV4_->toString(true))
.setUsername("ring")
.setPassword("ring")
.setRealm("ring"));
ice_config.master = false;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_slave = factory->createTransport("slave ICE");
ice_slave->initIceInstance(ice_config);
cv_create.notify_all();
CPPUNIT_ASSERT(
cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
CPPUNIT_ASSERT(ice_slave->getLocalAddress(1).toString(false) == turnV4_->toString(false));
}
void
IceTest::testReceiveTooManyCandidates()
{
const auto& addr4 = dht_->getPublicAddress(AF_INET);
CPPUNIT_ASSERT(addr4.size() != 0);
CPPUNIT_ASSERT(turnV4_);
dhtnet::IceTransportOptions ice_config;
ice_config.upnpEnable = true;
ice_config.tcpEnable = true;
std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
std::mutex mtx, mtx_create, mtx_resp, mtx_init;
std::condition_variable cv, cv_create, cv_resp, cv_init;
std::string init = {};
std::string response = {};
bool iceMasterReady = false, iceSlaveReady = false;
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
{
std::unique_lock lk_create {mtx_create};
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_master != nullptr;
}));
}
auto iceAttributes = ice_master->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_master->getLocalCandidates(1)) {
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
}
init = icemsg.str();
cv_init.notify_one();
{
std::unique_lock lk_resp {mtx_resp};
CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
return !response.empty();
}));
auto sdp = ice_master->parseIceCandidates(response);
CPPUNIT_ASSERT(
ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
}
});
};
ice_config.onNegoDone = [&](bool ok) {
iceMasterReady = ok;
cv.notify_one();
};
ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
.setUri(turnV4_->toString(true))
.setUsername("ring")
.setPassword("ring")
.setRealm("ring"));
ice_config.master = true;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_master = factory->createTransport("master ICE");
ice_master->initIceInstance(ice_config);
cv_create.notify_all();
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
{
std::unique_lock lk_create {mtx_create};
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_slave != nullptr;
}));
}
auto iceAttributes = ice_slave->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_master->getLocalCandidates(1)) {
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
}
for (auto i = 0; i < std::min(256, PJ_ICE_ST_MAX_CAND); ++i) {
icemsg << "Hc0a800a5 1 TCP 2130706431 192.168.0." << i
<< " 43613 typ host tcptype passive"
<< "\n";
icemsg << "Hc0a800a5 1 TCP 2130706431 192.168.0." << i
<< " 9 typ host tcptype active"
<< "\n";
}
{
std::lock_guard lk_resp {mtx_resp};
response = icemsg.str();
cv_resp.notify_one();
}
std::unique_lock lk_init {mtx_init};
CPPUNIT_ASSERT(
cv_init.wait_for(lk_init, std::chrono::seconds(10), [&] { return !init.empty(); }));
auto sdp = ice_slave->parseIceCandidates(init);
CPPUNIT_ASSERT(
ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceSlaveReady = ok;
cv.notify_one();
};
ice_config.master = false;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_slave = factory->createTransport("slave ICE");
ice_slave->initIceInstance(ice_config);
cv_create.notify_all();
std::unique_lock lk {mtx};
CPPUNIT_ASSERT(
cv.wait_for(lk, std::chrono::seconds(10), [&] { return iceMasterReady && iceSlaveReady; }));
}
void
IceTest::testCompleteOnFailure()
{
const auto& addr4 = dht_->getPublicAddress(AF_INET);
CPPUNIT_ASSERT(addr4.size() != 0);
CPPUNIT_ASSERT(turnV4_);
dhtnet::IceTransportOptions ice_config;
ice_config.upnpEnable = true;
ice_config.tcpEnable = true;
std::shared_ptr<dhtnet::IceTransport> ice_master, ice_slave;
std::mutex mtx, mtx_create, mtx_resp, mtx_init;
std::unique_lock lk {mtx}, lk_create {mtx_create}, lk_resp {mtx_resp},
lk_init {mtx_init};
std::condition_variable cv, cv_create, cv_resp, cv_init;
std::string init = {};
std::string response = {};
bool iceMasterNotReady = false, iceSlaveNotReady = false;
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_master != nullptr;
}));
auto iceAttributes = ice_master->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_master->getLocalCandidates(1)) {
if (addr.find("relay") != std::string::npos) {
// We only want to relayed and modify the rest (to have CONNREFUSED)
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
} else {
// Replace host by non existing IP (we still need host to not fail the start)
std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
if (newaddr != addr)
icemsg << newaddr << "\n";
}
}
init = icemsg.str();
cv_init.notify_one();
CPPUNIT_ASSERT(cv_resp.wait_for(lk_resp, std::chrono::seconds(10), [&] {
return !response.empty();
}));
auto sdp = ice_master->parseIceCandidates(response);
CPPUNIT_ASSERT(
ice_master->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceMasterNotReady = !ok;
cv.notify_one();
};
ice_config.accountPublicAddr = dhtnet::IpAddr(*addr4[0].get());
ice_config.accountLocalAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
ice_config.master = true;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_master = factory->createTransport("master ICE");
ice_master->initIceInstance(ice_config);
cv_create.notify_all();
ice_config.onInitDone = [&](bool ok) {
CPPUNIT_ASSERT(ok);
dht::ThreadPool::io().run([&] {
CPPUNIT_ASSERT(cv_create.wait_for(lk_create, std::chrono::seconds(10), [&] {
return ice_slave != nullptr;
}));
auto iceAttributes = ice_slave->getLocalAttributes();
std::stringstream icemsg;
icemsg << iceAttributes.ufrag << "\n";
icemsg << iceAttributes.pwd << "\n";
for (const auto& addr : ice_slave->getLocalCandidates(1)) {
if (addr.find("relay") != std::string::npos) {
// We only want to relayed and modify the rest (to have CONNREFUSED)
icemsg << addr << "\n";
fmt::print("Added local ICE candidate {}\n", addr);
} else {
// Replace host by non existing IP (we still need host to not fail the start)
std::regex e("((?:[0-9]{1,3}\\.){3}[0-9]{1,3})");
auto newaddr = std::regex_replace(addr, e, "100.100.100.100");
if (newaddr != addr)
icemsg << newaddr << "\n";
}
}
response = icemsg.str();
cv_resp.notify_one();
CPPUNIT_ASSERT(
cv_init.wait_for(lk_resp, std::chrono::seconds(10), [&] { return !init.empty(); }));
auto sdp = ice_slave->parseIceCandidates(init);
CPPUNIT_ASSERT(
ice_slave->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates)));
});
};
ice_config.onNegoDone = [&](bool ok) {
iceSlaveNotReady = !ok;
cv.notify_one();
};
ice_config.turnServers.emplace_back(dhtnet::TurnServerInfo()
.setUri(turnV4_->toString(true))
.setUsername("ring")
.setPassword("ring")
.setRealm("ring"));
ice_config.master = false;
ice_config.streamsCount = 1;
ice_config.compCountPerStream = 1;
ice_config.upnpContext = upnpContext;
ice_config.factory = factory;
ice_slave = factory->createTransport("slave ICE");
ice_slave->initIceInstance(ice_config);
cv_create.notify_all();
// Check that nego failed and callback called
CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(120), [&] {
return iceMasterNotReady && iceSlaveNotReady;
}));
}
} // namespace test
}
JAMI_TEST_RUNNER(dhtnet::test::IceTest::name())