From edebe171393b550cd404fb94682482171b65b9dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Wed, 28 Jun 2023 13:50:15 -0400
Subject: [PATCH] connectionmanager: prevent JamiAccount usage

GitLab: #778
Change-Id: I5e7eb072ebda81c4ae45316cc46842dafdeaad13
---
 src/connectivity/connectionmanager.cpp       | 674 +++++++++++++++----
 src/connectivity/connectionmanager.h         | 125 +++-
 src/connectivity/security/diffie-hellman.cpp |  29 +
 src/connectivity/security/diffie-hellman.h   |   3 +
 src/jamidht/jamiaccount.cpp                  | 147 ++--
 src/jamidht/jamiaccount.h                    |  18 +-
 src/sip/sipvoiplink.cpp                      |   1 -
 test/unitTest/call/call.cpp                  |   3 +-
 8 files changed, 761 insertions(+), 239 deletions(-)

diff --git a/src/connectivity/connectionmanager.cpp b/src/connectivity/connectionmanager.cpp
index e7ea8c066d..85afa5c084 100644
--- a/src/connectivity/connectionmanager.cpp
+++ b/src/connectivity/connectionmanager.cpp
@@ -16,12 +16,14 @@
  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 #include "connectionmanager.h"
-#include "jamidht/jamiaccount.h"
 #include "account_const.h"
-#include "jamidht/account_manager.h"
 #include "manager.h"
 #include "peer_connection.h"
 #include "logger.h"
+#include "fileutils.h"
+#include "connectivity/upnp/upnp_control.h"
+#include "connectivity/sip_utils.h"
+#include "connectivity/security/certstore.h"
 
 #include <asio.hpp>
 #include <opendht/crypto.h>
@@ -61,14 +63,31 @@ struct ConnectionInfo
     std::unique_ptr<asio::steady_timer> waitForAnswer_ {};
 };
 
