Bitcoin Core  24.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 <logging.h>
13 #include <net.h>
14 #include <netaddress.h>
15 #include <netbase.h>
16 #include <util/syscall_sandbox.h>
17 #include <util/system.h>
18 #include <util/thread.h>
19 #include <util/threadinterrupt.h>
20 
21 #ifdef USE_NATPMP
22 #include <compat/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 17. This excludes
31 // versions with known vulnerabilities.
32 static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 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)
43 static CThreadInterrupt g_mapport_interrupt;
44 static std::thread g_mapport_thread;
45 static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE};
46 static std::atomic<MapPortProtoFlag> g_mapport_current_proto{MapPortProtoFlag::NONE};
47 
48 using namespace std::chrono_literals;
49 static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
50 static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
51 
52 #ifdef USE_NATPMP
53 static uint16_t g_mapport_external_port = 0;
54 static 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 
62 static 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 
87 static 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.ToStringAddrPort());
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 
124 static 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
152 static 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  devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
163 
164  struct UPNPUrls urls;
165  struct IGDdatas data;
166  int r;
167 
168  r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
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  CNetAddr resolved;
179  if (LookupHost(externalIPAddress, resolved, false)) {
180  LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToStringAddr());
181  AddLocal(resolved, 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 {
223  bool ok;
224  do {
225  ok = false;
226 
227 #ifdef USE_UPNP
228  // High priority protocol.
229  if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
230  g_mapport_current_proto = MapPortProtoFlag::UPNP;
231  ok = ProcessUpnp();
232  if (ok) continue;
233  }
234 #endif // USE_UPNP
235 
236 #ifdef USE_NATPMP
237  // Low priority protocol.
238  if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) {
239  g_mapport_current_proto = MapPortProtoFlag::NAT_PMP;
240  ok = ProcessNatpmp();
241  if (ok) continue;
242  }
243 #endif // USE_NATPMP
244 
245  g_mapport_current_proto = MapPortProtoFlag::NONE;
246  if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
247  return;
248  }
249 
250  } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD));
251 }
252 
253 void StartThreadMapPort()
254 {
255  if (!g_mapport_thread.joinable()) {
256  assert(!g_mapport_interrupt);
257  g_mapport_thread = std::thread(&util::TraceThread, "mapport", &ThreadMapPort);
258  }
259 }
260 
261 static void DispatchMapPort()
262 {
263  if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
264  return;
265  }
266 
267  if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) {
268  StartThreadMapPort();
269  return;
270  }
271 
272  if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
274  StopMapPort();
275  return;
276  }
277 
278  if (g_mapport_enabled_protos & g_mapport_current_proto) {
279  // Enabling another protocol does not cause switching from the currently used one.
280  return;
281  }
282 
283  assert(g_mapport_thread.joinable());
284  assert(!g_mapport_interrupt);
285  // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp()
286  // to force trying the next protocol in the ThreadMapPort() loop.
287  g_mapport_interrupt();
288 }
289 
290 static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
291 {
292  if (enabled) {
293  g_mapport_enabled_protos |= proto;
294  } else {
295  g_mapport_enabled_protos &= ~proto;
296  }
297 }
298 
299 void StartMapPort(bool use_upnp, bool use_natpmp)
300 {
301  MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
302  MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp);
303  DispatchMapPort();
304 }
305 
306 void InterruptMapPort()
307 {
308  g_mapport_enabled_protos = MapPortProtoFlag::NONE;
309  if (g_mapport_thread.joinable()) {
310  g_mapport_interrupt();
311  }
312 }
313 
314 void StopMapPort()
315 {
316  if (g_mapport_thread.joinable()) {
317  g_mapport_thread.join();
318  g_mapport_interrupt.reset();
319  }
320 }
321 
322 #else // #if defined(USE_NATPMP) || defined(USE_UPNP)
323 void StartMapPort(bool use_upnp, bool use_natpmp)
324 {
325  // Intentionally left blank.
326 }
328 {
329  // Intentionally left blank.
330 }
332 {
333  // Intentionally left blank.
334 }
335 #endif // #if defined(USE_NATPMP) || defined(USE_UPNP)
int ret
#define PACKAGE_NAME
Network address.
Definition: netaddress.h:120
std::string ToStringAddr() const
Definition: netaddress.cpp:602
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:520
bool sleep_for(Clock::duration rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut)
std::string FormatFullVersion()
bool error(const char *fmt, const Args &... args)
Definition: logging.h:261
#define LogPrintf(...)
Definition: logging.h:236
void StartMapPort(bool use_upnp, bool use_natpmp)
Definition: mapport.cpp:323
void StopMapPort()
Definition: mapport.cpp:331
void InterruptMapPort()
Definition: mapport.cpp:327
MapPortProtoFlag
Definition: mapport.h:12
@ UPNP
Definition: mapport.h:14
@ NAT_PMP
Definition: mapport.h:15
@ NONE
Definition: logging.h:39
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:129
bool fDiscover
Definition: net.cpp:116
bool AddLocal(const CService &addr_, int nScore)
Definition: net.cpp:286
@ LOCAL_MAPPED
Definition: net.h:142
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...
#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())