Skip to content
Snippets Groups Projects
Commit 1afa10bc authored by Guillaume Roguez's avatar Guillaume Roguez
Browse files

Revert "NAT-PMP: add initial support"

This reverts commit 150035a8.

reasons of the revert:
- doesn't link on Mac
- doesn't build on IOS
- build and link on linux, but the shared library is not found in rpath

Change-Id: Ie2eb1fe587adea1b607fde727abe6c641762495b
Notes: libnatpmp is not used on Android
parent 150035a8
Branches
Tags
No related merge requests found
......@@ -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])
......
e50b1f68ce9254bb2f068ddc37417a3c417b80f7b3fb3d84e3e9af4a144d89e204ab993b54c01657335e855d0124a8fcbbf96ce78db7b9ae0b03b6eb79de2e09 libnatpmp-20150609.tar.gz
# 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 $@
......@@ -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;
#endif // HAVE_LIBUPNP
UPnPContext::UPnPContext()
static int
cp_callback(Upnp_EventType event_type, void* event, void* user_data)
{
#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 (auto upnpContext = static_cast<UPnPContext*>(user_data))
return upnpContext->handleUPnPEvents(event_type, event);
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);
}
RING_WARN("UPnP callback without UPnPContext");
return 0;
}
if (not found) delete pmp_igd;
closenatpmp(&natpmp);
RING_DBG("NAT-PMP: ended");
});
clientRegistered_ = true;
#endif
#if HAVE_LIBUPNP
UPnPContext::UPnPContext()
{
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();
removeMappingsByLocalIPAndDescription(it.second.get(), Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION);
}
pmpCv_.notify_all();
}
#endif
}
}
#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();
}
/**
......@@ -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,
deletePortMapping(igd,
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
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;
}
......
......@@ -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_ */
......@@ -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
}
......
......@@ -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_ */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment