// 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_NATPMP #include #include #endif // USE_NATPMP #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 // USE_UPNP #include #include #include #include #include #include #if defined(USE_NATPMP) || defined(USE_UPNP) static CThreadInterrupt g_mapport_interrupt; static std::thread g_mapport_thread; static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE}; static std::atomic g_mapport_current_proto{MapPortProtoFlag::NONE}; using namespace std::chrono_literals; static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min}; static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min}; #ifdef USE_NATPMP static uint16_t g_mapport_external_port = 0; static bool NatpmpInit(natpmp_t* natpmp) { const int r_init = initnatpmp(natpmp, /* detect gateway automatically */ 0, /* forced gateway - NOT APPLIED*/ 0); if (r_init == 0) return true; LogPrintf("natpmp: initnatpmp() failed with %d error.\n", r_init); return false; } static bool NatpmpDiscover(natpmp_t* natpmp, struct in_addr& external_ipv4_addr) { const int r_send = sendpublicaddressrequest(natpmp); if (r_send == 2 /* OK */) { int r_read; natpmpresp_t response; do { r_read = readnatpmpresponseorretry(natpmp, &response); } while (r_read == NATPMP_TRYAGAIN); if (r_read == 0) { external_ipv4_addr = response.pnu.publicaddress.addr; return true; } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) { LogPrintf("natpmp: The gateway does not support NAT-PMP.\n"); } else { LogPrintf("natpmp: readnatpmpresponseorretry() for public address failed with %d error.\n", r_read); } } else { LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", r_send); } return false; } static bool NatpmpMapping(natpmp_t* natpmp, const struct in_addr& external_ipv4_addr, uint16_t private_port, bool& external_ip_discovered) { const uint16_t suggested_external_port = g_mapport_external_port ? g_mapport_external_port : private_port; const int r_send = sendnewportmappingrequest(natpmp, NATPMP_PROTOCOL_TCP, private_port, suggested_external_port, 3600 /*seconds*/); if (r_send == 12 /* OK */) { int r_read; natpmpresp_t response; do { r_read = readnatpmpresponseorretry(natpmp, &response); } while (r_read == NATPMP_TRYAGAIN); if (r_read == 0) { auto pm = response.pnu.newportmapping; if (private_port == pm.privateport && pm.lifetime > 0) { g_mapport_external_port = pm.mappedpublicport; const CService external{external_ipv4_addr, pm.mappedpublicport}; if (!external_ip_discovered && fDiscover) { AddLocal(external, LOCAL_MAPPED); external_ip_discovered = true; } LogPrintf("natpmp: Port mapping successful. External address = %s\n", external.ToString()); return true; } else { LogPrintf("natpmp: Port mapping failed.\n"); } } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) { LogPrintf("natpmp: The gateway does not support NAT-PMP.\n"); } else { LogPrintf("natpmp: readnatpmpresponseorretry() for port mapping failed with %d error.\n", r_read); } } else { LogPrintf("natpmp: sendnewportmappingrequest() failed with %d error.\n", r_send); } return false; } static bool ProcessNatpmp() { bool ret = false; natpmp_t natpmp; struct in_addr external_ipv4_addr; if (NatpmpInit(&natpmp) && NatpmpDiscover(&natpmp, external_ipv4_addr)) { bool external_ip_discovered = false; const uint16_t private_port = GetListenPort(); do { ret = NatpmpMapping(&natpmp, external_ipv4_addr, private_port, external_ip_discovered); } while (ret && g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); g_mapport_interrupt.reset(); const int r_send = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, private_port, g_mapport_external_port, /* remove a port mapping */ 0); g_mapport_external_port = 0; if (r_send == 12 /* OK */) { LogPrintf("natpmp: Port mapping removed successfully.\n"); } else { LogPrintf("natpmp: sendnewportmappingrequest(0) failed with %d error.\n", r_send); } } closenatpmp(&natpmp); return ret; } #endif // USE_NATPMP #ifdef USE_UPNP 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().c_str()); AddLocal(resolved, LOCAL_MAPPED); } } 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_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); g_mapport_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; } #endif // USE_UPNP static void ThreadMapPort() { bool ok; do { ok = false; #ifdef USE_UPNP // High priority protocol. if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) { g_mapport_current_proto = MapPortProtoFlag::UPNP; ok = ProcessUpnp(); if (ok) continue; } #endif // USE_UPNP #ifdef USE_NATPMP // Low priority protocol. if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) { g_mapport_current_proto = MapPortProtoFlag::NAT_PMP; ok = ProcessNatpmp(); if (ok) continue; } #endif // USE_NATPMP g_mapport_current_proto = MapPortProtoFlag::NONE; if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) { return; } } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD)); } void StartThreadMapPort() { if (!g_mapport_thread.joinable()) { assert(!g_mapport_interrupt); g_mapport_thread = std::thread(std::bind(&TraceThread, "mapport", &ThreadMapPort)); } } static void DispatchMapPort() { if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) { return; } if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) { StartThreadMapPort(); return; } if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) { InterruptMapPort(); StopMapPort(); return; } if (g_mapport_enabled_protos & g_mapport_current_proto) { // Enabling another protocol does not cause switching from the currently used one. return; } assert(g_mapport_thread.joinable()); assert(!g_mapport_interrupt); // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp() // to force trying the next protocol in the ThreadMapPort() loop. g_mapport_interrupt(); } static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled) { if (enabled) { g_mapport_enabled_protos |= proto; } else { g_mapport_enabled_protos &= ~proto; } } void StartMapPort(bool use_upnp, bool use_natpmp) { MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp); MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp); DispatchMapPort(); } void InterruptMapPort() { g_mapport_enabled_protos = MapPortProtoFlag::NONE; if (g_mapport_thread.joinable()) { g_mapport_interrupt(); } } void StopMapPort() { if (g_mapport_thread.joinable()) { g_mapport_thread.join(); g_mapport_interrupt.reset(); } } #else // #if defined(USE_NATPMP) || defined(USE_UPNP) void StartMapPort(bool use_upnp, bool use_natpmp) { // Intentionally left blank. } void InterruptMapPort() { // Intentionally left blank. } void StopMapPort() { // Intentionally left blank. } #endif // #if defined(USE_NATPMP) || defined(USE_UPNP)