diff options
author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2015-03-31 14:10:02 -0700 |
---|---|---|
committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2015-03-31 14:10:02 -0700 |
commit | 8990fb8267b5ed5677d61a421e0dfba41e365058 (patch) | |
tree | 7f8423f7f0f0c99e715678e0abbdeb85e4bc28ed /osdep | |
parent | 2c5dbecb3c1e9d8257c1c80eac7fcae5fb51b508 (diff) | |
download | infinitytier-8990fb8267b5ed5677d61a421e0dfba41e365058.tar.gz infinitytier-8990fb8267b5ed5677d61a421e0dfba41e365058.zip |
osnet -> osdep
Diffstat (limited to 'osdep')
-rw-r--r-- | osdep/BSDEthernetTap.cpp | 429 | ||||
-rw-r--r-- | osdep/BSDEthernetTap.hpp | 84 | ||||
-rw-r--r-- | osdep/BSDEthernetTapFactory.cpp | 78 | ||||
-rw-r--r-- | osdep/BSDEthernetTapFactory.hpp | 63 | ||||
-rw-r--r-- | osdep/LinuxEthernetTap.cpp | 424 | ||||
-rw-r--r-- | osdep/LinuxEthernetTap.hpp | 85 | ||||
-rw-r--r-- | osdep/LinuxEthernetTapFactory.cpp | 74 | ||||
-rw-r--r-- | osdep/LinuxEthernetTapFactory.hpp | 63 | ||||
-rw-r--r-- | osdep/NativeSocketManager.cpp | 989 | ||||
-rw-r--r-- | osdep/NativeSocketManager.hpp | 117 | ||||
-rw-r--r-- | osdep/OSXEthernetTap.cpp | 639 | ||||
-rw-r--r-- | osdep/OSXEthernetTap.hpp | 91 | ||||
-rw-r--r-- | osdep/OSXEthernetTapFactory.cpp | 122 | ||||
-rw-r--r-- | osdep/OSXEthernetTapFactory.hpp | 76 | ||||
-rw-r--r-- | osdep/Phy.hpp | 817 | ||||
-rw-r--r-- | osdep/README.md | 6 | ||||
-rw-r--r-- | osdep/WindowsEthernetTap.cpp | 867 | ||||
-rw-r--r-- | osdep/WindowsEthernetTap.hpp | 115 | ||||
-rw-r--r-- | osdep/WindowsEthernetTapFactory.cpp | 162 | ||||
-rw-r--r-- | osdep/WindowsEthernetTapFactory.hpp | 90 |
20 files changed, 5391 insertions, 0 deletions
diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp new file mode 100644 index 00000000..e9899bc4 --- /dev/null +++ b/osdep/BSDEthernetTap.cpp @@ -0,0 +1,429 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <unistd.h> +#include <signal.h> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/select.h> +#include <sys/cdefs.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <ifaddrs.h> +#include <net/if_arp.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/route.h> + +#include <string> +#include <map> +#include <set> +#include <algorithm> + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "BSDEthernetTap.hpp" + +#define ZT_BASE32_CHARS "0123456789abcdefghijklmnopqrstuv" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +namespace ZeroTier { + +BSDEthernetTap::BSDEthernetTap( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) : + EthernetTap("BSDEthernetTap",mac,mtu,metric), + _handler(handler), + _arg(arg), + _mtu(mtu), + _metric(metric), + _fd(0), + _enabled(true) +{ + static Mutex globalTapCreateLock; + char devpath[64],ethaddr[64],mtustr[32],metstr[32],tmpdevname[32]; + struct stat stattmp; + + // On FreeBSD at least we can rename, so use nwid to generate a deterministic unique zt#### name using base32 + // As a result we don't use desiredDevice + _dev = "zt"; + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 60) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 55) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 50) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 45) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 40) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 35) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 30) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 25) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 20) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 15) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 10) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 5) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)(nwid & 0x1f)]); + + Mutex::Lock _gl(globalTapCreateLock); + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + + // On BSD we create taps and they can have high numbers, so use ones starting + // at 9993 to not conflict with other stuff. Then we rename it to zt<base32 of nwid> + std::map<std::string,bool> devFiles(Utils::listDirectory("/dev")); + for(int i=9993;i<(9993+128);++i) { + Utils::snprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i); + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname); + if (devFiles.count(std::string(tmpdevname)) == 0) { + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",tmpdevname,"create",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + } else throw std::runtime_error("fork() failed"); + + if (!stat(devpath,&stattmp)) { + cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",tmpdevname,"name",_dev.c_str(),(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode) + throw std::runtime_error("ifconfig rename operation failed"); + } else throw std::runtime_error("fork() failed"); + + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) + break; + else throw std::runtime_error("unable to open created tap device"); + } else { + throw std::runtime_error("cannot find /dev node for newly created tap device"); + } + } + } + + 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"); + } + + // 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); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + 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"); + } + } + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + ::pipe(_shutdownSignalPipe); + + _thread = Thread::start(this); +} + +BSDEthernetTap::~BSDEthernetTap() +{ + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + } +} + +void BSDEthernetTap::setEnabled(bool en) +{ + _enabled = en; + // TODO: interface status change +} + +bool BSDEthernetTap::enabled() const +{ + return _enabled; +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; // never reached, make compiler shut up about return value +} + +bool BSDEthernetTap::addIP(const InetAddress &ip) +{ + if (!ip) + return false; + + std::set<InetAddress> 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<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) + break; + } + } + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; +} + +bool BSDEthernetTap::removeIP(const InetAddress &ip) +{ + if (ips().count(ip) > 0) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::set<InetAddress> BSDEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::set<InetAddress>(); + + std::set<InetAddress> r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(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 BSDEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[4096]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + ::write(_fd,putBuf,len); + } +} + +std::string BSDEthernetTap::deviceName() const +{ + return _dev; +} + +void BSDEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +bool BSDEthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups) +{ + std::set<MulticastGroup> newGroups; + struct ifmaddrs *ifmap = (struct ifmaddrs *)0; + if (!getifmaddrs(&ifmap)) { + struct 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 <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.insert(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + freeifmaddrs(ifmap); + } + + { + std::set<InetAddress> allIps(ips()); + for(std::set<InetAddress>::const_iterator i(allIps.begin());i!=allIps.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +bool BSDEthernetTap::injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + return false; +} + +void BSDEthernetTap::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)) + 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; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + data.copyFrom(getBuf + 14,(unsigned int)r - 14); + _handler(_arg,from,to,etherType,data); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp new file mode 100644 index 00000000..582292a1 --- /dev/null +++ b/osdep/BSDEthernetTap.hpp @@ -0,0 +1,84 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_BSDETHERNETTAP_HPP +#define ZT_BSDETHERNETTAP_HPP + +#include <stdio.h> +#include <stdlib.h> + +#include <stdexcept> + +#include "../node/EthernetTap.hpp" +#include "../node/Thread.hpp" + +namespace ZeroTier { + +class BSDEthernetTap : public EthernetTap +{ +public: + BSDEthernetTap( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + + virtual ~BSDEthernetTap(); + + virtual void setEnabled(bool en); + virtual bool enabled() const; + virtual bool addIP(const InetAddress &ip); + virtual bool removeIP(const InetAddress &ip); + virtual std::set<InetAddress> 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 void setFriendlyName(const char *friendlyName); + virtual bool updateMulticastGroups(std::set<MulticastGroup> &groups); + virtual bool injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + + void threadMain() + throw(); + +private: + void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); + void *_arg; + Thread _thread; + std::string _dev; + unsigned int _mtu; + unsigned int _metric; + int _fd; + int _shutdownSignalPipe[2]; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/BSDEthernetTapFactory.cpp b/osdep/BSDEthernetTapFactory.cpp new file mode 100644 index 00000000..79ae73f7 --- /dev/null +++ b/osdep/BSDEthernetTapFactory.cpp @@ -0,0 +1,78 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "BSDEthernetTapFactory.hpp" +#include "BSDEthernetTap.hpp" + +namespace ZeroTier { + +BSDEthernetTapFactory::BSDEthernetTapFactory() +{ +} + +BSDEthernetTapFactory::~BSDEthernetTapFactory() +{ + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) + delete *d; +} + +EthernetTap *BSDEthernetTapFactory::open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) +{ + Mutex::Lock _l(_devices_m); + EthernetTap *t = new BSDEthernetTap(mac,mtu,metric,nwid,desiredDevice,friendlyName,handler,arg); + _devices.push_back(t); + return t; +} + +void BSDEthernetTapFactory::close(EthernetTap *tap,bool destroyPersistentDevices) +{ + { + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) { + if (*d == tap) { + _devices.erase(d); + break; + } + } + } + delete tap; +} + +} // namespace ZeroTier diff --git a/osdep/BSDEthernetTapFactory.hpp b/osdep/BSDEthernetTapFactory.hpp new file mode 100644 index 00000000..63e77339 --- /dev/null +++ b/osdep/BSDEthernetTapFactory.hpp @@ -0,0 +1,63 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_BSDETHERNETTAPFACTORY_HPP +#define ZT_BSDETHERNETTAPFACTORY_HPP + +#include <vector> +#include <string> + +#include "../node/EthernetTapFactory.hpp" +#include "../node/Mutex.hpp" + +namespace ZeroTier { + +class BSDEthernetTapFactory : public EthernetTapFactory +{ +public: + BSDEthernetTapFactory(); + virtual ~BSDEthernetTapFactory(); + + virtual EthernetTap *open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + virtual void close(EthernetTap *tap,bool destroyPersistentDevices); + +private: + std::vector<EthernetTap *> _devices; + Mutex _devices_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp new file mode 100644 index 00000000..ee89159e --- /dev/null +++ b/osdep/LinuxEthernetTap.cpp @@ -0,0 +1,424 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/select.h> +#include <netinet/in.h> +#include <net/if_arp.h> +#include <arpa/inet.h> +#include <linux/if.h> +#include <linux/if_tun.h> +#include <linux/if_addr.h> +#include <linux/if_ether.h> +#include <ifaddrs.h> + +#include <string> +#include <map> +#include <set> +#include <algorithm> + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "LinuxEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +namespace ZeroTier { + +static Mutex __tapCreateLock; + +LinuxEthernetTap::LinuxEthernetTap( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) : + EthernetTap("LinuxEthernetTap",mac,mtu,metric), + _handler(handler), + _arg(arg), + _fd(0), + _enabled(true) +{ + char procpath[128]; + struct stat sbuf; + + Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + + _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 ((desiredDevice)&&(desiredDevice[0])) { + Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),desiredDevice); + 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"); + } + + _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; + mac.copyTo(ifr.ifr_ifru.ifru_hwaddr.sa_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); + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + ::fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + ::pipe(_shutdownSignalPipe); + + _thread = Thread::start(this); +} + +LinuxEthernetTap::~LinuxEthernetTap() +{ + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); +} + +void LinuxEthernetTap::setEnabled(bool en) +{ + _enabled = en; + // TODO: interface status change +} + +bool LinuxEthernetTap::enabled() const +{ + return _enabled; +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + Utils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/ip","/sbin/ip","addr","del",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + ::execl("/usr/sbin/ip","/usr/sbin/ip","addr","del",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + ::_exit(-1); + } else { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } +} + +bool LinuxEthernetTap::addIP(const InetAddress &ip) +{ + if (!ip) + return false; + std::set<InetAddress> allIps(ips()); + if (allIps.count(ip) > 0) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::set<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) { + if (i->ipsEqual(ip)) + ___removeIp(_dev,*i); + } + + long cpid = (long)vfork(); + if (cpid == 0) { + Utils::redirectUnixOutputs("/dev/null",(const char *)0); + if (ip.isV4()) { + ::execl("/sbin/ip","/sbin/ip","addr","add",ip.toString().c_str(),"broadcast",ip.broadcast().toIpString().c_str(),"dev",_dev.c_str(),(const char *)0); + ::execl("/usr/sbin/ip","/usr/sbin/ip","addr","add",ip.toString().c_str(),"broadcast",ip.broadcast().toIpString().c_str(),"dev",_dev.c_str(),(const char *)0); + } else { + ::execl("/sbin/ip","/sbin/ip","addr","add",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + ::execl("/usr/sbin/ip","/usr/sbin/ip","addr","add",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + } + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + + return false; +} + +bool LinuxEthernetTap::removeIP(const InetAddress &ip) +{ + if (ips().count(ip) > 0) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::set<InetAddress> LinuxEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::set<InetAddress>(); + + std::set<InetAddress> r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(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 LinuxEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[8194]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + ::write(_fd,putBuf,len); + } +} + +std::string LinuxEthernetTap::deviceName() const +{ + return _dev; +} + +void LinuxEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +bool LinuxEthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups) +{ + char *ptr,*ptr2; + unsigned char mac[6]; + std::set<MulticastGroup> 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.c_str()))&&(mcastmac)&&(Utils::unhex(mcastmac,mac,6) == 6)) + newGroups.insert(MulticastGroup(MAC(mac,6),0)); + } + } + ::close(fd); + } + + { + std::set<InetAddress> allIps(ips()); + for(std::set<InetAddress>::const_iterator i(allIps.begin());i!=allIps.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +bool LinuxEthernetTap::injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + return false; +} + +void LinuxEthernetTap::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)) + 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; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + data.copyFrom(getBuf + 14,(unsigned int)r - 14); + _handler(_arg,from,to,etherType,data); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp new file mode 100644 index 00000000..31980ea0 --- /dev/null +++ b/osdep/LinuxEthernetTap.hpp @@ -0,0 +1,85 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_LINUXETHERNETTAP_HPP +#define ZT_LINUXETHERNETTAP_HPP + +#include <stdio.h> +#include <stdlib.h> + +#include <stdexcept> + +#include "../node/EthernetTap.hpp" +#include "../node/Thread.hpp" + +namespace ZeroTier { + +/** + * Linux Ethernet tap using kernel tun/tap driver + */ +class LinuxEthernetTap : public EthernetTap +{ +public: + LinuxEthernetTap( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + + virtual ~LinuxEthernetTap(); + + virtual void setEnabled(bool en); + virtual bool enabled() const; + virtual bool addIP(const InetAddress &ip); + virtual bool removeIP(const InetAddress &ip); + virtual std::set<InetAddress> 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 void setFriendlyName(const char *friendlyName); + virtual bool updateMulticastGroups(std::set<MulticastGroup> &groups); + virtual bool injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + + void threadMain() + throw(); + +private: + void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); + void *_arg; + Thread _thread; + std::string _dev; + int _fd; + int _shutdownSignalPipe[2]; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/LinuxEthernetTapFactory.cpp b/osdep/LinuxEthernetTapFactory.cpp new file mode 100644 index 00000000..014c6514 --- /dev/null +++ b/osdep/LinuxEthernetTapFactory.cpp @@ -0,0 +1,74 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "LinuxEthernetTapFactory.hpp" +#include "LinuxEthernetTap.hpp" + +namespace ZeroTier { + +LinuxEthernetTapFactory::LinuxEthernetTapFactory() +{ +} + +LinuxEthernetTapFactory::~LinuxEthernetTapFactory() +{ + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) + delete *d; +} + +EthernetTap *LinuxEthernetTapFactory::open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) +{ + Mutex::Lock _l(_devices_m); + EthernetTap *t = new LinuxEthernetTap(mac,mtu,metric,nwid,desiredDevice,friendlyName,handler,arg); + _devices.push_back(t); + return t; +} + +void LinuxEthernetTapFactory::close(EthernetTap *tap,bool destroyPersistentDevices) +{ + { + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) { + if (*d == tap) { + _devices.erase(d); + break; + } + } + } + delete tap; +} + +} // namespace ZeroTier diff --git a/osdep/LinuxEthernetTapFactory.hpp b/osdep/LinuxEthernetTapFactory.hpp new file mode 100644 index 00000000..e61863ed --- /dev/null +++ b/osdep/LinuxEthernetTapFactory.hpp @@ -0,0 +1,63 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_LINUXETHERNETTAPFACTORY_HPP +#define ZT_LINUXETHERNETTAPFACTORY_HPP + +#include <vector> +#include <string> + +#include "../node/EthernetTapFactory.hpp" +#include "../node/Mutex.hpp" + +namespace ZeroTier { + +class LinuxEthernetTapFactory : public EthernetTapFactory +{ +public: + LinuxEthernetTapFactory(); + virtual ~LinuxEthernetTapFactory(); + + virtual EthernetTap *open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + virtual void close(EthernetTap *tap,bool destroyPersistentDevices); + +private: + std::vector<EthernetTap *> _devices; + Mutex _devices_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/NativeSocketManager.cpp b/osdep/NativeSocketManager.cpp new file mode 100644 index 00000000..797764ef --- /dev/null +++ b/osdep/NativeSocketManager.cpp @@ -0,0 +1,989 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +/* Native SocketManager for Windows and Unix */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <time.h> +#include <sys/types.h> + +#include <algorithm> + +#include "../node/Constants.hpp" +#include "NativeSocketManager.hpp" + +#ifndef __WINDOWS__ +#include <errno.h> +#include <unistd.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <signal.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#endif // !__WINDOWS__ + +// Uncomment to turn off TCP Nagle +//#define ZT_TCP_NODELAY + +// Allow us to use the same value on Windows and *nix +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif + +#ifdef __WINDOWS__ +#define CLOSE_SOCKET(s) ::closesocket(s) +#else +#define CLOSE_SOCKET(s) ::close(s) +#endif + +namespace ZeroTier { + +////////////////////////////////////////////////////////////////////////////// +// Socket implementations +////////////////////////////////////////////////////////////////////////////// + +class NativeSocket : public Socket +{ +public: +#ifdef __WINDOWS__ + NativeSocket(const Type &t,SOCKET s) : Socket(t),_sock(s) {} + SOCKET _sock; +#else + NativeSocket(const Type &t,int s) : Socket(t),_sock(s) {} + int _sock; +#endif +}; + +/** + * Native UDP socket + */ +class NativeUdpSocket : public NativeSocket +{ +public: +#ifdef __WINDOWS__ + NativeUdpSocket(Type t,SOCKET s) : NativeSocket(t,s) {} +#else + NativeUdpSocket(Type t,int s) : NativeSocket(t,s) {} +#endif + + virtual ~NativeUdpSocket() + { +#ifdef __WINDOWS__ + ::closesocket(_sock); +#else + ::close(_sock); +#endif + } + + virtual bool send(const InetAddress &to,const void *msg,unsigned int msglen) + { + if (to.isV6()) { +#ifdef __WINDOWS__ + return ((int)sendto(_sock,(const char *)msg,msglen,0,to.saddr(),to.saddrLen()) == (int)msglen); +#else + return ((int)sendto(_sock,msg,msglen,0,to.saddr(),to.saddrLen()) == (int)msglen); +#endif + } else { +#ifdef __WINDOWS__ + return ((int)sendto(_sock,(const char *)msg,msglen,0,to.saddr(),to.saddrLen()) == (int)msglen); +#else + return ((int)sendto(_sock,msg,msglen,0,to.saddr(),to.saddrLen()) == (int)msglen); +#endif + } + } + + inline bool notifyAvailableForRead(const SharedPtr<Socket> &self,NativeSocketManager *sm,void (*handler)(const SharedPtr<Socket> &,void *,const InetAddress &,Buffer<ZT_SOCKET_MAX_MESSAGE_LEN> &),void *arg) + { + Buffer<ZT_SOCKET_MAX_MESSAGE_LEN> buf; + InetAddress from; + socklen_t salen = from.saddrSpaceLen(); + int n = (int)recvfrom(_sock,(char *)(buf.data()),ZT_SOCKET_MAX_MESSAGE_LEN,0,from.saddr(),&salen); + if (n > 0) { + buf.setSize((unsigned int)n); + try { + handler(self,arg,from,buf); + } catch ( ... ) {} // handlers should not throw + } + return true; + } + + inline bool notifyAvailableForWrite(const SharedPtr<Socket> &self,NativeSocketManager *sm) + { + return true; + } +}; + +/** + * A TCP socket encapsulating ZeroTier packets over a TCP stream connection + * + * This implements a simple packet encapsulation that is designed to look like + * a TLS connection. It's not a TLS connection, but it sends TLS format record + * headers. It could be extended in the future to implement a fake TLS + * handshake. + * + * At the moment, each packet is just made to look like TLS application data: + * <[1] TLS content type> - currently 0x17 for "application data" + * <[1] TLS major version> - currently 0x03 for TLS 1.2 + * <[1] TLS minor version> - currently 0x03 for TLS 1.2 + * <[2] payload length> - 16-bit length of payload in bytes + * <[...] payload> - Message payload + * + * The primary purpose of TCP sockets is to work over ports like HTTPS(443), + * allowing users behind particularly fascist firewalls to at least reach + * ZeroTier's supernodes. UDP is the preferred method of communication as + * encapsulating L2 and L3 protocols over TCP is inherently inefficient + * due to double-ACKs. So TCP is only used as a fallback. + */ +class NativeTcpSocket : public NativeSocket +{ +public: +#ifdef __WINDOWS__ + NativeTcpSocket(NativeSocketManager *sm,SOCKET s,Socket::Type t,bool c,const InetAddress &r) : +#else + NativeTcpSocket(NativeSocketManager *sm,int s,Socket::Type t,bool c,const InetAddress &r) : +#endif + NativeSocket(t,s), + _lastActivity(Utils::now()), + _sm(sm), + _inptr(0), + _outptr(0), + _connecting(c), + _remote(r) {} + + virtual ~NativeTcpSocket() + { +#ifdef __WINDOWS__ + ::closesocket(_sock); +#else + ::close(_sock); +#endif + } + + virtual bool send(const InetAddress &to,const void *msg,unsigned int msglen) + { + if (msglen > ZT_SOCKET_MAX_MESSAGE_LEN) + return false; // message too big + if (!msglen) + return true; // sanity check + + Mutex::Lock _l(_writeLock); + + bool writeInProgress = ((_outptr != 0)||(_connecting)); + + if ((_outptr + 5 + msglen) > (unsigned int)sizeof(_outbuf)) + return false; + + _outbuf[_outptr++] = 0x17; // look like TLS data + _outbuf[_outptr++] = 0x03; + _outbuf[_outptr++] = 0x03; // look like TLS 1.2 + _outbuf[_outptr++] = (unsigned char)((msglen >> 8) & 0xff); + _outbuf[_outptr++] = (unsigned char)(msglen & 0xff); + for(unsigned int i=0;i<msglen;++i) + _outbuf[_outptr++] = ((const unsigned char *)msg)[i]; + + if (!writeInProgress) { + // If no output was enqueued before this, try to send() it and then + // start a queued write if any remains after that. + + int n = (int)::send(_sock,(const char *)_outbuf,_outptr,0); + if (n > 0) + memmove(_outbuf,_outbuf + (unsigned int)n,_outptr -= (unsigned int)n); + + if (_outptr) { + _sm->_startNotifyWrite(this); + _sm->whack(); + } + } // else just leave in _outbuf[] to get written when stream is available for write + + return true; + } + + inline bool notifyAvailableForRead(const SharedPtr<Socket> &self,NativeSocketManager *sm,void (*handler)(const SharedPtr<Socket> &,void *,const InetAddress &,Buffer<ZT_SOCKET_MAX_MESSAGE_LEN> &),void *arg) + { + unsigned char buf[65536]; + + int n = (int)::recv(_sock,(char *)buf,sizeof(buf),0); + if (n <= 0) + return false; // read error, stream probably closed + + unsigned int p = _inptr,pl = 0; + for(int k=0;k<n;++k) { + _inbuf[p++] = buf[k]; + if (p >= (int)sizeof(_inbuf)) + return false; // read overrun, packet too large or invalid + + if ((!pl)&&(p >= 5)) { + if (_inbuf[0] == 0x17) { + // fake TLS data frame, next two bytes are TLS version and are ignored + pl = (((unsigned int)_inbuf[3] << 8) | (unsigned int)_inbuf[4]) + 5; + } else return false; // in the future we may support fake TLS handshakes + } + + if ((pl)&&(p >= pl)) { + Buffer<ZT_SOCKET_MAX_MESSAGE_LEN> data(_inbuf + 5,pl - 5); + memmove(_inbuf,_inbuf + pl,p -= pl); + try { + handler(self,arg,_remote,data); + } catch ( ... ) {} // handlers should not throw + pl = 0; + } + } + _inptr = p; + + return true; + } + + inline bool notifyAvailableForWrite(const SharedPtr<Socket> &self,NativeSocketManager *sm) + { + Mutex::Lock _l(_writeLock); + + if (_connecting) + _connecting = false; + + if (_outptr) { + int n = (int)::send(_sock,(const char *)_outbuf,_outptr,0); +#ifdef __WINDOWS__ + if (n == SOCKET_ERROR) { + switch(WSAGetLastError()) { + case WSAEINTR: + case WSAEWOULDBLOCK: + break; + default: + return false; + } +#else + if (n <= 0) { + switch(errno) { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && ( !defined(EAGAIN) || (EWOULDBLOCK != EAGAIN) ) + case EWOULDBLOCK: +#endif +#ifdef EINTR + case EINTR: +#endif + break; + default: + return false; + } +#endif + } else memmove(_outbuf,_outbuf + (unsigned int)n,_outptr -= (unsigned int)n); + } + + if (!_outptr) + sm->_stopNotifyWrite(this); + + return true; + } + + unsigned char _inbuf[ZT_SOCKET_MAX_MESSAGE_LEN]; + unsigned char _outbuf[ZT_SOCKET_MAX_MESSAGE_LEN * 4]; + uint64_t _lastActivity; // updated whenever data is received, checked directly by SocketManager for stale TCP cleanup + NativeSocketManager *_sm; + unsigned int _inptr; + unsigned int _outptr; + bool _connecting; // manipulated directly by SocketManager, true if connect() is in progress + InetAddress _remote; + Mutex _writeLock; +}; + +////////////////////////////////////////////////////////////////////////////// + +#ifdef __WINDOWS__ +// hack copied from StackOverflow, behaves a bit like pipe() on *nix systems +static inline void winPipeHack(SOCKET fds[2]) +{ + struct sockaddr_in inaddr; + struct sockaddr addr; + SOCKET lst=::socket(AF_INET, SOCK_STREAM,IPPROTO_TCP); + memset(&inaddr, 0, sizeof(inaddr)); + memset(&addr, 0, sizeof(addr)); + inaddr.sin_family = AF_INET; + inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + inaddr.sin_port = 0; + int yes=1; + setsockopt(lst,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); + bind(lst,(struct sockaddr *)&inaddr,sizeof(inaddr)); + listen(lst,1); + int len=sizeof(inaddr); + getsockname(lst, &addr,&len); + fds[0]=::socket(AF_INET, SOCK_STREAM,0); + connect(fds[0],&addr,len); + fds[1]=accept(lst,0,0); + closesocket(lst); +} +#endif + +NativeSocketManager::NativeSocketManager(int localUdpPort,int localTcpPort) : + SocketManager(), + _whackSendPipe(INVALID_SOCKET), + _whackReceivePipe(INVALID_SOCKET), + _tcpV4ListenSocket(INVALID_SOCKET), + _tcpV6ListenSocket(INVALID_SOCKET), + _nfds(0) +{ + FD_ZERO(&_readfds); + FD_ZERO(&_writefds); + + // Create a pipe or socket pair that can be used to interrupt select() +#ifdef __WINDOWS__ + { + SOCKET tmps[2] = { INVALID_SOCKET,INVALID_SOCKET }; + winPipeHack(tmps); + _whackSendPipe = tmps[0]; + _whackReceivePipe = tmps[1]; + u_long iMode=1; + ioctlsocket(tmps[1],FIONBIO,&iMode); + } +#else + { + int tmpfds[2]; + if (::pipe(tmpfds)) + throw std::runtime_error("pipe() failed"); + _whackSendPipe = tmpfds[1]; + _whackReceivePipe = tmpfds[0]; + fcntl(_whackReceivePipe,F_SETFL,O_NONBLOCK); + } +#endif + FD_SET(_whackReceivePipe,&_readfds); + + if (localTcpPort > 0) { + if (localTcpPort > 0xffff) { + _closeSockets(); + throw std::runtime_error("invalid local TCP port number"); + } + + { // bind TCP IPv6 + _tcpV6ListenSocket = ::socket(AF_INET6,SOCK_STREAM,0); +#ifdef __WINDOWS__ + if (_tcpV6ListenSocket != INVALID_SOCKET) { + { + BOOL f; + f = TRUE; ::setsockopt(_tcpV6ListenSocket,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = TRUE; ::setsockopt(_tcpV6ListenSocket,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + u_long iMode=1; + ioctlsocket(_tcpV6ListenSocket,FIONBIO,&iMode); + } +#else + if (_tcpV6ListenSocket > 0) { + { + int f; + f = 1; ::setsockopt(_tcpV6ListenSocket,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); + f = 1; ::setsockopt(_tcpV6ListenSocket,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + fcntl(_tcpV6ListenSocket,F_SETFL,O_NONBLOCK); + } +#endif // __WINDOWS__ / not __WINDOWS__ + + struct sockaddr_in6 sin6; + memset(&sin6,0,sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(localTcpPort); + memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr)); + if (::bind(_tcpV6ListenSocket,(const struct sockaddr *)&sin6,sizeof(sin6))) { + _closeSockets(); + throw std::runtime_error("unable to bind to local TCP port"); + } + + if (::listen(_tcpV6ListenSocket,1024)) { + _closeSockets(); + throw std::runtime_error("listen() failed"); + } + + FD_SET(_tcpV6ListenSocket,&_readfds); + } + } + + { // bind TCP IPv4 + _tcpV4ListenSocket = ::socket(AF_INET,SOCK_STREAM,0); +#ifdef __WINDOWS__ + if (_tcpV4ListenSocket == INVALID_SOCKET) { +#else + if (_tcpV4ListenSocket <= 0) { +#endif + _closeSockets(); + throw std::runtime_error("unable to create IPv4 SOCK_STREAM socket"); + } + +#ifdef __WINDOWS__ + { + BOOL f = TRUE; ::setsockopt(_tcpV4ListenSocket,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + u_long iMode=1; + ioctlsocket(_tcpV4ListenSocket,FIONBIO,&iMode); + } +#else + { + int f = 1; ::setsockopt(_tcpV4ListenSocket,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + fcntl(_tcpV4ListenSocket,F_SETFL,O_NONBLOCK); + } +#endif + + struct sockaddr_in sin4; + memset(&sin4,0,sizeof(sin4)); + sin4.sin_family = AF_INET; + sin4.sin_port = htons(localTcpPort); + sin4.sin_addr.s_addr = INADDR_ANY; + if (::bind(_tcpV4ListenSocket,(const struct sockaddr *)&sin4,sizeof(sin4))) { + _closeSockets(); + throw std::runtime_error("unable to bind to local TCP port"); + } + + if (::listen(_tcpV4ListenSocket,1024)) { + _closeSockets(); + throw std::runtime_error("listen() failed"); + } + + FD_SET(_tcpV4ListenSocket,&_readfds); + } + } + + if (localUdpPort > 0) { + if (localUdpPort > 0xffff) { + _closeSockets(); + throw std::runtime_error("invalid local UDP port number"); + } + + { // bind UDP IPv6 +#ifdef __WINDOWS__ + SOCKET s = ::socket(AF_INET6,SOCK_DGRAM,0); + if (s != INVALID_SOCKET) { +#else + int s = ::socket(AF_INET6,SOCK_DGRAM,0); + if (s > 0) { +#endif + + { + int bs = 1048576; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + bs = 1048576; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } +#ifdef __WINDOWS__ + BOOL f; + f = TRUE; setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = FALSE; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = FALSE; setsockopt(s,IPPROTO_IPV6,IPV6_DONTFRAG,(const char *)&f,sizeof(f)); + f = TRUE; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char *)&f,sizeof(f)); +#else + int f; + f = 1; setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); + f = 0; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = 1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(void *)&f,sizeof(f)); +#ifdef IP_DONTFRAG + f = 0; setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&f,sizeof(f)); +#endif +#ifdef IP_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IP,IP_MTU_DISCOVER,&f,sizeof(f)); +#endif +#ifdef IPV6_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IPV6,IPV6_MTU_DISCOVER,&f,sizeof(f)); +#endif +#endif + } + + struct sockaddr_in6 sin6; + memset(&sin6,0,sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(localUdpPort); + memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr)); + if (::bind(s,(const struct sockaddr *)&sin6,sizeof(sin6))) { + CLOSE_SOCKET(s); + _closeSockets(); + throw std::runtime_error("unable to bind to port"); + } + + _udpV6Socket = SharedPtr<Socket>(new NativeUdpSocket(Socket::ZT_SOCKET_TYPE_UDP_V6,s)); +#ifdef __WINDOWS__ + u_long iMode=1; + ioctlsocket(s,FIONBIO,&iMode); +#else + fcntl(s,F_SETFL,O_NONBLOCK); +#endif + FD_SET(s,&_readfds); + } + } + + { // bind UDP IPv4 +#ifdef __WINDOWS__ + SOCKET s = ::socket(AF_INET,SOCK_DGRAM,0); + if (s == INVALID_SOCKET) { + _closeSockets(); + throw std::runtime_error("unable to create IPv4 SOCK_DGRAM socket"); + } +#else + int s = ::socket(AF_INET,SOCK_DGRAM,0); + if (s <= 0) { + _closeSockets(); + throw std::runtime_error("unable to create IPv4 SOCK_DGRAM socket"); + } +#endif + + { + int bs = 1048576; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + bs = 1048576; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } +#ifdef __WINDOWS__ + BOOL f; + f = FALSE; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = FALSE; setsockopt(s,IPPROTO_IP,IP_DONTFRAGMENT,(const char *)&f,sizeof(f)); + f = TRUE; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char *)&f,sizeof(f)); +#else + int f; + f = 0; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = 1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(void *)&f,sizeof(f)); +#ifdef IP_DONTFRAG + f = 0; setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&f,sizeof(f)); +#endif +#ifdef IP_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IP,IP_MTU_DISCOVER,&f,sizeof(f)); +#endif +#endif + } + + struct sockaddr_in sin4; + memset(&sin4,0,sizeof(sin4)); + sin4.sin_family = AF_INET; + sin4.sin_port = htons(localUdpPort); + sin4.sin_addr.s_addr = INADDR_ANY; + if (::bind(s,(const struct sockaddr *)&sin4,sizeof(sin4))) { + CLOSE_SOCKET(s); + _closeSockets(); + throw std::runtime_error("unable to bind to port"); + } + + _udpV4Socket = SharedPtr<Socket>(new NativeUdpSocket(Socket::ZT_SOCKET_TYPE_UDP_V4,s)); +#ifdef __WINDOWS__ + u_long iMode=1; + ioctlsocket(s,FIONBIO,&iMode); +#else + fcntl(s,F_SETFL,O_NONBLOCK); +#endif + FD_SET(s,&_readfds); + } + } + + _updateNfds(); +} + +NativeSocketManager::~NativeSocketManager() +{ + Mutex::Lock _l(_pollLock); + _closeSockets(); +} + +bool NativeSocketManager::send(const InetAddress &to,bool tcp,bool autoConnectTcp,const void *msg,unsigned int msglen) +{ + if (tcp) { + SharedPtr<Socket> ts; + { + Mutex::Lock _l(_tcpSockets_m); + std::map< InetAddress,SharedPtr<Socket> >::iterator opents(_tcpSockets.find(to)); + if (opents != _tcpSockets.end()) + ts = opents->second; + } + if (ts) + return ts->send(to,msg,msglen); + + if (!autoConnectTcp) + return false; + +#ifdef __WINDOWS__ + SOCKET s = ::socket(to.isV4() ? AF_INET : AF_INET6,SOCK_STREAM,0); + if (s == INVALID_SOCKET) + return false; + { u_long iMode=1; ioctlsocket(s,FIONBIO,&iMode); } +#ifdef ZT_TCP_NODELAY + { BOOL f = TRUE; setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } +#endif +#else + int s = ::socket(to.isV4() ? AF_INET : AF_INET6,SOCK_STREAM,0); + if (s <= 0) + return false; + if (s >= FD_SETSIZE) { + ::close(s); + return false; + } + fcntl(s,F_SETFL,O_NONBLOCK); +#ifdef ZT_TCP_NODELAY + { int f = 1; setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } +#endif +#endif + + bool connecting = false; + if (::connect(s,to.saddr(),to.saddrLen())) { +#ifdef __WINDOWS__ + if (WSAGetLastError() != WSAEWOULDBLOCK) { +#else + if (errno != EINPROGRESS) { +#endif + CLOSE_SOCKET(s); + return false; + } else connecting = true; + } + + ts = SharedPtr<Socket>(new NativeTcpSocket(this,s,Socket::ZT_SOCKET_TYPE_TCP_OUT,connecting,to)); + if (!ts->send(to,msg,msglen)) { + _fdSetLock.lock(); + FD_CLR(s,&_readfds); + FD_CLR(s,&_writefds); + _fdSetLock.unlock(); + return false; + } + + { + Mutex::Lock _l(_tcpSockets_m); + _tcpSockets[to] = ts; + } + + _fdSetLock.lock(); + FD_SET(s,&_readfds); + if (connecting) + FD_SET(s,&_writefds); + _fdSetLock.unlock(); + + _updateNfds(); + whack(); + + return true; + } else if (to.isV4()) { + if (_udpV4Socket) + return _udpV4Socket->send(to,msg,msglen); + } else if (to.isV6()) { + if (_udpV6Socket) + return _udpV6Socket->send(to,msg,msglen); + } + return false; +} + +void NativeSocketManager::poll(unsigned long timeout,void (*handler)(const SharedPtr<Socket> &,void *,const InetAddress &,Buffer<ZT_SOCKET_MAX_MESSAGE_LEN> &),void *arg) +{ + fd_set rfds,wfds,efds; + struct timeval tv; + std::vector< SharedPtr<Socket> > ts; +#ifdef __WINDOWS__ + SOCKET sockfd; +#else + int sockfd; +#endif + + Mutex::Lock _l(_pollLock); + + _fdSetLock.lock(); + memcpy(&rfds,&_readfds,sizeof(rfds)); + memcpy(&wfds,&_writefds,sizeof(wfds)); + _fdSetLock.unlock(); + FD_ZERO(&efds); + +#ifdef __WINDOWS__ + // Windows signals failed connects in exceptfds + { + Mutex::Lock _l2(_tcpSockets_m); + for(std::map< InetAddress,SharedPtr<Socket> >::iterator s(_tcpSockets.begin());s!=_tcpSockets.end();++s) { + if (((NativeTcpSocket *)s->second.ptr())->_connecting) + FD_SET(((NativeTcpSocket *)s->second.ptr())->_sock,&efds); + } + } +#endif + + tv.tv_sec = (long)(timeout / 1000); + tv.tv_usec = (long)((timeout % 1000) * 1000); + select(_nfds + 1,&rfds,&wfds,&efds,(timeout > 0) ? &tv : (struct timeval *)0); + + if (FD_ISSET(_whackReceivePipe,&rfds)) { + char tmp[16]; +#ifdef __WINDOWS__ + ::recv(_whackReceivePipe,tmp,16,0); +#else + ::read(_whackReceivePipe,tmp,16); +#endif + } + + if ((_tcpV4ListenSocket != INVALID_SOCKET)&&(FD_ISSET(_tcpV4ListenSocket,&rfds))) { + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + sockfd = accept(_tcpV4ListenSocket,(struct sockaddr *)&from,&fromlen); +#ifdef __WINDOWS__ + if (sockfd != INVALID_SOCKET) { +#else + if (sockfd > 0) { + if (sockfd < FD_SETSIZE) { +#endif + InetAddress fromia((const struct sockaddr *)&from); + Mutex::Lock _l2(_tcpSockets_m); + try { + _tcpSockets[fromia] = SharedPtr<Socket>(new NativeTcpSocket(this,sockfd,Socket::ZT_SOCKET_TYPE_TCP_IN,false,fromia)); +#ifdef __WINDOWS__ + { u_long iMode=1; ioctlsocket(sockfd,FIONBIO,&iMode); } +#ifdef ZT_TCP_NODELAY + { BOOL f = TRUE; setsockopt(sockfd,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } +#endif +#else + fcntl(sockfd,F_SETFL,O_NONBLOCK); +#ifdef ZT_TCP_NODELAY + { int f = 1; setsockopt(sockfd,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } +#endif +#endif + _fdSetLock.lock(); + FD_SET(sockfd,&_readfds); + _fdSetLock.unlock(); + if ((int)sockfd > (int)_nfds) + _nfds = (int)sockfd; + } catch ( ... ) { + CLOSE_SOCKET(sockfd); + } +#ifndef __WINDOWS__ + } else { + CLOSE_SOCKET(sockfd); + } +#endif + } + } + if ((_tcpV6ListenSocket != INVALID_SOCKET)&&(FD_ISSET(_tcpV6ListenSocket,&rfds))) { + struct sockaddr_in6 from; + socklen_t fromlen = sizeof(from); + sockfd = accept(_tcpV6ListenSocket,(struct sockaddr *)&from,&fromlen); +#ifdef __WINDOWS__ + if (sockfd != INVALID_SOCKET) { +#else + if (sockfd > 0) { + if (sockfd < FD_SETSIZE) { +#endif + InetAddress fromia((const struct sockaddr *)&from); + Mutex::Lock _l2(_tcpSockets_m); + try { + _tcpSockets[fromia] = SharedPtr<Socket>(new NativeTcpSocket(this,sockfd,Socket::ZT_SOCKET_TYPE_TCP_IN,false,fromia)); +#ifdef __WINDOWS__ + { u_long iMode=1; ioctlsocket(sockfd,FIONBIO,&iMode); } +#ifdef ZT_TCP_NODELAY + { BOOL f = TRUE; setsockopt(sockfd,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } +#endif +#else + fcntl(sockfd,F_SETFL,O_NONBLOCK); +#ifdef ZT_TCP_NODELAY + { int f = 1; setsockopt(sockfd,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } +#endif +#endif + _fdSetLock.lock(); + FD_SET(sockfd,&_readfds); + _fdSetLock.unlock(); + if ((int)sockfd > (int)_nfds) + _nfds = (int)sockfd; + } catch ( ... ) { + CLOSE_SOCKET(sockfd); + } +#ifndef __WINDOWS__ + } else { + CLOSE_SOCKET(sockfd); + } +#endif + } + } + + { + NativeUdpSocket *usock = (NativeUdpSocket *)_udpV4Socket.ptr(); + if ((usock)&&(FD_ISSET(usock->_sock,&rfds))) { + usock->notifyAvailableForRead(_udpV4Socket,this,handler,arg); + } + usock = (NativeUdpSocket *)_udpV6Socket.ptr(); + if ((usock)&&(FD_ISSET(usock->_sock,&rfds))) { + usock->notifyAvailableForRead(_udpV6Socket,this,handler,arg); + } + } + + bool closedSockets = false; + { // grab copy of TCP sockets list because _tcpSockets[] might be changed in a handler + Mutex::Lock _l2(_tcpSockets_m); + if (!_tcpSockets.empty()) { + ts.reserve(_tcpSockets.size()); + uint64_t now = Utils::now(); + for(std::map< InetAddress,SharedPtr<Socket> >::iterator s(_tcpSockets.begin());s!=_tcpSockets.end();) { + NativeTcpSocket *tsock = (NativeTcpSocket *)s->second.ptr(); +#ifdef __WINDOWS__ + if ( ((now - tsock->_lastActivity) < ZT_TCP_TUNNEL_ACTIVITY_TIMEOUT) && (! ((tsock->_connecting)&&(FD_ISSET(tsock->_sock,&efds))) ) ) { +#else + if ((now - tsock->_lastActivity) < ZT_TCP_TUNNEL_ACTIVITY_TIMEOUT) { +#endif + ts.push_back(s->second); + ++s; + } else { + _fdSetLock.lock(); + FD_CLR(tsock->_sock,&_readfds); + FD_CLR(tsock->_sock,&_writefds); + _fdSetLock.unlock(); + _tcpSockets.erase(s++); + closedSockets = true; + } + } + } + } + for(std::vector< SharedPtr<Socket> >::iterator s(ts.begin());s!=ts.end();++s) { + NativeTcpSocket *tsock = (NativeTcpSocket *)s->ptr(); + if (FD_ISSET(tsock->_sock,&wfds)) { + if (!tsock->notifyAvailableForWrite(*s,this)) { + { + Mutex::Lock _l2(_tcpSockets_m); + _tcpSockets.erase(tsock->_remote); + } + _fdSetLock.lock(); + FD_CLR(tsock->_sock,&_readfds); + FD_CLR(tsock->_sock,&_writefds); + _fdSetLock.unlock(); + closedSockets = true; + continue; + } + } + if (FD_ISSET(tsock->_sock,&rfds)) { + if (!tsock->notifyAvailableForRead(*s,this,handler,arg)) { + { + Mutex::Lock _l2(_tcpSockets_m); + _tcpSockets.erase(tsock->_remote); + } + _fdSetLock.lock(); + FD_CLR(tsock->_sock,&_readfds); + FD_CLR(tsock->_sock,&_writefds); + _fdSetLock.unlock(); + closedSockets = true; + continue; + } + } + } + if (closedSockets) + _updateNfds(); +} + +void NativeSocketManager::whack() +{ + _whackSendPipe_m.lock(); +#ifdef __WINDOWS__ + ::send(_whackSendPipe,(const char *)this,1,0); +#else + ::write(_whackSendPipe,(const void *)this,1); // data is arbitrary, just send a byte +#endif + _whackSendPipe_m.unlock(); +} + +void NativeSocketManager::closeTcpSockets() +{ + { + Mutex::Lock _l2(_tcpSockets_m); + _fdSetLock.lock(); + for(std::map< InetAddress,SharedPtr<Socket> >::iterator s(_tcpSockets.begin());s!=_tcpSockets.end();++s) { + FD_CLR(((NativeTcpSocket *)s->second.ptr())->_sock,&_readfds); + FD_CLR(((NativeTcpSocket *)s->second.ptr())->_sock,&_writefds); + } + _fdSetLock.unlock(); + _tcpSockets.clear(); + } + _updateNfds(); +} + +void NativeSocketManager::_startNotifyWrite(const NativeSocket *sock) +{ + _fdSetLock.lock(); + FD_SET(sock->_sock,&_writefds); + _fdSetLock.unlock(); +} + +void NativeSocketManager::_stopNotifyWrite(const NativeSocket *sock) +{ + _fdSetLock.lock(); + FD_CLR(sock->_sock,&_writefds); + _fdSetLock.unlock(); +} + +void NativeSocketManager::_closeSockets() +{ +#ifdef __WINDOWS__ + if (_whackSendPipe != INVALID_SOCKET) + ::closesocket(_whackSendPipe); + if (_whackReceivePipe != INVALID_SOCKET) + ::closesocket(_whackReceivePipe); + if (_tcpV4ListenSocket != INVALID_SOCKET) + ::closesocket(_tcpV4ListenSocket); + if (_tcpV6ListenSocket != INVALID_SOCKET) + ::closesocket(_tcpV6ListenSocket); +#else + if (_whackSendPipe > 0) + ::close(_whackSendPipe); + if (_whackReceivePipe > 0) + ::close(_whackReceivePipe); + if (_tcpV4ListenSocket > 0) + ::close(_tcpV4ListenSocket); + if (_tcpV4ListenSocket > 0) + ::close(_tcpV6ListenSocket); +#endif +} + +void NativeSocketManager::_updateNfds() +{ +#ifdef __WINDOWS__ + SOCKET nfds = _whackSendPipe; +#else + int nfds = _whackSendPipe; +#endif + if (_whackReceivePipe > nfds) + nfds = _whackReceivePipe; + if (_tcpV4ListenSocket > nfds) + nfds = _tcpV4ListenSocket; + if (_tcpV6ListenSocket > nfds) + nfds = _tcpV6ListenSocket; + if ((_udpV4Socket)&&(((NativeUdpSocket *)_udpV4Socket.ptr())->_sock > nfds)) + nfds = ((NativeUdpSocket *)_udpV4Socket.ptr())->_sock; + if ((_udpV6Socket)&&(((NativeUdpSocket *)_udpV6Socket.ptr())->_sock > nfds)) + nfds = ((NativeUdpSocket *)_udpV6Socket.ptr())->_sock; + Mutex::Lock _l(_tcpSockets_m); + for(std::map< InetAddress,SharedPtr<Socket> >::const_iterator s(_tcpSockets.begin());s!=_tcpSockets.end();++s) { + if (((NativeTcpSocket *)s->second.ptr())->_sock > nfds) + nfds = ((NativeTcpSocket *)s->second.ptr())->_sock; + } + _nfds = (int)nfds; +} + +} // namespace ZeroTier diff --git a/osdep/NativeSocketManager.hpp b/osdep/NativeSocketManager.hpp new file mode 100644 index 00000000..5db06d6a --- /dev/null +++ b/osdep/NativeSocketManager.hpp @@ -0,0 +1,117 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_NATIVESOCKETMANAGER_HPP +#define ZT_NATIVESOCKETMANAGER_HPP + +#include <stdio.h> +#include <stdlib.h> + +#include <map> +#include <stdexcept> + +#include "../node/Constants.hpp" +#include "../node/SharedPtr.hpp" +#include "../node/Mutex.hpp" +#include "../node/SocketManager.hpp" +#include "../node/Socket.hpp" + +#ifdef __WINDOWS__ +#include <WinSock2.h> +#include <WS2tcpip.h> +#include <Windows.h> +#else +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/select.h> +#endif + +namespace ZeroTier { + +class NativeSocket; +class NativeUdpSocket; +class NativeTcpSocket; + +/** + * Native socket manager for Unix and Windows + */ +class NativeSocketManager : public SocketManager +{ + friend class NativeUdpSocket; + friend class NativeTcpSocket; + +public: + NativeSocketManager(int localUdpPort,int localTcpPort); + virtual ~NativeSocketManager(); + + virtual bool send(const InetAddress &to,bool tcp,bool autoConnectTcp,const void *msg,unsigned int msglen); + virtual void poll(unsigned long timeout,void (*handler)(const SharedPtr<Socket> &,void *,const InetAddress &,Buffer<ZT_SOCKET_MAX_MESSAGE_LEN> &),void *arg); + virtual void whack(); + virtual void closeTcpSockets(); + +private: + // Used by TcpSocket to register/unregister for write availability notification + void _startNotifyWrite(const NativeSocket *sock); + void _stopNotifyWrite(const NativeSocket *sock); + + // Called in SocketManager destructor or in constructor cleanup before exception throwing + void _closeSockets(); + + // Called in SocketManager to recompute _nfds for select() based implementation + void _updateNfds(); + +#ifdef __WINDOWS__ + SOCKET _whackSendPipe; + SOCKET _whackReceivePipe; + SOCKET _tcpV4ListenSocket; + SOCKET _tcpV6ListenSocket; +#else + int _whackSendPipe; + int _whackReceivePipe; + int _tcpV4ListenSocket; + int _tcpV6ListenSocket; +#endif + Mutex _whackSendPipe_m; + + SharedPtr<Socket> _udpV4Socket; + SharedPtr<Socket> _udpV6Socket; + + fd_set _readfds; + fd_set _writefds; + volatile int _nfds; + Mutex _fdSetLock; + + std::map< InetAddress,SharedPtr<Socket> > _tcpSockets; + Mutex _tcpSockets_m; + + Mutex _pollLock; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/OSXEthernetTap.cpp b/osdep/OSXEthernetTap.cpp new file mode 100644 index 00000000..396605c5 --- /dev/null +++ b/osdep/OSXEthernetTap.cpp @@ -0,0 +1,639 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <unistd.h> +#include <signal.h> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/select.h> +#include <sys/cdefs.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/route.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <netinet6/in6_var.h> +#include <netinet/in_var.h> +#include <netinet/icmp6.h> + +// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!? +struct prf_ra { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; +} prf_ra; + +#include <netinet6/nd6.h> +#include <ifaddrs.h> + +// 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 + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- +// 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 <sys/socket.h> */ +/* 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); +} + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- + +#include <string> +#include <map> +#include <set> +#include <algorithm> + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "OSXEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +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; +} + +namespace ZeroTier { + +OSXEthernetTap::OSXEthernetTap( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) : + EthernetTap("OSXEthernetTap",mac,mtu,metric), + _handler(handler), + _arg(arg), + _mtu(mtu), + _metric(metric), + _fd(0), + _enabled(true) +{ + static Mutex globalTapCreateLock; + char devpath[64],ethaddr[64],mtustr[32],metstr[32]; + struct stat stattmp; + + Mutex::Lock _gl(globalTapCreateLock); + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + if (stat("/dev/zt0",&stattmp)) + throw std::runtime_error("/dev/zt# tap devices do not exist"); + + // Try to reopen the last device we had, if we had one and it's still unused. + bool recalledDevice = false; + if ((desiredDevice)&&(desiredDevice[0] == 'z')&&(desiredDevice[1] == 't')) { + if ((strchr(desiredDevice,'/'))||(strchr(desiredDevice,'.'))) // security sanity check + throw std::runtime_error("invalid desiredDevice parameter"); + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",desiredDevice); + if (stat(devpath,&stattmp) == 0) { + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + _dev = desiredDevice; + recalledDevice = true; + } + } + } + + // Open the first unused tap device if we didn't recall a previous one. + if (!recalledDevice) { + for(int i=0;i<64;++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) { + char foo[16]; + Utils::snprintf(foo,sizeof(foo),"zt%d",i); + _dev = foo; + 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"); + } + + // 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); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + 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.c_str(),true,false); + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + ::pipe(_shutdownSignalPipe); + + _thread = Thread::start(this); +} + +OSXEthernetTap::~OSXEthernetTap() +{ + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); +} + +void OSXEthernetTap::setEnabled(bool en) +{ + _enabled = en; + // TODO: interface status change +} + +bool OSXEthernetTap::enabled() const +{ + return _enabled; +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; // never reached, make compiler shut up about return value +} + +bool OSXEthernetTap::addIP(const InetAddress &ip) +{ + if (!ip) + return false; + + std::set<InetAddress> 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<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) + break; + } + } + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; +} + +bool OSXEthernetTap::removeIP(const InetAddress &ip) +{ + if (ips().count(ip) > 0) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::set<InetAddress> OSXEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::set<InetAddress>(); + + std::set<InetAddress> r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(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 OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[4096]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + ::write(_fd,putBuf,len); + } +} + +std::string OSXEthernetTap::deviceName() const +{ + return _dev; +} + +void OSXEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +bool OSXEthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups) +{ + std::set<MulticastGroup> 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 <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.insert(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + _intl_freeifmaddrs(ifmap); + } + + { + std::set<InetAddress> allIps(ips()); + for(std::set<InetAddress>::const_iterator i(allIps.begin());i!=allIps.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +bool OSXEthernetTap::injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + return false; +} + +void OSXEthernetTap::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)) + 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; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + data.copyFrom(getBuf + 14,(unsigned int)r - 14); + _handler(_arg,from,to,etherType,data); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/OSXEthernetTap.hpp b/osdep/OSXEthernetTap.hpp new file mode 100644 index 00000000..efcb550f --- /dev/null +++ b/osdep/OSXEthernetTap.hpp @@ -0,0 +1,91 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_OSXETHERNETTAP_HPP +#define ZT_OSXETHERNETTAP_HPP + +#include <stdio.h> +#include <stdlib.h> + +#include <stdexcept> + +#include "../node/EthernetTap.hpp" +#include "../node/Thread.hpp" + +namespace ZeroTier { + +/** + * OSX Ethernet tap using ZeroTier kernel extension zt# devices + * + * This also installs a friendly-named network device in the system network + * configuration, permitting network devices to be seen and configured in + * the OSX system preferences app. + */ +class OSXEthernetTap : public EthernetTap +{ +public: + OSXEthernetTap( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + + virtual ~OSXEthernetTap(); + + virtual void setEnabled(bool en); + virtual bool enabled() const; + virtual bool addIP(const InetAddress &ip); + virtual bool removeIP(const InetAddress &ip); + virtual std::set<InetAddress> 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 void setFriendlyName(const char *friendlyName); + virtual bool updateMulticastGroups(std::set<MulticastGroup> &groups); + virtual bool injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + + void threadMain() + throw(); + +private: + void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); + void *_arg; + Thread _thread; + std::string _dev; + unsigned int _mtu; + unsigned int _metric; + int _fd; + int _shutdownSignalPipe[2]; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/OSXEthernetTapFactory.cpp b/osdep/OSXEthernetTapFactory.cpp new file mode 100644 index 00000000..4cad8daa --- /dev/null +++ b/osdep/OSXEthernetTapFactory.cpp @@ -0,0 +1,122 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "OSXEthernetTapFactory.hpp" +#include "OSXEthernetTap.hpp" + +#include "../node/Utils.hpp" + +namespace ZeroTier { + +OSXEthernetTapFactory::OSXEthernetTapFactory(const char *pathToTapKext,const char *tapKextName) : + _pathToTapKext((pathToTapKext) ? pathToTapKext : ""), + _tapKextName((tapKextName) ? tapKextName : "") +{ + struct stat stattmp; + + if ((_pathToTapKext.length())&&(_tapKextName.length())) { + if (stat("/dev/zt0",&stattmp)) { + long kextpid = (long)vfork(); + if (kextpid == 0) { + ::chdir(_pathToTapKext.c_str()); + Utils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/kextload","/sbin/kextload","-q","-repository",_pathToTapKext.c_str(),_tapKextName.c_str(),(const char *)0); + ::_exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + ::waitpid(kextpid,&exitcode,0); + } else throw std::runtime_error("unable to create subprocess with fork()"); + } + } + + if (stat("/dev/zt0",&stattmp)) { + ::usleep(500); // give tap device driver time to start up and try again + if (stat("/dev/zt0",&stattmp)) + throw std::runtime_error("/dev/zt# tap devices do not exist and unable to load kernel extension"); + } +} + +OSXEthernetTapFactory::~OSXEthernetTapFactory() +{ + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) + delete *d; + + if ((_pathToTapKext.length())&&(_tapKextName.length())) { + // Attempt to unload kext. If anything else is using a /dev/zt# node, this + // fails and the kext stays in the kernel. + char tmp[16384]; + sprintf(tmp,"%s/%s",_pathToTapKext.c_str(),_tapKextName.c_str()); + long kextpid = (long)vfork(); + if (kextpid == 0) { + Utils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/kextunload","/sbin/kextunload",tmp,(const char *)0); + ::_exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + ::waitpid(kextpid,&exitcode,0); + } + } +} + +EthernetTap *OSXEthernetTapFactory::open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) +{ + Mutex::Lock _l(_devices_m); + EthernetTap *t = new OSXEthernetTap(mac,mtu,metric,nwid,desiredDevice,friendlyName,handler,arg); + _devices.push_back(t); + return t; +} + +void OSXEthernetTapFactory::close(EthernetTap *tap,bool destroyPersistentDevices) +{ + { + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) { + if (*d == tap) { + _devices.erase(d); + break; + } + } + } + delete tap; +} + +} // namespace ZeroTier diff --git a/osdep/OSXEthernetTapFactory.hpp b/osdep/OSXEthernetTapFactory.hpp new file mode 100644 index 00000000..2f2ee527 --- /dev/null +++ b/osdep/OSXEthernetTapFactory.hpp @@ -0,0 +1,76 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_OSXETHERNETTAPFACTORY_HPP +#define ZT_OSXETHERNETTAPFACTORY_HPP + +#include <vector> +#include <string> + +#include "../node/EthernetTapFactory.hpp" +#include "../node/Mutex.hpp" + +namespace ZeroTier { + +class OSXEthernetTapFactory : public EthernetTapFactory +{ +public: + /** + * Create OSX ethernet tap factory + * + * If kext paths are specified, an attempt will be made to load the kext + * on launch if not present and unload it on shutdown. + * + * @param pathToTapKext Full path to the location of the tap kext + * @param tapKextName Name of tap kext as found within tap kext path (usually "tap.kext") + * @throws std::runtime_error Tap not available and unable to load kext + */ + OSXEthernetTapFactory(const char *pathToTapKext,const char *tapKextName); + + virtual ~OSXEthernetTapFactory(); + + virtual EthernetTap *open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + virtual void close(EthernetTap *tap,bool destroyPersistentDevices); + +private: + std::vector<EthernetTap *> _devices; + Mutex _devices_m; + std::string _pathToTapKext; + std::string _tapKextName; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp new file mode 100644 index 00000000..6abdf8ad --- /dev/null +++ b/osdep/Phy.hpp @@ -0,0 +1,817 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_PHY_HPP +#define ZT_PHY_HPP + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <list> + +#if defined(_WIN32) || defined(_WIN64) + +#include <WinSock2.h> +#include <WS2tcpip.h> +#include <Windows.h> + +#define ZT_PHY_SOCKFD_TYPE SOCKET +#define ZT_PHY_SOCKFD_NULL (INVALID_SOCKET) +#define ZT_PHY_SOCKFD_VALID(s) ((s) != INVALID_SOCKET) +#define ZT_PHY_CLOSE_SOCKET(s) ::closesocket(s) +#define ZT_PHY_MAX_SOCKETS (FD_SETSIZE) +#define ZT_PHY_SOCKADDR_STORAGE_TYPE struct sockaddr_storage + +#else // not Windows + +#include <errno.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#define ZT_PHY_SOCKFD_TYPE int +#define ZT_PHY_SOCKFD_NULL (-1) +#define ZT_PHY_SOCKFD_VALID(s) ((s) > -1) +#define ZT_PHY_CLOSE_SOCKET(s) ::close(s) +#define ZT_PHY_MAX_SOCKETS (FD_SETSIZE) +#define ZT_PHY_SOCKADDR_STORAGE_TYPE struct sockaddr_storage + +#endif // Windows or not + +namespace ZeroTier { + +/** + * Opaque socket type + */ +typedef void PhySocket; + +/** + * Simple templated non-blocking sockets implementation + * + * Yes there is boost::asio and libuv, but I like small binaries and I hate + * build dependencies. Both drag in a whole bunch of pasta with them. + * + * This implementation takes four functions or function objects as template + * paramters: + * + * ON_DATAGRAM_FUNCTION(PhySocket *sock,void **uptr,const struct sockaddr *from,void *data,unsigned long len) + * ON_TCP_CONNECT_FUNCTION(PhySocket *sock,void **uptr,bool success) + * ON_TCP_ACCEPT_FUNCTION(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) + * ON_TCP_CLOSE_FUNCTION(PhySocket *sock,void **uptr) + * ON_TCP_DATA_FUNCTION(PhySocket *sock,void **uptr,void *data,unsigned long len) + * ON_TCP_WRITABLE_FUNCTION(PhySocket *sock,void **uptr) + * + * These templates typically refer to function objects. Templates are used to + * avoid the call overhead of indirection, which is surprisingly high for high + * bandwidth applications pushing a lot of packets. + * + * The 'sock' pointer above is an opaque pointer to a socket. Each socket + * has a 'uptr' user-settable/modifiable pointer associated with it, which + * can be set on bind/connect calls and is passed as a void ** to permit + * resetting at any time. The ACCEPT handler takes two sets of sock and + * uptr: sockL and uptrL for the listen socket, and sockN and uptrN for + * the new TCP connection socket that has just been created. + * + * Handlers are always called. On outgoing TCP connection, CONNECT is always + * called on either success or failure followed by DATA and/or WRITABLE as + * indicated. On socket close, handlers are called unless close() is told + * explicitly not to call handlers. It is safe to close a socket within a + * handler, and in that case close() can be told not to call handlers to + * prevent recursion. + * + * This isn't thread-safe with the exception of whack(), which is safe to + * call from another thread to abort poll(). + */ +template < + typename ON_DATAGRAM_FUNCTION, + typename ON_TCP_CONNECT_FUNCTION, + typename ON_TCP_ACCEPT_FUNCTION, + typename ON_TCP_CLOSE_FUNCTION, + typename ON_TCP_DATA_FUNCTION, + typename ON_TCP_WRITABLE_FUNCTION > +class Phy +{ +private: + ON_DATAGRAM_FUNCTION _datagramHandler; + ON_TCP_CONNECT_FUNCTION _tcpConnectHandler; + ON_TCP_ACCEPT_FUNCTION _tcpAcceptHandler; + ON_TCP_CLOSE_FUNCTION _tcpCloseHandler; + ON_TCP_DATA_FUNCTION _tcpDataHandler; + ON_TCP_WRITABLE_FUNCTION _tcpWritableHandler; + + enum PhySocketType + { + ZT_PHY_SOCKET_TCP_OUT_PENDING = 0x00, + ZT_PHY_SOCKET_TCP_OUT_CONNECTED = 0x01, + ZT_PHY_SOCKET_TCP_IN = 0x02, + ZT_PHY_SOCKET_TCP_LISTEN = 0x03, + ZT_PHY_SOCKET_RAW = 0x04, + ZT_PHY_SOCKET_UDP = 0x05 + }; + + struct PhySocketImpl + { + PhySocketType type; + ZT_PHY_SOCKFD_TYPE sock; + void *uptr; // user-settable pointer + ZT_PHY_SOCKADDR_STORAGE_TYPE saddr; // remote for TCP_OUT and TCP_IN, local for TCP_LISTEN, RAW, and UDP + }; + + std::list<PhySocketImpl> _socks; + fd_set _readfds; + fd_set _writefds; +#if defined(_WIN32) || defined(_WIN64) + fd_set _exceptfds; +#endif + long _nfds; + + ZT_PHY_SOCKFD_TYPE _whackReceiveSocket; + ZT_PHY_SOCKFD_TYPE _whackSendSocket; + + bool _noDelay; + +public: + /** + * @param datagramHandler Function or function object to handle UDP or RAW datagrams + * @param tcpConnectHandler Handler for outgoing TCP connection attempts (success or failure) + * @param tcpAcceptHandler Handler for incoming TCP connections + * @param tcpDataHandler Handler for incoming TCP data + * @param tcpWritableHandler Handler to be called when TCP sockets are writable (if notification is on) + * @param noDelay If true, disable Nagle algorithm on new TCP sockets + */ + Phy( + ON_DATAGRAM_FUNCTION datagramHandler, + ON_TCP_CONNECT_FUNCTION tcpConnectHandler, + ON_TCP_ACCEPT_FUNCTION tcpAcceptHandler, + ON_TCP_CLOSE_FUNCTION tcpCloseHandler, + ON_TCP_DATA_FUNCTION tcpDataHandler, + ON_TCP_WRITABLE_FUNCTION tcpWritableHandler, + bool noDelay + ) : + _datagramHandler(datagramHandler), + _tcpConnectHandler(tcpConnectHandler), + _tcpAcceptHandler(tcpAcceptHandler), + _tcpCloseHandler(tcpCloseHandler), + _tcpDataHandler(tcpDataHandler), + _tcpWritableHandler(tcpWritableHandler) + { + FD_ZERO(&_readfds); + FD_ZERO(&_writefds); + +#if defined(_WIN32) || defined(_WIN64) + FD_ZERO(&_exceptfds); + + SOCKET pipes[2]; + { // hack copied from StackOverflow, behaves a bit like pipe() on *nix systems + struct sockaddr_in inaddr; + struct sockaddr addr; + SOCKET lst=::socket(AF_INET, SOCK_STREAM,IPPROTO_TCP); + if (lst == INVALID_SOCKET) + throw std::runtime_error("unable to create pipes for select() abort"); + memset(&inaddr, 0, sizeof(inaddr)); + memset(&addr, 0, sizeof(addr)); + inaddr.sin_family = AF_INET; + inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + inaddr.sin_port = 0; + int yes=1; + setsockopt(lst,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); + bind(lst,(struct sockaddr *)&inaddr,sizeof(inaddr)); + listen(lst,1); + int len=sizeof(inaddr); + getsockname(lst, &addr,&len); + pipes[0]=::socket(AF_INET, SOCK_STREAM,0); + if (pipes[0] == INVALID_SOCKET) + throw std::runtime_error("unable to create pipes for select() abort"); + connect(pipes[0],&addr,len); + pipes[1]=accept(lst,0,0); + closesocket(lst); + } +#else // not Windows + int pipes[2]; + if (::pipe(pipes)) + throw std::runtime_error("unable to create pipes for select() abort"); +#endif // Windows or not + + _nfds = (pipes[0] > pipes[1]) ? (long)pipes[0] : (long)pipes[1]; + _whackReceiveSocket = pipes[0]; + _whackSendSocket = pipes[1]; + _noDelay = noDelay; + } + + ~Phy() + { + while (!_socks.empty()) + this->close((PhySocket *)&(_socks.front()),true); + ZT_PHY_CLOSE_SOCKET(_whackReceiveSocket); + ZT_PHY_CLOSE_SOCKET(_whackSendSocket); + } + + /** + * Cause poll() to stop waiting immediately + */ + inline void whack() + { +#if defined(_WIN32) || defined(_WIN64) + ::send(_whackSendSocket,(const char *)this,1,0); +#else + ::write(_whackSendSocket,(PhySocket *)this,1); +#endif + } + + /** + * @return Number of open sockets + */ + inline unsigned long count() const throw() { return _socks.size(); } + + /** + * @return Maximum number of sockets allowed + */ + inline unsigned long maxCount() const throw() { return ZT_PHY_MAX_SOCKETS; } + + /** + * Bind a UDP socket + * + * @param localAddress Local endpoint address and port + * @param uptr Initial value of user pointer associated with this socket (default: NULL) + * @param bufferSize Desired socket receive/send buffer size -- will set as close to this as possible (default: 0, leave alone) + * @return Socket or NULL on failure to bind + */ + inline PhySocket *udpBind(const struct sockaddr *localAddress,void *uptr = (void *)0,int bufferSize = 0) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + ZT_PHY_SOCKFD_TYPE s = ::socket(localAddress->sa_family,SOCK_DGRAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) + return (PhySocket *)0; + + if (bufferSize > 0) { + int bs = bufferSize; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + bs = bufferSize; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + } + +#if defined(_WIN32) || defined(_WIN64) + { + BOOL f; + if (localAddress->sa_family == AF_INET6) { + f = TRUE; setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = FALSE; setsockopt(s,IPPROTO_IPV6,IPV6_DONTFRAG,(const char *)&f,sizeof(f)); + } + f = FALSE; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = TRUE; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char *)&f,sizeof(f)); + } +#else // not Windows + { + int f; + if (localAddress->sa_family == AF_INET6) { + f = 1; setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); +#ifdef IPV6_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IPV6,IPV6_MTU_DISCOVER,&f,sizeof(f)); +#endif + } + f = 0; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = 1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(void *)&f,sizeof(f)); +#ifdef IP_DONTFRAG + f = 0; setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&f,sizeof(f)); +#endif +#ifdef IP_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IP,IP_MTU_DISCOVER,&f,sizeof(f)); +#endif + } +#endif // Windows or not + + if (::bind(s,localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + +#if defined(_WIN32) || defined(_WIN64) + { u_long iMode=1; ioctlsocket(s,FIONBIO,&iMode); } +#else + fcntl(s,F_SETFL,O_NONBLOCK); +#endif + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_UDP; + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + + return (PhySocket *)&sws; + } + + /** + * Send a UDP packet + * + * @param sock UDP socket + * @param remoteAddress Destination address (must be correct type for socket) + * @param data Data to send + * @param len Length of packet + * @return True if packet appears to have been sent successfully + */ + inline bool udpSend(PhySocket *sock,const struct sockaddr *remoteAddress,const void *data,unsigned long len) + { + PhySocketImpl &sws = *(reinterpret_cast<PhySocketImpl *>(sock)); + return ((long)::sendto(sws.sock,data,len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); + } + + /** + * Bind a local listen socket to listen for new TCP connections + * + * @param localAddress Local address and port + * @param uptr Initial value of uptr for new socket (default: NULL) + * @return Socket or NULL on failure to bind + */ + inline PhySocket *tcpListen(const struct sockaddr *localAddress,void *uptr = (void *)0) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + ZT_PHY_SOCKFD_TYPE s = ::socket(localAddress->sa_family,SOCK_STREAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) + return (PhySocket *)0; + +#if defined(_WIN32) || defined(_WIN64) + { + BOOL f; + f = TRUE; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = TRUE; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = (_noDelay ? TRUE : FALSE); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + u_long iMode=1; + ioctlsocket(s,FIONBIO,&iMode); + } +#else + { + int f; + f = 1; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); + f = 1; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = (_noDelay ? 1 : 0); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + fcntl(s,F_SETFL,O_NONBLOCK); + } +#endif + + if (::bind(s,localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + + if (::listen(s,1024)) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_TCP_LISTEN; + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + + return (PhySocket *)&sws; + } + + /** + * Start a non-blocking connect; CONNECT handler is called on success or failure + * + * A return value of NULL indicates a synchronous failure such as a + * failure to open a socket. The TCP connection handler is not called + * in this case. + * + * It is possible on some platforms for an "instant connect" to occur, + * such as when connecting to a loopback address. In this case, the + * 'connected' result parameter will be set to 'true' and if the + * 'callConnectHandler' flag is true (the default) the TCP connect + * handler will be called before the function returns. + * + * These semantics can be a bit confusing, but they're less so than + * the underlying semantics of asynchronous TCP connect. + * + * @param remoteAddress Remote address + * @param connected Result parameter: set to whether an "instant connect" has occurred (true if yes) + * @param uptr Initial value of uptr for new socket (default: NULL) + * @param callConnectHandler If true, call TCP connect handler even if result is known before function exit (default: true) + * @return New socket or NULL on failure + */ + inline PhySocket *tcpConnect(const struct sockaddr *remoteAddress,bool &connected,void *uptr = (void *)0,bool callConnectHandler = true) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + ZT_PHY_SOCKFD_TYPE s = ::socket(remoteAddress->sa_family,SOCK_STREAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) { + connected = false; + return (PhySocket *)0; + } + +#if defined(_WIN32) || defined(_WIN64) + { + BOOL f; + f = TRUE; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = TRUE; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = (_noDelay ? TRUE : FALSE); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + u_long iMode=1; + ioctlsocket(s,FIONBIO,&iMode); + } +#else + { + int f; + f = 1; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); + f = 1; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = (_noDelay ? 1 : 0); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + fcntl(s,F_SETFL,O_NONBLOCK); + } +#endif + + connected = true; + if (::connect(s,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + connected = false; +#if defined(_WIN32) || defined(_WIN64) + if (WSAGetLastError() != WSAEWOULDBLOCK) { +#else + if (errno != EINPROGRESS) { +#endif + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } // else connection is proceeding asynchronously... + } + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + if (connected) { + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_TCP_OUT_CONNECTED; + } else { + FD_SET(s,&_writefds); +#if defined(_WIN32) || defined(_WIN64) + FD_SET(s,&_exceptfds); +#endif + sws.type = ZT_PHY_SOCKET_TCP_OUT_PENDING; + } + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + + if ((callConnectHandler)&&(connected)) { + try { + _tcpConnectHandler((PhySocket *)&sws,&(sws.uptr),true); + } catch ( ... ) {} + } + + return (PhySocket *)&sws; + } + + /** + * Attempt to send data to a TCP connection (non-blocking) + * + * If -1 is returned, the socket should no longer be used as it is now + * destroyed. If callCloseHandler is true, the close handler will be + * called before the function returns. + * + * @param sock An open TCP socket (other socket types will fail) + * @param data Data to send + * @param len Length of data + * @param callCloseHandler If true, call close handler on socket closing failure condition + * @return Number of bytes actually sent or -1 on fatal error (socket closure) + */ + inline long tcpSend(PhySocket *sock,const void *data,unsigned long len,bool callCloseHandler) + { + PhySocketImpl &sws = *(reinterpret_cast<PhySocketImpl *>(sock)); + long n = (long)::send(sws.sock,data,len,0); +#if defined(_WIN32) || defined(_WIN64) + if (n == SOCKET_ERROR) { + switch(WSAGetLastError()) { + case WSAEINTR: + case WSAEWOULDBLOCK: + return 0; + default: + this->close(sock,callCloseHandler); + return -1; + } + } +#else // not Windows + if (n < 0) { + switch(errno) { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && ( !defined(EAGAIN) || (EWOULDBLOCK != EAGAIN) ) + case EWOULDBLOCK: +#endif +#ifdef EINTR + case EINTR: +#endif + return 0; + default: + this->close(sock,callCloseHandler); + return -1; + } + } +#endif // Windows or not + return n; + } + + /** + * Set whether we want to be notified via the TCP writability handler when a socket is writable + * + * Call whack() if this is being done from another thread and you want + * it to take effect immediately. Otherwise it is only guaranteed to + * take effect on the next poll(). + * + * @param sock TCP connection socket (other types are not valid) + * @param notifyWritable Want writable notifications? + */ + inline const void tcpSetNotifyWritable(PhySocket *sock,bool notifyWritable) + { + PhySocketImpl &sws = *(reinterpret_cast<PhySocketImpl *>(sock)); + if (notifyWritable) { + FD_SET(sws.sock,&_writefds); + } else { + FD_CLR(sws.sock,&_writefds); + } + } + + /** + * Wait for activity and handle one or more events + * + * Note that this is not guaranteed to wait up to 'timeout' even + * if nothing happens, as whack() or other events such as signals + * may cause premature termination. + * + * @param timeout Timeout in milliseconds or 0 for none (forever) + */ + inline void poll(unsigned long timeout) + { + char buf[131072]; + struct sockaddr_storage ss; + struct timeval tv; + fd_set rfds,wfds,efds; + + memcpy(&rfds,&_readfds,sizeof(rfds)); + memcpy(&wfds,&_writefds,sizeof(wfds)); +#if defined(_WIN32) || defined(_WIN64) + memcpy(&efds,&_exceptfds,sizeof(efds)); +#else + FD_ZERO(&efds); +#endif + + tv.tv_sec = (long)(timeout / 1000); + tv.tv_usec = (long)((timeout % 1000) * 1000); + if (::select((int)_nfds + 1,&rfds,&wfds,&efds,(timeout > 0) ? &tv : (struct timeval *)0) <= 0) + return; + + if (FD_ISSET(_whackReceiveSocket,&rfds)) { + char tmp[16]; +#if defined(_WIN32) || defined(_WIN64) + ::recv(_whackReceiveSocket,tmp,16,0); +#else + ::read(_whackReceiveSocket,tmp,16); +#endif + } + + for(typename std::list<PhySocketImpl>::iterator s(_socks.begin()),nexts;s!=_socks.end();s=nexts) { + nexts = s; ++nexts; // we can delete the linked list item, so traverse now + + switch (s->type) { + + case ZT_PHY_SOCKET_TCP_OUT_PENDING: +#if defined(_WIN32) || defined(_WIN64) + if (FD_ISSET(s->sock,&efds)) + this->close((PhySocket *)&(*s),true); + else // ... if +#endif + if (FD_ISSET(s->sock,&wfds)) { + socklen_t slen = sizeof(ss); + if (::getpeername(s->sock,(struct sockaddr *)&ss,&slen) != 0) { + this->close((PhySocket *)&(*s),true); + } else { + s->type = ZT_PHY_SOCKET_TCP_OUT_CONNECTED; + FD_SET(s->sock,&_readfds); + FD_CLR(s->sock,&_writefds); +#if defined(_WIN32) || defined(_WIN64) + FD_CLR(s->sock,&_exceptfds); +#endif + try { + _tcpConnectHandler((PhySocket *)&(*s),&(s->uptr),true); + } catch ( ... ) {} + } + } + break; + + case ZT_PHY_SOCKET_TCP_OUT_CONNECTED: + case ZT_PHY_SOCKET_TCP_IN: + if (FD_ISSET(s->sock,&rfds)) { + long n = (long)::recv(s->sock,buf,sizeof(buf),0); + if (n <= 0) { + this->close((PhySocket *)&(*s),true); + } else { + try { + _tcpDataHandler((PhySocket *)&(*s),&(s->uptr),(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } + } + if ((FD_ISSET(s->sock,&wfds))&&(FD_ISSET(s->sock,&_writefds))) { + try { + _tcpWritableHandler((PhySocket *)&(*s),&(s->uptr)); + } catch ( ... ) {} + } + break; + + case ZT_PHY_SOCKET_TCP_LISTEN: + if (FD_ISSET(s->sock,&rfds)) { + memset(&ss,0,sizeof(ss)); + socklen_t slen = sizeof(ss); + ZT_PHY_SOCKFD_TYPE newSock = ::accept(s->sock,(struct sockaddr *)&ss,&slen); + if (ZT_PHY_SOCKFD_VALID(newSock)) { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) { + ZT_PHY_CLOSE_SOCKET(newSock); + } else { +#if defined(_WIN32) || defined(_WIN64) + { BOOL f = (_noDelay ? TRUE : FALSE); setsockopt(newSock,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } + { u_long iMode=1; ioctlsocket(newSock,FIONBIO,&iMode); } +#else + { int f = (_noDelay ? 1 : 0); setsockopt(newSock,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } + fcntl(newSock,F_SETFL,O_NONBLOCK); +#endif + _socks.push_back(PhySocketImpl()); + PhySocketImpl &sws = _socks.back(); + FD_SET(newSock,&_readfds); + if ((long)newSock > _nfds) + _nfds = (long)newSock; + sws.type = ZT_PHY_SOCKET_TCP_IN; + sws.sock = newSock; + sws.uptr = (void *)0; + memcpy(&(sws.saddr),&ss,sizeof(struct sockaddr_storage)); + try { + _tcpAcceptHandler((PhySocket *)&(*s),(PhySocket *)&(_socks.back()),&(s->uptr),&(sws.uptr),(const struct sockaddr *)&(sws.saddr)); + } catch ( ... ) {} + } + } + } + break; + + case ZT_PHY_SOCKET_UDP: + if (FD_ISSET(s->sock,&rfds)) { + memset(&ss,0,sizeof(ss)); + socklen_t slen = sizeof(ss); + long n = (long)::recvfrom(s->sock,buf,sizeof(buf),0,(struct sockaddr *)&ss,&slen); + if (n > 0) { + try { + _datagramHandler((PhySocket *)&(*s),&(s->uptr),(const struct sockaddr *)&ss,(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } + } + break; + + default: + break; + + } + } + } + + inline void close(PhySocket *sock,bool callHandlers) + { + if (!sock) + return; + PhySocketImpl &sws = *(reinterpret_cast<PhySocketImpl *>(sock)); + + FD_CLR(sws.sock,&_readfds); + FD_CLR(sws.sock,&_writefds); +#if defined(_WIN32) || defined(_WIN64) + FD_CLR(sws.sock,&_exceptfds); +#endif + + ZT_PHY_CLOSE_SOCKET(sws.sock); + + switch(sws.type) { + case ZT_PHY_SOCKET_TCP_OUT_PENDING: + if (callHandlers) { + try { + _tcpConnectHandler(sock,&(sws.uptr),false); + } catch ( ... ) {} + } + break; + case ZT_PHY_SOCKET_TCP_OUT_CONNECTED: + case ZT_PHY_SOCKET_TCP_IN: + if (callHandlers) { + try { + _tcpCloseHandler(sock,&(sws.uptr)); + } catch ( ... ) {} + } + break; + default: + break; + } + + long oldSock = (long)sws.sock; + + for(typename std::list<PhySocketImpl>::iterator s(_socks.begin());s!=_socks.end();++s) { + if (reinterpret_cast<PhySocket *>(&(*s)) == sock) { + _socks.erase(s); + break; + } + } + + if (oldSock >= _nfds) { + long nfds = (long)_whackSendSocket; + if ((long)_whackReceiveSocket > nfds) + nfds = (long)_whackReceiveSocket; + for(typename std::list<PhySocketImpl>::iterator s(_socks.begin());s!=_socks.end();++s) { + if ((long)s->sock > nfds) + nfds = (long)s->sock; + } + _nfds = nfds; + } + } +}; + +// Typedefs for using regular naked functions as template parameters to Phy<> +typedef void (*Phy_OnDatagramFunctionPtr)(PhySocket *sock,void **uptr,const struct sockaddr *from,void *data,unsigned long len); +typedef void (*Phy_OnTcpConnectFunction)(PhySocket *sock,void **uptr,bool success); +typedef void (*Phy_OnTcpAcceptFunction)(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from); +typedef void (*Phy_OnTcpCloseFunction)(PhySocket *sock,void **uptr); +typedef void (*Phy_OnTcpDataFunction)(PhySocket *sock,void **uptr,void *data,unsigned long len); +typedef void (*Phy_OnTcpWritableFunction)(PhySocket *sock,void **uptr); + +/** + * Phy<> typedef'd to use simple naked function pointers + */ +typedef Phy<Phy_OnDatagramFunctionPtr,Phy_OnTcpConnectFunction,Phy_OnTcpAcceptFunction,Phy_OnTcpCloseFunction,Phy_OnTcpDataFunction,Phy_OnTcpWritableFunction> SimpleFunctionPhy; + +} // namespace ZeroTier + +#endif diff --git a/osdep/README.md b/osdep/README.md new file mode 100644 index 00000000..114e26f2 --- /dev/null +++ b/osdep/README.md @@ -0,0 +1,6 @@ +Network and Virtual Network Port Interfaces for Real OSes +====== + +This folder contains implementations of EthernetTap, EthernetTapFactory, and RoutingTable that bind to operating system level interfaces and drivers on Linux, Mac, Windows, and other platforms. + +It also contains NativeSocketManager which implements SocketManager using standard sockets (or WinSock2) and select() for multiplexing. diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp new file mode 100644 index 00000000..5d445380 --- /dev/null +++ b/osdep/WindowsEthernetTap.cpp @@ -0,0 +1,867 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <WinSock2.h> +#include <Windows.h> +#include <tchar.h> +#include <winreg.h> +#include <wchar.h> +#include <ws2ipdef.h> +#include <WS2tcpip.h> +#include <IPHlpApi.h> +#include <nldef.h> +#include <netioapi.h> +#include <atlbase.h> +#include <netlistmgr.h> +#include <nldef.h> + +#include <iostream> + +#include "../node/Constants.hpp" + +#include "WindowsEthernetTap.hpp" +#include "WindowsEthernetTapFactory.hpp" +#include "../node/Utils.hpp" +#include "../node/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); + +#define ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE + +namespace ZeroTier { + +// Only create or delete devices one at a time +static Mutex _systemTapInitLock; + +WindowsEthernetTap::WindowsEthernetTap( + const char *pathToHelpers, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) : + EthernetTap("WindowsEthernetTap",mac,mtu,metric), + _handler(handler), + _arg(arg), + _nwid(nwid), + _tap(INVALID_HANDLE_VALUE), + _injectSemaphore(INVALID_HANDLE_VALUE), + _pathToHelpers(pathToHelpers), + _run(true), + _initialized(false), + _enabled(true) +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + char tag[24]; + + if (mtu > 2800) + throw std::runtime_error("MTU too large for Windows tap"); + + Mutex::Lock _l(_systemTapInitLock); + + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + throw std::runtime_error("unable to open registry key for network adapter enumeration"); + + std::set<std::string> existingDeviceInstances; + std::string mySubkeyName; + + // We "tag" registry entries with the network ID to identify persistent devices + Utils::snprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); + + // Look for the tap instance that corresponds with this network + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + if (!strnicmp(data,"zttap",5)) { + std::string instanceId; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + instanceId.assign(data,dataLen); + existingDeviceInstances.insert(instanceId); + } + + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + + 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)) { + _netCfgInstanceId = instanceId; + _deviceInstanceId = instanceIdPath; + + mySubkeyName = subkeyName; + break; // found it! + } + } + } + } + } + } else break; // no more subkeys or error occurred enumerating them + } + + // If there is no device, try to create one + bool creatingNewDevice = (_netCfgInstanceId.length() == 0); + if (creatingNewDevice) { + // Log devcon output to a file + HANDLE devconLog = CreateFileA((_pathToHelpers + "\\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); + + // Execute devcon to install an instance of the Microsoft Loopback Adapter + 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("\"") + _pathToHelpers + WindowsEthernetTapFactory::WINENV.devcon + "\" install \"" + _pathToHelpers + WindowsEthernetTapFactory::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 ") + WindowsEthernetTapFactory::WINENV.devcon); + } + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + + // Scan for the new instance by simply looking for taps that weren't originally there... + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + if (!strnicmp(data,"zttap",5)) { + type = 0; + dataLen = sizeof(data); + 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)); + _netCfgInstanceId.assign(data,dataLen); + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + _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,_netCfgInstanceId.c_str(),"EnableDHCP",REG_DWORD,&enable,sizeof(enable)); + RegCloseKey(tcpIpInterfaces); + } + + break; // found it! + } + } + } + } + } else break; // no more keys or error occurred + } + } + + if (_netCfgInstanceId.length() > 0) { + char tmps[64]; + unsigned int tmpsl = Utils::snprintf(tmps,sizeof(tmps),"%.2X-%.2X-%.2X-%.2X-%.2X-%.2X",(unsigned int)mac[0],(unsigned int)mac[1],(unsigned int)mac[2],(unsigned int)mac[3],(unsigned int)mac[4],(unsigned int)mac[5]) + 1; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"NetworkAddress",REG_SZ,tmps,tmpsl); + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"MAC",REG_SZ,tmps,tmpsl); + DWORD tmp = mtu; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"MTU",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + + tmp = 0; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"*NdisDeviceType",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + tmp = IF_TYPE_ETHERNET_CSMACD; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"*IfType",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + + if (creatingNewDevice) { + tmp = 0; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"EnableDHCP",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + } + RegCloseKey(nwAdapters); + } else { + RegCloseKey(nwAdapters); + throw std::runtime_error("unable to find or create tap adapter"); + } + + // Convert device GUID junk... blech... is there an easier way to do this? + { + char nobraces[128]; + const char *nbtmp1 = _netCfgInstanceId.c_str(); + char *nbtmp2 = nobraces; + while (*nbtmp1) { + if ((*nbtmp1 != '{')&&(*nbtmp1 != '}')) + *nbtmp2++ = *nbtmp1; + ++nbtmp1; + } + *nbtmp2 = (char)0; + if (UuidFromStringA((RPC_CSTR)nobraces,&_deviceGuid) != RPC_S_OK) + throw std::runtime_error("unable to convert instance ID GUID to native GUID (invalid NetCfgInstanceId in registry?)"); + } + + // Look up interface LUID... why are there (at least) four fucking ways to refer to a network device in Windows? + if (ConvertInterfaceGuidToLuid(&_deviceGuid,&_deviceLuid) != NO_ERROR) + throw std::runtime_error("unable to convert device interface GUID to LUID"); + + if (friendlyName) + setFriendlyName(friendlyName); + + // Start background thread that actually performs I/O + _injectSemaphore = CreateSemaphore(NULL,0,1,NULL); + _thread = Thread::start(this); + + // Certain functions can now work (e.g. ips()) + _initialized = true; +} + +WindowsEthernetTap::~WindowsEthernetTap() +{ + _run = false; + ReleaseSemaphore(_injectSemaphore,1,NULL); + Thread::join(_thread); + CloseHandle(_injectSemaphore); + _disableTapDevice(); +} + +void WindowsEthernetTap::setEnabled(bool en) +{ + _enabled = en; +} + +bool WindowsEthernetTap::enabled() const +{ + return _enabled; +} + +bool WindowsEthernetTap::addIP(const InetAddress &ip) +{ + if (!_initialized) + return false; + if (!ip.netmaskBits()) // sanity check... netmask of 0.0.0.0 is WUT? + return false; + + std::set<InetAddress> haveIps(ips()); + + try { + // Add IP to interface at the netlink level if not already assigned. + if (!haveIps.count(ip)) { + MIB_UNICASTIPADDRESS_ROW ipr; + + InitializeUnicastIpAddressEntry(&ipr); + if (ip.isV4()) { + ipr.Address.Ipv4.sin_family = AF_INET; + ipr.Address.Ipv4.sin_addr.S_un.S_addr = *((const uint32_t *)ip.rawIpData()); + ipr.OnLinkPrefixLength = ip.port(); + if (ipr.OnLinkPrefixLength >= 32) + return false; + } else if (ip.isV6()) { + ipr.Address.Ipv6.sin6_family = AF_INET6; + memcpy(ipr.Address.Ipv6.sin6_addr.u.Byte,ip.rawIpData(),16); + ipr.OnLinkPrefixLength = ip.port(); + if (ipr.OnLinkPrefixLength >= 128) + return false; + } else return false; + + ipr.PrefixOrigin = IpPrefixOriginManual; + ipr.SuffixOrigin = IpSuffixOriginManual; + ipr.ValidLifetime = 0xffffffff; + ipr.PreferredLifetime = 0xffffffff; + + ipr.InterfaceLuid = _deviceLuid; + ipr.InterfaceIndex = _getDeviceIndex(); + + if (CreateUnicastIpAddressEntry(&ipr) == NO_ERROR) { + haveIps.insert(ip); + } else { + return false; + } + } + + std::vector<std::string> regIps(_getRegistryIPv4Value("IPAddress")); + if (std::find(regIps.begin(),regIps.end(),ip.toIpString()) == regIps.end()) { + std::vector<std::string> regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); + regIps.push_back(ip.toIpString()); + regSubnetMasks.push_back(ip.netmask().toIpString()); + _setRegistryIPv4Value("IPAddress",regIps); + _setRegistryIPv4Value("SubnetMask",regSubnetMasks); + } + //_syncIpsWithRegistry(haveIps,_netCfgInstanceId); + } catch ( ... ) { + return false; + } + return true; +} + +bool WindowsEthernetTap::removeIP(const InetAddress &ip) +{ + if (!_initialized) + return false; + try { + MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; + if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { + for(DWORD i=0;i<ipt->NumEntries;++i) { + if (ipt->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { + InetAddress addr; + switch(ipt->Table[i].Address.si_family) { + case AF_INET: + addr.set(&(ipt->Table[i].Address.Ipv4.sin_addr.S_un.S_addr),4,ipt->Table[i].OnLinkPrefixLength); + break; + case AF_INET6: + addr.set(ipt->Table[i].Address.Ipv6.sin6_addr.u.Byte,16,ipt->Table[i].OnLinkPrefixLength); + if (addr.isLinkLocal()) + continue; // can't remove link-local IPv6 addresses + break; + } + if (addr == ip) { + DeleteUnicastIpAddressEntry(&(ipt->Table[i])); + FreeMibTable(ipt); + + std::vector<std::string> regIps(_getRegistryIPv4Value("IPAddress")); + std::vector<std::string> regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); + std::string ipstr(ip.toIpString()); + for(std::vector<std::string>::iterator rip(regIps.begin()),rm(regSubnetMasks.begin());((rip!=regIps.end())&&(rm!=regSubnetMasks.end()));++rip,++rm) { + if (*rip == ipstr) { + regIps.erase(rip); + regSubnetMasks.erase(rm); + _setRegistryIPv4Value("IPAddress",regIps); + _setRegistryIPv4Value("SubnetMask",regSubnetMasks); + break; + } + } + + return true; + } + } + } + FreeMibTable((PVOID)ipt); + } + } catch ( ... ) {} + return false; +} + +std::set<InetAddress> WindowsEthernetTap::ips() const +{ + static const InetAddress linkLocalLoopback("fe80::1",64); // what is this and why does Windows assign it? + std::set<InetAddress> addrs; + + if (!_initialized) + return addrs; + + try { + MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; + if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { + for(DWORD i=0;i<ipt->NumEntries;++i) { + if (ipt->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { + switch(ipt->Table[i].Address.si_family) { + case AF_INET: { + InetAddress ip(&(ipt->Table[i].Address.Ipv4.sin_addr.S_un.S_addr),4,ipt->Table[i].OnLinkPrefixLength); + if (ip != InetAddress::LO4) + addrs.insert(ip); + } break; + case AF_INET6: { + InetAddress ip(ipt->Table[i].Address.Ipv6.sin6_addr.u.Byte,16,ipt->Table[i].OnLinkPrefixLength); + if ((ip != linkLocalLoopback)&&(ip != InetAddress::LO6)) + addrs.insert(ip); + } break; + } + } + } + FreeMibTable(ipt); + } + } catch ( ... ) {} // sanity check, shouldn't happen unless out of memory + + return addrs; +} + +void WindowsEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + if ((!_initialized)||(!_enabled)||(_tap == INVALID_HANDLE_VALUE)||(len > (ZT_IF_MTU))) + return; + + Mutex::Lock _l(_injectPending_m); + _injectPending.push( std::pair<Array<char,ZT_IF_MTU + 32>,unsigned int>(Array<char,ZT_IF_MTU + 32>(),len + 14) ); + char *d = _injectPending.back().first.data; + to.copyTo(d,6); + from.copyTo(d + 6,6); + d[12] = (char)((etherType >> 8) & 0xff); + d[13] = (char)(etherType & 0xff); + memcpy(d + 14,data,len); + + ReleaseSemaphore(_injectSemaphore,1,NULL); +} + +std::string WindowsEthernetTap::deviceName() const +{ + char tmp[1024]; + if (ConvertInterfaceLuidToNameA(&_deviceLuid,tmp,sizeof(tmp)) != NO_ERROR) + return std::string("[ConvertInterfaceLuidToName() failed]"); + return std::string(tmp); +} + +void WindowsEthernetTap::setFriendlyName(const char *dn) +{ + if (!_initialized) + return; + HKEY ifp; + 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 WindowsEthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups) +{ + if (!_initialized) + return false; + HANDLE t = _tap; + if (t == INVALID_HANDLE_VALUE) + return false; + + std::set<MulticastGroup> newGroups; + + // Ensure that groups are added for each IP... this handles the MAC:ADI + // groups that are created from IPv4 addresses. Some of these may end + // up being duplicates of what the IOCTL returns but that's okay since + // the set<> will filter that. + std::set<InetAddress> ipaddrs(ips()); + for(std::set<InetAddress>::const_iterator i(ipaddrs.begin());i!=ipaddrs.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + + // The ZT1 tap driver supports an IOCTL to get multicast memberships at the L2 + // level... something Windows does not seem to expose ordinarily. This lets + // pretty much anything work... IPv4, IPv6, IPX, oldskool Netbios, who knows... + unsigned char mcastbuf[TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE]; + DWORD bytesReturned = 0; + if (DeviceIoControl(t,TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS,(LPVOID)0,0,(LPVOID)mcastbuf,sizeof(mcastbuf),&bytesReturned,NULL)) { + MAC mac; + DWORD i = 0; + while ((i + 6) <= bytesReturned) { + mac.setTo(mcastbuf + i,6); + i += 6; + if ((mac.isMulticast())&&(!mac.isBroadcast())) { + // exclude the nulls that may be returned or any other junk Windows puts in there + newGroups.insert(MulticastGroup(mac,0)); + } + } + } + + bool changed = false; + + for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +bool WindowsEthernetTap::injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + return false; +} + +void WindowsEthernetTap::threadMain() + throw() +{ + char tapPath[256]; + OVERLAPPED tapOvlRead,tapOvlWrite; + HANDLE wait4[3]; + char *tapReadBuf = (char *)0; + + // Shouldn't be needed, but Windows does not overcommit. This Windows + // tap code is defensive to schizoid paranoia degrees. + while (!tapReadBuf) { + tapReadBuf = (char *)::malloc(ZT_IF_MTU + 32); + if (!tapReadBuf) + Sleep(1000); + } + + // Tap is in this weird Windows global pseudo file space + Utils::snprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_netCfgInstanceId.c_str()); + + /* More insanity: repetatively try to enable/disable tap device. The first + * time we succeed, close it and do it again. This is to fix a driver init + * bug that seems to be extremely non-deterministic and to only occur after + * headless MSI upgrade. It cannot be reproduced in any other circumstance. + * + * Eventually when ZeroTier has actual money we will have someone create an + * NDIS6 tap driver. Yes, we'll likely be cool and open source it. */ + bool throwOneAway = true; + while (_run) { + _disableTapDevice(); + Sleep(250); + if (!_enableTapDevice()) { + ::free(tapReadBuf); + _enabled = false; + return; // only happens if devcon is missing or totally fails + } + Sleep(250); + + _tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL); + if (_tap == INVALID_HANDLE_VALUE) { + Sleep(500); + continue; + } + + { + uint32_t tmpi = 1; + DWORD bytesReturned = 0; + DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL); + bytesReturned = 0; + DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL); + } + + { +#ifdef ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE + /* This inserts a fake default route and a fake ARP entry, forcing + * Windows to detect this as a "real" network and apply proper + * firewall rules. + * + * This hack is completely stupid, but Windows made me do it + * by being broken and insane. + * + * Background: Windows tries to detect its network location by + * matching it to the ARP address of the default route. Networks + * without default routes are "unidentified networks" and cannot + * have their firewall classification changed by the user (easily). + * + * Yes, you read that right. + * + * The common workaround is to set *NdisDeviceType to 1, which + * totally disables all Windows firewall functionality. This is + * the answer you'll find on most forums for things like OpenVPN. + * + * Yes, you read that right. + * + * The default route workaround is also known, but for this to + * work there must be a known default IP that resolves to a known + * ARP address. This works for an OpenVPN tunnel, but not here + * because this isn't a tunnel. It's a mesh. There is no "other + * end," or any other known always on IP. + * + * So let's make a fake one and shove it in there along with its + * fake static ARP entry. Also makes it instant-on and static. + * + * We'll have to see what DHCP does with this. In the future we + * probably will not want to do this on DHCP-enabled networks, so + * when we enable DHCP we will go in and yank this wacko hacko from + * the routing table before doing so. + * + * Like Jesse Pinkman would say: "YEEEEAAH BITCH!" */ + const uint32_t fakeIp = htonl(0x19fffffe); // 25.255.255.254 -- unrouted IPv4 block + for(int i=0;i<8;++i) { + MIB_IPNET_ROW2 ipnr; + memset(&ipnr,0,sizeof(ipnr)); + ipnr.Address.si_family = AF_INET; + ipnr.Address.Ipv4.sin_addr.s_addr = fakeIp; + ipnr.InterfaceLuid.Value = _deviceLuid.Value; + ipnr.PhysicalAddress[0] = _mac[0] ^ 0x10; // just make something up that's consistent and not part of this net + ipnr.PhysicalAddress[1] = 0x00; + ipnr.PhysicalAddress[2] = (UCHAR)((_deviceGuid.Data1 >> 24) & 0xff); + ipnr.PhysicalAddress[3] = (UCHAR)((_deviceGuid.Data1 >> 16) & 0xff); + ipnr.PhysicalAddress[4] = (UCHAR)((_deviceGuid.Data1 >> 8) & 0xff); + ipnr.PhysicalAddress[5] = (UCHAR)(_deviceGuid.Data1 & 0xff); + ipnr.PhysicalAddressLength = 6; + ipnr.State = NlnsPermanent; + ipnr.IsRouter = 1; + ipnr.IsUnreachable = 0; + ipnr.ReachabilityTime.LastReachable = 0x0fffffff; + ipnr.ReachabilityTime.LastUnreachable = 1; + DWORD result = CreateIpNetEntry2(&ipnr); + if (result != NO_ERROR) + Sleep(500); + else break; + } + for(int i=0;i<8;++i) { + MIB_IPFORWARD_ROW2 nr; + memset(&nr,0,sizeof(nr)); + InitializeIpForwardEntry(&nr); + nr.InterfaceLuid.Value = _deviceLuid.Value; + nr.DestinationPrefix.Prefix.si_family = AF_INET; // rest is left as 0.0.0.0/0 + nr.NextHop.si_family = AF_INET; + nr.NextHop.Ipv4.sin_addr.s_addr = fakeIp; + nr.Metric = 9999; // do not use as real default route + nr.Protocol = MIB_IPPROTO_NETMGMT; + DWORD result = CreateIpForwardEntry2(&nr); + if (result != NO_ERROR) + Sleep(500); + else break; + } +#endif + } + + if (throwOneAway) { + throwOneAway = false; + CloseHandle(_tap); + _tap = INVALID_HANDLE_VALUE; + Sleep(1000); + continue; + } else break; + } + + memset(&tapOvlRead,0,sizeof(tapOvlRead)); + tapOvlRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); + memset(&tapOvlWrite,0,sizeof(tapOvlWrite)); + tapOvlWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); + + wait4[0] = _injectSemaphore; + wait4[1] = tapOvlRead.hEvent; + wait4[2] = tapOvlWrite.hEvent; // only included if writeInProgress is true + + // Start overlapped read, which is always active + ReadFile(_tap,tapReadBuf,sizeof(tapReadBuf),NULL,&tapOvlRead); + bool writeInProgress = false; + + for(;;) { + if (!_run) break; + DWORD r = WaitForMultipleObjectsEx(writeInProgress ? 3 : 2,wait4,FALSE,5000,TRUE); + if (!_run) break; + + if ((r == WAIT_TIMEOUT)||(r == WAIT_FAILED)) + continue; + + if (HasOverlappedIoCompleted(&tapOvlRead)) { + DWORD bytesRead = 0; + if (GetOverlappedResult(_tap,&tapOvlRead,&bytesRead,FALSE)) { + if ((bytesRead > 14)&&(_enabled)) { + MAC to(tapReadBuf,6); + MAC from(tapReadBuf + 6,6); + 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,ZT_IF_MTU + 32,NULL,&tapOvlRead); + } + + if (writeInProgress) { + if (HasOverlappedIoCompleted(&tapOvlWrite)) { + writeInProgress = false; + _injectPending_m.lock(); + _injectPending.pop(); + } else continue; // still writing, so skip code below and wait + } else _injectPending_m.lock(); + + if (!_injectPending.empty()) { + WriteFile(_tap,_injectPending.front().first.data,_injectPending.front().second,NULL,&tapOvlWrite); + writeInProgress = true; + } + + _injectPending_m.unlock(); + } + + CancelIo(_tap); + + CloseHandle(tapOvlRead.hEvent); + CloseHandle(tapOvlWrite.hEvent); + CloseHandle(_tap); + _tap = INVALID_HANDLE_VALUE; + + ::free(tapReadBuf); +} + +bool WindowsEthernetTap::_disableTapDevice() +{ + HANDLE devconLog = CreateFileA((_pathToHelpers + "\\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("\"") + _pathToHelpers + WindowsEthernetTapFactory::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; +} + +bool WindowsEthernetTap::_enableTapDevice() +{ + HANDLE devconLog = CreateFileA((_pathToHelpers + "\\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("\"") + _pathToHelpers + WindowsEthernetTapFactory::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; +} + +NET_IFINDEX WindowsEthernetTap::_getDeviceIndex() +{ + MIB_IF_TABLE2 *ift = (MIB_IF_TABLE2 *)0; + + if (GetIfTable2Ex(MibIfTableRaw,&ift) != NO_ERROR) + throw std::runtime_error("GetIfTable2Ex() failed"); + + for(ULONG i=0;i<ift->NumEntries;++i) { + if (ift->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { + NET_IFINDEX idx = ift->Table[i].InterfaceIndex; + FreeMibTable(ift); + return idx; + } + } + + FreeMibTable(&ift); + + throw std::runtime_error("interface not found"); +} + +std::vector<std::string> WindowsEthernetTap::_getRegistryIPv4Value(const char *regKey) +{ + std::vector<std::string> value; + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + char buf[16384]; + DWORD len = sizeof(buf); + DWORD kt = REG_MULTI_SZ; + if (RegGetValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),regKey,0,&kt,&buf,&len) == ERROR_SUCCESS) { + switch(kt) { + case REG_SZ: + if (len > 0) + value.push_back(std::string(buf)); + break; + case REG_MULTI_SZ: { + for(DWORD k=0,s=0;k<len;++k) { + if (!buf[k]) { + if (s < k) { + value.push_back(std::string(buf + s)); + s = k + 1; + } else break; + } + } + } break; + } + } + RegCloseKey(tcpIpInterfaces); + } + return value; +} + +void WindowsEthernetTap::_setRegistryIPv4Value(const char *regKey,const std::vector<std::string> &value) +{ + std::string regMulti; + for(std::vector<std::string>::const_iterator s(value.begin());s!=value.end();++s) { + regMulti.append(*s); + regMulti.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 (regMulti.length() > 0) { + regMulti.push_back((char)0); + RegSetKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),regKey,REG_MULTI_SZ,regMulti.data(),(DWORD)regMulti.length()); + } else { + RegDeleteKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),regKey); + } + RegCloseKey(tcpIpInterfaces); + } +} + +} // namespace ZeroTier diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp new file mode 100644 index 00000000..08dc8d17 --- /dev/null +++ b/osdep/WindowsEthernetTap.hpp @@ -0,0 +1,115 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_WINDOWSETHERNETTAP_HPP +#define ZT_WINDOWSETHERNETTAP_HPP + +#include <stdio.h> +#include <stdlib.h> + +#include <ifdef.h> + +#include <string> +#include <queue> +#include <stdexcept> + +#include "../node/Constants.hpp" +#include "../node/EthernetTap.hpp" +#include "../node/Mutex.hpp" +#include "../node/Thread.hpp" +#include "../node/Array.hpp" +#include "../node/MulticastGroup.hpp" + +namespace ZeroTier { + +class WindowsEthernetTap : public EthernetTap +{ +public: + WindowsEthernetTap( + const char *pathToHelpers, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + + virtual ~WindowsEthernetTap(); + + virtual void setEnabled(bool en); + virtual bool enabled() const; + virtual bool addIP(const InetAddress &ip); + virtual bool removeIP(const InetAddress &ip); + virtual std::set<InetAddress> 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 void setFriendlyName(const char *friendlyName); + virtual bool updateMulticastGroups(std::set<MulticastGroup> &groups); + virtual bool injectPacketFromHost(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + + inline const NET_LUID &luid() const { return _deviceLuid; } + inline const GUID &guid() const { return _deviceGuid; } + inline const std::string &instanceId() const { return _deviceInstanceId; } + + void threadMain() + throw(); + +private: + bool _disableTapDevice(); + bool _enableTapDevice(); + NET_IFINDEX _getDeviceIndex(); // throws on failure + std::vector<std::string> _getRegistryIPv4Value(const char *regKey); + void _setRegistryIPv4Value(const char *regKey,const std::vector<std::string> &value); + + void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); + void *_arg; + uint64_t _nwid; + Thread _thread; + + volatile HANDLE _tap; + HANDLE _injectSemaphore; + + GUID _deviceGuid; + NET_LUID _deviceLuid; + std::string _netCfgInstanceId; // NetCfgInstanceId, a GUID + std::string _deviceInstanceId; // DeviceInstanceID, another kind of "instance ID" + + std::queue< std::pair< Array<char,ZT_IF_MTU + 32>,unsigned int > > _injectPending; + Mutex _injectPending_m; + + std::string _pathToHelpers; + + volatile bool _run; + volatile bool _initialized; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/WindowsEthernetTapFactory.cpp b/osdep/WindowsEthernetTapFactory.cpp new file mode 100644 index 00000000..996460a1 --- /dev/null +++ b/osdep/WindowsEthernetTapFactory.cpp @@ -0,0 +1,162 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "WindowsEthernetTapFactory.hpp" +#include "WindowsEthernetTap.hpp" + +namespace ZeroTier { + +WindowsEthernetTapFactory::Env::Env() +{ +#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 +} +const WindowsEthernetTapFactory::Env WindowsEthernetTapFactory::WINENV; + +WindowsEthernetTapFactory::WindowsEthernetTapFactory(const char *pathToHelpers) : + _pathToHelpers(pathToHelpers) +{ +} + +WindowsEthernetTapFactory::~WindowsEthernetTapFactory() +{ + Mutex::Lock _l(_devices_m); + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) + delete *d; +} + +EthernetTap *WindowsEthernetTapFactory::open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) +{ + Mutex::Lock _l(_devices_m); + EthernetTap *t = new WindowsEthernetTap(_pathToHelpers.c_str(),mac,mtu,metric,nwid,desiredDevice,friendlyName,handler,arg); + _devices.push_back(t); + return t; +} + +void WindowsEthernetTapFactory::close(EthernetTap *tap,bool destroyPersistentDevices) +{ + if (!tap) + return; + + std::string instanceId(((WindowsEthernetTap *)tap)->instanceId()); + Mutex::Lock _l(_devices_m); + + for(std::vector<EthernetTap *>::iterator d(_devices.begin());d!=_devices.end();++d) { + if (*d == tap) { + _devices.erase(d); + break; + } + } + + delete tap; + + if (destroyPersistentDevices) + _deletePersistentTapDevice(_pathToHelpers.c_str(),instanceId.c_str()); +} + +void WindowsEthernetTapFactory::destroyAllPersistentTapDevices(const char *pathToHelpers) +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + std::set<std::string> instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return; + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + if (!strnicmp(data,"zttap",5)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + for(std::set<std::string>::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) + _deletePersistentTapDevice(pathToHelpers,iidp->c_str()); +} + +void WindowsEthernetTapFactory::_deletePersistentTapDevice(const char *pathToHelpers,const char *instanceId) +{ + HANDLE devconLog = CreateFileA((std::string(pathToHelpers) + "\\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("\"") + pathToHelpers + WINENV.devcon + "\" remove @" + instanceId).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); +} + +} // namespace ZeroTier diff --git a/osdep/WindowsEthernetTapFactory.hpp b/osdep/WindowsEthernetTapFactory.hpp new file mode 100644 index 00000000..47e146e3 --- /dev/null +++ b/osdep/WindowsEthernetTapFactory.hpp @@ -0,0 +1,90 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_WINDOWSETHERNETTAPFACTORY_HPP +#define ZT_WINDOWSETHERNETTAPFACTORY_HPP + +#include <vector> +#include <string> + +#include "../node/EthernetTapFactory.hpp" +#include "../node/Mutex.hpp" + +namespace ZeroTier { + +class WindowsEthernetTapFactory : public EthernetTapFactory +{ +public: + class Env + { + public: + Env(); + BOOL is64Bit; // true if WIN64 or WoW64 (32-bit binary on 64-bit architecture) + const char *devcon; // name of devcon binary in pathToHelpers to use + const char *tapDriver; // relative path to driver under pathToHelpers to use + }; + + /** + * Constants related to Windows environment, computed on program start + */ + static const Env WINENV; + + /** + * @param pathToHelpers Path to devcon32.exe, devcon64.exe, and other required helper binaries (ZeroTier running directory) + */ + WindowsEthernetTapFactory(const char *pathToHelpers); + virtual ~WindowsEthernetTapFactory(); + + virtual EthernetTap *open( + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *desiredDevice, + const char *friendlyName, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg); + virtual void close(EthernetTap *tap,bool destroyPersistentDevices); + + /** + * Uninstalls all persistent tap devices in the system belonging to ZeroTier + * + * This is for uninstallation. Do not call this while tap devices are active. + */ + static void destroyAllPersistentTapDevices(const char *pathToHelpers); + +private: + static void _deletePersistentTapDevice(const char *pathToHelpers,const char *instanceId); + + std::string _pathToHelpers; + std::vector<EthernetTap *> _devices; + Mutex _devices_m; +}; + +} // namespace ZeroTier + +#endif |