diff --git a/daemon/src/account.cpp b/daemon/src/account.cpp index c1077b74ab6bcb778245373d4a91cf27d8ac9a35..40f83e3b9b795c29542c28f777409211743280c9 100644 --- a/daemon/src/account.cpp +++ b/daemon/src/account.cpp @@ -502,11 +502,11 @@ Account::getUPnPIpAddress() const * ie: if it is able to make port mappings */ bool -Account::getUPnPActive() const +Account::getUPnPActive(std::chrono::seconds timeout) const { std::lock_guard<std::mutex> lk(upnp_mtx); if (upnpEnabled_) - return upnp_->hasValidIGD(); + return upnp_->hasValidIGD(timeout); return false; } diff --git a/daemon/src/account.h b/daemon/src/account.h index f295bd93e34cc3ccf6a65f60cfea411b9a51a3d9..6165cc4cfcad61da655ad5d592bdaf3a3f708102 100644 --- a/daemon/src/account.h +++ b/daemon/src/account.h @@ -53,6 +53,7 @@ #include <stdexcept> #include <atomic> #include <mutex> +#include <chrono> namespace ring { namespace upnp { class Controller; @@ -255,7 +256,7 @@ class Account : public Serializable, public std::enable_shared_from_this<Account * returns whether or not UPnP is enabled and active * ie: if it is able to make port mappings */ - bool getUPnPActive() const; + bool getUPnPActive(std::chrono::seconds timeout = {}) const; /** * Get the UPnP IP (external router) address. diff --git a/daemon/src/upnp/upnp_context.cpp b/daemon/src/upnp/upnp_context.cpp index c3e2eb34d375a402adf1637d92c322191ec4827d..2d72b46ec57fff1cc9ca4fbfa301f47e141af08f 100644 --- a/daemon/src/upnp/upnp_context.cpp +++ b/daemon/src/upnp/upnp_context.cpp @@ -40,6 +40,7 @@ #include <memory> #include <condition_variable> #include <random> +#include <chrono> #include <cstdlib> // for std::free #if HAVE_LIBUPNP @@ -178,39 +179,33 @@ UPnPContext::~UPnPContext() void UPnPContext::searchForIGD() { - if (clientRegistered_) { - std::lock_guard<std::mutex> lock(igdSearchMutex_); - /* send out search for both types, as some routers may possibly only reply to one */ - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT_SEC, UPNP_ROOT_DEVICE, nullptr); - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT_SEC, UPNP_IGD_DEVICE, nullptr); - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT_SEC, UPNP_WANIP_SERVICE, nullptr); - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT_SEC, UPNP_WANPPP_SERVICE, nullptr); - - pendingIGDSearchRequests_ = 4; - } else - RING_WARN("UPnP: Control Point not registered."); + if (not clientRegistered_) { + RING_WARN("UPnP: Control Point not registered"); + return; + } + + /* send out search for both types, as some routers may possibly only reply to one */ + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, nullptr); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, nullptr); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, nullptr); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, nullptr); } -/** - * returns 'true' if there is at least one valid (connected) IGD - * note: this function will until an IGD has been found or SEARCH_TIMEOUT_SEC - * have expired; the timeout starts when the context is created as we start - * searchign for IGDs immediately - */ bool -UPnPContext::hasValidIGD() +UPnPContext::hasValidIGD(std::chrono::seconds timeout) { if (not clientRegistered_) { - RING_WARN("UPnP: Control Point not registered."); + RING_WARN("UPnP: Control Point not registered"); return false; } - { - std::unique_lock<std::mutex> lock(igdSearchMutex_); - igdSearchCondition_.wait(lock, [this] { return pendingIGDSearchRequests_ <= 0;}); + std::unique_lock<std::mutex> lock(validIGDMutex_); + if (!validIGDCondVar_.wait_for(lock, timeout, + [this]{return not validIGDs_.empty();})) { + RING_WARN("UPnP: Valid IGD search timeout"); + return false; } - std::lock_guard<std::mutex> lock(validIGDMutex_); return not validIGDs_.empty(); } @@ -256,7 +251,7 @@ UPnPContext::addMapping(IGD* igd, auto iter = globalMappings->find(port_external); if (iter != globalMappings->end()) { /* mapping exists with same external port */ - GlobalMapping *mapping_ptr = &iter->second; + GlobalMapping* mapping_ptr = &iter->second; if (*mapping_ptr == mapping) { /* the same mapping, so nothing needs to be done */ *upnp_error = UPNP_E_SUCCESS; @@ -337,17 +332,12 @@ UPnPContext::addAnyMapping(uint16_t port_desired, bool use_same_port, bool unique) { - if (not hasValidIGD()) { - RING_WARN("UPnP: no valid IGD availabe"); - return {}; - } - /* get a lock on the igd list because we don't want the igd to be modified * or removed from the list while using it */ std::lock_guard<std::mutex> lock(validIGDMutex_); IGD* igd = chooseIGD_unlocked(); if (not igd) { - RING_WARN("UPnP: no valid IGD availabe"); + RING_WARN("UPnP: no valid IGD available"); return {}; } @@ -402,18 +392,13 @@ UPnPContext::addAnyMapping(uint16_t port_desired, void UPnPContext::removeMapping(const Mapping& mapping) { - if (not hasValidIGD()) { - RING_WARN("UPnP: no valid IGD availabe"); - return; - } - /* get a lock on the igd list because we don't want the igd to be modified * or removed from the list while using it */ std::lock_guard<std::mutex> lock(validIGDMutex_); IGD* igd = chooseIGD_unlocked(); if (not igd) { - RING_WARN("UPnP: no valid IGD availabe"); - return;; + RING_WARN("UPnP: no valid IGD available"); + return; } /* first make sure the mapping exists in the global list of the igd */ @@ -451,17 +436,12 @@ UPnPContext::removeMapping(const Mapping& mapping) IpAddr UPnPContext::getExternalIP() { - if (not hasValidIGD()) { - RING_WARN("UPnP: no valid IGD availabe"); - return {}; - } - /* get a lock on the igd list because we don't want the igd to be modified * or removed from the list while using it */ std::lock_guard<std::mutex> lock(validIGDMutex_); IGD* igd = chooseIGD_unlocked(); if (not igd) { - RING_WARN("UPnP: no valid IGD availabe"); + RING_WARN("UPnP: no valid IGD available"); return {}; } @@ -637,15 +617,6 @@ 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)); } - - /* check if we have pending search requests and notify anyone waiting */ - { - std::lock_guard<std::mutex> lock(igdSearchMutex_); - if (pendingIGDSearchRequests_ > 0) { - pendingIGDSearchRequests_ = 0; - } - } - igdSearchCondition_.notify_all(); } } @@ -692,7 +663,6 @@ get_first_element_item(IXML_Element* element, const char* item) return ret; } - int cp_callback(Upnp_EventType event_type, void* event, void* /*user_data*/) { @@ -801,16 +771,8 @@ cp_callback(Upnp_EventType event_type, void* event, void* /*user_data*/) break; case UPNP_DISCOVERY_SEARCH_TIMEOUT: - { RING_DBG("UPnP: Control Point search timeout"); - { - std::lock_guard<std::mutex> lock(upnpContext->igdSearchMutex_); - if (upnpContext->pendingIGDSearchRequests_ > 0) - --(upnpContext->pendingIGDSearchRequests_); - } - upnpContext->igdSearchCondition_.notify_all(); - } - break; + break; case UPNP_CONTROL_ACTION_COMPLETE: { diff --git a/daemon/src/upnp/upnp_context.h b/daemon/src/upnp/upnp_context.h index cff06827cfa24c3431ea745273526a98d6ad76e8..523112af9c91c93f53fde9af5973ab3d2543affb 100644 --- a/daemon/src/upnp/upnp_context.h +++ b/daemon/src/upnp/upnp_context.h @@ -40,6 +40,7 @@ #include <mutex> #include <memory> #include <condition_variable> +#include <chrono> #if HAVE_LIBUPNP #include <upnp/upnp.h> @@ -58,22 +59,19 @@ namespace ring { namespace upnp { class UPnPContext { public: + constexpr static unsigned SEARCH_TIMEOUT {30}; -#if HAVE_LIBUPNP - - /* search timeout in seconds */ - constexpr static int SEARCH_TIMEOUT_SEC = 30; +#if HAVE_LIBUPNP UPnPContext(); ~UPnPContext(); /** - * returns 'true' if there is at least one valid (connected) IGD - * note: this function will wait until an IGD has been found or SEARCH_TIMEOUT_SEC - * have expired; the timeout starts when the context is created as we start - * searching for IGDs immediately + * Returns 'true' if there is at least one valid (connected) IGD. + * @param timeout Time to wait until a valid IGD is found. + * If timeout is not given or 0, the function pool (non-blocking). */ - bool hasValidIGD(); + bool hasValidIGD(std::chrono::seconds timeout = {}); /** * tries to add mapping from and to the port_desired @@ -86,8 +84,6 @@ public: * that the external and internal ports are the same * * returns a valid mapping on success and an invalid mapping on failure - * - * note: this function will call hasValidIGD and wait upto SEARCH_TIMEOUT_SEC */ Mapping addAnyMapping(uint16_t port_desired, uint16_t port_local, @@ -97,11 +93,12 @@ public: /** * tries to remove the given mapping - * - * note: this function will call hasValidIGD and wait upto SEARCH_TIMEOUT_SEC */ void removeMapping(const Mapping& mapping); + /** + * tries to get the external ip of the router + */ IpAddr getExternalIP(); #else @@ -162,7 +159,8 @@ private: * the mutex is used to access these lists and IGDs in a thread-safe manner */ std::map<std::string, std::unique_ptr<IGD>> validIGDs_; - std::mutex validIGDMutex_; + mutable std::mutex validIGDMutex_; + std::condition_variable validIGDCondVar_; /** * chooses the IGD to use (currently selects the first one in the map) @@ -173,11 +171,6 @@ private: /* sends out async search for IGD */ void searchForIGD(); - /* vars to sync search timeout */ - unsigned pendingIGDSearchRequests_ {0}; - std::mutex igdSearchMutex_; - std::condition_variable igdSearchCondition_; - /** * callback function for the UPnP client (control point) * all UPnP events received by the client are processed here @@ -233,4 +226,4 @@ std::shared_ptr<UPnPContext> getUPnPContext(); }} // namespace ring::upnp -#endif /* UPNP_CONTEXT_H_ */ \ No newline at end of file +#endif /* UPNP_CONTEXT_H_ */ diff --git a/daemon/src/upnp/upnp_control.cpp b/daemon/src/upnp/upnp_control.cpp index e21d55c87603148c287fd3a9237b647ddf9b32a7..e1abef1be56c6249117ae8e635152653254288d1 100644 --- a/daemon/src/upnp/upnp_control.cpp +++ b/daemon/src/upnp/upnp_control.cpp @@ -56,29 +56,15 @@ Controller::~Controller() removeMappings(); } -/** - * Return whether or not this controller has a valid IGD, - * if 'flase' then all requests will fail - */ bool -Controller::hasValidIGD() +Controller::hasValidIGD(std::chrono::seconds timeout) { #if HAVE_LIBUPNP - return upnpContext_->hasValidIGD(); + return upnpContext_->hasValidIGD(timeout); #endif return false; } -/** - * tries to add mapping from and to the port_desired - * if unique == true, makes sure the client is not using this port already - * if the mapping fails, tries other available ports until success - * - * tries to use a random port between 1024 < > 65535 if desired port fails - * - * maps port_desired to port_local; if use_same_port == true, makes sure that - * that the extranl and internal ports are the same - */ bool Controller::addAnyMapping(uint16_t port_desired, uint16_t port_local, @@ -104,9 +90,6 @@ Controller::addAnyMapping(uint16_t port_desired, return false; } -/** - * addAnyMapping with the local port being the same as the external port - */ bool Controller::addAnyMapping(uint16_t port_desired, PortType type, @@ -116,11 +99,6 @@ Controller::addAnyMapping(uint16_t port_desired, addAnyMapping(port_desired, port_desired, type, true, unique, port_used); } -/** - * removes mappings added by this instance of the specified port type - * if an mapping has more than one user in the global list, it is not deleted - * from the router, but the number of users is decremented - */ void Controller::removeMappings(PortType type) { #if HAVE_LIBUPNP @@ -132,10 +110,6 @@ Controller::removeMappings(PortType type) { } #endif } - -/** - * removes all mappings added by this instance - */ void Controller::removeMappings() { @@ -145,9 +119,6 @@ Controller::removeMappings() #endif } -/** - * tries to get the external ip of the router - */ IpAddr Controller::getExternalIP() { diff --git a/daemon/src/upnp/upnp_control.h b/daemon/src/upnp/upnp_control.h index e2c1770322d2dc7b560e3be863f2a02a670af81c..86d4d2916eed23159122139897af52bf2c6934c9 100644 --- a/daemon/src/upnp/upnp_control.h +++ b/daemon/src/upnp/upnp_control.h @@ -36,6 +36,7 @@ #endif #include <memory> +#include <chrono> #include "noncopyable.h" #include "upnp_igd.h" @@ -58,16 +59,11 @@ public: ~Controller(); /** - * Return whether or not this controller has a valid IGD, - * if 'flase' then all requests will fail + * Return whether or not this controller has a valid IGD. + * @param timeout Time to wait until a valid IGD is found. + * If timeout is not given or 0, the function pool (non-blocking). */ - bool hasValidIGD(); - - /** - * like hasValidIGD, but calls the given callback when the IGD is found - * or when the search times out without finding one - */ - // void waitForValildIGD(IGDFoundCallback cb); + bool hasValidIGD(std::chrono::seconds timeout = {}); /** * tries to add mapping from and to the port_desired