+/**
+ * returns whether or not UPnP is enabled and active_
+ * ie: if it is able to make port mappings
+ */
+bool
+ConnectionManager::Config::getUPnPActive() const
+{
+    std::lock_guard<std::mutex> lk(upnp_mtx);
+    if (upnpCtrl_)
+        return upnpCtrl_->isReady();
+    return false;
+}
+
 class ConnectionManager::Impl : public std::enable_shared_from_this<ConnectionManager::Impl>
 {
 public:
-    explicit Impl(JamiAccount& account)
-        : account {account}
+    explicit Impl(std::shared_ptr<ConnectionManager::Config> config_)
+        : config_ {std::move(config_)}
+        , rand {dht::crypto::getSeededRandomEngine<std::mt19937_64>()}
     {}
     ~Impl() {}
 
+    std::shared_ptr<dht::DhtRunner> dht() { return config_->dht_; }
+    const dht::crypto::Identity& identity() const { return config_->id_; }
+
     void removeUnusedConnections(const DeviceId& deviceId = {})
     {
         std::vector<std::shared_ptr<ConnectionInfo>> unused {};
@@ -163,6 +182,87 @@ public:
     void onPeerResponse(const PeerConnectionRequest& req);
     void onDhtConnected(const dht::crypto::PublicKey& devicePk);
 
+    const std::shared_future<tls::DhParams> dhParams() const;
+    tls::CertificateStore& certStore() const { return *config_->certStore_; }
+
+    mutable std::mutex messageMutex_ {};
+    std::set<std::string, std::less<>> treatedMessages_ {};
+
+    void loadTreatedMessages();
+    void saveTreatedMessages() const;
+
+    /// \return true if the given DHT message identifier has been treated
+    /// \note if message has not been treated yet this method store this id and returns true at
+    /// further calls
+    bool isMessageTreated(std::string_view id);
+
+    /**
+     * Published IPv4/IPv6 addresses, used only if defined by the user in account
+     * configuration
+     *
+     */
+    IpAddr publishedIp_[2] {};
+
+    // This will be stored in the configuration
+    std::string publishedIpAddress_ {};
+
+    /**
+     * Published port, used only if defined by the user
+     */
+    pj_uint16_t publishedPort_ {sip_utils::DEFAULT_SIP_PORT};
+
+    /**
+     * interface name on which this account is bound
+     */
+    std::string interface_ {"default"};
+
+    /**
+     * Get the local interface name on which this account is bound.
+     */
+    const std::string& getLocalInterface() const { return interface_; }
+
+    /**
+     * Get the published IP address, fallbacks to NAT if family is unspecified
+     * Prefers the usage of IPv4 if possible.
+     */
+    IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const;
+
+    /**
+     * Set published IP address according to given family
+     */
+    void setPublishedAddress(const IpAddr& ip_addr);
+
+    /**
+     * Store the local/public addresses used to register
+     */
+    void storeActiveIpAddress(std::function<void()>&& cb = {});
+
+    /**
+     * Create and return ICE options.
+     */
+    void getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept;
+    IceTransportOptions getIceOptions() const noexcept;
+
+    /**
+     * Inform that a potential peer device have been found.
+     * Returns true only if the device certificate is a valid device certificate.
+     * In that case (true is returned) the account_id parameter is set to the peer account ID.
+     */
+    static bool foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
+                                dht::InfoHash& account_id);
+
+    bool findCertificate(const dht::PkId& id,
+                         std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb);
+    bool findCertificate(
+        const dht::InfoHash& h,
+        std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
+
+    /**
+     * returns whether or not UPnP is enabled and active
+     * ie: if it is able to make port mappings
+     */
+    bool getUPnPActive() const;
+
     /**
      * Triggered when a new TLS socket is ready to use
      * @param ok        If succeed
@@ -175,7 +275,11 @@ public:
                               const dht::Value::Id& vid,
                               const std::string& name = "");
 
-    JamiAccount& account;
+    std::shared_ptr<ConnectionManager::Config> config_;
+
+    mutable std::mt19937_64 rand;
+
+    iOSConnectedCallback iOSConnectedCb_ {};
 
     std::mutex infosMtx_ {};
     // Note: Someone can ask multiple sockets, so to avoid any race condition,
@@ -348,17 +452,16 @@ ConnectionManager::Impl::connectDeviceStartIce(
     value->user_type = "peer_request";
 
     // Send connection request through DHT
-    JAMI_DBG() << account << "Request connection to " << deviceId;
-    account.dht()->putEncrypted(
-        dht::InfoHash::get(PeerConnectionRequest::key_prefix + devicePk->getId().toString()),
-        devicePk,
-        value,
-        [deviceId, accId = account.getAccountID()](bool ok) {
-            JAMI_DEBUG("[Account {:s}] Send connection request to {:s}. Put encrypted {:s}",
-                       accId,
-                       deviceId.toString(),
-                       (ok ? "ok" : "failed"));
-        });
+    JAMI_DBG() << "Request connection to " << deviceId;
+    dht()->putEncrypted(dht::InfoHash::get(PeerConnectionRequest::key_prefix
+                                           + devicePk->getId().toString()),
+                        devicePk,
+                        value,
+                        [deviceId](bool ok) {
+                            JAMI_DEBUG("Sent connection request to {:s}. Put encrypted {:s}",
+                                       deviceId.toString(),
+                                       (ok ? "ok" : "failed"));
+                        });
     // Wait for call to onResponse() operated by DHT
     if (isDestroying_) {
         onConnected(true); // This avoid to wait new negotiation when destroying
@@ -404,7 +507,7 @@ ConnectionManager::Impl::onResponse(const asio::error_code& ec,
     auto sdp = ice->parseIceCandidates(info->response_.ice_msg);
 
     if (not ice->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates))) {
-        JAMI_WARN("[Account:%s] start ICE failed", account.getAccountID().c_str());
+        JAMI_WARN("start ICE failed");
         info->onConnected_(false);
         return;
     }
@@ -440,13 +543,12 @@ ConnectionManager::Impl::connectDeviceOnNegoDone(
                                                         true);
 
     // Negotiate a TLS session
-    JAMI_DBG() << account
-               << "Start TLS session - Initied by connectDevice(). Launched by channel: " << name
+    JAMI_DBG() << "Start TLS session - Initied by connectDevice(). Launched by channel: " << name
                << " - device:" << deviceId << " - vid: " << vid;
     info->tls_ = std::make_unique<TlsSocketEndpoint>(std::move(endpoint),
-                                                     account.certStore(),
-                                                     account.identity(),
-                                                     account.dhParams(),
+                                                     certStore(),
+                                                     identity(),
+                                                     dhParams(),
                                                      *cert);
 
     info->tls_->setOnReady(
@@ -466,37 +568,37 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId,
                                        bool forceNewSocket,
                                        const std::string& connType)
 {
-    if (!account.dht()) {
+    if (!dht()) {
         cb(nullptr, deviceId);
         return;
     }
-    if (deviceId.toString() == account.currentDeviceId()) {
+    if (deviceId.toString() == identity().second->getLongId().toString()) {
         cb(nullptr, deviceId);
         return;
     }
-    account.findCertificate(deviceId,
-                            [w = weak(),
-                             deviceId,
-                             name,
-                             cb = std::move(cb),
-                             noNewSocket,
-                             forceNewSocket,
-                             connType](const std::shared_ptr<dht::crypto::Certificate>& cert) {
-                                if (!cert) {
-                                    JAMI_ERR("No valid certificate found for device %s",
-                                             deviceId.to_c_str());
-                                    cb(nullptr, deviceId);
-                                    return;
-                                }
-                                if (auto shared = w.lock()) {
-                                    shared->connectDevice(cert,
-                                                          name,
-                                                          std::move(cb),
-                                                          noNewSocket,
-                                                          forceNewSocket,
-                                                          connType);
-                                }
-                            });
+    findCertificate(deviceId,
+                    [w = weak(),
+                     deviceId,
+                     name,
+                     cb = std::move(cb),
+                     noNewSocket,
+                     forceNewSocket,
+                     connType](const std::shared_ptr<dht::crypto::Certificate>& cert) {
+                        if (!cert) {
+                            JAMI_ERR("No valid certificate found for device %s",
+                                     deviceId.to_c_str());
+                            cb(nullptr, deviceId);
+                            return;
+                        }
+                        if (auto shared = w.lock()) {
+                            shared->connectDevice(cert,
+                                                  name,
+                                                  std::move(cb),
+                                                  noNewSocket,
+                                                  forceNewSocket,
+                                                  connType);
+                        }
+                    });
 }
 
 void
@@ -522,7 +624,7 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
             cb(nullptr, deviceId);
             return;
         }
-        dht::Value::Id vid = ValueIdDist(1, JAMI_ID_MAX_VAL)(sthis->account.rand);
+        dht::Value::Id vid = ValueIdDist(1, JAMI_ID_MAX_VAL)(sthis->rand);
         auto isConnectingToDevice = false;
         {
             std::lock_guard<std::mutex> lk(sthis->connectCbsMtx_);
@@ -531,7 +633,7 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
                 const auto& pendings = pendingsIt->second;
                 while (pendings.connecting.find(vid) != pendings.connecting.end()
                        && pendings.waiting.find(vid) != pendings.waiting.end()) {
-                    vid = ValueIdDist(1, JAMI_ID_MAX_VAL)(sthis->account.rand);
+                    vid = ValueIdDist(1, JAMI_ID_MAX_VAL)(sthis->rand);
                 }
             }
             // Check if already connecting
@@ -580,14 +682,14 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
         };
 
         // If no socket exists, we need to initiate an ICE connection.
-        sthis->account.getIceOptions([w,
-                                      deviceId = std::move(deviceId),
-                                      devicePk = std::move(devicePk),
-                                      name = std::move(name),
-                                      cert = std::move(cert),
-                                      vid,
-                                      connType,
-                                      eraseInfo](auto&& ice_config) {
+        sthis->getIceOptions([w,
+                              deviceId = std::move(deviceId),
+                              devicePk = std::move(devicePk),
+                              name = std::move(name),
+                              cert = std::move(cert),
+                              vid,
+                              connType,
+                              eraseInfo](auto&& ice_config) {
             auto sthis = w.lock();
             if (!sthis) {
                 dht::ThreadPool::io().run([eraseInfo = std::move(eraseInfo)] { eraseInfo(); });
@@ -623,11 +725,11 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
                 });
             };
             ice_config.onNegoDone = [w,
-                                     deviceId = std::move(deviceId),
-                                     name = std::move(name),
-                                     cert = std::move(cert),
-                                     vid,
-                                     eraseInfo](bool ok) {
+                    deviceId,
+                    name,
+                     cert = std::move(cert),
+                     vid,
+                     eraseInfo](bool ok) {
                 dht::ThreadPool::io().run([w = std::move(w),
                                            deviceId = std::move(deviceId),
                                            name = std::move(name),
@@ -650,10 +752,9 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
             }
             std::unique_lock<std::mutex> lk {info->mutex_};
             ice_config.master = false;
-            ice_config.streamsCount = JamiAccount::ICE_STREAMS_COUNT;
-            ice_config.compCountPerStream = JamiAccount::ICE_COMP_COUNT_PER_STREAM;
-            info->ice_ = Manager::instance().getIceTransportFactory().createUTransport(
-                sthis->account.getAccountID());
+            ice_config.streamsCount = 1;
+            ice_config.compCountPerStream = 1;
+            info->ice_ = Manager::instance().getIceTransportFactory().createUTransport("");
             if (!info->ice_) {
                 JAMI_ERR("Cannot initialize ICE session.");
                 eraseInfo();
@@ -715,7 +816,7 @@ void
 ConnectionManager::Impl::onPeerResponse(const PeerConnectionRequest& req)
 {
     auto device = req.owner->getLongId();
-    JAMI_INFO() << account << " New response received from " << device.to_c_str();
+    JAMI_INFO() << "New response received from " << device.to_c_str();
     if (auto info = getInfo(device, req.id)) {
         std::lock_guard<std::mutex> lk {info->mutex_};
         info->responseReceived_ = true;
@@ -727,26 +828,24 @@ ConnectionManager::Impl::onPeerResponse(const PeerConnectionRequest& req)
                                                    device,
                                                    req.id));
     } else {
-        JAMI_WARN() << account << " respond received, but cannot find request";
+        JAMI_WARN() << "Respond received, but cannot find request";
     }
 }
 
 void
 ConnectionManager::Impl::onDhtConnected(const dht::crypto::PublicKey& devicePk)
 {
-    if (!account.dht()) {
+    if (!dht())
         return;
-    }
-    account.dht()->listen<PeerConnectionRequest>(
+    dht()->listen<PeerConnectionRequest>(
         dht::InfoHash::get(PeerConnectionRequest::key_prefix + devicePk.getId().toString()),
         [w = weak()](PeerConnectionRequest&& req) {
             auto shared = w.lock();
             if (!shared)
                 return false;
-            if (shared->account.isMessageTreated(to_hex_string(req.id))) {
-                // Message already treated. Just ignore
+            // Message already treated. Just ignore
+            if (shared->isMessageTreated(to_hex_string(req.id)))
                 return true;
-            }
             if (req.isAnswer) {
                 JAMI_DBG() << "Received request answer from " << req.owner->getLongId();
             } else {
@@ -756,7 +855,7 @@ ConnectionManager::Impl::onDhtConnected(const dht::crypto::PublicKey& devicePk)
                 shared->onPeerResponse(req);
             } else {
                 // Async certificate checking
-                shared->account.findCertificate(
+                shared->findCertificate(
                     req.from,
                     [w, req = std::move(req)](
                         const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
@@ -764,21 +863,15 @@ ConnectionManager::Impl::onDhtConnected(const dht::crypto::PublicKey& devicePk)
                         if (!shared)
                             return;
                         dht::InfoHash peer_h;
-                        if (AccountManager::foundPeerDevice(cert, peer_h)) {
+                        if (foundPeerDevice(cert, peer_h)) {
 #if TARGET_OS_IOS
-                            if ((req.connType == "videoCall" || req.connType == "audioCall")
-                                && jami::Manager::instance().isIOSExtension) {
-                                bool hasVideo = req.connType == "videoCall";
-                                emitSignal<libjami::ConversationSignal::CallConnectionRequest>(
-                                    shared->account.getAccountID(), peer_h.toString(), hasVideo);
+                            if (shared->iOSConnectedCb_(req.connType, peer_h))
                                 return;
-                            }
 #endif
                             shared->onDhtPeerRequest(req, cert);
                         } else {
-                            JAMI_WARN()
-                                << shared->account << "Rejected untrusted connection request from "
-                                << req.owner->getLongId();
+                            JAMI_WARN() << "Rejected untrusted connection request from "
+                                        << req.owner->getLongId();
                         }
                     });
             }
@@ -860,17 +953,16 @@ ConnectionManager::Impl::answerTo(IceTransport& ice,
     auto value = std::make_shared<dht::Value>(std::move(val));
     value->user_type = "peer_request";
 
-    JAMI_DBG() << account << "[CNX] connection accepted, DHT reply to " << from->getLongId();
-    account.dht()->putEncrypted(
-        dht::InfoHash::get(PeerConnectionRequest::key_prefix + from->getId().toString()),
-        from,
-        value,
-        [from, accId = account.getAccountID()](bool ok) {
-            JAMI_DEBUG("[Account {:s}] Answer to connection request from {:s}. Put encrypted {:s}",
-                       accId,
-                       from->getLongId().toString(),
-                       (ok ? "ok" : "failed"));
-        });
+    JAMI_DBG() << "[CNX] connection accepted, DHT reply to " << from->getLongId();
+    dht()->putEncrypted(dht::InfoHash::get(PeerConnectionRequest::key_prefix
+                                           + from->getId().toString()),
+                        from,
+                        value,
+                        [from](bool ok) {
+                            JAMI_DEBUG("Answer to connection request from {:s}. Put encrypted {:s}",
+                                       from->getLongId().toString(),
+                                       (ok ? "ok" : "failed"));
+                        });
 }
 
 bool
@@ -893,7 +985,7 @@ ConnectionManager::Impl::onRequestStartIce(const PeerConnectionRequest& req)
     auto sdp = ice->parseIceCandidates(req.ice_msg);
     answerTo(*ice, req.id, req.owner);
     if (not ice->startIce({sdp.rem_ufrag, sdp.rem_pwd}, std::move(sdp.rem_candidates))) {
-        JAMI_ERR("[Account:%s] start ICE failed - fallback to TURN", account.getAccountID().c_str());
+        JAMI_ERR("Start ICE failed - fallback to TURN");
         ice = nullptr;
         if (connReadyCb_)
             connReadyCb_(deviceId, "", nullptr);
@@ -924,18 +1016,18 @@ ConnectionManager::Impl::onRequestOnNegoDone(const PeerConnectionRequest& req)
 
     // init TLS session
     auto ph = req.from;
-    JAMI_DBG() << account << "Start TLS session - Initied by DHT request. Device:" << req.from
+    JAMI_DBG() << "Start TLS session - Initied by DHT request. Device:" << req.from
                << " - vid: " << req.id;
     info->tls_ = std::make_unique<TlsSocketEndpoint>(
         std::move(endpoint),
-        account.certStore(),
-        account.identity(),
-        account.dhParams(),
+        certStore(),
+        identity(),
+        dhParams(),
         [ph, w = weak()](const dht::crypto::Certificate& cert) {
             auto shared = w.lock();
             if (!shared)
                 return false;
-            auto crt = shared->account.certStore().getCertificate(cert.getLongId().toString());
+            auto crt = shared->certStore().getCertificate(cert.getLongId().toString());
             if (!crt)
                 return false;
             return crt->getPacked() == cert.getPacked();
@@ -954,16 +1046,14 @@ ConnectionManager::Impl::onDhtPeerRequest(const PeerConnectionRequest& req,
                                           const std::shared_ptr<dht::crypto::Certificate>& /*cert*/)
 {
     auto deviceId = req.owner->getLongId();
-    JAMI_INFO() << account << "New connection requested by " << deviceId;
+    JAMI_INFO() << "New connection requested by " << deviceId;
     if (!iceReqCb_ || !iceReqCb_(deviceId)) {
-        JAMI_INFO("[Account:%s] refuse connection from %s",
-                  account.getAccountID().c_str(),
-                  deviceId.toString().c_str());
+        JAMI_INFO("Refuse connection from %s", deviceId.toString().c_str());
         return;
     }
 
     // Because the connection is accepted, create an ICE socket.
-    account.getIceOptions([w = weak(), req, deviceId](auto&& ice_config) {
+    getIceOptions([w = weak(), req, deviceId](auto&& ice_config) {
         auto shared = w.lock();
         if (!shared)
             return;
@@ -1025,15 +1115,12 @@ ConnectionManager::Impl::onDhtPeerRequest(const PeerConnectionRequest& req,
             std::lock_guard<std::mutex> lk(shared->infosMtx_);
             shared->infos_[{deviceId, req.id}] = info;
         }
-        JAMI_INFO("[Account:%s] accepting connection from %s",
-                  shared->account.getAccountID().c_str(),
-                  deviceId.toString().c_str());
+        JAMI_INFO("accepting connection from %s", deviceId.toString().c_str());
         std::unique_lock<std::mutex> lk {info->mutex_};
-        ice_config.streamsCount = JamiAccount::ICE_STREAMS_COUNT;
-        ice_config.compCountPerStream = JamiAccount::ICE_COMP_COUNT_PER_STREAM;
+        ice_config.streamsCount = 1;
+        ice_config.compCountPerStream = 1; // TCP
         ice_config.master = true;
-        info->ice_ = Manager::instance().getIceTransportFactory().createUTransport(
-            shared->account.getAccountID());
+        info->ice_ = Manager::instance().getIceTransportFactory().createUTransport("");
         if (not info->ice_) {
             JAMI_ERR("Cannot initialize ICE session.");
             eraseInfo();
@@ -1094,8 +1181,296 @@ ConnectionManager::Impl::addNewMultiplexedSocket(const CallbackId& id, const std
     });
 }
 
-ConnectionManager::ConnectionManager(JamiAccount& account)
-    : pimpl_ {std::make_shared<Impl>(account)}
+const std::shared_future<tls::DhParams>
+ConnectionManager::Impl::dhParams() const
+{
+    return dht::ThreadPool::computation().get<tls::DhParams>(
+        std::bind(tls::DhParams::loadDhParams, fileutils::get_cache_dir() + DIR_SEPARATOR_STR "dhParams"));
+    ;
+}
+
+template<typename ID = dht::Value::Id>
+std::set<ID, std::less<>>
+loadIdList(const std::string& path)
+{
+    std::set<ID, std::less<>> ids;
+    std::ifstream file = fileutils::ifstream(path);
+    if (!file.is_open()) {
+        JAMI_DBG("Could not load %s", path.c_str());
+        return ids;
+    }
+    std::string line;
+    while (std::getline(file, line)) {
+        if constexpr (std::is_same<ID, std::string>::value) {
+            ids.emplace(std::move(line));
+        } else if constexpr (std::is_integral<ID>::value) {
+            ID vid;
+            if (auto [p, ec] = std::from_chars(line.data(), line.data() + line.size(), vid, 16);
+                ec == std::errc()) {
+                ids.emplace(vid);
+            }
+        }
+    }
+    return ids;
+}
+
+template<typename List = std::set<dht::Value::Id>>
+void
+saveIdList(const std::string& path, const List& ids)
+{
+    std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary);
+    if (!file.is_open()) {
+        JAMI_ERR("Could not save to %s", path.c_str());
+        return;
+    }
+    for (auto& c : ids)
+        file << std::hex << c << "\n";
+}
+
+void
+ConnectionManager::Impl::loadTreatedMessages()
+{
+    std::lock_guard<std::mutex> lock(messageMutex_);
+    auto path = config_->cachePath + DIR_SEPARATOR_STR "treatedMessages";
+    treatedMessages_ = loadIdList<std::string>(path);
+    if (treatedMessages_.empty()) {
+        auto messages = loadIdList(path);
+        for (const auto& m : messages)
+            treatedMessages_.emplace(to_hex_string(m));
+    }
+}
+
+void
+ConnectionManager::Impl::saveTreatedMessages() const
+{
+    dht::ThreadPool::io().run([w = weak()]() {
+        if (auto sthis = w.lock()) {
+            auto& this_ = *sthis;
+            std::lock_guard<std::mutex> lock(this_.messageMutex_);
+            fileutils::check_dir(this_.config_->cachePath.c_str());
+            saveIdList<decltype(this_.treatedMessages_)>(this_.config_->cachePath
+                                                             + DIR_SEPARATOR_STR "treatedMessages",
+                                                         this_.treatedMessages_);
+        }
+    });
+}
+
+bool
+ConnectionManager::Impl::isMessageTreated(std::string_view id)
+{
+    std::lock_guard<std::mutex> lock(messageMutex_);
+    auto res = treatedMessages_.emplace(id);
+    if (res.second) {
+        saveTreatedMessages();
+        return false;
+    }
+    return true;
+}
+
+/**
+ * returns whether or not UPnP is enabled and active_
+ * ie: if it is able to make port mappings
+ */
+bool
+ConnectionManager::Impl::getUPnPActive() const
+{
+    return config_->getUPnPActive();
+}
+
+IpAddr
+ConnectionManager::Impl::getPublishedIpAddress(uint16_t family) const
+{
+    if (family == AF_INET)
+        return publishedIp_[0];
+    if (family == AF_INET6)
+        return publishedIp_[1];
+
+    assert(family == AF_UNSPEC);
+
+    // If family is not set, prefere IPv4 if available. It's more
+    // likely to succeed behind NAT.
+    if (publishedIp_[0])
+        return publishedIp_[0];
+    if (publishedIp_[1])
+        return publishedIp_[1];
+    return {};
+}
+
+void
+ConnectionManager::Impl::setPublishedAddress(const IpAddr& ip_addr)
+{
+    if (ip_addr.getFamily() == AF_INET) {
+        publishedIp_[0] = ip_addr;
+    } else {
+        publishedIp_[1] = ip_addr;
+    }
+}
+
+void
+ConnectionManager::Impl::storeActiveIpAddress(std::function<void()>&& cb)
+{
+    dht()->getPublicAddress([this, cb = std::move(cb)](std::vector<dht::SockAddr>&& results) {
+        bool hasIpv4 {false}, hasIpv6 {false};
+        for (auto& result : results) {
+            auto family = result.getFamily();
+            if (family == AF_INET) {
+                if (not hasIpv4) {
+                    hasIpv4 = true;
+                    JAMI_DBG("Store DHT public IPv4 address : %s", result.toString().c_str());
+                    setPublishedAddress(*result.get());
+                    if (config_->upnpCtrl_) {
+                        config_->upnpCtrl_->setPublicAddress(*result.get());
+                    }
+                }
+            } else if (family == AF_INET6) {
+                if (not hasIpv6) {
+                    hasIpv6 = true;
+                    JAMI_DBG("Store DHT public IPv6 address : %s", result.toString().c_str());
+                    setPublishedAddress(*result.get());
+                }
+            }
+            if (hasIpv4 and hasIpv6)
+                break;
+        }
+        if (cb)
+            cb();
+    });
+}
+
+void
+ConnectionManager::Impl::getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept
+{
+    storeActiveIpAddress([this, cb = std::move(cb)] {
+        IceTransportOptions opts = ConnectionManager::Impl::getIceOptions();
+        auto publishedAddr = getPublishedIpAddress();
+
+        if (publishedAddr) {
+            auto interfaceAddr = ip_utils::getInterfaceAddr(getLocalInterface(),
+                                                            publishedAddr.getFamily());
+            if (interfaceAddr) {
+                opts.accountLocalAddr = interfaceAddr;
+                opts.accountPublicAddr = publishedAddr;
+            }
+        }
+        if (cb)
+            cb(std::move(opts));
+    });
+}
+
+IceTransportOptions
+ConnectionManager::Impl::getIceOptions() const noexcept
+{
+    IceTransportOptions opts;
+    opts.upnpEnable = getUPnPActive();
+
+    if (config_->stunEnabled_)
+        opts.stunServers.emplace_back(StunServerInfo().setUri(config_->stunServer_));
+    if (config_->turnEnabled_) {
+        auto cached = false;
+        std::lock_guard<std::mutex> lk(config_->cachedTurnMutex_);
+        cached = config_->cacheTurnV4_ || config_->cacheTurnV6_;
+        if (config_->cacheTurnV4_ && *config_->cacheTurnV4_) {
+            opts.turnServers.emplace_back(TurnServerInfo()
+                                              .setUri(config_->cacheTurnV4_->toString(true))
+                                              .setUsername(config_->turnServerUserName_)
+                                              .setPassword(config_->turnServerPwd_)
+                                              .setRealm(config_->turnServerRealm_));
+        }
+        // NOTE: first test with ipv6 turn was not concluant and resulted in multiple
+        // co issues. So this needs some debug. for now just disable
+        // if (cacheTurnV6_ && *cacheTurnV6_) {
+        //    opts.turnServers.emplace_back(TurnServerInfo()
+        //                                      .setUri(cacheTurnV6_->toString(true))
+        //                                      .setUsername(turnServerUserName_)
+        //                                      .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;
+}
+
+bool
+ConnectionManager::Impl::foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
+                                         dht::InfoHash& account_id)
+{
+    if (not crt)
+        return false;
+
+    auto top_issuer = crt;
+    while (top_issuer->issuer)
+        top_issuer = top_issuer->issuer;
+
+    // Device certificate can't be self-signed
+    if (top_issuer == crt) {
+        JAMI_WARN("Found invalid peer device: %s", crt->getLongId().toString().c_str());
+        return false;
+    }
+
+    // Check peer certificate chain
+    // Trust store with top issuer as the only CA
+    dht::crypto::TrustList peer_trust;
+    peer_trust.add(*top_issuer);
+    if (not peer_trust.verify(*crt)) {
+        JAMI_WARN("Found invalid peer device: %s", crt->getLongId().toString().c_str());
+        return false;
+    }
+
+    // Check cached OCSP response
+    if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD) {
+        JAMI_ERR("Certificate %s is disabled by cached OCSP response", crt->getLongId().to_c_str());
+        return false;
+    }
+
+    account_id = crt->issuer->getId();
+    JAMI_WARN("Found peer device: %s account:%s CA:%s",
+              crt->getLongId().toString().c_str(),
+              account_id.toString().c_str(),
+              top_issuer->getId().toString().c_str());
+    return true;
+}
+
+bool
+ConnectionManager::Impl::findCertificate(
+    const dht::PkId& id, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
+{
+    if (auto cert = certStore().getCertificate(id.toString())) {
+        if (cb)
+            cb(cert);
+    } else if (cb)
+        cb(nullptr);
+    return true;
+}
+
+bool
+ConnectionManager::Impl::findCertificate(const dht::InfoHash& h,
+    std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
+{
+    if (auto cert = certStore().getCertificate(h.toString())) {
+        if (cb)
+            cb(cert);
+    } else {
+        dht()->findCertificate(h,
+                              [cb = std::move(cb), this](
+                                  const std::shared_ptr<dht::crypto::Certificate>& crt) {
+                                  if (crt)
+                                      certStore().pinCertificate(crt);
+                                  if (cb)
+                                      cb(crt);
+                              });
+    }
+    return true;
+}
+
+ConnectionManager::ConnectionManager(std::shared_ptr<ConnectionManager::Config> config_)
+    : pimpl_ {std::make_shared<Impl>(config_)}
 {}
 
 ConnectionManager::~ConnectionManager()
@@ -1144,7 +1519,7 @@ ConnectionManager::closeConnectionsWith(const std::string& peerUri)
         for (auto iter = pimpl_->infos_.begin(); iter != pimpl_->infos_.end();) {
             auto const& [key, value] = *iter;
             auto deviceId = key.first;
-            auto cert = pimpl_->account.certStore().getCertificate(deviceId.toString());
+            auto cert = pimpl_->certStore().getCertificate(deviceId.toString());
             if (cert && cert->issuer && peerUri == cert->issuer->getId().toString()) {
                 connInfos.emplace_back(value);
                 peersDevices.emplace(deviceId);
@@ -1197,6 +1572,12 @@ ConnectionManager::onConnectionReady(ConnectionReadyCallback&& cb)
     pimpl_->connReadyCb_ = std::move(cb);
 }
 
+void
+ConnectionManager::oniOSConnected(iOSConnectedCallback&& cb)
+{
+    pimpl_->iOSConnectedCb_ = std::move(cb);
+}
+
 std::size_t
 ConnectionManager::activeSockets() const
 {
@@ -1208,16 +1589,12 @@ void
 ConnectionManager::monitor() const
 {
     std::lock_guard<std::mutex> lk(pimpl_->infosMtx_);
-    JAMI_DBG("ConnectionManager for account %s (%s), current status:",
-             pimpl_->account.getAccountID().c_str(),
-             pimpl_->account.getUserUri().c_str());
+    JAMI_DBG("ConnectionManager current status:");
     for (const auto& [_, ci] : pimpl_->infos_) {
         if (ci->socket_)
             ci->socket_->monitor();
     }
-    JAMI_DBG("ConnectionManager for account %s (%s), end status.",
-             pimpl_->account.getAccountID().c_str(),
-             pimpl_->account.getUserUri().c_str());
+    JAMI_DBG("ConnectionManager end status.");
 }
 
 void
@@ -1230,4 +1607,55 @@ ConnectionManager::connectivityChanged()
     }
 }
 
+void
+ConnectionManager::getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept
+{
+    pimpl_->getIceOptions(std::move(cb));
+}
+
+IceTransportOptions
+ConnectionManager::getIceOptions() const noexcept
+{
+    return pimpl_->getIceOptions();
+}
+
+IpAddr
+ConnectionManager::getPublishedIpAddress(uint16_t family) const
+{
+    return pimpl_->getPublishedIpAddress(family);
+}
+
+void
+ConnectionManager::setPublishedAddress(const IpAddr& ip_addr)
+{
+    pimpl_->setPublishedAddress(ip_addr);
+}
+
+void
+ConnectionManager::storeActiveIpAddress(std::function<void()>&& cb)
+{
+    pimpl_->storeActiveIpAddress(std::move(cb));
+}
+
+std::shared_ptr<ConnectionManager::Config>
+ConnectionManager::getConfig()
+{
+    return pimpl_->config_;
+}
+
+bool
+ConnectionManager::findCertificate(const dht::PkId& id,
+                        std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
+{
+    return pimpl_->findCertificate(id, std::move(cb));
+}
+
+bool
+ConnectionManager::findCertificate(
+    const dht::InfoHash& h,
+    std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
+{
+    return pimpl_->findCertificate(h, std::move(cb));
+}
+
 } // namespace jami
diff --git a/src/connectivity/connectionmanager.h b/src/connectivity/connectionmanager.h
index b81b8390af..79f9fb3d22 100644
--- a/src/connectivity/connectionmanager.h
+++ b/src/connectivity/connectionmanager.h
@@ -27,10 +27,11 @@
 #include <opendht/default_types.h>
 
 #include "multiplexed_socket.h"
+#include "connectivity/security/diffie-hellman.h"
+#include "connectivity/upnp/upnp_control.h"
 
 namespace jami {
 
-class JamiAccount;
 class ChannelSocket;
 class ConnectionManager;
 
@@ -69,6 +70,9 @@ using ConnectCallback = std::function<void(const std::shared_ptr<ChannelSocket>&
 using ConnectionReadyCallback = std::function<
     void(const DeviceId&, const std::string& /* channel_name */, std::shared_ptr<ChannelSocket>)>;
 
+using iOSConnectedCallback
+    = std::function<bool(const std::string& /* connType */, dht::InfoHash /* peer_h */)>;
+
 /**
  * Manages connections to other devices
  * @note the account MUST be valid if ConnectionManager lives
@@ -76,7 +80,9 @@ using ConnectionReadyCallback = std::function<
 class ConnectionManager
 {
 public:
-    ConnectionManager(JamiAccount& account);
+    class Config;
+
+    ConnectionManager(std::shared_ptr<Config> config_);
     ~ConnectionManager();
 
     /**
@@ -145,6 +151,12 @@ public:
      */
     void onConnectionReady(ConnectionReadyCallback&& cb);
 
+    /**
+     * Trigger cb when connection with peer is ready for iOS devices
+     * @param cb    Callback to trigger
+     */
+    void oniOSConnected(iOSConnectedCallback&& cb);
+
     /**
      * @return the number of active sockets
      */
@@ -160,10 +172,119 @@ public:
      */
     void connectivityChanged();
 
+    /**
+     * Create and return ICE options.
+     */
+    void getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept;
+    IceTransportOptions getIceOptions() const noexcept;
+
+    /**
+     * Get the published IP address, fallbacks to NAT if family is unspecified
+     * Prefers the usage of IPv4 if possible.
+     */
+    IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const;
+
+    /**
+     * Set published IP address according to given family
+     */
+    void setPublishedAddress(const IpAddr& ip_addr);
+
+    /**
+     * Store the local/public addresses used to register
+     */
+    void storeActiveIpAddress(std::function<void()>&& cb = {});
+
+    std::shared_ptr<Config> getConfig();
+
+    bool findCertificate(const dht::PkId& id,
+                         std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb);
+    bool findCertificate(
+        const dht::InfoHash& h,
+        std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
+
 private:
     ConnectionManager() = delete;
     class Impl;
     std::shared_ptr<Impl> pimpl_;
 };
 
+class ConnectionManager::Config : public std::enable_shared_from_this<ConnectionManager::Config>
+{
+public:
+    explicit Config(std::shared_ptr<dht::DhtRunner> dht_,
+                    const dht::crypto::Identity& id_,
+                    tls::CertificateStore& certStore_,
+                    std::shared_ptr<jami::upnp::Controller> upnpCtrl_,
+                    std::string turnServer_,
+                    std::string turnServerUserName_,
+                    std::string turnServerPwd_,
+                    std::string turnServerRealm_,
+                    bool turnEnabled_)
+        : dht_ {std::move(dht_)}
+        , id_ {id_}
+        , upnpCtrl_ {std::move(upnpCtrl_)}
+        , turnServer_ {std::move(turnServer_)}
+        , turnServerUserName_ {std::move(turnServerUserName_)}
+        , turnServerPwd_ {std::move(turnServerPwd_)}
+        , turnServerRealm_ {std::move(turnServerRealm_)}
+        , turnEnabled_ {turnEnabled_}
+        , certStore_(&certStore_)
+    {}
+
+    ~Config() {}
+
+    /**
+     * Determine if STUN public address resolution is required to register this account. In this
+     * case a STUN server hostname must be specified.
+     */
+    bool stunEnabled_ {false};
+
+    /**
+     * The STUN server hostname (optional), used to provide the public IP address in case the
+     * softphone stay behind a NAT.
+     */
+    std::string stunServer_ {};
+
+    /**
+     * Determine if TURN public address resolution is required to register this account. In this
+     * case a TURN server hostname must be specified.
+     */
+    bool turnEnabled_ {false};
+
+    /**
+     * The TURN server hostname (optional), used to provide the public IP address in case the
+     * softphone stay behind a NAT.
+     */
+    std::string turnServer_;
+    std::string turnServerUserName_;
+    std::string turnServerPwd_;
+    std::string turnServerRealm_;
+
+    mutable std::mutex cachedTurnMutex_ {};
+    std::unique_ptr<IpAddr> cacheTurnV4_ {};
+    std::unique_ptr<IpAddr> cacheTurnV6_ {};
+
+    std::string cachePath {};
+
+    std::shared_ptr<dht::DhtRunner> dht_;
+    const dht::crypto::Identity& id_;
+
+    const dht::crypto::Identity& identity() const { return id_; }
+
+    tls::CertificateStore* certStore_;
+
+    /**
+     * UPnP IGD controller and the mutex to access it
+     */
+    bool upnpEnabled_;
+    mutable std::mutex upnp_mtx {};
+    std::shared_ptr<jami::upnp::Controller> upnpCtrl_;
+
+    /**
+     * returns whether or not UPnP is enabled and active
+     * ie: if it is able to make port mappings
+     */
+    bool getUPnPActive() const;
+};
+
 } // namespace jami
\ No newline at end of file
diff --git a/src/connectivity/security/diffie-hellman.cpp b/src/connectivity/security/diffie-hellman.cpp
index 47f5f123c6..bc0a854ec7 100644
--- a/src/connectivity/security/diffie-hellman.cpp
+++ b/src/connectivity/security/diffie-hellman.cpp
@@ -20,6 +20,7 @@
 
 #include "diffie-hellman.h"
 #include "logger.h"
+#include "fileutils.h"
 
 #include <chrono>
 #include <ciso646>
@@ -106,5 +107,33 @@ DhParams::generate()
     return params;
 }
 
+DhParams
+DhParams::loadDhParams(const std::string& path)
+{
+    std::lock_guard<std::mutex> l(fileutils::getFileLock(path));
+    try {
+        // writeTime throw exception if file doesn't exist
+        auto duration = std::chrono::system_clock::now() - fileutils::writeTime(path);
+        if (duration >= std::chrono::hours(24 * 3)) // file is valid only 3 days
+            throw std::runtime_error("file too old");
+
+        JAMI_DBG("Loading DhParams from file '%s'", path.c_str());
+        return {fileutils::loadFile(path)};
+    } catch (const std::exception& e) {
+        JAMI_DBG("Failed to load DhParams file '%s': %s", path.c_str(), e.what());
+        if (auto params = tls::DhParams::generate()) {
+            try {
+                fileutils::saveFile(path, params.serialize(), 0600);
+                JAMI_DBG("Saved DhParams to file '%s'", path.c_str());
+            } catch (const std::exception& ex) {
+                JAMI_WARN("Failed to save DhParams in file '%s': %s", path.c_str(), ex.what());
+            }
+            return params;
+        }
+        JAMI_ERR("Can't generate DH params.");
+        return {};
+    }
+}
+
 } // namespace tls
 } // namespace jami
diff --git a/src/connectivity/security/diffie-hellman.h b/src/connectivity/security/diffie-hellman.h
index 9e8f487c2f..c38bce579c 100644
--- a/src/connectivity/security/diffie-hellman.h
+++ b/src/connectivity/security/diffie-hellman.h
@@ -25,6 +25,7 @@
 #include <vector>
 #include <memory>
 #include <cstdint>
+#include <string>
 
 namespace jami {
 namespace tls {
@@ -60,6 +61,8 @@ public:
 
     static DhParams generate();
 
+    static DhParams loadDhParams(const std::string& path);
+
 private:
     std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)*>
         params_ {nullptr, gnutls_dh_params_deinit};
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index b62cc26536..350c8f84e8 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -381,7 +381,7 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::
         return {};
 
     auto uri = Uri(toUrl);
-    getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
+    connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
         if (call->isIceEnabled()) {
             if (not call->createIceMediaTransport(false)
                 or not call->initIceMediaTransport(true, std::forward<IceTransportOptions>(opts))) {
@@ -752,8 +752,9 @@ JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
 
     const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
 
-    IpAddr addrSdp = getPublishedSameasLocal() ? localAddress
-                                               : getPublishedIpAddress(target.getFamily());
+    IpAddr addrSdp = getPublishedSameasLocal()
+                         ? localAddress
+                         : connectionManager_->getPublishedIpAddress(target.getFamily());
 
     // fallback on local address
     if (not addrSdp)
@@ -1928,7 +1929,7 @@ JamiAccount::doRegister_()
         connectionManager_->onICERequest([this](const DeviceId& deviceId) {
             std::promise<bool> accept;
             std::future<bool> fut = accept.get_future();
-            accountManager_->findCertificate(
+            connectionManager_->findCertificate(
                 deviceId, [this, &accept](const std::shared_ptr<dht::crypto::Certificate>& cert) {
                     dht::InfoHash peer_account_id;
                     auto res = accountManager_->onPeerCertificate(cert,
@@ -2336,7 +2337,8 @@ JamiAccount::setRegistrationState(RegistrationState state,
         if (state == RegistrationState::REGISTERED) {
             JAMI_WARNING("[Account {}] connected", getAccountID());
             turnCache_->refresh();
-            storeActiveIpAddress();
+            if (connectionManager_)
+                connectionManager_->storeActiveIpAddress();
         } else if (state == RegistrationState::TRYING) {
             JAMI_WARNING("[Account {}] connecting…", getAccountID());
         } else {
@@ -2362,11 +2364,12 @@ JamiAccount::connectivityChanged()
     dht_->connectivityChanged();
     {
         std::lock_guard<std::mutex> lkCM(connManagerMtx_);
-        if (connectionManager_)
+        if (connectionManager_) {
             connectionManager_->connectivityChanged();
+            // reset cache
+            connectionManager_->setPublishedAddress({});
+        }
     }
-    // reset cache
-    setPublishedAddress({});
 }
 
 bool
@@ -2374,8 +2377,8 @@ JamiAccount::findCertificate(
     const dht::InfoHash& h,
     std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
 {
-    if (accountManager_)
-        return accountManager_->findCertificate(h, std::move(cb));
+    if (connectionManager_)
+        return connectionManager_->findCertificate(h, std::move(cb));
     return false;
 }
 
@@ -2383,16 +2386,16 @@ bool
 JamiAccount::findCertificate(
     const dht::PkId& id, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
 {
-    if (accountManager_)
-        return accountManager_->findCertificate(id, std::move(cb));
+    if (connectionManager_)
+        return connectionManager_->findCertificate(id, std::move(cb));
     return false;
 }
 
 bool
 JamiAccount::findCertificate(const std::string& crt_id)
 {
-    if (accountManager_)
-        return accountManager_->findCertificate(dht::InfoHash(crt_id));
+    if (connectionManager_)
+        return connectionManager_->findCertificate(dht::InfoHash(crt_id));
     return false;
 }
 
@@ -2525,34 +2528,6 @@ JamiAccount::getKnownDevices() const
     return ids;
 }
 
-tls::DhParams
-JamiAccount::loadDhParams(std::string path)
-{
-    std::lock_guard<std::mutex> l(fileutils::getFileLock(path));
-    try {
-        // writeTime throw exception if file doesn't exist
-        auto duration = clock::now() - fileutils::writeTime(path);
-        if (duration >= std::chrono::hours(24 * 3)) // file is valid only 3 days
-            throw std::runtime_error("file too old");
-
-        JAMI_DBG("Loading DhParams from file '%s'", path.c_str());
-        return {fileutils::loadFile(path)};
-    } catch (const std::exception& e) {
-        JAMI_DBG("Failed to load DhParams file '%s': %s", path.c_str(), e.what());
-        if (auto params = tls::DhParams::generate()) {
-            try {
-                fileutils::saveFile(path, params.serialize(), 0600);
-                JAMI_DBG("Saved DhParams to file '%s'", path.c_str());
-            } catch (const std::exception& ex) {
-                JAMI_WARN("Failed to save DhParams in file '%s': %s", path.c_str(), ex.what());
-            }
-            return params;
-        }
-        JAMI_ERR("Can't generate DH params.");
-        return {};
-    }
-}
-
 void
 JamiAccount::loadCachedUrl(const std::string& url,
                            const std::string& cachePath,
@@ -2687,7 +2662,7 @@ JamiAccount::generateDhParams()
     // make sure cachePath_ is writable
     fileutils::check_dir(cachePath_.c_str(), 0700);
     dhParams_ = dht::ThreadPool::computation().get<tls::DhParams>(
-        std::bind(loadDhParams, cachePath_ + DIR_SEPARATOR_STR "dhParams"));
+        std::bind(tls::DhParams::loadDhParams, cachePath_ + DIR_SEPARATOR_STR "dhParams"));
 }
 
 MatchRank
@@ -3332,59 +3307,10 @@ JamiAccount::onIsComposing(const std::string& conversationId,
     }
 }
 
-void
-JamiAccount::getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept
-{
-    storeActiveIpAddress([this, cb = std::move(cb)] {
-        auto opts = SIPAccountBase::getIceOptions();
-        auto publishedAddr = getPublishedIpAddress();
-
-        if (publishedAddr) {
-            auto interfaceAddr = ip_utils::getInterfaceAddr(getLocalInterface(),
-                                                            publishedAddr.getFamily());
-            if (interfaceAddr) {
-                opts.accountLocalAddr = interfaceAddr;
-                opts.accountPublicAddr = publishedAddr;
-            }
-        }
-        if (cb)
-            cb(std::move(opts));
-    });
-}
-
-void
-JamiAccount::storeActiveIpAddress(std::function<void()>&& cb)
-{
-    dht_->getPublicAddress([this, cb = std::move(cb)](std::vector<dht::SockAddr>&& results) {
-        bool hasIpv4 {false}, hasIpv6 {false};
-        for (auto& result : results) {
-            auto family = result.getFamily();
-            if (family == AF_INET) {
-                if (not hasIpv4) {
-                    hasIpv4 = true;
-                    JAMI_DBG("[Account %s] Store DHT public IPv4 address : %s",
-                             getAccountID().c_str(),
-                             result.toString().c_str());
-                    setPublishedAddress(*result.get());
-                    if (upnpCtrl_) {
-                        upnpCtrl_->setPublicAddress(*result.get());
-                    }
-                }
-            } else if (family == AF_INET6) {
-                if (not hasIpv6) {
-                    hasIpv6 = true;
-                    JAMI_DBG("[Account %s] Store DHT public IPv6 address : %s",
-                             getAccountID().c_str(),
-                             result.toString().c_str());
-                    setPublishedAddress(*result.get());
-                }
-            }
-            if (hasIpv4 and hasIpv6)
-                break;
-        }
-        if (cb)
-            cb();
-    });
+IceTransportOptions
+JamiAccount::getIceOptions() const noexcept
+{
+    return connectionManager_->getIceOptions();
 }
 
 bool
@@ -4255,15 +4181,38 @@ void
 JamiAccount::initConnectionManager()
 {
     if (!connectionManager_) {
-        connectionManager_ = std::make_unique<ConnectionManager>(*this);
-        channelHandlers_[Uri::Scheme::SWARM]
-            = std::make_unique<SwarmChannelHandler>(shared(), *connectionManager_.get());
+        auto connectionManagerConfig
+            = std::make_shared<ConnectionManager::Config>(dht(),
+                                                          identity(),
+                                                          certStore(),
+                                                          upnpCtrl_,
+                                                          config().turnServer,
+                                                          config().turnServerUserName,
+                                                          config().turnServerPwd,
+                                                          config().turnServerRealm,
+                                                          config().turnEnabled);
+        connectionManagerConfig->cachePath = cachePath_;
+        connectionManager_ = std::make_unique<ConnectionManager>(connectionManagerConfig);
         channelHandlers_[Uri::Scheme::GIT]
             = std::make_unique<ConversationChannelHandler>(shared(), *connectionManager_.get());
         channelHandlers_[Uri::Scheme::SYNC]
             = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
         channelHandlers_[Uri::Scheme::DATA_TRANSFER]
             = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
+
+#if TARGET_OS_IOS
+        connectionManager_->oniOSConnected([&](const std::string& connType, dht::InfoHash peer_h) {
+            if ((connType == "videoCall" || connType == "audioCall")
+                && jami::Manager::instance().isIOSExtension) {
+                bool hasVideo = connType == "videoCall";
+                emitSignal<libjami::ConversationSignal::CallConnectionRequest>("",
+                                                                               peer_h.toString(),
+                                                                               hasVideo);
+                return true;
+            }
+            return false;
+        });
+#endif
     }
 }
 
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 18fcdcf509..93dbf72744 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -329,6 +329,11 @@ public:
                             const std::map<std::string, std::string>& msg);
     void onIsComposing(const std::string& conversationId, const std::string& peer, bool isWriting);
 
+    /**
+     * Create and return ICE options.
+     */
+    IceTransportOptions getIceOptions() const noexcept;
+
     /* Devices */
     void addDevice(const std::string& password);
     /**
@@ -423,16 +428,6 @@ public:
      */
     std::map<std::string, std::string> getNearbyPeers() const override;
 
-    /**
-     * Store the local/public addresses used to register
-     */
-    void storeActiveIpAddress(std::function<void()>&& cb = {});
-
-    /**
-     * Create and return ICE options.
-     */
-    void getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept;
-
 #ifdef LIBJAMI_TESTABLE
     ConnectionManager& connectionManager()
     {
@@ -693,8 +688,6 @@ private:
                                const std::shared_ptr<dht::crypto::Certificate>& from_cert,
                                const dht::InfoHash& from);
 
-    static tls::DhParams loadDhParams(std::string path);
-
     void loadCachedUrl(const std::string& url,
                        const std::string& cachePath,
                        const std::chrono::seconds& cacheDuration,
@@ -792,7 +785,6 @@ private:
      */
     // TODO move in separate class
     void testTurn(IpAddr server);
-    void cacheTurnServers();
     std::unique_ptr<TurnTransport> testTurnV4_;
     std::unique_ptr<TurnTransport> testTurnV6_;
     void refreshTurnDelay(bool scheduleNext);
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index 588a859d54..7f8149a5db 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -411,7 +411,6 @@ transaction_request_cb(pjsip_rx_data* rdata)
     auto call = account->newIncomingCall(std::string(remote_user),
                                          MediaAttribute::mediaAttributesToMediaMaps(localMediaList),
                                          transport);
-
     if (!call) {
         return PJ_FALSE;
     }
diff --git a/test/unitTest/call/call.cpp b/test/unitTest/call/call.cpp
index 2e0ca02958..139e8c4bcc 100644
--- a/test/unitTest/call/call.cpp
+++ b/test/unitTest/call/call.cpp
@@ -297,9 +297,10 @@ CallTest::testDeclineMultiDevice()
 
     JAMI_INFO("Start call between alice and Bob");
     auto bobAccount2 = Manager::instance().getAccount<JamiAccount>(bob2Id);
+
     auto call = libjami::placeCallWithMedia(aliceId, bobUri, {});
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return callReceived == 2 && !callIdBob.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return callReceived == 2 && !callIdBob.empty(); }));
 
     JAMI_INFO("Stop call between alice and Bob");
     callStopped = 0;
-- 
GitLab