Changeset View
Changeset View
Standalone View
Standalone View
src/mapport.cpp
| // Copyright (c) 2011-2020 The Bitcoin Core developers | // Copyright (c) 2011-2020 The Bitcoin Core developers | ||||
| // Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
| #if defined(HAVE_CONFIG_H) | #if defined(HAVE_CONFIG_H) | ||||
| #include <config/bitcoin-config.h> | #include <config/bitcoin-config.h> | ||||
| #endif | #endif | ||||
| #include <mapport.h> | #include <mapport.h> | ||||
| #include <clientversion.h> | #include <clientversion.h> | ||||
| #include <common/netif.h> | |||||
| #include <common/pcp.h> | |||||
| #include <common/system.h> | #include <common/system.h> | ||||
| #include <logging.h> | #include <logging.h> | ||||
| #include <net.h> | #include <net.h> | ||||
| #include <netaddress.h> | #include <netaddress.h> | ||||
| #include <netbase.h> | #include <netbase.h> | ||||
| #include <random.h> | |||||
| #include <util/thread.h> | #include <util/thread.h> | ||||
| #include <util/threadinterrupt.h> | #include <util/threadinterrupt.h> | ||||
| #ifdef USE_NATPMP | |||||
| #include <compat/compat.h> | |||||
| #include <natpmp.h> | |||||
| #endif // USE_NATPMP | |||||
| #ifdef USE_UPNP | #ifdef USE_UPNP | ||||
| #include <miniupnpc/miniupnpc.h> | #include <miniupnpc/miniupnpc.h> | ||||
| #include <miniupnpc/upnpcommands.h> | #include <miniupnpc/upnpcommands.h> | ||||
| #include <miniupnpc/upnperrors.h> | #include <miniupnpc/upnperrors.h> | ||||
| // The minimum supported miniUPnPc API version is set to 10. This keeps | // The minimum supported miniUPnPc API version is set to 10. This keeps | ||||
| // compatibility with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. | // compatibility with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. | ||||
| static_assert(MINIUPNPC_API_VERSION >= 10, | static_assert(MINIUPNPC_API_VERSION >= 10, | ||||
| "miniUPnPc API version >= 10 assumed"); | "miniUPnPc API version >= 10 assumed"); | ||||
| #endif // USE_UPNP | #endif // USE_UPNP | ||||
| #include <atomic> | #include <atomic> | ||||
| #include <cassert> | #include <cassert> | ||||
| #include <chrono> | #include <chrono> | ||||
| #include <functional> | #include <functional> | ||||
| #include <string> | #include <string> | ||||
| #include <thread> | #include <thread> | ||||
| #if defined(USE_NATPMP) || defined(USE_UPNP) | |||||
| static CThreadInterrupt g_mapport_interrupt; | static CThreadInterrupt g_mapport_interrupt; | ||||
| static std::thread g_mapport_thread; | static std::thread g_mapport_thread; | ||||
| static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE}; | static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE}; | ||||
| static std::atomic<MapPortProtoFlag> g_mapport_current_proto{ | static std::atomic<MapPortProtoFlag> g_mapport_current_proto{ | ||||
| MapPortProtoFlag::NONE}; | MapPortProtoFlag::NONE}; | ||||
| using namespace std::chrono_literals; | using namespace std::chrono_literals; | ||||
| static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min}; | static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min}; | ||||
| static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min}; | static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min}; | ||||
| #ifdef USE_NATPMP | static bool ProcessPCP() { | ||||
| static uint16_t g_mapport_external_port = 0; | // The same nonce is used for all mappings, this is allowed by the spec, and | ||||
| static bool NatpmpInit(natpmp_t *natpmp) { | // simplifies keeping track of them. | ||||
| const int r_init = initnatpmp(natpmp, /* detect gateway automatically */ 0, | PCPMappingNonce pcp_nonce; | ||||
| /* forced gateway - NOT APPLIED*/ 0); | GetRandBytes(pcp_nonce); | ||||
| 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) { | bool ret = false; | ||||
| external_ipv4_addr = response.pnu.publicaddress.addr; | bool no_resources = false; | ||||
| return true; | const uint16_t private_port = GetListenPort(); | ||||
| } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) { | // Multiply the reannounce period by two, as we'll try to renew | ||||
| LogPrintf("natpmp: The gateway does not support NAT-PMP.\n"); | // approximately halfway. | ||||
| } else { | const uint32_t requested_lifetime = | ||||
| LogPrintf("natpmp: readnatpmpresponseorretry() for public address " | std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count(); | ||||
| "failed with %d error.\n", | uint32_t actual_lifetime = 0; | ||||
| r_read); | std::chrono::milliseconds sleep_time; | ||||
| } | |||||
| } else { | // Local functor to handle result from PCP/NATPMP mapping. | ||||
| LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", | auto handle_mapping = | ||||
| r_send); | [&](std::variant<MappingResult, MappingError> &res) -> void { | ||||
| if (MappingResult *mapping = std::get_if<MappingResult>(&res)) { | |||||
| LogPrintLevel(BCLog::NET, BCLog::Level::Info, | |||||
| "portmap: Added mapping %s\n", mapping->ToString()); | |||||
| AddLocal(mapping->external, LOCAL_MAPPED); | |||||
| ret = true; | |||||
| actual_lifetime = std::min(actual_lifetime, mapping->lifetime); | |||||
| } else if (MappingError *err = std::get_if<MappingError>(&res)) { | |||||
| // Detailed error will already have been logged internally in | |||||
| // respective Portmap function. | |||||
| if (*err == MappingError::NO_RESOURCES) { | |||||
| no_resources = true; | |||||
| } | } | ||||
| 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 { | do { | ||||
| r_read = readnatpmpresponseorretry(natpmp, &response); | actual_lifetime = requested_lifetime; | ||||
| } while (r_read == NATPMP_TRYAGAIN); | no_resources = | ||||
| false; // Set to true if there was any "no resources" error. | |||||
Fabien: move the comments on their own line | |||||
| if (r_read == 0) { | ret = false; // Set to true if any mapping succeeds. | ||||
| auto pm = response.pnu.newportmapping; | |||||
| if (private_port == pm.privateport && pm.lifetime > 0) { | // IPv4 | ||||
| g_mapport_external_port = pm.mappedpublicport; | std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4); | ||||
| const CService external{external_ipv4_addr, | if (!gateway4) { | ||||
| pm.mappedpublicport}; | LogPrintLevel( | ||||
| if (!external_ip_discovered && fDiscover) { | BCLog::NET, BCLog::Level::Debug, | ||||
| AddLocal(external, LOCAL_MAPPED); | "portmap: Could not determine IPv4 default gateway\n"); | ||||
| external_ip_discovered = true; | } else { | ||||
| } | LogPrintLevel(BCLog::NET, BCLog::Level::Debug, | ||||
| LogPrintf( | "portmap: gateway [IPv4]: %s\n", | ||||
| "natpmp: Port mapping successful. External address = %s\n", | gateway4->ToStringAddr()); | ||||
| external.ToStringAddrPort()); | |||||
| return true; | // Open a port mapping on whatever local address we have toward the | ||||
| } else { | // gateway. | ||||
| LogPrintf("natpmp: Port mapping failed.\n"); | struct in_addr inaddr_any; | ||||
| inaddr_any.s_addr = htonl(INADDR_ANY); | |||||
| auto res = | |||||
| PCPRequestPortMap(pcp_nonce, *gateway4, CNetAddr(inaddr_any), | |||||
| private_port, requested_lifetime); | |||||
| MappingError *pcp_err = std::get_if<MappingError>(&res); | |||||
| if (pcp_err && *pcp_err == MappingError::UNSUPP_VERSION) { | |||||
| LogPrintLevel(BCLog::NET, BCLog::Level::Debug, | |||||
| "portmap: Got unsupported PCP version response, " | |||||
| "falling back to NAT-PMP\n"); | |||||
| res = NATPMPRequestPortMap(*gateway4, private_port, | |||||
| requested_lifetime); | |||||
| } | |||||
| handle_mapping(res); | |||||
| } | |||||
| // IPv6 | |||||
| std::optional<CNetAddr> gateway6 = QueryDefaultGateway(NET_IPV6); | |||||
| if (!gateway6) { | |||||
| LogPrintLevel( | |||||
| BCLog::NET, BCLog::Level::Debug, | |||||
| "portmap: Could not determine IPv6 default gateway\n"); | |||||
| } else { | |||||
| LogPrintLevel(BCLog::NET, BCLog::Level::Debug, | |||||
| "portmap: gateway [IPv6]: %s\n", | |||||
| gateway6->ToStringAddr()); | |||||
| // Try to open pinholes for all routable local IPv6 addresses. | |||||
| for (const auto &addr : GetLocalAddresses()) { | |||||
| if (!addr.IsRoutable() || !addr.IsIPv6()) { | |||||
| continue; | |||||
| } | } | ||||
| } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) { | auto res = PCPRequestPortMap(pcp_nonce, *gateway6, addr, | ||||
| LogPrintf("natpmp: The gateway does not support NAT-PMP.\n"); | private_port, requested_lifetime); | ||||
| } else { | handle_mapping(res); | ||||
| LogPrintf("natpmp: readnatpmpresponseorretry() for port mapping " | |||||
| "failed with %d error.\n", | |||||
| r_read); | |||||
| } | } | ||||
| } else { | |||||
| LogPrintf("natpmp: sendnewportmappingrequest() failed with %d error.\n", | |||||
| r_send); | |||||
| } | } | ||||
| // Log message if we got NO_RESOURCES. | |||||
| if (no_resources) { | |||||
| LogPrintLevel( | |||||
| BCLog::NET, BCLog::Level::Warning, | |||||
| "portmap: At least one mapping failed because of a " | |||||
| "NO_RESOURCES error. This usually indicates that the port is " | |||||
| "already used on the router. If this is the only instance of " | |||||
| "bitcoin running on the network, this will resolve itself " | |||||
| "automatically. Otherwise, you might want to choose a " | |||||
| "different P2P port to prevent this conflict.\n"); | |||||
| } | |||||
| // Sanity-check returned lifetime. | |||||
| if (actual_lifetime < 30) { | |||||
| LogPrintLevel(BCLog::NET, BCLog::Level::Warning, | |||||
| "portmap: Got impossibly short mapping lifetime of " | |||||
| "%d seconds\n", | |||||
| actual_lifetime); | |||||
| return false; | return false; | ||||
| } | } | ||||
| // RFC6887 11.2.1 recommends that clients send their first renewal | |||||
| // packet at a time chosen with uniform random distribution in the range | |||||
| // 1/2 to 5/8 of expiration time. | |||||
| std::chrono::seconds sleep_time_min(actual_lifetime / 2); | |||||
| std::chrono::seconds sleep_time_max(actual_lifetime * 5 / 8); | |||||
| sleep_time = sleep_time_min + | |||||
| FastRandomContext().randrange<std::chrono::milliseconds>( | |||||
| sleep_time_max - sleep_time_min); | |||||
| } while (ret && g_mapport_interrupt.sleep_for(sleep_time)); | |||||
| // We don't delete the mappings when the thread is interrupted because this | |||||
| // would add additional complexity, so we rather just choose a fairly short | |||||
| // expiry time. | |||||
| [[maybe_unused]] 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; | return ret; | ||||
| } | } | ||||
| #endif // USE_NATPMP | |||||
| #ifdef USE_UPNP | #ifdef USE_UPNP | ||||
| static bool ProcessUpnp() { | static bool ProcessUpnp() { | ||||
| bool ret = false; | bool ret = false; | ||||
| std::string port = strprintf("%u", GetListenPort()); | std::string port = strprintf("%u", GetListenPort()); | ||||
| const char *multicastif = nullptr; | const char *multicastif = nullptr; | ||||
| const char *minissdpdpath = nullptr; | const char *minissdpdpath = nullptr; | ||||
| struct UPNPDev *devlist = nullptr; | struct UPNPDev *devlist = nullptr; | ||||
| ▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| #endif // USE_UPNP | #endif // USE_UPNP | ||||
| static void ThreadMapPort() { | static void ThreadMapPort() { | ||||
| bool ok; | bool ok; | ||||
| do { | do { | ||||
| ok = false; | ok = false; | ||||
| #ifdef USE_UPNP | |||||
| // High priority protocol. | // High priority protocol. | ||||
| if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) { | if (g_mapport_enabled_protos & MapPortProtoFlag::PCP) { | ||||
| g_mapport_current_proto = MapPortProtoFlag::UPNP; | g_mapport_current_proto = MapPortProtoFlag::PCP; | ||||
| ok = ProcessUpnp(); | ok = ProcessPCP(); | ||||
| if (ok) { | if (ok) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| } | } | ||||
| #endif // USE_UPNP | |||||
| #ifdef USE_NATPMP | #ifdef USE_UPNP | ||||
| // Low priority protocol. | // Low priority protocol. | ||||
| if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) { | if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) { | ||||
| g_mapport_current_proto = MapPortProtoFlag::NAT_PMP; | g_mapport_current_proto = MapPortProtoFlag::UPNP; | ||||
| ok = ProcessNatpmp(); | ok = ProcessUpnp(); | ||||
| if (ok) { | if (ok) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| } | } | ||||
| #endif // USE_NATPMP | #endif // USE_UPNP | ||||
| g_mapport_current_proto = MapPortProtoFlag::NONE; | g_mapport_current_proto = MapPortProtoFlag::NONE; | ||||
| if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) { | if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) { | ||||
| return; | return; | ||||
| } | } | ||||
| } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD)); | } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD)); | ||||
| } | } | ||||
| Show All 29 Lines | if (g_mapport_enabled_protos & g_mapport_current_proto) { | ||||
| // Enabling another protocol does not cause switching from the currently | // Enabling another protocol does not cause switching from the currently | ||||
| // used one. | // used one. | ||||
| return; | return; | ||||
| } | } | ||||
| assert(g_mapport_thread.joinable()); | assert(g_mapport_thread.joinable()); | ||||
| assert(!g_mapport_interrupt); | assert(!g_mapport_interrupt); | ||||
| // Interrupt a protocol-specific loop in the ThreadUpnp() or in the | // Interrupt a protocol-specific loop in the ThreadUpnp() or in the | ||||
| // ThreadNatpmp() to force trying the next protocol in the ThreadMapPort() | // ThreadPCP() to force trying the next protocol in the ThreadMapPort() | ||||
| // loop. | // loop. | ||||
| g_mapport_interrupt(); | g_mapport_interrupt(); | ||||
| } | } | ||||
| static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled) { | static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled) { | ||||
| if (enabled) { | if (enabled) { | ||||
| g_mapport_enabled_protos |= proto; | g_mapport_enabled_protos |= proto; | ||||
| } else { | } else { | ||||
| g_mapport_enabled_protos &= ~proto; | g_mapport_enabled_protos &= ~proto; | ||||
| } | } | ||||
| } | } | ||||
| void StartMapPort(bool use_upnp, bool use_natpmp) { | void StartMapPort(bool use_upnp, bool use_pcp) { | ||||
| MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp); | MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp); | ||||
| MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp); | MapPortProtoSetEnabled(MapPortProtoFlag::PCP, use_pcp); | ||||
| DispatchMapPort(); | DispatchMapPort(); | ||||
| } | } | ||||
| void InterruptMapPort() { | void InterruptMapPort() { | ||||
| g_mapport_enabled_protos = MapPortProtoFlag::NONE; | g_mapport_enabled_protos = MapPortProtoFlag::NONE; | ||||
| if (g_mapport_thread.joinable()) { | if (g_mapport_thread.joinable()) { | ||||
| g_mapport_interrupt(); | g_mapport_interrupt(); | ||||
| } | } | ||||
| } | } | ||||
| void StopMapPort() { | void StopMapPort() { | ||||
| if (g_mapport_thread.joinable()) { | if (g_mapport_thread.joinable()) { | ||||
| g_mapport_thread.join(); | g_mapport_thread.join(); | ||||
| g_mapport_interrupt.reset(); | 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) | |||||
move the comments on their own line