diff --git a/src/connectivity/Makefile.am b/src/connectivity/Makefile.am index 3c8f482f2..d16c5cc4d 100644 --- a/src/connectivity/Makefile.am +++ b/src/connectivity/Makefile.am @@ -18,10 +18,6 @@ libconnectivity_la_SOURCES = \ ./connectivity/peer_connection.h \ ./connectivity/sip_utils.cpp \ ./connectivity/sip_utils.h \ - ./connectivity/turn_transport.cpp \ - ./connectivity/turn_transport.h \ - ./connectivity/turn_cache.cpp \ - ./connectivity/turn_cache.h \ ./connectivity/utf8_utils.cpp \ ./connectivity/utf8_utils.h \ ./connectivity/transport/peer_channel.h diff --git a/src/connectivity/peer_connection.h b/src/connectivity/peer_connection.h index b6d010b03..be7f10296 100644 --- a/src/connectivity/peer_connection.h +++ b/src/connectivity/peer_connection.h @@ -52,6 +52,9 @@ using OnStateChangeCb = std::function; using OnReadyCb = std::function; using onShutdownCb = std::function; +class TurnTransport; +class ConnectedTurnTransport; + //============================================================================== class Stream diff --git a/src/connectivity/turn_cache.cpp b/src/connectivity/turn_cache.cpp deleted file mode 100644 index 2a6d856f9..000000000 --- a/src/connectivity/turn_cache.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2022 Savoir-faire Linux Inc. - * - * Author: Sébastien Blin - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "connectivity/turn_cache.h" - -#include "logger.h" -#include "fileutils.h" -#include "manager.h" -#include "opendht/thread_pool.h" // TODO remove asio - -namespace jami { - -TurnCache::TurnCache(const std::string& accountId, - const std::string& cachePath, - const TurnTransportParams& params, - bool enabled) - : accountId_(accountId) - , cachePath_(cachePath) - , io_context(Manager::instance().ioContext()) -{ - refreshTimer_ = std::make_unique(*io_context, - std::chrono::steady_clock::now()); - reconfigure(params, enabled); -} - -TurnCache::~TurnCache() {} - -std::optional -TurnCache::getResolvedTurn(uint16_t family) const -{ - if (family == AF_INET && cacheTurnV4_) { - return *cacheTurnV4_; - } else if (family == AF_INET6 && cacheTurnV6_) { - return *cacheTurnV6_; - } - return std::nullopt; -} - -void -TurnCache::reconfigure(const TurnTransportParams& params, bool enabled) -{ - params_ = params; - enabled_ = enabled; - { - std::lock_guard lk(cachedTurnMutex_); - // Force re-resolution - isRefreshing_ = false; - cacheTurnV4_.reset(); - cacheTurnV6_.reset(); - testTurnV4_.reset(); - testTurnV6_.reset(); - } - refreshTimer_->expires_at(std::chrono::steady_clock::now()); - refreshTimer_->async_wait(std::bind(&TurnCache::refresh, this, std::placeholders::_1)); -} - -void -TurnCache::refresh(const asio::error_code& ec) -{ - if (ec == asio::error::operation_aborted) - return; - // The resolution of the TURN server can take quite some time (if timeout). - // So, run this in its own io thread to avoid to block the main thread. - // Avoid multiple refresh - if (isRefreshing_.exchange(true)) - return; - if (!enabled_) { - // In this case, we do not use any TURN server - std::lock_guard lk(cachedTurnMutex_); - cacheTurnV4_.reset(); - cacheTurnV6_.reset(); - isRefreshing_ = false; - return; - } - JAMI_INFO("[Account %s] Refresh cache for TURN server resolution", accountId_.c_str()); - // Retrieve old cached value if available. - // This means that we directly get the correct value when launching the application on the - // same network - // No need to resolve, it's already a valid address - auto server = params_.domain; - if (IpAddr::isValid(server, AF_INET)) { - testTurn(IpAddr(server, AF_INET)); - return; - } else if (IpAddr::isValid(server, AF_INET6)) { - testTurn(IpAddr(server, AF_INET6)); - return; - } - // Else cache resolution result - fileutils::recursive_mkdir(cachePath_ + DIR_SEPARATOR_STR + "domains", 0700); - auto pathV4 = cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v4." + server; - IpAddr testV4, testV6; - if (auto turnV4File = std::ifstream(pathV4)) { - std::string content((std::istreambuf_iterator(turnV4File)), - std::istreambuf_iterator()); - testV4 = IpAddr(content, AF_INET); - } - auto pathV6 = cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v6." + server; - if (auto turnV6File = std::ifstream(pathV6)) { - std::string content((std::istreambuf_iterator(turnV6File)), - std::istreambuf_iterator()); - testV6 = IpAddr(content, AF_INET6); - } - // Resolve just in case. The user can have a different connectivity - auto turnV4 = IpAddr {server, AF_INET}; - { - if (turnV4) { - // Cache value to avoid a delay when starting up Jami - std::ofstream turnV4File(pathV4); - turnV4File << turnV4.toString(); - } else - fileutils::remove(pathV4, true); - // Update TURN - testV4 = IpAddr(std::move(turnV4)); - } - auto turnV6 = IpAddr {server, AF_INET6}; - { - if (turnV6) { - // Cache value to avoid a delay when starting up Jami - std::ofstream turnV6File(pathV6); - turnV6File << turnV6.toString(); - } else - fileutils::remove(pathV6, true); - // Update TURN - testV6 = IpAddr(std::move(turnV6)); - } - if (testV4) - testTurn(testV4); - if (testV6) - testTurn(testV6); - - refreshTurnDelay(!testV4 && !testV6); -} - -void -TurnCache::testTurn(IpAddr server) -{ - TurnTransportParams params = params_; - params.server = server; - std::lock_guard lk(cachedTurnMutex_); - auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_; - turn.reset(); // Stop previous TURN - try { - turn = std::make_unique( - params, std::move([this, server](bool ok) { - std::lock_guard lk(cachedTurnMutex_); - auto& cacheTurn = server.isIpv4() ? cacheTurnV4_ : cacheTurnV6_; - if (!ok) { - JAMI_ERROR("Connection to {:s} failed - reset", server.toString()); - cacheTurn.reset(); - } else { - JAMI_DEBUG("Connection to {:s} ready", server.toString()); - cacheTurn = std::make_unique(server); - } - refreshTurnDelay(!cacheTurnV6_ && !cacheTurnV4_); - auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_; - turn->shutdown(); - })); - } catch (const std::exception& e) { - JAMI_ERROR("TurnTransport creation error: {}", e.what()); - } -} - -void -TurnCache::refreshTurnDelay(bool scheduleNext) -{ - isRefreshing_ = false; - if (scheduleNext) { - JAMI_WARNING("[Account {:s}] Cache for TURN resolution failed.", accountId_); - refreshTimer_->expires_at(std::chrono::steady_clock::now() + turnRefreshDelay_); - refreshTimer_->async_wait(std::bind(&TurnCache::refresh, this, std::placeholders::_1)); - if (turnRefreshDelay_ < std::chrono::minutes(30)) - turnRefreshDelay_ *= 2; - } else { - JAMI_DEBUG("[Account {:s}] Cache refreshed for TURN resolution", accountId_); - turnRefreshDelay_ = std::chrono::seconds(10); - } -} - -} // namespace jami diff --git a/src/connectivity/turn_cache.h b/src/connectivity/turn_cache.h deleted file mode 100644 index 967ede7ab..000000000 --- a/src/connectivity/turn_cache.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2022 Savoir-faire Linux Inc. - * - * Author: Sébastien Blin - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#pragma once - -#include "connectivity/ip_utils.h" -#include "connectivity/turn_transport.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace jami { - -class TurnCache -{ -public: - TurnCache(const std::string& accountId, - const std::string& cachePath, - const TurnTransportParams& params, - bool enabled); - ~TurnCache(); - - std::optional getResolvedTurn(uint16_t family = AF_INET) const; - /** - * Pass a new configuration for the cache - * @param param The new configuration - */ - void reconfigure(const TurnTransportParams& params, bool enabled); - /** - * Refresh cache from current configuration - */ - void refresh(const asio::error_code& ec = {}); - -private: - std::string accountId_; - std::string cachePath_; - TurnTransportParams params_; - std::atomic_bool enabled_ {false}; - /** - * Avoid to refresh the cache multiple times - */ - std::atomic_bool isRefreshing_ {false}; - /** - * This will cache the turn server resolution each time we launch - * Jami, or for each connectivityChange() - */ - void testTurn(IpAddr server); - std::unique_ptr testTurnV4_; - std::unique_ptr testTurnV6_; - - // Used to detect if a turn server is down. - void refreshTurnDelay(bool scheduleNext); - std::chrono::seconds turnRefreshDelay_ {std::chrono::seconds(10)}; - - // Store resoved turn addresses - mutable std::mutex cachedTurnMutex_ {}; - std::unique_ptr cacheTurnV4_ {}; - std::unique_ptr cacheTurnV6_ {}; - - // io - std::shared_ptr io_context; - std::unique_ptr refreshTimer_; -}; - -} // namespace jami diff --git a/src/connectivity/turn_transport.cpp b/src/connectivity/turn_transport.cpp deleted file mode 100644 index 3b510da23..000000000 --- a/src/connectivity/turn_transport.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2004-2022 Savoir-faire Linux Inc. - * - * Author: Guillaume Roguez - * Author: Sébastien Blin - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "connectivity/turn_transport.h" -#include "connectivity/sip_utils.h" -#include "manager.h" - -#include - -#include -#include -#include - -#define TRY(ret) \ - do { \ - if ((ret) != PJ_SUCCESS) \ - throw std::runtime_error(#ret " failed"); \ - } while (0) - -namespace jami { - -class TurnTransport::Impl -{ -public: - Impl(std::function&& cb) - { - io_context = Manager::instance().ioContext(); - cb_ = std::move(cb); - } - ~Impl(); - - /** - * Detect new TURN state - */ - void onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state); - - /** - * Pool events from pjsip - */ - void ioJob(const asio::error_code& ec); - - void start() - { - // Start ioTask - std::lock_guard lock(ioMutex_); - ioTask_ = std::make_unique(*io_context, - std::chrono::steady_clock::now()); - ioTask_->async_wait(std::bind(&TurnTransport::Impl::ioJob, this, std::placeholders::_1)); - } - - void stop() - { - std::lock_guard lock(ioMutex_); - stopped_ = true; - if (ioTask_) - ioTask_->cancel(); - } - - TurnTransportParams settings; - - pj_caching_pool poolCache {}; - pj_pool_t* pool {nullptr}; - pj_stun_config stunConfig {}; - pj_turn_sock* relay {nullptr}; - pj_str_t relayAddr {}; - IpAddr peerRelayAddr; // address where peers should connect to - IpAddr mappedAddr; - std::function cb_; - - // io - std::mutex ioMutex_; - std::shared_ptr io_context; - std::atomic_bool stopped_; - std::unique_ptr ioTask_; -}; - -TurnTransport::Impl::~Impl() -{ - stop(); - pj_caching_pool_destroy(&poolCache); -} -void -TurnTransport::Impl::onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state) -{ - if (new_state == PJ_TURN_STATE_READY) { - pj_turn_session_info info; - pj_turn_sock_get_info(relay, &info); - peerRelayAddr = IpAddr {info.relay_addr}; - mappedAddr = IpAddr {info.mapped_addr}; - JAMI_DEBUG("TURN server ready, peer relay address: {:s}", - peerRelayAddr.toString(true, true).c_str()); - cb_(true); - } else if (old_state <= PJ_TURN_STATE_READY and new_state > PJ_TURN_STATE_READY) { - JAMI_WARNING("TURN server disconnected ({:s})", pj_turn_state_name(new_state)); - cb_(false); - } -} -void -TurnTransport::Impl::ioJob(const asio::error_code& ec) -{ - if (ec == asio::error::operation_aborted) - return; - if (ec) { - JAMI_ERROR("Error: {:s}", ec.message()); - return; - } - const pj_time_val delay = {0, 10}; - pj_ioqueue_poll(stunConfig.ioqueue, &delay); - pj_timer_heap_poll(stunConfig.timer_heap, nullptr); - // If cancelled while running, ioTask_ expires at will crash - std::lock_guard lock(ioMutex_); - if (!stopped_) { - ioTask_->expires_at(std::chrono::steady_clock::now()); - ioTask_->async_wait(std::bind(&TurnTransport::Impl::ioJob, this, std::placeholders::_1)); - } -} - -TurnTransport::TurnTransport(const TurnTransportParams& params, std::function&& cb) - : pimpl_ {new Impl(std::move(cb))} -{ - auto server = params.server; - if (!server.getPort()) - server.setPort(PJ_STUN_PORT); - if (server.isUnspecified()) - throw std::invalid_argument("invalid turn server address"); - pimpl_->settings = params; - // PJSIP memory pool - pj_caching_pool_init(&pimpl_->poolCache, &pj_pool_factory_default_policy, 0); - pimpl_->pool = pj_pool_create(&pimpl_->poolCache.factory, "TurnTransport", 512, 512, nullptr); - if (not pimpl_->pool) - throw std::runtime_error("pj_pool_create() failed"); - // STUN config - pj_stun_config_init(&pimpl_->stunConfig, &pimpl_->poolCache.factory, 0, nullptr, nullptr); - // create global timer heap - TRY(pj_timer_heap_create(pimpl_->pool, 1000, &pimpl_->stunConfig.timer_heap)); - // create global ioqueue - TRY(pj_ioqueue_create(pimpl_->pool, 16, &pimpl_->stunConfig.ioqueue)); - // TURN callbacks - pj_turn_sock_cb relay_cb; - pj_bzero(&relay_cb, sizeof(relay_cb)); - relay_cb.on_state = - [](pj_turn_sock* relay, pj_turn_state_t old_state, pj_turn_state_t new_state) { - auto pimpl = static_cast(pj_turn_sock_get_user_data(relay)); - pimpl->onTurnState(old_state, new_state); - }; - // TURN socket config - pj_turn_sock_cfg turn_sock_cfg; - pj_turn_sock_cfg_default(&turn_sock_cfg); - turn_sock_cfg.max_pkt_size = 4096; - // TURN socket creation - TRY(pj_turn_sock_create(&pimpl_->stunConfig, - server.getFamily(), - PJ_TURN_TP_TCP, - &relay_cb, - &turn_sock_cfg, - &*this->pimpl_, - &pimpl_->relay)); - // TURN allocation setup - pj_turn_alloc_param turn_alloc_param; - pj_turn_alloc_param_default(&turn_alloc_param); - turn_alloc_param.peer_conn_type = PJ_TURN_TP_TCP; - pj_stun_auth_cred cred; - pj_bzero(&cred, sizeof(cred)); - cred.type = PJ_STUN_AUTH_CRED_STATIC; - pj_cstr(&cred.data.static_cred.realm, pimpl_->settings.realm.c_str()); - pj_cstr(&cred.data.static_cred.username, pimpl_->settings.username.c_str()); - cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; - pj_cstr(&cred.data.static_cred.data, pimpl_->settings.password.c_str()); - pimpl_->relayAddr = pj_strdup3(pimpl_->pool, server.toString().c_str()); - // TURN connection/allocation - JAMI_DEBUG("Connecting to TURN {:s}", server.toString(true, true)); - TRY(pj_turn_sock_alloc(pimpl_->relay, - &pimpl_->relayAddr, - server.getPort(), - nullptr, - &cred, - &turn_alloc_param)); - pimpl_->start(); -} - -TurnTransport::~TurnTransport() {} - -void -TurnTransport::shutdown() -{ - pimpl_->stop(); -} - -} // namespace jami diff --git a/src/connectivity/turn_transport.h b/src/connectivity/turn_transport.h deleted file mode 100644 index 560222129..000000000 --- a/src/connectivity/turn_transport.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2022 Savoir-faire Linux Inc. - * - * Author: Guillaume Roguez - * Author: Sébastien Blin - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#pragma once - -#include "connectivity/ip_utils.h" - -#include -#include -#include - -namespace jami { - -struct TurnTransportParams -{ - IpAddr server; - std::string domain; // Used by cache_turn - // Plain Credentials - std::string realm; - std::string username; - std::string password; -}; - -/** - * This class is used to test connection to TURN servers - * No other logic is implemented. - */ -class TurnTransport -{ -public: - TurnTransport(const TurnTransportParams& param, std::function&& cb); - ~TurnTransport(); - void shutdown(); - -private: - TurnTransport() = delete; - class Impl; - std::unique_ptr pimpl_; -}; - -} // namespace jami diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index 7c3d0e06c..ee003ccfe 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -290,7 +290,8 @@ JamiAccount::JamiAccount(const std::string& accountId) , dataPath_(cachePath_ + DIR_SEPARATOR_STR "values") , connectionManager_ {} , nonSwarmTransferManager_(std::make_shared(accountId, "")) -{} +{ +} JamiAccount::~JamiAccount() noexcept { @@ -901,7 +902,7 @@ JamiAccount::loadConfig() registeredName_ = config().registeredName; try { auto str = fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy", - std::chrono::hours(24 * 7)); + std::chrono::hours(24 * 7)); std::string err; Json::Value root; Json::CharReaderBuilder rbuilder; @@ -1964,14 +1965,11 @@ JamiAccount::doRegister_() getAccountID().c_str(), name.c_str()); - if (this->config().turnEnabled && turnCache_) { - auto addr = turnCache_->getResolvedTurn(); - if (addr == std::nullopt) { - // If TURN is enabled, but no TURN cached, there can be a temporary - // resolution error to solve. Sometimes, a connectivity change is not - // enough, so even if this case is really rare, it should be easy to avoid. - turnCache_->refresh(); - } + if (this->config().turnEnabled && !cacheTurnV4_) { + // If TURN is enabled, but no TURN cached, there can be a temporary resolution + // error to solve. Sometimes, a connectivity change is not enough, so even if + // this case is really rare, it should be easy to avoid. + cacheTurnServers(); } auto uri = Uri(name); @@ -2280,7 +2278,7 @@ JamiAccount::setRegistrationState(RegistrationState state, if (registrationState_ != state) { if (state == RegistrationState::REGISTERED) { JAMI_WARN("[Account %s] connected", getAccountID().c_str()); - turnCache_->refresh(); + cacheTurnServers(); storeActiveIpAddress(); } else if (state == RegistrationState::TRYING) { JAMI_WARN("[Account %s] connecting…", getAccountID().c_str()); @@ -2344,10 +2342,11 @@ JamiAccount::setCertificateStatus(const std::string& cert_id, { bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false; if (done) { - dht_->findCertificate(dht::InfoHash(cert_id), - [](const std::shared_ptr& crt) {}); - emitSignal( - getAccountID(), cert_id, tls::TrustStore::statusToStr(status)); + findCertificate(cert_id); + emitSignal(getAccountID(), + cert_id, + tls::TrustStore::statusToStr( + status)); } return done; } @@ -3533,6 +3532,106 @@ JamiAccount::handleMessage(const std::string& from, const std::pairisRefreshing_.exchange(true)) + return; + const auto& conf = this_->config(); + if (!conf.turnEnabled) { + // In this case, we do not use any TURN server + std::lock_guard lk(this_->cachedTurnMutex_); + this_->cacheTurnV4_.reset(); + this_->cacheTurnV6_.reset(); + this_->isRefreshing_ = false; + return; + } + JAMI_INFO("[Account %s] Refresh cache for TURN server resolution", + this_->getAccountID().c_str()); + // Retrieve old cached value if available. + // This means that we directly get the correct value when launching the application on the + // same network + std::string server = conf.turnServer.empty() ? DEFAULT_TURN_SERVER : conf.turnServer; + // No need to resolve, it's already a valid address + if (IpAddr::isValid(server, AF_INET)) { + this_->cacheTurnV4_ = std::make_unique(server, AF_INET); + this_->isRefreshing_ = false; + return; + } else if (IpAddr::isValid(server, AF_INET6)) { + this_->cacheTurnV6_ = std::make_unique(server, AF_INET6); + this_->isRefreshing_ = false; + return; + } + // Else cache resolution result + fileutils::recursive_mkdir(this_->cachePath_ + DIR_SEPARATOR_STR + "domains", 0700); + auto pathV4 = this_->cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v4." + + server; + if (auto turnV4File = std::ifstream(pathV4)) { + std::string content((std::istreambuf_iterator(turnV4File)), + std::istreambuf_iterator()); + std::lock_guard lk(this_->cachedTurnMutex_); + this_->cacheTurnV4_ = std::make_unique(content, AF_INET); + } + auto pathV6 = this_->cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v6." + + server; + if (auto turnV6File = std::ifstream(pathV6)) { + std::string content((std::istreambuf_iterator(turnV6File)), + std::istreambuf_iterator()); + std::lock_guard lk(this_->cachedTurnMutex_); + this_->cacheTurnV6_ = std::make_unique(content, AF_INET6); + } + // Resolve just in case. The user can have a different connectivity + auto turnV4 = IpAddr {server, AF_INET}; + { + if (turnV4) { + // Cache value to avoid a delay when starting up Jami + std::ofstream turnV4File(pathV4); + turnV4File << turnV4.toString(); + } else + fileutils::remove(pathV4, true); + std::lock_guard lk(this_->cachedTurnMutex_); + // Update TURN + this_->cacheTurnV4_ = std::make_unique(std::move(turnV4)); + } + auto turnV6 = IpAddr {server, AF_INET6}; + { + if (turnV6) { + // Cache value to avoid a delay when starting up Jami + std::ofstream turnV6File(pathV6); + turnV6File << turnV6.toString(); + } else + fileutils::remove(pathV6, true); + std::lock_guard lk(this_->cachedTurnMutex_); + // Update TURN + this_->cacheTurnV6_ = std::make_unique(std::move(turnV6)); + } + this_->isRefreshing_ = false; + if (!this_->cacheTurnV6_ && !this_->cacheTurnV4_) { + JAMI_WARN("[Account %s] Cache for TURN resolution failed.", + this_->getAccountID().c_str()); + Manager::instance().scheduleTaskIn( + [w]() { + if (auto shared = w.lock()) + shared->cacheTurnServers(); + }, + this_->turnRefreshDelay_); + if (this_->turnRefreshDelay_ < std::chrono::minutes(30)) + this_->turnRefreshDelay_ *= 2; + } else { + JAMI_INFO("[Account %s] Cache refreshed for TURN resolution", + this_->getAccountID().c_str()); + this_->turnRefreshDelay_ = std::chrono::seconds(10); + } + }); +} + void JamiAccount::callConnectionClosed(const DeviceId& deviceId, bool eraseDummy) { diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index 72761daac..7c68f9f82 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -36,7 +36,6 @@ #include "data_transfer.h" #include "uri.h" #include "jamiaccount_config.h" -#include "connectivity/peer_connection.h" #include "noncopyable.h" #include "connectivity/ip_utils.h" @@ -612,8 +611,7 @@ private: struct BuddyInfo; struct DiscoveredPeer; - inline std::string getProxyConfigKey() const - { + inline std::string getProxyConfigKey() const { const auto& conf = config(); return dht::InfoHash::get(conf.proxyServer + conf.proxyListUrl).toString(); } @@ -787,12 +785,7 @@ private: * This will cache the turn server resolution each time we launch * Jami, or for each connectivityChange() */ - // TODO move in separate class - void testTurn(IpAddr server); void cacheTurnServers(); - std::unique_ptr testTurnV4_; - std::unique_ptr testTurnV6_; - void refreshTurnDelay(bool scheduleNext); std::chrono::seconds turnRefreshDelay_ {std::chrono::seconds(10)}; diff --git a/src/meson.build b/src/meson.build index b0b5b08d6..06da38ed7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -29,8 +29,6 @@ libjami_sources = files( 'connectivity/multiplexed_socket.cpp', 'connectivity/peer_connection.cpp', 'connectivity/sip_utils.cpp', - 'connectivity/turn_cache.cpp', - 'connectivity/turn_transport.cpp', 'connectivity/utf8_utils.cpp', 'im/instant_messaging.cpp', 'im/message_engine.cpp', diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp index 1ecf03a0a..32c9aa567 100644 --- a/src/sip/sipaccountbase.cpp +++ b/src/sip/sipaccountbase.cpp @@ -143,20 +143,6 @@ SIPAccountBase::loadConfig() IpAddr publishedIp {conf.publishedIp}; if (not conf.publishedSameasLocal and publishedIp) setPublishedAddress(publishedIp); - TurnTransportParams turnParams; - turnParams.domain = conf.turnServer; - turnParams.username = conf.turnServerUserName; - turnParams.password = conf.turnServerPwd; - turnParams.realm = conf.turnServerRealm; - if (!turnCache_) { - auto cachePath = fileutils::get_cache_dir() + DIR_SEPARATOR_STR + getAccountID(); - turnCache_ = std::make_unique(getAccountID(), - cachePath, - turnParams, - conf.turnEnabled); - } else { - turnCache_->reconfigure(turnParams, conf.turnEnabled); - } } std::map @@ -255,11 +241,13 @@ SIPAccountBase::getIceOptions() const noexcept // if (config().stunEnabled) // opts.stunServers.emplace_back(StunServerInfo().setUri(stunServer_)); - if (config().turnEnabled && turnCache_) { - auto turnAddr = turnCache_->getResolvedTurn(); - if (turnAddr != std::nullopt) { + if (config().turnEnabled) { + auto cached = false; + std::lock_guard lk(cachedTurnMutex_); + cached = cacheTurnV4_ || cacheTurnV6_; + if (cacheTurnV4_ && *cacheTurnV4_) { opts.turnServers.emplace_back(TurnServerInfo() - .setUri(turnAddr->toString(true)) + .setUri(cacheTurnV4_->toString(true)) .setUsername(config().turnServerUserName) .setPassword(config().turnServerPwd) .setRealm(config().turnServerRealm)); @@ -273,6 +261,14 @@ SIPAccountBase::getIceOptions() const noexcept // .setPassword(turnServerPwd_) // .setRealm(turnServerRealm_)); //} + // Nothing cached, so do the resolution + if (!cached) { + opts.turnServers.emplace_back(TurnServerInfo() + .setUri(config().turnServer) + .setUsername(config().turnServerUserName) + .setPassword(config().turnServerPwd) + .setRealm(config().turnServerRealm)); + } } return opts; } diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h index 70abe43ac..a660d8266 100644 --- a/src/sip/sipaccountbase.h +++ b/src/sip/sipaccountbase.h @@ -28,7 +28,6 @@ #include "connectivity/sip_utils.h" #include "connectivity/ip_utils.h" -#include "connectivity/turn_cache.h" #include "noncopyable.h" #include "im/message_engine.h" #include "sipaccountbase_config.h" @@ -86,8 +85,7 @@ public: virtual ~SIPAccountBase() noexcept; - const SipAccountBaseConfig& config() const - { + const SipAccountBaseConfig& config() const { return *static_cast(&Account::config()); } @@ -218,6 +216,21 @@ public: public: // overloaded methods virtual void flush() override; + /** + * Return current turn resolved addresses + * @return {unique_ptr(v4 resolved), unique_ptr(v6 resolved)} + */ + std::array, 2> turnCache() + { + std::lock_guard lk {cachedTurnMutex_}; + std::array, 2> result = {}; + if (cacheTurnV4_ && *cacheTurnV4_) + result[0] = std::make_unique(*cacheTurnV4_); + if (cacheTurnV6_ && *cacheTurnV6_) + result[1] = std::make_unique(*cacheTurnV6_); + return result; + } + protected: /** * Retrieve volatile details such as recent registration errors @@ -266,7 +279,9 @@ protected: std::chrono::steady_clock::time_point::min()}; std::shared_ptr composingTimeout_; - std::unique_ptr turnCache_; + mutable std::mutex cachedTurnMutex_ {}; + std::unique_ptr cacheTurnV4_ {}; + std::unique_ptr cacheTurnV6_ {}; private: NON_COPYABLE(SIPAccountBase); diff --git a/test/unitTest/call/call.cpp b/test/unitTest/call/call.cpp index a52088dc6..4e6f24965 100644 --- a/test/unitTest/call/call.cpp +++ b/test/unitTest/call/call.cpp @@ -38,7 +38,7 @@ using namespace libjami::Account; using namespace libjami::Call::Details; -using namespace std::literals::chrono_literals; + namespace jami { namespace test { @@ -48,8 +48,7 @@ public: CallTest() { // Init daemon - libjami::init( - libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG)); + libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG)); if (not Manager::instance().initialized) CPPUNIT_ASSERT(libjami::start("jami-sample.yml")); } @@ -69,7 +68,6 @@ private: void testDeclineMultiDevice(); void testTlsInfosPeerCertificate(); void testSocketInfos(); - void testInvalidTurn(); CPPUNIT_TEST_SUITE(CallTest); CPPUNIT_TEST(testCall); @@ -78,7 +76,6 @@ private: CPPUNIT_TEST(testDeclineMultiDevice); CPPUNIT_TEST(testTlsInfosPeerCertificate); CPPUNIT_TEST(testSocketInfos); - CPPUNIT_TEST(testInvalidTurn); CPPUNIT_TEST_SUITE_END(); }; @@ -141,12 +138,12 @@ CallTest::testCall() JAMI_INFO("Start call between alice and Bob"); auto call = libjami::placeCallWithMedia(aliceId, bobUri, {}); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callReceived.load(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callReceived.load(); })); JAMI_INFO("Stop call between alice and Bob"); callStopped = 0; Manager::instance().hangupCall(aliceId, call); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callStopped == 2; })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped == 2; })); } void @@ -193,16 +190,17 @@ CallTest::testCachedCall() successfullyConnected = true; cv.notify_one(); }); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return successfullyConnected.load(); })); + CPPUNIT_ASSERT( + cv.wait_for(lk, std::chrono::seconds(30), [&] { return successfullyConnected.load(); })); JAMI_INFO("Start call between alice and Bob"); auto call = libjami::placeCallWithMedia(aliceId, bobUri, {}); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callReceived.load(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callReceived.load(); })); callStopped = 0; JAMI_INFO("Stop call between alice and Bob"); Manager::instance().hangupCall(aliceId, call); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callStopped == 2; })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped == 2; })); } void @@ -294,12 +292,14 @@ CallTest::testDeclineMultiDevice() JAMI_INFO("Start call between alice and Bob"); auto call = libjami::placeCallWithMedia(aliceId, bobUri, {}); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callReceived == 2 && !callIdBob.empty(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { + return callReceived == 2 && !callIdBob.empty(); + })); JAMI_INFO("Stop call between alice and Bob"); callStopped = 0; Manager::instance().refuseCall(bobId, callIdBob); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped.load() >= 3; /* >= because there is subcalls */ })); } @@ -344,10 +344,11 @@ CallTest::testTlsInfosPeerCertificate() JAMI_INFO("Start call between alice and Bob"); auto callId = libjami::placeCallWithMedia(aliceId, bobUri, {}); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !bobCallId.empty(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !bobCallId.empty(); })); Manager::instance().answerCall(bobId, bobCallId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceCallState == "CURRENT"; })); + CPPUNIT_ASSERT( + cv.wait_for(lk, std::chrono::seconds(30), [&] { return aliceCallState == "CURRENT"; })); auto call = std::dynamic_pointer_cast(aliceAccount->getCall(callId)); auto* transport = call->getTransport(); @@ -359,7 +360,7 @@ CallTest::testTlsInfosPeerCertificate() JAMI_INFO("Stop call between alice and Bob"); callStopped = 0; Manager::instance().hangupCall(aliceId, callId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callStopped == 2; })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped == 2; })); } void @@ -412,10 +413,12 @@ CallTest::testSocketInfos() JAMI_INFO("Start call between alice and Bob"); auto callId = libjami::placeCallWithMedia(aliceId, bobUri, {}); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !bobCallId.empty(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !bobCallId.empty(); })); Manager::instance().answerCall(bobId, bobCallId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceCallState == "CURRENT" && mediaReady; })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { + return aliceCallState == "CURRENT" && mediaReady; + })); JAMI_INFO("Detail debug"); auto details = libjami::getCallDetails(aliceId, callId); @@ -431,55 +434,7 @@ CallTest::testSocketInfos() JAMI_INFO("Stop call between alice and Bob"); callStopped = 0; Manager::instance().hangupCall(aliceId, callId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callStopped == 2; })); -} - -void -CallTest::testInvalidTurn() -{ - auto aliceAccount = Manager::instance().getAccount(aliceId); - auto bobAccount = Manager::instance().getAccount(bobId); - auto bobUri = bobAccount->getUsername(); - auto aliceUri = aliceAccount->getUsername(); - - std::mutex mtx; - std::unique_lock lk {mtx}; - std::condition_variable cv; - std::map> confHandlers; - std::atomic_bool callReceived {false}; - std::atomic callStopped {0}; - // Watch signals - confHandlers.insert(libjami::exportable_callback( - [&](const std::string&, - const std::string&, - const std::string&, - const std::vector>&) { - callReceived = true; - cv.notify_one(); - })); - confHandlers.insert(libjami::exportable_callback( - [&](const std::string&, const std::string&, const std::string& state, signed) { - if (state == "OVER") { - callStopped += 1; - if (callStopped == 2) - cv.notify_one(); - } - })); - libjami::registerSignalHandlers(confHandlers); - - std::map details; - details[ConfProperties::TURN::SERVER] = "1.1.1.1"; - libjami::setAccountDetails(aliceId, details); - - JAMI_INFO("Start call between alice and Bob"); - auto call = libjami::placeCallWithMedia(aliceId, bobUri, {}); - - CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return callReceived.load(); })); - - JAMI_INFO("Stop call between alice and Bob"); - callStopped = 0; - Manager::instance().hangupCall(aliceId, call); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callStopped == 2; })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped == 2; })); } } // namespace test