From 5e58a7d54ab9bd241bb3f7bbc90a8cfa80663515 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 21 Jul 2014 09:18:33 -0700 Subject: Split UnixEthernetTap by platform to get rid of ifdef spaghetti. --- node/EthernetTap.hpp | 20 +- node/EthernetTapFactory.hpp | 27 +- node/osdep/LinuxEthernetTap.cpp | 482 +++++++++++++++++++ node/osdep/LinuxEthernetTap.hpp | 85 ++++ node/osdep/OSXEthernetTap.cpp | 698 +++++++++++++++++++++++++++ node/osdep/OSXEthernetTap.hpp | 89 ++++ node/osdep/UnixEthernetTap.cpp | 1007 --------------------------------------- node/osdep/UnixEthernetTap.hpp | 105 ---- 8 files changed, 1388 insertions(+), 1125 deletions(-) create mode 100644 node/osdep/LinuxEthernetTap.cpp create mode 100644 node/osdep/LinuxEthernetTap.hpp create mode 100644 node/osdep/OSXEthernetTap.cpp create mode 100644 node/osdep/OSXEthernetTap.hpp delete mode 100644 node/osdep/UnixEthernetTap.cpp delete mode 100644 node/osdep/UnixEthernetTap.hpp (limited to 'node') diff --git a/node/EthernetTap.hpp b/node/EthernetTap.hpp index da725553..ab59c957 100644 --- a/node/EthernetTap.hpp +++ b/node/EthernetTap.hpp @@ -70,15 +70,6 @@ public: */ virtual bool enabled() const = 0; - /** - * Set the user display name for this connection - * - * This does nothing on platforms that don't have this concept. - * - * @param dn User display name - */ - virtual void setDisplayName(const char *dn) = 0; - /** * @return MAC address of this interface */ @@ -89,6 +80,11 @@ public: */ inline unsigned int mtu() const throw() { return _mtu; } + /** + * @return Interface metric + */ + inline unsigned int metric() const throw() { return _metric; } + /** * Add an IP to this interface * @@ -192,14 +188,16 @@ public: virtual bool updateMulticastGroups(std::set &groups) = 0; protected: - EthernetTap(const char *cn,const MAC &m,unsigned int mt) : + EthernetTap(const char *cn,const MAC &m,unsigned int mt,unsigned int met) : _implName(cn), _mac(m), - _mtu(mt) {} + _mtu(mt), + _metric(met) {} const char *_implName; MAC _mac; unsigned int _mtu; + unsigned int _metric; }; } // namespace ZeroTier diff --git a/node/EthernetTapFactory.hpp b/node/EthernetTapFactory.hpp index 568afa81..94d13ba6 100644 --- a/node/EthernetTapFactory.hpp +++ b/node/EthernetTapFactory.hpp @@ -30,6 +30,8 @@ #include +#include + #include "MAC.hpp" namespace ZeroTier { @@ -58,15 +60,36 @@ public: * * On some platforms (Windows) this can be a time-consuming operation. * + * Note that close() must be used. Do not just delete the tap instance, + * since this may leave orphaned resources or cause other problems. + * * @param mac MAC address * @param mtu Device MTU * @param metric Interface metric (higher = lower priority, may not be supported on all OSes) * @param nwid ZeroTier network ID * @param desiredDevice Desired system device name or NULL for no preference * @param friendlyName Friendly name of this interface or NULL for none (not used on all platforms) - * @return EthernetTap instance or NULL on failure + * @param handler Function to call when packets are received + * @param arg First argument to provide to handler + * @return EthernetTap instance + * @throws std::runtime_error Unable to initialize tap device + */ + 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) = 0; + + /** + * Close an ethernet tap device + * + * @param tap Tap instance */ - virtual EthernetTap *get(const MAC &mac,unsigned int mtu,unsigned int metric,uint64_t nwid,const char *desiredDevice,const char *friendlyName) = 0; + virtual void close(EthernetTap *tap) = 0; }; } // namespace ZeroTier diff --git a/node/osdep/LinuxEthernetTap.cpp b/node/osdep/LinuxEthernetTap.cpp new file mode 100644 index 00000000..a4414cc6 --- /dev/null +++ b/node/osdep/LinuxEthernetTap.cpp @@ -0,0 +1,482 @@ +/* + * 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); + + TRACE("tap %s created",_dev.c_str()); + + _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[4096 + 14]; + if ((_fd > 0)&&(len <= _mtu)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + + int n = ::write(_fd,putBuf,len); + if (n <= 0) { + LOG("error writing packet to Ethernet tap device: %s",strerror(errno)); + } else if (n != (int)len) { + // Saw this gremlin once, so log it if we see it again... OSX tap + // or something seems to have goofy issues with certain MTUs. + LOG("ERROR: write underrun: %s tap write() wrote %d of %u bytes of frame",_dev.c_str(),n,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; + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + if (etherType != 0x8100) { // VLAN tagged frames are not supported! + data.copyFrom(getBuf + 14,(unsigned int)r - 14); + _handler(_arg,from,to,etherType,data); + } + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/node/osdep/LinuxEthernetTap.hpp b/node/osdep/LinuxEthernetTap.hpp new file mode 100644 index 00000000..e6aea2ec --- /dev/null +++ b/node/osdep/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/node/osdep/OSXEthernetTap.cpp b/node/osdep/OSXEthernetTap.cpp new file mode 100644 index 00000000..d5d02f40 --- /dev/null +++ b/node/osdep/OSXEthernetTap.cpp @@ -0,0 +1,698 @@ +/* + * 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[16],tmp[4096]; + 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"); + + // Check for existence of ZT tap devices, try to load module if not there + const char *kextload = UNIX_COMMANDS[ZT_MAC_KEXTLOAD_COMMAND]; + if ((stat("/dev/zt0",&stattmp))&&(kextload)) { + strcpy(tmp,_r->homePath.c_str()); + long kextpid = (long)vfork(); + if (kextpid == 0) { + chdir(tmp); + execl(kextload,kextload,"-q","-repository",tmp,"tap.kext",(const char *)0); + _exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + waitpid(kextpid,&exitcode,0); + usleep(500); + } else throw std::runtime_error("unable to create subprocess with fork()"); + } + if (stat("/dev/zt0",&stattmp)) + throw std::runtime_error("/dev/zt# tap devices do not exist and unable to load kernel extension"); + + // Try to reopen the last device we had, if we had one and it's still unused. + bool recalledDevice = false; + if ((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"); + } + + const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; + if (!ifconfig) { + ::close(_fd); + throw std::runtime_error("unable to find 'ifconfig' command on system"); + } + + // Configure MAC address and MTU, bring interface up + Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); + Utils::snprintf(mtustr,sizeof(mtustr),"%u",mtu); + long cpid; + if ((cpid = (long)vfork()) == 0) { + execl(ifconfig,ifconfig,_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"up",(const char *)0); + _exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } + } + + _setIpv6Stuff(_dev.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); + + EthernetTap_instances_m.lock(); + ++EthernetTap_instances; + EthernetTap_instances_m.unlock(); +} + +OSXEthernetTap::~OSXEthernetTap() +{ + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); + + EthernetTap_instances_m.lock(); + int instances = --EthernetTap_instances; + EthernetTap_instances_m.unlock(); + if (instances <= 0) { + // Unload OSX kernel extension on the deletion of the last EthernetTap + // instance. + const char *kextunload = UNIX_COMMANDS[ZT_MAC_KEXTUNLOAD_COMMAND]; + if (kextunload) { + char tmp[4096]; + sprintf(tmp,"%s/tap.kext",_r->homePath.c_str()); + long kextpid = (long)vfork(); + if (kextpid == 0) { + execl(kextunload,kextunload,tmp,(const char *)0); + _exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + waitpid(kextpid,&exitcode,0); + } + } + } +} + +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) +{ + const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; + if (!ifconfig) + return false; + long cpid; + if ((cpid = (long)vfork()) == 0) { + execl(ifconfig,ifconfig,_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; // never reached, make compiler shut up about return value +} + +bool OSXEthernetTap::addIP(const InetAddress &ip) +{ + const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; + if (!ifconfig) { + LOG("ERROR: could not configure IP address for %s: unable to find 'ifconfig' command on system (checked /sbin, /bin, /usr/sbin, /usr/bin)",_dev.c_str()); + return false; + } + + if (!ip) + return false; + + std::set allIps(ips()); + if (allIps.count(ip) > 0) + return true; // IP/netmask already assigned + + // Remove and reconfigure if address is the same but netmask is different + for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) { + break; + } else { + LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); + } + } + } + + long cpid; + if ((cpid = (long)vfork()) == 0) { + execl(ifconfig,ifconfig,_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + _exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + + return false; +} + +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 + 14]; + if ((_fd > 0)&&(len <= _mtu)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + + int n = ::write(_fd,putBuf,len); + if (n <= 0) { + LOG("error writing packet to Ethernet tap device: %s",strerror(errno)); + } else if (n != (int)len) { + // Saw this gremlin once, so log it if we see it again... OSX tap + // or something seems to have goofy issues with certain MTUs. + LOG("ERROR: write underrun: %s tap write() wrote %d of %u bytes of frame",_dev.c_str(),n,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)) { + 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; + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + if (etherType != 0x8100) { // VLAN tagged frames are not supported! + data.copyFrom(getBuf + 14,(unsigned int)r - 14); + _handler(_arg,from,to,etherType,data); + } + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/node/osdep/OSXEthernetTap.hpp b/node/osdep/OSXEthernetTap.hpp new file mode 100644 index 00000000..7ca63730 --- /dev/null +++ b/node/osdep/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/node/osdep/UnixEthernetTap.cpp b/node/osdep/UnixEthernetTap.cpp deleted file mode 100644 index 36bb7442..00000000 --- a/node/osdep/UnixEthernetTap.cpp +++ /dev/null @@ -1,1007 +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 "Constants.hpp" -#include "UnixEthernetTap.hpp" -#include "Logger.hpp" -#include "RuntimeEnvironment.hpp" -#include "Utils.hpp" -#include "Mutex.hpp" - -// ff:ff:ff:ff:ff:ff with no ADI -static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - -// Command identifiers used with command finder static (on various *nixes) -#define ZT_UNIX_IP_COMMAND 1 -#define ZT_UNIX_IFCONFIG_COMMAND 2 -#define ZT_MAC_KEXTLOAD_COMMAND 3 -#define ZT_MAC_KEXTUNLOAD_COMMAND 4 - -// Finds external commands on startup ---------------------------------------- -class _CommandFinder -{ -public: - _CommandFinder() - { - _findCmd(ZT_UNIX_IFCONFIG_COMMAND,"ifconfig"); -#ifdef __LINUX__ - _findCmd(ZT_UNIX_IP_COMMAND,"ip"); -#endif -#ifdef __APPLE__ - _findCmd(ZT_MAC_KEXTLOAD_COMMAND,"kextload"); - _findCmd(ZT_MAC_KEXTUNLOAD_COMMAND,"kextunload"); -#endif - } - inline const char *operator[](int id) const - throw() - { - std::map::const_iterator c(_paths.find(id)); - if (c == _paths.end()) - return (const char *)0; - return c->second.c_str(); - } -private: - inline void _findCmd(int id,const char *name) - { - char tmp[4096]; - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/sbin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/sbin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/bin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - ZeroTier::Utils::snprintf(tmp,sizeof(tmp),"/usr/bin/%s",name); - if (ZeroTier::Utils::fileExists(tmp)) { - _paths[id] = tmp; - return; - } - } - std::map _paths; -}; -static const _CommandFinder UNIX_COMMANDS; -// --------------------------------------------------------------------------- - -#ifdef __LINUX__ -#include -#include -#include -#include -#include -#endif // __LINUX__ - -#ifdef __APPLE__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -struct prf_ra { // stupid OSX compile fix... in6_var defines this in a struct which namespaces it for C++ - u_char onlink : 1; - u_char autonomous : 1; - u_char reserved : 6; -} prf_ra; -#include -#include -#include -#include -#include -#include - -// These are KERNEL_PRIVATE... why? -#ifndef SIOCAUTOCONF_START -#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */ -#endif -#ifndef SIOCAUTOCONF_STOP -#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */ -#endif - -static volatile int EthernetTap_instances = 0; -static ZeroTier::Mutex EthernetTap_instances_m; - -static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts) -{ - struct in6_ndireq nd; - struct in6_ifreq ifr; - - int s = socket(AF_INET6,SOCK_DGRAM,0); - if (s <= 0) - return false; - - memset(&nd,0,sizeof(nd)); - strncpy(nd.ifname,ifname,sizeof(nd.ifname)); - - if (ioctl(s,SIOCGIFINFO_IN6,&nd)) { - close(s); - return false; - } - - unsigned long oldFlags = (unsigned long)nd.ndi.flags; - - if (performNUD) - nd.ndi.flags |= ND6_IFF_PERFORMNUD; - else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD; - - if (oldFlags != (unsigned long)nd.ndi.flags) { - if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) { - close(s); - return false; - } - } - - memset(&ifr,0,sizeof(ifr)); - strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name)); - if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) { - close(s); - return false; - } - - close(s); - return true; -} - -#endif // __APPLE__ - -namespace ZeroTier { - -// Only permit one tap to be opened concurrently across the entire process -static Mutex __tapCreateLock; - -#ifdef __LINUX__ -UnixEthernetTap::UnixEthernetTap( - 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("UnixEthernetTap",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); - - TRACE("tap %s created",_dev.c_str()); - - _thread = Thread::start(this); -} -#endif // __LINUX__ - -#ifdef __APPLE__ -UnixEthernetTap::UnixEthernetTap( - 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("UnixEthernetTap",mac,mtu), - _r(renv), - _handler(handler), - _arg(arg), - _fd(0), - _enabled(true) -{ - char devpath[64],ethaddr[64],mtustr[16],tmp[4096]; - struct stat stattmp; - Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally - - if (mtu > 4096) - throw std::runtime_error("max tap MTU is 4096"); - - // Check for existence of ZT tap devices, try to load module if not there - const char *kextload = UNIX_COMMANDS[ZT_MAC_KEXTLOAD_COMMAND]; - if ((stat("/dev/zt0",&stattmp))&&(kextload)) { - strcpy(tmp,_r->homePath.c_str()); - long kextpid = (long)vfork(); - if (kextpid == 0) { - chdir(tmp); - execl(kextload,kextload,"-q","-repository",tmp,"tap.kext",(const char *)0); - _exit(-1); - } else if (kextpid > 0) { - int exitcode = -1; - waitpid(kextpid,&exitcode,0); - usleep(500); - } else throw std::runtime_error("unable to create subprocess with fork()"); - } - if (stat("/dev/zt0",&stattmp)) - throw std::runtime_error("/dev/zt# tap devices do not exist and unable to load kernel extension"); - - // Try to reopen the last device we had, if we had one and it's still unused. - bool recalledDevice = false; - if ((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"); - } - - const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; - if (!ifconfig) { - ::close(_fd); - throw std::runtime_error("unable to find 'ifconfig' command on system"); - } - - // Configure MAC address and MTU, bring interface up - Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); - Utils::snprintf(mtustr,sizeof(mtustr),"%u",mtu); - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ifconfig,ifconfig,_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"up",(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - if (exitcode) { - ::close(_fd); - throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); - } - } - - _setIpv6Stuff(_dev.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); - - EthernetTap_instances_m.lock(); - ++EthernetTap_instances; - EthernetTap_instances_m.unlock(); -} -#endif // __APPLE__ - -UnixEthernetTap::~UnixEthernetTap() -{ - ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit - Thread::join(_thread); - ::close(_fd); - ::close(_shutdownSignalPipe[0]); - ::close(_shutdownSignalPipe[1]); - -#ifdef __APPLE__ - EthernetTap_instances_m.lock(); - int instances = --EthernetTap_instances; - EthernetTap_instances_m.unlock(); - if (instances <= 0) { - // Unload OSX kernel extension on the deletion of the last EthernetTap - // instance. - const char *kextunload = UNIX_COMMANDS[ZT_MAC_KEXTUNLOAD_COMMAND]; - if (kextunload) { - char tmp[4096]; - sprintf(tmp,"%s/tap.kext",_r->homePath.c_str()); - long kextpid = (long)vfork(); - if (kextpid == 0) { - execl(kextunload,kextunload,tmp,(const char *)0); - _exit(-1); - } else if (kextpid > 0) { - int exitcode = -1; - waitpid(kextpid,&exitcode,0); - } - } - } -#endif // __APPLE__ -} - -void UnixEthernetTap::setEnabled(bool en) -{ - _enabled = en; - // TODO: interface status change -} - -bool UnixEthernetTap::enabled() const -{ - return _enabled; -} - -void UnixEthernetTap::setDisplayName(const char *dn) -{ -} - -#ifdef __LINUX__ -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 UnixEthernetTap::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; -} -#endif // __LINUX__ - -#ifdef __APPLE__ -static bool ___removeIp(const std::string &_dev,const InetAddress &ip) -{ - const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; - if (!ifconfig) - return false; - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ifconfig,ifconfig,_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } - return false; // never reached, make compiler shut up about return value -} - -bool UnixEthernetTap::addIP(const InetAddress &ip) -{ - const char *ifconfig = UNIX_COMMANDS[ZT_UNIX_IFCONFIG_COMMAND]; - if (!ifconfig) { - LOG("ERROR: could not configure IP address for %s: unable to find 'ifconfig' command on system (checked /sbin, /bin, /usr/sbin, /usr/bin)",_dev.c_str()); - return false; - } - - if (!ip) - return false; - - std::set allIps(ips()); - if (allIps.count(ip) > 0) - return true; // IP/netmask already assigned - - // Remove and reconfigure if address is the same but netmask is different - for(std::set::iterator i(allIps.begin());i!=allIps.end();++i) { - if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { - if (___removeIp(_dev,*i)) { - break; - } else { - LOG("WARNING: failed to remove old IP/netmask %s to replace with %s",i->toString().c_str(),ip.toString().c_str()); - } - } - } - - long cpid; - if ((cpid = (long)vfork()) == 0) { - execl(ifconfig,ifconfig,_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); - _exit(-1); - } else { - int exitcode = -1; - waitpid(cpid,&exitcode,0); - return (exitcode == 0); - } - - return false; -} -#endif // __APPLE__ - -bool UnixEthernetTap::removeIP(const InetAddress &ip) -{ - if (ips().count(ip) > 0) { - if (___removeIp(_dev,ip)) - return true; - } - return false; -} - -std::set UnixEthernetTap::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 UnixEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) -{ - char putBuf[4096 + 14]; - if ((_fd > 0)&&(len <= _mtu)) { - to.copyTo(putBuf,6); - from.copyTo(putBuf + 6,6); - *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); - memcpy(putBuf + 14,data,len); - len += 14; - - int n = ::write(_fd,putBuf,len); - if (n <= 0) { - LOG("error writing packet to Ethernet tap device: %s",strerror(errno)); - } else if (n != (int)len) { - // Saw this gremlin once, so log it if we see it again... OSX tap - // or something seems to have goofy issues with certain MTUs. - LOG("ERROR: write underrun: %s tap write() wrote %d of %u bytes of frame",_dev.c_str(),n,len); - } - } -} - -std::string UnixEthernetTap::deviceName() const -{ - return _dev; -} - -std::string UnixEthernetTap::persistentId() const -{ - return std::string(); -} - -#ifdef __LINUX__ -bool UnixEthernetTap::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; -} -#endif // __LINUX__ - -#ifdef __APPLE__ -// -------------------------------------------------------------------------- -// This source is from: -// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt -// It's here because OSX 10.6 does not have this convenience function. - -#define SALIGN (sizeof(uint32_t) - 1) -#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \ -(SALIGN + 1)) -#define MAX_SYSCTL_TRY 5 -#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA) - -/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from */ -/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */ -//#define DARWIN_COMPAT - -//#ifdef DARWIN_COMPAT -#define GIM_SYSCTL_MIB NET_RT_IFLIST2 -#define GIM_RTM_ADDR RTM_NEWMADDR2 -//#else -//#define GIM_SYSCTL_MIB NET_RT_IFMALIST -//#define GIM_RTM_ADDR RTM_NEWMADDR -//#endif - -// Not in 10.6 includes so use our own -struct _intl_ifmaddrs { - struct _intl_ifmaddrs *ifma_next; - struct sockaddr *ifma_name; - struct sockaddr *ifma_addr; - struct sockaddr *ifma_lladdr; -}; - -static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif) -{ - int icnt = 1; - int dcnt = 0; - int ntry = 0; - size_t len; - size_t needed; - int mib[6]; - int i; - char *buf; - char *data; - char *next; - char *p; - struct ifma_msghdr2 *ifmam; - struct _intl_ifmaddrs *ifa, *ift; - struct rt_msghdr *rtm; - struct sockaddr *sa; - - mib[0] = CTL_NET; - mib[1] = PF_ROUTE; - mib[2] = 0; /* protocol */ - mib[3] = 0; /* wildcard address family */ - mib[4] = GIM_SYSCTL_MIB; - mib[5] = 0; /* no flags */ - do { - if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) - return (-1); - if ((buf = (char *)malloc(needed)) == NULL) - return (-1); - if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { - if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { - free(buf); - return (-1); - } - free(buf); - buf = NULL; - } - } while (buf == NULL); - - for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { - rtm = (struct rt_msghdr *)(void *)next; - if (rtm->rtm_version != RTM_VERSION) - continue; - switch (rtm->rtm_type) { - case GIM_RTM_ADDR: - ifmam = (struct ifma_msghdr2 *)(void *)rtm; - if ((ifmam->ifmam_addrs & RTA_IFA) == 0) - break; - icnt++; - p = (char *)(ifmam + 1); - for (i = 0; i < RTAX_MAX; i++) { - if ((RTA_MASKS & ifmam->ifmam_addrs & - (1 << i)) == 0) - continue; - sa = (struct sockaddr *)(void *)p; - len = SA_RLEN(sa); - dcnt += len; - p += len; - } - break; - } - } - - data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt); - if (data == NULL) { - free(buf); - return (-1); - } - - ifa = (struct _intl_ifmaddrs *)(void *)data; - data += sizeof(struct _intl_ifmaddrs) * icnt; - - memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt); - ift = ifa; - - for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { - rtm = (struct rt_msghdr *)(void *)next; - if (rtm->rtm_version != RTM_VERSION) - continue; - - switch (rtm->rtm_type) { - case GIM_RTM_ADDR: - ifmam = (struct ifma_msghdr2 *)(void *)rtm; - if ((ifmam->ifmam_addrs & RTA_IFA) == 0) - break; - - p = (char *)(ifmam + 1); - for (i = 0; i < RTAX_MAX; i++) { - if ((RTA_MASKS & ifmam->ifmam_addrs & - (1 << i)) == 0) - continue; - sa = (struct sockaddr *)(void *)p; - len = SA_RLEN(sa); - switch (i) { - case RTAX_GATEWAY: - ift->ifma_lladdr = - (struct sockaddr *)(void *)data; - memcpy(data, p, len); - data += len; - break; - - case RTAX_IFP: - ift->ifma_name = - (struct sockaddr *)(void *)data; - memcpy(data, p, len); - data += len; - break; - - case RTAX_IFA: - ift->ifma_addr = - (struct sockaddr *)(void *)data; - memcpy(data, p, len); - data += len; - break; - - default: - data += len; - break; - } - p += len; - } - ift->ifma_next = ift + 1; - ift = ift->ifma_next; - break; - } - } - - free(buf); - - if (ift > ifa) { - ift--; - ift->ifma_next = NULL; - *pif = ifa; - } else { - *pif = NULL; - free(ifa); - } - return (0); -} - -static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp) -{ - free(ifmp); -} - -// -------------------------------------------------------------------------- - -bool UnixEthernetTap::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; -} -#endif // __APPLE__ - -void UnixEthernetTap::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; - to.setTo(getBuf,6); - from.setTo(getBuf + 6,6); - unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); - if (etherType != 0x8100) { // VLAN tagged frames are not supported! - data.copyFrom(getBuf + 14,(unsigned int)r - 14); - _handler(_arg,from,to,etherType,data); - } - r = 0; - } - } - } - } -} - -} // namespace ZeroTier diff --git a/node/osdep/UnixEthernetTap.hpp b/node/osdep/UnixEthernetTap.hpp deleted file mode 100644 index 2d19b19f..00000000 --- a/node/osdep/UnixEthernetTap.hpp +++ /dev/null @@ -1,105 +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_UNIXETHERNETTAP_HPP -#define ZT_UNIXETHERNETTAP_HPP - -#include -#include - -#include - -#include "EthernetTap.hpp" -#include "Mutex.hpp" -#include "Thread.hpp" - -namespace ZeroTier { - -class RuntimeEnvironment; - -/** - * Tap device using Unix taps (contains special case code for Linux, OSX, etc. but they're all similar) - */ -class UnixEthernetTap : public EthernetTap -{ -public: - /** - * Construct a new TAP device - * - * Handler arguments: arg,from,to,etherType,data - * - * @param renv Runtime environment - * @param tryToGetDevice Unix device name (e.g. zt0) - * @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 tap device - */ - UnixEthernetTap( - 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); - - virtual ~UnixEthernetTap(); - - 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(); - -private: - const RuntimeEnvironment *_r; - 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 -- cgit v1.2.3