summaryrefslogtreecommitdiff
path: root/osdep
diff options
context:
space:
mode:
authorAdam Ierymenko <adam.ierymenko@gmail.com>2015-03-31 14:10:02 -0700
committerAdam Ierymenko <adam.ierymenko@gmail.com>2015-03-31 14:10:02 -0700
commit8990fb8267b5ed5677d61a421e0dfba41e365058 (patch)
tree7f8423f7f0f0c99e715678e0abbdeb85e4bc28ed /osdep
parent2c5dbecb3c1e9d8257c1c80eac7fcae5fb51b508 (diff)
downloadinfinitytier-8990fb8267b5ed5677d61a421e0dfba41e365058.tar.gz
infinitytier-8990fb8267b5ed5677d61a421e0dfba41e365058.zip
osnet -> osdep
Diffstat (limited to 'osdep')
-rw-r--r--osdep/BSDEthernetTap.cpp429
-rw-r--r--osdep/BSDEthernetTap.hpp84
-rw-r--r--osdep/BSDEthernetTapFactory.cpp78
-rw-r--r--osdep/BSDEthernetTapFactory.hpp63
-rw-r--r--osdep/LinuxEthernetTap.cpp424
-rw-r--r--osdep/LinuxEthernetTap.hpp85
-rw-r--r--osdep/LinuxEthernetTapFactory.cpp74
-rw-r--r--osdep/LinuxEthernetTapFactory.hpp63
-rw-r--r--osdep/NativeSocketManager.cpp989
-rw-r--r--osdep/NativeSocketManager.hpp117
-rw-r--r--osdep/OSXEthernetTap.cpp639
-rw-r--r--osdep/OSXEthernetTap.hpp91
-rw-r--r--osdep/OSXEthernetTapFactory.cpp122
-rw-r--r--osdep/OSXEthernetTapFactory.hpp76
-rw-r--r--osdep/Phy.hpp817
-rw-r--r--osdep/README.md6
-rw-r--r--osdep/WindowsEthernetTap.cpp867
-rw-r--r--osdep/WindowsEthernetTap.hpp115
-rw-r--r--osdep/WindowsEthernetTapFactory.cpp162
-rw-r--r--osdep/WindowsEthernetTapFactory.hpp90
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