diff --git a/CMakeLists.txt b/CMakeLists.txt index f4ce3b738958d2225f0bac55650dcacafcceb383..d78795898045cf06935d6c780b9e9d5544eb1e3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,12 @@ if (BUILD_TOOLS) target_link_libraries(dnc PRIVATE dhtnet fmt::fmt) target_include_directories(dnc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools) install(TARGETS dnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(upnpctrl + tools/upnp/upnpctrl.cpp) + target_link_libraries(upnpctrl PRIVATE dhtnet fmt::fmt readline) + target_include_directories(upnpctrl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools) + install(TARGETS upnpctrl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() if (BUILD_TESTING AND NOT MSVC) diff --git a/tools/upnp/upnpctrl.cpp b/tools/upnp/upnpctrl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..586898b15156d8e04a2d13310cd89bcd722953a8 --- /dev/null +++ b/tools/upnp/upnpctrl.cpp @@ -0,0 +1,96 @@ +#include "upnp/upnp_control.h" +#include "upnp/upnp_context.h" +#include "string_utils.h" +#include <asio/executor_work_guard.hpp> +#include <opendht/log.h> + +#include <readline/readline.h> +#include <readline/history.h> + +void +print_help() +{ + fmt::print("Commands:\n" + " help, h, ?\n" + " quit, exit, q, x\n" + " ip\n" + " open <port> <protocol>\n"); +} + +std::string to_lower(std::string_view str_v) { + std::string str(str_v); + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c){ return std::tolower(c); } + ); + return str; +} + +int +main(int argc, char** argv) +{ + auto ioContext = std::make_shared<asio::io_context>(); + std::shared_ptr<dht::log::Logger> logger = dht::log::getStdLogger(); + auto upnpContext = std::make_shared<dhtnet::upnp::UPnPContext>(ioContext, logger); + + auto ioContextRunner = std::make_shared<std::thread>([context = ioContext]() { + try { + auto work = asio::make_work_guard(*context); + context->run(); + } catch (const std::exception& ex) { + // print the error; + } + }); + + auto controller = std::make_shared<dhtnet::upnp::Controller>(upnpContext); + std::set<std::shared_ptr<dhtnet::upnp::Mapping>> mappings; + + while (true) { + char* l = readline("> "); + if (not l) + break; + std::string_view line{l}; + if (line.empty()) + continue; + add_history(l); + auto args = dhtnet::split_string(line, ' '); + auto command = args[0]; + if (command == "quit" || command == "exit" || command == "q" || command == "x") + break; + else if (command == "help" || command == "h" || command == "?") { + print_help(); + } + else if (command == "ip") { + fmt::print("{}\n", controller->getExternalIP().toString()); + } else if (command == "open") { + if (args.size() < 3) { + fmt::print("Usage: open <port> <protocol>\n"); + continue; + } + auto protocol = to_lower(args[2]) == "udp" ? dhtnet::upnp::PortType::UDP : dhtnet::upnp::PortType::TCP; + mappings.emplace(controller->reserveMapping(dhtnet::to_int<in_port_t>(args[1]), protocol)); + } else if (command == "close") { + if (args.size() < 2) { + fmt::print("Usage: close <port>\n"); + continue; + } + auto port = dhtnet::to_int<in_port_t>(args[1]); + for (auto it = mappings.begin(); it != mappings.end(); ) { + if ((*it)->getExternalPort() == port) { + controller->releaseMapping(**it); + it = mappings.erase(it); + } else { + ++it; + } + } + } else { + fmt::print("Unknown command: {}\n", command); + } + } + fmt::print("Stopping..."); + for (auto c: mappings) + controller->releaseMapping(*c); + mappings.clear(); + + ioContext->stop(); + ioContextRunner->join(); +}