Skip to content

Commit 52f8ef6

Browse files
committed
net: Replace libnatpmp with built-in NATPMP+PCP implementation in mapport
1 parent 97c9717 commit 52f8ef6

File tree

6 files changed

+97
-129
lines changed

6 files changed

+97
-129
lines changed

src/init.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -571,11 +571,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
571571
#else
572572
hidden_args.emplace_back("-upnp");
573573
#endif
574-
#ifdef USE_NATPMP
575-
argsman.AddArg("-natpmp", strprintf("Use NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
576-
#else
577-
hidden_args.emplace_back("-natpmp");
578-
#endif // USE_NATPMP
574+
argsman.AddArg("-natpmp", strprintf("Use PCP or NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
579575
argsman.AddArg("-whitebind=<[permissions@]addr>", "Bind to the given address and add permission flags to the peers connecting to it. "
580576
"Use [host]:port notation for IPv6. Allowed permissions: " + Join(NET_PERMISSIONS_DOC, ", ") + ". "
581577
"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)
18581854
LogPrintf("nBestHeight = %d\n", chain_active_height);
18591855
if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time});
18601856

1861-
// Map ports with UPnP or NAT-PMP.
1857+
// Map ports with UPnP or NAT-PMP
18621858
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP));
18631859

18641860
CConnman::Options connOptions;

src/interfaces/node.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class Node
121121
virtual void resetSettings() = 0;
122122

123123
//! Map port.
124-
virtual void mapPort(bool use_upnp, bool use_natpmp) = 0;
124+
virtual void mapPort(bool use_upnp, bool use_pcp) = 0;
125125

126126
//! Get proxy.
127127
virtual bool getProxy(Network net, Proxy& proxy_info) = 0;

src/mapport.cpp

Lines changed: 90 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@
1212
#include <net.h>
1313
#include <netaddress.h>
1414
#include <netbase.h>
15+
#include <random.h>
16+
#include <util/netif.h>
17+
#include <util/pcp.h>
1518
#include <util/thread.h>
1619
#include <util/threadinterrupt.h>
1720

18-
#ifdef USE_NATPMP
19-
#include <compat/compat.h>
20-
#include <natpmp.h>
21-
#endif // USE_NATPMP
22-
2321
#ifdef USE_UPNP
2422
#include <miniupnpc/miniupnpc.h>
2523
#include <miniupnpc/upnpcommands.h>
@@ -36,7 +34,6 @@ static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed"
3634
#include <string>
3735
#include <thread>
3836

39-
#if defined(USE_NATPMP) || defined(USE_UPNP)
4037
static CThreadInterrupt g_mapport_interrupt;
4138
static std::thread g_mapport_thread;
4239
static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE};
@@ -46,104 +43,96 @@ using namespace std::chrono_literals;
4643
static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
4744
static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
4845

49-
#ifdef USE_NATPMP
50-
static uint16_t g_mapport_external_port = 0;
51-
static bool NatpmpInit(natpmp_t* natpmp)
46+
static bool ProcessPCP()
5247
{
53-
const int r_init = initnatpmp(natpmp, /* detect gateway automatically */ 0, /* forced gateway - NOT APPLIED*/ 0);
54-
if (r_init == 0) return true;
55-
LogPrintf("natpmp: initnatpmp() failed with %d error.\n", r_init);
56-
return false;
57-
}
48+
// The same nonce is used for all mappings, this is allowed by the spec, and simplifies keeping track of them.
49+
PCPMappingNonce pcp_nonce;
50+
GetRandBytes(pcp_nonce);
5851

