Commit 3165a70c authored by Adrien Béraud's avatar Adrien Béraud Committed by gerrit2

NAT-PMP: add initial support

* add optional dependency to libnatpmp
* can run with or without UPnP
* transparently replaces UPnP when available

Change-Id: I1bbded421833cdc2506b42106e30cc8b3dde4c2d
Tuleap: #805
parent d0cdb69c
......@@ -531,6 +531,20 @@ 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])
......
This diff is collapsed.
......@@ -18,8 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef UPNP_CONTEXT_H_
#define UPNP_CONTEXT_H_
#pragma once
#ifdef HAVE_CONFIG_H
#include "config.h"
......@@ -32,6 +31,7 @@
#include <condition_variable>
#include <chrono>
#include <atomic>
#include <thread>
#if HAVE_LIBUPNP
#ifdef _WIN32
......@@ -43,6 +43,10 @@
#include <upnp/upnptools.h>
#endif
#if HAVE_LIBNATPMP
#include <natpmp.h>
#endif
#include "noncopyable.h"
#include "upnp_igd.h"
......@@ -56,7 +60,6 @@ class UPnPContext {
public:
constexpr static unsigned SEARCH_TIMEOUT {30};
#if HAVE_LIBUPNP
UPnPContext();
~UPnPContext();
......@@ -104,27 +107,68 @@ public:
*/
IpAddr getLocalIP() const;
/**
* callback function for the UPnP client (control point)
* all UPnP events received by the client are processed here
*/
int handleUPnPEvents(Upnp_EventType event_type, void* event);
/**
* Inform the UPnP context that the network status has changed. This clears the list of known
* IGDs
*/
void connectivityChanged();
#else
/* use default constructor and destructor */
UPnPContext() = default;
~UPnPContext() = default;
#endif
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_
*/
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};
std::shared_ptr<PMPIGD> pmpIGD_;
void PMPsearchForIGD(const std::shared_ptr<PMPIGD>& pmp_igd, natpmp_t& natpmp);
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;
#endif
#if HAVE_LIBUPNP
/**
......@@ -156,40 +200,18 @@ private:
UpnpDevice_Handle deviceHandle_ {-1};
/**
* keep track if we've successfully registered
* a client and/ore device
* keep track if we've successfully registered a device
*/
std::atomic_bool clientRegistered_ {false};
bool deviceRegistered_ {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_;
static int cp_callback(Upnp_EventType event_type, void* event, void* user_data);
/**
* Map of valid IGD listeners.
*/
std::map<size_t, IGDFoundCallback> igdListeners_;
/**
* Last provided token for valid IGD listeners.
* 0 is the invalid token.
* callback function for the UPnP client (control point)
* all UPnP events received by the client are processed here
*/
size_t listenerToken_ {0};
int handleUPnPEvents(Upnp_EventType event_type, void* event);
/**
* 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();
......@@ -202,33 +224,24 @@ 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 IGD* igd);
bool isIGDConnected(const UPnPIGD& igd);
IpAddr getExternalIP(const IGD* igd);
IpAddr getExternalIP(const UPnPIGD& igd);
void removeMappingsByLocalIPAndDescription(const IGD* igd,
void removeMappingsByLocalIPAndDescription(const UPnPIGD& igd,
const std::string& description);
bool deletePortMapping(const IGD* igd,
bool deletePortMapping(const UPnPIGD& igd,
const std::string& port_external,
const std::string& protocol);
bool addPortMapping(const IGD* igd,
bool addPortMapping(const UPnPIGD& igd,
const Mapping& mapping,
int* error_code);
#endif /* HAVE_LIBUPNP */
};
......@@ -242,5 +255,3 @@ private:
std::shared_ptr<UPnPContext> getUPnPContext();
}} // namespace ring::upnp
#endif /* UPNP_CONTEXT_H_ */
......@@ -46,19 +46,14 @@ 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
......@@ -66,11 +61,9 @@ 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
......@@ -81,7 +74,6 @@ Controller::addAnyMapping(uint16_t port_desired,
bool unique,
uint16_t *port_used)
{
#if HAVE_LIBUPNP
if (not upnpContext_)
return false;
......@@ -97,7 +89,6 @@ Controller::addAnyMapping(uint16_t port_desired,
instanceMappings.emplace(usedPort, std::move(mapping));
return true;
}
#endif
return false;
}
......@@ -113,7 +104,6 @@ Controller::addAnyMapping(uint16_t port_desired,
void
Controller::removeMappings(PortType type) {
#if HAVE_LIBUPNP
if (not upnpContext_)
return;
......@@ -123,34 +113,28 @@ 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,12 +18,16 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef UPNP_IGD_H_
#define UPNP_IGD_H_
#pragma once
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string>
#include <map>
#include <functional>
#include <chrono>
#include "noncopyable.h"
#include "ip_utils.h"
......@@ -63,13 +67,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();
......@@ -83,6 +87,11 @@ 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);
......@@ -123,7 +132,6 @@ using IGDFoundCallback = std::function<void()>;
/* defines a UPnP capable Internet Gateway Device (a router) */
class IGD {
public:
/* device address seen by IGD */
IpAddr localIp;
......@@ -136,7 +144,22 @@ public:
/* constructors */
IGD() {}
IGD(std::string UDN,
/* 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,
std::string baseURL,
std::string friendlyName,
std::string serviceType,
......@@ -152,12 +175,6 @@ 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_; };
......@@ -167,8 +184,6 @@ 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_ {};
......@@ -179,9 +194,47 @@ 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};
};
}} // namespace ring::upnp
#endif
#endif /* UPNP_IGD_H_ */
}} // namespace ring::upnp
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment