/* * 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: 2025-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 #define PM_TRACE(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "PortMapper", __VA_ARGS__)) #else #define PM_TRACE(...) fprintf(stderr, __VA_ARGS__) #endif #include #include #include #include #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 #include #else #include "../ext/miniupnpc/miniupnpc.h" #include "../ext/miniupnpc/upnpcommands.h" #endif #ifdef ZT_USE_SYSTEM_NATPMP #include #else #ifdef __ANDROID__ #include "natpmp.h" #else #include "../ext/libnatpmp/natpmp.h" #endif #endif namespace 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 #ifdef ZT_PORTMAPPER_TRACE fprintf(stderr,"PortMapper: started for UDP port %d" ZT_EOL_S,localPort); #endif while (run) { // --------------------------------------------------------------------- // 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 } } // --------------------------------------------------------------------- // --------------------------------------------------------------------- // 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); if ((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); } 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 surface; }; PortMapper::PortMapper(int localUdpPortToMap,const char *uniqueName) { _impl = new PortMapperImpl(localUdpPortToMap,uniqueName); Thread::start(_impl); } PortMapper::~PortMapper() { _impl->run = false; } std::vector PortMapper::get() const { Mutex::Lock _l(_impl->surface_l); return _impl->surface; } } // namespace ZeroTier #endif // ZT_USE_MINIUPNPC