/* * 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 #define PM_TRACE(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "PortMapper", __VA_ARGS__)) #else #define PM_TRACE(...) fprintf(stderr, __VA_ARGS__) #endif #include "../node/Utils.hpp" #include "OSUtils.hpp" #include "PortMapper.hpp" #include #include #include #include // 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 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 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