aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlaanwj <126646+laanwj@users.noreply.github.com>2024-05-05 16:20:45 +0200
committerlaanwj <126646+laanwj@users.noreply.github.com>2024-09-30 11:37:55 +0200
commit52f8ef66c61b82457a161f3b90cc87f57d1dda80 (patch)
tree6cf7341f4452f4f4649d42d1108cc4683027b41e
parent97c97177cdb2f596aa7d4a65c4bde87de50a96f2 (diff)
net: Replace libnatpmp with built-in NATPMP+PCP implementation in mapport
-rw-r--r--src/init.cpp8
-rw-r--r--src/interfaces/node.h2
-rw-r--r--src/mapport.cpp208
-rw-r--r--src/mapport.h4
-rw-r--r--src/net.h2
-rw-r--r--src/node/interfaces.cpp2
6 files changed, 97 insertions, 129 deletions
diff --git a/src/init.cpp b/src/init.cpp
index 8fbdb880bc..a82f79ade4 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -571,11 +571,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
#else
hidden_args.emplace_back("-upnp");
#endif
-#ifdef USE_NATPMP
- argsman.AddArg("-natpmp", strprintf("Use NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
-#else
- hidden_args.emplace_back("-natpmp");
-#endif // USE_NATPMP
+ argsman.AddArg("-natpmp", strprintf("Use PCP or NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-whitebind=<[permissions@]addr>", "Bind to the given address and add permission flags to the peers connecting to it. "
"Use [host]:port notation for IPv6. Allowed permissions: " + Join(NET_PERMISSIONS_DOC, ", ") + ". "
"Specify multiple permissions separated by commas (default: download,noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -1858,7 +1854,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
LogPrintf("nBestHeight = %d\n", chain_active_height);
if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time});
- // Map ports with UPnP or NAT-PMP.
+ // Map ports with UPnP or NAT-PMP
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP));
CConnman::Options connOptions;
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index b87c78db52..91a623a65d 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -121,7 +121,7 @@ public:
virtual void resetSettings() = 0;
//! Map port.
- virtual void mapPort(bool use_upnp, bool use_natpmp) = 0;
+ virtual void mapPort(bool use_upnp, bool use_pcp) = 0;
//! Get proxy.
virtual bool getProxy(Network net, Proxy& proxy_info) = 0;
diff --git a/src/mapport.cpp b/src/mapport.cpp
index 1920297be6..fd15708edc 100644
--- a/src/mapport.cpp
+++ b/src/mapport.cpp
@@ -12,14 +12,12 @@
#include <net.h>
#include <netaddress.h>
#include <netbase.h>
+#include <random.h>
+#include <util/netif.h>
+#include <util/pcp.h>
#include <util/thread.h>
#include <util/threadinterrupt.h>
-#ifdef USE_NATPMP
-#include <compat/compat.h>
-#include <natpmp.h>
-#endif // USE_NATPMP
-
#ifdef USE_UPNP
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
@@ -36,7 +34,6 @@ static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed"
#include <string>
#include <thread>
-#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};
@@ -46,104 +43,96 @@ 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)
+static bool ProcessPCP()
{
- 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;
-}
+ // The same nonce is used for all mappings, this is allowed by the spec, and simplifies keeping track of them.
+ PCPMappingNonce pcp_nonce;
+ GetRandBytes(pcp_nonce);
-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");
+ bool ret = false;
+ bool no_resources = false;
+ const uint16_t private_port = GetListenPort();
+ // Multiply the reannounce period by two, as we'll try to renew approximately halfway.
+ const uint32_t requested_lifetime = std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count();
+ uint32_t actual_lifetime = 0;
+ std::chrono::milliseconds sleep_time;
+
+ // Local functor to handle result from PCP/NATPMP mapping.
+ auto handle_mapping = [&](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;
+ }
+ }
+ };
+
+ do {
+ actual_lifetime = requested_lifetime;
+ no_resources = false; // Set to true if there was any "no resources" error.
+ ret = false; // Set to true if any mapping succeeds.
+
+ // IPv4
+ std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4);
+ if (!gateway4) {
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv4 default gateway\n");
} else {
- LogPrintf("natpmp: readnatpmpresponseorretry() for public address failed with %d error.\n", r_read);
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv4]: %s\n", gateway4->ToStringAddr());
+
+ // Open a port mapping on whatever local address we have toward the gateway.
+ 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);
}
- } else {
- LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", r_send);
- }
- return false;
-}
+ // 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());
-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.ToStringAddrPort());
- return true;
- } else {
- LogPrintf("natpmp: Port mapping failed.\n");
+ // Try to open pinholes for all routable local IPv6 addresses.
+ for (const auto &addr: GetLocalAddresses()) {
+ if (!addr.IsRoutable() || !addr.IsIPv6()) continue;
+ auto res = PCPRequestPortMap(pcp_nonce, *gateway6, addr, private_port, requested_lifetime);
+ handle_mapping(res);
}
- } 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();
+ // 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");
+ }
- 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);
+ // 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;
}
- }
+ // 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.
- closenatpmp(&natpmp);
return ret;
}
-#endif // USE_NATPMP
#ifdef USE_UPNP
static bool ProcessUpnp()
@@ -223,23 +212,21 @@ static void ThreadMapPort()
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 (g_mapport_enabled_protos & MapPortProtoFlag::PCP) {
+ g_mapport_current_proto = MapPortProtoFlag::PCP;
+ ok = ProcessPCP();
if (ok) continue;
}
-#endif // USE_UPNP
-#ifdef USE_NATPMP
+#ifdef USE_UPNP
// Low priority protocol.
- if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) {
- g_mapport_current_proto = MapPortProtoFlag::NAT_PMP;
- ok = ProcessNatpmp();
+ if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
+ g_mapport_current_proto = MapPortProtoFlag::UPNP;
+ ok = ProcessUpnp();
if (ok) continue;
}
-#endif // USE_NATPMP
+#endif // USE_UPNP
g_mapport_current_proto = MapPortProtoFlag::NONE;
if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
@@ -281,7 +268,7 @@ static void DispatchMapPort()
assert(g_mapport_thread.joinable());
assert(!g_mapport_interrupt);
- // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp()
+ // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadPCP()
// to force trying the next protocol in the ThreadMapPort() loop.
g_mapport_interrupt();
}
@@ -295,10 +282,10 @@ static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
}
}
-void StartMapPort(bool use_upnp, bool use_natpmp)
+void StartMapPort(bool use_upnp, bool use_pcp)
{
MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
- MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp);
+ MapPortProtoSetEnabled(MapPortProtoFlag::PCP, use_pcp);
DispatchMapPort();
}
@@ -317,18 +304,3 @@ void StopMapPort()
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)
diff --git a/src/mapport.h b/src/mapport.h
index 6f55c46f6c..51202687f2 100644
--- a/src/mapport.h
+++ b/src/mapport.h
@@ -12,10 +12,10 @@ static constexpr bool DEFAULT_NATPMP = false;
enum MapPortProtoFlag : unsigned int {
NONE = 0x00,
UPNP = 0x01,
- NAT_PMP = 0x02,
+ PCP = 0x02, // PCP with NAT-PMP fallback.
};
-void StartMapPort(bool use_upnp, bool use_natpmp);
+void StartMapPort(bool use_upnp, bool use_pcp);
void InterruptMapPort();
void StopMapPort();
diff --git a/src/net.h b/src/net.h
index 2f65a01ce1..86618bed91 100644
--- a/src/net.h
+++ b/src/net.h
@@ -148,7 +148,7 @@ enum
LOCAL_NONE, // unknown
LOCAL_IF, // address a local interface listens on
LOCAL_BIND, // address explicit bound to
- LOCAL_MAPPED, // address reported by UPnP or NAT-PMP
+ LOCAL_MAPPED, // address reported by UPnP or PCP
LOCAL_MANUAL, // address explicitly specified (-externalip=)
LOCAL_MAX
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 0e418c58bb..babd6fdd83 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -181,7 +181,7 @@ public:
});
args().WriteSettingsFile();
}
- void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); }
+ void mapPort(bool use_upnp, bool use_pcp) override { StartMapPort(use_upnp, use_pcp); }
bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); }
size_t getNodeCount(ConnectionDirection flags) override
{