diff --git a/src/dring/account_const.h b/src/dring/account_const.h index 203385cfc784337e099e70de0c755116b834ff06..34415f620cf2c09fcf133d308df1372bf20f0609 100644 --- a/src/dring/account_const.h +++ b/src/dring/account_const.h @@ -147,6 +147,8 @@ constexpr static const char ACCOUNT_PEER_DISCOVERY [] = "Account.accountDiscove constexpr static const char ACCOUNT_PUBLISH [] = "Account.accountPublish"; constexpr static const char MANAGER_URI [] = "Account.managerUri"; constexpr static const char MANAGER_USERNAME [] = "Account.managerUsername"; +constexpr static const char BOOTSTRAP_LIST_URL [] = "Account.bootstrapListUrl"; +constexpr static const char DHT_PROXY_LIST_URL [] = "Account.dhtProxyListUrl"; namespace Audio { diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index c23bd4fe4f1f14ffff0247dae7c53aac593ce9c0..2027cb69604384bdd3f15556f79ef70573ac085d 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -74,6 +74,8 @@ #include <opendht/thread_pool.h> #include <opendht/peer_discovery.h> +#include <opendht/http.h> + #include <yaml-cpp/yaml.h> #include <json/json.h> @@ -695,6 +697,7 @@ void JamiAccount::serialize(YAML::Emitter &out) const out << YAML::Key << Conf::PROXY_ENABLED_KEY << YAML::Value << proxyEnabled_; out << YAML::Key << Conf::PROXY_SERVER_KEY << YAML::Value << proxyServer_; out << YAML::Key << Conf::PROXY_PUSH_TOKEN_KEY << YAML::Value << deviceKey_; + out << YAML::Key << DRing::Account::ConfProperties::DHT_PROXY_LIST_URL << YAML::Value << proxyListUrl_; #if HAVE_RINGNS out << YAML::Key << DRing::Account::ConfProperties::RingNS::URI << YAML::Value << nameServer_; @@ -743,6 +746,11 @@ void JamiAccount::unserialize(const YAML::Node &node) parseValue(node, Conf::PROXY_ENABLED_KEY, proxyEnabled_); parseValue(node, Conf::PROXY_SERVER_KEY, proxyServer_); parseValue(node, Conf::PROXY_PUSH_TOKEN_KEY, deviceKey_); + try { + parseValue(node, DRing::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_); + } catch (const std::exception& e) { + proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL; + } parseValueOptional(node, DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_); parseValueOptional(node, DRing::Account::ConfProperties::MANAGER_URI, managerUri_); @@ -1068,6 +1076,7 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details if (hostname_.empty()) hostname_ = DHT_DEFAULT_BOOTSTRAP; + parseString(details, DRing::Account::ConfProperties::BOOTSTRAP_LIST_URL, bootstrapListUrl_); parseInt(details, Conf::CONFIG_DHT_PORT, dhtPort_); parseBool(details, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_); parseBool(details, DRing::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery_); @@ -1093,8 +1102,9 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details parsePath(details, DRing::Account::ConfProperties::ARCHIVE_PATH, archive_path, idPath_); parseString(details, DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_); + auto oldProxyServer = proxyServer_, oldProxyServerList = proxyListUrl_; + parseString(details, DRing::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_); parseBool(details, DRing::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_); - auto oldProxyServer = proxyServer_; parseString(details, DRing::Account::ConfProperties::PROXY_SERVER, proxyServer_); parseString(details, DRing::Account::ConfProperties::PROXY_PUSH_TOKEN, deviceKey_); // Migrate from old versions @@ -1103,11 +1113,13 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details || proxyServer_ == "dhtproxy.ring.cx") && proxyServerCached_.empty())) proxyServer_ = DHT_DEFAULT_PROXY; - if (proxyServer_ != oldProxyServer) { + if (proxyServer_ != oldProxyServer || oldProxyServerList != proxyListUrl_) { JAMI_DBG("DHT Proxy configuration changed, resetting cache"); proxyServerCached_ = {}; auto proxyCachePath = cachePath_ + DIR_SEPARATOR_STR "dhtproxy"; + auto proxyListCachePath = cachePath_ + DIR_SEPARATOR_STR "dhtproxylist"; std::remove(proxyCachePath.c_str()); + std::remove(proxyListCachePath.c_str()); } #if HAVE_RINGNS @@ -1165,6 +1177,7 @@ JamiAccount::getAccountDetails() const a.emplace(Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, "-1"); a.emplace(DRing::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_ ? TRUE_STR : FALSE_STR); a.emplace(DRing::Account::ConfProperties::PROXY_SERVER, proxyServer_); + a.emplace(DRing::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_); a.emplace(DRing::Account::ConfProperties::PROXY_PUSH_TOKEN, deviceKey_); a.emplace(DRing::Account::ConfProperties::MANAGER_URI, managerUri_); a.emplace(DRing::Account::ConfProperties::MANAGER_USERNAME, managerUsername_); @@ -1464,32 +1477,44 @@ JamiAccount::handlePendingCall(PendingCall& pc, bool incoming) } void -JamiAccount::mapPortUPnP() +JamiAccount::registerAsyncOps() { - upnp_->requestMappingAdd([this](uint16_t port_used, bool success) { - auto oldPort = static_cast<in_port_t>(dhtPortUsed_); - auto newPort = success ? port_used : dhtPort_; - - if (not success and not dht_->isRunning()) { - JAMI_WARN("[Account %s] Failed to open port %u: starting DHT anyways", getAccountID().c_str(), oldPort); + auto onLoad = [this, loaded = std::make_shared<std::atomic_uint>()]{ + if (++(*loaded) == 2u) doRegister_(); - return; - } + }; + + loadCachedProxyServer([this, onLoad](const std::string& proxy) { + onLoad(); + }); + + if (upnp_) { + upnp_->requestMappingAdd([this, onLoad](uint16_t port_used, bool success) { + auto oldPort = static_cast<in_port_t>(dhtPortUsed_); + auto newPort = success ? port_used : dhtPort_; - if (oldPort != newPort or not dht_->isRunning()){ - dhtPortUsed_ = newPort; - if (not dht_->isRunning()) { - JAMI_WARN("[Account %s] Starting DHT on port %u", getAccountID().c_str(), newPort); - doRegister_(); + if (not success and not dht_->isRunning()) { + JAMI_WARN("[Account %s] Failed to open port %u: starting DHT anyways", getAccountID().c_str(), oldPort); + onLoad(); + return; + } + + if (oldPort != newPort or not dht_->isRunning()){ + dhtPortUsed_ = newPort; + if (not dht_->isRunning()) { + JAMI_WARN("[Account %s] Starting DHT on port %u", getAccountID().c_str(), newPort); + onLoad(); + } else { + JAMI_WARN("[Account %s] DHT port changed to %u: restarting network", getAccountID().c_str(), newPort); + dht_->connectivityChanged(); + } } else { - JAMI_WARN("[Account %s] DHT port changed to %u: restarting network", getAccountID().c_str(), newPort); + JAMI_WARN("[Account %s] DHT port %u opened: restarting network", getAccountID().c_str(), newPort); dht_->connectivityChanged(); } - } else { - JAMI_WARN("[Account %s] DHT port %u opened: restarting network", getAccountID().c_str(), newPort); - dht_->connectivityChanged(); - } - }, dhtPort_, jami::upnp::PortType::UDP, false); + }, dhtPort_, jami::upnp::PortType::UDP, false); + } else + onLoad(); } void @@ -1512,10 +1537,10 @@ JamiAccount::doRegister() } /* if UPnP is enabled, then wait for IGD to complete registration */ - if (upnp_) { + if (upnp_ or proxyServerCached_.empty()) { JAMI_DBG("UPnP: Attempting to map ports for Jami account"); setRegistrationState(RegistrationState::TRYING); - mapPortUPnP(); + registerAsyncOps(); } else { doRegister_(); } @@ -1668,7 +1693,7 @@ JamiAccount::doRegister_() config.dht_config.node_config.maintain_storage = false; config.dht_config.node_config.persist_path = cachePath_+DIR_SEPARATOR_STR "dhtstate"; config.dht_config.id = id_; - config.proxy_server = getDhtProxyServer(); + config.proxy_server = getDhtProxyServer(proxyServer_); config.push_node_id = getAccountID(); config.push_token = deviceKey_; config.threaded = true; @@ -1830,7 +1855,7 @@ JamiAccount::doRegister_() dhtPeerConnector_->onDhtConnected(accountManager_->getInfo()->deviceId); - std::lock_guard<std::mutex> lock(buddyInfoMtx); + std::lock_guard<std::mutex> bLock(buddyInfoMtx); for (auto& buddy : trackedBuddies_) { buddy.second.devices_cnt = 0; trackPresence(buddy.first, buddy.second); @@ -2127,6 +2152,7 @@ JamiAccount::getKnownDevices() const 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); @@ -2151,14 +2177,90 @@ JamiAccount::loadDhParams(std::string path) } } +void +JamiAccount::loadCachedUrl(const std::string& url, + const std::string& cachePath, + const std::chrono::seconds& cacheDuration, + std::function<void(const dht::http::Response& response)> cb) +{ + auto lock = std::make_shared<std::lock_guard<std::mutex>>(fileutils::getFileLock(cachePath)); + dht::ThreadPool::io().run([lock, cb, url, cachePath, cacheDuration, w=weak()]() { + try { + // writeTime throws exception if file doesn't exist + auto duration = clock::now() - fileutils::writeTime(cachePath); + if (duration > cacheDuration) + throw std::runtime_error("file too old"); + + JAMI_DBG("Loading '%.*s' from cache file '%.*s'", (int)url.size(), url.c_str(), (int)cachePath.size(), cachePath.c_str()); + auto data = fileutils::loadFile(cachePath); + dht::http::Response ret; + ret.body = {data.begin(), data.end()}; + ret.status_code = 200; + cb(ret); + } catch (const std::exception& e) { + JAMI_DBG("Failed to load '%.*s' from '%.*s': %s", (int)url.size(), url.c_str(), (int)cachePath.size(), cachePath.c_str(), e.what()); + + if (auto sthis = w.lock()) { + auto ioContext = Manager::instance().ioContext(); + auto req = std::make_shared<dht::http::Request>(*ioContext, url, [lock, cb, cachePath, w](const dht::http::Response& response) { + if (response.status_code == 200) { + try { + fileutils::saveFile(cachePath, (const uint8_t*)response.body.data(), response.body.size(), 0600); + JAMI_DBG("Cached result to '%.*s'", (int)cachePath.size(), cachePath.c_str()); + } catch (const std::exception& ex) { + JAMI_WARN("Failed to save result to %.*s: %s", (int)cachePath.size(), cachePath.c_str(), ex.what()); + } + } else { + JAMI_WARN("Failed to download url"); + } + cb(response); + if (auto req = response.request.lock()) + if (auto sthis = w.lock()) + sthis->requests_.erase(req); + }); + sthis->requests_.emplace(req); + req->send(); + } + } + }); +} + +void +JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb) +{ + if (proxyServerCached_.empty()) { + JAMI_DBG("[Account %s] loading DHT proxy URL", getAccountID().c_str()); + if (proxyListUrl_.empty()) { + cb(getDhtProxyServer(proxyServer_)); + } else { + loadCachedUrl(proxyListUrl_, + cachePath_ + DIR_SEPARATOR_STR "dhtproxylist", + std::chrono::hours(24 * 3), + [w=weak(), cb=std::move(cb)](const dht::http::Response& response){ + if (auto sthis = w.lock()) { + sthis->proxyServerCached_.clear(); + if (response.status_code == 200) { + cb(sthis->getDhtProxyServer(response.body)); + } else { + cb(sthis->getDhtProxyServer(sthis->proxyServer_)); + } + } + } + ); + } + } else { + cb(proxyServerCached_); + } +} + std::string -JamiAccount::getDhtProxyServer() +JamiAccount::getDhtProxyServer(const std::string& serverList) { if (!proxyEnabled_) return {}; if (proxyServerCached_.empty()) { std::vector<std::string> proxys; // Split the list of servers - std::sregex_iterator begin = {proxyServer_.begin(), proxyServer_.end(), PROXY_REGEX}, end; + std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end; for (auto it = begin; it != end; ++it) { auto &match = *it; if (match[5].matched and match[6].matched) { diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index 0596524400399dd4dc6c8448f3dc17d596c06f4e..1f66e94e6d2e22a4abefe5b85f9858c658710a6c 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -59,9 +59,9 @@ class Emitter; namespace dev { - template <unsigned N> class FixedHash; - using h160 = FixedHash<20>; - using Address = h160; +template <unsigned N> class FixedHash; +using h160 = FixedHash<20>; +using Address = h160; } namespace jami { @@ -85,6 +85,8 @@ public: constexpr static const in_port_t DHT_DEFAULT_PORT = 4222; constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net"; constexpr static const char* const DHT_DEFAULT_PROXY = "dhtproxy.jami.net:[80-100]"; + constexpr static const char* const DHT_DEFAULT_BOOTSTRAP_LIST_URL = "https://config.jami.net/boostrapList"; + constexpr static const char* const DHT_DEFAULT_PROXY_LIST_URL = "https://config.jami.net/proxyList"; /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE; @@ -477,9 +479,9 @@ private: void onTrackedBuddyOnline(const dht::InfoHash&); /** - * Maps require port via UPnP + * Maps require port via UPnP and other async ops */ - void mapPortUPnP(); + void registerAsyncOps(); /** * Add port mapping callback function. */ @@ -525,7 +527,13 @@ private: static tls::DhParams loadDhParams(std::string path); - std::string getDhtProxyServer(); + void loadCachedUrl(const std::string& url, + const std::string& cachePath, + const std::chrono::seconds& cacheDuration, + std::function<void(const dht::http::Response& response)>); + + std::string getDhtProxyServer(const std::string& serverList); + void loadCachedProxyServer(std::function<void(const std::string&)> cb); /** * The TLS settings, used only if tls is chosen as a sip transport. @@ -541,7 +549,7 @@ private: std::string nameServer_; std::string registeredName_; #endif - std::shared_ptr<dht::Logger> logger_; + std::shared_ptr<dht::Logger> logger_; std::shared_ptr<dht::DhtRunner> dht_ {}; std::unique_ptr<AccountManager> accountManager_; @@ -583,6 +591,8 @@ private: mutable std::mutex dhtValuesMtx_; bool dhtPublicInCalls_ {true}; + std::string bootstrapListUrl_; + /** * DHT port preference */ @@ -601,6 +611,7 @@ private: /** * Proxy */ + std::string proxyListUrl_; bool proxyEnabled_ {false}; std::string proxyServer_ {}; std::string proxyServerCached_ {}; @@ -656,6 +667,8 @@ private: * Jami, or for each connectivityChange() */ void cacheTurnServers(); + + std::set<std::shared_ptr<dht::http::Request>> requests_; }; static inline std::ostream& operator<< (std::ostream& os, const JamiAccount& acc) diff --git a/src/jamidht/namedirectory.h b/src/jamidht/namedirectory.h index c35b76f6835898d26eeaea9852e3696c829f1384..8b70b9132d4eef54c4c2bb1f377c7d5eb46941bc 100644 --- a/src/jamidht/namedirectory.h +++ b/src/jamidht/namedirectory.h @@ -36,6 +36,7 @@ struct PublicKey; } namespace http { class Request; +struct Response; class Resolver; } struct Logger; @@ -96,7 +97,7 @@ private: */ std::shared_ptr<asio::io_context> httpContext_; std::shared_ptr<dht::http::Resolver> resolver_; - std::map<unsigned int /*id*/, std::shared_ptr<dht::http::Request>> requests_; + std::map<unsigned, std::shared_ptr<dht::http::Request>> requests_; std::map<std::string, std::string> nameCache_ {}; std::map<std::string, std::string> addrCache_ {};