diff --git a/src/manager.cpp b/src/manager.cpp index 7ce39ed1da5245b9894f7ea576366b32b90a0ae2..3c135388058be9d3d554eb05fab4b7de47a60c57 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -91,6 +91,8 @@ using random_device = dht::crypto::random_device; #include "data_transfer.h" #include "dring/media_const.h" +#include "upnp/upnp_context.h" + #include <libavutil/ffversion.h> #include <opendht/thread_pool.h> @@ -845,6 +847,9 @@ Manager::finish() noexcept return; try { + // Terminate UPNP context + jami::upnp::UPnPContext::getUPnPContext()->shutdown(); + // Forbid call creation callFactory.forbid(); @@ -894,6 +899,7 @@ Manager::finish() noexcept } if (pimpl_->ioContextRunner_.joinable()) pimpl_->ioContextRunner_.join(); + } catch (const VoipLinkException& err) { JAMI_ERR("%s", err.what()); } @@ -2828,7 +2834,6 @@ Manager::removeAccounts() removeAccount(acc); } - std::vector<std::string_view> Manager::loadAccountOrder() const { diff --git a/src/upnp/protocol/natpmp/nat_pmp.cpp b/src/upnp/protocol/natpmp/nat_pmp.cpp index eab6049fe32e650ce29a3dc209b0b41fbf12df99..c7d20f890549359d92701f6a76d79f6793b6c3fa 100644 --- a/src/upnp/protocol/natpmp/nat_pmp.cpp +++ b/src/upnp/protocol/natpmp/nat_pmp.cpp @@ -27,6 +27,7 @@ namespace upnp { NatPmp::NatPmp() { + JAMI_DBG("NAT-PMP: Instance [%p] created", this); getNatpmpScheduler()->run([this] { threadId_ = getCurrentThread(); igd_ = std::make_shared<PMPIGD>(); @@ -35,7 +36,7 @@ NatPmp::NatPmp() NatPmp::~NatPmp() { - clearIgds(); + JAMI_DBG("NAT-PMP: Instance [%p] destroyed", this); } void @@ -46,8 +47,6 @@ NatPmp::initNatPmp() return; } - CHECK_VALID_THREAD(); - initialized_ = false; hostAddress_ = ip_utils::getLocalAddr(pj_AF_INET()); @@ -116,10 +115,27 @@ NatPmp::initNatPmp() void NatPmp::setObserver(UpnpMappingObserver* obs) { + if (not isValidThread()) { + getNatpmpScheduler()->run([this, obs] { setObserver(obs); }); + return; + } + JAMI_DBG("NAT-PMP: Setting observer to %p", obs); + observer_ = obs; } +void +NatPmp::terminate() +{ + if (not isValidThread()) { + getNatpmpScheduler()->run([this] { terminate(); }); + return; + } + + observer_ = nullptr; +} + void NatPmp::clearIgds() { @@ -128,8 +144,6 @@ NatPmp::clearIgds() return; } - CHECK_VALID_THREAD(); - initialized_ = false; if (searchForIgdTimer_) searchForIgdTimer_->cancel(); @@ -148,8 +162,6 @@ NatPmp::searchForIgd() return; } - CHECK_VALID_THREAD(); - if (not initialized_) { initNatPmp(); } @@ -180,6 +192,11 @@ NatPmp::getIgdList(std::list<std::shared_ptr<IGD>>& igdList) const bool NatPmp::isReady() const { + if (observer_ == nullptr) { + JAMI_ERR("NAT-PMP: the observer is not set!"); + return false; + } + // Must at least have a valid local address. if (not getHostAddress() or getHostAddress().isLoopback()) return false; @@ -616,6 +633,8 @@ NatPmp::processIgdUpdate(UpnpIgdEvent event) removeAllMappings(); } + if (observer_ == nullptr) + return; // Process the response on the context thread. runOnUpnpContextThread([obs = observer_, igd = igd_, event] { obs->onIgdUpdated(igd, event); }); } @@ -623,6 +642,9 @@ NatPmp::processIgdUpdate(UpnpIgdEvent event) void NatPmp::processMappingAdded(const Mapping& map) { + if (observer_ == nullptr) + return; + // Process the response on the context thread. runOnUpnpContextThread([obs = observer_, igd = igd_, map] { obs->onMappingAdded(igd, map); }); } @@ -630,6 +652,9 @@ NatPmp::processMappingAdded(const Mapping& map) void NatPmp::processMappingRenewed(const Mapping& map) { + if (observer_ == nullptr) + return; + // Process the response on the context thread. runOnUpnpContextThread([obs = observer_, igd = igd_, map] { obs->onMappingRenewed(igd, map); }); } @@ -637,6 +662,9 @@ NatPmp::processMappingRenewed(const Mapping& map) void NatPmp::processMappingRemoved(const Mapping& map) { + if (observer_ == nullptr) + return; + // Process the response on the context thread. runOnUpnpContextThread([obs = observer_, igd = igd_, map] { obs->onMappingRemoved(igd, map); }); } diff --git a/src/upnp/protocol/natpmp/nat_pmp.h b/src/upnp/protocol/natpmp/nat_pmp.h index ddf0a7ea8bfa40916b321a826cc7ca565625d150..fe2bf22956771f54db6a64f2621f9bc7346e9336 100644 --- a/src/upnp/protocol/natpmp/nat_pmp.h +++ b/src/upnp/protocol/natpmp/nat_pmp.h @@ -97,6 +97,10 @@ public: // Removes a mapping. void requestMappingRemove(const Mapping& igdMapping) override; + // Terminate. Nothing to do here, the clean-up is done when + // the IGD is cleared. + void terminate() override; + private: void initNatPmp(); void getIgdPublicAddress(); diff --git a/src/upnp/protocol/pupnp/pupnp.cpp b/src/upnp/protocol/pupnp/pupnp.cpp index 0b81a8c0992f3a1ad565501cb274d7d146d5b550..20aef2bede6dd725f73f1941c10f8ef8491c3f68 100644 --- a/src/upnp/protocol/pupnp/pupnp.cpp +++ b/src/upnp/protocol/pupnp/pupnp.cpp @@ -98,20 +98,90 @@ errorOnResponse(IXML_Document* doc) PUPnP::PUPnP() { + JAMI_DBG("PUPnP: Instance [%p] created", this); threadId_ = getCurrentThread(); +} - if (not initUpnpLib()) - return; - if (not registerClient()) +PUPnP::~PUPnP() +{ + JAMI_DBG("PUPnP: Instance [%p] destroyed", this); +} + +void +PUPnP::initUpnpLib() +{ + assert(not initialized_); + + int upnp_err = UpnpInit2(nullptr, 0); + + if (upnp_err != UPNP_E_SUCCESS) { + JAMI_ERR("PUPnP: Can't initialize libupnp: %s", UpnpGetErrorMessage(upnp_err)); + UpnpFinish(); + initialized_ = false; return; + } + + // Disable embedded WebServer if any. + if (UpnpIsWebserverEnabled() == 1) { + JAMI_WARN("PUPnP: Web-server is enabled. Disabling"); + UpnpEnableWebserver(0); + if (UpnpIsWebserverEnabled() == 1) { + JAMI_ERR("PUPnP: Could not disable Web-server!"); + } else { + JAMI_DBG("PUPnP: Web-server successfully disabled"); + } + } + + char* ip_address = UpnpGetServerIpAddress(); + char* ip_address6 = nullptr; + unsigned short port = UpnpGetServerPort(); + unsigned short port6 = 0; +#if UPNP_ENABLE_IPV6 + ip_address6 = UpnpGetServerIp6Address(); + port6 = UpnpGetServerPort6(); +#endif + if (ip_address6 and port6) + JAMI_DBG("PUPnP: Initialized on %s:%u | %s:%u", ip_address, port, ip_address6, port6); + else + JAMI_DBG("PUPnP: Initialized on %s:%u", ip_address, port); + + // Relax the parser to allow malformed XML text. + ixmlRelaxParser(1); + + initialized_ = true; +} + +void +PUPnP::registerClient() +{ + assert(not clientRegistered_); + + CHECK_VALID_THREAD(); + + // Register Upnp control point. + std::unique_lock<std::mutex> lk(ctrlptMutex_); + int upnp_err = UpnpRegisterClient(ctrlPtCallback, this, &ctrlptHandle_); + if (upnp_err != UPNP_E_SUCCESS) { + JAMI_ERR("PUPnP: Can't register client: %s", UpnpGetErrorMessage(upnp_err)); + } else { + JAMI_DBG("PUPnP: Successfully registered client"); + clientRegistered_ = true; + } +} + +void +PUPnP::startPUPnP() +{ + JAMI_DBG("PUPnP: Starting PUPNP internal thread"); pupnpThread_ = std::thread([this] { + JAMI_DBG("PUPnP: Internal thread started"); + pupnpRun_ = true; while (pupnpRun_) { { std::unique_lock<std::mutex> lk(igdListMutex_); pupnpCv_.wait(lk, [this] { - return not clientRegistered_ or not pupnpRun_ or searchForIgd_ - or not dwnldlXmlList_.empty(); + return not pupnpRun_ or searchForIgd_ or not dwnldlXmlList_.empty(); }); } @@ -158,90 +228,60 @@ PUPnP::PUPnP() } } } + + JAMI_DBG("PUPnP: Internal thread stopped"); }); } -PUPnP::~PUPnP() +void +PUPnP::setObserver(UpnpMappingObserver* obs) { - // Clear all the lists. + CHECK_VALID_THREAD(); + + JAMI_DBG("PUPnP: Setting observer to %p", obs); + + observer_ = obs; +} + +void +PUPnP::terminate() +{ + CHECK_VALID_THREAD(); + + JAMI_DBG("PUPnP: Terminate instance %p", this); + + clientRegistered_ = false; + observer_ = nullptr; + { std::lock_guard<std::mutex> lock(validIgdListMutex_); std::lock_guard<std::mutex> lk(ctrlptMutex_); - validIgdList_.clear(); - clientRegistered_ = false; + UpnpUnRegisterClient(ctrlptHandle_); } - { - std::lock_guard<std::mutex> lk2(igdListMutex_); - discoveredIgdList_.clear(); - dwnldlXmlList_.clear(); - cancelXmlList_.clear(); - pupnpRun_ = false; + if (UpnpFinish() != UPNP_E_SUCCESS) { + JAMI_ERR("PUPnP: Failed to properly close lib-upnp"); } - UpnpFinish(); - + pupnpRun_ = false; // Notify thread to terminate. pupnpCv_.notify_all(); if (pupnpThread_.joinable()) pupnpThread_.join(); - JAMI_DBG("PUPnP: Instance [%p] destroyed", this); -} - -bool -PUPnP::initUpnpLib() -{ - int upnp_err = UpnpInit2(nullptr, 0); - - if (upnp_err != UPNP_E_SUCCESS) { - JAMI_ERR("PUPnP: Can't initialize libupnp: %s", UpnpGetErrorMessage(upnp_err)); - UpnpFinish(); - pupnpRun_ = false; - return false; + // Clear all the lists. + { + std::lock_guard<std::mutex> lock(validIgdListMutex_); + validIgdList_.clear(); } - char* ip_address = UpnpGetServerIpAddress(); - char* ip_address6 = nullptr; - unsigned short port = UpnpGetServerPort(); - unsigned short port6 = 0; -#if UPNP_ENABLE_IPV6 - ip_address6 = UpnpGetServerIp6Address(); - port6 = UpnpGetServerPort6(); -#endif - if (ip_address6 and port6) - JAMI_DBG("PUPnP: Initialized on %s:%u | %s:%u", ip_address, port, ip_address6, port6); - else - JAMI_DBG("PUPnP: Initialized on %s:%u", ip_address, port); - // Relax the parser to allow malformed XML text. - ixmlRelaxParser(1); - - return true; -} - -bool -PUPnP::registerClient() -{ - // Register Upnp control point. - std::unique_lock<std::mutex> lk(ctrlptMutex_); - int upnp_err = UpnpRegisterClient(ctrlPtCallback, this, &ctrlptHandle_); - if (upnp_err != UPNP_E_SUCCESS) { - JAMI_ERR("PUPnP: Can't register client: %s", UpnpGetErrorMessage(upnp_err)); - pupnpRun_ = false; - return false; - } else { - JAMI_DBG("PUPnP: Successfully registered client"); - clientRegistered_ = true; + { + std::lock_guard<std::mutex> lk2(igdListMutex_); + discoveredIgdList_.clear(); + dwnldlXmlList_.clear(); + cancelXmlList_.clear(); } - - return true; -} - -void -PUPnP::setObserver(UpnpMappingObserver* obs) -{ - observer_ = obs; } void @@ -295,25 +335,58 @@ PUPnP::searchForIgd() // Update local address before searching. hostAddress_ = ip_utils::getLocalAddr(pj_AF_INET()); - if (not isReady()) { - if (igdSearchCounter_++ < PUPNP_MAX_RESTART_SEARCH_RETRIES) { - JAMI_DBG("PUPnP: Start search for IGD: attempt %u", igdSearchCounter_); + if (isReady()) { + JAMI_WARN("PUPnP: Already have a valid IGD. Skipping the search request"); + return; + } - searchForIgd_ = true; - pupnpCv_.notify_one(); + if (igdSearchCounter_++ >= PUPNP_MAX_RESTART_SEARCH_RETRIES) { + JAMI_WARN("PUPnP: Setup failed after %u trials. PUPnP will be disabled!", + PUPNP_MAX_RESTART_SEARCH_RETRIES); + return; + } - // Cancel the current timer (if any) and re-schedule. - if (searchForIgdTimer_) - searchForIgdTimer_->cancel(); + JAMI_DBG("PUPnP: Start search for IGD: attempt %u", igdSearchCounter_); - searchForIgdTimer_ = getUpnContextScheduler() - ->scheduleIn([this] { searchForIgd(); }, - PUPNP_TIMEOUT_BEFORE_IGD_SEARCH_RETRY); - } else { - JAMI_WARN("PUPnP: Setup failed after %u trials. PUPnP will be disabled!", - PUPNP_MAX_RESTART_SEARCH_RETRIES); + if (not pupnpRun_) { + startPUPnP(); + } + + // Do not init if the host is not valid. Otherwise, the init will fail + // anyway and may put libupnp in an unstable state (mainly deadlocks) + // even if the UpnpFinish() method is called. + if (not hostAddress_ or hostAddress_.isLoopback()) { + JAMI_WARN("PUPnP: Host address is invalid. Skipping the IGD search"); + } else { + // Init and register if needed + if (not initialized_) { + initUpnpLib(); + } + if (initialized_ and not clientRegistered_) { + registerClient(); } } + + // Start search + if (clientRegistered_ and pupnpRun_) { + assert(initialized_); + JAMI_DBG("PUPnP: Start search for IGD"); + searchForIgd_ = true; + pupnpCv_.notify_one(); + } else { + JAMI_WARN("PUPnP: PUPNP not fully setup. Skipping the IGD search"); + } + + // Cancel the current timer (if any) and re-schedule. + if (searchForIgdTimer_) + searchForIgdTimer_->cancel(); + + searchForIgdTimer_ = getUpnContextScheduler()->scheduleIn( + [w = weak()] { + if (auto upnpThis = w.lock()) + upnpThis->searchForIgd(); + }, + PUPNP_TIMEOUT_BEFORE_IGD_SEARCH_RETRY); } void @@ -369,7 +442,8 @@ PUPnP::incrementErrorsCounter(const std::shared_ptr<IGD>& igd) // Disable this IGD. igd->setValid(false); // Notify the listener. - observer_->onIgdUpdated(igd, UpnpIgdEvent::INVALID_STATE); + if (observer_) + observer_->onIgdUpdated(igd, UpnpIgdEvent::INVALID_STATE); } } @@ -484,18 +558,24 @@ PUPnP::validateIgd(const IGDInfo& info) deleteMappingsByDescription(igd_candidate, Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX); // Report to the listener. - observer_->onIgdUpdated(igd_candidate, UpnpIgdEvent::ADDED); + if (observer_) + observer_->onIgdUpdated(igd_candidate, UpnpIgdEvent::ADDED); // Subscribe to IGD events. int upnp_err = UpnpSubscribeAsync(ctrlptHandle_, eventSub.c_str(), - SUBSCRIBE_TIMEOUT, + UPNP_INFINITE, subEventCallback, this); - if (upnp_err != UPNP_E_SUCCESS) - JAMI_WARN("PUPnP: Error when trying to request subscription for %s -> %s", + if (upnp_err != UPNP_E_SUCCESS) { + JAMI_WARN("PUPnP: Failed to send subscribe request to %s: error %i - %s", igd_candidate->getUID().c_str(), + upnp_err, UpnpGetErrorMessage(upnp_err)); + } else { + JAMI_DBG("PUPnP: Successfully sent subscribe request to %s", + igd_candidate->getUID().c_str()); + } return true; } @@ -503,13 +583,16 @@ PUPnP::validateIgd(const IGDInfo& info) void PUPnP::requestMappingAdd(const std::shared_ptr<IGD>& igd, const Mapping& mapping) { - if (auto pupnp_igd = std::dynamic_pointer_cast<const UPnPIGD>(igd)) { - actionAddPortMappingAsync(*pupnp_igd, mapping); + if (auto pupnp_igd = std::dynamic_pointer_cast<UPnPIGD>(igd)) { + dht::ThreadPool::io().run([w = weak(), pupnp_igd, mapping] { + if (auto upnpThis = w.lock()) + upnpThis->actionAddPortMapping(pupnp_igd, mapping); + }); } } bool -PUPnP::actionAddPortMappingAsync(const UPnPIGD& igd, const Mapping& mapping) +PUPnP::actionAddPortMappingAsync(const std::shared_ptr<UPnPIGD>& igd, const Mapping& mapping) { if (not clientRegistered_) { return false; @@ -519,48 +602,49 @@ PUPnP::actionAddPortMappingAsync(const UPnPIGD& igd, const Mapping& mapping) // Set action sequence. UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewRemoteHost", ""); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewExternalPort", mapping.getExternalPortStr().c_str()); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewProtocol", mapping.getTypeStr()); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewInternalPort", mapping.getInternalPortStr().c_str()); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewInternalClient", getHostAddress().toString().c_str()); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewEnabled", "1"); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewPortMappingDescription", mapping.toString().c_str()); UpnpAddToAction(&action_container_ptr, ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), "NewLeaseDuration", "0"); action.reset(action_container_ptr); + int upnp_err = UpnpSendActionAsync(ctrlptHandle_, - igd.getControlURL().c_str(), - igd.getServiceType().c_str(), + igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), ctrlPtCallback, @@ -568,12 +652,14 @@ PUPnP::actionAddPortMappingAsync(const UPnPIGD& igd, const Mapping& mapping) if (upnp_err != UPNP_E_SUCCESS) { JAMI_WARN("PUPnP: Failed to send async action %s from: %s, %d: %s", ACTION_ADD_PORT_MAPPING, - igd.getServiceType().c_str(), + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); return false; } + JAMI_DBG("PUPnP: Sent request to open port %s", mapping.toString().c_str()); + return true; } @@ -585,8 +671,6 @@ PUPnP::processAddMapAction(const std::string& ctrlURL, { Mapping mapToAdd(portType, ePort, iPort); - JAMI_DBG("PUPnP: Opened port %s", mapToAdd.toString().c_str()); - { std::lock_guard<std::mutex> lock(validIgdListMutex_); for (auto const& it : validIgdList_) { @@ -601,9 +685,11 @@ PUPnP::processAddMapAction(const std::string& ctrlURL, } if (mapToAdd.getIgd()) { - observer_->onMappingAdded(mapToAdd.getIgd(), std::move(mapToAdd)); + JAMI_DBG("PUPnP: Opened port %s", mapToAdd.toString().c_str()); + if (observer_) + observer_->onMappingAdded(mapToAdd.getIgd(), std::move(mapToAdd)); } else { - JAMI_WARN("PUPnP: Did not find matching ctrl URL [%s] for (map %s)", + JAMI_WARN("PUPnP: Did not find matching ctrl URL [%s] for %s", ctrlURL.c_str(), mapToAdd.toString().c_str()); } @@ -616,6 +702,7 @@ PUPnP::processRemoveMapAction(const std::string& ctrlURL, PortType portType) { Mapping mapToRemove(portType, ePort, iPort); + { std::lock_guard<std::mutex> lock(validIgdListMutex_); for (auto const& it : validIgdList_) { @@ -629,26 +716,34 @@ PUPnP::processRemoveMapAction(const std::string& ctrlURL, } if (mapToRemove.getIgd()) { - observer_->onMappingRemoved(mapToRemove.getIgd(), std::move(mapToRemove)); + JAMI_DBG("PUPnP: Closed port %s", mapToRemove.toString().c_str()); + if (observer_) + observer_->onMappingRemoved(mapToRemove.getIgd(), std::move(mapToRemove)); } else { - JAMI_WARN("PUPnP: Did not find matching ctrl URL [%s] for (map %s)", + JAMI_WARN("PUPnP: Did not find matching ctrl URL [%s] for %s", ctrlURL.c_str(), mapToRemove.toString().c_str()); } } void -PUPnP::requestMappingRemove(const Mapping& igdMapping) +PUPnP::requestMappingRemove(const Mapping& mapping) { + auto igd = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd()); + + if (not igd) + return; + std::lock_guard<std::mutex> lock(validIgdListMutex_); - // Iterate over all IGDs in internal list and try to remove selected mapping. for (auto const& it : validIgdList_) { - if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) { - std::lock_guard<std::mutex> lk2(ctrlptMutex_); - actionDeletePortMappingAsync(*igd, - igdMapping.getExternalPortStr(), - igdMapping.getTypeStr()); - return; + if (std::dynamic_pointer_cast<UPnPIGD>(it) == igd) { + // Send remove request using the matching IGD + dht::ThreadPool::io().run([w = weak(), igd, mapping] { + if (auto upnpThis = w.lock()) { + upnpThis->actionDeletePortMapping(igd, mapping); + } + }); + break; } } } @@ -695,13 +790,24 @@ PUPnP::eventTypeToString(Upnp_EventType eventType) int PUPnP::ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data) { - if (auto pupnp = static_cast<PUPnP*>(user_data)) { - std::lock_guard<std::mutex> lk(pupnp->ctrlptMutex_); - return pupnp->handleCtrlPtUPnPEvents(event_type, event); + auto pupnp = static_cast<PUPnP*>(user_data); + + if (pupnp == nullptr) { + JAMI_WARN("PUPnP: Control point callback without PUPnP"); + return UPNP_E_SUCCESS; } - JAMI_WARN("PUPnP: Control point callback without PUPnP"); - return 0; + auto upnpThis = pupnp->weak().lock(); + + if (not upnpThis) + return UPNP_E_SUCCESS; + + // Ignore if already unregistered. + if (not upnpThis->clientRegistered_) + return UPNP_E_SUCCESS; + + // Process the callback. + return upnpThis->handleCtrlPtUPnPEvents(event_type, event); } PUPnP::CtrlAction @@ -760,27 +866,27 @@ PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId, return; } - dwnldlXmlList_.emplace_back( - dht::ThreadPool::io().get<pIGDInfo>([this, location = std::move(igdLocationUrl)] { - IXML_Document* doc_container_ptr = nullptr; - XMLDocument doc_desc_ptr(nullptr, ixmlDocument_free); - int upnp_err = UpnpDownloadXmlDoc(location.c_str(), &doc_container_ptr); - if (doc_container_ptr) - doc_desc_ptr.reset(doc_container_ptr); - pupnpCv_.notify_all(); - if (upnp_err != UPNP_E_SUCCESS or not doc_desc_ptr) { - JAMI_WARN("PUPnP: Error downloading device XML document from %s -> %s", - location.c_str(), - UpnpGetErrorMessage(upnp_err)); - return std::make_unique<IGDInfo>( - IGDInfo {std::move(location), XMLDocument(nullptr, ixmlDocument_free)}); - } else { - JAMI_DBG("PUPnP: Succeeded to download device XML document from %s", - location.c_str()); - return std::make_unique<IGDInfo>( - IGDInfo {std::move(location), std::move(doc_desc_ptr)}); - } - })); + dwnldlXmlList_.emplace_back(dht::ThreadPool::io().get<pIGDInfo>([this, + location = std::move( + igdLocationUrl)] { + IXML_Document* doc_container_ptr = nullptr; + XMLDocument doc_desc(nullptr, ixmlDocument_free); + int upnp_err = UpnpDownloadXmlDoc(location.c_str(), &doc_container_ptr); + doc_desc.reset(doc_container_ptr); + + pupnpCv_.notify_all(); + + if (upnp_err != UPNP_E_SUCCESS or not doc_desc) { + JAMI_WARN("PUPnP: Error downloading device XML document from %s -> %s", + location.c_str(), + UpnpGetErrorMessage(upnp_err)); + return std::make_unique<IGDInfo>( + IGDInfo {std::move(location), XMLDocument(nullptr, ixmlDocument_free)}); + } else { + JAMI_DBG("PUPnP: Succeeded to download device XML document from %s", location.c_str()); + return std::make_unique<IGDInfo>(IGDInfo {std::move(location), std::move(doc_desc)}); + } + })); } void @@ -813,26 +919,26 @@ PUPnP::processDiscoveryAdvertisementByebye(const std::string& cpDeviceId) } // Notify the listener. - if (igd) { + if (observer_ and igd) { observer_->onIgdUpdated(igd, UpnpIgdEvent::REMOVED); } } void -PUPnP::processDiscoverySubscriptionExpired(const std::string& eventSubUrl) +PUPnP::processDiscoverySubscriptionExpired(Upnp_EventType event_type, const std::string& eventSubUrl) { std::lock_guard<std::mutex> lk(validIgdListMutex_); for (auto& it : validIgdList_) { if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) { if (igd->getEventSubURL() == eventSubUrl) { - JAMI_DBG("PUPnP: Received [%s] event for IGD [%s] %s. Will be renewed.", - PUPnP::eventTypeToString(UPNP_EVENT_SUBSCRIPTION_EXPIRED), + JAMI_DBG("PUPnP: Received [%s] event for IGD [%s] %s. Request a new subscribe.", + PUPnP::eventTypeToString(event_type), igd->getUID().c_str(), igd->getLocalIp().toString().c_str()); std::lock_guard<std::mutex> lk1(ctrlptMutex_); UpnpSubscribeAsync(ctrlptHandle_, eventSubUrl.c_str(), - SUBSCRIBE_TIMEOUT, + UPNP_INFINITE, subEventCallback, this); break; @@ -844,10 +950,6 @@ PUPnP::processDiscoverySubscriptionExpired(const std::string& eventSubUrl) int PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event) { - // Ignore if not registered - if (not PUPnP::clientRegistered_) - return UPNP_E_SUCCESS; - switch (event_type) { // "ALIVE" events are processed as "SEARCH RESULT". It might be usefull // if "SEARCH RESULT" was missed. @@ -888,7 +990,6 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event) upnpThis->processDiscoveryAdvertisementByebye(deviceId); } }); - break; } case UPNP_DISCOVERY_SEARCH_TIMEOUT: { @@ -902,36 +1003,63 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event) // Nothing to do. break; } - case UPNP_EVENT_AUTORENEWAL_FAILED: // Fall through. Treat failed autorenewal like an expired - // subscription. + // Treat failed autorenewal like an expired subscription. + case UPNP_EVENT_AUTORENEWAL_FAILED: case UPNP_EVENT_SUBSCRIPTION_EXPIRED: // This event will occur only if autorenewal is disabled. { + JAMI_WARN("PUPnP: Received Subscription Event %s", eventTypeToString(event_type)); const UpnpEventSubscribe* es_event = (const UpnpEventSubscribe*) event; + if (es_event == nullptr) { + JAMI_WARN("PUPnP: Received Subscription Event with null pointer"); + break; + } std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event)); // Process the response on the main thread. - runOnUpnpContextThread([w = weak(), publisherUrl = std::move(publisherUrl)] { + runOnUpnpContextThread([w = weak(), event_type, publisherUrl = std::move(publisherUrl)] { if (auto upnpThis = w.lock()) { - upnpThis->processDiscoverySubscriptionExpired(publisherUrl); + upnpThis->processDiscoverySubscriptionExpired(event_type, publisherUrl); } }); break; } - case UPNP_EVENT_SUBSCRIBE_COMPLETE: { - break; - } + case UPNP_EVENT_SUBSCRIBE_COMPLETE: case UPNP_EVENT_UNSUBSCRIBE_COMPLETE: { + UpnpEventSubscribe* es_event = (UpnpEventSubscribe*) event; + if (es_event == nullptr) { + JAMI_WARN("PUPnP: Received Subscription Event with null pointer"); + } else { + UpnpEventSubscribe_delete(es_event); + } break; } case UPNP_CONTROL_ACTION_COMPLETE: { const UpnpActionComplete* a_event = (const UpnpActionComplete*) event; - - if (UpnpActionComplete_get_ErrCode(a_event) == UPNP_E_SUCCESS) { + if (a_event == nullptr) { + JAMI_WARN("PUPnP: Received Action Complete Event with null pointer"); + break; + } + auto res = UpnpActionComplete_get_ErrCode(a_event); + if (res != UPNP_E_SUCCESS and res != UPNP_E_TIMEDOUT) { + auto err = UpnpActionComplete_get_ErrCode(a_event); + JAMI_WARN("PUPnP: Received Action Complete error %i %s", err, UpnpGetErrorMessage(err)); + } else { auto actionRequest = UpnpActionComplete_get_ActionRequest(a_event); // Abort if there is no action to process. - if (actionRequest == nullptr) + if (actionRequest == nullptr) { + JAMI_WARN("PUPnP: Can't get the Action Request data from the event"); break; + } + + auto actionResult = UpnpActionComplete_get_ActionResult(a_event); + if (actionResult != nullptr) { + ixmlDocument_free(actionResult); + } else { + JAMI_WARN("PUPnP: Action Result document not found"); + } + auto upnpString = UpnpActionComplete_get_CtrlUrl(a_event); + std::string ctrlUrl {UpnpString_get_String(upnpString), UpnpString_get_Length(upnpString)}; @@ -945,6 +1073,8 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event) std::string iPortStr(getFirstDocItem(actionRequest, "NewInternalPort")); std::string portTypeStr(getFirstDocItem(actionRequest, "NewProtocol")); + ixmlDocument_free(actionRequest); + uint16_t ePort = ePortStr.empty() ? 0 : std::stoi(ePortStr); uint16_t iPort = iPortStr.empty() ? 0 : std::stoi(iPortStr); PortType portType = portTypeStr == "UDP" ? upnp::PortType::UDP @@ -957,18 +1087,19 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event) ePort, iPort, portType] { - if (auto upnpThis = w.lock()) { - switch (ctrlAction) { - case CtrlAction::ADD_PORT_MAPPING: - upnpThis->processAddMapAction(ctrlUrl, ePort, iPort, portType); - break; - case CtrlAction::DELETE_PORT_MAPPING: - upnpThis->processRemoveMapAction(ctrlUrl, ePort, iPort, portType); - break; - default: - // All other control actions are ignored. - break; - } + auto upnpThis = w.lock(); + if (not upnpThis) + return; + switch (ctrlAction) { + case CtrlAction::ADD_PORT_MAPPING: + upnpThis->processAddMapAction(ctrlUrl, ePort, iPort, portType); + break; + case CtrlAction::DELETE_PORT_MAPPING: + upnpThis->processRemoveMapAction(ctrlUrl, ePort, iPort, portType); + break; + default: + // All other control actions are ignored. + break; } }); } @@ -994,16 +1125,15 @@ PUPnP::subEventCallback(Upnp_EventType event_type, const void* event, void* user } int -PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType /*unused*/, const void* event) +PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event) { - if (event == nullptr) { + UpnpEventSubscribe* es_event = static_cast<UpnpEventSubscribe*>(const_cast<void*>(event)); + + if (es_event == nullptr) { JAMI_ERR("PUPnP: Unexpected null pointer!"); return UPNP_E_INVALID_ARGUMENT; } - - const UpnpEventSubscribe* es_event = (const UpnpEventSubscribe*) event; std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event)); - int upnp_err = UpnpEventSubscribe_get_ErrCode(es_event); if (upnp_err != UPNP_E_SUCCESS) { JAMI_WARN("PUPnP: Subscription error %s from %s", @@ -1144,9 +1274,9 @@ PUPnP::actionIsIgdConnected(const UPnPIGD& igd) return false; // Action and response pointers. - XMLDocument action(nullptr, ixmlDocument_free); // Action pointer. - XMLDocument response(nullptr, ixmlDocument_free); // Response pointer. + XMLDocument action(nullptr, ixmlDocument_free); // Action pointer. IXML_Document* action_container_ptr = nullptr; + XMLDocument response(nullptr, ixmlDocument_free); // Response pointer. IXML_Document* response_container_ptr = nullptr; // Set action name. @@ -1156,11 +1286,12 @@ PUPnP::actionIsIgdConnected(const UPnPIGD& igd) igd.getServiceType().c_str(), 0, nullptr); - if (not action_container_ptr) { + action.reset(action_container_ptr); + + if (not action) { JAMI_WARN("PUPnP: Failed to make GetStatusInfo action"); return false; } - action.reset(action_container_ptr); int upnp_err = UpnpSendAction(ctrlptHandle_, igd.getControlURL().c_str(), @@ -1168,11 +1299,12 @@ PUPnP::actionIsIgdConnected(const UPnPIGD& igd) nullptr, action.get(), &response_container_ptr); - if (upnp_err != UPNP_E_SUCCESS) { + response.reset(response_container_ptr); + + if (not response or upnp_err != UPNP_E_SUCCESS) { JAMI_WARN("PUPnP: Failed to send GetStatusInfo action -> %s", UpnpGetErrorMessage(upnp_err)); return false; } - response.reset(response_container_ptr); if (errorOnResponse(response.get())) { JAMI_WARN("PUPnP: Failed to get GetStatusInfo from %s -> %d: %s", @@ -1207,11 +1339,12 @@ PUPnP::actionGetExternalIP(const UPnPIGD& igd) IXML_Document* action_container_ptr = nullptr; action_container_ptr = UpnpMakeAction(action_name, igd.getServiceType().c_str(), 0, nullptr); - if (not action_container_ptr) { + action.reset(action_container_ptr); + + if (not action) { JAMI_WARN("PUPnP: Failed to make GetExternalIPAddress action"); return {}; } - action.reset(action_container_ptr); IXML_Document* response_container_ptr = nullptr; int upnp_err = UpnpSendAction(ctrlptHandle_, @@ -1220,12 +1353,13 @@ PUPnP::actionGetExternalIP(const UPnPIGD& igd) nullptr, action.get(), &response_container_ptr); - if (upnp_err != UPNP_E_SUCCESS) { + response.reset(response_container_ptr); + + if (not response or upnp_err != UPNP_E_SUCCESS) { JAMI_WARN("PUPnP: Failed to send GetExternalIPAddress action -> %s", UpnpGetErrorMessage(upnp_err)); return {}; } - response.reset(response_container_ptr); if (errorOnResponse(response.get())) { JAMI_WARN("PUPnP: Failed to get GetExternalIPAddress from %s -> %d: %s", @@ -1255,9 +1389,10 @@ PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string for (int entry_idx = 0;; entry_idx++) { std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); // Action pointer. + IXML_Document* action_container_ptr = nullptr; + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); // Response pointer. - IXML_Document* action_container_ptr = nullptr; IXML_Document* response_container_ptr = nullptr; UpnpAddToAction(&action_container_ptr, @@ -1267,6 +1402,11 @@ PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string std::to_string(entry_idx).c_str()); action.reset(action_container_ptr); + if (not action) { + JAMI_WARN("PUPnP: Failed to add NewPortMappingIndex action"); + break; + } + int upnp_err = UpnpSendAction(ctrlptHandle_, upnpIgd->getControlURL().c_str(), upnpIgd->getServiceType().c_str(), @@ -1274,6 +1414,7 @@ PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string action.get(), &response_container_ptr); response.reset(response_container_ptr); + if (not response) { // No existing mapping. Abort silently. break; @@ -1359,161 +1500,107 @@ PUPnP::deleteMappingsByDescription(const std::shared_ptr<IGD>& igd, const std::s } bool -PUPnP::actionDeletePortMapping(const UPnPIGD& igd, - const std::string& port_external, - const std::string& protocol) +PUPnP::actionAddPortMapping(const std::shared_ptr<UPnPIGD>& igd, const Mapping& mapping) { if (not clientRegistered_) return false; // Action and response pointers. - std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> - action(nullptr, ixmlDocument_free); // Action pointer. - std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> - response(nullptr, ixmlDocument_free); // Response pointer. - IXML_Document* action_container_ptr = nullptr; - - // Set action name. - std::string action_name {"DeletePortMapping"}; - - // Set action sequence. - UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), - "NewRemoteHost", - ""); - UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), - "NewExternalPort", - port_external.c_str()); - UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), - "NewProtocol", - protocol.c_str()); - - action.reset(action_container_ptr); - int upnp_err = UpnpSendActionAsync(ctrlptHandle_, - igd.getControlURL().c_str(), - igd.getServiceType().c_str(), - nullptr, - action.get(), - ctrlPtCallback, - this); - - if (upnp_err != UPNP_E_SUCCESS) { - JAMI_WARN("PUPnP: Failed to send %s request from: %s, %d: %s", - action_name.c_str(), - igd.getServiceType().c_str(), - upnp_err, - UpnpGetErrorMessage(upnp_err)); - return false; - } - - JAMI_DBG("PUPnP: Successfully sent %s request to %s", - action_name.c_str(), - igd.getServiceType().c_str()); - - return true; -} - -bool -PUPnP::actionAddPortMapping(const UPnPIGD& igd, - const Mapping& mapping, - UPnPProtocol::UpnpError& error_code) -{ - if (not clientRegistered_) - return false; - - error_code = UPnPProtocol::UpnpError::ERROR_OK; - - // Action and response pointers. - XMLDocument action(nullptr, ixmlDocument_free); // Action pointer. - XMLDocument response(nullptr, ixmlDocument_free); // Response pointer. + XMLDocument action(nullptr, ixmlDocument_free); IXML_Document* action_container_ptr = nullptr; + XMLDocument response(nullptr, ixmlDocument_free); IXML_Document* response_container_ptr = nullptr; - // Set action name. - std::string action_name {"AddPortMapping"}; - // Set action sequence. UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewRemoteHost", ""); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewExternalPort", mapping.getExternalPortStr().c_str()); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewProtocol", mapping.getTypeStr()); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewInternalPort", mapping.getInternalPortStr().c_str()); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewInternalClient", getHostAddress().toString().c_str()); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewEnabled", "1"); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewPortMappingDescription", mapping.toString().c_str()); UpnpAddToAction(&action_container_ptr, - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), "NewLeaseDuration", "0"); action.reset(action_container_ptr); int upnp_err = UpnpSendAction(ctrlptHandle_, - igd.getControlURL().c_str(), - igd.getServiceType().c_str(), + igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_container_ptr); + response.reset(response_container_ptr); + if (upnp_err != UPNP_E_SUCCESS) { JAMI_WARN("PUPnP: Failed to send action %s from: %s, %d: %s", - action_name.c_str(), - igd.getServiceType().c_str(), + ACTION_ADD_PORT_MAPPING, + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); - error_code = UPnPProtocol::UpnpError::INVALID_ERR; return false; } - if (not response_container_ptr) { - JAMI_WARN("PUPnP: Failed to get response from %s", action_name.c_str()); + if (not response) { + JAMI_WARN("PUPnP: Failed to get response from %s", ACTION_ADD_PORT_MAPPING); return false; } - response.reset(response_container_ptr); // Check if there is an error code. std::string errorCode = getFirstDocItem(response.get(), "errorCode"); if (not errorCode.empty()) { std::string errorDescription = getFirstDocItem(response.get(), "errorDescription"); JAMI_WARN("PUPnP: %s returned with error: %s: %s", - action_name.c_str(), + ACTION_ADD_PORT_MAPPING, errorCode.c_str(), errorDescription.c_str()); - error_code = UPnPProtocol::UpnpError::INVALID_ERR; return false; } + + JAMI_DBG("PUPnP: Sent request to open port %s", mapping.toString().c_str()); + + // Process the response on the main thread. + runOnUpnpContextThread([w = weak(), mapping, igd] { + auto upnpThis = w.lock(); + if (not upnpThis) + return; + upnpThis->processAddMapAction(igd->getControlURL(), + mapping.getExternalPort(), + mapping.getInternalPort(), + mapping.getType()); + }); + return true; } @@ -1563,5 +1650,86 @@ PUPnP::actionDeletePortMappingAsync(const UPnPIGD& igd, return true; } +bool +PUPnP::actionDeletePortMapping(const std::shared_ptr<UPnPIGD>& igd, const Mapping& mapping) +{ + if (not clientRegistered_) { + return false; + } + + // Action and response pointers. + XMLDocument action(nullptr, ixmlDocument_free); + IXML_Document* action_container_ptr = nullptr; + XMLDocument response(nullptr, ixmlDocument_free); + IXML_Document* response_container_ptr = nullptr; + + // Set action sequence. + UpnpAddToAction(&action_container_ptr, + ACTION_DELETE_PORT_MAPPING, + igd->getServiceType().c_str(), + "NewRemoteHost", + ""); + UpnpAddToAction(&action_container_ptr, + ACTION_DELETE_PORT_MAPPING, + igd->getServiceType().c_str(), + "NewExternalPort", + mapping.getExternalPortStr().c_str()); + UpnpAddToAction(&action_container_ptr, + ACTION_DELETE_PORT_MAPPING, + igd->getServiceType().c_str(), + "NewProtocol", + mapping.getTypeStr()); + + action.reset(action_container_ptr); + + int upnp_err = UpnpSendAction(ctrlptHandle_, + igd->getControlURL().c_str(), + igd->getServiceType().c_str(), + nullptr, + action.get(), + &response_container_ptr); + response.reset(response_container_ptr); + + if (upnp_err != UPNP_E_SUCCESS) { + JAMI_WARN("PUPnP: Failed to send action %s from: %s, %d: %s", + ACTION_DELETE_PORT_MAPPING, + igd->getServiceType().c_str(), + upnp_err, + UpnpGetErrorMessage(upnp_err)); + return false; + } + + if (not response) { + JAMI_WARN("PUPnP: Failed to get response from %s", ACTION_DELETE_PORT_MAPPING); + return false; + } + + // Check if there is an error code. + std::string errorCode = getFirstDocItem(response.get(), "errorCode"); + if (not errorCode.empty()) { + std::string errorDescription = getFirstDocItem(response.get(), "errorDescription"); + JAMI_WARN("PUPnP: %s returned with error: %s: %s", + ACTION_DELETE_PORT_MAPPING, + errorCode.c_str(), + errorDescription.c_str()); + return false; + } + + JAMI_DBG("PUPnP: Sent request to close port %s", mapping.toString().c_str()); + + // Process the response on the main thread. + runOnUpnpContextThread([w = weak(), mapping, igd] { + auto upnpThis = w.lock(); + if (not upnpThis) + return; + upnpThis->processRemoveMapAction(igd->getControlURL(), + mapping.getExternalPort(), + mapping.getInternalPort(), + mapping.getType()); + }); + + return true; +} + } // namespace upnp } // namespace jami diff --git a/src/upnp/protocol/pupnp/pupnp.h b/src/upnp/protocol/pupnp/pupnp.h index 3290bab6ae65e737c4fa1bdff2052bdce4a3d750..e490f5ffd6fdd19662f1773d6b2485c427f5e62a 100644 --- a/src/upnp/protocol/pupnp/pupnp.h +++ b/src/upnp/protocol/pupnp/pupnp.h @@ -69,7 +69,6 @@ constexpr static int CONFLICT_IN_MAPPING = 718; // Timeout values (in seconds). constexpr static unsigned int SEARCH_TIMEOUT {60}; -constexpr static unsigned int SUBSCRIBE_TIMEOUT {300}; // Max number of IGD search attempts before failure. constexpr static unsigned int PUPNP_MAX_RESTART_SEARCH_RETRIES {5}; // Time-out between two successive IGD search. @@ -98,15 +97,9 @@ public: PUPnP(); ~PUPnP(); - bool initUpnpLib(); - bool registerClient(); - // Set the observer void setObserver(UpnpMappingObserver* obs) override; - // Start search for UPNP devices - void searchForDevices(); - // Returns the protocol type. NatProtocolType getProtocol() const override { return NatProtocolType::PUPNP; } @@ -142,9 +135,25 @@ public: // Removes a mapping. void requestMappingRemove(const Mapping& igdMapping) override; + void terminate() override; + private: + NON_COPYABLE(PUPnP); + ScheduledExecutor* getUpnContextScheduler() { return UpnpThreadUtil::getScheduler(); } + // Init lib-upnp + void initUpnpLib(); + + // Register the client + void registerClient(); + + // Start the internal thread. + void startPUPnP(); + + // Start search for UPNP devices + void searchForDevices(); + // Return true if it has at least one valid IGD. bool hasValidIgd() const; @@ -168,12 +177,12 @@ private: uint16_t iPort, PortType portType); - // Returns control point action callback based on xml node. - CtrlAction getAction(const char* xmlNode); - // Validate IGD from the xml document received from the router. bool validateIgd(const IGDInfo&); + // Returns control point action callback based on xml node. + static CtrlAction getAction(const char* xmlNode); + // Control point callback. static int ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data); #if UPNP_VERSION < 10800 @@ -187,7 +196,8 @@ private: const std::string& igdUrl, const IpAddr& dstAddr); void processDiscoveryAdvertisementByebye(const std::string& deviceId); - void processDiscoverySubscriptionExpired(const std::string& eventSubUrl); + void processDiscoverySubscriptionExpired(Upnp_EventType event_type, + const std::string& eventSubUrl); // Callback event handler function for the UPnP client (control point). int handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event); @@ -202,7 +212,7 @@ private: #endif // Callback subscription event function for handling subscription request. - int handleSubscriptionUPnPEvent(Upnp_EventType /*unused*/, const void* event); + int handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event); // Parses the IGD candidate. std::unique_ptr<UPnPIGD> parseIgd(IXML_Document* doc, std::string locationUrl); @@ -212,30 +222,24 @@ private: bool actionIsIgdConnected(const UPnPIGD& igd); IpAddr actionGetExternalIP(const UPnPIGD& igd); - bool actionDeletePortMapping(const UPnPIGD& igd, - const std::string& port_external, - const std::string& protocol); - bool actionAddPortMapping(const UPnPIGD& igd, - const Mapping& mapping, - UPnPProtocol::UpnpError& upnp_error); - bool actionAddPortMappingAsync(const UPnPIGD& igd, const Mapping& mapping); + bool actionAddPortMapping(const std::shared_ptr<UPnPIGD>& igd, const Mapping& mapping); + bool actionAddPortMappingAsync(const std::shared_ptr<UPnPIGD>& igd, const Mapping& mapping); bool actionDeletePortMappingAsync(const UPnPIGD& igd, const std::string& port_external, const std::string& protocol); + bool actionDeletePortMapping(const std::shared_ptr<UPnPIGD>& igd, const Mapping& mapping); // Event type to string static const char* eventTypeToString(Upnp_EventType eventType); -private: - // Indicates of the client is registered. Since at most only one instance - // of this class is created, using a static is unambiguous. - std::atomic_bool clientRegistered_ {false}; - std::weak_ptr<PUPnP> weak() { return std::static_pointer_cast<PUPnP>(shared_from_this()); } - NON_COPYABLE(PUPnP); + // Initialization status. + std::atomic_bool initialized_ {false}; + // Client registration status. + std::atomic_bool clientRegistered_ {false}; std::condition_variable pupnpCv_ {}; // Condition variable for thread-safe signaling. - std::atomic_bool pupnpRun_ {true}; // Variable to allow the thread to run. + std::atomic_bool pupnpRun_ {false}; // Variable to allow the thread to run. std::thread pupnpThread_ {}; // PUPnP thread for non-blocking client registration. std::shared_ptr<Task> searchForIgdTimer_ {}; unsigned int igdSearchCounter_ {0}; @@ -248,8 +252,7 @@ private: dwnldlXmlList_; // List of futures for blocking xml download function calls. std::list<std::future<pIGDInfo>> cancelXmlList_; // List of abandoned documents - mutable std::mutex igdListMutex_; // Mutex used to protect IGD instances. - + mutable std::mutex igdListMutex_; // Mutex used to protect IGD instances. std::mutex ctrlptMutex_; // Mutex for client handle protection. UpnpClient_Handle ctrlptHandle_ {-1}; // Control point handle. diff --git a/src/upnp/protocol/upnp_protocol.h b/src/upnp/protocol/upnp_protocol.h index eed6cf00f6ed936e18160efa5291896acc44f90c..ce229b2f13c195268f5c13a16480e8fc9807f433 100644 --- a/src/upnp/protocol/upnp_protocol.h +++ b/src/upnp/protocol/upnp_protocol.h @@ -124,10 +124,13 @@ public: // Get the host (local) address. const IpAddr& getHostAddress() const { return hostAddress_; } + // Terminate + virtual void terminate() = 0; + protected: // The host (local) address. Must be fully set before making any request. IpAddr hostAddress_ {}; - UpnpMappingObserver* observer_; + UpnpMappingObserver* observer_ {nullptr}; }; } // namespace upnp diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp index e52f86bc1024df8b837c090da3519fd16d077f0c..f4031a92dbdcf51718b3c2fb6d5097c6c1d4f054 100644 --- a/src/upnp/upnp_context.cpp +++ b/src/upnp/upnp_context.cpp @@ -35,14 +35,6 @@ constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000}; constexpr static uint16_t UPNP_UDP_PORT_MIN {20000}; constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000}; -std::shared_ptr<UPnPContext> -UPnPContext::getUPnPContext() -{ - // This is the unique shared instance (singleton) of UPnPContext class. - static auto context = std::make_shared<UPnPContext>(); - return context; -} - UPnPContext::UPnPContext() { JAMI_DBG("Creating UPnPContext instance [%p]", this); @@ -57,15 +49,39 @@ UPnPContext::UPnPContext() } } -UPnPContext::~UPnPContext() +std::shared_ptr<UPnPContext> +UPnPContext::getUPnPContext() { + // This is the unique shared instance (singleton) of UPnPContext class. + static auto context = std::make_shared<UPnPContext>(); + return context; +} + +void +UPnPContext::shutdown() +{ + if (not isValidThread()) { + runOnUpnpContextThread([this] { shutdown(); }); + return; + } + + JAMI_DBG("Shutdown UPnPContext instance [%p]", this); + stopUpnp(true); + for (auto const& [_, proto] : protocolList_) + proto->terminate(); + std::lock_guard<std::mutex> lock(mappingMutex_); mappingList_->clear(); - mappingListUpdateTimer_->cancel(); + if (mappingListUpdateTimer_) + mappingListUpdateTimer_->cancel(); controllerList_.clear(); + protocolList_.clear(); +} +UPnPContext::~UPnPContext() +{ JAMI_DBG("UPnPContext instance [%p] destroyed", this); } @@ -113,7 +129,7 @@ UPnPContext::stopUpnp(bool forceRelease) return; } - JAMI_DBG("Stoping UPNP context"); + JAMI_DBG("Stopping UPNP context"); // Clear all current mappings if any. @@ -186,8 +202,6 @@ UPnPContext::connectivityChanged() return; } - CHECK_VALID_THREAD(); - if (controllerList_.empty()) return; @@ -297,8 +311,6 @@ UPnPContext::releaseMapping(const Mapping& map) return; } - CHECK_VALID_THREAD(); - auto mapPtr = getMappingWithKey(map.getMapKey()); if (not mapPtr) { @@ -327,11 +339,11 @@ UPnPContext::registerController(void* controller) auto ret = controllerList_.emplace(controller); if (not ret.second) { - JAMI_WARN("Controller %p is already registered", this); + JAMI_WARN("Controller %p is already registered", controller); return; } - JAMI_DBG("Successfully registered controller %p", this); + JAMI_DBG("Successfully registered controller %p", controller); if (not started_) startUpnp(); } @@ -345,13 +357,14 @@ UPnPContext::unregisterController(void* controller) } if (controllerList_.erase(controller) == 1) { - JAMI_DBG("Successfully unregistered controller %p", this); + JAMI_DBG("Successfully unregistered controller %p", controller); } else { - JAMI_ERR("Trying to unregister an unknown controller %p", this); + JAMI_DBG("Controller %p was already removed", controller); } - if (controllerList_.empty()) + if (controllerList_.empty()) { stopUpnp(); + } } uint16_t @@ -385,8 +398,6 @@ UPnPContext::requestMapping(const Mapping::sharedPtr_t& map) return; } - CHECK_VALID_THREAD(); - auto const& igd = getPreferredIgd(); // We must have at least a valid IGD pointer if we get here. // Not this method is called only if there were a valid IGD, however, @@ -836,8 +847,6 @@ UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event) return; } - CHECK_VALID_THREAD(); - char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED" : event == UpnpIgdEvent::REMOVED ? "REMOVED" : "INVALID"; @@ -1023,8 +1032,6 @@ UPnPContext::deleteAllMappings(PortType type) return; } - CHECK_VALID_THREAD(); - std::lock_guard<std::mutex> lock(mappingMutex_); auto& mappingList = getMappingList(type); @@ -1044,8 +1051,6 @@ UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& ma return; } - CHECK_VALID_THREAD(); - auto map = getMappingWithKey(mapRes.getMapKey()); // Notify the listener. if (map and map->getNotifyCallback()) diff --git a/src/upnp/upnp_context.h b/src/upnp/upnp_context.h index d6879ff8c9930fd56a147cc315fa7b1737127bdb..6ce476a03e0fcf399cf5e2aa7bad2beed953d69d 100644 --- a/src/upnp/upnp_context.h +++ b/src/upnp/upnp_context.h @@ -90,9 +90,12 @@ public: UPnPContext(); ~UPnPContext(); - // Retrieve the UPnPContext singleton + // Retrieve the UPnPContext singleton. static std::shared_ptr<UPnPContext> getUPnPContext(); + // Terminate the instance. + void shutdown(); + // Set the known public address void setPublicAddress(const IpAddr& addr);