59-
static bool NatpmpDiscover(natpmp_t* natpmp, struct in_addr& external_ipv4_addr)
60-
{
61-
const int r_send = sendpublicaddressrequest(natpmp);
62-
if (r_send == 2 /* OK */) {
63-
int r_read;
64-
natpmpresp_t response;
65-
do {
66-
r_read = readnatpmpresponseorretry(natpmp, &response);
67-
} while (r_read == NATPMP_TRYAGAIN);
68-
69-
if (r_read == 0) {
70-
external_ipv4_addr = response.pnu.publicaddress.addr;
71-
return true;
72-
} else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) {
73-
LogPrintf("natpmp: The gateway does not support NAT-PMP.\n");
52+
bool ret = false;
53+
bool no_resources = false;
54+
const uint16_t private_port = GetListenPort();
55+
// Multiply the reannounce period by two, as we'll try to renew approximately halfway.
56+
const uint32_t requested_lifetime = std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count();
57+
uint32_t actual_lifetime = 0;
58+
std::chrono::milliseconds sleep_time;
59+
60+
// Local functor to handle result from PCP/NATPMP mapping.
61+
auto handle_mapping = [&](std::variant<MappingResult, MappingError> &res) -> void {
62+
if (MappingResult* mapping = std::get_if<MappingResult>(&res)) {
63+
LogPrintLevel(BCLog::NET, BCLog::Level::Info, "portmap: Added mapping %s\n", mapping->ToString());
64+
AddLocal(mapping->external, LOCAL_MAPPED);
65+
ret = true;
66+
actual_lifetime = std::min(actual_lifetime, mapping->lifetime);
67+
} else if (MappingError *err = std::get_if<MappingError>(&res)) {
68+
// Detailed error will already have been logged internally in respective Portmap function.
69+
if (*err == MappingError::NO_RESOURCES) {
70+
no_resources = true;
71+
}
72+
}
73+
};
74+
75+
do {
76+
actual_lifetime = requested_lifetime;
77+
no_resources = false; // Set to true if there was any "no resources" error.
78+
ret = false; // Set to true if any mapping succeeds.
79+
80+
// IPv4
81+
std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4);
82+
if (!gateway4) {
83+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv4 default gateway\n");
7484
} else {
75-
LogPrintf("natpmp: readnatpmpresponseorretry() for public address failed with %d error.\n", r_read);
85+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv4]: %s\n", gateway4->ToStringAddr());
86+
87+
// Open a port mapping on whatever local address we have toward the gateway.
88+
struct in_addr inaddr_any;
89+
inaddr_any.s_addr = htonl(INADDR_ANY);
90+
auto res = PCPRequestPortMap(pcp_nonce, *gateway4, CNetAddr(inaddr_any), private_port, requested_lifetime);
91+
MappingError* pcp_err = std::get_if<MappingError>(&res);
92+
if (pcp_err && *pcp_err == MappingError::UNSUPP_VERSION) {
93+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Got unsupported PCP version response, falling back to NAT-PMP\n");
94+
res = NATPMPRequestPortMap(*gateway4, private_port, requested_lifetime);
95+
}
96+
handle_mapping(res);
7697
}
77-
} else {
78-
LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", r_send);
79-
}
8098

81-
return false;
82-
}
99+
// IPv6
100+
std::optional<CNetAddr> gateway6 = QueryDefaultGateway(NET_IPV6);
101+
if (!gateway6) {
102+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv6 default gateway\n");
103+
} else {
104+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv6]: %s\n", gateway6->ToStringAddr());
83105

