// Copyright (c) 2011-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #ifdef USE_UPNP #include #include #include // The minimum supported miniUPnPc API version is set to 10. This keeps compatibility // with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); #endif #include #include #include #include #include #include #ifdef USE_UPNP static CThreadInterrupt g_upnp_interrupt; static std::thread g_upnp_thread; static std::atomic_uint g_mapport_target_proto{MapPortProtoFlag::NONE}; using namespace std::chrono_literals; static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min}; static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min}; static bool ProcessUpnp() { bool ret = false; std::string port = strprintf("%u", GetListenPort()); const char * multicastif = nullptr; const char * minissdpdpath = nullptr; struct UPNPDev * devlist = nullptr; char lanaddr[64]; int error = 0; #if MINIUPNPC_API_VERSION < 14 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); #else devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); #endif struct UPNPUrls urls; struct IGDdatas data; int r; r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); if (r == 1) { if (fDiscover) { char externalIPAddress[40]; r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); if (r != UPNPCOMMAND_SUCCESS) { LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); } else { if (externalIPAddress[0]) { CNetAddr resolved; if (LookupHost(externalIPAddress, resolved, false)) { LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); AddLocal(resolved, LOCAL_UPNP); } } else { LogPrintf("UPnP: GetExternalIPAddress failed.\n"); } } } std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); do { r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); if (r != UPNPCOMMAND_SUCCESS) { ret = false; LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); break; } else { ret = true; LogPrintf("UPnP Port Mapping successful.\n"); } } while (g_upnp_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); g_upnp_interrupt.reset(); r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); freeUPNPDevlist(devlist); devlist = nullptr; FreeUPNPUrls(&urls); } else { LogPrintf("No valid UPnP IGDs found\n"); freeUPNPDevlist(devlist); devlist = nullptr; if (r != 0) FreeUPNPUrls(&urls); } return ret; } static void ThreadMapPort() { do { if (ProcessUpnp()) return; } while (g_upnp_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD)); } void StartThreadMapPort() { if (!g_upnp_thread.joinable()) { assert(!g_upnp_interrupt); g_upnp_thread = std::thread((std::bind(&TraceThread, "upnp", &ThreadMapPort))); } } static void DispatchMapPort() { if (g_mapport_target_proto == MapPortProtoFlag::UPNP) { StartThreadMapPort(); } else { InterruptMapPort(); StopMapPort(); } } static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled) { if (enabled) { g_mapport_target_proto |= proto; } else { g_mapport_target_proto &= ~proto; } } void StartMapPort(bool use_upnp) { MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp); DispatchMapPort(); } void InterruptMapPort() { if(g_upnp_thread.joinable()) { g_upnp_interrupt(); } } void StopMapPort() { if(g_upnp_thread.joinable()) { g_upnp_thread.join(); g_upnp_interrupt.reset(); } } #else void StartMapPort(bool use_upnp) { // Intentionally left blank. } void InterruptMapPort() { // Intentionally left blank. } void StopMapPort() { // Intentionally left blank. } #endif