From 538e8a86c8b907222303ebec0b97a6a9a87fd849 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Sat, 26 Jul 2014 13:10:41 -0700 Subject: Move node/osdep/ to sys/ --- node/osdep/BSDRoutingTable.cpp | 329 ------------ node/osdep/BSDRoutingTable.hpp | 51 -- node/osdep/LinuxEthernetTap.cpp | 474 ----------------- node/osdep/LinuxEthernetTap.hpp | 85 ---- node/osdep/LinuxEthernetTapFactory.cpp | 83 --- node/osdep/LinuxEthernetTapFactory.hpp | 64 --- node/osdep/LinuxRoutingTable.cpp | 247 --------- node/osdep/LinuxRoutingTable.hpp | 49 -- node/osdep/OSXEthernetTap.cpp | 634 ----------------------- node/osdep/OSXEthernetTap.hpp | 89 ---- node/osdep/OSXEthernetTapFactory.cpp | 126 ----- node/osdep/OSXEthernetTapFactory.hpp | 77 --- node/osdep/WindowsEthernetTap.cpp | 837 ------------------------------- node/osdep/WindowsEthernetTap.hpp | 130 ----- node/osdep/WindowsEthernetTapFactory.cpp | 0 node/osdep/WindowsEthernetTapFactory.hpp | 0 node/osdep/WindowsRoutingTable.cpp | 0 node/osdep/WindowsRoutingTable.hpp | 0 sys/BSDRoutingTable.cpp | 329 ++++++++++++ sys/BSDRoutingTable.hpp | 51 ++ sys/LinuxEthernetTap.cpp | 474 +++++++++++++++++ sys/LinuxEthernetTap.hpp | 85 ++++ sys/LinuxEthernetTapFactory.cpp | 83 +++ sys/LinuxEthernetTapFactory.hpp | 64 +++ sys/LinuxRoutingTable.cpp | 247 +++++++++ sys/LinuxRoutingTable.hpp | 49 ++ sys/OSXEthernetTap.cpp | 634 +++++++++++++++++++++++ sys/OSXEthernetTap.hpp | 89 ++++ sys/OSXEthernetTapFactory.cpp | 126 +++++ sys/OSXEthernetTapFactory.hpp | 77 +++ sys/WindowsEthernetTap.cpp | 837 +++++++++++++++++++++++++++++++ sys/WindowsEthernetTap.hpp | 130 +++++ sys/WindowsEthernetTapFactory.cpp | 0 sys/WindowsEthernetTapFactory.hpp | 0 sys/WindowsRoutingTable.cpp | 0 sys/WindowsRoutingTable.hpp | 0 36 files changed, 3275 insertions(+), 3275 deletions(-) delete mode 100644 node/osdep/BSDRoutingTable.cpp delete mode 100644 node/osdep/BSDRoutingTable.hpp delete mode 100644 node/osdep/LinuxEthernetTap.cpp delete mode 100644 node/osdep/LinuxEthernetTap.hpp delete mode 100644 node/osdep/LinuxEthernetTapFactory.cpp delete mode 100644 node/osdep/LinuxEthernetTapFactory.hpp delete mode 100644 node/osdep/LinuxRoutingTable.cpp delete mode 100644 node/osdep/LinuxRoutingTable.hpp delete mode 100644 node/osdep/OSXEthernetTap.cpp delete mode 100644 node/osdep/OSXEthernetTap.hpp delete mode 100644 node/osdep/OSXEthernetTapFactory.cpp delete mode 100644 node/osdep/OSXEthernetTapFactory.hpp delete mode 100644 node/osdep/WindowsEthernetTap.cpp delete mode 100644 node/osdep/WindowsEthernetTap.hpp delete mode 100644 node/osdep/WindowsEthernetTapFactory.cpp delete mode 100644 node/osdep/WindowsEthernetTapFactory.hpp delete mode 100644 node/osdep/WindowsRoutingTable.cpp delete mode 100644 node/osdep/WindowsRoutingTable.hpp create mode 100644 sys/BSDRoutingTable.cpp create mode 100644 sys/BSDRoutingTable.hpp create mode 100644 sys/LinuxEthernetTap.cpp create mode 100644 sys/LinuxEthernetTap.hpp create mode 100644 sys/LinuxEthernetTapFactory.cpp create mode 100644 sys/LinuxEthernetTapFactory.hpp create mode 100644 sys/LinuxRoutingTable.cpp create mode 100644 sys/LinuxRoutingTable.hpp create mode 100644 sys/OSXEthernetTap.cpp create mode 100644 sys/OSXEthernetTap.hpp create mode 100644 sys/OSXEthernetTapFactory.cpp create mode 100644 sys/OSXEthernetTapFactory.hpp create mode 100644 sys/WindowsEthernetTap.cpp create mode 100644 sys/WindowsEthernetTap.hpp create mode 100644 sys/WindowsEthernetTapFactory.cpp create mode 100644 sys/WindowsEthernetTapFactory.hpp create mode 100644 sys/WindowsRoutingTable.cpp create mode 100644 sys/WindowsRoutingTable.hpp diff --git a/node/osdep/BSDRoutingTable.cpp b/node/osdep/BSDRoutingTable.cpp deleted file mode 100644 index fede9cce..00000000 --- a/node/osdep/BSDRoutingTable.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "../Constants.hpp" -#include "BSDRoutingTable.hpp" - -// All I wanted was the bloody rounting table. I didn't expect the Spanish inquisition. - -#define ZT_BSD_ROUTE_CMD "/sbin/route" - -namespace ZeroTier { - -BSDRoutingTable::BSDRoutingTable() -{ -} - -BSDRoutingTable::~BSDRoutingTable() -{ -} - -std::vector BSDRoutingTable::get(bool includeLinkLocal,bool includeLoopback) const -{ - std::vector entries; - int mib[6]; - size_t needed; - - mib[0] = CTL_NET; - mib[1] = PF_ROUTE; - mib[2] = 0; - mib[3] = 0; - mib[4] = NET_RT_DUMP; - mib[5] = 0; - if (!sysctl(mib,6,NULL,&needed,NULL,0)) { - if (needed <= 0) - return entries; - - char *buf = (char *)::malloc(needed); - if (buf) { - if (!sysctl(mib,6,buf,&needed,NULL,0)) { - struct rt_msghdr *rtm; - for(char *next=buf,*end=buf+needed;nextrtm_msglen; - - if (((rtm->rtm_flags & RTF_LLINFO) == 0)&&((rtm->rtm_flags & RTF_HOST) == 0)&&((rtm->rtm_flags & RTF_UP) != 0)&&((rtm->rtm_flags & RTF_MULTICAST) == 0)) { - RoutingTable::Entry e; - e.deviceIndex = -9999; // unset - - int which = 0; - while (saptr < saend) { - struct sockaddr *sa = (struct sockaddr *)saptr; - unsigned int salen = sa->sa_len; - if (!salen) - break; - - // Skip missing fields in rtm_addrs bit field - while ((rtm->rtm_addrs & 1) == 0) { - rtm->rtm_addrs >>= 1; - ++which; - if (which > 6) - break; - } - if (which > 6) - break; - - rtm->rtm_addrs >>= 1; - switch(which++) { - case 0: - //printf("RTA_DST\n"); - if (sa->sa_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; - // Nobody expects the Spanish inquisition! - if ((sin6->sin6_addr.s6_addr[0] == 0xfe)&&((sin6->sin6_addr.s6_addr[1] & 0xc0) == 0x80)) { - // Our chief weapon is... in-band signaling! - // Seriously who in the living fuck thought this was a good idea and - // then had the sadistic idea to not document it anywhere? Of course it's - // not like there is any documentation on BSD sysctls anyway. - unsigned int interfaceIndex = ((((unsigned int)sin6->sin6_addr.s6_addr[2]) << 8) & 0xff) | (((unsigned int)sin6->sin6_addr.s6_addr[3]) & 0xff); - sin6->sin6_addr.s6_addr[2] = 0; - sin6->sin6_addr.s6_addr[3] = 0; - if (!sin6->sin6_scope_id) - sin6->sin6_scope_id = interfaceIndex; - } - } - e.destination.set(sa); - break; - case 1: - //printf("RTA_GATEWAY\n"); - switch(sa->sa_family) { - case AF_LINK: - e.deviceIndex = (int)((const struct sockaddr_dl *)sa)->sdl_index; - break; - case AF_INET: - case AF_INET6: - e.gateway.set(sa); - break; - } - break; - case 2: { - if (e.destination.isV6()) { - salen = sizeof(struct sockaddr_in6); // Confess! - unsigned int bits = 0; - for(int i=0;i<16;++i) { - unsigned char c = (unsigned char)((const struct sockaddr_in6 *)sa)->sin6_addr.s6_addr[i]; - if (c == 0xff) - bits += 8; - else break; - /* must they be multiples of 8? Most of the BSD source I can find says yes..? - else { - while ((c & 0x80) == 0x80) { - ++bits; - c <<= 1; - } - break; - } - */ - } - e.destination.setPort(bits); - } else { - salen = sizeof(struct sockaddr_in); // Confess! - e.destination.setPort((unsigned int)Utils::countBits((uint32_t)((const struct sockaddr_in *)sa)->sin_addr.s_addr)); - } - //printf("RTA_NETMASK\n"); - } break; - /* - case 3: - //printf("RTA_GENMASK\n"); - break; - case 4: - //printf("RTA_IFP\n"); - break; - case 5: - //printf("RTA_IFA\n"); - break; - case 6: - //printf("RTA_AUTHOR\n"); - break; - */ - } - - saptr += salen; - } - - e.metric = (int)rtm->rtm_rmx.rmx_hopcount; - if (e.metric < 0) - e.metric = 0; - - if (((includeLinkLocal)||(!e.destination.isLinkLocal()))&&((includeLoopback)||((!e.destination.isLoopback())&&(!e.gateway.isLoopback())))) - entries.push_back(e); - } - - next = saend; - } - } - - ::free(buf); - } - } - - for(std::vector::iterator e1(entries.begin());e1!=entries.end();++e1) { - if ((!e1->device[0])&&(e1->deviceIndex >= 0)) - if_indextoname(e1->deviceIndex,e1->device); - } - for(std::vector::iterator e1(entries.begin());e1!=entries.end();++e1) { - if ((!e1->device[0])&&(e1->gateway)) { - int bestMetric = 9999999; - for(std::vector::iterator e2(entries.begin());e2!=entries.end();++e2) { - if ((e1->gateway.within(e2->destination))&&(e2->metric <= bestMetric)) { - bestMetric = e2->metric; - Utils::scopy(e1->device,sizeof(e1->device),e2->device); - } - } - } - } - - std::sort(entries.begin(),entries.end()); - - return entries; -} - -RoutingTable::Entry BSDRoutingTable::set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric) -{ - if ((!gateway)&&((!device)||(!device[0]))) - return RoutingTable::Entry(); - - std::vector rtab(get(true,true)); - - for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { - if (e->destination == destination) { - if (((!device)||(!device[0]))||(!strcmp(device,e->device))) { - long p = (long)fork(); - if (p > 0) { - int exitcode = -1; - ::waitpid(p,&exitcode,0); - } else if (p == 0) { - ::close(STDOUT_FILENO); - ::close(STDERR_FILENO); - ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,"delete",(destination.isV6() ? "-inet6" : "-inet"),destination.toString().c_str(),(const char *)0); - ::_exit(-1); - } - } - } - } - - if (metric < 0) - return RoutingTable::Entry(); - - { - char hcstr[64]; - Utils::snprintf(hcstr,sizeof(hcstr),"%d",metric); - long p = (long)fork(); - if (p > 0) { - int exitcode = -1; - ::waitpid(p,&exitcode,0); - } else if (p == 0) { - ::close(STDOUT_FILENO); - ::close(STDERR_FILENO); - if (gateway) { - ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,"add",(destination.isV6() ? "-inet6" : "-inet"),destination.toString().c_str(),gateway.toIpString().c_str(),"-hopcount",hcstr,(const char *)0); - } else if ((device)&&(device[0])) { - ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,"add",(destination.isV6() ? "-inet6" : "-inet"),destination.toString().c_str(),"-interface",device,"-hopcount",hcstr,(const char *)0); - } - ::_exit(-1); - } - } - - rtab = get(true,true); - std::vector::iterator bestEntry(rtab.end()); - for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { - if ((e->destination == destination)&&(e->gateway.ipsEqual(gateway))) { - if ((device)&&(device[0])) { - if (!strcmp(device,e->device)) { - if (metric == e->metric) - bestEntry = e; - } - } - if (bestEntry == rtab.end()) - bestEntry = e; - } - } - if (bestEntry != rtab.end()) - return *bestEntry; - - return RoutingTable::Entry(); -} - -} // namespace ZeroTier - -// Enable and build to test routing table interface -#if 0 -using namespace ZeroTier; -int main(int argc,char **argv) -{ - BSDRoutingTable rt; - - printf(" \n"); - std::vector ents(rt.get()); - for(std::vector::iterator e(ents.begin());e!=ents.end();++e) - printf("%s\n",e->toString().c_str()); - printf("\n"); - - printf("adding 1.1.1.0 and 2.2.2.0...\n"); - rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,1); - rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",1); - printf("\n"); - - printf(" \n"); - ents = rt.get(); - for(std::vector::iterator e(ents.begin());e!=ents.end();++e) - printf("%s\n",e->toString().c_str()); - printf("\n"); - - printf("deleting 1.1.1.0 and 2.2.2.0...\n"); - rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,-1); - rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",-1); - printf("\n"); - - printf(" \n"); - ents = rt.get(); - for(std::vector::iterator e(ents.begin());e!=ents.end();++e) - printf("%s\n",e->toString().c_str()); - printf("\n"); - - return 0; -} -#endif diff --git a/node/osdep/BSDRoutingTable.hpp b/node/osdep/BSDRoutingTable.hpp deleted file mode 100644 index c0e98943..00000000 --- a/node/osdep/BSDRoutingTable.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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_BSDROUTINGTABLE_HPP -#define ZT_BSDROUTINGTABLE_HPP - -#include "../RoutingTable.hpp" - -namespace ZeroTier { - -/** - * Routing table interface for BSD with sysctl() and BSD /sbin/route - * - * Has currently only been tested on OSX/Darwin. - */ -class BSDRoutingTable : public RoutingTable -{ -public: - BSDRoutingTable(); - virtual ~BSDRoutingTable(); - virtual std::vector get(bool includeLinkLocal = false,bool includeLoopback = false) const; - virtual RoutingTable::Entry set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric); -}; - -} // namespace ZeroTier - -#endif diff --git a/node/osdep/LinuxEthernetTap.cpp b/node/osdep/LinuxEthernetTap.cpp deleted file mode 100644 index 0b0c1c4f..00000000 --- a/node/osdep/LinuxEthernetTap.cpp +++ /dev/null @@ -1,474 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "../Constants.hpp" -#include "../Utils.hpp" -#include "../Mutex.hpp" -#include "LinuxEthernetTap.hpp" - -// ff:ff:ff:ff:ff:ff with no ADI -static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - -// On startup, searches the path for required external utilities (which might vary by Linux distro) -#define ZT_UNIX_IP_COMMAND 1 -class _CommandFinder -{ -public: - _CommandFinder() - { - _findCmd(ZT_UNIX_IP_COMMAND,"ip"); - } - inline const char *operator[](int id) const - throw() - { - std::map::const_iterator c(_paths.find(id)); - if (c == _paths.end()) - return (const char *)0; - return c->second.c_str(); - } -private: - inline void _findCmd(int id,const char *name) - { - char tmp[4096]; - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/sbin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/sbin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/bin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/bin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - } - std::map _paths; -}; -static const _CommandFinder UNIX_COMMANDS; - -namespace ZeroTier { - -// Only permit one tap to be opened concurrently across the entire process -static Mutex __tapCreateLock; - -LinuxEthernetTap::LinuxEthernetTap( - const RuntimeEnvironment *renv, - const char *tryToGetDevice, - const MAC &mac, - unsigned int mtu, - void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), - void *arg) - throw(std::runtime_error) : - EthernetTap("LinuxEthernetTap",mac,mtu), - _r(renv), - _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 > 4096) - throw std::runtime_error("max tap MTU is 4096"); - - _fd = ::open("/dev/net/tun",O_RDWR); - if (_fd <= 0) - throw std::runtime_error(std::string("could not open TUN/TAP device: ") + strerror(errno)); - - struct ifreq ifr; - memset(&ifr,0,sizeof(ifr)); - - // Try to recall our last device name, or pick an unused one if that fails. - bool recalledDevice = false; - if ((tryToGetDevice)&&(tryToGetDevice[0])) { - Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),tryToGetDevice); - 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) -{ - const char *ipcmd = UNIX_COMMANDS[ZT_UNIX_IP_COMMAND]; - if (!ipcmd) - return false; - long cpid = (long)vfork(); - if (cpid == 0) { - execl(ipcmd,ipcmd,"addr","del",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } -} - -bool LinuxEthernetTap::addIP(const InetAddress &ip) -{ - const char *ipcmd = UNIX_COMMANDS[ZT_UNIX_IP_COMMAND]; - if (!ipcmd) { - LOG("ERROR: could not configure IP address for %s: unable to find 'ip' command on system (checked /sbin, /bin, /usr/sbin, /usr/bin)",_dev.c_str()); - return false; - } - - if (!ip) - return false; - - std::set allIps(ips()); - if (allIps.count(ip) > 0) - return true; - - // Remove and reconfigure if address is the same but netmask is different - for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { - if (i->ipsEqual(ip)) { - if (___removeIp(_dev,*i)) { - break; - } else { - LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); - } - } - } - - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ipcmd,ipcmd,"addr","add",ip.toString().c_str(),"dev",_dev.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 LinuxEthernetTap::ips() const -{ - struct ifaddrs *ifa = (struct ifaddrs *)0; - if (getifaddrs(&ifa)) - return std::set(); - - std::set r; - - struct ifaddrs *p = ifa; - while (p) { - if ((!strcmp(p->ifa_name,_dev.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; -} - -std::string LinuxEthernetTap::persistentId() const -{ - return std::string(); -} - -bool LinuxEthernetTap::updateMulticastGroups(std::set &groups) -{ - char *ptr,*ptr2; - unsigned char mac[6]; - std::set newGroups; - - int fd = ::open("/proc/net/dev_mcast",O_RDONLY); - if (fd > 0) { - char buf[131072]; - int n = (int)::read(fd,buf,sizeof(buf)); - if ((n > 0)&&(n < (int)sizeof(buf))) { - buf[n] = (char)0; - for(char *l=strtok_r(buf,"\r\n",&ptr);(l);l=strtok_r((char *)0,"\r\n",&ptr)) { - int fno = 0; - char *devname = (char *)0; - char *mcastmac = (char *)0; - for(char *f=strtok_r(l," \t",&ptr2);(f);f=strtok_r((char *)0," \t",&ptr2)) { - if (fno == 1) - devname = f; - else if (fno == 4) - mcastmac = f; - ++fno; - } - if ((devname)&&(!strcmp(devname,_dev.c_str()))&&(mcastmac)&&(Utils::unhex(mcastmac,mac,6) == 6)) - newGroups.insert(MulticastGroup(MAC(mac,6),0)); - } - } - ::close(fd); - } - - { - std::set allIps(ips()); - for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) - newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); - } - - bool changed = false; - - for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { - if (!groups.count(*mg)) { - groups.insert(*mg); - changed = true; - } - } - for(std::set::iterator mg(groups.begin());mg!=groups.end();) { - if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { - groups.erase(mg++); - changed = true; - } else ++mg; - } - - return changed; -} - -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)) { - TRACE("unexpected error reading from tap: %s",strerror(errno)); - break; - } - } else { - // Some tap drivers like to send the ethernet frame and the - // payload in two chunks, so handle that by accumulating - // data until we have at least a frame. - r += n; - if (r > 14) { - if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms - r = _mtu + 14; - - 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/node/osdep/LinuxEthernetTap.hpp b/node/osdep/LinuxEthernetTap.hpp deleted file mode 100644 index e6aea2ec..00000000 --- a/node/osdep/LinuxEthernetTap.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include - -#include - -#include "../EthernetTap.hpp" -#include "../Mutex.hpp" -#include "../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 ips() const; - virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); - virtual std::string deviceName() const; - virtual std::string persistentId() const; - virtual bool updateMulticastGroups(std::set &groups); - - 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/node/osdep/LinuxEthernetTapFactory.cpp b/node/osdep/LinuxEthernetTapFactory.cpp deleted file mode 100644 index fccb6bd5..00000000 --- a/node/osdep/LinuxEthernetTapFactory.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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::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) -{ - { - Mutex::Lock _l(_devices_m); - for(std::vector::iterator d(_devices.begin());d!=_devices.end();++d) { - if (*d == tap) { - _devices.erase(d); - break; - } - } - } - delete tap; -} - -std::vector allTapDeviceNames() const -{ - std::vector dn; - Mutex::Lock _l(_devices_m); - for(std::vector::const_iterator d(_devices.begin());d!=_devices.end();++d) - dn.push_back(d->deviceName()); - return dn; -} - -} // namespace ZeroTier diff --git a/node/osdep/LinuxEthernetTapFactory.hpp b/node/osdep/LinuxEthernetTapFactory.hpp deleted file mode 100644 index a5d13216..00000000 --- a/node/osdep/LinuxEthernetTapFactory.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include - -#include "../EthernetTapFactory.hpp" -#include "../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); - virtual std::vector allTapDeviceNames() const; - -private: - std::vector _devices; - Mutex _devices_m; -}; - -} // namespace ZeroTier - -#endif diff --git a/node/osdep/LinuxRoutingTable.cpp b/node/osdep/LinuxRoutingTable.cpp deleted file mode 100644 index 67a5b22d..00000000 --- a/node/osdep/LinuxRoutingTable.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "../Constants.hpp" -#include "../Utils.hpp" -#include "LinuxRoutingTable.hpp" - -#define ZT_LINUX_IP_COMMAND "/sbin/ip" - -namespace ZeroTier { - -LinuxRoutingTable::LinuxRoutingTable() -{ -} - -LinuxRoutingTable::~LinuxRoutingTable() -{ -} - -std::vector LinuxRoutingTable::get(bool includeLinkLocal,bool includeLoopback) const -{ - char buf[131072]; - char *stmp,*stmp2; - std::vector entries; - - { - int fd = ::open("/proc/net/route",O_RDONLY); - if (fd <= 0) - buf[0] = (char)0; - else { - int n = (int)::read(fd,buf,sizeof(buf) - 1); - ::close(fd); - if (n < 0) n = 0; - buf[n] = (char)0; - } - } - - int lineno = 0; - for(char *line=Utils::stok(buf,"\r\n",&stmp);(line);line=Utils::stok((char *)0,"\r\n",&stmp)) { - if (lineno == 0) { - ++lineno; - continue; // skip header - } - - char *iface = (char *)0; - uint32_t destination = 0; - uint32_t gateway = 0; - int metric = 0; - uint32_t mask = 0; - - int fno = 0; - for(char *f=Utils::stok(line,"\t \r\n",&stmp2);(f);f=Utils::stok((char *)0,"\t \r\n",&stmp2)) { - switch(fno) { - case 0: iface = f; break; - case 1: destination = (uint32_t)Utils::hexStrToULong(f); break; - case 2: gateway = (uint32_t)Utils::hexStrToULong(f); break; - case 6: metric = (int)Utils::strToInt(f); break; - case 7: mask = (uint32_t)Utils::hexStrToULong(f); break; - } - ++fno; - } - - if ((iface)&&(destination)) { - RoutingTable::Entry e; - if (destination) - e.destination.set(&destination,4,Utils::countBits(mask)); - e.gateway.set(&gateway,4,0); - e.deviceIndex = 0; // not used on Linux - e.metric = metric; - Utils::scopy(e.device,sizeof(e.device),iface); - if ((e.destination)&&((includeLinkLocal)||(!e.destination.isLinkLocal()))&&((includeLoopback)||((!e.destination.isLoopback())&&(!e.gateway.isLoopback())&&(strcmp(iface,"lo"))))) - entries.push_back(e); - } - - ++lineno; - } - - { - int fd = ::open("/proc/net/ipv6_route",O_RDONLY); - if (fd <= 0) - buf[0] = (char)0; - else { - int n = (int)::read(fd,buf,sizeof(buf) - 1); - ::close(fd); - if (n < 0) n = 0; - buf[n] = (char)0; - } - } - - for(char *line=Utils::stok(buf,"\r\n",&stmp);(line);line=Utils::stok((char *)0,"\r\n",&stmp)) { - char *destination = (char *)0; - unsigned int destPrefixLen = 0; - char *gateway = (char *)0; // next hop in ipv6 terminology - int metric = 0; - char *device = (char *)0; - - int fno = 0; - for(char *f=Utils::stok(line,"\t \r\n",&stmp2);(f);f=Utils::stok((char *)0,"\t \r\n",&stmp2)) { - switch(fno) { - case 0: destination = f; break; - case 1: destPrefixLen = (unsigned int)Utils::hexStrToULong(f); break; - case 4: gateway = f; break; - case 5: metric = (int)Utils::hexStrToLong(f); break; - case 9: device = f; break; - } - ++fno; - } - - if ((device)&&(destination)) { - unsigned char tmp[16]; - RoutingTable::Entry e; - Utils::unhex(destination,tmp,16); - if ((!Utils::isZero(tmp,16))&&(tmp[0] != 0xff)) - e.destination.set(tmp,16,destPrefixLen); - Utils::unhex(gateway,tmp,16); - e.gateway.set(tmp,16,0); - e.deviceIndex = 0; // not used on Linux - e.metric = metric; - Utils::scopy(e.device,sizeof(e.device),device); - if ((e.destination)&&((includeLinkLocal)||(!e.destination.isLinkLocal()))&&((includeLoopback)||((!e.destination.isLoopback())&&(!e.gateway.isLoopback())&&(strcmp(device,"lo"))))) - entries.push_back(e); - } - } - - std::sort(entries.begin(),entries.end()); - return entries; -} - -RoutingTable::Entry LinuxRoutingTable::set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric) -{ - if ((!gateway)&&((!device)||(!device[0]))) - return RoutingTable::Entry(); - - std::vector rtab(get(true,true)); - - for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { - if (e->destination == destination) { - if (((!device)||(!device[0]))||(!strcmp(device,e->device))) { - } - } - } - - if (metric < 0) - return RoutingTable::Entry(); - - - - rtab = get(true,true); - std::vector::iterator bestEntry(rtab.end()); - for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { - if ((e->destination == destination)&&(e->gateway.ipsEqual(gateway))) { - if ((device)&&(device[0])) { - if (!strcmp(device,e->device)) { - if (metric == e->metric) - bestEntry = e; - } - } - if (bestEntry == rtab.end()) - bestEntry = e; - } - } - if (bestEntry != rtab.end()) - return *bestEntry; - - return RoutingTable::Entry(); -} - -} // namespace ZeroTier - -// Enable and build to test routing table interface -//#if 0 -using namespace ZeroTier; -int main(int argc,char **argv) -{ - LinuxRoutingTable rt; - - printf(" \n"); - std::vector ents(rt.get()); - for(std::vector::iterator e(ents.begin());e!=ents.end();++e) - printf("%s\n",e->toString().c_str()); - printf("\n"); - - printf("adding 1.1.1.0 and 2.2.2.0...\n"); - rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,1); - rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",1); - printf("\n"); - - printf(" \n"); - ents = rt.get(); - for(std::vector::iterator e(ents.begin());e!=ents.end();++e) - printf("%s\n",e->toString().c_str()); - printf("\n"); - - printf("deleting 1.1.1.0 and 2.2.2.0...\n"); - rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,-1); - rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",-1); - printf("\n"); - - printf(" \n"); - ents = rt.get(); - for(std::vector::iterator e(ents.begin());e!=ents.end();++e) - printf("%s\n",e->toString().c_str()); - printf("\n"); - - return 0; -} -//#endif diff --git a/node/osdep/LinuxRoutingTable.hpp b/node/osdep/LinuxRoutingTable.hpp deleted file mode 100644 index 9a5d3fd6..00000000 --- a/node/osdep/LinuxRoutingTable.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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_LINUXROUTINGTABLE_HPP -#define ZT_LINUXROUTINGTABLE_HPP - -#include "../RoutingTable.hpp" - -namespace ZeroTier { - -/** - * Routing table interface via /proc/net/route, /proc/net/ipv6_route, and /sbin/route command - */ -class LinuxRoutingTable : public RoutingTable -{ -public: - LinuxRoutingTable(); - virtual ~LinuxRoutingTable(); - virtual std::vector get(bool includeLinkLocal = false,bool includeLoopback = false) const; - virtual RoutingTable::Entry set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric); -}; - -} // namespace ZeroTier - -#endif diff --git a/node/osdep/OSXEthernetTap.cpp b/node/osdep/OSXEthernetTap.cpp deleted file mode 100644 index 1e2ddbed..00000000 --- a/node/osdep/OSXEthernetTap.cpp +++ /dev/null @@ -1,634 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ -struct prf_ra { - u_char onlink : 1; - u_char autonomous : 1; - u_char reserved : 6; -} prf_ra; - -// 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 */ -/* 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 -#include -#include -#include - -#include "../Constants.hpp" -#include "../Utils.hpp" -#include "../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 { - -// Only permit one tap to be opened concurrently across the entire process -static Mutex __tapCreateLock; - -OSXEthernetTap::OSXEthernetTap( - const RuntimeEnvironment *renv, - const char *tryToGetDevice, - const MAC &mac, - unsigned int mtu, - void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), - void *arg) - throw(std::runtime_error) : - EthernetTap("OSXEthernetTap",mac,mtu,metric), - _r(renv), - _handler(handler), - _arg(arg), - _fd(0), - _enabled(true) -{ - char devpath[64],ethaddr[64],mtustr[32],metstr[32]; - struct stat stattmp; - Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally - - 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 and unable to load kernel extension"); - - // Try to reopen the last device we had, if we had one and it's still unused. - bool recalledDevice = false; - if ((tryToGetDevice)&&(tryToGetDevice[0])) { - Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",tryToGetDevice); - if (stat(devpath,&stattmp) == 0) { - _fd = ::open(devpath,O_RDWR); - if (_fd > 0) { - _dev = tryToGetDevice; - recalledDevice = true; - } - } - } - - // Open the first unused tap device if we didn't recall a previous one. - if (!recalledDevice) { - for(int i=0;i<256;++i) { - Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i); - if (stat(devpath,&stattmp)) - throw std::runtime_error("no more TAP devices available"); - _fd = ::open(devpath,O_RDWR); - if (_fd > 0) { - 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 (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 allIps(ips()); - if (allIps.count(ip) > 0) - return true; // IP/netmask already assigned - - // Remove and reconfigure if address is the same but netmask is different - for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { - if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { - if (___removeIp(_dev,*i)) { - break; - } else { - LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); - } - } - } - - long cpid = (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 OSXEthernetTap::ips() const -{ - struct ifaddrs *ifa = (struct ifaddrs *)0; - if (getifaddrs(&ifa)) - return std::set(); - - std::set r; - - struct ifaddrs *p = ifa; - while (p) { - if ((!strcmp(p->ifa_name,_dev.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; -} - -std::string OSXEthernetTap::persistentId() const -{ - return std::string(); -} - -bool OSXEthernetTap::updateMulticastGroups(std::set &groups) -{ - std::set newGroups; - struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0; - if (!_intl_getifmaddrs(&ifmap)) { - struct _intl_ifmaddrs *p = ifmap; - while (p) { - if (p->ifma_addr->sa_family == AF_LINK) { - struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; - struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; - if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _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 allIps(ips()); - for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) - newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); - } - - bool changed = false; - - for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { - if (!groups.count(*mg)) { - groups.insert(*mg); - changed = true; - } - } - for(std::set::iterator mg(groups.begin());mg!=groups.end();) { - if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { - groups.erase(mg++); - changed = true; - } else ++mg; - } - - return changed; -} - -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/node/osdep/OSXEthernetTap.hpp b/node/osdep/OSXEthernetTap.hpp deleted file mode 100644 index 7ca63730..00000000 --- a/node/osdep/OSXEthernetTap.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include - -#include - -#include "../EthernetTap.hpp" -#include "../Mutex.hpp" -#include "../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 ips() const; - virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); - virtual std::string deviceName() const; - virtual std::string persistentId() const; - virtual bool updateMulticastGroups(std::set &groups); - - 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/node/osdep/OSXEthernetTapFactory.cpp b/node/osdep/OSXEthernetTapFactory.cpp deleted file mode 100644 index 4d0f2069..00000000 --- a/node/osdep/OSXEthernetTapFactory.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include -#include -#include - -#include "OSXEthernetTapFactory.hpp" -#include "OSXEthernetTap.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()); - ::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::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) { - ::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) -{ - { - Mutex::Lock _l(_devices_m); - for(std::vector::iterator d(_devices.begin());d!=_devices.end();++d) { - if (*d == tap) { - _devices.erase(d); - break; - } - } - } - delete tap; -} - -std::vector allTapDeviceNames() const -{ - std::vector dn; - Mutex::Lock _l(_devices_m); - for(std::vector::const_iterator d(_devices.begin());d!=_devices.end();++d) - dn.push_back(d->deviceName()); - return dn; -} - -} // namespace ZeroTier diff --git a/node/osdep/OSXEthernetTapFactory.hpp b/node/osdep/OSXEthernetTapFactory.hpp deleted file mode 100644 index 720e038a..00000000 --- a/node/osdep/OSXEthernetTapFactory.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include - -#include "../EthernetTapFactory.hpp" -#include "../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); - virtual std::vector allTapDeviceNames() const; - -private: - std::vector _devices; - Mutex _devices_m; - std::string _pathToTapKext; - std::string _tapKextName; -}; - -} // namespace ZeroTier - -#endif diff --git a/node/osdep/WindowsEthernetTap.cpp b/node/osdep/WindowsEthernetTap.cpp deleted file mode 100644 index 8541ce7c..00000000 --- a/node/osdep/WindowsEthernetTap.cpp +++ /dev/null @@ -1,837 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 "../Constants.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../EthernetTap.hpp" -#include "../Utils.hpp" -#include "../Mutex.hpp" -#include "WindowsEthernetTap.hpp" - -#include "..\windows\TapDriver\tap-windows.h" - -// ff:ff:ff:ff:ff:ff with no ADI -static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - -namespace ZeroTier { - -// Helper function to get an adapter's LUID and index from its GUID. The LUID is -// constant but the index can change, so go ahead and just look them both up by -// the GUID which is constant. (The GUID is the instance ID in the registry.) -static inline std::pair _findAdapterByGuid(const GUID &guid) - throw(std::runtime_error) -{ - MIB_IF_TABLE2 *ift = (MIB_IF_TABLE2 *)0; - - if (GetIfTable2Ex(MibIfTableRaw,&ift) != NO_ERROR) - throw std::runtime_error("GetIfTable2Ex() failed"); - - for(ULONG i=0;iNumEntries;++i) { - if (ift->Table[i].InterfaceGuid == guid) { - std::pair tmp(ift->Table[i].InterfaceLuid,ift->Table[i].InterfaceIndex); - FreeMibTable(ift); - return tmp; - } - } - - FreeMibTable(&ift); - - throw std::runtime_error("interface not found"); -} - -// Only create or delete devices one at a time -static Mutex _systemTapInitLock; - -// Compute some basic environment stuff on startup -class _WinSysEnv -{ -public: - _WinSysEnv() - { -#ifdef _WIN64 - is64Bit = TRUE; - devcon = "\\devcon_x64.exe"; - tapDriver = "\\tap-windows\\x64\\zttap200.inf"; -#else - is64Bit = FALSE; - IsWow64Process(GetCurrentProcess(),&is64Bit); - devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); - tapDriver = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap200.inf" : "\\tap-windows\\x86\\zttap200.inf"); -#endif - } - BOOL is64Bit; - const char *devcon; - const char *tapDriver; -}; -static const _WinSysEnv _winEnv; - -static bool _disableTapDevice(const RuntimeEnvironment *_r,const std::string deviceInstanceId) -{ - HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" disable @" + deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - return false; - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return true; -} -static bool _enableTapDevice(const RuntimeEnvironment *_r,const std::string deviceInstanceId) -{ - HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" enable @" + deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - return false; - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return true; -} - -static void _syncIpsWithRegistry(const std::set &haveIps,const std::string netCfgInstanceId) -{ - // Update registry to contain all non-link-local IPs for this interface - std::string regMultiIps,regMultiNetmasks; - for(std::set::const_iterator i(haveIps.begin());i!=haveIps.end();++i) { - if (!i->isLinkLocal()) { - regMultiIps.append(i->toIpString()); - regMultiIps.push_back((char)0); - regMultiNetmasks.append(i->netmask().toIpString()); - regMultiNetmasks.push_back((char)0); - } - } - HKEY tcpIpInterfaces; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { - if (regMultiIps.length()) { - regMultiIps.push_back((char)0); - regMultiNetmasks.push_back((char)0); - RegSetKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"IPAddress",REG_MULTI_SZ,regMultiIps.data(),(DWORD)regMultiIps.length()); - RegSetKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"SubnetMask",REG_MULTI_SZ,regMultiNetmasks.data(),(DWORD)regMultiNetmasks.length()); - } else { - RegDeleteKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"IPAddress"); - RegDeleteKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"SubnetMask"); - } - } - RegCloseKey(tcpIpInterfaces); -} - -WindowsEthernetTap::WindowsEthernetTap( - const RuntimeEnvironment *renv, - const char *tag, - const MAC &mac, - unsigned int mtu, - void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), - void *arg) - throw(std::runtime_error) : - EthernetTap("WindowsEthernetTap",mac,mtu), - _r(renv), - _handler(handler), - _arg(arg), - _tap(INVALID_HANDLE_VALUE), - _injectSemaphore(INVALID_HANDLE_VALUE), - _run(true), - _initialized(false), - _enabled(true) -{ - char subkeyName[4096]; - char subkeyClass[4096]; - char data[4096]; - - if (mtu > ZT_IF_MTU) - 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 existingDeviceInstances; - std::string mySubkeyName; - - // Look for the tap instance that corresponds with our interface tag (network ID) - 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 - if (_netCfgInstanceId.length() == 0) { - // Log devcon output to a file - HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog == INVALID_HANDLE_VALUE) { - LOG("WARNING: unable to open devcon.log"); - } else { - 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("\"") + _r->homePath + _winEnv.devcon + "\" install \"" + _r->homePath + _winEnv.tapDriver + "\" zttap200").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - RegCloseKey(nwAdapters); - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - throw std::runtime_error(std::string("unable to find or execute devcon at ") + _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 - // there originally. The static mutex we lock ensures this can't step - // on its own toes. - 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[4096]; - 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(),"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?)"); - } - - // 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(_r,_deviceInstanceId); -} - -void WindowsEthernetTap::setEnabled(bool en) -{ - _enabled = en; -} - -bool WindowsEthernetTap::enabled() const -{ - return _enabled; -} - -void WindowsEthernetTap::setDisplayName(const char *dn) -{ - if (!_initialized) - return; - HKEY ifp; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,(std::string("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\") + _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::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 haveIps(ips()); - - try { - // Add IP to interface at the netlink level if not already assigned. - if (!haveIps.count(ip)) { - std::pair ifidx = _findAdapterByGuid(_deviceGuid); - 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 = ifidx.first; - ipr.InterfaceIndex = ifidx.second; - - if (CreateUnicastIpAddressEntry(&ipr) == NO_ERROR) { - haveIps.insert(ip); - } else { - LOG("unable to add IP address %s to interface %s: %d",ip.toString().c_str(),deviceName().c_str(),(int)GetLastError()); - return false; - } - } - - _syncIpsWithRegistry(haveIps,_netCfgInstanceId); - } catch (std::exception &exc) { - LOG("unexpected exception adding IP address %s to %s: %s",ip.toString().c_str(),deviceName().c_str(),exc.what()); - return false; - } catch ( ... ) { - LOG("unexpected exception adding IP address %s to %s: unknown exception",ip.toString().c_str(),deviceName().c_str()); - return false; - } - return true; -} - -bool WindowsEthernetTap::removeIP(const InetAddress &ip) -{ - if (!_initialized) - return false; - try { - MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; - std::pair ifidx = _findAdapterByGuid(_deviceGuid); - if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { - for(DWORD i=0;iNumEntries;++i) { - if ((ipt->Table[i].InterfaceLuid.Value == ifidx.first.Value)&&(ipt->Table[i].InterfaceIndex == ifidx.second)) { - 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); - _syncIpsWithRegistry(ips(),_netCfgInstanceId); - return true; - } - } - } - FreeMibTable((PVOID)ipt); - } - } catch (std::exception &exc) { - LOG("unexpected exception removing IP address %s from %s: %s",ip.toString().c_str(),deviceName().c_str(),exc.what()); - } catch ( ... ) { - LOG("unexpected exception removing IP address %s from %s: unknown exception",ip.toString().c_str(),deviceName().c_str()); - } - return false; -} - -std::set WindowsEthernetTap::ips() const -{ - static const InetAddress linkLocalLoopback("fe80::1",64); // what is this and why does Windows assign it? - std::set addrs; - - if (!_initialized) - return addrs; - - try { - MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; - std::pair ifidx = _findAdapterByGuid(_deviceGuid); - - if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { - for(DWORD i=0;iNumEntries;++i) { - if ((ipt->Table[i].InterfaceLuid.Value == ifidx.first.Value)&&(ipt->Table[i].InterfaceIndex == ifidx.second)) { - 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,unsigned int>(Array(),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 -{ - return _netCfgInstanceId; -} - -std::string WindowsEthernetTap::persistentId() const -{ - return _deviceInstanceId; -} - -bool WindowsEthernetTap::updateMulticastGroups(std::set &groups) -{ - if (!_initialized) - return false; - HANDLE t = _tap; - if (t == INVALID_HANDLE_VALUE) - return false; - - std::set 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 ipaddrs(ips()); - for(std::set::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::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { - if (!groups.count(*mg)) { - groups.insert(*mg); - changed = true; - } - } - for(std::set::iterator mg(groups.begin());mg!=groups.end();) { - if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { - groups.erase(mg++); - changed = true; - } else ++mg; - } - - return changed; -} - -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. - bool throwOneAway = true; - while (_run) { - _disableTapDevice(_r,_deviceInstanceId); - Sleep(250); - if (!_enableTapDevice(_r,_deviceInstanceId)) { - ::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); - - if (throwOneAway) { - throwOneAway = false; - CloseHandle(_tap); - _tap = INVALID_HANDLE_VALUE; - Sleep(250); - 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::deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid) -{ - Mutex::Lock _l(_systemTapInitLock); // only one thread may mess with taps at a time, process-wide - - HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - SetFilePointer(devconLog,0,0,FILE_END); - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" remove @" + pid).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); - return true; - } - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return false; -} - -int WindowsEthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices) -{ - char subkeyName[4096]; - char subkeyClass[4096]; - char data[4096]; - - std::set instanceIdPathsToRemove; - { - Mutex::Lock _l(_systemTapInitLock); // only one thread may mess with taps at a time, process-wide - - 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 -1; - - 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) { - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - if (dataLen <= 0) { - if (alsoRemoveUnassociatedDevices) - instanceIdPathsToRemove.insert(instanceIdPath); - } else { - if (!exceptThese.count(std::string(data,dataLen))) - instanceIdPathsToRemove.insert(instanceIdPath); - } - } else if (alsoRemoveUnassociatedDevices) - instanceIdPathsToRemove.insert(instanceIdPath); - } - } - } - } else break; // end of list or failure - } - - RegCloseKey(nwAdapters); - } - - int removed = 0; - for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { - if (deletePersistentTapDevice(_r,iidp->c_str())) - ++removed; - } - return removed; -} - -} // namespace ZeroTier diff --git a/node/osdep/WindowsEthernetTap.hpp b/node/osdep/WindowsEthernetTap.hpp deleted file mode 100644 index 74c56621..00000000 --- a/node/osdep/WindowsEthernetTap.hpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2011-2014 ZeroTier Networks LLC - * - * 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 . - * - * -- - * - * 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 -#include - -#include -#include -#include - -#include "../Constants.hpp" -#include "../EthernetTap.hpp" -#include "../Mutex.hpp" -#include "../Thread.hpp" -#include "../Array.hpp" - -namespace ZeroTier { - -/** - * Windows Ethernet tap device using bundled ztTap driver - */ -class WindowsEthernetTap : public EthernetTap -{ -public: - /** - * Open tap device, installing and creating one if it does not exist - * - * @param renv Runtime environment - * @param tag A tag (presently the hex network ID) used to identify persistent tap devices in the registry - * @param mac MAC address of device - * @param mtu MTU of device - * @param desc If non-NULL, a description (not used on all OSes) - * @param handler Handler function to be called when data is received from the tap - * @param arg First argument to handler function - * @throws std::runtime_error Unable to allocate device - */ - WindowsEthernetTap( - const RuntimeEnvironment *renv, - const char *tag, - const MAC &mac, - unsigned int mtu, - void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), - void *arg) - throw(std::runtime_error); - - virtual ~WindowsEthernetTap(); - - virtual void setEnabled(bool en); - virtual bool enabled() const; - virtual void setDisplayName(const char *dn); - virtual bool addIP(const InetAddress &ip); - virtual bool removeIP(const InetAddress &ip); - virtual std::set ips() const; - virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); - virtual std::string deviceName() const; - virtual std::string persistentId() const; - virtual bool updateMulticastGroups(std::set &groups); - - /** - * Thread main method; do not call elsewhere - */ - void threadMain() - throw(); - - /** - * Remove persistent tap device by device name - * - * @param _r Runtime environment - * @param pdev Device name as returned by persistentId() while tap is running - * @return True if a device was deleted - */ - static bool deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid); - - /** - * Clean persistent tap devices that are not in the supplied set - * - * @param _r Runtime environment - * @param exceptThese Devices to leave in place - * @param alsoRemoveUnassociatedDevices If true, remove devices not associated with any network as well - * @return Number of devices deleted or -1 if an error prevented the operation from being performed - */ - static int cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices); - -private: - void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); - void *_arg; - Thread _thread; - - volatile HANDLE _tap; - HANDLE _injectSemaphore; - GUID _deviceGuid; - std::string _netCfgInstanceId; // NetCfgInstanceId, a GUID - std::string _deviceInstanceId; // DeviceInstanceID, another kind of "instance ID" - std::queue< std::pair< Array,unsigned int > > _injectPending; - Mutex _injectPending_m; - volatile bool _run; - volatile bool _initialized; - volatile bool _enabled; -}; - -} // namespace ZeroTier - -#endif diff --git a/node/osdep/WindowsEthernetTapFactory.cpp b/node/osdep/WindowsEthernetTapFactory.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/node/osdep/WindowsEthernetTapFactory.hpp b/node/osdep/WindowsEthernetTapFactory.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/node/osdep/WindowsRoutingTable.cpp b/node/osdep/WindowsRoutingTable.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/node/osdep/WindowsRoutingTable.hpp b/node/osdep/WindowsRoutingTable.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/sys/BSDRoutingTable.cpp b/sys/BSDRoutingTable.cpp new file mode 100644 index 00000000..fede9cce --- /dev/null +++ b/sys/BSDRoutingTable.cpp @@ -0,0 +1,329 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../Constants.hpp" +#include "BSDRoutingTable.hpp" + +// All I wanted was the bloody rounting table. I didn't expect the Spanish inquisition. + +#define ZT_BSD_ROUTE_CMD "/sbin/route" + +namespace ZeroTier { + +BSDRoutingTable::BSDRoutingTable() +{ +} + +BSDRoutingTable::~BSDRoutingTable() +{ +} + +std::vector BSDRoutingTable::get(bool includeLinkLocal,bool includeLoopback) const +{ + std::vector entries; + int mib[6]; + size_t needed; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + if (!sysctl(mib,6,NULL,&needed,NULL,0)) { + if (needed <= 0) + return entries; + + char *buf = (char *)::malloc(needed); + if (buf) { + if (!sysctl(mib,6,buf,&needed,NULL,0)) { + struct rt_msghdr *rtm; + for(char *next=buf,*end=buf+needed;nextrtm_msglen; + + if (((rtm->rtm_flags & RTF_LLINFO) == 0)&&((rtm->rtm_flags & RTF_HOST) == 0)&&((rtm->rtm_flags & RTF_UP) != 0)&&((rtm->rtm_flags & RTF_MULTICAST) == 0)) { + RoutingTable::Entry e; + e.deviceIndex = -9999; // unset + + int which = 0; + while (saptr < saend) { + struct sockaddr *sa = (struct sockaddr *)saptr; + unsigned int salen = sa->sa_len; + if (!salen) + break; + + // Skip missing fields in rtm_addrs bit field + while ((rtm->rtm_addrs & 1) == 0) { + rtm->rtm_addrs >>= 1; + ++which; + if (which > 6) + break; + } + if (which > 6) + break; + + rtm->rtm_addrs >>= 1; + switch(which++) { + case 0: + //printf("RTA_DST\n"); + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + // Nobody expects the Spanish inquisition! + if ((sin6->sin6_addr.s6_addr[0] == 0xfe)&&((sin6->sin6_addr.s6_addr[1] & 0xc0) == 0x80)) { + // Our chief weapon is... in-band signaling! + // Seriously who in the living fuck thought this was a good idea and + // then had the sadistic idea to not document it anywhere? Of course it's + // not like there is any documentation on BSD sysctls anyway. + unsigned int interfaceIndex = ((((unsigned int)sin6->sin6_addr.s6_addr[2]) << 8) & 0xff) | (((unsigned int)sin6->sin6_addr.s6_addr[3]) & 0xff); + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + if (!sin6->sin6_scope_id) + sin6->sin6_scope_id = interfaceIndex; + } + } + e.destination.set(sa); + break; + case 1: + //printf("RTA_GATEWAY\n"); + switch(sa->sa_family) { + case AF_LINK: + e.deviceIndex = (int)((const struct sockaddr_dl *)sa)->sdl_index; + break; + case AF_INET: + case AF_INET6: + e.gateway.set(sa); + break; + } + break; + case 2: { + if (e.destination.isV6()) { + salen = sizeof(struct sockaddr_in6); // Confess! + unsigned int bits = 0; + for(int i=0;i<16;++i) { + unsigned char c = (unsigned char)((const struct sockaddr_in6 *)sa)->sin6_addr.s6_addr[i]; + if (c == 0xff) + bits += 8; + else break; + /* must they be multiples of 8? Most of the BSD source I can find says yes..? + else { + while ((c & 0x80) == 0x80) { + ++bits; + c <<= 1; + } + break; + } + */ + } + e.destination.setPort(bits); + } else { + salen = sizeof(struct sockaddr_in); // Confess! + e.destination.setPort((unsigned int)Utils::countBits((uint32_t)((const struct sockaddr_in *)sa)->sin_addr.s_addr)); + } + //printf("RTA_NETMASK\n"); + } break; + /* + case 3: + //printf("RTA_GENMASK\n"); + break; + case 4: + //printf("RTA_IFP\n"); + break; + case 5: + //printf("RTA_IFA\n"); + break; + case 6: + //printf("RTA_AUTHOR\n"); + break; + */ + } + + saptr += salen; + } + + e.metric = (int)rtm->rtm_rmx.rmx_hopcount; + if (e.metric < 0) + e.metric = 0; + + if (((includeLinkLocal)||(!e.destination.isLinkLocal()))&&((includeLoopback)||((!e.destination.isLoopback())&&(!e.gateway.isLoopback())))) + entries.push_back(e); + } + + next = saend; + } + } + + ::free(buf); + } + } + + for(std::vector::iterator e1(entries.begin());e1!=entries.end();++e1) { + if ((!e1->device[0])&&(e1->deviceIndex >= 0)) + if_indextoname(e1->deviceIndex,e1->device); + } + for(std::vector::iterator e1(entries.begin());e1!=entries.end();++e1) { + if ((!e1->device[0])&&(e1->gateway)) { + int bestMetric = 9999999; + for(std::vector::iterator e2(entries.begin());e2!=entries.end();++e2) { + if ((e1->gateway.within(e2->destination))&&(e2->metric <= bestMetric)) { + bestMetric = e2->metric; + Utils::scopy(e1->device,sizeof(e1->device),e2->device); + } + } + } + } + + std::sort(entries.begin(),entries.end()); + + return entries; +} + +RoutingTable::Entry BSDRoutingTable::set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric) +{ + if ((!gateway)&&((!device)||(!device[0]))) + return RoutingTable::Entry(); + + std::vector rtab(get(true,true)); + + for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { + if (e->destination == destination) { + if (((!device)||(!device[0]))||(!strcmp(device,e->device))) { + long p = (long)fork(); + if (p > 0) { + int exitcode = -1; + ::waitpid(p,&exitcode,0); + } else if (p == 0) { + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,"delete",(destination.isV6() ? "-inet6" : "-inet"),destination.toString().c_str(),(const char *)0); + ::_exit(-1); + } + } + } + } + + if (metric < 0) + return RoutingTable::Entry(); + + { + char hcstr[64]; + Utils::snprintf(hcstr,sizeof(hcstr),"%d",metric); + long p = (long)fork(); + if (p > 0) { + int exitcode = -1; + ::waitpid(p,&exitcode,0); + } else if (p == 0) { + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + if (gateway) { + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,"add",(destination.isV6() ? "-inet6" : "-inet"),destination.toString().c_str(),gateway.toIpString().c_str(),"-hopcount",hcstr,(const char *)0); + } else if ((device)&&(device[0])) { + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,"add",(destination.isV6() ? "-inet6" : "-inet"),destination.toString().c_str(),"-interface",device,"-hopcount",hcstr,(const char *)0); + } + ::_exit(-1); + } + } + + rtab = get(true,true); + std::vector::iterator bestEntry(rtab.end()); + for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { + if ((e->destination == destination)&&(e->gateway.ipsEqual(gateway))) { + if ((device)&&(device[0])) { + if (!strcmp(device,e->device)) { + if (metric == e->metric) + bestEntry = e; + } + } + if (bestEntry == rtab.end()) + bestEntry = e; + } + } + if (bestEntry != rtab.end()) + return *bestEntry; + + return RoutingTable::Entry(); +} + +} // namespace ZeroTier + +// Enable and build to test routing table interface +#if 0 +using namespace ZeroTier; +int main(int argc,char **argv) +{ + BSDRoutingTable rt; + + printf(" \n"); + std::vector ents(rt.get()); + for(std::vector::iterator e(ents.begin());e!=ents.end();++e) + printf("%s\n",e->toString().c_str()); + printf("\n"); + + printf("adding 1.1.1.0 and 2.2.2.0...\n"); + rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,1); + rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",1); + printf("\n"); + + printf(" \n"); + ents = rt.get(); + for(std::vector::iterator e(ents.begin());e!=ents.end();++e) + printf("%s\n",e->toString().c_str()); + printf("\n"); + + printf("deleting 1.1.1.0 and 2.2.2.0...\n"); + rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,-1); + rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",-1); + printf("\n"); + + printf(" \n"); + ents = rt.get(); + for(std::vector::iterator e(ents.begin());e!=ents.end();++e) + printf("%s\n",e->toString().c_str()); + printf("\n"); + + return 0; +} +#endif diff --git a/sys/BSDRoutingTable.hpp b/sys/BSDRoutingTable.hpp new file mode 100644 index 00000000..c0e98943 --- /dev/null +++ b/sys/BSDRoutingTable.hpp @@ -0,0 +1,51 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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_BSDROUTINGTABLE_HPP +#define ZT_BSDROUTINGTABLE_HPP + +#include "../RoutingTable.hpp" + +namespace ZeroTier { + +/** + * Routing table interface for BSD with sysctl() and BSD /sbin/route + * + * Has currently only been tested on OSX/Darwin. + */ +class BSDRoutingTable : public RoutingTable +{ +public: + BSDRoutingTable(); + virtual ~BSDRoutingTable(); + virtual std::vector get(bool includeLinkLocal = false,bool includeLoopback = false) const; + virtual RoutingTable::Entry set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric); +}; + +} // namespace ZeroTier + +#endif diff --git a/sys/LinuxEthernetTap.cpp b/sys/LinuxEthernetTap.cpp new file mode 100644 index 00000000..0b0c1c4f --- /dev/null +++ b/sys/LinuxEthernetTap.cpp @@ -0,0 +1,474 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../Constants.hpp" +#include "../Utils.hpp" +#include "../Mutex.hpp" +#include "LinuxEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +// On startup, searches the path for required external utilities (which might vary by Linux distro) +#define ZT_UNIX_IP_COMMAND 1 +class _CommandFinder +{ +public: + _CommandFinder() + { + _findCmd(ZT_UNIX_IP_COMMAND,"ip"); + } + inline const char *operator[](int id) const + throw() + { + std::map::const_iterator c(_paths.find(id)); + if (c == _paths.end()) + return (const char *)0; + return c->second.c_str(); + } +private: + inline void _findCmd(int id,const char *name) + { + char tmp[4096]; + ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/sbin/%s",name); + if (ZeroTier::Utils::fileExists(tmp)) { + _paths[id] = tmp; + return; + } + ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/sbin/%s",name); + if (ZeroTier::Utils::fileExists(tmp)) { + _paths[id] = tmp; + return; + } + ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/bin/%s",name); + if (ZeroTier::Utils::fileExists(tmp)) { + _paths[id] = tmp; + return; + } + ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/bin/%s",name); + if (ZeroTier::Utils::fileExists(tmp)) { + _paths[id] = tmp; + return; + } + } + std::map _paths; +}; +static const _CommandFinder UNIX_COMMANDS; + +namespace ZeroTier { + +// Only permit one tap to be opened concurrently across the entire process +static Mutex __tapCreateLock; + +LinuxEthernetTap::LinuxEthernetTap( + const RuntimeEnvironment *renv, + const char *tryToGetDevice, + const MAC &mac, + unsigned int mtu, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) + throw(std::runtime_error) : + EthernetTap("LinuxEthernetTap",mac,mtu), + _r(renv), + _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 > 4096) + throw std::runtime_error("max tap MTU is 4096"); + + _fd = ::open("/dev/net/tun",O_RDWR); + if (_fd <= 0) + throw std::runtime_error(std::string("could not open TUN/TAP device: ") + strerror(errno)); + + struct ifreq ifr; + memset(&ifr,0,sizeof(ifr)); + + // Try to recall our last device name, or pick an unused one if that fails. + bool recalledDevice = false; + if ((tryToGetDevice)&&(tryToGetDevice[0])) { + Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),tryToGetDevice); + 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) +{ + const char *ipcmd = UNIX_COMMANDS[ZT_UNIX_IP_COMMAND]; + if (!ipcmd) + return false; + long cpid = (long)vfork(); + if (cpid == 0) { + execl(ipcmd,ipcmd,"addr","del",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + _exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } +} + +bool LinuxEthernetTap::addIP(const InetAddress &ip) +{ + const char *ipcmd = UNIX_COMMANDS[ZT_UNIX_IP_COMMAND]; + if (!ipcmd) { + LOG("ERROR: could not configure IP address for %s: unable to find 'ip' command on system (checked /sbin, /bin, /usr/sbin, /usr/bin)",_dev.c_str()); + return false; + } + + if (!ip) + return false; + + std::set allIps(ips()); + if (allIps.count(ip) > 0) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { + if (i->ipsEqual(ip)) { + if (___removeIp(_dev,*i)) { + break; + } else { + LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); + } + } + } + + long cpid; + if ((cpid = (long)vfork()) == 0) { + execl(ipcmd,ipcmd,"addr","add",ip.toString().c_str(),"dev",_dev.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 LinuxEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::set(); + + std::set r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.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; +} + +std::string LinuxEthernetTap::persistentId() const +{ + return std::string(); +} + +bool LinuxEthernetTap::updateMulticastGroups(std::set &groups) +{ + char *ptr,*ptr2; + unsigned char mac[6]; + std::set newGroups; + + int fd = ::open("/proc/net/dev_mcast",O_RDONLY); + if (fd > 0) { + char buf[131072]; + int n = (int)::read(fd,buf,sizeof(buf)); + if ((n > 0)&&(n < (int)sizeof(buf))) { + buf[n] = (char)0; + for(char *l=strtok_r(buf,"\r\n",&ptr);(l);l=strtok_r((char *)0,"\r\n",&ptr)) { + int fno = 0; + char *devname = (char *)0; + char *mcastmac = (char *)0; + for(char *f=strtok_r(l," \t",&ptr2);(f);f=strtok_r((char *)0," \t",&ptr2)) { + if (fno == 1) + devname = f; + else if (fno == 4) + mcastmac = f; + ++fno; + } + if ((devname)&&(!strcmp(devname,_dev.c_str()))&&(mcastmac)&&(Utils::unhex(mcastmac,mac,6) == 6)) + newGroups.insert(MulticastGroup(MAC(mac,6),0)); + } + } + ::close(fd); + } + + { + std::set allIps(ips()); + for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +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)) { + TRACE("unexpected error reading from tap: %s",strerror(errno)); + break; + } + } else { + // Some tap drivers like to send the ethernet frame and the + // payload in two chunks, so handle that by accumulating + // data until we have at least a frame. + r += n; + if (r > 14) { + if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms + r = _mtu + 14; + + 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/sys/LinuxEthernetTap.hpp b/sys/LinuxEthernetTap.hpp new file mode 100644 index 00000000..e6aea2ec --- /dev/null +++ b/sys/LinuxEthernetTap.hpp @@ -0,0 +1,85 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include + +#include + +#include "../EthernetTap.hpp" +#include "../Mutex.hpp" +#include "../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 ips() const; + virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + virtual std::string deviceName() const; + virtual std::string persistentId() const; + virtual bool updateMulticastGroups(std::set &groups); + + 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/sys/LinuxEthernetTapFactory.cpp b/sys/LinuxEthernetTapFactory.cpp new file mode 100644 index 00000000..fccb6bd5 --- /dev/null +++ b/sys/LinuxEthernetTapFactory.cpp @@ -0,0 +1,83 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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::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) +{ + { + Mutex::Lock _l(_devices_m); + for(std::vector::iterator d(_devices.begin());d!=_devices.end();++d) { + if (*d == tap) { + _devices.erase(d); + break; + } + } + } + delete tap; +} + +std::vector allTapDeviceNames() const +{ + std::vector dn; + Mutex::Lock _l(_devices_m); + for(std::vector::const_iterator d(_devices.begin());d!=_devices.end();++d) + dn.push_back(d->deviceName()); + return dn; +} + +} // namespace ZeroTier diff --git a/sys/LinuxEthernetTapFactory.hpp b/sys/LinuxEthernetTapFactory.hpp new file mode 100644 index 00000000..a5d13216 --- /dev/null +++ b/sys/LinuxEthernetTapFactory.hpp @@ -0,0 +1,64 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include + +#include "../EthernetTapFactory.hpp" +#include "../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); + virtual std::vector allTapDeviceNames() const; + +private: + std::vector _devices; + Mutex _devices_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/sys/LinuxRoutingTable.cpp b/sys/LinuxRoutingTable.cpp new file mode 100644 index 00000000..67a5b22d --- /dev/null +++ b/sys/LinuxRoutingTable.cpp @@ -0,0 +1,247 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../Constants.hpp" +#include "../Utils.hpp" +#include "LinuxRoutingTable.hpp" + +#define ZT_LINUX_IP_COMMAND "/sbin/ip" + +namespace ZeroTier { + +LinuxRoutingTable::LinuxRoutingTable() +{ +} + +LinuxRoutingTable::~LinuxRoutingTable() +{ +} + +std::vector LinuxRoutingTable::get(bool includeLinkLocal,bool includeLoopback) const +{ + char buf[131072]; + char *stmp,*stmp2; + std::vector entries; + + { + int fd = ::open("/proc/net/route",O_RDONLY); + if (fd <= 0) + buf[0] = (char)0; + else { + int n = (int)::read(fd,buf,sizeof(buf) - 1); + ::close(fd); + if (n < 0) n = 0; + buf[n] = (char)0; + } + } + + int lineno = 0; + for(char *line=Utils::stok(buf,"\r\n",&stmp);(line);line=Utils::stok((char *)0,"\r\n",&stmp)) { + if (lineno == 0) { + ++lineno; + continue; // skip header + } + + char *iface = (char *)0; + uint32_t destination = 0; + uint32_t gateway = 0; + int metric = 0; + uint32_t mask = 0; + + int fno = 0; + for(char *f=Utils::stok(line,"\t \r\n",&stmp2);(f);f=Utils::stok((char *)0,"\t \r\n",&stmp2)) { + switch(fno) { + case 0: iface = f; break; + case 1: destination = (uint32_t)Utils::hexStrToULong(f); break; + case 2: gateway = (uint32_t)Utils::hexStrToULong(f); break; + case 6: metric = (int)Utils::strToInt(f); break; + case 7: mask = (uint32_t)Utils::hexStrToULong(f); break; + } + ++fno; + } + + if ((iface)&&(destination)) { + RoutingTable::Entry e; + if (destination) + e.destination.set(&destination,4,Utils::countBits(mask)); + e.gateway.set(&gateway,4,0); + e.deviceIndex = 0; // not used on Linux + e.metric = metric; + Utils::scopy(e.device,sizeof(e.device),iface); + if ((e.destination)&&((includeLinkLocal)||(!e.destination.isLinkLocal()))&&((includeLoopback)||((!e.destination.isLoopback())&&(!e.gateway.isLoopback())&&(strcmp(iface,"lo"))))) + entries.push_back(e); + } + + ++lineno; + } + + { + int fd = ::open("/proc/net/ipv6_route",O_RDONLY); + if (fd <= 0) + buf[0] = (char)0; + else { + int n = (int)::read(fd,buf,sizeof(buf) - 1); + ::close(fd); + if (n < 0) n = 0; + buf[n] = (char)0; + } + } + + for(char *line=Utils::stok(buf,"\r\n",&stmp);(line);line=Utils::stok((char *)0,"\r\n",&stmp)) { + char *destination = (char *)0; + unsigned int destPrefixLen = 0; + char *gateway = (char *)0; // next hop in ipv6 terminology + int metric = 0; + char *device = (char *)0; + + int fno = 0; + for(char *f=Utils::stok(line,"\t \r\n",&stmp2);(f);f=Utils::stok((char *)0,"\t \r\n",&stmp2)) { + switch(fno) { + case 0: destination = f; break; + case 1: destPrefixLen = (unsigned int)Utils::hexStrToULong(f); break; + case 4: gateway = f; break; + case 5: metric = (int)Utils::hexStrToLong(f); break; + case 9: device = f; break; + } + ++fno; + } + + if ((device)&&(destination)) { + unsigned char tmp[16]; + RoutingTable::Entry e; + Utils::unhex(destination,tmp,16); + if ((!Utils::isZero(tmp,16))&&(tmp[0] != 0xff)) + e.destination.set(tmp,16,destPrefixLen); + Utils::unhex(gateway,tmp,16); + e.gateway.set(tmp,16,0); + e.deviceIndex = 0; // not used on Linux + e.metric = metric; + Utils::scopy(e.device,sizeof(e.device),device); + if ((e.destination)&&((includeLinkLocal)||(!e.destination.isLinkLocal()))&&((includeLoopback)||((!e.destination.isLoopback())&&(!e.gateway.isLoopback())&&(strcmp(device,"lo"))))) + entries.push_back(e); + } + } + + std::sort(entries.begin(),entries.end()); + return entries; +} + +RoutingTable::Entry LinuxRoutingTable::set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric) +{ + if ((!gateway)&&((!device)||(!device[0]))) + return RoutingTable::Entry(); + + std::vector rtab(get(true,true)); + + for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { + if (e->destination == destination) { + if (((!device)||(!device[0]))||(!strcmp(device,e->device))) { + } + } + } + + if (metric < 0) + return RoutingTable::Entry(); + + + + rtab = get(true,true); + std::vector::iterator bestEntry(rtab.end()); + for(std::vector::iterator e(rtab.begin());e!=rtab.end();++e) { + if ((e->destination == destination)&&(e->gateway.ipsEqual(gateway))) { + if ((device)&&(device[0])) { + if (!strcmp(device,e->device)) { + if (metric == e->metric) + bestEntry = e; + } + } + if (bestEntry == rtab.end()) + bestEntry = e; + } + } + if (bestEntry != rtab.end()) + return *bestEntry; + + return RoutingTable::Entry(); +} + +} // namespace ZeroTier + +// Enable and build to test routing table interface +//#if 0 +using namespace ZeroTier; +int main(int argc,char **argv) +{ + LinuxRoutingTable rt; + + printf(" \n"); + std::vector ents(rt.get()); + for(std::vector::iterator e(ents.begin());e!=ents.end();++e) + printf("%s\n",e->toString().c_str()); + printf("\n"); + + printf("adding 1.1.1.0 and 2.2.2.0...\n"); + rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,1); + rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",1); + printf("\n"); + + printf(" \n"); + ents = rt.get(); + for(std::vector::iterator e(ents.begin());e!=ents.end();++e) + printf("%s\n",e->toString().c_str()); + printf("\n"); + + printf("deleting 1.1.1.0 and 2.2.2.0...\n"); + rt.set(InetAddress("1.1.1.0",24),InetAddress("1.2.3.4",0),(const char *)0,-1); + rt.set(InetAddress("2.2.2.0",24),InetAddress(),"en0",-1); + printf("\n"); + + printf(" \n"); + ents = rt.get(); + for(std::vector::iterator e(ents.begin());e!=ents.end();++e) + printf("%s\n",e->toString().c_str()); + printf("\n"); + + return 0; +} +//#endif diff --git a/sys/LinuxRoutingTable.hpp b/sys/LinuxRoutingTable.hpp new file mode 100644 index 00000000..9a5d3fd6 --- /dev/null +++ b/sys/LinuxRoutingTable.hpp @@ -0,0 +1,49 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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_LINUXROUTINGTABLE_HPP +#define ZT_LINUXROUTINGTABLE_HPP + +#include "../RoutingTable.hpp" + +namespace ZeroTier { + +/** + * Routing table interface via /proc/net/route, /proc/net/ipv6_route, and /sbin/route command + */ +class LinuxRoutingTable : public RoutingTable +{ +public: + LinuxRoutingTable(); + virtual ~LinuxRoutingTable(); + virtual std::vector get(bool includeLinkLocal = false,bool includeLoopback = false) const; + virtual RoutingTable::Entry set(const InetAddress &destination,const InetAddress &gateway,const char *device,int metric); +}; + +} // namespace ZeroTier + +#endif diff --git a/sys/OSXEthernetTap.cpp b/sys/OSXEthernetTap.cpp new file mode 100644 index 00000000..1e2ddbed --- /dev/null +++ b/sys/OSXEthernetTap.cpp @@ -0,0 +1,634 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ +struct prf_ra { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; +} prf_ra; + +// 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 */ +/* 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 +#include +#include +#include + +#include "../Constants.hpp" +#include "../Utils.hpp" +#include "../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 { + +// Only permit one tap to be opened concurrently across the entire process +static Mutex __tapCreateLock; + +OSXEthernetTap::OSXEthernetTap( + const RuntimeEnvironment *renv, + const char *tryToGetDevice, + const MAC &mac, + unsigned int mtu, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) + throw(std::runtime_error) : + EthernetTap("OSXEthernetTap",mac,mtu,metric), + _r(renv), + _handler(handler), + _arg(arg), + _fd(0), + _enabled(true) +{ + char devpath[64],ethaddr[64],mtustr[32],metstr[32]; + struct stat stattmp; + Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally + + 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 and unable to load kernel extension"); + + // Try to reopen the last device we had, if we had one and it's still unused. + bool recalledDevice = false; + if ((tryToGetDevice)&&(tryToGetDevice[0])) { + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",tryToGetDevice); + if (stat(devpath,&stattmp) == 0) { + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + _dev = tryToGetDevice; + recalledDevice = true; + } + } + } + + // Open the first unused tap device if we didn't recall a previous one. + if (!recalledDevice) { + for(int i=0;i<256;++i) { + Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i); + if (stat(devpath,&stattmp)) + throw std::runtime_error("no more TAP devices available"); + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + 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 (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 allIps(ips()); + if (allIps.count(ip) > 0) + return true; // IP/netmask already assigned + + // Remove and reconfigure if address is the same but netmask is different + for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) { + break; + } else { + LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); + } + } + } + + long cpid = (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 OSXEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::set(); + + std::set r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.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; +} + +std::string OSXEthernetTap::persistentId() const +{ + return std::string(); +} + +bool OSXEthernetTap::updateMulticastGroups(std::set &groups) +{ + std::set newGroups; + struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0; + if (!_intl_getifmaddrs(&ifmap)) { + struct _intl_ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _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 allIps(ips()); + for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +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/sys/OSXEthernetTap.hpp b/sys/OSXEthernetTap.hpp new file mode 100644 index 00000000..7ca63730 --- /dev/null +++ b/sys/OSXEthernetTap.hpp @@ -0,0 +1,89 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include + +#include + +#include "../EthernetTap.hpp" +#include "../Mutex.hpp" +#include "../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 ips() const; + virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + virtual std::string deviceName() const; + virtual std::string persistentId() const; + virtual bool updateMulticastGroups(std::set &groups); + + 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/sys/OSXEthernetTapFactory.cpp b/sys/OSXEthernetTapFactory.cpp new file mode 100644 index 00000000..4d0f2069 --- /dev/null +++ b/sys/OSXEthernetTapFactory.cpp @@ -0,0 +1,126 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include +#include +#include + +#include "OSXEthernetTapFactory.hpp" +#include "OSXEthernetTap.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()); + ::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::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) { + ::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) +{ + { + Mutex::Lock _l(_devices_m); + for(std::vector::iterator d(_devices.begin());d!=_devices.end();++d) { + if (*d == tap) { + _devices.erase(d); + break; + } + } + } + delete tap; +} + +std::vector allTapDeviceNames() const +{ + std::vector dn; + Mutex::Lock _l(_devices_m); + for(std::vector::const_iterator d(_devices.begin());d!=_devices.end();++d) + dn.push_back(d->deviceName()); + return dn; +} + +} // namespace ZeroTier diff --git a/sys/OSXEthernetTapFactory.hpp b/sys/OSXEthernetTapFactory.hpp new file mode 100644 index 00000000..720e038a --- /dev/null +++ b/sys/OSXEthernetTapFactory.hpp @@ -0,0 +1,77 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include + +#include "../EthernetTapFactory.hpp" +#include "../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); + virtual std::vector allTapDeviceNames() const; + +private: + std::vector _devices; + Mutex _devices_m; + std::string _pathToTapKext; + std::string _tapKextName; +}; + +} // namespace ZeroTier + +#endif diff --git a/sys/WindowsEthernetTap.cpp b/sys/WindowsEthernetTap.cpp new file mode 100644 index 00000000..8541ce7c --- /dev/null +++ b/sys/WindowsEthernetTap.cpp @@ -0,0 +1,837 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 "../Constants.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../EthernetTap.hpp" +#include "../Utils.hpp" +#include "../Mutex.hpp" +#include "WindowsEthernetTap.hpp" + +#include "..\windows\TapDriver\tap-windows.h" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +namespace ZeroTier { + +// Helper function to get an adapter's LUID and index from its GUID. The LUID is +// constant but the index can change, so go ahead and just look them both up by +// the GUID which is constant. (The GUID is the instance ID in the registry.) +static inline std::pair _findAdapterByGuid(const GUID &guid) + throw(std::runtime_error) +{ + MIB_IF_TABLE2 *ift = (MIB_IF_TABLE2 *)0; + + if (GetIfTable2Ex(MibIfTableRaw,&ift) != NO_ERROR) + throw std::runtime_error("GetIfTable2Ex() failed"); + + for(ULONG i=0;iNumEntries;++i) { + if (ift->Table[i].InterfaceGuid == guid) { + std::pair tmp(ift->Table[i].InterfaceLuid,ift->Table[i].InterfaceIndex); + FreeMibTable(ift); + return tmp; + } + } + + FreeMibTable(&ift); + + throw std::runtime_error("interface not found"); +} + +// Only create or delete devices one at a time +static Mutex _systemTapInitLock; + +// Compute some basic environment stuff on startup +class _WinSysEnv +{ +public: + _WinSysEnv() + { +#ifdef _WIN64 + is64Bit = TRUE; + devcon = "\\devcon_x64.exe"; + tapDriver = "\\tap-windows\\x64\\zttap200.inf"; +#else + is64Bit = FALSE; + IsWow64Process(GetCurrentProcess(),&is64Bit); + devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); + tapDriver = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap200.inf" : "\\tap-windows\\x86\\zttap200.inf"); +#endif + } + BOOL is64Bit; + const char *devcon; + const char *tapDriver; +}; +static const _WinSysEnv _winEnv; + +static bool _disableTapDevice(const RuntimeEnvironment *_r,const std::string deviceInstanceId) +{ + HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (devconLog != INVALID_HANDLE_VALUE) + SetFilePointer(devconLog,0,0,FILE_END); + + STARTUPINFOA startupInfo; + startupInfo.cb = sizeof(startupInfo); + if (devconLog != INVALID_HANDLE_VALUE) { + startupInfo.hStdOutput = devconLog; + startupInfo.hStdError = devconLog; + } + PROCESS_INFORMATION processInfo; + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" disable @" + deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + return false; + } + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + + return true; +} +static bool _enableTapDevice(const RuntimeEnvironment *_r,const std::string deviceInstanceId) +{ + HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (devconLog != INVALID_HANDLE_VALUE) + SetFilePointer(devconLog,0,0,FILE_END); + + STARTUPINFOA startupInfo; + startupInfo.cb = sizeof(startupInfo); + if (devconLog != INVALID_HANDLE_VALUE) { + startupInfo.hStdOutput = devconLog; + startupInfo.hStdError = devconLog; + } + PROCESS_INFORMATION processInfo; + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" enable @" + deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + return false; + } + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + + return true; +} + +static void _syncIpsWithRegistry(const std::set &haveIps,const std::string netCfgInstanceId) +{ + // Update registry to contain all non-link-local IPs for this interface + std::string regMultiIps,regMultiNetmasks; + for(std::set::const_iterator i(haveIps.begin());i!=haveIps.end();++i) { + if (!i->isLinkLocal()) { + regMultiIps.append(i->toIpString()); + regMultiIps.push_back((char)0); + regMultiNetmasks.append(i->netmask().toIpString()); + regMultiNetmasks.push_back((char)0); + } + } + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + if (regMultiIps.length()) { + regMultiIps.push_back((char)0); + regMultiNetmasks.push_back((char)0); + RegSetKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"IPAddress",REG_MULTI_SZ,regMultiIps.data(),(DWORD)regMultiIps.length()); + RegSetKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"SubnetMask",REG_MULTI_SZ,regMultiNetmasks.data(),(DWORD)regMultiNetmasks.length()); + } else { + RegDeleteKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"IPAddress"); + RegDeleteKeyValueA(tcpIpInterfaces,netCfgInstanceId.c_str(),"SubnetMask"); + } + } + RegCloseKey(tcpIpInterfaces); +} + +WindowsEthernetTap::WindowsEthernetTap( + const RuntimeEnvironment *renv, + const char *tag, + const MAC &mac, + unsigned int mtu, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) + throw(std::runtime_error) : + EthernetTap("WindowsEthernetTap",mac,mtu), + _r(renv), + _handler(handler), + _arg(arg), + _tap(INVALID_HANDLE_VALUE), + _injectSemaphore(INVALID_HANDLE_VALUE), + _run(true), + _initialized(false), + _enabled(true) +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + if (mtu > ZT_IF_MTU) + 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 existingDeviceInstances; + std::string mySubkeyName; + + // Look for the tap instance that corresponds with our interface tag (network ID) + 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 + if (_netCfgInstanceId.length() == 0) { + // Log devcon output to a file + HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if (devconLog == INVALID_HANDLE_VALUE) { + LOG("WARNING: unable to open devcon.log"); + } else { + 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("\"") + _r->homePath + _winEnv.devcon + "\" install \"" + _r->homePath + _winEnv.tapDriver + "\" zttap200").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + RegCloseKey(nwAdapters); + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + throw std::runtime_error(std::string("unable to find or execute devcon at ") + _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 + // there originally. The static mutex we lock ensures this can't step + // on its own toes. + 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[4096]; + 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(),"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?)"); + } + + // 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(_r,_deviceInstanceId); +} + +void WindowsEthernetTap::setEnabled(bool en) +{ + _enabled = en; +} + +bool WindowsEthernetTap::enabled() const +{ + return _enabled; +} + +void WindowsEthernetTap::setDisplayName(const char *dn) +{ + if (!_initialized) + return; + HKEY ifp; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,(std::string("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\") + _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::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 haveIps(ips()); + + try { + // Add IP to interface at the netlink level if not already assigned. + if (!haveIps.count(ip)) { + std::pair ifidx = _findAdapterByGuid(_deviceGuid); + 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 = ifidx.first; + ipr.InterfaceIndex = ifidx.second; + + if (CreateUnicastIpAddressEntry(&ipr) == NO_ERROR) { + haveIps.insert(ip); + } else { + LOG("unable to add IP address %s to interface %s: %d",ip.toString().c_str(),deviceName().c_str(),(int)GetLastError()); + return false; + } + } + + _syncIpsWithRegistry(haveIps,_netCfgInstanceId); + } catch (std::exception &exc) { + LOG("unexpected exception adding IP address %s to %s: %s",ip.toString().c_str(),deviceName().c_str(),exc.what()); + return false; + } catch ( ... ) { + LOG("unexpected exception adding IP address %s to %s: unknown exception",ip.toString().c_str(),deviceName().c_str()); + return false; + } + return true; +} + +bool WindowsEthernetTap::removeIP(const InetAddress &ip) +{ + if (!_initialized) + return false; + try { + MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; + std::pair ifidx = _findAdapterByGuid(_deviceGuid); + if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { + for(DWORD i=0;iNumEntries;++i) { + if ((ipt->Table[i].InterfaceLuid.Value == ifidx.first.Value)&&(ipt->Table[i].InterfaceIndex == ifidx.second)) { + 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); + _syncIpsWithRegistry(ips(),_netCfgInstanceId); + return true; + } + } + } + FreeMibTable((PVOID)ipt); + } + } catch (std::exception &exc) { + LOG("unexpected exception removing IP address %s from %s: %s",ip.toString().c_str(),deviceName().c_str(),exc.what()); + } catch ( ... ) { + LOG("unexpected exception removing IP address %s from %s: unknown exception",ip.toString().c_str(),deviceName().c_str()); + } + return false; +} + +std::set WindowsEthernetTap::ips() const +{ + static const InetAddress linkLocalLoopback("fe80::1",64); // what is this and why does Windows assign it? + std::set addrs; + + if (!_initialized) + return addrs; + + try { + MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; + std::pair ifidx = _findAdapterByGuid(_deviceGuid); + + if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { + for(DWORD i=0;iNumEntries;++i) { + if ((ipt->Table[i].InterfaceLuid.Value == ifidx.first.Value)&&(ipt->Table[i].InterfaceIndex == ifidx.second)) { + 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,unsigned int>(Array(),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 +{ + return _netCfgInstanceId; +} + +std::string WindowsEthernetTap::persistentId() const +{ + return _deviceInstanceId; +} + +bool WindowsEthernetTap::updateMulticastGroups(std::set &groups) +{ + if (!_initialized) + return false; + HANDLE t = _tap; + if (t == INVALID_HANDLE_VALUE) + return false; + + std::set 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 ipaddrs(ips()); + for(std::set::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::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +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. + bool throwOneAway = true; + while (_run) { + _disableTapDevice(_r,_deviceInstanceId); + Sleep(250); + if (!_enableTapDevice(_r,_deviceInstanceId)) { + ::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); + + if (throwOneAway) { + throwOneAway = false; + CloseHandle(_tap); + _tap = INVALID_HANDLE_VALUE; + Sleep(250); + 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::deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid) +{ + Mutex::Lock _l(_systemTapInitLock); // only one thread may mess with taps at a time, process-wide + + HANDLE devconLog = CreateFileA((_r->homePath + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + STARTUPINFOA startupInfo; + startupInfo.cb = sizeof(startupInfo); + if (devconLog != INVALID_HANDLE_VALUE) { + SetFilePointer(devconLog,0,0,FILE_END); + startupInfo.hStdOutput = devconLog; + startupInfo.hStdError = devconLog; + } + PROCESS_INFORMATION processInfo; + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + _r->homePath + _winEnv.devcon + "\" remove @" + pid).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); + return true; + } + if (devconLog != INVALID_HANDLE_VALUE) + CloseHandle(devconLog); + + return false; +} + +int WindowsEthernetTap::cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices) +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + std::set instanceIdPathsToRemove; + { + Mutex::Lock _l(_systemTapInitLock); // only one thread may mess with taps at a time, process-wide + + 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 -1; + + 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) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + if (dataLen <= 0) { + if (alsoRemoveUnassociatedDevices) + instanceIdPathsToRemove.insert(instanceIdPath); + } else { + if (!exceptThese.count(std::string(data,dataLen))) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } else if (alsoRemoveUnassociatedDevices) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + int removed = 0; + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + if (deletePersistentTapDevice(_r,iidp->c_str())) + ++removed; + } + return removed; +} + +} // namespace ZeroTier diff --git a/sys/WindowsEthernetTap.hpp b/sys/WindowsEthernetTap.hpp new file mode 100644 index 00000000..74c56621 --- /dev/null +++ b/sys/WindowsEthernetTap.hpp @@ -0,0 +1,130 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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 . + * + * -- + * + * 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 +#include + +#include +#include +#include + +#include "../Constants.hpp" +#include "../EthernetTap.hpp" +#include "../Mutex.hpp" +#include "../Thread.hpp" +#include "../Array.hpp" + +namespace ZeroTier { + +/** + * Windows Ethernet tap device using bundled ztTap driver + */ +class WindowsEthernetTap : public EthernetTap +{ +public: + /** + * Open tap device, installing and creating one if it does not exist + * + * @param renv Runtime environment + * @param tag A tag (presently the hex network ID) used to identify persistent tap devices in the registry + * @param mac MAC address of device + * @param mtu MTU of device + * @param desc If non-NULL, a description (not used on all OSes) + * @param handler Handler function to be called when data is received from the tap + * @param arg First argument to handler function + * @throws std::runtime_error Unable to allocate device + */ + WindowsEthernetTap( + const RuntimeEnvironment *renv, + const char *tag, + const MAC &mac, + unsigned int mtu, + void (*handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &), + void *arg) + throw(std::runtime_error); + + virtual ~WindowsEthernetTap(); + + virtual void setEnabled(bool en); + virtual bool enabled() const; + virtual void setDisplayName(const char *dn); + virtual bool addIP(const InetAddress &ip); + virtual bool removeIP(const InetAddress &ip); + virtual std::set ips() const; + virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + virtual std::string deviceName() const; + virtual std::string persistentId() const; + virtual bool updateMulticastGroups(std::set &groups); + + /** + * Thread main method; do not call elsewhere + */ + void threadMain() + throw(); + + /** + * Remove persistent tap device by device name + * + * @param _r Runtime environment + * @param pdev Device name as returned by persistentId() while tap is running + * @return True if a device was deleted + */ + static bool deletePersistentTapDevice(const RuntimeEnvironment *_r,const char *pid); + + /** + * Clean persistent tap devices that are not in the supplied set + * + * @param _r Runtime environment + * @param exceptThese Devices to leave in place + * @param alsoRemoveUnassociatedDevices If true, remove devices not associated with any network as well + * @return Number of devices deleted or -1 if an error prevented the operation from being performed + */ + static int cleanPersistentTapDevices(const RuntimeEnvironment *_r,const std::set &exceptThese,bool alsoRemoveUnassociatedDevices); + +private: + void (*_handler)(void *,const MAC &,const MAC &,unsigned int,const Buffer<4096> &); + void *_arg; + Thread _thread; + + volatile HANDLE _tap; + HANDLE _injectSemaphore; + GUID _deviceGuid; + std::string _netCfgInstanceId; // NetCfgInstanceId, a GUID + std::string _deviceInstanceId; // DeviceInstanceID, another kind of "instance ID" + std::queue< std::pair< Array,unsigned int > > _injectPending; + Mutex _injectPending_m; + volatile bool _run; + volatile bool _initialized; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/sys/WindowsEthernetTapFactory.cpp b/sys/WindowsEthernetTapFactory.cpp new file mode 100644 index 00000000..e69de29b diff --git a/sys/WindowsEthernetTapFactory.hpp b/sys/WindowsEthernetTapFactory.hpp new file mode 100644 index 00000000..e69de29b diff --git a/sys/WindowsRoutingTable.cpp b/sys/WindowsRoutingTable.cpp new file mode 100644 index 00000000..e69de29b diff --git a/sys/WindowsRoutingTable.hpp b/sys/WindowsRoutingTable.hpp new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3