diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index e8a682944..4b65a681a 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -20,7 +20,7 @@ - + diff --git a/node/WindowsEthernetTap.cpp b/node/WindowsEthernetTap.cpp index 5b7ea5756..8a9bc035d 100644 --- a/node/WindowsEthernetTap.cpp +++ b/node/WindowsEthernetTap.cpp @@ -25,998 +25,7 @@ * LLC. Start here: http://www.zerotier.com/ */ -#include -#include -#include -#include - #include "Constants.hpp" -#include "EthernetTap.hpp" -#include "Logger.hpp" -#include "RuntimeEnvironment.hpp" -#include "Utils.hpp" -#include "Mutex.hpp" -#include "Utils.hpp" - -// ff:ff:ff:ff:ff:ff with no ADI -static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - -// -// TAP implementation for *nix OSes, with some specialization for different flavors -// - -#ifdef __UNIX_LIKE__ ///////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Command identifiers used with command finder static (on various *nixes) -#define ZT_UNIX_IP_COMMAND 1 -#define ZT_UNIX_IFCONFIG_COMMAND 2 -#define ZT_MAC_KEXTLOAD_COMMAND 3 -#define ZT_MAC_KEXTUNLOAD_COMMAND 4 - -// Finds external commands on startup -class _CommandFinder -{ -public: - _CommandFinder() - { - _findCmd(ZT_UNIX_IFCONFIG_COMMAND,"ifconfig"); -#ifdef __LINUX__ - _findCmd(ZT_UNIX_IP_COMMAND,"ip"); -#endif -#ifdef __APPLE__ - _findCmd(ZT_MAC_KEXTLOAD_COMMAND,"kextload"); - _findCmd(ZT_MAC_KEXTUNLOAD_COMMAND,"kextunload"); -#endif - } - - // returns NULL if command was not found - inline const char *operator[](int id) const - throw() - { - std::map::const_iterator c(_paths.find(id)); - if (c == _paths.end()) - return (const char *)0; - return c->second.c_str(); - } - -private: - inline void _findCmd(int id,const char *name) - { - char tmp[4096]; - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/sbin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/sbin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/bin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/bin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - } - std::map _paths; -}; -static const _CommandFinder UNIX_COMMANDS; - -#ifdef __LINUX__ -#include -#include -#include -#include -#include -#endif // __LINUX__ - -#ifdef __APPLE__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -struct prf_ra { // stupid OSX compile fix... in6_var defines this in a struct which namespaces it for C++ - u_char onlink : 1; - u_char autonomous : 1; - u_char reserved : 6; -} prf_ra; -#include -#include -#include -#include -#include -#include - -// These are KERNEL_PRIVATE... why? -#ifndef SIOCAUTOCONF_START -#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */ -#endif -#ifndef SIOCAUTOCONF_STOP -#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */ -#endif - -static volatile int EthernetTap_instances = 0; -static ZeroTier::Mutex EthernetTap_instances_m; - -static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts) -{ - struct in6_ndireq nd; - struct in6_ifreq ifr; - - int s = socket(AF_INET6,SOCK_DGRAM,0); - if (s <= 0) - return false; - - memset(&nd,0,sizeof(nd)); - strncpy(nd.ifname,ifname,sizeof(nd.ifname)); - - if (ioctl(s,SIOCGIFINFO_IN6,&nd)) { - close(s); - return false; - } - - unsigned long oldFlags = (unsigned long)nd.ndi.flags; - - if (performNUD) - nd.ndi.flags |= ND6_IFF_PERFORMNUD; - else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD; - - if (oldFlags != (unsigned long)nd.ndi.flags) { - if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) { - close(s); - return false; - } - } - - memset(&ifr,0,sizeof(ifr)); - strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name)); - if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) { - close(s); - return false; - } - - close(s); - return true; -} - -#endif // __APPLE__ - -namespace ZeroTier { - -// Only permit one tap to be opened concurrently across the entire process -static Mutex __tapCreateLock; - -#ifdef __LINUX__ -EthernetTap::EthernetTap( - const RuntimeEnvironment *renv, - const char *tag, - const MAC &mac, - unsigned int mtu, - void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), - void *arg) - throw(std::runtime_error) : - _mac(mac), - _mtu(mtu), - _r(renv), - _handler(handler), - _arg(arg), - _fd(0) -{ - char procpath[128]; - struct stat sbuf; - Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally - - if (mtu > 4096) - throw std::runtime_error("max tap MTU is 4096"); - - _fd = ::open("/dev/net/tun",O_RDWR); - if (_fd <= 0) - throw std::runtime_error(std::string("could not open TUN/TAP device: ") + strerror(errno)); - - struct ifreq ifr; - memset(&ifr,0,sizeof(ifr)); - - // Try to recall our last device name, or pick an unused one if that fails. - bool recalledDevice = false; - if ((tag)&&(tag[0])) { - Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),tag); - Utils::snprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); - recalledDevice = (stat(procpath,&sbuf) != 0); - } - if (!recalledDevice) { - int devno = 0; - do { - Utils::snprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"zt%d",devno++); - Utils::snprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); - } while (stat(procpath,&sbuf) == 0); // try zt#++ until we find one that does not exist - } - - ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - if (ioctl(_fd,TUNSETIFF,(void *)&ifr) < 0) { - ::close(_fd); - throw std::runtime_error("unable to configure TUN/TAP device for TAP operation"); - } - - strcpy(_dev,ifr.ifr_name); - - ioctl(_fd,TUNSETPERSIST,0); // valgrind may generate a false alarm here - - // Open an arbitrary socket to talk to netlink - int sock = socket(AF_INET,SOCK_DGRAM,0); - if (sock <= 0) { - ::close(_fd); - throw std::runtime_error("unable to open netlink socket"); - } - - // Set MAC address - ifr.ifr_ifru.ifru_hwaddr.sa_family = ARPHRD_ETHER; - memcpy(ifr.ifr_ifru.ifru_hwaddr.sa_data,mac.data,6); - if (ioctl(sock,SIOCSIFHWADDR,(void *)&ifr) < 0) { - ::close(_fd); - ::close(sock); - throw std::runtime_error("unable to configure TAP hardware (MAC) address"); - return; - } - - // Set MTU - ifr.ifr_ifru.ifru_mtu = (int)mtu; - if (ioctl(sock,SIOCSIFMTU,(void *)&ifr) < 0) { - ::close(_fd); - ::close(sock); - throw std::runtime_error("unable to configure TAP MTU"); - } - - if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { - ::close(_fd); - throw std::runtime_error("unable to set flags on file descriptor for TAP device"); - } - - /* Bring interface up */ - if (ioctl(sock,SIOCGIFFLAGS,(void *)&ifr) < 0) { - ::close(_fd); - ::close(sock); - throw std::runtime_error("unable to get TAP interface flags"); - } - ifr.ifr_flags |= IFF_UP; - if (ioctl(sock,SIOCSIFFLAGS,(void *)&ifr) < 0) { - ::close(_fd); - ::close(sock); - throw std::runtime_error("unable to set TAP interface flags"); - } - - ::close(sock); - - ::pipe(_shutdownSignalPipe); - - TRACE("tap %s created",_dev); - - _thread = Thread::start(this); -} -#endif // __LINUX__ - -#ifdef __APPLE__ -EthernetTap::EthernetTap( - const RuntimeEnvironment *renv, - const char *tag, - const MAC &mac, - unsigned int mtu, - void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), - void *arg) - throw(std::runtime_error) : - _mac(mac), - _mtu(mtu), - _r(renv), - _handler(handler), - _arg(arg), - _fd(0) -{ - char devpath[64],ethaddr[64],mtustr[16],tmp[4096]; - struct stat stattmp; - Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally - - if (mtu > 4096) - throw std::runtime_error("max tap MTU is 4096"); - - // Check for existence of ZT tap devices, try to load module if not there - const char *kextload = UNIX_COMMANDS[ZT_MAC_KEXTLOAD_COMMAND]; - if ((stat("/dev/zt0",&stattmp))&&(kextload)) { - strcpy(tmp,_r->homePath.c_str()); - long kextpid = (long)vfork(); - if (kextpid == 0) { - chdir(tmp); - execl(kextload,kextload,"-q","-repository",tmp,"tap.kext",(const char *)0); - _exit(-1); - } else if (kextpid > 0) { - int exitcode = -1; - waitpid(kextpid,&exitcode,0); - usleep(500); - } else throw std::runtime_error("unable to create subprocess with fork()"); - } - if (stat("/dev/zt0",&stattmp)) - throw std::runtime_error("/dev/zt# tap devices do not exist and unable to load kernel extension"); - - // Try to reopen the last device we had, if we had one and it's still unused. - bool recalledDevice = false; - if ((tag)&&(tag[0])) { - Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",tag); - if (stat(devpath,&stattmp) == 0) { - _fd = ::open(devpath,O_RDWR); - if (_fd > 0) { - Utils::scopy(_dev,sizeof(_dev),tag); - recalledDevice = true; - } - } - } - - // Open the first unused tap device if we didn't recall a previous one. - if (!recalledDevice) { - for(int i=0;i<256;++i) { - Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i); - if (stat(devpath,&stattmp)) - throw std::runtime_error("no more TAP devices available"); - _fd = ::open(devpath,O_RDWR); - if (_fd > 0) { - Utils::snprintf(_dev,sizeof(_dev),"zt%d",i); - break; - } - } - } - - if (_fd <= 0) - throw std::runtime_error("unable to open TAP device or no more devices available"); - - if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { - ::close(_fd); - throw std::runtime_error("unable to set flags on file descriptor for TAP device"); - } - - const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; - if (!ifconfig) { - ::close(_fd); - throw std::runtime_error("unable to find 'ifconfig' command on system"); - } - - // Configure MAC address and MTU, bring interface up - Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); - Utils::snprintf(mtustr,sizeof(mtustr),"%u",mtu); - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ifconfig,ifconfig,_dev,"lladdr",ethaddr,"mtu",mtustr,"up",(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - if (exitcode) { - ::close(_fd); - throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); - } - } - - _setIpv6Stuff(_dev,true,false); - - ::pipe(_shutdownSignalPipe); - - _thread = Thread::start(this); - - EthernetTap_instances_m.lock(); - ++EthernetTap_instances; - EthernetTap_instances_m.unlock(); -} -#endif // __APPLE__ - -EthernetTap::~EthernetTap() -{ - ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit - Thread::join(_thread); - ::close(_fd); - -#ifdef __APPLE__ - EthernetTap_instances_m.lock(); - int instances = --EthernetTap_instances; - EthernetTap_instances_m.unlock(); - if (instances <= 0) { - // Unload OSX kernel extension on the deletion of the last EthernetTap - // instance. - const char *kextunload = UNIX_COMMANDS[ZT_MAC_KEXTUNLOAD_COMMAND]; - if (kextunload) { - char tmp[4096]; - sprintf(tmp,"%s/tap.kext",_r->homePath.c_str()); - long kextpid = (long)vfork(); - if (kextpid == 0) { - execl(kextunload,kextunload,tmp,(const char *)0); - _exit(-1); - } else if (kextpid > 0) { - int exitcode = -1; - waitpid(kextpid,&exitcode,0); - } - } - } -#endif // __APPLE__ -} - -void EthernetTap::setDisplayName(const char *dn) -{ -} - -#ifdef __LINUX__ -static bool ___removeIp(const char *_dev,const InetAddress &ip) -{ - const char *ipcmd = UNIX_COMMANDS[ZT_UNIX_IP_COMMAND]; - if (!ipcmd) - return false; - long cpid = (long)vfork(); - if (cpid == 0) { - execl(ipcmd,ipcmd,"addr","del",ip.toString().c_str(),"dev",_dev,(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } -} - -bool EthernetTap::addIP(const InetAddress &ip) -{ - const char *ipcmd = UNIX_COMMANDS[ZT_UNIX_IP_COMMAND]; - if (!ipcmd) { - LOG("ERROR: could not configure IP address for %s: unable to find 'ip' command on system (checked /sbin, /bin, /usr/sbin, /usr/bin)",_dev); - return false; - } - - if (!ip) - return false; - - std::set allIps(ips()); - if (allIps.count(ip) > 0) - return true; - - // Remove and reconfigure if address is the same but netmask is different - for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { - if (i->ipsEqual(ip)) { - if (___removeIp(_dev,*i)) { - break; - } else { - LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); - } - } - } - - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ipcmd,ipcmd,"addr","add",ip.toString().c_str(),"dev",_dev,(const char *)0); - _exit(-1); - } else if (cpid > 0) { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } - - return false; -} -#endif // __LINUX__ - -#ifdef __APPLE__ -static bool ___removeIp(const char *_dev,const InetAddress &ip) -{ - const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; - if (!ifconfig) - return false; - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ifconfig,ifconfig,_dev,"inet",ip.toIpString().c_str(),"-alias",(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } - return false; // never reached, make compiler shut up about return value -} - -bool EthernetTap::addIP(const InetAddress &ip) -{ - const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; - if (!ifconfig) { - LOG("ERROR: could not configure IP address for %s: unable to find 'ifconfig' command on system (checked /sbin, /bin, /usr/sbin, /usr/bin)",_dev); - return false; - } - - if (!ip) - return false; - - std::set allIps(ips()); - if (allIps.count(ip) > 0) - return true; // IP/netmask already assigned - - // Remove and reconfigure if address is the same but netmask is different - for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { - if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { - if (___removeIp(_dev,*i)) { - break; - } else { - LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); - } - } - } - - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ifconfig,ifconfig,_dev,ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } - - return false; -} -#endif // __APPLE__ - -bool EthernetTap::removeIP(const InetAddress &ip) -{ - if (ips().count(ip) > 0) { - if (___removeIp(_dev,ip)) - return true; - } - return false; -} - -std::set EthernetTap::ips() const -{ - struct ifaddrs *ifa = (struct ifaddrs *)0; - if (getifaddrs(&ifa)) - return std::set(); - - std::set r; - - struct ifaddrs *p = ifa; - while (p) { - if ((!strcmp(p->ifa_name,_dev))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) { - switch(p->ifa_addr->sa_family) { - case AF_INET: { - struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr; - struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask; - r.insert(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr))); - } break; - case AF_INET6: { - struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr; - struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask; - uint32_t b[4]; - memcpy(b,nm->sin6_addr.s6_addr,sizeof(b)); - r.insert(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3]))); - } break; - } - } - p = p->ifa_next; - } - - if (ifa) - freeifaddrs(ifa); - - return r; -} - -void EthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) -{ - char putBuf[4096 + 14]; - if ((_fd > 0)&&(len <= _mtu)) { - for(int i=0;i<6;++i) - putBuf[i] = to.data[i]; - for(int i=0;i<6;++i) - putBuf[i+6] = from.data[i]; - *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); - memcpy(putBuf + 14,data,len); - len += 14; - - int n = ::write(_fd,putBuf,len); - if (n <= 0) { - LOG("error writing packet to Ethernet tap device: %s",strerror(errno)); - } else if (n != (int)len) { - // Saw this gremlin once, so log it if we see it again... OSX tap - // or something seems to have goofy issues with certain MTUs. - LOG("ERROR: write underrun: %s tap write() wrote %d of %u bytes of frame",_dev,n,len); - } - } -} - -std::string EthernetTap::deviceName() const -{ - return std::string(_dev); -} - -std::string EthernetTap::persistentId() const -{ - return std::string(); -} - -#ifdef __LINUX__ -bool EthernetTap::updateMulticastGroups(std::set &groups) -{ - char *ptr,*ptr2; - unsigned char mac[6]; - std::set newGroups; - - int fd = ::open("/proc/net/dev_mcast",O_RDONLY); - if (fd > 0) { - char buf[131072]; - int n = (int)::read(fd,buf,sizeof(buf)); - if ((n > 0)&&(n < (int)sizeof(buf))) { - buf[n] = (char)0; - for(char *l=strtok_r(buf,"\r\n",&ptr);(l);l=strtok_r((char *)0,"\r\n",&ptr)) { - int fno = 0; - char *devname = (char *)0; - char *mcastmac = (char *)0; - for(char *f=strtok_r(l," \t",&ptr2);(f);f=strtok_r((char *)0," \t",&ptr2)) { - if (fno == 1) - devname = f; - else if (fno == 4) - mcastmac = f; - ++fno; - } - if ((devname)&&(!strcmp(devname,_dev))&&(mcastmac)&&(Utils::unhex(mcastmac,mac,6) == 6)) - newGroups.insert(MulticastGroup(MAC(mac),0)); - } - } - ::close(fd); - } - - { - std::set allIps(ips()); - for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) - newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); - } - - bool changed = false; - - newGroups.insert(_blindWildcardMulticastGroup); // always join this - - for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { - if (!groups.count(*mg)) { - groups.insert(*mg); - changed = true; - } - } - for(std::set::iterator mg(groups.begin());mg!=groups.end();) { - if (!newGroups.count(*mg)) { - groups.erase(mg++); - changed = true; - } else ++mg; - } - - return changed; -} -#endif // __LINUX__ - -#ifdef __APPLE__ -// -------------------------------------------------------------------------- -// This source is from: -// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt -// It's here because OSX 10.6 does not have this convenience function. - -#define SALIGN (sizeof(uint32_t) - 1) -#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \ -(SALIGN + 1)) -#define MAX_SYSCTL_TRY 5 -#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA) - -/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from */ -/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */ -//#define DARWIN_COMPAT - -//#ifdef DARWIN_COMPAT -#define GIM_SYSCTL_MIB NET_RT_IFLIST2 -#define GIM_RTM_ADDR RTM_NEWMADDR2 -//#else -//#define GIM_SYSCTL_MIB NET_RT_IFMALIST -//#define GIM_RTM_ADDR RTM_NEWMADDR -//#endif - -// Not in 10.6 includes so use our own -struct _intl_ifmaddrs { - struct _intl_ifmaddrs *ifma_next; - struct sockaddr *ifma_name; - struct sockaddr *ifma_addr; - struct sockaddr *ifma_lladdr; -}; - -static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif) -{ - int icnt = 1; - int dcnt = 0; - int ntry = 0; - size_t len; - size_t needed; - int mib[6]; - int i; - char *buf; - char *data; - char *next; - char *p; - struct ifma_msghdr2 *ifmam; - struct _intl_ifmaddrs *ifa, *ift; - struct rt_msghdr *rtm; - struct sockaddr *sa; - - mib[0] = CTL_NET; - mib[1] = PF_ROUTE; - mib[2] = 0; /* protocol */ - mib[3] = 0; /* wildcard address family */ - mib[4] = GIM_SYSCTL_MIB; - mib[5] = 0; /* no flags */ - do { - if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) - return (-1); - if ((buf = (char *)malloc(needed)) == NULL) - return (-1); - if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { - if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { - free(buf); - return (-1); - } - free(buf); - buf = NULL; - } - } while (buf == NULL); - - for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { - rtm = (struct rt_msghdr *)(void *)next; - if (rtm->rtm_version != RTM_VERSION) - continue; - switch (rtm->rtm_type) { - case GIM_RTM_ADDR: - ifmam = (struct ifma_msghdr2 *)(void *)rtm; - if ((ifmam->ifmam_addrs & RTA_IFA) == 0) - break; - icnt++; - p = (char *)(ifmam + 1); - for (i = 0; i < RTAX_MAX; i++) { - if ((RTA_MASKS & ifmam->ifmam_addrs & - (1 << i)) == 0) - continue; - sa = (struct sockaddr *)(void *)p; - len = SA_RLEN(sa); - dcnt += len; - p += len; - } - break; - } - } - - data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt); - if (data == NULL) { - free(buf); - return (-1); - } - - ifa = (struct _intl_ifmaddrs *)(void *)data; - data += sizeof(struct _intl_ifmaddrs) * icnt; - - memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt); - ift = ifa; - - for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { - rtm = (struct rt_msghdr *)(void *)next; - if (rtm->rtm_version != RTM_VERSION) - continue; - - switch (rtm->rtm_type) { - case GIM_RTM_ADDR: - ifmam = (struct ifma_msghdr2 *)(void *)rtm; - if ((ifmam->ifmam_addrs & RTA_IFA) == 0) - break; - - p = (char *)(ifmam + 1); - for (i = 0; i < RTAX_MAX; i++) { - if ((RTA_MASKS & ifmam->ifmam_addrs & - (1 << i)) == 0) - continue; - sa = (struct sockaddr *)(void *)p; - len = SA_RLEN(sa); - switch (i) { - case RTAX_GATEWAY: - ift->ifma_lladdr = - (struct sockaddr *)(void *)data; - memcpy(data, p, len); - data += len; - break; - - case RTAX_IFP: - ift->ifma_name = - (struct sockaddr *)(void *)data; - memcpy(data, p, len); - data += len; - break; - - case RTAX_IFA: - ift->ifma_addr = - (struct sockaddr *)(void *)data; - memcpy(data, p, len); - data += len; - break; - - default: - data += len; - break; - } - p += len; - } - ift->ifma_next = ift + 1; - ift = ift->ifma_next; - break; - } - } - - free(buf); - - if (ift > ifa) { - ift--; - ift->ifma_next = NULL; - *pif = ifa; - } else { - *pif = NULL; - free(ifa); - } - return (0); -} - -static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp) -{ - free(ifmp); -} - -// -------------------------------------------------------------------------- - -bool EthernetTap::updateMulticastGroups(std::set &groups) -{ - std::set newGroups; - struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0; - if (!_intl_getifmaddrs(&ifmap)) { - struct _intl_ifmaddrs *p = ifmap; - while (p) { - if (p->ifma_addr->sa_family == AF_LINK) { - struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; - struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; - if ((la->sdl_alen == 6)&&(in->sdl_nlen <= sizeof(_dev))&&(!memcmp(_dev,in->sdl_data,in->sdl_nlen))) - newGroups.insert(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen),0)); - } - p = p->ifma_next; - } - _intl_freeifmaddrs(ifmap); - } - - { - std::set allIps(ips()); - for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) - newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); - } - - bool changed = false; - - newGroups.insert(_blindWildcardMulticastGroup); // always join this - - for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { - if (!groups.count(*mg)) { - groups.insert(*mg); - changed = true; - } - } - for(std::set::iterator mg(groups.begin());mg!=groups.end();) { - if (!newGroups.count(*mg)) { - groups.erase(mg++); - changed = true; - } else ++mg; - } - - return changed; -} -#endif // __APPLE__ - -void EthernetTap::threadMain() - throw() -{ - fd_set readfds,nullfds; - MAC to,from; - int n,nfds,r; - char getBuf[8194]; - Buffer<4096> data; - - // Wait for a moment after startup -- wait for Network to finish - // constructing itself. - Thread::sleep(500); - - FD_ZERO(&readfds); - FD_ZERO(&nullfds); - nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1; - - r = 0; - for(;;) { - FD_SET(_shutdownSignalPipe[0],&readfds); - FD_SET(_fd,&readfds); - select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0); - - if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread - break; - - if (FD_ISSET(_fd,&readfds)) { - n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r); - if (n < 0) { - if ((errno != EINTR)&&(errno != ETIMEDOUT)) { - TRACE("unexpected error reading from tap: %s",strerror(errno)); - break; - } - } else { - // Some tap drivers like to send the ethernet frame and the - // payload in two chunks, so handle that by accumulating - // data until we have at least a frame. - r += n; - if (r > 14) { - if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms - r = _mtu + 14; - for(int i=0;i<6;++i) - to.data[i] = (unsigned char)getBuf[i]; - for(int i=0;i<6;++i) - from.data[i] = (unsigned char)getBuf[i + 6]; - unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); - if (etherType != 0x8100) { // VLAN tagged frames are not supported! - data.copyFrom(getBuf + 14,(unsigned int)r - 14); - _handler(_arg,from,to,etherType,data); - } - r = 0; - } - } - } - } -} - -bool EthernetTap::deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid) -{ - return false; -} - -int EthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDev) -{ - return 0; -} - -} // namespace ZeroTier - -#endif // __UNIX_LIKE__ ////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////////////// - -#ifdef __WINDOWS__ /////////////////////////////////////////////////////////// #include #include @@ -1024,17 +33,27 @@ int EthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const st #include #include #include -#include -#include -#include #include #include #include +#include +#include +#include #include #include +#include "EthernetTap.hpp" +#include "WindowsEthernetTap.hpp" +#include "Logger.hpp" +#include "RuntimeEnvironment.hpp" +#include "Utils.hpp" +#include "Mutex.hpp" + #include "..\windows\TapDriver\tap-windows.h" +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + namespace ZeroTier { // Helper function to get an adapter's LUID and index from its GUID. The LUID is @@ -1061,9 +80,119 @@ static inline std::pair _findAdapterByGuid(const GUID &gui throw std::runtime_error("interface not found"); } +// Only create or manipulate devices one at a time to avoid weird driver layer demons static Mutex _systemTapInitLock; -EthernetTap::EthernetTap( +// Compute some basic environment stuff on startup +class _WinSysEnv +{ +public: + _WinSysEnv() + { +#ifdef _WIN64 + is64Bit = TRUE; + devcon = "\\devcon_x64.exe"; + tapDriver = "\\tap-windows\\x64\\zttap200.inf"; +#else + is64Bit = FALSE; + IsWow64Process(GetCurrentProcess(),&is64Bit); + devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); + tapDriver = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap200.inf" : "\\tap-windows\\x86\\zttap200.inf"); +#endif + } + BOOL is64Bit; + const char *devcon; + const char *tapDriver; +}; +static const _WinSysEnv _winEnv; + +static bool _disableTapDevice(const RuntimeEnvironment *_r,const std::string deviceInstanceId) +{ + HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (devconLog != INVALID_HANDLE_VALUE) + SetFilePointer(devconLog,0,0,FILE_END); + + STARTUPINFOA startupInfo; + startupInfo.cb = sizeof(startupInfo); + if (devconLog != INVALID_HANDLE_VALUE) { + startupInfo.hStdOutput = devconLog; + startupInfo.hStdError = devconLog; + } + PROCESS_INFORMATION processInfo; + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" disable @" + deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + return false; + } + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + + return true; +} +static bool _enableTapDevice(const RuntimeEnvironment *_r,const std::string deviceInstanceId) +{ + HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (devconLog != INVALID_HANDLE_VALUE) + SetFilePointer(devconLog,0,0,FILE_END); + + STARTUPINFOA startupInfo; + startupInfo.cb = sizeof(startupInfo); + if (devconLog != INVALID_HANDLE_VALUE) { + startupInfo.hStdOutput = devconLog; + startupInfo.hStdError = devconLog; + } + PROCESS_INFORMATION processInfo; + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" enable @" + deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + return false; + } + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + + return true; +} + +static void _syncIpsWithRegistry(const std::set &haveIps,const std::string netCfgInstanceId) +{ + // Update registry to contain all non-link-local IPs for this interface + std::string regMultiIps,regMultiNetmasks; + for(std::set::const_iterator i(haveIps.begin());i!=haveIps.end();++i) { + if (!i->isLinkLocal()) { + regMultiIps.append(i->toIpString()); + regMultiIps.push_back((char)0); + regMultiNetmasks.append(i->netmask().toIpString()); + regMultiNetmasks.push_back((char)0); + } + } + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + if (regMultiIps.length()) { + regMultiIps.push_back((char)0); + regMultiNetmasks.push_back((char)0); + RegSetKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"IPAddress",REG_MULTI_SZ,regMultiIps.data(),(DWORD)regMultiIps.length()); + RegSetKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"SubnetMask",REG_MULTI_SZ,regMultiNetmasks.data(),(DWORD)regMultiNetmasks.length()); + } else { + RegDeleteKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"IPAddress"); + RegDeleteKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"SubnetMask"); + } + } + RegCloseKey(tcpIpInterfaces); +} + +WindowsEthernetTap::WindowsEthernetTap( const RuntimeEnvironment *renv, const char *tag, const MAC &mac, @@ -1071,15 +200,15 @@ EthernetTap::EthernetTap( void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), void *arg) throw(std::runtime_error) : - _mac(mac), - _mtu(mtu), + EthernetTap("WindowsEthernetTap",mac,mtu), _r(renv), _handler(handler), _arg(arg), _tap(INVALID_HANDLE_VALUE), _injectSemaphore(INVALID_HANDLE_VALUE), _run(true), - _initialized(false) + _initialized(false), + _enabled(false) { char subkeyName[4096]; char subkeyClass[4096]; @@ -1088,17 +217,6 @@ EthernetTap::EthernetTap( if (mtu > ZT_IF_MTU) throw std::runtime_error("MTU too large for Windows tap"); -#ifdef _WIN64 - BOOL is64Bit = TRUE; - const char *devcon = "\\devcon_x64.exe"; - const char *tapDriver = "\\tap-windows\\x64\\zttap200.inf"; -#else - BOOL is64Bit = FALSE; - IsWow64Process(GetCurrentProcess(),&is64Bit); - const char *devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); - const char *tapDriver = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap200.inf" : "\\tap-windows\\x86\\zttap200.inf"); -#endif - Mutex::Lock _l(_systemTapInitLock); // only one thread may mess with taps at a time, process-wide HKEY nwAdapters; @@ -1135,14 +253,14 @@ EthernetTap::EthernetTap( if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) instanceIdPath.assign(data,dataLen); - if ((_myDeviceInstanceId.length() == 0)&&(instanceId.length() != 0)&&(instanceIdPath.length() != 0)) { + if ((_netCfgInstanceId.length() == 0)&&(instanceId.length() != 0)&&(instanceIdPath.length() != 0)) { type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { data[dataLen] = '\0'; if (!strcmp(data,tag)) { - _myDeviceInstanceId = instanceId; - _myDeviceInstanceIdPath = instanceIdPath; + _netCfgInstanceId = instanceId; + _deviceInstanceId = instanceIdPath; mySubkeyName = subkeyName; break; // found it! } @@ -1154,7 +272,7 @@ EthernetTap::EthernetTap( } // If there is no device, try to create one - if (_myDeviceInstanceId.length() == 0) { + if (_netCfgInstanceId.length() == 0) { // Log devcon output to a file HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); if (devconLog == INVALID_HANDLE_VALUE) { @@ -1174,11 +292,11 @@ EthernetTap::EthernetTap( PROCESS_INFORMATION processInfo; memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + devcon + "\" install \"" + _r->homePath + tapDriver + "\" zttap200").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" install \"" + _r->homePath + _winEnv.tapDriver + "\" zttap200").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { RegCloseKey(nwAdapters); if (devconLog != INVALID_HANDLE_VALUE) CloseHandle(devconLog); - throw std::runtime_error(std::string("unable to find or execute devcon at ")+devcon); + throw std::runtime_error(std::string("unable to find or execute devcon at ") + _winEnv.devcon); } WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); @@ -1207,18 +325,18 @@ EthernetTap::EthernetTap( if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { if (existingDeviceInstances.count(std::string(data,dataLen)) == 0) { RegSetKeyValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",REG_SZ,tag,(DWORD)(strlen(tag)+1)); - _myDeviceInstanceId.assign(data,dataLen); + _netCfgInstanceId.assign(data,dataLen); type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) - _myDeviceInstanceIdPath.assign(data,dataLen); + _deviceInstanceId.assign(data,dataLen); mySubkeyName = subkeyName; // Disable DHCP by default on newly created devices HKEY tcpIpInterfaces; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { DWORD enable = 0; - RegSetKeyValueA(tcpIpInterfaces,_myDeviceInstanceId.c_str(),"EnableDHCP",REG_DWORD,&enable,sizeof(enable)); + RegSetKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),"EnableDHCP",REG_DWORD,&enable,sizeof(enable)); RegCloseKey(tcpIpInterfaces); } @@ -1231,7 +349,7 @@ EthernetTap::EthernetTap( } } - if (_myDeviceInstanceId.length() > 0) { + if (_netCfgInstanceId.length() > 0) { char tmps[4096]; unsigned int tmpsl = Utils::snprintf(tmps,sizeof(tmps),"%.2X-%.2X-%.2X-%.2X-%.2X-%.2X",(unsigned int)mac.data[0],(unsigned int)mac.data[1],(unsigned int)mac.data[2],(unsigned int)mac.data[3],(unsigned int)mac.data[4],(unsigned int)mac.data[5]) + 1; RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"NetworkAddress",REG_SZ,tmps,tmpsl); @@ -1250,7 +368,7 @@ EthernetTap::EthernetTap( // Convert device GUID junk... blech... is there an easier way to do this? { char nobraces[128]; - const char *nbtmp1 = _myDeviceInstanceId.c_str(); + const char *nbtmp1 = _netCfgInstanceId.c_str(); char *nbtmp2 = nobraces; while (*nbtmp1) { if ((*nbtmp1 != '{')&&(*nbtmp1 != '}')) @@ -1263,59 +381,13 @@ EthernetTap::EthernetTap( } // Disable and enable interface to ensure registry settings take effect - { - HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - { - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + devcon + "\" disable @" + _myDeviceInstanceIdPath).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - RegCloseKey(nwAdapters); - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - throw std::runtime_error(std::string("unable to find or execute devcon at ")+devcon); - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - Sleep(250); - { - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + devcon + "\" enable @" + _myDeviceInstanceIdPath).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - RegCloseKey(nwAdapters); - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - throw std::runtime_error(std::string("unable to find or execute devcon at ")+devcon); - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - Sleep(250); - } + _disableTapDevice(_r,_deviceInstanceId); + if (!_enableTapDevice(_r,_deviceInstanceId)) + throw std::runtime_error("cannot enable tap device driver"); // Open the tap, which is in this weird Windows analog of /dev char tapPath[4096]; - Utils::snprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_myDeviceInstanceId.c_str()); + Utils::snprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_netCfgInstanceId.c_str()); for(int openTrials=0;;) { // Try multiple times, since there seem to be reports from the field // of driver init timing issues. Blech. @@ -1327,10 +399,11 @@ EthernetTap::EthernetTap( } else break; } - // Set media status to enabled - uint32_t tmpi = 1; - DWORD bytesReturned = 0; - DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL); + setEnabled(true); + if (!_enabled) { + CloseHandle(_tap); + throw std::runtime_error("cannot enable tap device using IOCTL"); + } // Initialized overlapped I/O structures and related events memset(&_tapOvlRead,0,sizeof(_tapOvlRead)); @@ -1346,7 +419,7 @@ EthernetTap::EthernetTap( _initialized = true; } -EthernetTap::~EthernetTap() +WindowsEthernetTap::~WindowsEthernetTap() { _run = false; @@ -1358,48 +431,39 @@ EthernetTap::~EthernetTap() CloseHandle(_tapOvlWrite.hEvent); CloseHandle(_injectSemaphore); -#ifdef _WIN64 - BOOL is64Bit = TRUE; - const char *devcon = "\\devcon_x64.exe"; -#else - BOOL is64Bit = FALSE; - IsWow64Process(GetCurrentProcess(),&is64Bit); - const char *devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); -#endif - - // Disable network device on shutdown - HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - SetFilePointer(devconLog,0,0,FILE_END); - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + devcon + "\" disable @" + _myDeviceInstanceIdPath).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); + _disableTapDevice(_r,_deviceInstanceId); } -void EthernetTap::setDisplayName(const char *dn) +void WindowsEthernetTap::setEnabled(bool en) +{ + if (_tap == INVALID_HANDLE_VALUE) { + _enabled = false; + } else { + uint32_t tmpi = (en ? 1 : 0); + DWORD bytesReturned = 0; + if (DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL)) + _enabled = en; + else _enabled = false; + } +} + +bool WindowsEthernetTap::enabled() const +{ + return _enabled; +} + +void WindowsEthernetTap::setDisplayName(const char *dn) { if (!_initialized) return; HKEY ifp; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,(std::string("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\") + _myDeviceInstanceId).c_str(),0,KEY_READ|KEY_WRITE,&ifp) == ERROR_SUCCESS) { + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,(std::string("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\") + _netCfgInstanceId).c_str(),0,KEY_READ|KEY_WRITE,&ifp) == ERROR_SUCCESS) { RegSetKeyValueA(ifp,"Connection","Name",REG_SZ,(LPCVOID)dn,(DWORD)(strlen(dn)+1)); RegCloseKey(ifp); } } -bool EthernetTap::addIP(const InetAddress &ip) +bool WindowsEthernetTap::addIP(const InetAddress &ip) { if (!_initialized) return false; @@ -1420,7 +484,9 @@ bool EthernetTap::addIP(const InetAddress &ip) ipr.Address.Ipv4.sin_addr.S_un.S_addr = *((const uint32_t *)ip.rawIpData()); ipr.OnLinkPrefixLength = ip.port(); } else if (ip.isV6()) { - // TODO + ipr.Address.Ipv6.sin6_family = AF_INET6; + memcpy(ipr.Address.Ipv6.sin6_addr.u.Byte,ip.rawIpData(),16); + ipr.OnLinkPrefixLength = ip.port(); } else return false; ipr.PrefixOrigin = IpPrefixOriginManual; @@ -1439,7 +505,7 @@ bool EthernetTap::addIP(const InetAddress &ip) } } - _syncIpsWithRegistry(haveIps); + _syncIpsWithRegistry(haveIps,_netCfgInstanceId); } catch (std::exception &exc) { LOG("unexpected exception adding IP address %s to %s: %s",ip.toString().c_str(),deviceName().c_str(),exc.what()); } catch ( ... ) { @@ -1448,7 +514,7 @@ bool EthernetTap::addIP(const InetAddress &ip) return false; } -bool EthernetTap::removeIP(const InetAddress &ip) +bool WindowsEthernetTap::removeIP(const InetAddress &ip) { if (!_initialized) return false; @@ -1472,7 +538,7 @@ bool EthernetTap::removeIP(const InetAddress &ip) if (addr == ip) { DeleteUnicastIpAddressEntry(&(ipt->Table[i])); FreeMibTable(ipt); - _syncIpsWithRegistry(ips()); + _syncIpsWithRegistry(ips(),_netCfgInstanceId); return true; } } @@ -1487,7 +553,7 @@ bool EthernetTap::removeIP(const InetAddress &ip) return false; } -std::set EthernetTap::ips() const +std::set WindowsEthernetTap::ips() const { static const InetAddress linkLocalLoopback("fe80::1",64); // what is this and why does Windows assign it? std::set addrs; @@ -1518,14 +584,14 @@ std::set EthernetTap::ips() const } FreeMibTable(ipt); } - } catch ( ... ) {} + } catch ( ... ) {} // sanity check, shouldn't happen unless out of memory return addrs; } -void EthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +void WindowsEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) { - if (!_initialized) + if ((!_initialized)||(!_enabled)) return; if (len > (ZT_IF_MTU)) return; // sanity check @@ -1544,17 +610,17 @@ void EthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const ReleaseSemaphore(_injectSemaphore,1,NULL); } -std::string EthernetTap::deviceName() const +std::string WindowsEthernetTap::deviceName() const { - return _myDeviceInstanceId; + return _netCfgInstanceId; } -std::string EthernetTap::persistentId() const +std::string WindowsEthernetTap::persistentId() const { - return _myDeviceInstanceIdPath; + return _deviceInstanceId; } -bool EthernetTap::updateMulticastGroups(std::set &groups) +bool WindowsEthernetTap::updateMulticastGroups(std::set &groups) { if (!_initialized) return false; @@ -1611,7 +677,7 @@ bool EthernetTap::updateMulticastGroups(std::set &groups) return changed; } -void EthernetTap::threadMain() +void WindowsEthernetTap::threadMain() throw() { HANDLE wait4[3]; @@ -1623,6 +689,10 @@ void EthernetTap::threadMain() bool writeInProgress = false; for(;;) { + // Windows can DIAF + if (_enabled) + setEnabled(true); + if (!_run) break; DWORD r = WaitForMultipleObjectsEx(writeInProgress ? 3 : 2,wait4,FALSE,INFINITE,TRUE); if (!_run) break; @@ -1630,12 +700,14 @@ void EthernetTap::threadMain() if (HasOverlappedIoCompleted(&_tapOvlRead)) { DWORD bytesRead = 0; if (GetOverlappedResult(_tap,&_tapOvlRead,&bytesRead,FALSE)) { - if (bytesRead > 14) { + if ((bytesRead > 14)&&(_enabled)) { MAC to(_tapReadBuf); MAC from(_tapReadBuf + 6); - unsigned int etherType = Utils::ntoh(*((const uint16_t *)(_tapReadBuf + 12))); - Buffer<4096> tmp(_tapReadBuf + 14,bytesRead - 14); - _handler(_arg,from,to,etherType,tmp); + unsigned int etherType = ((((unsigned int)_tapReadBuf[12]) & 0xff) << 8) | (((unsigned int)_tapReadBuf[13]) & 0xff); + try { + Buffer<4096> tmp(_tapReadBuf + 14,bytesRead - 14); + _handler(_arg,from,to,etherType,tmp); + } catch ( ... ) {} // handlers should not throw } } ReadFile(_tap,_tapReadBuf,sizeof(_tapReadBuf),NULL,&_tapOvlRead); @@ -1660,17 +732,8 @@ void EthernetTap::threadMain() CancelIo(_tap); } -bool EthernetTap::deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid) +bool WindowsEthernetTap::deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid) { -#ifdef _WIN64 - BOOL is64Bit = TRUE; - const char *devcon = "\\devcon_x64.exe"; -#else - BOOL is64Bit = FALSE; - IsWow64Process(GetCurrentProcess(),&is64Bit); - const char *devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); -#endif - Mutex::Lock _l(_systemTapInitLock); // only one thread may mess with taps at a time, process-wide HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); @@ -1684,7 +747,7 @@ bool EthernetTap::deletePersistentTapDevice(const RuntimeEnvironment *_r,const c PROCESS_INFORMATION processInfo; memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + devcon + "\" remove @" + pid).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" remove @" + pid).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); @@ -1698,7 +761,7 @@ bool EthernetTap::deletePersistentTapDevice(const RuntimeEnvironment *_r,const c return false; } -int EthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices) +int WindowsEthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices) { char subkeyName[4096]; char subkeyClass[4096]; @@ -1759,33 +822,4 @@ int EthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const st return removed; } -void EthernetTap::_syncIpsWithRegistry(const std::set &haveIps) -{ - // Update registry to contain all non-link-local IPs for this interface - std::string regMultiIps,regMultiNetmasks; - for(std::set::const_iterator i(haveIps.begin());i!=haveIps.end();++i) { - if (!i->isLinkLocal()) { - regMultiIps.append(i->toIpString()); - regMultiIps.push_back((char)0); - regMultiNetmasks.append(i->netmask().toIpString()); - regMultiNetmasks.push_back((char)0); - } - } - HKEY tcpIpInterfaces; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { - if (regMultiIps.length()) { - regMultiIps.push_back((char)0); - regMultiNetmasks.push_back((char)0); - RegSetKeyValueA(tcpIpInterfaces,_myDeviceInstanceId.c_str(),"IPAddress",REG_MULTI_SZ,regMultiIps.data(),(DWORD)regMultiIps.length()); - RegSetKeyValueA(tcpIpInterfaces,_myDeviceInstanceId.c_str(),"SubnetMask",REG_MULTI_SZ,regMultiNetmasks.data(),(DWORD)regMultiNetmasks.length()); - } else { - RegDeleteKeyValueA(tcpIpInterfaces,_myDeviceInstanceId.c_str(),"IPAddress"); - RegDeleteKeyValueA(tcpIpInterfaces,_myDeviceInstanceId.c_str(),"SubnetMask"); - } - } - RegCloseKey(tcpIpInterfaces); -} - } // namespace ZeroTier - -#endif // __WINDOWS__ //////////////////////////////////////////////////////// diff --git a/node/WindowsEthernetTap.hpp b/node/WindowsEthernetTap.hpp index f58dc748d..4d9d7c90e 100644 --- a/node/WindowsEthernetTap.hpp +++ b/node/WindowsEthernetTap.hpp @@ -25,51 +25,39 @@ * LLC. Start here: http://www.zerotier.com/ */ -#ifndef ZT_ETHERNETTAP_HPP -#define ZT_ETHERNETTAP_HPP +#ifndef ZT_WINDOWSETHERNETTAP_HPP +#define ZT_WINDOWSETHERNETTAP_HPP #include #include -#include -#include -#include -#include #include #include #include #include "Constants.hpp" -#include "InetAddress.hpp" -#include "MAC.hpp" +#include "EthernetTap.hpp" #include "Mutex.hpp" -#include "MulticastGroup.hpp" #include "Thread.hpp" -#include "Buffer.hpp" -#include "Array.hpp" -#ifdef __WINDOWS__ #include #include -#endif namespace ZeroTier { class RuntimeEnvironment; /** - * System ethernet tap device + * Windows Ethernet tap device using bundled ztTap driver */ -class EthernetTap +class WindowsEthernetTap : public EthernetTap { public: /** - * Construct a new TAP device - * - * Handler arguments: arg,from,to,etherType,data + * Open tap device, installing and creating one if it does not exist * * @param renv Runtime environment - * @param tag A tag used to persist tap identity at the OS layer (e.g. nwid in hex for Windows or device name for *nix) + * @param tag A tag (presently the hex network ID) used to identify persistent tap devices in the registry * @param mac MAC address of device * @param mtu MTU of device * @param desc If non-NULL, a description (not used on all OSes) @@ -77,7 +65,7 @@ public: * @param arg First argument to handler function * @throws std::runtime_error Unable to allocate device */ - EthernetTap( + WindowsEthernetTap( const RuntimeEnvironment *renv, const char *tag, const MAC &mac, @@ -86,120 +74,18 @@ public: void *arg) throw(std::runtime_error); - /** - * Close tap and shut down thread - * - * This may block for a few seconds while thread exits. - */ - ~EthernetTap(); + virtual ~WindowsEthernetTap(); - /** - * Set the user display name for this connection - * - * This does nothing on platforms that don't have this concept. - * - * @param dn User display name - */ - void setDisplayName(const char *dn); - - /** - * @return MAC address of this interface - */ - inline const MAC &mac() const throw() { return _mac; } - - /** - * @return MTU of this interface - */ - inline unsigned int mtu() const throw() { return _mtu; } - - /** - * Add an IP to this interface - * - * @param ip IP and netmask (netmask stored in port field) - * @return True if IP added successfully - */ - bool addIP(const InetAddress &ip); - - /** - * Remove an IP from this interface - * - * Link-local IP addresses may not be able to be removed, depending on platform and type. - * - * @param ip IP and netmask (netmask stored in port field) - * @return True if IP removed successfully - */ - bool removeIP(const InetAddress &ip); - - /** - * @return All IP addresses (V4 and V6) assigned to this interface (including link-local) - */ - std::set ips() const; - - /** - * Set this tap's IP addresses to exactly this set of IPs - * - * New IPs are created, ones not in this list are removed. - * - * @param ips IP addresses with netmask in port field - */ - inline void setIps(const std::set &allIps) - { - for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) - addIP(*i); - std::set myIps(ips()); -#ifdef __APPLE__ - bool haveV6LinkLocal = false; - for(std::set::iterator i(myIps.begin());i!=myIps.end();++i) { - if (i->isLinkLocal()) { - if (i->isV6()) - haveV6LinkLocal = true; - } else if (!allIps.count(*i)) - removeIP(*i); - } - if (!haveV6LinkLocal) - addIP(InetAddress::makeIpv6LinkLocal(_mac)); -#else - for(std::set::iterator i(myIps.begin());i!=myIps.end();++i) { - if ((!i->isLinkLocal())&&(!allIps.count(*i))) - removeIP(*i); - } -#endif - } - - /** - * Put a frame, making it available to the OS for processing - * - * @param from MAC address from which frame originated - * @param to MAC address of destination (typically MAC of tap itself) - * @param etherType Ethernet protocol ID - * @param data Frame payload - * @param len Length of frame - */ - void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); - - /** - * @return OS-specific device or connection name - */ - std::string deviceName() const; - - /** - * @return OS-internal persistent device ID or empty string if not applicable to this platform or not persistent - */ - std::string persistentId() const; - - /** - * Fill or modify a set to contain multicast groups for this device - * - * This populates a set or, if already populated, modifies it to contain - * only multicast groups in which this device is interested. - * - * This should always include the blind wildcard MulticastGroup (MAC of - * ff:ff:ff:ff:ff:ff and 0 ADI field). - * - * @param groups Set to modify in place - * @return True if set was changed since last call - */ - bool updateMulticastGroups(std::set &groups); + virtual void setEnabled(bool en); + virtual bool enabled() const; + virtual void setDisplayName(const char *dn); + virtual bool addIP(const InetAddress &ip); + virtual bool removeIP(const InetAddress &ip); + virtual std::set ips() const; + virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + virtual std::string deviceName() const; + virtual std::string persistentId() const; + virtual bool updateMulticastGroups(std::set &groups); /** * Thread main method; do not call elsewhere @@ -210,10 +96,6 @@ public: /** * Remove persistent tap device by device name * - * This has no effect on platforms that do not have persistent taps. - * On platforms like Windows with persistent devices the device is - * uninstalled. - * * @param _r Runtime environment * @param pdev Device name as returned by persistentId() while tap is running * @return True if a device was deleted @@ -223,10 +105,6 @@ public: /** * Clean persistent tap devices that are not in the supplied set * - * This has no effect on platforms that do not have persistent taps. - * On platforms like Windows with persistent devices the device is - * uninstalled. - * * @param _r Runtime environment * @param exceptThese Devices to leave in place * @param alsoRemoveUnassociatedDevices If true, remove devices not associated with any network as well @@ -235,37 +113,23 @@ public: static int cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices); private: - const MAC _mac; - const unsigned int _mtu; - const RuntimeEnvironment *_r; - void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); void *_arg; - Thread _thread; -#ifdef __UNIX_LIKE__ - char _dev[16]; - int _fd; - int _shutdownSignalPipe[2]; -#endif - -#ifdef __WINDOWS__ - void _syncIpsWithRegistry(const std::set &haveIps); - HANDLE _tap; OVERLAPPED _tapOvlRead,_tapOvlWrite; char _tapReadBuf[ZT_IF_MTU + 32]; HANDLE _injectSemaphore; GUID _deviceGuid; - std::string _myDeviceInstanceId; // NetCfgInstanceId, a GUID - std::string _myDeviceInstanceIdPath; // DeviceInstanceID, another kind of "instance ID" + std::string _netCfgInstanceId; // NetCfgInstanceId, a GUID + std::string _deviceInstanceId; // DeviceInstanceID, another kind of "instance ID" std::queue< std::pair< Array,unsigned int > > _injectPending; Mutex _injectPending_m; volatile bool _run; volatile bool _initialized; -#endif + volatile bool _enabled; }; } // namespace ZeroTier diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index c682aeee1..714896a68 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -25,7 +25,6 @@ - @@ -52,6 +51,7 @@ + @@ -107,6 +107,7 @@ + diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 03a119fec..a58b54bb4 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -30,9 +30,6 @@ Source Files - - Source Files - Source Files @@ -123,6 +120,9 @@ Source Files + + Source Files + @@ -287,6 +287,9 @@ Header Files + + Header Files +