| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 | /* * Copyright (c)2019 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * * Change Date: 2026-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. *//****/#ifdef ZT_USE_MINIUPNPC// Uncomment to dump debug messages//#define ZT_PORTMAPPER_TRACE 1#ifdef __ANDROID__#include <android/log.h>#define PM_TRACE(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "PortMapper", __VA_ARGS__))#else#define PM_TRACE(...) fprintf(stderr, __VA_ARGS__)#endif#include <stdio.h>#include <stdlib.h>#include <string.h>#include <string>#include "../node/Utils.hpp"#include "OSUtils.hpp"#include "PortMapper.hpp"// These must be defined to get rid of dynamic export stuff in libminiupnpc and libnatpmp#ifdef __WINDOWS__#ifndef MINIUPNP_STATICLIB#define MINIUPNP_STATICLIB#endif#ifndef STATICLIB#define STATICLIB#endif#endif#ifdef ZT_USE_SYSTEM_MINIUPNPC#include <miniupnpc/miniupnpc.h>#include <miniupnpc/upnpcommands.h>#else#include "../ext/miniupnpc/miniupnpc.h"#include "../ext/miniupnpc/upnpcommands.h"#endif#ifdef ZT_USE_SYSTEM_NATPMP#include <natpmp.h>#else#ifdef __ANDROID__#include "natpmp.h"#else#include "../ext/libnatpmp/natpmp.h"#endif#endifnamespace ZeroTier {class PortMapperImpl{public:	PortMapperImpl(int localUdpPortToMap,const char *un) :		run(true),		localPort(localUdpPortToMap),		uniqueName(un)	{	}	~PortMapperImpl() {}	void threadMain()		throw()	{		int mode = 0; // 0 == NAT-PMP, 1 == UPnP		int retrytime = 500;#ifdef ZT_PORTMAPPER_TRACE		fprintf(stderr,"PortMapper: started for UDP port %d" ZT_EOL_S,localPort);#endif		while (run) {			{				// use initnatpmp to check if we can bind a port at all				natpmp_t _natpmp;				int result = initnatpmp(&_natpmp,0,0);				if (result == NATPMP_ERR_CANNOTGETGATEWAY || result == NATPMP_ERR_SOCKETERROR) {					closenatpmp(&_natpmp);#ifdef ZT_PORTMAPPER_TRACE					PM_TRACE("PortMapper: init failed %d. You might not have an internet connection yet. Trying again in %d" ZT_EOL_S, result, retrytime);#endif					Thread::sleep(retrytime);					retrytime = retrytime * 2;					if (retrytime > ZT_PORTMAPPER_REFRESH_DELAY / 10) {						retrytime = ZT_PORTMAPPER_REFRESH_DELAY / 10;					}					continue;				} else {					closenatpmp(&_natpmp);					retrytime = 500;				}			}			// ---------------------------------------------------------------------			// NAT-PMP mode (preferred)			// ---------------------------------------------------------------------			if (mode == 0) {			  natpmp_t natpmp;			  natpmpresp_t response;				int r = 0;				bool natPmpSuccess = false;				for(int tries=0;tries<60;++tries) {					int tryPort = (int)localPort + tries;					if (tryPort >= 65535)						tryPort = (tryPort - 65535) + 1025;					memset(&natpmp,0,sizeof(natpmp));					memset(&response,0,sizeof(response));					if (initnatpmp(&natpmp,0,0) != 0) {						mode = 1;						closenatpmp(&natpmp);#ifdef ZT_PORTMAPPER_TRACE                        PM_TRACE("PortMapper: NAT-PMP: init failed, switching to UPnP mode" ZT_EOL_S);#endif						break;					}					InetAddress publicAddress;					sendpublicaddressrequest(&natpmp);					int64_t myTimeout = OSUtils::now() + 5000;					do {						fd_set fds;						struct timeval timeout;						FD_ZERO(&fds);						FD_SET(natpmp.s, &fds);						getnatpmprequesttimeout(&natpmp, &timeout);						select(FD_SETSIZE, &fds, NULL, NULL, &timeout);						r = readnatpmpresponseorretry(&natpmp, &response);						if (OSUtils::now() >= myTimeout)							break;					} while (r == NATPMP_TRYAGAIN);					if (r == 0) {						publicAddress = InetAddress((uint32_t)response.pnu.publicaddress.addr.s_addr,0);					} else {#ifdef ZT_PORTMAPPER_TRACE                        PM_TRACE("PortMapper: NAT-PMP: request for external address failed, aborting..." ZT_EOL_S);#endif						closenatpmp(&natpmp);						break;					}				  sendnewportmappingrequest(&natpmp,NATPMP_PROTOCOL_UDP,localPort,tryPort,(ZT_PORTMAPPER_REFRESH_DELAY * 2) / 1000);					myTimeout = OSUtils::now() + 10000;					do {				    fd_set fds;				    struct timeval timeout;				    FD_ZERO(&fds);				    FD_SET(natpmp.s, &fds);				    getnatpmprequesttimeout(&natpmp, &timeout);				    select(FD_SETSIZE, &fds, NULL, NULL, &timeout);				    r = readnatpmpresponseorretry(&natpmp, &response);						if (OSUtils::now() >= myTimeout)							break;				  } while (r == NATPMP_TRYAGAIN);					if (r == 0) {						publicAddress.setPort(response.pnu.newportmapping.mappedpublicport);#ifdef ZT_PORTMAPPER_TRACE                        char paddr[128];                        PM_TRACE("PortMapper: NAT-PMP: mapped %u to %s" ZT_EOL_S,(unsigned int)localPort,publicAddress.toString(paddr));#endif						Mutex::Lock sl(surface_l);						surface.clear();						surface.push_back(publicAddress);						natPmpSuccess = true;						closenatpmp(&natpmp);						break;					} else {						closenatpmp(&natpmp);						// continue					}				}				if (!natPmpSuccess) {					mode = 1;#ifdef ZT_PORTMAPPER_TRACE                    PM_TRACE("PortMapper: NAT-PMP: request failed, switching to UPnP mode" ZT_EOL_S);#endif					continue;				}			}			// ---------------------------------------------------------------------			// ---------------------------------------------------------------------			// UPnP mode			// ---------------------------------------------------------------------			if (mode == 1) {				char lanaddr[4096];				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				char inport[16];				char outport[16];				struct UPNPUrls urls;				struct IGDdatas data;				int upnpError = 0;				UPNPDev *devlist = upnpDiscoverAll(5000,(const char *)0,(const char *)0,0,0,2,&upnpError);				if (devlist) {#ifdef ZT_PORTMAPPER_TRACE					{						UPNPDev *dev = devlist;						while (dev) {                            PM_TRACE("PortMapper: found UPnP device at URL '%s': %s" ZT_EOL_S,dev->descURL,dev->st);							dev = dev->pNext;						}					}#endif					memset(lanaddr,0,sizeof(lanaddr));					memset(externalip,0,sizeof(externalip));					memset(&urls,0,sizeof(urls));					memset(&data,0,sizeof(data));					OSUtils::ztsnprintf(inport,sizeof(inport),"%d",localPort);					int foundValidIGD = 0;					if ((foundValidIGD = UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) {#ifdef ZT_PORTMAPPER_TRACE                        PM_TRACE("PortMapper: UPnP: my LAN IP address: %s" ZT_EOL_S,lanaddr);#endif						if ((UPNP_GetExternalIPAddress(urls.controlURL,data.first.servicetype,externalip) == UPNPCOMMAND_SUCCESS)&&(externalip[0])) {#ifdef ZT_PORTMAPPER_TRACE                            PM_TRACE("PortMapper: UPnP: my external IP address: %s" ZT_EOL_S,externalip);#endif							for(int tries=0;tries<60;++tries) {								int tryPort = (int)localPort + tries;								if (tryPort >= 65535)									tryPort = (tryPort - 65535) + 1025;								OSUtils::ztsnprintf(outport,sizeof(outport),"%u",tryPort);								// First check and see if this port is already mapped to the								// same unique name. If so, keep this mapping and don't try								// to map again since this can break buggy routers. But don't								// fail if this command fails since not all routers support it.								{									char haveIntClient[128]; // 128 == big enough for all these as per miniupnpc "documentation"									char haveIntPort[128];									char haveDesc[128];									char haveEnabled[128];									char haveLeaseDuration[128];									memset(haveIntClient,0,sizeof(haveIntClient));									memset(haveIntPort,0,sizeof(haveIntPort));									memset(haveDesc,0,sizeof(haveDesc));									memset(haveEnabled,0,sizeof(haveEnabled));									memset(haveLeaseDuration,0,sizeof(haveLeaseDuration));									if ((UPNP_GetSpecificPortMappingEntry(urls.controlURL,data.first.servicetype,outport,"UDP",(const char *)0,haveIntClient,haveIntPort,haveDesc,haveEnabled,haveLeaseDuration) == UPNPCOMMAND_SUCCESS)&&(uniqueName == haveDesc)) {#ifdef ZT_PORTMAPPER_TRACE                                        PM_TRACE("PortMapper: UPnP: reusing previously reserved external port: %s" ZT_EOL_S,outport);#endif										Mutex::Lock sl(surface_l);										surface.clear();										InetAddress tmp(externalip);										tmp.setPort(tryPort);										surface.push_back(tmp);										break;									}								}								// Try to map this port								int mapResult = 0;								if ((mapResult = UPNP_AddPortMapping(urls.controlURL,data.first.servicetype,outport,inport,lanaddr,uniqueName.c_str(),"UDP",(const char *)0,"0")) == UPNPCOMMAND_SUCCESS) {#ifdef ZT_PORTMAPPER_TRACE                                    PM_TRACE("PortMapper: UPnP: reserved external port: %s" ZT_EOL_S,outport);#endif									Mutex::Lock sl(surface_l);									surface.clear();									InetAddress tmp(externalip);									tmp.setPort(tryPort);									surface.push_back(tmp);									break;								} else {#ifdef ZT_PORTMAPPER_TRACE                                    PM_TRACE("PortMapper: UPnP: UPNP_AddPortMapping(%s) failed: %d" ZT_EOL_S,outport,mapResult);#endif									Thread::sleep(1000);								}							}						} else {							mode = 0;#ifdef ZT_PORTMAPPER_TRACE                            PM_TRACE("PortMapper: UPnP: UPNP_GetExternalIPAddress failed, returning to NAT-PMP mode" ZT_EOL_S);#endif						}					} else {						mode = 0;#ifdef ZT_PORTMAPPER_TRACE                        PM_TRACE("PortMapper: UPnP: UPNP_GetValidIGD failed, returning to NAT-PMP mode" ZT_EOL_S);#endif					}					freeUPNPDevlist(devlist);					if(foundValidIGD) {						FreeUPNPUrls(&urls);					}				} else {					mode = 0;#ifdef ZT_PORTMAPPER_TRACE                    PM_TRACE("PortMapper: upnpDiscover failed, returning to NAT-PMP mode: %d" ZT_EOL_S,upnpError);#endif				}			}			// ---------------------------------------------------------------------#ifdef ZT_PORTMAPPER_TRACE            PM_TRACE("UPNPClient: rescanning in %d ms" ZT_EOL_S,ZT_PORTMAPPER_REFRESH_DELAY);#endif			Thread::sleep(ZT_PORTMAPPER_REFRESH_DELAY);		}		delete this;	}	volatile bool run;	int localPort;	std::string uniqueName;	Mutex surface_l;	std::vector<InetAddress> surface;};PortMapper::PortMapper(int localUdpPortToMap,const char *uniqueName){	_impl = new PortMapperImpl(localUdpPortToMap,uniqueName);	Thread::start(_impl);}PortMapper::~PortMapper(){	_impl->run = false;}std::vector<InetAddress> PortMapper::get() const{	Mutex::Lock _l(_impl->surface_l);	return _impl->surface;}} // namespace ZeroTier#endif // ZT_USE_MINIUPNPC
 |