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