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