mapport.cpp raw
1 // Copyright (c) 2011-2022 The Limenka 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 <limenka-build-config.h> // IWYU pragma: keep
6
7 #include <mapport.h>
8
9 #include <clientversion.h>
10 #include <common/netif.h>
11 #include <common/pcp.h>
12 #include <common/system.h>
13 #include <logging.h>
14 #include <net.h>
15 #include <netaddress.h>
16 #include <netbase.h>
17 #include <random.h>
18 #include <util/thread.h>
19 #include <util/threadinterrupt.h>
20
21 #ifdef USE_UPNP
22 #include <cstddef> // workaround missing include in miniupnpc 2.3.3
23 #include <miniupnpc/miniupnpc.h>
24 #include <miniupnpc/upnpcommands.h>
25 #include <miniupnpc/upnperrors.h>
26 // The minimum supported miniUPnPc API version is set to 17. This excludes
27 // versions with known vulnerabilities.
28 static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed");
29 #endif // USE_UPNP
30
31 #include <atomic>
32 #include <cassert>
33 #include <chrono>
34 #include <functional>
35 #include <string>
36 #include <thread>
37
38 static CThreadInterrupt g_mapport_interrupt;
39 static std::thread g_mapport_thread;
40 static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE};
41 static std::atomic<MapPortProtoFlag> g_mapport_current_proto{MapPortProtoFlag::NONE};
42
43 using namespace std::chrono_literals;
44 static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
45 static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
46
47 static bool ProcessPCP()
48 {
49 // The same nonce is used for all mappings, this is allowed by the spec, and simplifies keeping track of them.
50 PCPMappingNonce pcp_nonce;
51 GetRandBytes(pcp_nonce);
52
53 bool ret = false;
54 bool no_resources = false;
55 const uint16_t private_port = GetListenPort();
56 // Multiply the reannounce period by two, as we'll try to renew approximately halfway.
57 const uint32_t requested_lifetime = std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count();
58 uint32_t actual_lifetime = 0;
59 std::chrono::milliseconds sleep_time;
60
61 // Local functor to handle result from PCP/NATPMP mapping.
62 auto handle_mapping = [&](std::variant<MappingResult, MappingError> &res) -> void {
63 if (MappingResult* mapping = std::get_if<MappingResult>(&res)) {
64 LogPrintLevel(BCLog::NET, BCLog::Level::Info, "portmap: Added mapping %s\n", mapping->ToString());
65 AddLocal(mapping->external, LOCAL_MAPPED);
66 ret = true;
67 actual_lifetime = std::min(actual_lifetime, mapping->lifetime);
68 } else if (MappingError *err = std::get_if<MappingError>(&res)) {
69 // Detailed error will already have been logged internally in respective Portmap function.
70 if (*err == MappingError::NO_RESOURCES) {
71 no_resources = true;
72 }
73 }
74 };
75
76 do {
77 actual_lifetime = requested_lifetime;
78 no_resources = false; // Set to true if there was any "no resources" error.
79 ret = false; // Set to true if any mapping succeeds.
80
81 // IPv4
82 std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4);
83 if (!gateway4) {
84 LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv4 default gateway\n");
85 } else {
86 LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv4]: %s\n", gateway4->ToStringAddr());
87
88 // Open a port mapping on whatever local address we have toward the gateway.
89 struct in_addr inaddr_any;
90 inaddr_any.s_addr = htonl(INADDR_ANY);
91 auto res = PCPRequestPortMap(pcp_nonce, *gateway4, CNetAddr(inaddr_any), private_port, requested_lifetime, g_mapport_interrupt);
92 MappingError* pcp_err = std::get_if<MappingError>(&res);
93 if (pcp_err && *pcp_err == MappingError::UNSUPP_VERSION) {
94 LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Got unsupported PCP version response, falling back to NAT-PMP\n");
95 res = NATPMPRequestPortMap(*gateway4, private_port, requested_lifetime, g_mapport_interrupt);
96 }
97 handle_mapping(res);
98 }
99
100 // IPv6
101 std::optional<CNetAddr> gateway6 = QueryDefaultGateway(NET_IPV6);
102 if (!gateway6) {
103 LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv6 default gateway\n");
104 } else {
105 LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv6]: %s\n", gateway6->ToStringAddr());
106
107 // Try to open pinholes for all routable local IPv6 addresses.
108 for (const auto &addr: GetLocalAddresses()) {
109 if (!addr.IsRoutable() || !addr.IsIPv6()) continue;
110 auto res = PCPRequestPortMap(pcp_nonce, *gateway6, addr, private_port, requested_lifetime, g_mapport_interrupt);
111 handle_mapping(res);
112 }
113 }
114
115 // Log message if we got NO_RESOURCES.
116 if (no_resources) {
117 LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: At least one mapping failed because of a NO_RESOURCES error. This usually indicates that the port is already used on the router. If this is the only instance of limenka running on the network, this will resolve itself automatically. Otherwise, you might want to choose a different P2P port to prevent this conflict.\n");
118 }
119
120 // Sanity-check returned lifetime.
121 if (actual_lifetime < 30) {
122 LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: Got impossibly short mapping lifetime of %d seconds\n", actual_lifetime);
123 return false;
124 }
125 // RFC6887 11.2.1 recommends that clients send their first renewal packet at a time chosen with uniform random
126 // distribution in the range 1/2 to 5/8 of expiration time.
127 std::chrono::seconds sleep_time_min(actual_lifetime / 2);
128 std::chrono::seconds sleep_time_max(actual_lifetime * 5 / 8);
129 sleep_time = sleep_time_min + FastRandomContext().randrange<std::chrono::milliseconds>(sleep_time_max - sleep_time_min);
130 } while (ret && g_mapport_interrupt.sleep_for(sleep_time));
131
132 // We don't delete the mappings when the thread is interrupted because this would add additional complexity, so
133 // we rather just choose a fairly short expiry time.
134
135 return ret;
136 }
137
138 #ifdef USE_UPNP
139 static bool ProcessUpnp()
140 {
141 bool ret = false;
142 std::string port = strprintf("%u", GetListenPort());
143 const char * multicastif = nullptr;
144 const char * minissdpdpath = nullptr;
145 struct UPNPDev * devlist = nullptr;
146 char lanaddr[64];
147
148 int error = 0;
149 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
150
151 struct UPNPUrls urls;
152 struct IGDdatas data;
153 int r;
154 #if MINIUPNPC_API_VERSION <= 17
155 r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
156 #else
157 r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr), nullptr, 0);
158 #endif
159 if (r == 1)
160 {
161 if (fDiscover) {
162 char externalIPAddress[40];
163 r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
164 if (r != UPNPCOMMAND_SUCCESS) {
165 LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r);
166 } else {
167 if (externalIPAddress[0]) {
168 std::optional<CNetAddr> resolved{LookupHost(externalIPAddress, false)};
169 if (resolved.has_value()) {
170 LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved->ToStringAddr());
171 AddLocal(resolved.value(), LOCAL_MAPPED);
172 }
173 } else {
174 LogPrintf("UPnP: GetExternalIPAddress failed.\n");
175 }
176 }
177 }
178
179 std::string strDesc = CLIENT_NAME " " + FormatFullVersion();
180
181 do {
182 r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", nullptr, "0");
183
184 if (r != UPNPCOMMAND_SUCCESS) {
185 ret = false;
186 LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r));
187 break;
188 } else {
189 ret = true;
190 LogPrintf("UPnP Port Mapping successful.\n");
191 }
192 } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD));
193 g_mapport_interrupt.reset();
194
195 r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", nullptr);
196 LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
197 freeUPNPDevlist(devlist); devlist = nullptr;
198 FreeUPNPUrls(&urls);
199 } else {
200 LogPrintf("No valid UPnP IGDs found\n");
201 freeUPNPDevlist(devlist); devlist = nullptr;
202 if (r != 0)
203 FreeUPNPUrls(&urls);
204 }
205
206 return ret;
207 }
208 #endif // USE_UPNP
209
210 static void ThreadMapPort()
211 {
212 bool ok;
213 do {
214 ok = false;
215
216 // High priority protocol.
217 if (g_mapport_enabled_protos & MapPortProtoFlag::PCP) {
218 g_mapport_current_proto = MapPortProtoFlag::PCP;
219 ok = ProcessPCP();
220 if (ok) continue;
221 }
222
223 #ifdef USE_UPNP
224 // Low priority protocol.
225 if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
226 g_mapport_current_proto = MapPortProtoFlag::UPNP;
227 ok = ProcessUpnp();
228 if (ok) continue;
229 }
230 #endif // USE_UPNP
231
232 g_mapport_current_proto = MapPortProtoFlag::NONE;
233 if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
234 return;
235 }
236
237 } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD));
238 }
239
240 void StartThreadMapPort()
241 {
242 if (!g_mapport_thread.joinable()) {
243 assert(!g_mapport_interrupt);
244 g_mapport_thread = std::thread(&util::TraceThread, "mapport", &ThreadMapPort);
245 }
246 }
247
248 static void DispatchMapPort()
249 {
250 if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
251 return;
252 }
253
254 if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) {
255 StartThreadMapPort();
256 return;
257 }
258
259 if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
260 InterruptMapPort();
261 StopMapPort();
262 return;
263 }
264
265 if (g_mapport_enabled_protos & g_mapport_current_proto) {
266 // Enabling another protocol does not cause switching from the currently used one.
267 return;
268 }
269
270 assert(g_mapport_thread.joinable());
271 assert(!g_mapport_interrupt);
272 // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadPCP()
273 // to force trying the next protocol in the ThreadMapPort() loop.
274 g_mapport_interrupt();
275 }
276
277 static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
278 {
279 if (enabled) {
280 g_mapport_enabled_protos |= proto;
281 } else {
282 g_mapport_enabled_protos &= ~proto;
283 }
284 }
285
286 bool MapPortIsProtoEnabled(const MapPortProtoFlag proto)
287 {
288 return g_mapport_enabled_protos & proto;
289 }
290
291 void StartMapPort(bool use_upnp, bool use_pcp)
292 {
293 MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
294 MapPortProtoSetEnabled(MapPortProtoFlag::PCP, use_pcp);
295 DispatchMapPort();
296 }
297
298 void InterruptMapPort()
299 {
300 g_mapport_enabled_protos = MapPortProtoFlag::NONE;
301 if (g_mapport_thread.joinable()) {
302 g_mapport_interrupt();
303 }
304 }
305
306 void StopMapPort()
307 {
308 if (g_mapport_thread.joinable()) {
309 g_mapport_thread.join();
310 g_mapport_interrupt.reset();
311 }
312 }
313