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