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