From 67b46caa08eb54d103f0d41345242976d0e3a709 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Tue, 24 Dec 2019 16:12:50 -0500
Subject: [PATCH] turn: cache resolved addresses

This avoid to re-resolve the TURN each time we need a new connection
The turn is resolved each time a connectivityChange occurs or
at the initialization of the account.
The resolved address is stored in the cache directory to speed-up
the initialization when possible. This Improve connection speed on
local netowrks.

Change-Id: Idff2631a4c9b874b5ef4226ac248b9045b310f68
---
 src/jamidht/jamiaccount.cpp | 71 ++++++++++++++++++++++++++++++++++++-
 src/jamidht/jamiaccount.h   | 10 ++++++
 src/jamidht/p2p.cpp         | 13 ++++++-
 src/sip/sipaccountbase.cpp  | 34 ++++++++++++++----
 src/sip/sipaccountbase.h    | 18 ++++++++++
 5 files changed, 137 insertions(+), 9 deletions(-)

diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index ad3f1dc866..99e08a4175 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -1519,6 +1519,8 @@ JamiAccount::doRegister()
     } else {
         doRegister_();
     }
+
+    cacheTurnServers(); // reset cache for TURN servers
 }
 
 std::vector<std::string>
@@ -1987,7 +1989,9 @@ JamiAccount::connectivityChanged()
     }
 
     dht_->connectivityChanged();
-    setPublishedAddress({}); // reset cache
+    // reset cache
+    setPublishedAddress({});
+    cacheTurnServers();
 }
 
 bool
@@ -2642,4 +2646,69 @@ JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
     }
 }
 
+void
+JamiAccount::cacheTurnServers()
+{
+    // 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.
+    dht::ThreadPool::io().run([this] {
+        // Avoid multiple refresh
+        if (isRefreshing_.exchange(true)) return;
+        if (!turnEnabled_) {
+            // In this case, we do not use any TURN server
+            std::lock_guard<std::mutex> lk(cachedTurnMutex_);
+            cacheTurnV4_.reset();
+            cacheTurnV6_.reset();
+            isRefreshing_ = false;
+            return;
+        }
+        JAMI_INFO("Refresh cache for TURN server resolution");
+        // 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 = turnServer_.empty() ? DEFAULT_TURN_SERVER : turnServer_;
+        fileutils::recursive_mkdir(cachePath_ + DIR_SEPARATOR_STR + "domains", 0700);
+        auto pathV4 = cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v4." + server;
+        if (auto turnV4File = std::ifstream(pathV4)) {
+            std::string content((std::istreambuf_iterator<char>(turnV4File)), std::istreambuf_iterator<char>());
+            std::lock_guard<std::mutex> lk(cachedTurnMutex_);
+            cacheTurnV4_ = std::make_unique<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>());
+            std::lock_guard<std::mutex> lk(cachedTurnMutex_);
+            cacheTurnV6_ = std::make_unique<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);
+            }
+            std::lock_guard<std::mutex> lk(cachedTurnMutex_);
+            // Update TURN
+            cacheTurnV4_ = std::make_unique<IpAddr>(std::move(turnV4));
+        }
+        auto turnV6 = IpAddr {server.empty() ? DEFAULT_TURN_SERVER : 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<std::mutex> lk(cachedTurnMutex_);
+            // Update TURN
+            cacheTurnV6_ = std::make_unique<IpAddr>(std::move(turnV6));
+        }
+        isRefreshing_ = false;
+        JAMI_INFO("Cache refreshed for TURN resolution");
+    });
+}
+
 } // namespace jami
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 8b053481f8..0596524400 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -646,6 +646,16 @@ private:
     bool accountPublish_ {false};
 
     std::shared_ptr<RepeatedTask> eventHandler {};