84-
static bool NatpmpMapping(natpmp_t* natpmp, const struct in_addr& external_ipv4_addr, uint16_t private_port, bool& external_ip_discovered)
85-
{
86-
const uint16_t suggested_external_port = g_mapport_external_port ? g_mapport_external_port : private_port;
87-
const int r_send = sendnewportmappingrequest(natpmp, NATPMP_PROTOCOL_TCP, private_port, suggested_external_port, 3600 /*seconds*/);
88-
if (r_send == 12 /* OK */) {
89-
int r_read;
90-
natpmpresp_t response;
91-
do {
92-
r_read = readnatpmpresponseorretry(natpmp, &response);
93-
} while (r_read == NATPMP_TRYAGAIN);
94-
95-
if (r_read == 0) {
96-
auto pm = response.pnu.newportmapping;
97-
if (private_port == pm.privateport && pm.lifetime > 0) {
98-
g_mapport_external_port = pm.mappedpublicport;
99-
const CService external{external_ipv4_addr, pm.mappedpublicport};
100-
if (!external_ip_discovered && fDiscover) {
101-
AddLocal(external, LOCAL_MAPPED);
102-
external_ip_discovered = true;
103-
}
104-
LogPrintf("natpmp: Port mapping successful. External address = %s\n", external.ToStringAddrPort());
105-
return true;
106-
} else {
107-
LogPrintf("natpmp: Port mapping failed.\n");
106+
// Try to open pinholes for all routable local IPv6 addresses.
107+
for (const auto &addr: GetLocalAddresses()) {
108+
if (!addr.IsRoutable() || !addr.IsIPv6()) continue;
109+
auto res = PCPRequestPortMap(pcp_nonce, *gateway6, addr, private_port, requested_lifetime);
110+
handle_mapping(res);
108111
}
109-
} else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) {
110-
LogPrintf("natpmp: The gateway does not support NAT-PMP.\n");
111-
} else {
112-
LogPrintf("natpmp: readnatpmpresponseorretry() for port mapping failed with %d error.\n", r_read);
113112
}
114-
} else {
115-
LogPrintf("natpmp: sendnewportmappingrequest() failed with %d error.\n", r_send);
116-
}
117-
118-
return false;
119-
}
120113

121-
static bool ProcessNatpmp()
122-
{
123-
bool ret = false;
124-
natpmp_t natpmp;
125-
struct in_addr external_ipv4_addr;
126-
if (NatpmpInit(&natpmp) && NatpmpDiscover(&natpmp, external_ipv4_addr)) {
127-
bool external_ip_discovered = false;
128-
const uint16_t private_port = GetListenPort();
129-
do {
130-
ret = NatpmpMapping(&natpmp, external_ipv4_addr, private_port, external_ip_discovered);
131-
} while (ret && g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD));
132-
g_mapport_interrupt.reset();
114+
// Log message if we got NO_RESOURCES.
115+
if (no_resources) {
116+
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");
117+
}
133118

134-
const int r_send = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, private_port, g_mapport_external_port, /* remove a port mapping */ 0);
135-
g_mapport_external_port = 0;
136-
if (r_send == 12 /* OK */) {
137-
LogPrintf("natpmp: Port mapping removed successfully.\n");
138-
} else {
139-
LogPrintf("natpmp: sendnewportmappingrequest(0) failed with %d error.\n", r_send);
119+
// Sanity-check returned lifetime.
120+
if (actual_lifetime < 30) {
121+
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: Got impossibly short mapping lifetime of %d seconds\n", actual_lifetime);
122+
return false;
140123
}
141-
}
124+
// RFC6887 11.2.1 recommends that clients send their first renewal packet at a time chosen with uniform random
125+
// distribution in the range 1/2 to 5/8 of expiration time.
126+
std::chrono::seconds sleep_time_min(actual_lifetime / 2);
127+
std::chrono::seconds sleep_time_max(actual_lifetime * 5 / 8);
128+
sleep_time = sleep_time_min + FastRandomContext().randrange<std::chrono::milliseconds>(sleep_time_max - sleep_time_min);
129+
} while (ret && g_mapport_interrupt.sleep_for(sleep_time));
130+
131+
// We don't delete the mappings when the thread is interrupted because this would add additional complexity, so
132+
// we rather just choose a fairly short expiry time.
142133

143-
closenatpmp(&natpmp);
144134
return ret;
145135
}
146-
#endif // USE_NATPMP
147136

