Bitcoin Core  27.99.0
P2P Digital Currency
mapport.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2022 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 #include <config/bitcoin-config.h> // IWYU pragma: keep
6 
7 #include <mapport.h>
8 
9 #include <clientversion.h>
10 #include <common/system.h>
11 #include <logging.h>
12 #include <net.h>
13 #include <netaddress.h>
14 #include <netbase.h>
15 #include <util/thread.h>
16 #include <util/threadinterrupt.h>
17 
18 #ifdef USE_NATPMP
19 #include <compat/compat.h>
20 #include <natpmp.h>
21 #endif // USE_NATPMP
22 
23 #ifdef USE_UPNP
24 #include <miniupnpc/miniupnpc.h>
25 #include <miniupnpc/upnpcommands.h>
26 #include <miniupnpc/upnperrors.h>
27 // The minimum supported miniUPnPc API version is set to 17. This excludes
28 // versions with known vulnerabilities.
29 static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed");
30 #endif // USE_UPNP
31 
32 #include <atomic>
33 #include <cassert>
34 #include <chrono>
35 #include <functional>
36 #include <string>
37 #include <thread>
38 
39 #if defined(USE_NATPMP) || defined(USE_UPNP)
40 static CThreadInterrupt g_mapport_interrupt;
41 static std::thread g_mapport_thread;
42 static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE};
43 static std::atomic<MapPortProtoFlag> g_mapport_current_proto{MapPortProtoFlag::NONE};
44 
45 using namespace std::chrono_literals;
46 static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
47 static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
48 
49 #ifdef USE_NATPMP
50 static uint16_t g_mapport_external_port = 0;
51 static bool NatpmpInit(natpmp_t* natpmp)
52 {
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 }
58 
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");
74  } else {
75  LogPrintf("natpmp: readnatpmpresponseorretry() for public address failed with %d error.\n", r_read);
76  }
77  } else {
78  LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", r_send);
79  }
80 
81  return false;
82 }
83 
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");
108  }
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);
113  }
114  } else {
115  LogPrintf("natpmp: sendnewportmappingrequest() failed with %d error.\n", r_send);
116  }
117 
118  return false;
119 }
120 
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();
133 
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);
140  }
141  }
142 
143  closenatpmp(&natpmp);
144  return ret;
145 }
146 #endif // USE_NATPMP
147 
148 #ifdef USE_UPNP
149 static bool ProcessUpnp()
150 {
151  bool ret = false;
152  std::string port = strprintf("%u", GetListenPort());
153  const char * multicastif = nullptr;
154  const char * minissdpdpath = nullptr;
155  struct UPNPDev * devlist = nullptr;
156  char lanaddr[64];
157 
158  int error = 0;
159  devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
160 
161  struct UPNPUrls urls;
162  struct IGDdatas data;
163  int r;
164 #if MINIUPNPC_API_VERSION <= 17
165  r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
166 #else
167  r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr), nullptr, 0);
168 #endif
169  if (r == 1)
170  {
171  if (fDiscover) {
172  char externalIPAddress[40];
173  r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
174  if (r != UPNPCOMMAND_SUCCESS) {
175  LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r);
176  } else {
177  if (externalIPAddress[0]) {
178  std::optional<CNetAddr> resolved{LookupHost(externalIPAddress, false)};
179  if (resolved.has_value()) {
180  LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved->ToStringAddr());
181  AddLocal(resolved.value(), LOCAL_MAPPED);
182  }
183  } else {
184  LogPrintf("UPnP: GetExternalIPAddress failed.\n");
185  }
186  }
187  }
188 
189  std::string strDesc = PACKAGE_NAME " " + FormatFullVersion();
190 
191  do {
192  r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", nullptr, "0");
193 
194  if (r != UPNPCOMMAND_SUCCESS) {
195  ret = false;
196  LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r));
197  break;
198  } else {
199  ret = true;
200  LogPrintf("UPnP Port Mapping successful.\n");
201  }
202  } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD));
203  g_mapport_interrupt.reset();
204 
205  r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", nullptr);
206  LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
207  freeUPNPDevlist(devlist); devlist = nullptr;
208  FreeUPNPUrls(&urls);
209  } else {
210  LogPrintf("No valid UPnP IGDs found\n");
211  freeUPNPDevlist(devlist); devlist = nullptr;
212  if (r != 0)
213  FreeUPNPUrls(&urls);
214  }
215 
216  return ret;
217 }
218 #endif // USE_UPNP
219 
220 static void ThreadMapPort()
221 {
222  bool ok;
223  do {
224  ok = false;
225 
226 #ifdef USE_UPNP
227  // High priority protocol.
228  if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
229  g_mapport_current_proto = MapPortProtoFlag::UPNP;
230  ok = ProcessUpnp();
231  if (ok) continue;
232  }
233 #endif // USE_UPNP
234 
235 #ifdef USE_NATPMP
236  // Low priority protocol.
237  if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) {
238  g_mapport_current_proto = MapPortProtoFlag::NAT_PMP;
239  ok = ProcessNatpmp();
240  if (ok) continue;
241  }
242 #endif // USE_NATPMP
243 
244  g_mapport_current_proto = MapPortProtoFlag::NONE;
245  if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
246  return;
247  }
248 
249  } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD));
250 }
251 
252 void StartThreadMapPort()
253 {
254  if (!g_mapport_thread.joinable()) {
255  assert(!g_mapport_interrupt);
256  g_mapport_thread = std::thread(&util::TraceThread, "mapport", &ThreadMapPort);
257  }
258 }
259 
260 static void DispatchMapPort()
261 {
262  if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
263  return;
264  }
265 
266  if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) {
267  StartThreadMapPort();
268  return;
269  }
270 
271  if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
273  StopMapPort();
274  return;
275  }
276 
277  if (g_mapport_enabled_protos & g_mapport_current_proto) {
278  // Enabling another protocol does not cause switching from the currently used one.
279  return;
280  }
281 
282  assert(g_mapport_thread.joinable());
283  assert(!g_mapport_interrupt);
284  // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp()
285  // to force trying the next protocol in the ThreadMapPort() loop.
286  g_mapport_interrupt();
287 }
288 
289 static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
290 {
291  if (enabled) {
292  g_mapport_enabled_protos |= proto;
293  } else {
294  g_mapport_enabled_protos &= ~proto;
295  }
296 }
297 
298 void StartMapPort(bool use_upnp, bool use_natpmp)
299 {
300  MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
301  MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp);
302  DispatchMapPort();
303 }
304 
305 void InterruptMapPort()
306 {
307  g_mapport_enabled_protos = MapPortProtoFlag::NONE;
308  if (g_mapport_thread.joinable()) {
309  g_mapport_interrupt();
310  }
311 }
312 
313 void StopMapPort()
314 {
315  if (g_mapport_thread.joinable()) {
316  g_mapport_thread.join();
317  g_mapport_interrupt.reset();
318  }
319 }
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 }
327 {
328  // Intentionally left blank.
329 }
331 {
332  // Intentionally left blank.
333 }
334 #endif // #if defined(USE_NATPMP) || defined(USE_UPNP)
int ret
#define PACKAGE_NAME
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:531
A helper class for interruptible sleeps.
bool sleep_for(Clock::duration rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut)
std::string FormatFullVersion()
#define LogPrintf(...)
Definition: logging.h:244
void StartMapPort(bool use_upnp, bool use_natpmp)
Definition: mapport.cpp:322
void StopMapPort()
Definition: mapport.cpp:330
void InterruptMapPort()
Definition: mapport.cpp:326
MapPortProtoFlag
Definition: mapport.h:12
@ UPNP
Definition: mapport.h:14
@ NAT_PMP
Definition: mapport.h:15
@ NONE
Definition: logging.h:40
void TraceThread(std::string_view thread_name, std::function< void()> thread_func)
A wrapper for do-something-once thread functions.
Definition: thread.cpp:16
uint16_t GetListenPort()
Definition: net.cpp:133
bool fDiscover
Definition: net.cpp:113
bool AddLocal(const CService &addr_, int nScore)
Definition: net.cpp:266
@ LOCAL_MAPPED
Definition: net.h:151
std::vector< CNetAddr > LookupHost(const std::string &name, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function)
Resolve a host string to its corresponding network addresses.
Definition: netbase.cpp:177
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1161
assert(!tx.IsCoinBase())