+
+    /**
+     * 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 cacheTurnServers();
 };
 
 static inline std::ostream& operator<< (std::ostream& os, const JamiAccount& acc)
diff --git a/src/jamidht/p2p.cpp b/src/jamidht/p2p.cpp
index 89a6a65271..2a0cf2d8a5 100644
--- a/src/jamidht/p2p.cpp
+++ b/src/jamidht/p2p.cpp
@@ -548,8 +548,14 @@ DhtPeerConnector::Impl::turnConnect()
     auto username = details[Conf::CONFIG_TURN_SERVER_UNAME];
     auto password = details[Conf::CONFIG_TURN_SERVER_PWD];
 
+    auto turnCache = account.turnCache();
+
     auto turn_param_v4 = TurnTransportParams {};
-    turn_param_v4.server = IpAddr {server.empty() ? "turn.jami.net" : server};
+    if (turnCache[0]) {
+        turn_param_v4.server = *turnCache[0];
+    } else {
+        turn_param_v4.server = IpAddr {server.empty() ? "turn.jami.net" : server};
+    }
     turn_param_v4.realm = realm.empty() ? "ring" : realm;
     turn_param_v4.username = username.empty() ? "ring" : username;
     turn_param_v4.password = password.empty() ? "ring" : password;
@@ -576,6 +582,11 @@ DhtPeerConnector::Impl::turnConnect()
 
         if (!turnAuthv6_ || !turnAuthv6_->isReady()) {
             auto turn_param_v6 = turn_param_v4;
+            if (turnCache[1]) {
+                turn_param_v6.server = *turnCache[1];
+            } else {
+                turn_param_v6.server = IpAddr {server.empty() ? "turn.jami.net" : server};
+            }
             turn_param_v6.authorized_family = PJ_AF_INET6;
             turnAuthv6_ = std::make_unique<TurnTransport>(turn_param_v6);
         }
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index 0997c765ec..9427b13634 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -370,15 +370,35 @@ SIPAccountBase::getIceOptions() const noexcept
 {
     auto opts = Account::getIceOptions(); // Local copy of global account ICE settings
 
-    // Note: we don't check of servers pre-existance, let underlaying stack do the job
     if (stunEnabled_)
         opts.stunServers.emplace_back(StunServerInfo().setUri(stunServer_));
-    if (turnEnabled_)
-        opts.turnServers.emplace_back(TurnServerInfo()
-                                      .setUri(turnServer_)
-                                      .setUsername(turnServerUserName_)
-                                      .setPassword(turnServerPwd_)
-                                      .setRealm(turnServerRealm_));
+    if (turnEnabled_) {
+        auto cached = false;
+        std::lock_guard<std::mutex> lk(cachedTurnMutex_);
+        cached = cacheTurnV4_ || cacheTurnV6_;
+        if (cacheTurnV4_ && *cacheTurnV4_) {
+            opts.turnServers.emplace_back(TurnServerInfo()
+                                .setUri(*cacheTurnV4_)
+                                .setUsername(turnServerUserName_)
+                                .setPassword(turnServerPwd_)
+                                .setRealm(turnServerRealm_));
+        }
+        if (cacheTurnV6_ && *cacheTurnV6_) {
+            opts.turnServers.emplace_back(TurnServerInfo()
+                                .setUri(*cacheTurnV4_)
+                                .setUsername(turnServerUserName_)
+                                .setPassword(turnServerPwd_)
+                                .setRealm(turnServerRealm_));
+        }
+        // Nothing cached, so do the resolution
+        if (!cached) {
+            opts.turnServers.emplace_back(TurnServerInfo()
+                                        .setUri(turnServer_)
+                                        .setUsername(turnServerUserName_)
+                                        .setPassword(turnServerPwd_)
+                                        .setRealm(turnServerRealm_));
+        }
+    }
     return opts;
 }
 
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index 6235313e9d..ea9ab75f72 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -298,6 +298,20 @@ public:
 public: // overloaded methods
     virtual void flush() override;
 
+    /**
+     * Return current turn resolved addresses
+     * @return {unique_ptr(v4 resolved), unique_ptr(v6 resolved)}
+     */
+    std::array<std::unique_ptr<IpAddr>, 2> turnCache() {
+        std::lock_guard<std::mutex> lk {cachedTurnMutex_};
+        std::array<std::unique_ptr<IpAddr>, 2> result = {};
+        if (cacheTurnV4_ && *cacheTurnV4_)
+            result[0] = std::make_unique<IpAddr>(*cacheTurnV4_);
+        if (cacheTurnV6_ && *cacheTurnV6_)
+            result[1] = std::make_unique<IpAddr>(*cacheTurnV6_);
+        return result;
+    }
+
 protected:
     virtual void serialize(YAML::Emitter &out) const override;
     virtual void serializeTls(YAML::Emitter &out) const;
@@ -445,6 +459,10 @@ protected:
     static constexpr size_t MAX_WAITING_MESSAGES_SIZE = 1000;
     std::deque<DRing::Message> lastMessages_;
 
+    mutable std::mutex cachedTurnMutex_ {};
+    std::unique_ptr<IpAddr> cacheTurnV4_ {};
+    std::unique_ptr<IpAddr> cacheTurnV6_ {};
+
 private:
     NON_COPYABLE(SIPAccountBase);
 
-- 
GitLab