add turn cache
Change-Id: I71ed970e4005d73035223ac8b32c24b27e923f1e
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9641f89..3ddea4e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,8 @@
src/security/tls_session.cpp
src/security/certstore.cpp
src/security/threadloop.cpp
+ src/turn/turn_cache.cpp
+ src/turn/turn_transport.cpp
src/upnp/upnp_context.cpp
src/upnp/upnp_control.cpp
src/upnp/protocol/mapping.cpp
diff --git a/include/turn_cache.h b/include/turn_cache.h
new file mode 100644
index 0000000..c8d3684
--- /dev/null
+++ b/include/turn_cache.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
+ *
+ * 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/>.
+ */
+#pragma once
+
+#include "ip_utils.h"
+#include "turn_params.h"
+
+#include <asio.hpp>
+
+#include <atomic>
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+
+namespace dht {
+namespace log {
+class Logger;
+}
+}
+
+namespace dhtnet {
+
+using Logger = dht::log::Logger;
+
+class TurnTransport;
+
+class TurnCache : public std::enable_shared_from_this<TurnCache>
+{
+public:
+ TurnCache(const std::string& accountId,
+ const std::string& cachePath,
+ const std::shared_ptr<asio::io_context>& io_context,
+ const std::shared_ptr<Logger>& logger,
+ const TurnTransportParams& params,
+ bool enabled);
+ ~TurnCache();
+
+ std::optional<IpAddr> 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<TurnTransport> testTurnV4_;
+ std::unique_ptr<TurnTransport> 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<IpAddr> cacheTurnV4_ {};
+ std::unique_ptr<IpAddr> cacheTurnV6_ {};
+
+ void onConnected(const asio::error_code& ec, bool ok, IpAddr server);
+
+ // io
+ std::shared_ptr<asio::io_context> io_context;
+ std::unique_ptr<asio::steady_timer> refreshTimer_;
+ std::unique_ptr<asio::steady_timer> onConnectedTimer_;
+
+ std::mutex shutdownMtx_;
+
+ std::shared_ptr<Logger> logger_;
+
+ // Asio :(
+ // https://stackoverflow.com/questions/35507956/is-it-safe-to-destroy-boostasio-timer-from-its-handler-or-handler-dtor
+ std::weak_ptr<TurnCache> weak()
+ {
+ return std::static_pointer_cast<TurnCache>(shared_from_this());
+ }
+};
+
+} // namespace jami
diff --git a/include/turn_params.h b/include/turn_params.h
new file mode 100644
index 0000000..1708ad9
--- /dev/null
+++ b/include/turn_params.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
+ *
+ * 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/>.
+ */
+#pragma once
+
+#include "ip_utils.h"
+#include <string>
+
+namespace dhtnet {
+
+struct TurnTransportParams
+{
+ dhtnet::IpAddr server;
+ std::string domain; // Used by cache_turn
+ // Plain Credentials
+ std::string realm;
+ std::string username;
+ std::string password;
+};
+
+}
diff --git a/src/turn/turn_cache.cpp b/src/turn/turn_cache.cpp
new file mode 100644
index 0000000..12dfe8b
--- /dev/null
+++ b/src/turn/turn_cache.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
+ *
+ * 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 "turn_cache.h"
+#include "fileutils.h"
+#include "turn_transport.h"
+
+#include <opendht/thread_pool.h>
+#include <opendht/logger.h>
+#include <fstream>
+
+namespace dhtnet {
+
+TurnCache::TurnCache(const std::string& accountId,
+ const std::string& cachePath,
+ const std::shared_ptr<asio::io_context>& io_ctx,
+ const std::shared_ptr<Logger>& logger,
+ const TurnTransportParams& params,
+ bool enabled)
+ : accountId_(accountId)
+ , cachePath_(cachePath)
+ , io_context(io_ctx)
+ , logger_(logger)
+{
+ refreshTimer_ = std::make_unique<asio::steady_timer>(*io_context,
+ std::chrono::steady_clock::now());
+ onConnectedTimer_ = std::make_unique<asio::steady_timer>(*io_context,
+ std::chrono::steady_clock::now());
+}
+
+TurnCache::~TurnCache() {
+ {
+ std::lock_guard<std::mutex> lock(shutdownMtx_);
+ if (refreshTimer_) {
+ refreshTimer_->cancel();
+ refreshTimer_.reset();
+ }
+ if (onConnectedTimer_) {
+ onConnectedTimer_->cancel();
+ onConnectedTimer_.reset();
+ }
+ }
+ {
+ std::lock_guard<std::mutex> lock(cachedTurnMutex_);
+ testTurnV4_.reset();
+ testTurnV6_.reset();
+ cacheTurnV4_.reset();
+ cacheTurnV6_.reset();
+ }
+}
+
+std::optional<IpAddr>
+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<std::mutex> lk(cachedTurnMutex_);
+ // Force re-resolution
+ isRefreshing_ = false;
+ cacheTurnV4_.reset();
+ cacheTurnV6_.reset();
+ testTurnV4_.reset();
+ testTurnV6_.reset();
+ }
+ std::lock_guard<std::mutex> lock(shutdownMtx_);
+ if (refreshTimer_) {
+ refreshTimer_->expires_at(std::chrono::steady_clock::now());
+ refreshTimer_->async_wait(std::bind(&TurnCache::refresh, shared_from_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<std::mutex> lk(cachedTurnMutex_);
+ cacheTurnV4_.reset();
+ cacheTurnV6_.reset();
+ isRefreshing_ = false;
+ return;
+ }
+
+ if(logger_) logger_->debug("[Account {}] Refresh cache for TURN server resolution", accountId_);
+ // 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<char>(turnV4File)),
+ std::istreambuf_iterator<char>());
+ 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<char>(turnV6File)),
+ std::istreambuf_iterator<char>());
+ 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<std::mutex> lk(cachedTurnMutex_);
+ auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_;
+ turn.reset(); // Stop previous TURN
+ try {
+ turn = std::make_unique<TurnTransport>(
+ params, [this, server](bool ok) {
+ // Stop server in an async job, because this callback can be called
+ // immediately and cachedTurnMutex_ must not be locked.
+ std::lock_guard<std::mutex> lock(shutdownMtx_);
+ if (onConnectedTimer_) {
+ onConnectedTimer_->expires_at(std::chrono::steady_clock::now());
+ onConnectedTimer_->async_wait(std::bind(&TurnCache::onConnected, shared_from_this(), std::placeholders::_1, ok, server));
+ }
+ });
+ } catch (const std::exception& e) {
+ if(logger_) logger_->error("TurnTransport creation error: {}", e.what());
+ }
+}
+
+void
+TurnCache::onConnected(const asio::error_code& ec, bool ok, IpAddr server)
+{
+ if (ec == asio::error::operation_aborted)
+ return;
+
+ std::lock_guard<std::mutex> lk(cachedTurnMutex_);
+ auto& cacheTurn = server.isIpv4() ? cacheTurnV4_ : cacheTurnV6_;
+ if (!ok) {
+ if(logger_) logger_->error("Connection to {:s} failed - reset", server.toString());
+ cacheTurn.reset();
+ } else {
+ if(logger_) logger_->debug("Connection to {:s} ready", server.toString());
+ cacheTurn = std::make_unique<IpAddr>(server);
+ }
+ refreshTurnDelay(!cacheTurnV6_ && !cacheTurnV4_);
+ if (auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_)
+ turn->shutdown();
+}
+
+
+void
+TurnCache::refreshTurnDelay(bool scheduleNext)
+{
+ isRefreshing_ = false;
+ if (scheduleNext) {
+ std::lock_guard<std::mutex> lock(shutdownMtx_);
+ if(logger_) logger_->warn("[Account {:s}] Cache for TURN resolution failed.", accountId_);
+ if (refreshTimer_) {
+ refreshTimer_->expires_at(std::chrono::steady_clock::now() + turnRefreshDelay_);
+ refreshTimer_->async_wait(std::bind(&TurnCache::refresh, shared_from_this(), std::placeholders::_1));
+ }
+ if (turnRefreshDelay_ < std::chrono::minutes(30))
+ turnRefreshDelay_ *= 2;
+ } else {
+ if(logger_) logger_->debug("[Account {:s}] Cache refreshed for TURN resolution", accountId_);
+ turnRefreshDelay_ = std::chrono::seconds(10);
+ }
+}
+
+} // namespace jami
diff --git a/src/turn/turn_transport.cpp b/src/turn/turn_transport.cpp
new file mode 100644
index 0000000..bfca535
--- /dev/null
+++ b/src/turn/turn_transport.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
+ *
+ * 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 "turn_transport.h"
+#include "../sip_utils.h"
+
+#include <atomic>
+#include <thread>
+
+#include <pjnath.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+#define TRY(ret) \
+ do { \
+ if ((ret) != PJ_SUCCESS) \
+ throw std::runtime_error(#ret " failed"); \
+ } while (0)
+
+namespace dhtnet {
+
+class TurnLock
+{
+ pj_grp_lock_t* lk_;
+
+public:
+ TurnLock(pj_turn_sock* sock)
+ : lk_(pj_turn_sock_get_grp_lock(sock))
+ {
+ lock();
+ }
+
+ ~TurnLock() { unlock(); }
+
+ void lock() { pj_grp_lock_add_ref(lk_); }
+
+ void unlock() { pj_grp_lock_dec_ref(lk_); }
+};
+
+class TurnTransport::Impl
+{
+public:
+ Impl(std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger)
+ : cb_(std::move(cb)), logger_(logger) {}
+ ~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();
+
+ void start()
+ {
+ ioWorker = std::thread([this] { ioJob(); });
+ }
+
+ void shutdown() {
+ std::lock_guard<std::mutex> lock(shutdownMtx_);
+ if (relay) {
+ pj_turn_sock_destroy(relay);
+ relay = nullptr;
+ }
+ turnLock.reset();
+ if (ioWorker.joinable())
+ ioWorker.join();
+ }
+
+ TurnTransportParams settings;
+
+ pj_caching_pool poolCache {};
+ pj_pool_t* pool {nullptr};
+ pj_stun_config stunConfig {};
+ pj_turn_sock* relay {nullptr};
+ std::unique_ptr<TurnLock> turnLock;
+ pj_str_t relayAddr {};
+ IpAddr peerRelayAddr; // address where peers should connect to
+ IpAddr mappedAddr;
+ std::function<void(bool)> cb_;
+
+ std::thread ioWorker;
+ std::atomic_bool stopped_ {false};
+ std::atomic_bool cbCalled_ {false};
+ std::mutex shutdownMtx_;
+ std::shared_ptr<Logger> logger_;
+};
+
+TurnTransport::Impl::~Impl()
+{
+ shutdown();
+ 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_DESTROYING) {
+ stopped_ = true;
+ return;
+ }
+
+ 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};
+ if(logger_) logger_->debug("TURN server ready, peer relay address: {:s}",
+ peerRelayAddr.toString(true, true).c_str());
+ cbCalled_ = true;
+ cb_(true);
+ } else if (old_state <= PJ_TURN_STATE_READY and new_state > PJ_TURN_STATE_READY and not cbCalled_) {
+ if(logger_) logger_->debug("TURN server disconnected ({:s})", pj_turn_state_name(new_state));
+ cb_(false);
+ }
+}
+
+void
+TurnTransport::Impl::ioJob()
+{
+ const pj_time_val delay = {0, 10};
+ while (!stopped_) {
+ pj_ioqueue_poll(stunConfig.ioqueue, &delay);
+ pj_timer_heap_poll(stunConfig.timer_heap, nullptr);
+ }
+}
+
+TurnTransport::TurnTransport(const TurnTransportParams& params, std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger)
+ : pimpl_ {new Impl(std::move(cb), logger)}
+{
+ 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<Impl*>(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
+ if (logger) logger->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_->turnLock = std::make_unique<TurnLock>(pimpl_->relay);
+ pimpl_->start();
+}
+
+TurnTransport::~TurnTransport() {}
+
+void
+TurnTransport::shutdown()
+{
+ pimpl_->shutdown();
+}
+
+} // namespace jami
diff --git a/src/turn/turn_transport.h b/src/turn/turn_transport.h
new file mode 100644
index 0000000..da9404e
--- /dev/null
+++ b/src/turn/turn_transport.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
+ *
+ * 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/>.
+ */
+#pragma once
+
+#include "ip_utils.h"
+#include "turn_params.h"
+
+#include <opendht/logger.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace dht {
+namespace log {
+class Logger;
+}
+}
+
+namespace dhtnet {
+
+using Logger = dht::log::Logger;
+
+/**
+ * This class is used to test connection to TURN servers
+ * No other logic is implemented.
+ */
+class TurnTransport
+{
+public:
+ TurnTransport(const TurnTransportParams& param, std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger = {});
+ ~TurnTransport();
+ void shutdown();
+
+private:
+ TurnTransport() = delete;
+ class Impl;
+ std::unique_ptr<Impl> pimpl_;
+};
+
+} // namespace jami