diff --git a/configure.ac b/configure.ac index ab744a105d252114acb1495a5c790809d201991f..10e7970da289b3ec8e2d07ff5df498aef1c881fb 100644 --- a/configure.ac +++ b/configure.ac @@ -531,20 +531,6 @@ AS_IF([test "x$with_upnp" = "xyes"], AC_DEFINE([HAVE_LIBUPNP], 0, [Define if you have libupnp])]) ]) -# LIBNATPMP -dnl check for libnatpmp -AC_ARG_WITH([natpmp], [AS_HELP_STRING([--without-natpmp], - [disable support for NAT-PMP])], [], [with_natpmp=yes]) - -AS_IF([test "x$with_natpmp" != xno], - [AC_CHECK_HEADER([natpmp.h], - AC_CHECK_LIB([natpmp], [initnatpmp], [], [with_natpmp=no]), - [AC_MSG_WARN([Unable to find the libnatpmp headers (you may need to install the dev package). You may use --without-natpmp to compile without NAT-PMP support.]); - with_natpmp=no]) - ],[]) - -AC_DEFINE_UNQUOTED([HAVE_LIBNATPMP], `if test "x$with_natpmp" != xno; then echo 1; else echo 0; fi`, [Define if you have libnatpmp]) - AC_DEFINE_UNQUOTED([HAVE_SHM], `if test -z "${HAVE_LINUX_TRUE}" && test -z "${HAVE_ANDROID_FALSE}" ; then echo 1; else echo 0; fi`, [Define if you have shared memory support]) diff --git a/contrib/src/natpmp/SHA512SUMS b/contrib/src/natpmp/SHA512SUMS deleted file mode 100644 index 956a4e3cd0e0b5c63bfb954a67de4d1e6f9ff8d0..0000000000000000000000000000000000000000 --- a/contrib/src/natpmp/SHA512SUMS +++ /dev/null @@ -1 +0,0 @@ -e50b1f68ce9254bb2f068ddc37417a3c417b80f7b3fb3d84e3e9af4a144d89e204ab993b54c01657335e855d0124a8fcbbf96ce78db7b9ae0b03b6eb79de2e09 libnatpmp-20150609.tar.gz diff --git a/contrib/src/natpmp/rules.mak b/contrib/src/natpmp/rules.mak deleted file mode 100644 index 49ddcb006cb57005560838125889afe126553c98..0000000000000000000000000000000000000000 --- a/contrib/src/natpmp/rules.mak +++ /dev/null @@ -1,21 +0,0 @@ -# libnatpmp -NATPMP_VERSION := 20150609 -NATPMP_URL := http://miniupnp.free.fr/files/download.php?file=libnatpmp-$(NATPMP_VERSION).tar.gz - -PKGS += natpmp -ifeq ($(call need_pkg,'libnatpmp'),) -PKGS_FOUND += natpmp -endif - -$(TARBALLS)/libnatpmp-$(NATPMP_VERSION).tar.gz: - $(call download,$(NATPMP_URL)) - -.sum-natpmp: libnatpmp-$(NATPMP_VERSION).tar.gz - -natpmp: libnatpmp-$(NATPMP_VERSION).tar.gz .sum-natpmp - $(UNPACK) - $(MOVE) - -.natpmp: natpmp - cd $< && $(MAKE) INSTALLPREFIX="$(PREFIX)" install - touch $@ diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp index ace784f2c440977da2945b99a18e25c2b8f80260..8c95f4ab116e0bf8b72c18f054fcc4a01b9e7a4c 100644 --- a/src/upnp/upnp_context.cpp +++ b/src/upnp/upnp_context.cpp @@ -38,10 +38,6 @@ #include <upnp/upnptools.h> #endif -#if HAVE_LIBNATPMP -#include <natpmp.h> -#endif - #include "logger.h" #include "ip_utils.h" #include "upnp_igd.h" @@ -65,20 +61,6 @@ getUPnPContext() return context; } -/* UPnP error codes */ -constexpr static int INVALID_ARGS = 402; -constexpr static int ARRAY_IDX_INVALID = 713; -constexpr static int CONFLICT_IN_MAPPING = 718; - -/* max number of times to retry mapping if it fails due to conflict; - * there isn't much logic in picking this number... ideally not many ports should - * be mapped in a system, so a few number of random port retries should work; - * a high number of retries would indicate there might be some kind of bug or else - * incompatibility with the router; we use it to prevent an infinite loop of - * retrying to map the entry - */ -constexpr static unsigned MAX_RETRIES = 20; - #if HAVE_LIBUPNP /* UPnP IGD definitions */ @@ -89,10 +71,23 @@ constexpr static const char * UPNP_WANCON_DEVICE = "urn:schemas-upnp-org:device: constexpr static const char * UPNP_WANIP_SERVICE = "urn:schemas-upnp-org:service:WANIPConnection:1"; constexpr static const char * UPNP_WANPPP_SERVICE = "urn:schemas-upnp-org:service:WANPPPConnection:1"; +/* UPnP error codes */ +constexpr static int INVALID_ARGS = 402; constexpr static const char * INVALID_ARGS_STR = "402"; +constexpr static int ARRAY_IDX_INVALID = 713; constexpr static const char * ARRAY_IDX_INVALID_STR = "713"; +constexpr static int CONFLICT_IN_MAPPING = 718; constexpr static const char * CONFLICT_IN_MAPPING_STR = "718"; +/* max number of times to retry mapping if it fails due to conflict; + * there isn't much logic in picking this number... ideally not many ports should + * be mapped in a system, so a few number of random port retries should work; + * a high number of retries would indicate there might be some kind of bug or else + * incompatibility with the router; we use it to prevent an infinite loop of + * retrying to map the entry + */ +constexpr static unsigned MAX_RETRIES = 20; + /* * Local prototypes */ @@ -101,68 +96,18 @@ static std::string get_first_doc_item(IXML_Document*, const char*); static std::string get_first_element_item(IXML_Element*, const char*); static void checkResponseError(IXML_Document*); -#else - -constexpr static int UPNP_E_SUCCESS = 0; +static int +cp_callback(Upnp_EventType event_type, void* event, void* user_data) +{ + if (auto upnpContext = static_cast<UPnPContext*>(user_data)) + return upnpContext->handleUPnPEvents(event_type, event); -#endif // HAVE_LIBUPNP + RING_WARN("UPnP callback without UPnPContext"); + return 0; +} UPnPContext::UPnPContext() { -#if HAVE_LIBNATPMP - pmpThread_ = std::thread([this]() { - PMPIGD* pmp_igd = new PMPIGD(); - natpmp_t natpmp; - bool found {false}; - - while (pmpRun_) { - if (initnatpmp(&natpmp, 0, 0) < 0) { - RING_ERR("NAT-PMP: can't initialize libnatpmp"); - std::unique_lock<std::mutex> lk(pmpMutex_); - pmpCv_.wait_for(lk, std::chrono::minutes(1)); - } else { - RING_DBG("NAT-PMP: initialized"); - break; - } - } - - while (pmpRun_) { - std::unique_lock<std::mutex> lk(pmpMutex_); - pmpCv_.wait_until(lk, pmp_igd->getRenewalTime(), [&] { - return not pmpRun_ or pmp_igd->getRenewalTime() <= clock::now(); - }); - if (not pmpRun_) break; - - auto now = clock::now(); - - if (pmp_igd->renewal_ < now) { - PMPsearchForIGD(pmp_igd, natpmp, found); - } - if (found) { - if (pmp_igd->clearAll_) { - PMPdeleteAllPortMapping(*pmp_igd, natpmp, NATPMP_PROTOCOL_UDP); - PMPdeleteAllPortMapping(*pmp_igd, natpmp, NATPMP_PROTOCOL_TCP); - pmp_igd->clearAll_ = false; - pmp_igd->toRemove_.clear(); - } else if (not pmp_igd->toRemove_.empty()) { - for (auto& m : pmp_igd->toRemove_) - PMPaddPortMapping(*pmp_igd, natpmp, m, true); - pmp_igd->toRemove_.clear(); - } - auto mapping = pmp_igd->getNextMappingToRenew(); - if (mapping and mapping->renewal_ < now) - PMPaddPortMapping(*pmp_igd, natpmp, *mapping); - } - } - if (not found) delete pmp_igd; - closenatpmp(&natpmp); - RING_DBG("NAT-PMP: ended"); - }); - clientRegistered_ = true; -#endif - -#if HAVE_LIBUPNP - int upnp_err; char* ip_address = nullptr; unsigned short port = 0; @@ -210,39 +155,19 @@ UPnPContext::UPnPContext() * we will probably receive their advertisements either way */ searchForIGD(); -#endif } UPnPContext::~UPnPContext() { /* make sure everything is unregistered, freed, and UpnpFinish() is called */ + { std::lock_guard<std::mutex> lock(validIGDMutex_); for( auto const &it : validIGDs_) { -#if HAVE_LIBUPNP - if (auto igd = dynamic_cast<UPnPIGD*>(it.second.get())) - removeMappingsByLocalIPAndDescription(*igd, Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); -#endif -#if HAVE_LIBNATPMP - if (auto pmp = dynamic_cast<PMPIGD*>(it.second.get())) { - { - std::lock_guard<std::mutex> lk(pmpMutex_); - pmp->clearAll(); - } - pmpCv_.notify_all(); - } -#endif + removeMappingsByLocalIPAndDescription(it.second.get(), Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); } } -#if HAVE_LIBNATPMP - pmpRun_ = false; - pmpCv_.notify_all(); - if (pmpThread_.joinable()) - pmpThread_.join(); -#endif - -#if HAVE_LIBUPNP if (clientRegistered_) UpnpUnRegisterClient( ctrlptHandle_ ); @@ -252,7 +177,21 @@ UPnPContext::~UPnPContext() #ifndef _WIN32 UpnpFinish(); #endif -#endif +} + +void +UPnPContext::searchForIGD() +{ + 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, this); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this); } bool @@ -300,11 +239,7 @@ UPnPContext::chooseIGD_unlocked() const { if (validIGDs_.empty()) return nullptr; - auto natpmp = validIGDs_.find("NATPMP"); - if (natpmp == validIGDs_.end()) - return validIGDs_.begin()->second.get(); - else - return natpmp->second.get(); + return validIGDs_.begin()->second.get(); } /** @@ -349,21 +284,11 @@ UPnPContext::addMapping(IGD* igd, } /* mapping doesn't exist, so try to add it */ - RING_DBG("adding port mapping : %s", mapping.toString().c_str()); + RING_DBG("UPnP: adding port mapping : %s", mapping.toString().c_str()); -#if HAVE_LIBUPNP - auto upnp = dynamic_cast<const UPnPIGD*>(igd); - if (not upnp or addPortMapping(*upnp, mapping, upnp_error)) -#endif - { + if(addPortMapping(igd, mapping, upnp_error)) { /* success; add it to global list */ globalMappings->emplace(port_external, std::move(GlobalMapping{mapping})); -#if HAVE_LIBNATPMP -#if HAVE_LIBUPNP - if (not upnp) -#endif - pmpCv_.notify_all(); -#endif return mapping; } return {}; @@ -379,17 +304,17 @@ generateRandomPort() /* define the range */ static std::uniform_int_distribution<uint16_t> dist(Mapping::UPNP_PORT_MIN, Mapping::UPNP_PORT_MAX); - return dist(gen); + return dist(gen);; } /** * chooses a random port that is not yet used by the daemon for UPnP */ uint16_t -UPnPContext::chooseRandomPort(const IGD& igd, PortType type) +UPnPContext::chooseRandomPort(const IGD* igd, PortType type) { auto globalMappings = type == PortType::UDP ? - &igd.udpMappings : &igd.tcpMappings; + &igd->udpMappings : &igd->tcpMappings; uint16_t port = generateRandomPort(); @@ -438,7 +363,7 @@ UPnPContext::addAnyMapping(uint16_t port_desired, auto iter = globalMappings->find(port_desired); if (iter != globalMappings->end()) { /* port already used, we need a unique port */ - port_desired = chooseRandomPort(*igd, type); + port_desired = chooseRandomPort(igd, type); } } @@ -463,7 +388,7 @@ UPnPContext::addAnyMapping(uint16_t port_desired, RING_DBG("UPnP: mapping failed (conflicting entry? err = %d), trying with a different port.", upnp_error); /* TODO: make sure we don't try sellecting the same random port twice if it fails ? */ - port_desired = chooseRandomPort(*igd, type); + port_desired = chooseRandomPort(igd, type); if (use_same_port) port_local = port_desired; mapping = addMapping(igd, port_desired, port_local, type, &upnp_error); @@ -498,33 +423,21 @@ UPnPContext::removeMapping(const Mapping& mapping) auto iter = globalMappings->find(mapping.getPortExternal()); if ( iter != globalMappings->end() ) { /* make sure its the same mapping */ - GlobalMapping& global_mapping = iter->second; - if (mapping == global_mapping ) { + GlobalMapping *global_mapping = &iter->second; + if (mapping == *global_mapping ) { /* now check the users */ - if (global_mapping.users > 1) { + if (global_mapping->users > 1) { /* more than one user, simply decrement the number */ - --(global_mapping.users); + --(global_mapping->users); RING_DBG("UPnP: decrementing users of mapping: %s, %d users remaining", - mapping.toString().c_str(), global_mapping.users); + mapping.toString().c_str(), global_mapping->users); } else { /* no other users, can delete */ RING_DBG("UPnP: removing port mapping : %s", mapping.toString().c_str()); -#if HAVE_LIBUPNP - if (auto upnp = dynamic_cast<UPnPIGD*>(igd)) - deletePortMapping(*upnp, - mapping.getPortExternalStr(), - mapping.getTypeStr()); -#endif -#if HAVE_LIBNATPMP - if (auto pmp = dynamic_cast<PMPIGD*>(igd)) { - { - std::lock_guard<std::mutex> lk(pmpMutex_); - pmp->toRemove_.emplace_back(std::move(global_mapping)); - } - pmpCv_.notify_all(); - } -#endif + deletePortMapping(igd, + mapping.getPortExternalStr(), + mapping.getTypeStr()); globalMappings->erase(iter); } } else { @@ -565,117 +478,6 @@ UPnPContext::getExternalIP() const return {}; } -#if HAVE_LIBNATPMP - -void -UPnPContext::PMPsearchForIGD(PMPIGD* pmp_igd, natpmp_t& natpmp, bool& found) -{ - if (sendpublicaddressrequest(&natpmp) < 0) { - RING_ERR("NAT-PMP: can't send request"); - pmp_igd->renewal_ = clock::now() + std::chrono::minutes(1); - return; - } - - while (pmpRun_) { - natpmpresp_t response; - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - auto r = readnatpmpresponseorretry(&natpmp, &response); - if (r < 0 && r != NATPMP_TRYAGAIN) { - RING_WARN("NAT-PMP: can't find device"); - pmp_igd->renewal_ = clock::now() + std::chrono::minutes(5); - break; - } - else if (r != NATPMP_TRYAGAIN) { - pmp_igd->localIp = ip_utils::getLocalAddr(AF_INET); - pmp_igd->publicIp = IpAddr(response.pnu.publicaddress.addr); - if (not found) { - found = true; - RING_DBG("NAT-PMP: found new device"); - RING_DBG("NAT-PMP: got external IP: %s", pmp_igd->publicIp.toString().c_str()); - { - std::lock_guard<std::mutex> lock(validIGDMutex_); - validIGDs_.emplace("NATPMP", std::unique_ptr<IGD>(pmp_igd)); - validIGDCondVar_.notify_all(); - for (const auto& l : igdListeners_) - l.second(); - } - } - pmp_igd->renewal_ = clock::now() + std::chrono::minutes(1); - break; - } - } -} - -void -UPnPContext::PMPaddPortMapping(const PMPIGD& /*pmp_igd*/, natpmp_t& natpmp, GlobalMapping& mapping, bool remove) const -{ - if (sendnewportmappingrequest(&natpmp, - mapping.getType() == PortType::UDP ? NATPMP_PROTOCOL_UDP : NATPMP_PROTOCOL_TCP, - mapping.getPortInternal(), - mapping.getPortExternal(), remove ? 0 : 3600) < 0) { - RING_ERR("NAT-PMP: can't send port mapping request"); - mapping.renewal_ = clock::now() + std::chrono::minutes(1); - return; - } - RING_DBG("NAT-PMP: sent port mapping %srequest", remove ? "removal " : ""); - while (pmpRun_) { - natpmpresp_t response; - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - auto r = readnatpmpresponseorretry(&natpmp, &response); - if (r < 0 && r != NATPMP_TRYAGAIN) { - RING_ERR("NAT-PMP: can't %sregister port mapping", remove ? "un" : ""); - break; - } - else if (r != NATPMP_TRYAGAIN) { - mapping.renewal_ = clock::now() - + std::chrono::seconds(response.pnu.newportmapping.lifetime/2); - break; - } - } -} - -void -UPnPContext::PMPdeleteAllPortMapping(const PMPIGD& /*pmp_igd*/, natpmp_t& natpmp, int proto) const -{ - if (sendnewportmappingrequest(&natpmp, proto, 0, 0, 0) < 0) { - RING_ERR("NAT-PMP: can't send all port mapping removal request"); - return; - } - RING_DBG("NAT-PMP: sent all port mapping removal request"); - while (pmpRun_) { - natpmpresp_t response; - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - auto r = readnatpmpresponseorretry(&natpmp, &response); - if (r < 0 && r != NATPMP_TRYAGAIN) { - RING_ERR("NAT-PMP: can't remove all port mappings"); - break; - } - else if (r != NATPMP_TRYAGAIN) { - break; - } - } -} - -#endif /* HAVE_LIBNATPMP */ - - -#if HAVE_LIBUPNP - -void -UPnPContext::searchForIGD() -{ - 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, this); - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this); - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this); - UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this); -} - /** * Parses the device description and adds desired devices to * relevant lists @@ -726,7 +528,7 @@ UPnPContext::parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event) } } - std::unique_ptr<UPnPIGD> new_igd; + std::unique_ptr<IGD> new_igd; int upnp_err; std::string friendlyName = get_first_doc_item(doc, "friendlyName"); @@ -814,9 +616,9 @@ UPnPContext::parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event) if (not (serviceId.empty() and controlURL.empty() and eventSubURL.empty()) ) { /* RING_DBG("UPnP: got service info from device:\n\tserviceType: %s\n\tserviceID: %s\n\tcontrolURL: %s\n\teventSubURL: %s", serviceType.c_str(), serviceId.c_str(), controlURL.c_str(), eventSubURL.c_str()); */ - new_igd.reset(new UPnPIGD(UDN, baseURL, friendlyName, serviceType, serviceId, controlURL, eventSubURL)); - if (isIGDConnected(*new_igd)) { - new_igd->publicIp = getExternalIP(*new_igd); + new_igd.reset(new IGD(UDN, baseURL, friendlyName, serviceType, serviceId, controlURL, eventSubURL)); + if (isIGDConnected(new_igd.get())) { + new_igd->publicIp = getExternalIP(new_igd.get()); if (new_igd->publicIp) { RING_DBG("UPnP: got external IP: %s", new_igd->publicIp.toString().c_str()); new_igd->localIp = ip_utils::getLocalAddr(pj_AF_INET()); @@ -844,7 +646,7 @@ UPnPContext::parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event) { std::lock_guard<std::mutex> lock(validIGDMutex_); /* delete all RING mappings first */ - removeMappingsByLocalIPAndDescription(*new_igd, Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); + removeMappingsByLocalIPAndDescription(new_igd.get(), Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); validIGDs_.emplace(UDN, std::move(new_igd)); validIGDCondVar_.notify_all(); for (const auto& l : igdListeners_) @@ -895,17 +697,6 @@ get_first_element_item(IXML_Element* element, const char* item) } return ret; } - -int -UPnPContext::cp_callback(Upnp_EventType event_type, void* event, void* user_data) -{ - if (auto upnpContext = static_cast<UPnPContext*>(user_data)) - return upnpContext->handleUPnPEvents(event_type, event); - - RING_WARN("UPnP callback without UPnPContext"); - return 0; -} - int UPnPContext::handleUPnPEvents(Upnp_EventType event_type, void* event) { @@ -1075,22 +866,22 @@ checkResponseError(IXML_Document* doc) } bool -UPnPContext::isIGDConnected(const UPnPIGD& igd) +UPnPContext::isIGDConnected(const IGD* igd) { bool connected = false; std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); - action.reset(UpnpMakeAction("GetStatusInfo", igd.getServiceType().c_str(), 0, nullptr)); + action.reset(UpnpMakeAction("GetStatusInfo", igd->getServiceType().c_str(), 0, nullptr)); std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); IXML_Document* response_ptr = nullptr; - int upnp_err = UpnpSendAction(ctrlptHandle_, igd.getControlURL().c_str(), - igd.getServiceType().c_str(), nullptr, action.get(), &response_ptr); + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); response.reset(response_ptr); checkResponseError(response.get()); if( upnp_err != UPNP_E_SUCCESS) { /* TODO: if failed, should we chck if the igd is disconnected? */ RING_WARN("UPnP: Failed to get GetStatusInfo from: %s, %d: %s", - igd.getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); return false; } @@ -1108,21 +899,21 @@ UPnPContext::isIGDConnected(const UPnPIGD& igd) } IpAddr -UPnPContext::getExternalIP(const UPnPIGD& igd) +UPnPContext::getExternalIP(const IGD* igd) { std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); - action.reset(UpnpMakeAction("GetExternalIPAddress", igd.getServiceType().c_str(), 0, nullptr)); + action.reset(UpnpMakeAction("GetExternalIPAddress", igd->getServiceType().c_str(), 0, nullptr)); std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); IXML_Document* response_ptr = nullptr; - int upnp_err = UpnpSendAction(ctrlptHandle_, igd.getControlURL().c_str(), - igd.getServiceType().c_str(), nullptr, action.get(), &response_ptr); + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); response.reset(response_ptr); checkResponseError(response.get()); if( upnp_err != UPNP_E_SUCCESS) { /* TODO: if failed, should we chck if the igd is disconnected? */ RING_WARN("UPnP: Failed to get GetExternalIPAddress from: %s, %d: %s", - igd.getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); return {}; } @@ -1131,15 +922,15 @@ UPnPContext::getExternalIP(const UPnPIGD& igd) } void -UPnPContext::removeMappingsByLocalIPAndDescription(const UPnPIGD& igd, const std::string& description) +UPnPContext::removeMappingsByLocalIPAndDescription(const IGD* igd, const std::string& description) { - if (!igd.localIp) { + if (!igd->localIp) { RING_DBG("UPnP: cannot determine local IP in function removeMappingsByLocalIPAndDescription()"); return; } RING_DBG("UPnP: removing all port mappings with description: \"%s\" and local ip: %s", - description.c_str(), igd.localIp.toString().c_str()); + description.c_str(), igd->localIp.toString().c_str()); int entry_idx = 0; bool done = false; @@ -1147,19 +938,19 @@ UPnPContext::removeMappingsByLocalIPAndDescription(const UPnPIGD& igd, const std do { std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); IXML_Document* action_ptr = nullptr; - UpnpAddToAction(&action_ptr, "GetGenericPortMappingEntry", igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, "GetGenericPortMappingEntry", igd->getServiceType().c_str(), "NewPortMappingIndex", ring::to_string(entry_idx).c_str()); action.reset(action_ptr); std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); IXML_Document* response_ptr = nullptr; - int upnp_err = UpnpSendAction(ctrlptHandle_, igd.getControlURL().c_str(), - igd.getServiceType().c_str(), nullptr, action.get(), &response_ptr); + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); response.reset(response_ptr); if( not response and upnp_err != UPNP_E_SUCCESS) { /* TODO: if failed, should we chck if the igd is disconnected? */ RING_WARN("UPnP: Failed to get GetGenericPortMappingEntry from: %s, %d: %s", - igd.getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); return; } @@ -1172,7 +963,7 @@ UPnPContext::removeMappingsByLocalIPAndDescription(const UPnPIGD& igd, const std std::string client_ip = get_first_doc_item(response.get(), "NewInternalClient"); /* check if same IP and description */ - if (IpAddr(client_ip) == igd.localIp and desc_actual.compare(description) == 0) { + if (IpAddr(client_ip) == igd->localIp and desc_actual.compare(description) == 0) { /* get the rest of the needed parameters */ std::string port_internal = get_first_doc_item(response.get(), "NewInternalPort"); std::string port_external = get_first_doc_item(response.get(), "NewExternalPort"); @@ -1205,28 +996,28 @@ UPnPContext::removeMappingsByLocalIPAndDescription(const UPnPIGD& igd, const std } bool -UPnPContext::deletePortMapping(const UPnPIGD& igd, const std::string& port_external, const std::string& protocol) +UPnPContext::deletePortMapping(const IGD* igd, const std::string& port_external, const std::string& protocol) { std::string action_name{"DeletePortMapping"}; std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); IXML_Document* action_ptr = nullptr; - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewRemoteHost", ""); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewExternalPort", port_external.c_str()); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewProtocol", protocol.c_str()); action.reset(action_ptr); std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); IXML_Document* response_ptr = nullptr; - int upnp_err = UpnpSendAction(ctrlptHandle_, igd.getControlURL().c_str(), - igd.getServiceType().c_str(), nullptr, action.get(), &response_ptr); + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); response.reset(response_ptr); if( upnp_err != UPNP_E_SUCCESS) { /* TODO: if failed, should we check if the igd is disconnected? */ RING_WARN("UPnP: Failed to get %s from: %s, %d: %s", action_name.c_str(), - igd.getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); return false; } /* check if there is an error code */ @@ -1241,41 +1032,41 @@ UPnPContext::deletePortMapping(const UPnPIGD& igd, const std::string& port_exter } bool -UPnPContext::addPortMapping(const UPnPIGD& igd, const Mapping& mapping, int* error_code) +UPnPContext::addPortMapping(const IGD* igd, const Mapping& mapping, int* error_code) { *error_code = UPNP_E_SUCCESS; std::string action_name{"AddPortMapping"}; std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); IXML_Document* action_ptr = nullptr; - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewRemoteHost", ""); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewExternalPort", mapping.getPortExternalStr().c_str()); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewProtocol", mapping.getTypeStr().c_str()); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewInternalPort", mapping.getPortInternalStr().c_str()); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), - "NewInternalClient", igd.localIp.toString().c_str()); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewInternalClient", igd->localIp.toString().c_str()); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewEnabled", "1"); - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewPortMappingDescription", mapping.getDescription().c_str()); /* for now assume lease duration is always infinite */ - UpnpAddToAction(&action_ptr, action_name.c_str(), igd.getServiceType().c_str(), + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), "NewLeaseDuration", "0"); action.reset(action_ptr); std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); IXML_Document* response_ptr = nullptr; - int upnp_err = UpnpSendAction(ctrlptHandle_, igd.getControlURL().c_str(), - igd.getServiceType().c_str(), nullptr, action.get(), &response_ptr); + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); response.reset(response_ptr); if( not response and upnp_err != UPNP_E_SUCCESS) { /* TODO: if failed, should we chck if the igd is disconnected? */ RING_WARN("UPnP: Failed to %s from: %s, %d: %s", action_name.c_str(), - igd.getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); *error_code = -1; /* make sure to -1 since we didn't get a response */ return false; } diff --git a/src/upnp/upnp_context.h b/src/upnp/upnp_context.h index 3ad8285655ada1304cdb48de90018b793cd27e64..72d06b75eafdcb2871f38755105c9e5d217512e0 100644 --- a/src/upnp/upnp_context.h +++ b/src/upnp/upnp_context.h @@ -18,7 +18,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#pragma once +#ifndef UPNP_CONTEXT_H_ +#define UPNP_CONTEXT_H_ #ifdef HAVE_CONFIG_H #include "config.h" @@ -31,7 +32,6 @@ #include <condition_variable> #include <chrono> #include <atomic> -#include <thread> #if HAVE_LIBUPNP #ifdef _WIN32 @@ -43,10 +43,6 @@ #include <upnp/upnptools.h> #endif -#if HAVE_LIBNATPMP -#include <natpmp.h> -#endif - #include "noncopyable.h" #include "upnp_igd.h" @@ -60,6 +56,7 @@ class UPnPContext { public: constexpr static unsigned SEARCH_TIMEOUT {30}; +#if HAVE_LIBUPNP UPnPContext(); ~UPnPContext(); @@ -107,63 +104,21 @@ public: */ IpAddr getLocalIP() const; -private: - NON_COPYABLE(UPnPContext); - - std::atomic_bool clientRegistered_ {false}; - /** - * map of valid IGDs - IGDs which have the correct services and are connected - * to some external network (have an external IP) - * - * the UDN string is used to uniquely identify the IGD - * - * the mutex is used to access these lists and IGDs in a thread-safe manner - */ - std::map<std::string, std::unique_ptr<IGD>> validIGDs_; - 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_ + * callback function for the UPnP client (control point) + * all UPnP events received by the client are processed here */ - IGD* chooseIGD_unlocked() const; - - - /* tries to add mapping, assumes you alreayd have lock on igd_mutex_ */ - Mapping addMapping(IGD* igd, - uint16_t port_external, - uint16_t port_internal, - PortType type, - int *upnp_error); - - uint16_t chooseRandomPort(const IGD& igd, PortType type); - -#if HAVE_LIBNATPMP - - std::thread pmpThread_; - std::mutex pmpMutex_; - std::condition_variable pmpCv_; - std::atomic_bool pmpRun_ {true}; - - void PMPsearchForIGD(PMPIGD* pmp_igd, natpmp_t& natpmp, bool& found); - void PMPaddPortMapping(const PMPIGD& pmp_igd, natpmp_t& natpmp, GlobalMapping& mapping, bool remove=false) const; - void PMPdeleteAllPortMapping(const PMPIGD& pmp_igd, natpmp_t& natpmp, int proto) const; + int handleUPnPEvents(Upnp_EventType event_type, void* event); +#else + /* use default constructor and destructor */ + UPnPContext() = default; + ~UPnPContext() = default; #endif +private: + NON_COPYABLE(UPnPContext); + #if HAVE_LIBUPNP /** @@ -195,18 +150,40 @@ private: UpnpDevice_Handle deviceHandle_ {-1}; /** - * keep track if we've successfully registered a device + * keep track if we've successfully registered + * a client and/ore device */ + std::atomic_bool clientRegistered_ {false}; bool deviceRegistered_ {false}; - static int cp_callback(Upnp_EventType event_type, void* event, void* user_data); + /** + * map of valid IGDs - IGDs which have the correct services and are connected + * to some external network (have an external IP) + * + * the UDN string is used to uniquely identify the IGD + * + * the mutex is used to access these lists and IGDs in a thread-safe manner + */ + std::map<std::string, std::unique_ptr<IGD>> validIGDs_; + mutable std::mutex validIGDMutex_; + std::condition_variable validIGDCondVar_; /** - * callback function for the UPnP client (control point) - * all UPnP events received by the client are processed here + * Map of valid IGD listeners. */ - int handleUPnPEvents(Upnp_EventType event_type, void* event); + 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_ + */ + IGD* chooseIGD_unlocked() const; /* sends out async search for IGD */ void searchForIGD(); @@ -219,24 +196,33 @@ private: void parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event); + /* tries to add mapping, assumes you alreayd have lock on igd_mutex_ */ + Mapping addMapping(IGD* igd, + uint16_t port_external, + uint16_t port_internal, + PortType type, + int *upnp_error); + + uint16_t chooseRandomPort(const IGD* igd, PortType type); /* these functions directly create UPnP actions * and make synchronous UPnP control point calls * they assume you have a lock on the igd_mutex_ */ - bool isIGDConnected(const UPnPIGD& igd); + bool isIGDConnected(const IGD* igd); - IpAddr getExternalIP(const UPnPIGD& igd); + IpAddr getExternalIP(const IGD* igd); - void removeMappingsByLocalIPAndDescription(const UPnPIGD& igd, + void removeMappingsByLocalIPAndDescription(const IGD* igd, const std::string& description); - bool deletePortMapping(const UPnPIGD& igd, + bool deletePortMapping(const IGD* igd, const std::string& port_external, const std::string& protocol); - bool addPortMapping(const UPnPIGD& igd, + bool addPortMapping(const IGD* igd, const Mapping& mapping, int* error_code); + #endif /* HAVE_LIBUPNP */ }; @@ -250,3 +236,5 @@ private: std::shared_ptr<UPnPContext> getUPnPContext(); }} // namespace ring::upnp + +#endif /* UPNP_CONTEXT_H_ */ diff --git a/src/upnp/upnp_control.cpp b/src/upnp/upnp_control.cpp index d6b06f345cee130e1116231c668eb0a2f0017730..4e3be9d60481ba39dadbfd817ed73aafa87485d8 100644 --- a/src/upnp/upnp_control.cpp +++ b/src/upnp/upnp_control.cpp @@ -46,14 +46,19 @@ Controller::~Controller() { /* remove all mappings */ removeMappings(); +#if HAVE_LIBUPNP if (listToken_ and upnpContext_) upnpContext_->removeIGDListener(listToken_); +#endif } bool Controller::hasValidIGD(std::chrono::seconds timeout) { +#if HAVE_LIBUPNP return upnpContext_ and upnpContext_->hasValidIGD(timeout); +#endif + return false; } void @@ -61,9 +66,11 @@ Controller::setIGDListener(IGDFoundCallback&& cb) { if (not upnpContext_) return; +#if HAVE_LIBUPNP if (listToken_) upnpContext_->removeIGDListener(listToken_); listToken_ = cb ? upnpContext_->addIGDListener(std::move(cb)) : 0; +#endif } bool @@ -74,6 +81,7 @@ Controller::addAnyMapping(uint16_t port_desired, bool unique, uint16_t *port_used) { +#if HAVE_LIBUPNP if (not upnpContext_) return false; @@ -89,6 +97,7 @@ Controller::addAnyMapping(uint16_t port_desired, instanceMappings.emplace(usedPort, std::move(mapping)); return true; } +#endif return false; } @@ -104,6 +113,7 @@ Controller::addAnyMapping(uint16_t port_desired, void Controller::removeMappings(PortType type) { +#if HAVE_LIBUPNP if (not upnpContext_) return; @@ -113,28 +123,34 @@ Controller::removeMappings(PortType type) { upnpContext_->removeMapping(mapping); iter = instanceMappings.erase(iter); } +#endif } - void Controller::removeMappings() { +#if HAVE_LIBUPNP removeMappings(PortType::UDP); removeMappings(PortType::TCP); +#endif } IpAddr Controller::getLocalIP() const { +#if HAVE_LIBUPNP if (upnpContext_) return upnpContext_->getLocalIP(); +#endif return {}; // empty address } IpAddr Controller::getExternalIP() const { +#if HAVE_LIBUPNP if (upnpContext_) return upnpContext_->getExternalIP(); +#endif return {}; // empty address } diff --git a/src/upnp/upnp_igd.h b/src/upnp/upnp_igd.h index 6a65de3f361ac9370a45bfef2cb8f96e845f86e5..3fdce01678488ddf30e5c79658de74fd0c010c2a 100644 --- a/src/upnp/upnp_igd.h +++ b/src/upnp/upnp_igd.h @@ -18,16 +18,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#pragma once - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif +#ifndef UPNP_IGD_H_ +#define UPNP_IGD_H_ #include <string> #include <map> #include <functional> -#include <chrono> #include "noncopyable.h" #include "ip_utils.h" @@ -67,13 +63,13 @@ public: friend bool operator== (const Mapping& cRedir1, const Mapping& cRedir2); friend bool operator!= (const Mapping& cRedir1, const Mapping& cRedir2); - uint16_t getPortExternal() const { return port_external_; } - std::string getPortExternalStr() const { return ring::to_string(port_external_); } - uint16_t getPortInternal() const { return port_internal_; } - std::string getPortInternalStr() const { return ring::to_string(port_internal_); } - PortType getType() const { return type_; } + uint16_t getPortExternal() const { return port_external_; }; + std::string getPortExternalStr() const { return ring::to_string(port_external_); }; + uint16_t getPortInternal() const { return port_internal_; }; + std::string getPortInternalStr() const { return ring::to_string(port_internal_); }; + PortType getType() const { return type_; }; std::string getTypeStr() const { return type_ == PortType::UDP ? "UDP" : "TCP"; } - std::string getDescription() const { return description_; } + std::string getDescription() const { return description_; }; std::string toString() const { return getPortExternalStr() + ":" + getPortInternalStr() + ", " + getTypeStr(); @@ -87,11 +83,6 @@ public: return isValid(); } -#if HAVE_LIBNATPMP - std::chrono::system_clock::time_point renewal_ {std::chrono::system_clock::time_point::min()}; - bool remove {false}; -#endif - private: NON_COPYABLE(Mapping); @@ -132,6 +123,7 @@ using IGDFoundCallback = std::function<void()>; /* defines a UPnP capable Internet Gateway Device (a router) */ class IGD { public: + /* device address seen by IGD */ IpAddr localIp; @@ -144,22 +136,7 @@ public: /* constructors */ IGD() {} - - /* move constructor and operator */ - IGD(IGD&&) = default; - IGD& operator=(IGD&&) = default; - - virtual ~IGD() = default; - -private: - NON_COPYABLE(IGD); -}; - -#if HAVE_LIBUPNP - -class UPnPIGD : public IGD { -public: - UPnPIGD(std::string UDN, + IGD(std::string UDN, std::string baseURL, std::string friendlyName, std::string serviceType, @@ -175,6 +152,12 @@ public: , eventSubURL_(eventSubURL) {} + /* move constructor and operator */ + IGD(IGD&&) = default; + IGD& operator=(IGD&&) = default; + + ~IGD() = default; + const std::string& getUDN() const { return UDN_; }; const std::string& getBaseURL() const { return baseURL_; }; const std::string& getFriendlyName() const { return friendlyName_; }; @@ -184,6 +167,8 @@ public: const std::string& getEventSubURL() const { return eventSubURL_; }; private: + NON_COPYABLE(IGD); + /* root device info */ std::string UDN_ {}; /* used to uniquely identify this UPnP device */ std::string baseURL_ {}; @@ -194,47 +179,9 @@ private: std::string serviceId_ {}; std::string controlURL_ {}; std::string eventSubURL_ {}; -}; - -#endif - -#if HAVE_LIBNATPMP - -using clock = std::chrono::system_clock; -using time_point = clock::time_point; - -class PMPIGD : public IGD { -public: - - void clearAll() { - toRemove_.clear(); - udpMappings.clear(); - tcpMappings.clear(); - clearAll_ = true; - } - - GlobalMapping* getNextMappingToRenew() const { - const GlobalMapping* mapping {nullptr}; - for (const auto& m : udpMappings) - if (!mapping or m.second.renewal_ < mapping->renewal_) - mapping = &m.second; - for (const auto& m : tcpMappings) - if (!mapping or m.second.renewal_ < mapping->renewal_) - mapping = &m.second; - return (GlobalMapping*)mapping; - } - time_point getRenewalTime() const { - const auto next = getNextMappingToRenew(); - auto nextTime = std::min(renewal_, next ? next->renewal_ : time_point::max()); - return toRemove_.empty() ? nextTime : std::min(nextTime, time_point::min()); - } - - time_point renewal_ {time_point::min()}; - std::vector<GlobalMapping> toRemove_ {}; - bool clearAll_ {false}; }; -#endif - }} // namespace ring::upnp + +#endif /* UPNP_IGD_H_ */