123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- /*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #ifdef ZT_USE_MINIUPNPC
- // Uncomment to dump debug messages
- //#define ZT_PORTMAPPER_TRACE 1
- #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
- #include "../ext/miniupnpc/miniupnpc.h"
- #include "../ext/miniupnpc/upnpcommands.h"
- #include "../ext/libnatpmp/natpmp.h"
- 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;
- #ifdef ZT_PORTMAPPER_TRACE
- fprintf(stderr,"PortMapper: NAT-PMP: init failed, switching to UPnP mode"ZT_EOL_S);
- #endif
- break;
- }
- InetAddress publicAddress;
- sendpublicaddressrequest(&natpmp);
- uint64_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
- fprintf(stderr,"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
- fprintf(stderr,"PortMapper: NAT-PMP: mapped %u to %s"ZT_EOL_S,(unsigned int)localPort,publicAddress.toString().c_str());
- #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
- fprintf(stderr,"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) {
- fprintf(stderr,"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));
- Utils::snprintf(inport,sizeof(inport),"%d",localPort);
- if ((UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) {
- #ifdef ZT_PORTMAPPER_TRACE
- fprintf(stderr,"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
- fprintf(stderr,"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;
- Utils::snprintf(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
- fprintf(stderr,"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
- fprintf(stderr,"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
- fprintf(stderr,"PortMapper: UPnP: UPNP_AddPortMapping(%s) failed: %d"ZT_EOL_S,outport,mapResult);
- #endif
- Thread::sleep(1000);
- }
- }
- } else {
- mode = 0;
- #ifdef ZT_PORTMAPPER_TRACE
- fprintf(stderr,"PortMapper: UPnP: UPNP_GetExternalIPAddress failed, returning to NAT-PMP mode"ZT_EOL_S);
- #endif
- }
- } else {
- mode = 0;
- #ifdef ZT_PORTMAPPER_TRACE
- fprintf(stderr,"PortMapper: UPnP: UPNP_GetValidIGD failed, returning to NAT-PMP mode"ZT_EOL_S);
- #endif
- }
- freeUPNPDevlist(devlist);
- } else {
- mode = 0;
- #ifdef ZT_PORTMAPPER_TRACE
- fprintf(stderr,"PortMapper: upnpDiscover failed, returning to NAT-PMP mode: %d"ZT_EOL_S,upnpError);
- #endif
- }
- }
- // ---------------------------------------------------------------------
- #ifdef ZT_PORTMAPPER_TRACE
- fprintf(stderr,"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
|