From 4a3835ad3dd1c04521c4f3215d56db607ebc4ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Thu, 30 Jul 2015 12:28:52 -0400 Subject: [PATCH] upnp: handle late IGD findings This commit bumps OpenDHT to make use of the related connectivityChanged() method of the dht. Issue: #78262 Change-Id: I64ea7b745cfc0653a09e8f6fb2d23b2d3fc6db4e --- contrib/src/opendht/rules.mak | 2 +- src/ringdht/ringaccount.cpp | 44 ++++++++++++++++++++++++++++------- src/ringdht/ringaccount.h | 2 ++ src/sip/sipaccount.cpp | 22 +++++++++++------- src/upnp/upnp_context.cpp | 20 ++++++++++++++++ src/upnp/upnp_context.h | 16 +++++++++++-- src/upnp/upnp_control.cpp | 21 ++++++++++++++++- src/upnp/upnp_control.h | 13 +++++++++-- src/upnp/upnp_igd.h | 3 +++ 9 files changed, 121 insertions(+), 22 deletions(-) diff --git a/contrib/src/opendht/rules.mak b/contrib/src/opendht/rules.mak index df9807ec52..7f864cdfda 100644 --- a/contrib/src/opendht/rules.mak +++ b/contrib/src/opendht/rules.mak @@ -1,5 +1,5 @@ # OPENDHT -OPENDHT_VERSION := 930e3e890bae83356ab04959c37aab970717dd05 +OPENDHT_VERSION := 3dc1966dcc6f9d407193832b030890132159fae6 OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz PKGS += opendht diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index 42d7c342fe..606281998a 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -601,6 +601,9 @@ RingAccount::handleEvents() bool RingAccount::mapPortUPnP() { + // return true if not using UPnP + bool added = true; + if (getUPnPActive()) { /* create port mapping from published port to local port to the local IP * note that since different RING accounts can use the same port, @@ -611,19 +614,20 @@ bool RingAccount::mapPortUPnP() */ uint16_t port_used; std::lock_guard<std::mutex> lock(upnp_mtx); - if (upnp_->addAnyMapping(dhtPort_, ring::upnp::PortType::UDP, false, &port_used)) { + added = upnp_->addAnyMapping(dhtPort_, ring::upnp::PortType::UDP, false, &port_used); + if (added) { if (port_used != dhtPort_) RING_DBG("UPnP could not map port %u for DHT, using %u instead", dhtPort_, port_used); dhtPortUsed_ = port_used; - return true; - } else { - /* failed to map any port */ - return false; } - } else { - /* not using UPnP, so return true */ - return true; } + + std::weak_ptr<RingAccount> w = std::static_pointer_cast<RingAccount>(shared_from_this()); + upnp_->setIGDListener([w] { + if (auto shared = w.lock()) + shared->connectivityChanged(); + }); + return added; } void RingAccount::doRegister() @@ -884,6 +888,7 @@ void RingAccount::doUnregister(std::function<void(bool)> released_cb) } /* RING_DBG("UPnP: removing port mapping for DHT account."); */ + upnp_->setIGDListener(); upnp_->removeMappings(); Manager::instance().unregisterEventHandler((uintptr_t)this); @@ -1190,4 +1195,27 @@ RingAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& dht::TrustRequest(DHT_TYPE_NS, payload)); } +void +RingAccount::connectivityChanged() +{ + if (not dht_.isRunning()) + return; + if ( upnpEnabled_ ) { + auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); + std::thread{[shared] { + auto& this_ = *shared.get(); + auto oldPort = static_cast<in_port_t>(this_.dhtPortUsed_); + if (not this_.mapPortUPnP()) + RING_WARN("UPnP: Could not map DHT port"); + auto newPort = static_cast<in_port_t>(this_.dhtPortUsed_); + if (oldPort != newPort) { + RING_WARN("DHT port changed: restarting network"); + this_.doRegister_(); + } else + this_.dht_.connectivityChanged(); + }}.detach(); + } else + dht_.connectivityChanged(); +} + } // namespace ring diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h index 4f54921199..88d23e5804 100644 --- a/src/ringdht/ringaccount.h +++ b/src/ringdht/ringaccount.h @@ -261,6 +261,8 @@ class RingAccount : public SIPAccountBase { void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload); + void connectivityChanged(); + private: void doRegister_(); diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp index 4dafb8adf2..134c677541 100644 --- a/src/sip/sipaccount.cpp +++ b/src/sip/sipaccount.cpp @@ -695,6 +695,9 @@ SIPAccount::getVolatileAccountDetails() const bool SIPAccount::mapPortUPnP() { + // return true if not using UPnP + bool added = true; + if (getUPnPActive()) { /* create port mapping from published port to local port to the local IP * note that since different accounts can use the same port, @@ -704,19 +707,21 @@ bool SIPAccount::mapPortUPnP() * a different port, if succesfull, then we have to use that port for SIP */ uint16_t port_used; - if (upnp_->addAnyMapping(publishedPort_, localPort_, ring::upnp::PortType::UDP, false, false, &port_used)) { + bool added = upnp_->addAnyMapping(publishedPort_, localPort_, ring::upnp::PortType::UDP, false, false, &port_used); + if (added) { if (port_used != publishedPort_) RING_DBG("UPnP could not map published port %u for SIP, using %u instead", publishedPort_, port_used); publishedPortUsed_ = port_used; - return true; - } else { - /* failed to map any port */ - return false; } - } else { - /* not using UPnP, so return true */ - return true; } + + std::weak_ptr<SIPAccount> w = std::static_pointer_cast<SIPAccount>(shared_from_this()); + upnp_->setIGDListener([w] { + if (auto shared = w.lock()) + shared->doRegister(); + }); + + return added; } void SIPAccount::doRegister() @@ -895,6 +900,7 @@ void SIPAccount::doUnregister(std::function<void(bool)> released_cb) released_cb(true); /* RING_DBG("UPnP: removing port mapping for SIP account."); */ + upnp_->setIGDListener(); upnp_->removeMappings(); } diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp index 64ebdabdfe..e6da0d249f 100644 --- a/src/upnp/upnp_context.cpp +++ b/src/upnp/upnp_context.cpp @@ -224,6 +224,24 @@ UPnPContext::hasValidIGD(std::chrono::seconds timeout) return not validIGDs_.empty(); } +size_t +UPnPContext::addIGDListener(IGDFoundCallback&& cb) +{ + std::lock_guard<std::mutex> lock(validIGDMutex_); + auto token = ++listenerToken_; + igdListeners_.emplace(token, std::move(cb)); + return token; +} + +void +UPnPContext::removeIGDListener(size_t token) +{ + std::lock_guard<std::mutex> lock(validIGDMutex_); + auto it = igdListeners_.find(token); + if (it != igdListeners_.end()) + igdListeners_.erase(it); +} + /** * chooses the IGD to use, * assumes you already have a lock on validIGDMutex_ @@ -643,6 +661,8 @@ UPnPContext::parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event) removeMappingsByLocalIPAndDescription(new_igd.get(), Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); validIGDs_.emplace(UDN, std::move(new_igd)); validIGDCondVar_.notify_all(); + for (const auto& l : igdListeners_) + l.second(); } } } diff --git a/src/upnp/upnp_context.h b/src/upnp/upnp_context.h index a4d090c22b..c9c869ea08 100644 --- a/src/upnp/upnp_context.h +++ b/src/upnp/upnp_context.h @@ -63,11 +63,9 @@ class IpAddr; namespace ring { namespace upnp { class UPnPContext { - public: constexpr static unsigned SEARCH_TIMEOUT {30}; - #if HAVE_LIBUPNP UPnPContext(); ~UPnPContext(); @@ -79,6 +77,9 @@ public: */ bool hasValidIGD(std::chrono::seconds timeout = {}); + size_t addIGDListener(IGDFoundCallback&& cb); + void removeIGDListener(size_t token); + /** * tries to add mapping from and to the port_desired * if unique == true, makes sure the client is not using this port already @@ -177,6 +178,17 @@ private: mutable std::mutex validIGDMutex_; std::condition_variable validIGDCondVar_; + /** + * Map of valid IGD listeners. + */ + std::map<size_t, IGDFoundCallback> igdListeners_; + + /** + * Last provided token for valid IGD listeners. + * 0 is the invalid token. + */ + size_t listenerToken_ {0}; + /** * chooses the IGD to use (currently selects the first one in the map) * assumes you already have a lock on igd_mutex_ diff --git a/src/upnp/upnp_control.cpp b/src/upnp/upnp_control.cpp index ffcf933fa5..9ec9375274 100644 --- a/src/upnp/upnp_control.cpp +++ b/src/upnp/upnp_control.cpp @@ -56,6 +56,8 @@ Controller::~Controller() { /* remove all mappings */ removeMappings(); + if (listToken_ and upnpContext_) + upnpContext_->removeIGDListener(listToken_); } bool @@ -67,6 +69,16 @@ Controller::hasValidIGD(std::chrono::seconds timeout) return false; } +void +Controller::setIGDListener(IGDFoundCallback&& cb) +{ + if (not upnpContext_) + return; + if (listToken_) + upnpContext_->removeIGDListener(listToken_); + listToken_ = cb ? upnpContext_->addIGDListener(std::move(cb)) : 0; +} + bool Controller::addAnyMapping(uint16_t port_desired, uint16_t port_local, @@ -79,6 +91,14 @@ Controller::addAnyMapping(uint16_t port_desired, if (not upnpContext_) return false; + auto& instanceMappings = type == PortType::UDP ? udpMappings_ : tcpMappings_; + Mapping target(port_desired, port_local, type); + for (const auto& m : instanceMappings) + if (m.second == target) { + RING_DBG("UPnP maping already existed: %s", m.second.toString().c_str()); + return true; + } + Mapping mapping = upnpContext_->addAnyMapping(port_desired, port_local, type, use_same_port, unique); if (mapping) { @@ -87,7 +107,6 @@ Controller::addAnyMapping(uint16_t port_desired, *port_used = usedPort; /* add to map */ - auto& instanceMappings = type == PortType::UDP ? udpMappings_ : tcpMappings_; instanceMappings.emplace(usedPort, std::move(mapping)); return true; } diff --git a/src/upnp/upnp_control.h b/src/upnp/upnp_control.h index 612456e689..924a95b044 100644 --- a/src/upnp/upnp_control.h +++ b/src/upnp/upnp_control.h @@ -51,8 +51,6 @@ class UPnPContext; class Controller { public: - typedef std::function<void(bool)> IGDFoundCallback; - /* constructor */ Controller(); /* destructor */ @@ -65,6 +63,12 @@ public: */ bool hasValidIGD(std::chrono::seconds timeout = {}); + /** + * Set or clear a listener for valid IGDs. + * For simplicity there is one listener per controller. + */ + void setIGDListener(IGDFoundCallback&& cb = {}); + /** * tries to add mapping from and to the port_desired * if unique == true, makes sure the client is not using this port already @@ -120,6 +124,11 @@ private: PortMapLocal udpMappings_; PortMapLocal tcpMappings_; + /** + * IGD listener token + */ + size_t listToken_ {0}; + /** * Try to remove all mappings of the given type */ diff --git a/src/upnp/upnp_igd.h b/src/upnp/upnp_igd.h index 90ead0df47..b0d472f4d7 100644 --- a/src/upnp/upnp_igd.h +++ b/src/upnp/upnp_igd.h @@ -33,6 +33,7 @@ #include <string> #include <map> +#include <functional> #include "noncopyable.h" #include "ip_utils.h" @@ -127,6 +128,8 @@ public: class PortMapLocal : public std::map<uint16_t, Mapping> {}; class PortMapGlobal : public std::map<uint16_t, GlobalMapping> {}; +using IGDFoundCallback = std::function<void()>; + /* defines a UPnP capable Internet Gateway Device (a router) */ class IGD { public: -- GitLab