Non-blocking UPnP discovery (#2346)

* Non-blocking UPnP device discovery

* Rename local variables with suffix _l
This commit is contained in:
Guilherme Lawless 2019-10-16 15:07:04 +01:00 committed by GitHub
commit 5fcefd8e37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 75 deletions

View file

@ -6,89 +6,97 @@
nano::port_mapping::port_mapping (nano::node & node_a) :
node (node_a),
devices (nullptr),
protocols ({ { { "TCP", 0, boost::asio::ip::address_v4::any (), 0 }, { "UDP", 0, boost::asio::ip::address_v4::any (), 0 } } }),
check_count (0),
on (false)
protocols ({ { { "TCP", 0, boost::asio::ip::address_v4::any (), 0 }, { "UDP", 0, boost::asio::ip::address_v4::any (), 0 } } })
{
urls = { 0 };
data = { { 0 } };
}
void nano::port_mapping::start ()
{
check_mapping_loop ();
on = true;
node.background ([this] {
this->check_mapping_loop ();
});
}
std::string nano::port_mapping::get_config_port (std::string const & node_port_a)
{
return node.config.external_port != 0 ? std::to_string (node.config.external_port) : node_port_a;
}
void nano::port_mapping::refresh_devices ()
{
if (!network_params.network.is_test_network ())
{
nano::lock_guard<std::mutex> lock (mutex);
int discover_error = 0;
freeUPNPDevlist (devices);
devices = upnpDiscover (2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, false, 2, &discover_error);
std::array<char, 64> local_address;
local_address.fill (0);
auto igd_error (UPNP_GetValidIGD (devices, &urls, &data, local_address.data (), sizeof (local_address)));
if (igd_error == 1 || igd_error == 2)
{
boost::system::error_code ec;
address = boost::asio::ip::address_v4::from_string (local_address.data (), ec);
}
upnp_state upnp_l;
int discover_error_l = 0;
upnp_l.devices = upnpDiscover (2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, false, 2, &discover_error_l);
std::array<char, 64> local_address_l;
local_address_l.fill (0);
auto igd_error_l (UPNP_GetValidIGD (upnp_l.devices, &upnp_l.urls, &upnp_l.data, local_address_l.data (), sizeof (local_address_l)));
if (check_count % 15 == 0)
{
node.logger.always_log (boost::str (boost::format ("UPnP local address: %1%, discovery: %2%, IGD search: %3%") % local_address.data () % discover_error % igd_error));
node.logger.always_log (boost::str (boost::format ("UPnP local address: %1%, discovery: %2%, IGD search: %3%") % local_address_l.data () % discover_error_l % igd_error_l));
if (node.config.logging.upnp_details_logging ())
{
for (auto i (devices); i != nullptr; i = i->pNext)
for (auto i (upnp_l.devices); i != nullptr; i = i->pNext)
{
node.logger.always_log (boost::str (boost::format ("UPnP device url: %1% st: %2% usn: %3%") % i->descURL % i->st % i->usn));
}
}
}
// Update port mapping
nano::lock_guard<std::mutex> guard_l (mutex);
upnp = std::move (upnp_l);
if (igd_error_l == 1 || igd_error_l == 2)
{
boost::system::error_code ec;
address = boost::asio::ip::address_v4::from_string (local_address_l.data (), ec);
}
}
}
nano::endpoint nano::port_mapping::external_address ()
{
nano::endpoint result (boost::asio::ip::address_v6{}, 0);
nano::lock_guard<std::mutex> lock (mutex);
nano::endpoint result_l (boost::asio::ip::address_v6{}, 0);
nano::lock_guard<std::mutex> guard_l (mutex);
for (auto & protocol : protocols)
{
if (protocol.external_port != 0)
{
result = nano::endpoint (protocol.external_address, protocol.external_port);
result_l = nano::endpoint (protocol.external_address, protocol.external_port);
}
}
return result;
return result_l;
}
void nano::port_mapping::refresh_mapping ()
{
if (!network_params.network.is_test_network ())
{
nano::lock_guard<std::mutex> lock (mutex);
auto node_port (std::to_string (node.network.endpoint ().port ()));
auto config_port (node.config.external_port != 0 ? std::to_string (node.config.external_port) : node_port);
nano::lock_guard<std::mutex> guard_l (mutex);
auto node_port_l (std::to_string (node.network.endpoint ().port ()));
auto config_port_l (get_config_port (node_port_l));
// We don't map the RPC port because, unless RPC authentication was added, this would almost always be a security risk
for (auto & protocol : protocols)
{
auto add_port_mapping_error (UPNP_AddPortMapping (urls.controlURL, data.first.servicetype, config_port.c_str (), node_port.c_str (), address.to_string ().c_str (), nullptr, protocol.name, nullptr, nullptr));
if (check_count % 15 == 0)
auto add_port_mapping_error_l (UPNP_AddPortMapping (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), node_port_l.c_str (), address.to_string ().c_str (), nullptr, protocol.name, nullptr, nullptr));
if (node.config.logging.upnp_details_logging ())
{
node.logger.always_log (boost::str (boost::format ("UPnP %1% port mapping response: %2%") % protocol.name % add_port_mapping_error));
node.logger.always_log (boost::str (boost::format ("UPnP %1% port mapping response: %2%") % protocol.name % add_port_mapping_error_l));
}
if (add_port_mapping_error == UPNPCOMMAND_SUCCESS)
if (add_port_mapping_error_l == UPNPCOMMAND_SUCCESS)
{
node.logger.always_log (boost::str (boost::format ("%1% mapped to %2%") % config_port % node_port));
protocol.external_port = static_cast<uint16_t> (std::atoi (config_port.data ()));
protocol.external_port = static_cast<uint16_t> (std::atoi (config_port_l.data ()));
if (node.config.logging.upnp_details_logging ())
{
node.logger.always_log (boost::str (boost::format ("%1% mapped to %2%") % config_port_l % node_port_l));
}
}
else
{
protocol.external_port = 0;
node.logger.always_log (boost::str (boost::format ("UPnP failed %1%: %2%") % add_port_mapping_error % strupnperror (add_port_mapping_error)));
node.logger.always_log (boost::str (boost::format ("UPnP failed %1%: %2%") % add_port_mapping_error_l % strupnperror (add_port_mapping_error_l)));
}
}
}
@ -96,57 +104,57 @@ void nano::port_mapping::refresh_mapping ()
int nano::port_mapping::check_mapping ()
{
int result (3600);
int result_l (3600);
if (!network_params.network.is_test_network ())
{
// Long discovery time and fast setup/teardown make this impractical for testing
nano::lock_guard<std::mutex> lock (mutex);
auto node_port (std::to_string (node.network.endpoint ().port ()));
auto config_port (node.config.external_port != 0 ? std::to_string (node.config.external_port) : node_port);
nano::lock_guard<std::mutex> guard_l (mutex);
auto node_port_l (std::to_string (node.network.endpoint ().port ()));
auto config_port_l (get_config_port (node_port_l));
for (auto & protocol : protocols)
{
std::array<char, 64> int_client;
std::array<char, 6> int_port;
std::array<char, 16> remaining_mapping_duration;
remaining_mapping_duration.fill (0);
auto verify_port_mapping_error (UPNP_GetSpecificPortMappingEntry (urls.controlURL, data.first.servicetype, config_port.c_str (), protocol.name, nullptr, int_client.data (), int_port.data (), nullptr, nullptr, remaining_mapping_duration.data ()));
if (verify_port_mapping_error == UPNPCOMMAND_SUCCESS)
std::array<char, 64> int_client_l;
std::array<char, 6> int_port_l;
std::array<char, 16> remaining_mapping_duration_l;
remaining_mapping_duration_l.fill (0);
auto verify_port_mapping_error_l (UPNP_GetSpecificPortMappingEntry (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), protocol.name, nullptr, int_client_l.data (), int_port_l.data (), nullptr, nullptr, remaining_mapping_duration_l.data ()));
if (verify_port_mapping_error_l == UPNPCOMMAND_SUCCESS)
{
protocol.remaining = std::atoi (remaining_mapping_duration.data ());
protocol.remaining = std::atoi (remaining_mapping_duration_l.data ());
}
else
{
protocol.remaining = 0;
node.logger.always_log (boost::str (boost::format ("UPNP_GetSpecificPortMappingEntry failed %1%: %2%") % verify_port_mapping_error % strupnperror (verify_port_mapping_error)));
node.logger.always_log (boost::str (boost::format ("UPNP_GetSpecificPortMappingEntry failed %1%: %2%") % verify_port_mapping_error_l % strupnperror (verify_port_mapping_error_l)));
}
result = std::min (result, protocol.remaining);
std::array<char, 64> external_address;
external_address.fill (0);
auto external_ip_error (UPNP_GetExternalIPAddress (urls.controlURL, data.first.servicetype, external_address.data ()));
if (external_ip_error == UPNPCOMMAND_SUCCESS)
result_l = std::min (result_l, protocol.remaining);
std::array<char, 64> external_address_l;
external_address_l.fill (0);
auto external_ip_error_l (UPNP_GetExternalIPAddress (upnp.urls.controlURL, upnp.data.first.servicetype, external_address_l.data ()));
if (external_ip_error_l == UPNPCOMMAND_SUCCESS)
{
boost::system::error_code ec;
protocol.external_address = boost::asio::ip::address_v4::from_string (external_address.data (), ec);
protocol.external_address = boost::asio::ip::address_v4::from_string (external_address_l.data (), ec);
}
else
{
protocol.external_address = boost::asio::ip::address_v4::any ();
node.logger.always_log (boost::str (boost::format ("UPNP_GetExternalIPAddress failed %1%: %2%") % verify_port_mapping_error % strupnperror (verify_port_mapping_error)));
node.logger.always_log (boost::str (boost::format ("UPNP_GetExternalIPAddress failed %1%: %2%") % verify_port_mapping_error_l % strupnperror (verify_port_mapping_error_l)));
}
if (check_count % 15 == 0)
if (node.config.logging.upnp_details_logging ())
{
node.logger.always_log (boost::str (boost::format ("UPnP %1% mapping verification response: %2%, external ip response: %3%, external ip: %4%, internal ip: %5%, remaining lease: %6%") % protocol.name % verify_port_mapping_error % external_ip_error % external_address.data () % address.to_string () % remaining_mapping_duration.data ()));
node.logger.always_log (boost::str (boost::format ("UPnP %1% mapping verification response: %2%, external ip response: %3%, external ip: %4%, internal ip: %5%, remaining lease: %6%") % protocol.name % verify_port_mapping_error_l % external_ip_error_l % external_address_l.data () % address.to_string () % remaining_mapping_duration_l.data ()));
}
}
}
return result;
return result_l;
}
void nano::port_mapping::check_mapping_loop ()
{
int wait_duration = network_params.portmapping.check_timeout;
int wait_duration_l = network_params.portmapping.check_timeout;
refresh_devices ();
if (devices != nullptr)
if (upnp.devices != nullptr)
{
auto remaining (check_mapping ());
// If the mapping is lost, refresh it
@ -157,7 +165,7 @@ void nano::port_mapping::check_mapping_loop ()
}
else
{
wait_duration = 300;
wait_duration_l = 300;
if (check_count < 10)
{
node.logger.always_log (boost::str (boost::format ("UPnP No IGD devices found")));
@ -167,7 +175,7 @@ void nano::port_mapping::check_mapping_loop ()
if (on)
{
auto node_l (node.shared ());
node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (wait_duration), [node_l]() {
node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (wait_duration_l), [node_l]() {
node_l->port_mapping.check_mapping_loop ();
});
}
@ -176,16 +184,45 @@ void nano::port_mapping::check_mapping_loop ()
void nano::port_mapping::stop ()
{
on = false;
nano::lock_guard<std::mutex> lock (mutex);
nano::lock_guard<std::mutex> guard_l (mutex);
for (auto & protocol : protocols)
{
if (protocol.external_port != 0)
{
// Be a good citizen for the router and shut down our mapping
auto delete_error (UPNP_DeletePortMapping (urls.controlURL, data.first.servicetype, std::to_string (protocol.external_port).c_str (), protocol.name, address.to_string ().c_str ()));
node.logger.always_log (boost::str (boost::format ("Shutdown port mapping response: %1%") % delete_error));
auto delete_error_l (UPNP_DeletePortMapping (upnp.urls.controlURL, upnp.data.first.servicetype, std::to_string (protocol.external_port).c_str (), protocol.name, address.to_string ().c_str ()));
if (delete_error_l)
{
node.logger.always_log (boost::str (boost::format ("Shutdown port mapping response: %1%") % delete_error_l));
}
}
}
freeUPNPDevlist (devices);
devices = nullptr;
}
nano::upnp_state::~upnp_state ()
{
if (devices)
{
freeUPNPDevlist (devices);
devices = nullptr;
}
}
nano::upnp_state & nano::upnp_state::operator= (nano::upnp_state && other_a)
{
if (this == &other_a)
{
return *this;
}
if (devices)
{
freeUPNPDevlist (devices);
}
devices = other_a.devices;
other_a.devices = nullptr;
urls = other_a.urls;
other_a.urls = { 0 };
data = other_a.data;
other_a.data = { { 0 } };
return *this;
}

View file

@ -22,6 +22,22 @@ public:
uint16_t external_port;
};
/** Collection of discovered UPnP devices and state*/
class upnp_state
{
public:
upnp_state () = default;
~upnp_state ();
upnp_state & operator= (upnp_state &&);
/** List of discovered UPnP devices */
UPNPDev * devices{ nullptr };
/** UPnP collected url information */
UPNPUrls urls{ 0 };
/** UPnP state */
IGDdatas data{ { 0 } };
};
/** UPnP port mapping */
class port_mapping
{
@ -38,18 +54,14 @@ private:
/** Refresh occasionally in case router loses mapping */
void check_mapping_loop ();
int check_mapping ();
std::mutex mutex;
std::string get_config_port (std::string const &);
upnp_state upnp;
nano::node & node;
/** List of all UPnP devices */
UPNPDev * devices;
/** UPnP collected url information */
UPNPUrls urls;
/** UPnP state */
IGDdatas data;
nano::network_params network_params;
boost::asio::ip::address_v4 address;
std::array<mapping_protocol, 2> protocols;
uint64_t check_count;
bool on;
uint64_t check_count{ 0 };
bool on{ false };
std::mutex mutex;
};
}