1
0
Fork 0
mirror of https://bitbucket.org/King_DuckZ/keepupnpup.git synced 2024-11-07 21:29:00 +00:00

Refactor code into separate files.

This commit is contained in:
King_DuckZ 2016-08-26 02:17:09 +02:00
parent be95a0f959
commit 308e364201
5 changed files with 483 additions and 263 deletions

View file

@ -23,6 +23,7 @@ endif()
add_executable(${PROJECT_NAME}
src/main.cpp
src/upnp.cpp
)
target_include_directories(${PROJECT_NAME}
@ -45,4 +46,4 @@ else()
)
endif()
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)

View file

@ -15,287 +15,50 @@
* along with "keepupnpup". If not, see <http://www.gnu.org/licenses/>.
*/
#include "upnp.hpp"
#include <iostream>
#include "miniupnpc.h"
#include "upnpcommands.h"
#include "upnperrors.h"
#include "enum.h"
#include <memory>
#include <vector>
#include <string>
#include <ciso646>
#include <cstdint>
#include <strings.h>
#include <array>
#if !defined(NDEBUG)
# define KUU_VERBOSE
#endif
BETTER_ENUM (Protocol, uint8_t,
UDP,
TCP,
Other
);
namespace {
class UPNPUrlsResource {
public:
enum IGDReply {
IGDValid = 1,
IGDNotConnected = 2,
IGDUpnpNotSureIGD = 3,
IGDNotSure,
IGDNone
};
explicit UPNPUrlsResource (struct UPNPDev* parDevList) :
m_lanaddr(),
m_externaladdr(),
m_igd_reply(IGDNone)
{
{
std::array<char, 64> lanaddr;
const auto i = UPNP_GetValidIGD(parDevList, &m_urls, &m_data, lanaddr.data(), lanaddr.size());
switch (i) {
case 1:
case 2:
case 3:
m_igd_reply = static_cast<IGDReply>(i);
break;
default:
m_igd_reply = IGDNotSure;
}
m_lanaddr = lanaddr.data();
}
{
std::array<char, 64> externaladdr;
const auto r = UPNP_GetExternalIPAddress(m_urls.controlURL, m_data.first.servicetype, externaladdr.data());
if (UPNPCOMMAND_SUCCESS != r)
m_externaladdr = externaladdr.data();
}
}
~UPNPUrlsResource() noexcept {
if (IGDNone != m_igd_reply) {
m_igd_reply = IGDNone;
FreeUPNPUrls(&m_urls);
}
}
const struct UPNPUrls* urls() const { return &m_urls; }
const struct IGDdatas* data() const { return &m_data; }
IGDReply igd_reply() const { return m_igd_reply; }
const std::string& lanaddr() const { return m_lanaddr; }
const std::string& externaladdr() const { return m_externaladdr; }
private:
std::string m_lanaddr;
std::string m_externaladdr;
struct UPNPUrls m_urls;
struct IGDdatas m_data;
IGDReply m_igd_reply;
};
struct Redirection {
Redirection() : protocol(Protocol::Other) {}
std::string internal_client;
std::string remote_host;
std::string desc;
Protocol protocol;
uint32_t duration;
uint16_t internal_port;
uint16_t external_port;
bool enabled;
};
using UPNPDevsPtr = std::unique_ptr<struct UPNPDev, void(*)(struct UPNPDev*)>;
std::vector<struct UPNPDev*> dev_list_to_vector (struct UPNPDev* parList) {
std::vector<struct UPNPDev*> retval;
for (auto dev = parList; dev; dev = dev->pNext) {
retval.push_back(dev);
}
return retval;
}
bool is_enabled (const std::string& parEnabled) {
if (parEnabled.size() == 4) {
if (strncasecmp("true", parEnabled.c_str(), 4) == 0)
return true;
}
else if (parEnabled.size() == 2) {
if (strncasecmp("on", parEnabled.c_str(), 2) == 0)
return true;
}
else if (parEnabled.size() == 3) {
if (strncasecmp("yes", parEnabled.c_str(), 3) == 0)
return true;
}
if (parEnabled == "1")
return true;
return false;
}
std::vector<Redirection> get_redirections (const UPNPUrlsResource& parResource) {
char intClient[40];
char intPort[6];
char extPort[6];
char protocol[4];
char desc[80];
char enabled[6];
char rHost[64];
char duration[16];
int r;
int z = 0;
std::vector<Redirection> retval;
do {
rHost[0] = '\0';
enabled[0] = '\0';
duration[0] = '\0';
desc[0] = '\0';
extPort[0] = '\0';
intPort[0] = '\0';
intClient[0] = '\0';
r = UPNP_GetGenericPortMappingEntry(
parResource.urls()->controlURL,
parResource.data()->first.servicetype,
std::to_string(z).c_str(),
extPort,
intClient,
intPort,
protocol,
desc,
enabled,
rHost,
duration
);
//if (r)
// printf("GetGenericPortMappingEntry() returned %d (%s)\n",
// r, strupnperror(r));
if (not r) {
Redirection redir;
better_enums::optional<Protocol> proto = Protocol::_from_string_nothrow(protocol);
redir.protocol = (proto ? *proto : +Protocol::Other);
redir.internal_client = intClient;
redir.remote_host = rHost;
redir.desc = desc;
redir.duration = static_cast<uint32_t>(stoul(std::string(duration)));
redir.internal_port = static_cast<uint16_t>(stoul(std::string(intPort)));
redir.external_port = static_cast<uint16_t>(stoul(std::string(extPort)));
redir.enabled = is_enabled(std::string(enabled));
retval.push_back(redir);
}
++z;
} while (not r);
return retval;
}
void add_port_mapping (
const UPNPUrlsResource& parResource,
uint16_t parExtPort,
uint16_t parIntPort,
const std::string& parAddr,
const std::string& parDesc,
uint32_t parDuration,
Protocol parProtocol
) {
const auto r = UPNP_AddPortMapping(
parResource.urls()->controlURL,
parResource.data()->first.servicetype,
std::to_string(parExtPort).c_str(),
std::to_string(parIntPort).c_str(),
parAddr.c_str(),
parDesc.c_str(),
parProtocol._to_string(),
0,
std::to_string(parDuration).c_str()
);
//if (UPNPCOMMAND_SUCCESS != r)
//printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
//eport, iport, iaddr, r, strupnperror(r));
}
bool has_mapping (const std::vector<Redirection>& parRedirs, const std::string& parAddr, uint16_t parPort, Protocol parProtocol) {
bool has_mapping (const std::vector<kuu::Redirection>& parRedirs, const std::string& parAddr, uint16_t parPort, kuu::Protocol parProtocol) {
for (auto& redir : parRedirs) {
if (redir.protocol == parProtocol and redir.internal_port == parPort and redir.internal_client == parAddr)
return true;
}
return false;
}
} //unnamed namespace
int main() {
int error;
UPNPDevsPtr devlist(nullptr, &freeUPNPDevlist);
void runapp() {
kuu::UPNP upnp;
devlist.reset(upnpDiscover(
2000,
nullptr, //multicastif
nullptr, //minissdpdpath
UPNP_LOCAL_PORT_ANY, //localport
0, //ipv6
2, //ttl
&error
));
const auto redirs = upnp.redirections();
const auto devs = dev_list_to_vector(devlist.get());
if (devs.empty()) {
std::cerr << "No UPNP devices found\n";
return 1;
}
else if (devs.size() > 1) {
std::cerr << "More than one UPNP device found (" << devs.size() << "), aborting\n";
return 1;
}
UPNPUrlsResource urls(devlist.get());
#if defined(KUU_VERBOSE)
std::cout << "Found: " << devs.front()->descURL << ", " << devs.front()->st << '\n';
switch (urls.igd_reply()) {
case UPNPUrlsResource::IGDValid:
std::cout << "Found valid IGD: " << urls.urls()->controlURL << '\n';
break;
case UPNPUrlsResource::IGDNotConnected:
std::cout << "Found a (not connected?) IGD: " << urls.urls()->controlURL << '\n';
break;
case UPNPUrlsResource::IGDUpnpNotSureIGD:
std::cout << "UPnP device found. Is it an IGD? " << urls.urls()->controlURL << '\n';
break;
case UPNPUrlsResource::IGDNotSure:
std::cout << "Found device (igd?): " << urls.urls()->controlURL << '\n';
}
std::cout << "Local LAN ip address: " << urls.lanaddr() << '\n';
std::cout << "External ip address: " << urls.externaladdr() << '\n';
#endif
const auto redirs = get_redirections(urls);
if (not has_mapping(redirs, urls.lanaddr(), 6881, Protocol::TCP))
add_port_mapping(urls, 6881, 6881, urls.lanaddr(), "RoutArm redir", 0, Protocol::TCP);
if (not has_mapping(redirs, urls.lanaddr(), 49164, Protocol::TCP))
add_port_mapping(urls, 49164, 49164, urls.lanaddr(), "RoutArm redir", 0, Protocol::TCP);
if (not has_mapping(redirs, urls.lanaddr(), 49164, Protocol::UDP))
add_port_mapping(urls, 49164, 49164, urls.lanaddr(), "RoutArm redir", 0, Protocol::UDP);
if (not has_mapping(redirs, upnp.lanaddr(), 6881, kuu::Protocol::TCP))
upnp.add_port_mapping(6881, 6881, upnp.lanaddr(), "RoutArm redir", 0, kuu::Protocol::TCP);
if (not has_mapping(redirs, upnp.lanaddr(), 49164, kuu::Protocol::TCP))
upnp.add_port_mapping(49164, 49164, upnp.lanaddr(), "RoutArm redir", 0, kuu::Protocol::TCP);
if (not has_mapping(redirs, upnp.lanaddr(), 49164, kuu::Protocol::UDP))
upnp.add_port_mapping(49164, 49164, upnp.lanaddr(), "RoutArm redir", 0, kuu::Protocol::UDP);
//for (auto& redir : redirs) {
// std::cout << redir.protocol << ", " << redir.remote_host << ":" << redir.external_port << " --> " <<
// redir.internal_client << ":" << redir.internal_port << ", " << redir.duration << ", " <<
// redir.enabled << R"(, ")" << redir.desc << "\"\n";
//}
}
} //unnamed namespace
int main() {
try {
runapp();
}
catch (const kuu::Exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}

43
src/redirection.hpp Normal file
View file

@ -0,0 +1,43 @@
/* Copyright 2016, Michele Santullo
* This file is part of "keepupnpup".
*
* "keepupnpup" is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* "keepupnpup" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "keepupnpup". If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "enum.h"
#include <string>
#include <cstdint>
namespace kuu {
BETTER_ENUM (Protocol, uint8_t,
UDP,
TCP,
Other
);
struct Redirection {
Redirection() : protocol(Protocol::Other) {}
std::string internal_client;
std::string remote_host;
std::string desc;
Protocol protocol;
uint32_t duration;
uint16_t internal_port;
uint16_t external_port;
bool enabled;
};
} //namespace kuu

330
src/upnp.cpp Normal file
View file

@ -0,0 +1,330 @@
/* Copyright 2016, Michele Santullo
* This file is part of "keepupnpup".
*
* "keepupnpup" is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* "keepupnpup" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "keepupnpup". If not, see <http://www.gnu.org/licenses/>.
*/
#include "upnp.hpp"
#include "miniupnpc.h"
#include "upnpcommands.h"
#include "upnperrors.h"
#include <array>
#include <algorithm>
#include <sstream>
#include <ciso646>
#include <strings.h>
#if !defined(NDEBUG)
# define KUU_VERBOSE
#endif
#if defined(KUU_VERBOSE)
# include <iostream>
#endif
namespace kuu {
namespace {
struct UPNPUrlsWithInitFlag {
UPNPUrlsWithInitFlag() : initialized(false) {}
struct UPNPUrls urls;
bool initialized;
operator struct UPNPUrls* () { return &urls; }
UPNPUrlsWithInitFlag* operator-> () { return this; }
};
//see: http://stackoverflow.com/questions/24611215/one-liner-for-raii-on-non-pointer
struct UPNPUrlDeleter {
typedef UPNPUrlsWithInitFlag pointer;
void operator() (UPNPUrlsWithInitFlag& parUrls) {
if (parUrls.initialized) {
parUrls.initialized = false;
FreeUPNPUrls(&parUrls.urls);
}
}
};
using UPNPUrlsPtr = std::unique_ptr<UPNPUrlsWithInitFlag, UPNPUrlDeleter>;
using UPNPDevsPtr = std::unique_ptr<struct UPNPDev, void(*)(struct UPNPDev*)>;
std::vector<struct UPNPDev*> dev_list_to_vector (struct UPNPDev* parList) {
std::vector<struct UPNPDev*> retval;
for (auto dev = parList; dev; dev = dev->pNext) {
retval.push_back(dev);
}
return retval;
}
std::string build_exception_msg (int parError, const char* parMessageIntro, const char* parMessage) {
std::ostringstream oss;
oss << parMessageIntro << " (" << parError << ")";
if (parMessage)
oss << ": \"" << parMessage << '"';
return oss.str();
}
bool is_enabled (const std::string& parEnabled) {
if (parEnabled.size() == 4) {
if (strncasecmp("true", parEnabled.c_str(), 4) == 0)
return true;
}
else if (parEnabled.size() == 2) {
if (strncasecmp("on", parEnabled.c_str(), 2) == 0)
return true;
}
else if (parEnabled.size() == 3) {
if (strncasecmp("yes", parEnabled.c_str(), 3) == 0)
return true;
}
if (parEnabled == "1")
return true;
return false;
}
#if defined(KUU_VERBOSE)
void print_devices (
const std::vector<struct UPNPDev*>& parDevs,
struct UPNPUrls* parUrls,
UPNP::IGDReply parIGDReply,
const std::string& parLanAddr,
const std::string& parExternalAddr
) {
std::cout << "Found: " << parDevs.front()->descURL << ", " << parDevs.front()->st << '\n';
switch (parIGDReply) {
case UPNP::IGDValid:
std::cout << "Found valid IGD: " << parUrls->controlURL << '\n';
break;
case UPNP::IGDNotConnected:
std::cout << "Found a (not connected?) IGD: " << parUrls->controlURL << '\n';
break;
case UPNP::IGDUpnpNotSureIGD:
std::cout << "UPnP device found. Is it an IGD? " << parUrls->controlURL << '\n';
break;
case UPNP::IGDNotSure:
std::cout << "Found device (igd?): " << parUrls->controlURL << '\n';
}
std::cout << "Local LAN ip address: " << parLanAddr << '\n';
std::cout << "External ip address: " << parExternalAddr << '\n';
}
#endif
} //unnamed namespace
struct UPNP::LocalData {
LocalData() :
lanaddr(),
externaladdr(),
igd_reply(IGDNone),
devlist(nullptr, &freeUPNPDevlist)
{
}
std::string lanaddr;
std::string externaladdr;
struct IGDdatas data;
IGDReply igd_reply;
UPNPDevsPtr devlist;
UPNPUrlsPtr urls;
};
UPNP::UPNP() :
m_local_data(std::make_unique<LocalData>())
{
int error;
UPNPDevsPtr devlist(
upnpDiscover(
2000,
nullptr, //multicastif
nullptr, //minissdpdpath
UPNP_LOCAL_PORT_ANY, //localport
0, //ipv6
2, //ttl
&error
),
&freeUPNPDevlist
);
if (error)
throw UPNPException(error, "Error initializing upnpc in upnpDiscover()", nullptr);
const auto devs = dev_list_to_vector(devlist.get());
if (devs.empty())
throw ScanException("No UPNP devices found");
else if (devs.size() > 1)
throw ScanException("More than one UPNP device found (" + std::to_string(devs.size()) + ")");
UPNPUrlsPtr urls;
{
std::array<char, 64> lanaddr;
const auto i = UPNP_GetValidIGD(
devlist.get(),
urls.get(),
&m_local_data->data,
lanaddr.data(),
lanaddr.size()
);
switch (i) {
case 1:
case 2:
case 3:
m_local_data->igd_reply = static_cast<IGDReply>(i);
break;
default:
m_local_data->igd_reply = IGDNotSure;
}
urls->initialized = true;
m_local_data->lanaddr = lanaddr.data();
}
{
std::array<char, 64> externaladdr;
const auto r = UPNP_GetExternalIPAddress(urls->urls.controlURL, m_local_data->data.first.servicetype, externaladdr.data());
if (UPNPCOMMAND_SUCCESS != r)
m_local_data->externaladdr = externaladdr.data();
}
#if defined(KUU_VERBOSE)
print_devices(devs, urls.get(), m_local_data->igd_reply, m_local_data->lanaddr, m_local_data->externaladdr);
#endif
m_local_data->urls.swap(urls);
m_local_data->devlist.swap(devlist);
}
UPNP::~UPNP() noexcept = default;
UPNP::IGDReply UPNP::igd_reply() const {
return m_local_data->igd_reply;
}
const std::string& UPNP::lanaddr() const {
return m_local_data->lanaddr;
}
const std::string& UPNP::externaladdr() const {
return m_local_data->externaladdr;
}
std::vector<Redirection> UPNP::redirections() const {
char intClient[40];
char intPort[6];
char extPort[6];
char protocol[4];
char desc[80];
char enabled[6];
char rHost[64];
char duration[16];
int r;
int z = 0;
std::vector<Redirection> retval;
do {
rHost[0] = '\0';
enabled[0] = '\0';
duration[0] = '\0';
desc[0] = '\0';
extPort[0] = '\0';
intPort[0] = '\0';
intClient[0] = '\0';
r = UPNP_GetGenericPortMappingEntry(
m_local_data->urls->urls.controlURL,
m_local_data->data.first.servicetype,
std::to_string(z).c_str(),
extPort,
intClient,
intPort,
protocol,
desc,
enabled,
rHost,
duration
);
if (not r) {
Redirection redir;
better_enums::optional<Protocol> proto = Protocol::_from_string_nothrow(protocol);
redir.protocol = (proto ? *proto : +Protocol::Other);
redir.internal_client = intClient;
redir.remote_host = rHost;
redir.desc = desc;
redir.duration = static_cast<uint32_t>(stoul(std::string(duration)));
redir.internal_port = static_cast<uint16_t>(stoul(std::string(intPort)));
redir.external_port = static_cast<uint16_t>(stoul(std::string(extPort)));
redir.enabled = is_enabled(std::string(enabled));
retval.push_back(redir);
}
else {
throw UPNPException(r, "Error in invocation of UPNP_GetGenericPortMappingEntry()", strupnperror(r));
}
++z;
} while (not r);
return retval;
}
void UPNP::add_port_mapping (
uint16_t parExtPort,
uint16_t parIntPort,
const std::string& parAddr,
const std::string& parDesc,
uint32_t parDuration,
Protocol parProtocol
) {
const auto r = UPNP_AddPortMapping(
m_local_data->urls->urls.controlURL,
m_local_data->data.first.servicetype,
std::to_string(parExtPort).c_str(),
std::to_string(parIntPort).c_str(),
parAddr.c_str(),
parDesc.c_str(),
parProtocol._to_string(),
0,
std::to_string(parDuration).c_str()
);
if (UPNPCOMMAND_SUCCESS != r) {
std::ostringstream oss;
oss << "Error in UPNP_AddPortMapping(\"" <<
m_local_data->urls->urls.controlURL << "\", \"" <<
m_local_data->data.first.servicetype << "\", " <<
parExtPort << ", " <<
parIntPort << ", \"" <<
parAddr << "\", \"" <<
parDesc << "\", " <<
parProtocol._to_string() << ")";
throw UPNPException(r, oss.str().c_str(), strupnperror(r));
}
}
UPNPException::UPNPException (int parError, const char* parMessageIntro, const char* parMessage) :
Exception(build_exception_msg(parError, parMessageIntro, parMessage)),
m_error(parError)
{
}
ScanException::ScanException (const std::string& parMessage) :
Exception(parMessage)
{
}
ScanException::ScanException (const char* parMessage) :
Exception(parMessage)
{
}
} //namespace kuu

83
src/upnp.hpp Normal file
View file

@ -0,0 +1,83 @@
/* Copyright 2016, Michele Santullo
* This file is part of "keepupnpup".
*
* "keepupnpup" is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* "keepupnpup" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "keepupnpup". If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "redirection.hpp"
#include <memory>
#include <string>
#include <stdexcept>
#include <vector>
#include <cstdint>
namespace kuu {
class UPNP {
public:
enum IGDReply {
IGDValid = 1,
IGDNotConnected = 2,
IGDUpnpNotSureIGD = 3,
IGDNotSure,
IGDNone
};
UPNP();
~UPNP() noexcept;
IGDReply igd_reply() const;
const std::string& lanaddr() const;
const std::string& externaladdr() const;
std::vector<Redirection> redirections() const;
void add_port_mapping (
uint16_t parExtPort,
uint16_t parIntPort,
const std::string& parAddr,
const std::string& parDesc,
uint32_t parDuration,
Protocol parProtocol
);
private:
struct LocalData;
std::unique_ptr<LocalData> m_local_data;
};
class Exception : public std::runtime_error {
protected:
explicit Exception (const char* parMessage) : std::runtime_error(parMessage) {}
explicit Exception (const std::string& parMessage) : std::runtime_error(parMessage) {}
};
class UPNPException : public Exception {
public:
UPNPException (int parError, const char* parMessageIntro, const char* parMessage);
virtual ~UPNPException() noexcept = default;
int error_code() const { return m_error; }
private:
int m_error;
};
class ScanException : public Exception {
public:
explicit ScanException (const char* parMessage);
explicit ScanException (const std::string& parMessage);
};
} //namespace kuu