Bitcoin Core 22.99.0
P2P Digital Currency
mapport.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2020 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#if defined(HAVE_CONFIG_H)
7#endif
8
9#include <mapport.h>
10
11#include <clientversion.h>
12#include <logging.h>
13#include <net.h>
14#include <netaddress.h>
15#include <netbase.h>
16#include <threadinterrupt.h>
18#include <util/system.h>
19#include <util/thread.h>
20
21#ifdef USE_NATPMP
22#include <compat.h>
23#include <natpmp.h>
24#endif // USE_NATPMP
25
26#ifdef USE_UPNP
27#include <miniupnpc/miniupnpc.h>
28#include <miniupnpc/upnpcommands.h>
29#include <miniupnpc/upnperrors.h>
30// The minimum supported miniUPnPc API version is set to 10. This keeps compatibility
31// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages.
32static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed");
33#endif // USE_UPNP
34
35#include <atomic>
36#include <cassert>
37#include <chrono>
38#include <functional>
39#include <string>
40#include <thread>
41
42#if defined(USE_NATPMP) || defined(USE_UPNP)
43static CThreadInterrupt g_mapport_interrupt;
44static std::thread g_mapport_thread;
45static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE};
46static std::atomic<MapPortProtoFlag> g_mapport_current_proto{MapPortProtoFlag::NONE};
47
48using namespace std::chrono_literals;
49static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
50static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
51
52#ifdef USE_NATPMP
53static uint16_t g_mapport_external_port = 0;
54static bool NatpmpInit(natpmp_t* natpmp)
55{
56 const int r_init = initnatpmp(natpmp, /* detect gateway automatically */ 0, /* forced gateway - NOT APPLIED*/ 0);
57 if (r_init == 0) return true;
58 LogPrintf("natpmp: initnatpmp() failed with %d error.\n", r_init);
59 return false;
60}
61
62static bool NatpmpDiscover(natpmp_t* natpmp, struct in_addr& external_ipv4_addr)
63{
64 const int r_send = sendpublicaddressrequest(natpmp);
65 if (r_send == 2 /* OK */) {
66 int r_read;
67 natpmpresp_t response;
68 do {
69 r_read = readnatpmpresponseorretry(natpmp, &response);
70 } while (r_read == NATPMP_TRYAGAIN);
71
72 if (r_read == 0) {
73 external_ipv4_addr = response.pnu.publicaddress.addr;
74 return true;
75 } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) {
76 LogPrintf("natpmp: The gateway does not support NAT-PMP.\n");
77 } else {
78 LogPrintf("natpmp: readnatpmpresponseorretry() for public address failed with %d error.\n", r_read);
79 }
80 } else {
81 LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", r_send);
82 }
83
84 return false;
85}
86
87static bool NatpmpMapping(natpmp_t* natpmp, const struct in_addr& external_ipv4_addr, uint16_t private_port, bool& external_ip_discovered)
88{
89 const uint16_t suggested_external_port = g_mapport_external_port ? g_mapport_external_port : private_port;
90 const int r_send = sendnewportmappingrequest(natpmp, NATPMP_PROTOCOL_TCP, private_port, suggested_external_port, 3600 /*seconds*/);
91 if (r_send == 12 /* OK */) {
92 int r_read;
93 natpmpresp_t response;
94 do {
95 r_read = readnatpmpresponseorretry(natpmp, &response);
96 } while (r_read == NATPMP_TRYAGAIN);
97
98 if (r_read == 0) {
99 auto pm = response.pnu.newportmapping;
100 if (private_port == pm.privateport && pm.lifetime > 0) {
101 g_mapport_external_port = pm.mappedpublicport;
102 const CService external{external_ipv4_addr, pm.mappedpublicport};
103 if (!external_ip_discovered && fDiscover) {
104 AddLocal(external, LOCAL_MAPPED);
105 external_ip_discovered = true;
106 }
107 LogPrintf("natpmp: Port mapping successful. External address = %s\n", external.ToString());
108 return true;
109 } else {
110 LogPrintf("natpmp: Port mapping failed.\n");
111 }
112 } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) {
113 LogPrintf("natpmp: The gateway does not support NAT-PMP.\n");
114 } else {
115 LogPrintf("natpmp: readnatpmpresponseorretry() for port mapping failed with %d error.\n", r_read);
116 }
117 } else {
118 LogPrintf("natpmp: sendnewportmappingrequest() failed with %d error.\n", r_send);
119 }
120
121 return false;
122}
123
124static bool ProcessNatpmp()
125{
126 bool ret = false;
127 natpmp_t natpmp;
128 struct in_addr external_ipv4_addr;
129 if (NatpmpInit(&natpmp) && NatpmpDiscover(&natpmp, external_ipv4_addr)) {
130 bool external_ip_discovered = false;
131 const uint16_t private_port = GetListenPort();
132 do {
133 ret = NatpmpMapping(&natpmp, external_ipv4_addr, private_port, external_ip_discovered);
134 } while (ret && g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD));
135 g_mapport_interrupt.reset();
136
137 const int r_send = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, private_port, g_mapport_external_port, /* remove a port mapping */ 0);
138 g_mapport_external_port = 0;
139 if (r_send == 12 /* OK */) {
140 LogPrintf("natpmp: Port mapping removed successfully.\n");
141 } else {
142 LogPrintf("natpmp: sendnewportmappingrequest(0) failed with %d error.\n", r_send);
143 }
144 }
145
146 closenatpmp(&natpmp);
147 return ret;
148}
149#endif // USE_NATPMP
150
151#ifdef USE_UPNP
152static bool ProcessUpnp()
153{
154 bool ret = false;
155 std::string port = strprintf("%u", GetListenPort());
156 const char * multicastif = nullptr;
157 const char * minissdpdpath = nullptr;
158 struct UPNPDev * devlist = nullptr;
159 char lanaddr[64];
160
161 int error = 0;
162#if MINIUPNPC_API_VERSION < 14
163 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error);
164#else
165 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
166#endif
167
168 struct UPNPUrls urls;
169 struct IGDdatas data;
170 int r;
171
172 r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
173 if (r == 1)
174 {
175 if (fDiscover) {
176 char externalIPAddress[40];
177 r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
178 if (r != UPNPCOMMAND_SUCCESS) {
179 LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r);
180 } else {
181 if (externalIPAddress[0]) {
182 CNetAddr resolved;
183 if (LookupHost(externalIPAddress, resolved, false)) {
184 LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString());
185 AddLocal(resolved, LOCAL_MAPPED);
186 }
187 } else {
188 LogPrintf("UPnP: GetExternalIPAddress failed.\n");
189 }
190 }
191 }
192
193 std::string strDesc = PACKAGE_NAME " " + FormatFullVersion();
194
195 do {
196 r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0");
197
198 if (r != UPNPCOMMAND_SUCCESS) {
199 ret = false;
200 LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r));
201 break;
202 } else {
203 ret = true;
204 LogPrintf("UPnP Port Mapping successful.\n");
205 }
206 } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD));
207 g_mapport_interrupt.reset();
208
209 r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0);
210 LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
211 freeUPNPDevlist(devlist); devlist = nullptr;
212 FreeUPNPUrls(&urls);
213 } else {
214 LogPrintf("No valid UPnP IGDs found\n");
215 freeUPNPDevlist(devlist); devlist = nullptr;
216 if (r != 0)
217 FreeUPNPUrls(&urls);
218 }
219
220 return ret;
221}
222#endif // USE_UPNP
223
224static void ThreadMapPort()
225{
227 bool ok;
228 do {
229 ok = false;
230
231#ifdef USE_UPNP
232 // High priority protocol.
233 if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
234 g_mapport_current_proto = MapPortProtoFlag::UPNP;
235 ok = ProcessUpnp();
236 if (ok) continue;
237 }
238#endif // USE_UPNP
239
240#ifdef USE_NATPMP
241 // Low priority protocol.
242 if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) {
243 g_mapport_current_proto = MapPortProtoFlag::NAT_PMP;
244 ok = ProcessNatpmp();
245 if (ok) continue;
246 }
247#endif // USE_NATPMP
248
249 g_mapport_current_proto = MapPortProtoFlag::NONE;
250 if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
251 return;
252 }
253
254 } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD));
255}
256
257void StartThreadMapPort()
258{
259 if (!g_mapport_thread.joinable()) {
260 assert(!g_mapport_interrupt);
261 g_mapport_thread = std::thread(&util::TraceThread, "mapport", &ThreadMapPort);
262 }
263}
264
265static void DispatchMapPort()
266{
267 if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
268 return;
269 }
270
271 if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) {
272 StartThreadMapPort();
273 return;
274 }
275
276 if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
278 StopMapPort();
279 return;
280 }
281
282 if (g_mapport_enabled_protos & g_mapport_current_proto) {
283 // Enabling another protocol does not cause switching from the currently used one.
284 return;
285 }
286
287 assert(g_mapport_thread.joinable());
288 assert(!g_mapport_interrupt);
289 // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp()
290 // to force trying the next protocol in the ThreadMapPort() loop.
291 g_mapport_interrupt();
292}
293
294static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
295{
296 if (enabled) {
297 g_mapport_enabled_protos |= proto;
298 } else {
299 g_mapport_enabled_protos &= ~proto;
300 }
301}
302
303void StartMapPort(bool use_upnp, bool use_natpmp)
304{
305 MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
306 MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp);
307 DispatchMapPort();
308}
309
310void InterruptMapPort()
311{
312 g_mapport_enabled_protos = MapPortProtoFlag::NONE;
313 if (g_mapport_thread.joinable()) {
314 g_mapport_interrupt();
315 }
316}
317
318void StopMapPort()
319{
320 if (g_mapport_thread.joinable()) {
321 g_mapport_thread.join();
322 g_mapport_interrupt.reset();
323 }
324}
325
326#else // #if defined(USE_NATPMP) || defined(USE_UPNP)
327void StartMapPort(bool use_upnp, bool use_natpmp)
328{
329 // Intentionally left blank.
330}
332{
333 // Intentionally left blank.
334}
336{
337 // Intentionally left blank.
338}
339#endif // #if defined(USE_NATPMP) || defined(USE_UPNP)
#define PACKAGE_NAME
Network address.
Definition: netaddress.h:119
std::string ToString() const
Definition: netaddress.cpp:631
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:523
bool sleep_for(std::chrono::milliseconds rel_time)
std::string FormatFullVersion()
#define LogPrintf(...)
Definition: logging.h:187
void StartMapPort(bool use_upnp, bool use_natpmp)
Definition: mapport.cpp:327
void StopMapPort()
Definition: mapport.cpp:335
void InterruptMapPort()
Definition: mapport.cpp:331
MapPortProtoFlag
Definition: mapport.h:20
@ UPNP
Definition: mapport.h:22
@ NAT_PMP
Definition: mapport.h:23
@ NONE
Definition: logging.h:37
void TraceThread(const char *thread_name, std::function< void()> thread_func)
A wrapper for do-something-once thread functions.
Definition: thread.cpp:13
uint16_t GetListenPort()
Definition: net.cpp:125
bool fDiscover
Definition: net.cpp:112
bool AddLocal(const CService &addr_, int nScore)
Definition: net.cpp:250
@ LOCAL_MAPPED
Definition: net.h:192
bool LookupHost(const std::string &name, std::vector< CNetAddr > &vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function)
Resolve a host string to its corresponding network addresses.
Definition: netbase.cpp:170
void SetSyscallSandboxPolicy(SyscallSandboxPolicy syscall_policy)
Force the current thread (and threads created from the current thread) into a restricted-service oper...
bool error(const char *fmt, const Args &... args)
Definition: system.h:49
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1164
assert(!tx.IsCoinBase())