diff --git a/.github/workflows/test-sync.yml b/.github/workflows/test-sync.yml index 03f30ea..f8ffc4e 100644 --- a/.github/workflows/test-sync.yml +++ b/.github/workflows/test-sync.yml @@ -24,7 +24,7 @@ jobs: run: | mkdir build cd build - cmake .. -DDEV_TEST_SYNC=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 + cmake .. -DWITH_UPNP=OFF -DDEV_TEST_SYNC=ON -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 make -j$(nproc) - name: Run p2pool @@ -60,7 +60,7 @@ jobs: run: | mkdir build cd build - cmake .. -DDEV_TEST_SYNC=ON + cmake .. -DWITH_UPNP=OFF -DDEV_TEST_SYNC=ON make -j3 - name: Run p2pool @@ -96,7 +96,7 @@ jobs: run: | mkdir build cd build - cmake .. -G "Visual Studio 17 2022" -DDEV_TEST_SYNC=ON + cmake .. -G "Visual Studio 17 2022" -DWITH_UPNP=OFF -DDEV_TEST_SYNC=ON & "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Msbuild\\Current\\Bin\\amd64\\msbuild" /m /p:Configuration=Debug p2pool.vcxproj - name: Run p2pool diff --git a/docs/COMMAND_LINE.MD b/docs/COMMAND_LINE.MD index d4c66c2..1029106 100644 --- a/docs/COMMAND_LINE.MD +++ b/docs/COMMAND_LINE.MD @@ -24,8 +24,9 @@ --no-autodiff Disable automatic difficulty adjustment for miners connected to stratum (WARNING: incompatible with Nicehash and MRR) --rpc-login Specify username[:password] required for Monero RPC server --socks5 Specify IP:port of a SOCKS5 proxy to use for outgoing connections ---no-dns disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too) ---p2p-external-port port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections +--no-dns Disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too) +--p2p-external-port Port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections +--no-upnp Disable UPnP port forwarding ``` ### Example command line diff --git a/src/main.cpp b/src/main.cpp index e3a3135..5c502ce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,8 +49,11 @@ void p2pool_usage() "--no-autodiff Disable automatic difficulty adjustment for miners connected to stratum (WARNING: incompatible with Nicehash and MRR)\n" "--rpc-login Specify username[:password] required for Monero RPC server\n" "--socks5 Specify IP:port of a SOCKS5 proxy to use for outgoing connections\n" - "--no-dns disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too)\n" - "--p2p-external-port port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections\n" + "--no-dns Disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too)\n" + "--p2p-external-port Port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections\n" +#ifdef WITH_UPNP + "--no-upnp Disable UPnP port forwarding\n" +#endif "--help Show this help message\n\n" "Example command line:\n\n" "%s --host 127.0.0.1 --rpc-port 18081 --zmq-port 18083 --wallet YOUR_WALLET_ADDRESS --stratum 0.0.0.0:%d --p2p 0.0.0.0:%d\n\n", diff --git a/src/p2p_server.cpp b/src/p2p_server.cpp index 242d6d6..87ce933 100644 --- a/src/p2p_server.cpp +++ b/src/p2p_server.cpp @@ -134,7 +134,7 @@ P2PServer::P2PServer(p2pool* pool) } load_peer_list(); - start_listening(params.m_p2pAddresses); + start_listening(params.m_p2pAddresses, params.m_upnp); } P2PServer::~P2PServer() @@ -989,7 +989,7 @@ void P2PServer::show_peers() const LOGINFO(0, "Total: " << n << " peers"); } -int P2PServer::listen_port() const +int P2PServer::external_listen_port() const { const Params& params = m_pool->params(); return params.m_p2pExternalPort ? params.m_p2pExternalPort : m_listenPort; @@ -1848,7 +1848,7 @@ void P2PServer::P2PClient::on_after_handshake(uint8_t* &p) LOGINFO(5, "sending LISTEN_PORT to " << static_cast(m_addrString)); *(p++) = static_cast(MessageId::LISTEN_PORT); - const int32_t port = m_owner->listen_port(); + const int32_t port = m_owner->external_listen_port(); memcpy(p, &port, sizeof(port)); p += sizeof(port); diff --git a/src/p2p_server.h b/src/p2p_server.h index 9f00446..639ce19 100644 --- a/src/p2p_server.h +++ b/src/p2p_server.h @@ -155,7 +155,7 @@ public: void show_peers_async(); size_t peer_list_size() const { MutexLock lock(m_peerListLock); return m_peerList.size(); } - int listen_port() const override; + int external_listen_port() const override; uint32_t max_outgoing_peers() const { return m_maxOutgoingPeers; } uint32_t max_incoming_peers() const { return m_maxIncomingPeers; } diff --git a/src/params.cpp b/src/params.cpp index e9b9855..4b0f69b 100644 --- a/src/params.cpp +++ b/src/params.cpp @@ -151,6 +151,11 @@ Params::Params(int argc, char* argv[]) ok = true; } + if (strcmp(argv[i], "--no-upnp") == 0) { + m_upnp = false; + ok = true; + } + if (!ok) { fprintf(stderr, "Unknown command line parameter %s\n\n", argv[i]); p2pool_usage(); diff --git a/src/params.h b/src/params.h index e812d25..5d1bc84 100644 --- a/src/params.h +++ b/src/params.h @@ -53,6 +53,7 @@ struct Params std::string m_socks5Proxy; bool m_dns = true; uint32_t m_p2pExternalPort = 0; + bool m_upnp = true; }; } // namespace p2pool diff --git a/src/stratum_server.cpp b/src/stratum_server.cpp index 9fe8f90..47a1ae1 100644 --- a/src/stratum_server.cpp +++ b/src/stratum_server.cpp @@ -82,7 +82,7 @@ StratumServer::StratumServer(p2pool* pool) uv_async_init_checked(&m_loop, &m_showWorkersAsync, on_show_workers); m_showWorkersAsync.data = this; - start_listening(pool->params().m_stratumAddresses); + start_listening(pool->params().m_stratumAddresses, pool->params().m_upnp); } StratumServer::~StratumServer() diff --git a/src/tcp_server.h b/src/tcp_server.h index c0d903b..c721ec1 100644 --- a/src/tcp_server.h +++ b/src/tcp_server.h @@ -42,7 +42,7 @@ public: uv_loop_t* get_loop() { return &m_loop; } - virtual int listen_port() const { return m_listenPort; } + virtual int external_listen_port() const { return m_listenPort; } bool connect_to_peer(bool is_v6, const raw_ip& ip, int port); virtual void on_connect_failed(bool /*is_v6*/, const raw_ip& /*ip*/, int /*port*/) {} @@ -158,7 +158,7 @@ private: uv_thread_t m_loopThread; protected: - void start_listening(const std::string& listen_addresses); + void start_listening(const std::string& listen_addresses, bool upnp); std::string m_socks5Proxy; bool m_socks5ProxyV6; diff --git a/src/tcp_server.inl b/src/tcp_server.inl index 0858433..ff5d633 100644 --- a/src/tcp_server.inl +++ b/src/tcp_server.inl @@ -128,7 +128,7 @@ void TCPServer::parse_address_list(const std::str } template -void TCPServer::start_listening(const std::string& listen_addresses) +void TCPServer::start_listening(const std::string& listen_addresses, bool upnp) { if (listen_addresses.empty()) { LOGERR(1, "listen address not set"); @@ -206,6 +206,14 @@ void TCPServer::start_listening(const std::string LOGINFO(1, "listening on " << log::Gray() << address); }); +#ifdef WITH_UPNP + if (upnp) { + add_portmapping(external_listen_port(), m_listenPort); + } +#else + (void)upnp; +#endif + const int err = uv_thread_create(&m_loopThread, loop, this); if (err) { LOGERR(1, "failed to start event loop thread, error " << uv_err_name(err)); diff --git a/src/util.cpp b/src/util.cpp index 4c0464c..d30cfe1 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -25,6 +25,11 @@ #include #endif +#ifdef WITH_UPNP +#include "miniupnpc.h" +#include "upnpcommands.h" +#endif + static constexpr char log_category_prefix[] = "Util "; namespace p2pool { @@ -560,4 +565,57 @@ UV_LoopUserData* GetLoopUserData(uv_loop_t* loop, bool create) return data; } +#ifdef WITH_UPNP +static struct UPnP_Discover +{ + UPnP_Discover() { devlist = upnpDiscover(1000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &error); } + ~UPnP_Discover() { freeUPNPDevlist(devlist); } + + int error; + UPNPDev* devlist; +} upnp_discover; + +void add_portmapping(int external_port, int internal_port) +{ + LOGINFO(1, "UPnP: trying to map WAN:" << external_port << " to LAN:" << internal_port); + + if (!upnp_discover.devlist) { + LOGWARN(1, "upnpDiscover: no UPnP IGD devices found, error " << upnp_discover.error); + return; + } + + UPNPUrls urls; + IGDdatas data; + char local_addr[64] = {}; + + int result = UPNP_GetValidIGD(upnp_discover.devlist, &urls, &data, local_addr, sizeof(local_addr)); + if (result != 1) { + LOGWARN(1, "UPNP_GetValidIGD returned " << result << ", no valid UPnP IGD devices found"); + return; + } + + LOGINFO(1, "UPnP: LAN IP address " << log::Gray() << static_cast(local_addr)); + + char ext_addr[64] = {}; + result = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, ext_addr); + if ((result != UPNPCOMMAND_SUCCESS) || !ext_addr[0]) { + LOGWARN(1, "UPNP_GetExternalIPAddress: failed to query external IP address, error " << result); + } + else { + LOGINFO(1, "UPnP: WAN IP address " << log::Gray() << static_cast(ext_addr)); + } + + const std::string eport = std::to_string(external_port); + const std::string iport = std::to_string(internal_port); + + result = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, eport.c_str(), iport.c_str(), local_addr, "P2Pool", "TCP", nullptr, nullptr); + if (result) { + LOGWARN(1, "UPNP_AddPortMapping returned error " << result); + } + else { + LOGINFO(1, "UPnP: Mapped " << log::Gray() << static_cast(ext_addr) << ':' << external_port << log::NoColor() << " to " << log::Gray() << static_cast(local_addr) << ':' << internal_port); + } +} +#endif + } // namespace p2pool diff --git a/src/util.h b/src/util.h index 2c1b476..6eb15b4 100644 --- a/src/util.h +++ b/src/util.h @@ -246,6 +246,10 @@ FORCEINLINE uint64_t bsr(uint64_t x) bool str_to_ip(bool is_v6, const char* ip, raw_ip& result); bool is_localhost(const std::string& host); +#ifdef WITH_UPNP +void add_portmapping(int external_port, int internal_port); +#endif + } // namespace p2pool void memory_tracking_start();