148137
#ifdef USE_UPNP
149138
static bool ProcessUpnp()
@@ -223,23 +212,21 @@ static void ThreadMapPort()
223212
do {
224213
ok = false;
225214

226-
#ifdef USE_UPNP
227215
// High priority protocol.
228-
if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
229-
g_mapport_current_proto = MapPortProtoFlag::UPNP;
230-
ok = ProcessUpnp();
216+
if (g_mapport_enabled_protos & MapPortProtoFlag::PCP) {
217+
g_mapport_current_proto = MapPortProtoFlag::PCP;
218+
ok = ProcessPCP();
231219
if (ok) continue;
232220
}
233-
#endif // USE_UPNP
234221

235-
#ifdef USE_NATPMP
222+
#ifdef USE_UPNP
236223
// Low priority protocol.
237-
if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) {
238-
g_mapport_current_proto = MapPortProtoFlag::NAT_PMP;
239-
ok = ProcessNatpmp();
224+
if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
225+
g_mapport_current_proto = MapPortProtoFlag::UPNP;
226+
ok = ProcessUpnp();
240227
if (ok) continue;
241228
}
242-
#endif // USE_NATPMP
229+
#endif // USE_UPNP
243230

244231
g_mapport_current_proto = MapPortProtoFlag::NONE;
245232
if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
@@ -281,7 +268,7 @@ static void DispatchMapPort()
281268

282269
assert(g_mapport_thread.joinable());
283270
assert(!g_mapport_interrupt);
284-
// Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp()
271+
// Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadPCP()
285272
// to force trying the next protocol in the ThreadMapPort() loop.
286273
g_mapport_interrupt();
287274
}
@@ -295,10 +282,10 @@ static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
295282
}
296283
}
297284

298-
void StartMapPort(bool use_upnp, bool use_natpmp)
285+
void StartMapPort(bool use_upnp, bool use_pcp)
299286
{
300287
MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
301-
MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp);
288+
MapPortProtoSetEnabled(MapPortProtoFlag::PCP, use_pcp);
302289
DispatchMapPort();
303290
}
304291

@@ -317,18 +304,3 @@ void StopMapPort()
317304
g_mapport_interrupt.reset();
318305
}
319306
}
320-
321-
#else // #if defined(USE_NATPMP) || defined(USE_UPNP)
322-
void StartMapPort(bool use_upnp, bool use_natpmp)
323-
{
324-
// Intentionally left blank.
325-
}
326-
void InterruptMapPort()
327-
{
328-
// Intentionally left blank.
329-
}
330-
void StopMapPort()
331-
{
332-
// Intentionally left blank.
333-
}
334-
#endif // #if defined(USE_NATPMP) || defined(USE_UPNP)

src/mapport.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ static constexpr bool DEFAULT_NATPMP = false;
1212
enum MapPortProtoFlag : unsigned int {
1313
NONE = 0x00,
1414
UPNP = 0x01,
15-
NAT_PMP = 0x02,
15+
PCP = 0x02, // PCP with NAT-PMP fallback.
1616
};
1717

18-
void StartMapPort(bool use_upnp, bool use_natpmp);
18+
void StartMapPort(bool use_upnp, bool use_pcp);
1919
void InterruptMapPort();
2020
void StopMapPort();
2121

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ enum
148148
LOCAL_NONE, // unknown
149149
LOCAL_IF, // address a local interface listens on
150150
LOCAL_BIND, // address explicit bound to
151-
LOCAL_MAPPED, // address reported by UPnP or NAT-PMP
151+
LOCAL_MAPPED, // address reported by UPnP or PCP
152152
LOCAL_MANUAL, // address explicitly specified (-externalip=)
153153

154154
LOCAL_MAX

src/node/interfaces.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class NodeImpl : public Node
181181
});
182182
args().WriteSettingsFile();
183183
}
184-
void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); }
184+
void mapPort(bool use_upnp, bool use_pcp) override { StartMapPort(use_upnp, use_pcp); }
185185
bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); }
186186
size_t getNodeCount(ConnectionDirection flags) override
187187
{

0 commit comments

Comments
 (0)