1 /*
2 * Copyright (c)2019 ZeroTier, Inc.
3 *
4 * Use of this software is governed by the Business Source License included
5 * in the LICENSE.TXT file in the project's root directory.
6 *
7 * Change Date: 2025-01-01
8 *
9 * On the date above, in accordance with the Business Source License, use
10 * of this software will be governed by version 2.0 of the Apache License.
11 */
12 /****/
13
14 #ifdef ZT_USE_MINIUPNPC
15
16 // Uncomment to dump debug messages
17 //#define ZT_PORTMAPPER_TRACE 1
18
19 #ifdef __ANDROID__
20 #include <android/log.h>
21 #define PM_TRACE(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "PortMapper", __VA_ARGS__))
22 #else
23 #define PM_TRACE(...) fprintf(stderr, __VA_ARGS__)
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include <string>
31
32 #include "../node/Utils.hpp"
33 #include "OSUtils.hpp"
34 #include "PortMapper.hpp"
35
36 // These must be defined to get rid of dynamic export stuff in libminiupnpc and libnatpmp
37 #ifdef __WINDOWS__
38 #ifndef MINIUPNP_STATICLIB
39 #define MINIUPNP_STATICLIB
40 #endif
41 #ifndef STATICLIB
42 #define STATICLIB
43 #endif
44 #endif
45
46 #ifdef ZT_USE_SYSTEM_MINIUPNPC
47 #include <miniupnpc/miniupnpc.h>
48 #include <miniupnpc/upnpcommands.h>
49 #else
50 #ifdef __ANDROID__
51 #include "miniupnpc.h"
52 #include "upnpcommands.h"
53 #else
54 #include "../ext/miniupnpc/miniupnpc.h"
55 #include "../ext/miniupnpc/upnpcommands.h"
56 #endif
57 #endif
58
59 #ifdef ZT_USE_SYSTEM_NATPMP
60 #include <natpmp.h>
61 #else
62 #ifdef __ANDROID__
63 #include "natpmp.h"
64 #else
65 #include "../ext/libnatpmp/natpmp.h"
66 #endif
67 #endif
68
69 namespace ZeroTier {
70
71 class PortMapperImpl
72 {
73 public:
PortMapperImpl(int localUdpPortToMap,const char * un)74 PortMapperImpl(int localUdpPortToMap,const char *un) :
75 run(true),
76 localPort(localUdpPortToMap),
77 uniqueName(un)
78 {
79 }
80
~PortMapperImpl()81 ~PortMapperImpl() {}
82
threadMain()83 void threadMain()
84 throw()
85 {
86 int mode = 0; // 0 == NAT-PMP, 1 == UPnP
87
88 #ifdef ZT_PORTMAPPER_TRACE
89 fprintf(stderr,"PortMapper: started for UDP port %d" ZT_EOL_S,localPort);
90 #endif
91
92 while (run) {
93
94 // ---------------------------------------------------------------------
95 // NAT-PMP mode (preferred)
96 // ---------------------------------------------------------------------
97 if (mode == 0) {
98 natpmp_t natpmp;
99 natpmpresp_t response;
100 int r = 0;
101
102 bool natPmpSuccess = false;
103 for(int tries=0;tries<60;++tries) {
104 int tryPort = (int)localPort + tries;
105 if (tryPort >= 65535)
106 tryPort = (tryPort - 65535) + 1025;
107
108 memset(&natpmp,0,sizeof(natpmp));
109 memset(&response,0,sizeof(response));
110
111 if (initnatpmp(&natpmp,0,0) != 0) {
112 mode = 1;
113 closenatpmp(&natpmp);
114 #ifdef ZT_PORTMAPPER_TRACE
115 PM_TRACE("PortMapper: NAT-PMP: init failed, switching to UPnP mode" ZT_EOL_S);
116 #endif
117 break;
118 }
119
120 InetAddress publicAddress;
121 sendpublicaddressrequest(&natpmp);
122 int64_t myTimeout = OSUtils::now() + 5000;
123 do {
124 fd_set fds;
125 struct timeval timeout;
126 FD_ZERO(&fds);
127 FD_SET(natpmp.s, &fds);
128 getnatpmprequesttimeout(&natpmp, &timeout);
129 select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
130 r = readnatpmpresponseorretry(&natpmp, &response);
131 if (OSUtils::now() >= myTimeout)
132 break;
133 } while (r == NATPMP_TRYAGAIN);
134 if (r == 0) {
135 publicAddress = InetAddress((uint32_t)response.pnu.publicaddress.addr.s_addr,0);
136 } else {
137 #ifdef ZT_PORTMAPPER_TRACE
138 PM_TRACE("PortMapper: NAT-PMP: request for external address failed, aborting..." ZT_EOL_S);
139 #endif
140 closenatpmp(&natpmp);
141 break;
142 }
143
144 sendnewportmappingrequest(&natpmp,NATPMP_PROTOCOL_UDP,localPort,tryPort,(ZT_PORTMAPPER_REFRESH_DELAY * 2) / 1000);
145 myTimeout = OSUtils::now() + 10000;
146 do {
147 fd_set fds;
148 struct timeval timeout;
149 FD_ZERO(&fds);
150 FD_SET(natpmp.s, &fds);
151 getnatpmprequesttimeout(&natpmp, &timeout);
152 select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
153 r = readnatpmpresponseorretry(&natpmp, &response);
154 if (OSUtils::now() >= myTimeout)
155 break;
156 } while (r == NATPMP_TRYAGAIN);
157 if (r == 0) {
158 publicAddress.setPort(response.pnu.newportmapping.mappedpublicport);
159 #ifdef ZT_PORTMAPPER_TRACE
160 char paddr[128];
161 PM_TRACE("PortMapper: NAT-PMP: mapped %u to %s" ZT_EOL_S,(unsigned int)localPort,publicAddress.toString(paddr));
162 #endif
163 Mutex::Lock sl(surface_l);
164 surface.clear();
165 surface.push_back(publicAddress);
166 natPmpSuccess = true;
167 closenatpmp(&natpmp);
168 break;
169 } else {
170 closenatpmp(&natpmp);
171 // continue
172 }
173 }
174
175 if (!natPmpSuccess) {
176 mode = 1;
177 #ifdef ZT_PORTMAPPER_TRACE
178 PM_TRACE("PortMapper: NAT-PMP: request failed, switching to UPnP mode" ZT_EOL_S);
179 #endif
180 }
181 }
182 // ---------------------------------------------------------------------
183
184 // ---------------------------------------------------------------------
185 // UPnP mode
186 // ---------------------------------------------------------------------
187 if (mode == 1) {
188 char lanaddr[4096];
189 char externalip[4096]; // no range checking? so make these buffers larger than any UDP packet a uPnP server could send us as a precaution :P
190 char inport[16];
191 char outport[16];
192 struct UPNPUrls urls;
193 struct IGDdatas data;
194
195 int upnpError = 0;
196 UPNPDev *devlist = upnpDiscoverAll(5000,(const char *)0,(const char *)0,0,0,2,&upnpError);
197 if (devlist) {
198
199 #ifdef ZT_PORTMAPPER_TRACE
200 {
201 UPNPDev *dev = devlist;
202 while (dev) {
203 PM_TRACE("PortMapper: found UPnP device at URL '%s': %s" ZT_EOL_S,dev->descURL,dev->st);
204 dev = dev->pNext;
205 }
206 }
207 #endif
208
209 memset(lanaddr,0,sizeof(lanaddr));
210 memset(externalip,0,sizeof(externalip));
211 memset(&urls,0,sizeof(urls));
212 memset(&data,0,sizeof(data));
213 OSUtils::ztsnprintf(inport,sizeof(inport),"%d",localPort);
214
215 if ((UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) {
216 #ifdef ZT_PORTMAPPER_TRACE
217 PM_TRACE("PortMapper: UPnP: my LAN IP address: %s" ZT_EOL_S,lanaddr);
218 #endif
219 if ((UPNP_GetExternalIPAddress(urls.controlURL,data.first.servicetype,externalip) == UPNPCOMMAND_SUCCESS)&&(externalip[0])) {
220 #ifdef ZT_PORTMAPPER_TRACE
221 PM_TRACE("PortMapper: UPnP: my external IP address: %s" ZT_EOL_S,externalip);
222 #endif
223
224 for(int tries=0;tries<60;++tries) {
225 int tryPort = (int)localPort + tries;
226 if (tryPort >= 65535)
227 tryPort = (tryPort - 65535) + 1025;
228 OSUtils::ztsnprintf(outport,sizeof(outport),"%u",tryPort);
229
230 // First check and see if this port is already mapped to the
231 // same unique name. If so, keep this mapping and don't try
232 // to map again since this can break buggy routers. But don't
233 // fail if this command fails since not all routers support it.
234 {
235 char haveIntClient[128]; // 128 == big enough for all these as per miniupnpc "documentation"
236 char haveIntPort[128];
237 char haveDesc[128];
238 char haveEnabled[128];
239 char haveLeaseDuration[128];
240 memset(haveIntClient,0,sizeof(haveIntClient));
241 memset(haveIntPort,0,sizeof(haveIntPort));
242 memset(haveDesc,0,sizeof(haveDesc));
243 memset(haveEnabled,0,sizeof(haveEnabled));
244 memset(haveLeaseDuration,0,sizeof(haveLeaseDuration));
245 if ((UPNP_GetSpecificPortMappingEntry(urls.controlURL,data.first.servicetype,outport,"UDP",(const char *)0,haveIntClient,haveIntPort,haveDesc,haveEnabled,haveLeaseDuration) == UPNPCOMMAND_SUCCESS)&&(uniqueName == haveDesc)) {
246 #ifdef ZT_PORTMAPPER_TRACE
247 PM_TRACE("PortMapper: UPnP: reusing previously reserved external port: %s" ZT_EOL_S,outport);
248 #endif
249 Mutex::Lock sl(surface_l);
250 surface.clear();
251 InetAddress tmp(externalip);
252 tmp.setPort(tryPort);
253 surface.push_back(tmp);
254 break;
255 }
256 }
257
258 // Try to map this port
259 int mapResult = 0;
260 if ((mapResult = UPNP_AddPortMapping(urls.controlURL,data.first.servicetype,outport,inport,lanaddr,uniqueName.c_str(),"UDP",(const char *)0,"0")) == UPNPCOMMAND_SUCCESS) {
261 #ifdef ZT_PORTMAPPER_TRACE
262 PM_TRACE("PortMapper: UPnP: reserved external port: %s" ZT_EOL_S,outport);
263 #endif
264 Mutex::Lock sl(surface_l);
265 surface.clear();
266 InetAddress tmp(externalip);
267 tmp.setPort(tryPort);
268 surface.push_back(tmp);
269 break;
270 } else {
271 #ifdef ZT_PORTMAPPER_TRACE
272 PM_TRACE("PortMapper: UPnP: UPNP_AddPortMapping(%s) failed: %d" ZT_EOL_S,outport,mapResult);
273 #endif
274 Thread::sleep(1000);
275 }
276 }
277
278 } else {
279 mode = 0;
280 #ifdef ZT_PORTMAPPER_TRACE
281 PM_TRACE("PortMapper: UPnP: UPNP_GetExternalIPAddress failed, returning to NAT-PMP mode" ZT_EOL_S);
282 #endif
283 }
284 } else {
285 mode = 0;
286 #ifdef ZT_PORTMAPPER_TRACE
287 PM_TRACE("PortMapper: UPnP: UPNP_GetValidIGD failed, returning to NAT-PMP mode" ZT_EOL_S);
288 #endif
289 }
290
291 freeUPNPDevlist(devlist);
292
293 } else {
294 mode = 0;
295 #ifdef ZT_PORTMAPPER_TRACE
296 PM_TRACE("PortMapper: upnpDiscover failed, returning to NAT-PMP mode: %d" ZT_EOL_S,upnpError);
297 #endif
298 }
299 }
300 // ---------------------------------------------------------------------
301
302 #ifdef ZT_PORTMAPPER_TRACE
303 PM_TRACE("UPNPClient: rescanning in %d ms" ZT_EOL_S,ZT_PORTMAPPER_REFRESH_DELAY);
304 #endif
305 Thread::sleep(ZT_PORTMAPPER_REFRESH_DELAY);
306 }
307
308 delete this;
309 }
310
311 volatile bool run;
312 int localPort;
313 std::string uniqueName;
314
315 Mutex surface_l;
316 std::vector<InetAddress> surface;
317 };
318
PortMapper(int localUdpPortToMap,const char * uniqueName)319 PortMapper::PortMapper(int localUdpPortToMap,const char *uniqueName)
320 {
321 _impl = new PortMapperImpl(localUdpPortToMap,uniqueName);
322 Thread::start(_impl);
323 }
324
~PortMapper()325 PortMapper::~PortMapper()
326 {
327 _impl->run = false;
328 }
329
get() const330 std::vector<InetAddress> PortMapper::get() const
331 {
332 Mutex::Lock _l(_impl->surface_l);
333 return _impl->surface;
334 }
335
336 } // namespace ZeroTier
337
338 #endif // ZT_USE_MINIUPNPC
339