diff --git a/core/Identity.hpp b/core/Identity.hpp index b074d02f0..86de8f865 100644 --- a/core/Identity.hpp +++ b/core/Identity.hpp @@ -25,8 +25,6 @@ #include "Containers.hpp" #define ZT_IDENTITY_STRING_BUFFER_LENGTH 1024 -#define ZT_IDENTITY_C25519_PUBLIC_KEY_SIZE ZT_C25519_COMBINED_PUBLIC_KEY_SIZE -#define ZT_IDENTITY_C25519_PRIVATE_KEY_SIZE ZT_C25519_COMBINED_PRIVATE_KEY_SIZE #define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE (7 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + ZT_ECC384_PUBLIC_KEY_SIZE) #define ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE (ZT_C25519_COMBINED_PRIVATE_KEY_SIZE + ZT_ECC384_PRIVATE_KEY_SIZE) #define ZT_IDENTITY_MARSHAL_SIZE_MAX (ZT_ADDRESS_LENGTH + 4 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE) diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp deleted file mode 100644 index b6f70c234..000000000 --- a/osdep/BlockingQueue.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c)2013-2020 ZeroTier, Inc. - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file in the project's root directory. - * - * Change Date: 2025-01-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2.0 of the Apache License. - */ -/****/ - -#ifndef ZT_BLOCKINGQUEUE_HPP -#define ZT_BLOCKINGQUEUE_HPP - -#include -#include -#include -#include -#include - -#include "../core/Constants.hpp" - -namespace ZeroTier { - -/** - * Simple C++11 thread-safe queue - * - * Do not use in core/ since we have not gone C++11 there yet. - */ -template -class BlockingQueue -{ -public: - enum TimedWaitResult - { - OK, - TIMED_OUT, - STOP - }; - - ZT_INLINE BlockingQueue(void) : r(true) {} - - ZT_INLINE void post(T t) - { - std::lock_guard lock(m); - q.push(t); - c.notify_one(); - } - - ZT_INLINE void postLimit(T t,const unsigned long limit) - { - std::unique_lock lock(m); - for(;;) { - if (q.size() < limit) { - q.push(t); - c.notify_one(); - break; - } - if (!r) - break; - gc.wait(lock); - } - } - - ZT_INLINE void stop(void) - { - std::lock_guard lock(m); - r = false; - c.notify_all(); - gc.notify_all(); - } - - ZT_INLINE bool get(T &value) - { - std::unique_lock lock(m); - if (!r) return false; - while (q.empty()) { - c.wait(lock); - if (!r) { - gc.notify_all(); - return false; - } - } - value = q.front(); - q.pop(); - gc.notify_all(); - return true; - } - - ZT_INLINE TimedWaitResult get(T &value,const unsigned long ms) - { - const std::chrono::milliseconds ms2{ms}; - std::unique_lock lock(m); - if (!r) return STOP; - while (q.empty()) { - if (c.wait_for(lock,ms2) == std::cv_status::timeout) - return ((r) ? TIMED_OUT : STOP); - else if (!r) - return STOP; - } - value = q.front(); - q.pop(); - return OK; - } - -private: - std::atomic_bool r; - std::queue q; - mutable std::mutex m; - mutable std::condition_variable c,gc; -}; - -} // namespace ZeroTier - -#endif diff --git a/osdep/CMakeLists.txt b/osdep/CMakeLists.txt index c6ee16a89..55d0283d9 100644 --- a/osdep/CMakeLists.txt +++ b/osdep/CMakeLists.txt @@ -8,10 +8,10 @@ set(src ) set(headers - BlockingQueue.hpp ManagedRoute.hpp OSUtils.hpp Thread.hpp + rust-osdep.h ) if(WIN32) @@ -27,11 +27,6 @@ elseif(UNIX) set(src ${src} freebsd_getifmaddrs.c) set(headers ${headers} freebsd_getifmaddrs.h) endif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - - if(CMAKE_SYSTEM_NAME MATCHES "Linux") - set(src ${src} LinuxEthernetTap.cpp LinuxNetLink.cpp) - set(headers ${headers} LinuxEthernetTap.hpp LinuxNetLink.hpp) - endif(CMAKE_SYSTEM_NAME MATCHES "Linux") endif(WIN32) add_library(${PROJECT_NAME} STATIC ${src} ${headers}) diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp deleted file mode 100644 index 25ed52edf..000000000 --- a/osdep/LinuxEthernetTap.cpp +++ /dev/null @@ -1,555 +0,0 @@ -/* - * Copyright (c)2019 ZeroTier, Inc. - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file in the project's root directory. - * - * Change Date: 2025-01-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2.0 of the Apache License. - */ -/****/ - -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wrestrict" -#endif - -#include "../node/Constants.hpp" - -#ifdef __LINUX__ - -#include "../node/Utils.hpp" -#include "../node/Mutex.hpp" -#include "../node/Dictionary.hpp" -#include "OSUtils.hpp" -#include "LinuxEthernetTap.hpp" -#include "LinuxNetLink.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifndef IFNAMSIZ -#define IFNAMSIZ 16 -#endif - -#define ZT_TAP_BUF_SIZE 16384 - -// ff:ff:ff:ff:ff:ff with no ADI -static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - -namespace ZeroTier { - -static const char _base32_chars[32] = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','2','3','4','5','6','7' }; -static void _base32_5_to_8(const uint8_t *in,char *out) -{ - out[0] = _base32_chars[(in[0]) >> 3]; - out[1] = _base32_chars[(in[0] & 0x07) << 2 | (in[1] & 0xc0) >> 6]; - out[2] = _base32_chars[(in[1] & 0x3e) >> 1]; - out[3] = _base32_chars[(in[1] & 0x01) << 4 | (in[2] & 0xf0) >> 4]; - out[4] = _base32_chars[(in[2] & 0x0f) << 1 | (in[3] & 0x80) >> 7]; - out[5] = _base32_chars[(in[3] & 0x7c) >> 2]; - out[6] = _base32_chars[(in[3] & 0x03) << 3 | (in[4] & 0xe0) >> 5]; - out[7] = _base32_chars[(in[4] & 0x1f)]; -} - -LinuxEthernetTap::LinuxEthernetTap( - const char *homePath, - const MAC &mac, - unsigned int mtu, - unsigned int metric, - uint64_t nwid, - const char *friendlyName, - void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), - void *arg) : - _handler(handler), - _arg(arg), - _nwid(nwid), - _mac(mac), - _homePath(homePath), - _mtu(mtu), - _fd(0), - _enabled(true) -{ - static std::mutex s_tapCreateLock; - char procpath[128],nwids[32]; - struct stat sbuf; - - // Create only one tap at a time globally. - std::lock_guard tapCreateLock(s_tapCreateLock); - - // Make sure Linux netlink is initialized. - (void)LinuxNetLink::getInstance(); - - OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid); - - _fd = ::open("/dev/net/tun",O_RDWR); - if (_fd <= 0) { - _fd = ::open("/dev/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)); - - // Restore device names from legacy devicemap, but for new devices we use a base32-based - // canonical device name. - std::map globalDeviceMap; - FILE *devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"r"); - if (devmapf) { - char buf[256]; - while (fgets(buf,sizeof(buf),devmapf)) { - char *x = (char *)0; - char *y = (char *)0; - char *saveptr = (char *)0; - for(char *f=Utils::stok(buf,"\r\n=",&saveptr);(f);f=Utils::stok((char *)0,"\r\n=",&saveptr)) { - if (!x) x = f; - else if (!y) y = f; - else break; - } - if ((x)&&(y)&&(x[0])&&(y[0])) - globalDeviceMap[x] = y; - } - fclose(devmapf); - } - bool recalledDevice = false; - std::map::const_iterator gdmEntry = globalDeviceMap.find(nwids); - if (gdmEntry != globalDeviceMap.end()) { - Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),gdmEntry->second.c_str()); - OSUtils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); - recalledDevice = (stat(procpath,&sbuf) != 0); - } - - if (!recalledDevice) { -#ifdef __SYNOLOGY__ - int devno = 50; - do { - OSUtils::ztsnprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"eth%d",devno++); - OSUtils::ztsnprintf(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 -#else - uint64_t trial = 0; // incremented in the very unlikely event of a name collision with another network - do { - const uint64_t nwid40 = (nwid ^ (nwid >> 24)) + trial++; - uint8_t tmp2[5]; - char tmp3[11]; - tmp2[0] = (uint8_t)((nwid40 >> 32) & 0xff); - tmp2[1] = (uint8_t)((nwid40 >> 24) & 0xff); - tmp2[2] = (uint8_t)((nwid40 >> 16) & 0xff); - tmp2[3] = (uint8_t)((nwid40 >> 8) & 0xff); - tmp2[4] = (uint8_t)(nwid40 & 0xff); - tmp3[0] = 'z'; - tmp3[1] = 't'; - _base32_5_to_8(tmp2,tmp3 + 2); - tmp3[10] = (char)0; - memcpy(ifr.ifr_name,tmp3,11); - OSUtils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); - } while (stat(procpath,&sbuf) == 0); -#endif - } - - 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"); - } - - ::ioctl(_fd,TUNSETPERSIST,0); // valgrind may generate a false alarm here - _dev = ifr.ifr_name; - ::fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); - - (void)::pipe(_shutdownSignalPipe); - - _tapReaderThread = std::thread([this]{ - fd_set readfds,nullfds; - int n,nfds,r; - void *buf = nullptr; - std::vector buffers; - - { - struct ifreq ifr; - memset(&ifr,0,sizeof(ifr)); - strcpy(ifr.ifr_name,_dev.c_str()); - - const int sock = socket(AF_INET,SOCK_DGRAM,0); - if (sock <= 0) - return; - - if (ioctl(sock,SIOCGIFFLAGS,(void *)&ifr) < 0) { - ::close(sock); - printf("WARNING: ioctl() failed setting up Linux tap device (bring interface up)\n"); - return; - } - ifr.ifr_flags |= IFF_UP; - if (ioctl(sock,SIOCSIFFLAGS,(void *)&ifr) < 0) { - ::close(sock); - printf("WARNING: ioctl() failed setting up Linux tap device (bring interface up)\n"); - return; - } - - // Some kernel versions seem to require you to yield while the device comes up - // before they will accept MTU and MAC. For others it doesn't matter, but is - // harmless. This was moved to the worker thread though so as not to block the - // main ZeroTier loop. - usleep(500000); - - 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(sock); - printf("WARNING: ioctl() failed setting up Linux tap device (set MAC)\n"); - return; - } - - ifr.ifr_ifru.ifru_mtu = (int)_mtu; - if (ioctl(sock,SIOCSIFMTU,(void *)&ifr) < 0) { - ::close(sock); - printf("WARNING: ioctl() failed setting up Linux tap device (set MTU)\n"); - return; - } - - fcntl(_fd,F_SETFL,O_NONBLOCK); - - ::close(sock); - } - - 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)) { - for(;;) { // read until there are no more packets, then return to outer select() loop - if (!buf) { - // To reduce use of the mutex, we keep a local buffer vector and - // swap (which is a pointer swap) with the global one when it's - // empty. This retrieves a batch of buffers to use. - if (buffers.empty()) { - std::lock_guard l(_buffers_l); - buffers.swap(_buffers); - } - if (buffers.empty()) { - buf = malloc(ZT_TAP_BUF_SIZE); - if (!buf) - break; - } else { - buf = buffers.back(); - buffers.pop_back(); - } - } - - n = (int)::read(_fd,reinterpret_cast(buf) + r,ZT_TAP_BUF_SIZE - r); - - if (n > 0) { - // 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) { - _tapq.post(std::pair(buf,r)); - buf = nullptr; - } - - r = 0; - } - } else { - r = 0; - break; - } - } - } - } - }); - - _tapProcessorThread = std::thread([this] { - MAC to,from; - std::pair qi; - while (_tapq.get(qi)) { - uint8_t *const b = reinterpret_cast(qi.first); - if (b) { - to.setTo(b, 6); - from.setTo(b + 6, 6); - unsigned int etherType = Utils::ntoh(((const uint16_t *)b)[6]); - _handler(_arg, nullptr, _nwid, from, to, etherType, 0, (const void *)(b + 14),(unsigned int)(qi.second - 14)); - { - std::lock_guard l(_buffers_l); - if (_buffers.size() < 128) - _buffers.push_back(qi.first); - else free(qi.first); - } - } else break; - } - }); -} - -LinuxEthernetTap::~LinuxEthernetTap() -{ - (void)::write(_shutdownSignalPipe[1],"\0",1); // causes reader thread to exit - _tapq.post(std::pair(nullptr,0)); // causes processor thread to exit - - ::close(_fd); - ::close(_shutdownSignalPipe[0]); - ::close(_shutdownSignalPipe[1]); - - _tapReaderThread.join(); - _tapProcessorThread.join(); - - for(std::vector::iterator i(_buffers.begin());i!=_buffers.end();++i) - free(*i); - std::vector< std::pair > dv(_tapq.drain()); - for(std::vector< std::pair >::iterator i(dv.begin());i!=dv.end();++i) { - if (i->first) - free(i->first); - } -} - -void LinuxEthernetTap::setEnabled(bool en) -{ - _enabled = en; -} - -bool LinuxEthernetTap::enabled() const -{ - return _enabled; -} - -static bool ___removeIp(const std::string &_dev,const InetAddress &ip) -{ - LinuxNetLink::getInstance().removeAddress(ip, _dev.c_str()); - return true; -} - -bool LinuxEthernetTap::addIps(std::vector ips) -{ -#ifdef __SYNOLOGY__ - std::string filepath = "/etc/sysconfig/network-scripts/ifcfg-"+_dev; - std::string cfg_contents = "DEVICE="+_dev+"\nBOOTPROTO=static"; - int ip4=0,ip6=0,ip4_tot=0,ip6_tot=0; - - for(int i=0; i<(int)ips.size(); i++) { - if (ips[i].isV4()) - ip4_tot++; - else - ip6_tot++; - } - // Assemble and write contents of ifcfg-dev file - for(int i=0; i<(int)ips.size(); i++) { - if (ips[i].isV4()) { - char iptmp[64],iptmp2[64]; - std::string numstr4 = ip4_tot > 1 ? std::to_string(ip4) : ""; - cfg_contents += "\nIPADDR"+numstr4+"="+ips[i].toIpString(iptmp) - + "\nNETMASK"+numstr4+"="+ips[i].netmask().toIpString(iptmp2)+"\n"; - ip4++; - } else { - char iptmp[64],iptmp2[64]; - std::string numstr6 = ip6_tot > 1 ? std::to_string(ip6) : ""; - cfg_contents += "\nIPV6ADDR"+numstr6+"="+ips[i].toIpString(iptmp) - + "\nNETMASK"+numstr6+"="+ips[i].netmask().toIpString(iptmp2)+"\n"; - ip6++; - } - } - OSUtils::writeFile(filepath.c_str(), cfg_contents.c_str(), cfg_contents.length()); - // Finally, add IPs - for(int i=0; i<(int)ips.size(); i++){ - LinuxNetLink::getInstance().addAddress(ips[i], _dev.c_str()); - } - return true; -#endif // __SYNOLOGY__ - return false; -} - -bool LinuxEthernetTap::addIp(const InetAddress &ip) -{ - if (!ip) - return false; - - std::vector allIps(ips()); - if (std::binary_search(allIps.begin(),allIps.end(),ip)) - return true; - - // Remove and reconfigure if address is the same but netmask is different - for(std::vector::iterator i(allIps.begin());i!=allIps.end();++i) { - if (i->ipsEqual(ip)) - ___removeIp(_dev,*i); - } - - LinuxNetLink::getInstance().addAddress(ip, _dev.c_str()); - - return true; -} - -bool LinuxEthernetTap::removeIp(const InetAddress &ip) -{ - if (!ip) - return true; - std::vector allIps(ips()); - if (std::find(allIps.begin(),allIps.end(),ip) != allIps.end()) { - if (___removeIp(_dev,ip)) - return true; - } - return false; -} - -std::vector LinuxEthernetTap::ips() const -{ - struct ifaddrs *ifa = (struct ifaddrs *)0; - if (getifaddrs(&ifa)) - return std::vector(); - - std::vector 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.push_back(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.push_back(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); - - std::sort(r.begin(),r.end()); - r.erase(std::unique(r.begin(),r.end()),r.end()); - - return r; -} - -void LinuxEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) -{ - char putBuf[ZT_MAX_MTU + 64]; - 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; - (void)::write(_fd,putBuf,len); - } -} - -std::string LinuxEthernetTap::deviceName() const -{ - return _dev; -} - -void LinuxEthernetTap::setFriendlyName(const char *friendlyName) -{ -} - -void LinuxEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) -{ - char *ptr,*ptr2; - unsigned char mac[6]; - std::vector 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.push_back(MulticastGroup(MAC(mac,6),0)); - } - } - ::close(fd); - } - - std::vector allIps(ips()); - for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) - newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); - - std::sort(newGroups.begin(),newGroups.end()); - newGroups.erase(std::unique(newGroups.begin(),newGroups.end()),newGroups.end()); - - for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { - if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) - added.push_back(*m); - } - for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { - if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) - removed.push_back(*m); - } - - _multicastGroups.swap(newGroups); -} - -void LinuxEthernetTap::setMtu(unsigned int mtu) -{ - if (_mtu != mtu) { - _mtu = mtu; - int sock = socket(AF_INET,SOCK_DGRAM,0); - if (sock > 0) { - struct ifreq ifr; - memset(&ifr,0,sizeof(ifr)); - ifr.ifr_ifru.ifru_mtu = (int)mtu; - ioctl(sock,SIOCSIFMTU,(void *)&ifr); - close(sock); - } - } -} - -} // namespace ZeroTier - -#endif // __LINUX__ diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp deleted file mode 100644 index 9e9206ead..000000000 --- a/osdep/LinuxEthernetTap.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c)2019 ZeroTier, Inc. - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file in the project's root directory. - * - * Change Date: 2025-01-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2.0 of the Apache License. - */ -/****/ - -#ifndef ZT_LINUXETHERNETTAP_HPP -#define ZT_LINUXETHERNETTAP_HPP - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include "../node/MulticastGroup.hpp" -#include "EthernetTap.hpp" -#include "BlockingQueue.hpp" - -namespace ZeroTier { - -class LinuxEthernetTap : public EthernetTap -{ -public: - LinuxEthernetTap( - const char *homePath, - const MAC &mac, - unsigned int mtu, - unsigned int metric, - uint64_t nwid, - const char *friendlyName, - void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), - void *arg); - - virtual ~LinuxEthernetTap(); - - virtual void setEnabled(bool en); - virtual bool enabled() const; - virtual bool addIp(const InetAddress &ip); - virtual bool addIps(std::vector ips); - virtual bool removeIp(const InetAddress &ip); - virtual std::vector ips() const; - virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); - virtual std::string deviceName() const; - virtual void setFriendlyName(const char *friendlyName); - virtual void scanMulticastGroups(std::vector &added,std::vector &removed); - virtual void setMtu(unsigned int mtu); - virtual void setDns(const char *domain, const std::vector &servers) {} - -private: - void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); - void *_arg; - uint64_t _nwid; - MAC _mac; - std::string _homePath; - std::string _dev; - std::vector _multicastGroups; - unsigned int _mtu; - int _fd; - int _shutdownSignalPipe[2]; - std::atomic_bool _enabled; - std::thread _tapReaderThread; - std::thread _tapProcessorThread; - std::mutex _buffers_l; - std::vector _buffers; - BlockingQueue< std::pair > _tapq; -}; - -} // namespace ZeroTier - -#endif diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index e457a777d..3635f6792 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -12,7 +12,6 @@ /****/ #include "../core/Constants.hpp" -#include "../core/Utils.hpp" #include "../core/Containers.hpp" #include "OSUtils.hpp" diff --git a/osdep/rust-osdep.cpp b/osdep/rust-osdep.cpp index 74355cf7c..600141580 100644 --- a/osdep/rust-osdep.cpp +++ b/osdep/rust-osdep.cpp @@ -1,4 +1,8 @@ #include "../core/Constants.hpp" +#include "../core/Mutex.hpp" +#include "../core/Containers.hpp" +#include "OSUtils.hpp" + #include "rust-osdep.h" #ifdef __APPLE__ @@ -13,16 +17,91 @@ extern "C" { #ifdef __APPLE__ -extern const unsigned long c_BIOCSBLEN = BIOCSBLEN; -extern const unsigned long c_BIOCIMMEDIATE = BIOCIMMEDIATE; -extern const unsigned long c_BIOCSSEESENT = BIOCSSEESENT; -extern const unsigned long c_BIOCSETIF = BIOCSETIF; -extern const unsigned long c_BIOCSHDRCMPLT = BIOCSHDRCMPLT; -extern const unsigned long c_BIOCPROMISC = BIOCPROMISC; -extern const unsigned long c_SIOCGIFINFO_IN6 = SIOCGIFINFO_IN6; -extern const unsigned long c_SIOCSIFINFO_FLAGS = SIOCSIFINFO_FLAGS; -extern const unsigned long c_SIOCAUTOCONF_START = SIOCAUTOCONF_START; -extern const unsigned long c_SIOCAUTOCONF_STOP = SIOCAUTOCONF_STOP; +const unsigned long c_BIOCSBLEN = BIOCSBLEN; +const unsigned long c_BIOCIMMEDIATE = BIOCIMMEDIATE; +const unsigned long c_BIOCSSEESENT = BIOCSSEESENT; +const unsigned long c_BIOCSETIF = BIOCSETIF; +const unsigned long c_BIOCSHDRCMPLT = BIOCSHDRCMPLT; +const unsigned long c_BIOCPROMISC = BIOCPROMISC; +const unsigned long c_SIOCGIFINFO_IN6 = SIOCGIFINFO_IN6; +const unsigned long c_SIOCSIFINFO_FLAGS = SIOCSIFINFO_FLAGS; +const unsigned long c_SIOCAUTOCONF_START = SIOCAUTOCONF_START; +const unsigned long c_SIOCAUTOCONF_STOP = SIOCAUTOCONF_STOP; #endif +const char *platformDefaultHomePath() +{ + static ZeroTier::Mutex s_lock; + static ZeroTier::String s_homePath; + + ZeroTier::Mutex::Lock l(s_lock); + if (s_homePath.empty()) { +#ifdef __QNAP__ + + char *cmd = "/sbin/getcfg zerotier Install_Path -f /etc/config/qpkg.conf"; + char buf[128]; + FILE *fp; + if ((fp = popen(cmd, "r")) == NULL) { + printf("Error opening pipe!\n"); + return NULL; + } + while (fgets(buf, 128, fp) != NULL) { } + if(pclose(fp)) { + printf("Command not found or exited with error status\n"); + return NULL; + } + String homeDir = String(buf); + homeDir.erase(std::remove(homeDir.begin(), homeDir.end(), '\n'), homeDir.end()); + s_homePath = homeDir; + +#else + +#ifdef __WINDOWS__ + + DWORD bufferSize = 65535; + ZeroTier::String userDefinedPath; + bufferSize = GetEnvironmentVariable("ZEROTIER_HOME", &userDefinedPath[0], bufferSize); + if (bufferSize) { + s_homePath = userDefinedPath; + } else { + char buf[16384]; + if (SUCCEEDED(SHGetFolderPathA(NULL,CSIDL_COMMON_APPDATA,NULL,0,buf))) { + ZeroTier::String tmp(buf); + tmp.append("\\ZeroTier"); + s_homePath = tmp; + } else { + s_homePath = "C:\\ZeroTier"; + } + } + +#else + + if (const char *userDefinedPath = getenv("ZEROTIER_HOME")) { + s_homePath = userDefinedPath; + } else { +#ifdef __APPLE__ + s_homePath = "/Library/Application Support/ZeroTier"; +#else +#ifdef __BSD__ + s_homePath = "/var/db/zerotier"; +#else + s_homePath = "/var/lib/zerotier"; +#endif // __BSD__ or not +#endif // __APPLE__ or not + } + +#endif // __WINDOWS__ or not + +#endif // __QNAP__ or not + + if (s_homePath.empty()) + s_homePath = "." ZT_PATH_SEPARATOR_S; + } + + return s_homePath.c_str(); +} + +int64_t msSinceEpoch() +{ return ZeroTier::OSUtils::now(); } + } diff --git a/osdep/rust-osdep.h b/osdep/rust-osdep.h index a2a8b841d..e076b108e 100644 --- a/osdep/rust-osdep.h +++ b/osdep/rust-osdep.h @@ -46,3 +46,14 @@ extern const unsigned long c_SIOCAUTOCONF_STOP; } #endif #endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char *platformDefaultHomePath(); +extern int64_t msSinceEpoch(); + +#ifdef __cplusplus +} +#endif diff --git a/rust-zerotier-service/build.rs b/rust-zerotier-service/build.rs index 788b9dafc..ff5ce3db2 100644 --- a/rust-zerotier-service/build.rs +++ b/rust-zerotier-service/build.rs @@ -1,6 +1,8 @@ fn main() { let d = env!("CARGO_MANIFEST_DIR"); println!("cargo:rustc-link-search=native={}/../build/core", d); + println!("cargo:rustc-link-search=native={}/../build/osdep", d); println!("cargo:rustc-link-lib=static=zt_core"); + println!("cargo:rustc-link-lib=static=zt_osdep"); println!("cargo:rustc-link-lib=c++"); } diff --git a/rust-zerotier-service/src/cli.rs b/rust-zerotier-service/src/cli.rs index 6d2b71142..2d57a0199 100644 --- a/rust-zerotier-service/src/cli.rs +++ b/rust-zerotier-service/src/cli.rs @@ -1,3 +1,16 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + use std::str::FromStr; use clap::{App, Arg, ArgMatches, ErrorKind}; diff --git a/rust-zerotier-service/src/commands/cert.rs b/rust-zerotier-service/src/commands/cert.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/controller.rs b/rust-zerotier-service/src/commands/controller.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/identity.rs b/rust-zerotier-service/src/commands/identity.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/join.rs b/rust-zerotier-service/src/commands/join.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/leave.rs b/rust-zerotier-service/src/commands/leave.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/locator.rs b/rust-zerotier-service/src/commands/locator.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/mod.rs b/rust-zerotier-service/src/commands/mod.rs new file mode 100644 index 000000000..8116efd57 --- /dev/null +++ b/rust-zerotier-service/src/commands/mod.rs @@ -0,0 +1,24 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +pub(crate) mod service; +pub(crate) mod status; +pub(crate) mod set; +pub(crate) mod peer; +pub(crate) mod network; +pub(crate) mod join; +pub(crate) mod leave; +pub(crate) mod controller; +pub(crate) mod identity; +pub(crate) mod locator; +pub(crate) mod cert; diff --git a/rust-zerotier-service/src/commands/network.rs b/rust-zerotier-service/src/commands/network.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/peer.rs b/rust-zerotier-service/src/commands/peer.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/service.rs b/rust-zerotier-service/src/commands/service.rs new file mode 100644 index 000000000..9998d6cbb --- /dev/null +++ b/rust-zerotier-service/src/commands/service.rs @@ -0,0 +1,206 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +use std::collections::BTreeMap; +use std::net::IpAddr; +use std::rc::Rc; +use std::str::FromStr; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use warp::Filter; +use warp::hyper::{HeaderMap, Method}; +use warp::hyper::body::Bytes; + +use zerotier_core::*; + +use crate::fastudpsocket::*; +use crate::localconfig::*; +use crate::network::Network; +use crate::getifaddrs; + +struct ServiceEventHandler {} + +impl NodeEventHandler for ServiceEventHandler { + fn virtual_network_config(&self, network_id: NetworkId, network_obj: &Arc, config_op: VirtualNetworkConfigOperation, config: Option<&VirtualNetworkConfig>) { + } + + #[inline(always)] + fn virtual_network_frame(&self, network_id: NetworkId, network_obj: &Arc, source_mac: MAC, dest_mac: MAC, ethertype: u16, vlan_id: u16, data: &[u8]) { + } + + fn event(&self, event: Event, event_data: &[u8]) { + } + + fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) { + } + + fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> Option> { + None + } + + #[inline(always)] + fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32 { + 0 + } + + #[inline(always)] + fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool { + true + } + + fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option { + None + } +} + +pub(crate) fn run() -> i32 { + let inaddr_v6_any = IpAddr::from_str("::0").unwrap(); + let mut process_exit_value: i32 = 0; + + // Current active local configuration for this node. + let mut local_config: Box = Box::new(LocalConfig::default()); + + // Event handler for Node. + let handler: Arc = Arc::new(ServiceEventHandler{}); + + // From this point on we are in Tokio async land... + let tokio_rt = tokio::runtime::Builder::new_multi_thread().thread_stack_size(zerotier_core::RECOMMENDED_THREAD_STACK_SIZE).build().unwrap(); + tokio_rt.block_on(async { + // Keeps track of FastUDPSocket instances by bound address. + let mut udp_sockets: BTreeMap = BTreeMap::new(); + + // Send something to interrupt_tx to interrupt the inner loop and force it to + // detect a change or exit if run has been set to false. + let (mut interrupt_tx, mut interrupt_rx) = futures::channel::mpsc::channel::(2); + + // Setting this to false terminates the service. It's atomic since this is multithreaded. + let run = AtomicBool::new(true); + + loop { + let mut warp_server_port = local_config.settings.primary_port; + + let root = warp::path::end().map(|| { + warp::reply::with_status("404", warp::hyper::StatusCode::NOT_FOUND) + }); + + let status = warp::path("status") + .and(warp::method()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) + .map(|method: Method, headers: HeaderMap, post_data: Bytes| { + "status" + }); + let network = warp::path!("network" / String) + .and(warp::method()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) + .map(|nwid_str: String, method: Method, headers: HeaderMap, post_data: Bytes| { + "network" + }); + let peer = warp::path!("peer" / String) + .and(warp::method()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) + .map(|peer_str: String, method: Method, headers: HeaderMap, post_data: Bytes| { + "peer" + }); + + let (mut shutdown_tx, mut shutdown_rx) = futures::channel::oneshot::channel(); + let warp_server = warp::serve(warp::any().and(root + .or(status) + .or(network) + .or(peer) + )).try_bind_with_graceful_shutdown((inaddr_v6_any, warp_server_port), async { let _ = shutdown_rx.await; }); + if warp_server.is_err() { + // TODO: log unable to bind to primary port + run.store(false, Ordering::Relaxed); + } + let warp_server = tokio_rt.spawn(warp_server.unwrap().1); + + let mut loop_delay = 10; + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(loop_delay)) => {}, + _ = interrupt_rx.next() => {}, + _ = tokio::signal::ctrl_c() => { + // TODO: log CTRL+C received + run.store(false, Ordering::Relaxed); + let _ = shutdown_tx.send(()); + break; + } + } + + // Enumerate physical addresses on the system, creating a map with an entry for + // the primary_port and another for the secondary_port if bound. + let mut system_addrs: BTreeMap = BTreeMap::new(); + getifaddrs::for_each_address(|addr: &InetAddress, dev: &str| { + if !local_config.settings.is_interface_blacklisted(dev) { + let mut a = addr.clone(); + a.set_port(local_config.settings.primary_port); + system_addrs.insert(a, String::from(dev)); + if local_config.settings.secondary_port.is_some() { + let mut a = addr.clone(); + a.set_port(local_config.settings.secondary_port.unwrap()); + system_addrs.insert(a, String::from(dev)); + } + } + }); + + // Close UDP bindings that no longer apply. + let mut udp_sockets_to_close: Vec = Vec::new(); + for sock in udp_sockets.iter() { + if !system_addrs.contains_key(sock.0) { + udp_sockets_to_close.push(sock.0.clone()); + } + } + for k in udp_sockets_to_close.iter() { + udp_sockets.remove(k); + } + + // Bind addresses that are not already bound. + for addr in system_addrs.iter() { + if !udp_sockets.contains_key(addr.0) { + let s = FastUDPSocket::new(addr.1.as_str(), addr.0, |raw_socket: &FastUDPRawOsSocket, from_address: &InetAddress, data: Buffer| { + // TODO: incoming packet handler + }); + if s.is_ok() { + udp_sockets.insert(addr.0.clone(), s.unwrap()); + } + } + } + + // TODO: check that ports are bound, implement port hunting or exit. + + if local_config.settings.primary_port != warp_server_port || !run.load(Ordering::Relaxed) { + let _ = shutdown_tx.send(()); + break; + } + } + + let _ = warp_server.await; + + if !run.load(Ordering::Relaxed) { + break; + } + tokio::time::sleep(Duration::from_millis(250)).await; + if !run.load(Ordering::Relaxed) { + break; + } + } + }); + + process_exit_value +} diff --git a/rust-zerotier-service/src/commands/set.rs b/rust-zerotier-service/src/commands/set.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/commands/status.rs b/rust-zerotier-service/src/commands/status.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rust-zerotier-service/src/fastudpsocket.rs b/rust-zerotier-service/src/fastudpsocket.rs index 8556fe71a..52904c91c 100644 --- a/rust-zerotier-service/src/fastudpsocket.rs +++ b/rust-zerotier-service/src/fastudpsocket.rs @@ -173,6 +173,8 @@ impl FastUDPSocket { sockets: Vec::new(), bind_address: address.clone() }; + s.threads.reserve(thread_count); + s.sockets.reserve(thread_count); let mut bind_failed_reason: &'static str = ""; for _ in 0..thread_count { @@ -211,6 +213,12 @@ impl FastUDPSocket { Ok(s) } + /// Get a slice of all raw sockets used. + #[inline(always)] + pub fn all_sockets(&self) -> &[FastUDPRawOsSocket] { + self.sockets.as_slice() + } + /// Send from this socket. /// This actually picks a thread's socket and sends from it. Since all /// are bound to the same IP:port which one is chosen doesn't matter. diff --git a/rust-zerotier-service/src/getifaddrs.rs b/rust-zerotier-service/src/getifaddrs.rs new file mode 100644 index 000000000..62a580462 --- /dev/null +++ b/rust-zerotier-service/src/getifaddrs.rs @@ -0,0 +1,76 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +use zerotier_core::InetAddress; +use std::ffi::CStr; +use std::ptr::{null_mut, copy_nonoverlapping}; +use std::mem::size_of; +use num_traits::cast::AsPrimitive; +use crate::osdep as osdep; + +#[inline(always)] +fn s6_addr_as_ptr(a: &A) -> *const A { + a as *const A +} + +/// Call supplied function or closure for each physical IP address in the system. +#[cfg(unix)] +pub(crate) fn for_each_address(mut f: F) { + unsafe { + let mut ifap: *mut osdep::ifaddrs = null_mut(); + if osdep::getifaddrs((&mut ifap as *mut *mut osdep::ifaddrs).cast()) == 0 { + let mut i = ifap; + while !i.is_null() { + if !(*i).ifa_addr.is_null() { + let mut a = InetAddress::new(); + + let sa_family = (*(*i).ifa_addr).sa_family as u8; + if sa_family == osdep::AF_INET as u8 { + copy_nonoverlapping((*i).ifa_addr.cast::(), (&mut a as *mut InetAddress).cast::(), size_of::()); + } else if sa_family == osdep::AF_INET6 as u8 { + copy_nonoverlapping((*i).ifa_addr.cast::(), (&mut a as *mut InetAddress).cast::(), size_of::()); + } else { + continue; + } + + let mut netmask_bits: u16 = 0; + if !(*i).ifa_netmask.is_null() { + if sa_family == osdep::AF_INET as u8 { + let mut a = (*(*i).ifa_netmask.cast::()).sin_addr.s_addr as u32; + netmask_bits = a.leading_ones() as u16; + } else if sa_family == osdep::AF_INET6 as u8 { + let a = s6_addr_as_ptr(&((*(*i).ifa_netmask.cast::()).sin6_addr)).cast::(); + for i in 0..16 as isize { + let mut b = *a.offset(i); + if b == 0xff { + netmask_bits += 8; + } else { + netmask_bits += b.leading_ones() as u16; + break; + } + } + } + } + a.set_port(netmask_bits); + + let dev = CStr::from_ptr((*i).ifa_name).to_str(); + if dev.is_ok() { + f(&a, dev.unwrap()); + } + } + i = (*i).ifa_next; + } + osdep::freeifaddrs(ifap.cast()); + } + } +} diff --git a/rust-zerotier-service/src/localconfig.rs b/rust-zerotier-service/src/localconfig.rs index 219a64832..0d46dfa43 100644 --- a/rust-zerotier-service/src/localconfig.rs +++ b/rust-zerotier-service/src/localconfig.rs @@ -220,7 +220,8 @@ impl LocalConfig { if md.is_err() { return Err(md.err().unwrap()); } - if md.unwrap().len() > 1048576 { + if md.unwrap().len() > 1048576 { // anti-memory-overflow sanity limit + return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "local config file too large (sanity limit: 1MiB)")) } let json = std::fs::read_to_string(path); if json.is_err() { diff --git a/rust-zerotier-service/src/main.rs b/rust-zerotier-service/src/main.rs index 37137802e..c8bf1fc7d 100644 --- a/rust-zerotier-service/src/main.rs +++ b/rust-zerotier-service/src/main.rs @@ -12,9 +12,10 @@ /****/ mod cli; +mod commands; mod fastudpsocket; mod localconfig; -mod physicallink; +mod getifaddrs; mod log; mod store; mod network; @@ -23,205 +24,51 @@ mod vnic; #[allow(non_snake_case,non_upper_case_globals,non_camel_case_types,dead_code,improper_ctypes)] mod osdep; -use std::cell::Cell; -use std::collections::BTreeMap; -use std::net::IpAddr; -use std::rc::Rc; -use std::str::FromStr; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::time::Duration; - -use futures::stream::{self, StreamExt}; - -use warp::Filter; -use warp::hyper::{HeaderMap, Method}; -use warp::hyper::body::Bytes; - -use zerotier_core::*; - -use crate::fastudpsocket::*; -use crate::localconfig::*; -use crate::log::Log; -use crate::physicallink::PhysicalLink; -use crate::network::Network; - -pub struct ServiceEventHandler {} - -impl NodeEventHandler for ServiceEventHandler { - fn virtual_network_config(&self, network_id: NetworkId, network_obj: &Arc, config_op: VirtualNetworkConfigOperation, config: Option<&VirtualNetworkConfig>) { - } - - #[inline(always)] - fn virtual_network_frame(&self, network_id: NetworkId, network_obj: &Arc, source_mac: MAC, dest_mac: MAC, ethertype: u16, vlan_id: u16, data: &[u8]) { - } - - fn event(&self, event: Event, event_data: &[u8]) { - } - - fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) { - } - - fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> Option> { - None - } - - #[inline(always)] - fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32 { - 0 - } - - #[inline(always)] - fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool { - true - } - - fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option { - None - } -} +use std::boxed::Box; +use std::ffi::CStr; +use std::path::Path; fn main() { - let cli_args = Some(cli::parse_cli_args()); - - let inaddr_v6_any = IpAddr::from_str("::0").unwrap(); let mut process_exit_value: i32 = 0; - // Current active local configuration for this node. - let mut local_config: Box = Box::new(LocalConfig::default()); + let mut zerotier_path; + unsafe { + zerotier_path = zerotier_core::cstr_to_string(osdep::platformDefaultHomePath(), 256); + } - // Handler for incoming packets from FastUDPSocket and incoming events from Node. - let handler: Arc = Arc::new(ServiceEventHandler{}); + let mut cli_args = Some(Box::new(cli::parse_cli_args())); - // From this point on we are in Tokio async land... - let tokio_rt = tokio::runtime::Builder::new_multi_thread().thread_stack_size(zerotier_core::RECOMMENDED_THREAD_STACK_SIZE).build().unwrap(); - tokio_rt.block_on(async { - // Keeps track of FastUDPSocket instances by bound address. - let mut udp_sockets: BTreeMap = BTreeMap::new(); - - // Send something to interrupt_tx to interrupt the inner loop and force it to - // detect a change or exit if run has been set to false. - let (mut interrupt_tx, mut interrupt_rx) = futures::channel::mpsc::channel::(2); - - // Setting this to false terminates the service. It's atomic since this is multithreaded. - let run = AtomicBool::new(true); - - loop { - let mut warp_server_port = local_config.settings.primary_port; - - let root = warp::path::end().map(|| { - warp::reply::with_status("404", warp::hyper::StatusCode::NOT_FOUND) - }); - - let status = warp::path("status") - .and(warp::method()) - .and(warp::header::headers_cloned()) - .and(warp::body::bytes()) - .map(|method: Method, headers: HeaderMap, post_data: Bytes| { - "status" - }); - let network = warp::path!("network" / String) - .and(warp::method()) - .and(warp::header::headers_cloned()) - .and(warp::body::bytes()) - .map(|nwid_str: String, method: Method, headers: HeaderMap, post_data: Bytes| { - "network" - }); - let peer = warp::path!("peer" / String) - .and(warp::method()) - .and(warp::header::headers_cloned()) - .and(warp::body::bytes()) - .map(|peer_str: String, method: Method, headers: HeaderMap, post_data: Bytes| { - "peer" - }); - - let (mut shutdown_tx, mut shutdown_rx) = futures::channel::oneshot::channel(); - let warp_server = warp::serve(warp::any().and( - root - .or(status) - .or(network) - .or(peer) - )).try_bind_with_graceful_shutdown((inaddr_v6_any, warp_server_port), async { let _ = shutdown_rx.await; }); - if warp_server.is_err() { - // TODO: log unable to bind to primary port - run.store(false, Ordering::Relaxed); - } - let warp_server = tokio_rt.spawn(warp_server.unwrap().1); - - let mut loop_delay = 10; - loop { - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(loop_delay)) => {}, - _ = interrupt_rx.next() => {}, - _ = tokio::signal::ctrl_c() => { - // TODO: log CTRL+C received - run.store(false, Ordering::Relaxed); - let _ = shutdown_tx.send(()); - break; - } - } - - // Enumerate physical addresses on the system, creating a map with an entry for - // the primary_port and another for the secondary_port if bound. - let mut system_addrs: BTreeMap> = BTreeMap::new(); - PhysicalLink::map(|link: PhysicalLink| { - if !local_config.settings.is_interface_blacklisted(link.device.as_str()) { - let l = Rc::new(link); - let mut a = l.address.clone(); - a.set_port(local_config.settings.primary_port); - system_addrs.insert(a, l.clone()); - if local_config.settings.secondary_port.is_some() { - let mut a = l.address.clone(); - a.set_port(local_config.settings.secondary_port.unwrap()); - system_addrs.insert(a, l.clone()); - } - } - }); - - // Close UDP bindings that no longer apply. - let mut udp_sockets_to_close: Vec = Vec::new(); - for sock in udp_sockets.iter() { - if !system_addrs.contains_key(sock.0) { - udp_sockets_to_close.push(sock.0.clone()); - } - } - for k in udp_sockets_to_close.iter() { - udp_sockets.remove(k); - } - - // Bind addresses that are not already bound. - for addr in system_addrs.iter() { - if !udp_sockets.contains_key(addr.0) { - let s = FastUDPSocket::new(addr.1.device.as_str(), addr.0, |raw_socket: &FastUDPRawOsSocket, from_address: &InetAddress, data: Buffer| { - // TODO - }); - if s.is_ok() { - udp_sockets.insert(addr.0.clone(), s.unwrap()); - } else if addr.0.port() == local_config.settings.primary_port { - run.store(false, Ordering::Relaxed); - // TODO: log failure to bind to primary port (UDP) - break; - } - } - } - - if local_config.settings.primary_port != warp_server_port || !run.load(Ordering::Relaxed) { - let _ = shutdown_tx.send(()); - break; - } - } - - let _ = warp_server.await; - - if !run.load(Ordering::Relaxed) { - break; - } - tokio::time::sleep(Duration::from_millis(250)).await; - if !run.load(Ordering::Relaxed) { - break; - } + let json_output; + let mut token: Option = None; + let mut token_path = Path::new(&zerotier_path).join("authtoken.secret"); + { + let a = cli_args.unwrap(); + json_output = a.is_present("json"); + let v = a.value_of("path"); + if v.is_some() { + zerotier_path = String::from(v.unwrap()); } - }); + let v = a.value_of("token"); + if v.is_some() { + token = Some(String::from(v.unwrap().trim())); + } + let v = a.value_of("token_path"); + if v.is_some() { + token_path = Path::new(v.unwrap().trim()).into_path_buf(); + } + } + + match cli_args.unwrap().subcommand_name().unwrap() { + "version" => { + let ver = zerotier_core::version(); + println!("{}.{}.{}", ver.0, ver.1, ver.2); + }, + "service" => { + cli_args = None; // free any memory we can when launching service + process_exit_value = commands::service::run() + }, + _ => cli::print_help(), // includes "help" + } std::process::exit(process_exit_value); } diff --git a/rust-zerotier-service/src/network.rs b/rust-zerotier-service/src/network.rs index d625df7d6..59448b140 100644 --- a/rust-zerotier-service/src/network.rs +++ b/rust-zerotier-service/src/network.rs @@ -1,3 +1,16 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + pub struct Network {} impl Network { diff --git a/rust-zerotier-service/src/physicallink.rs b/rust-zerotier-service/src/physicallink.rs deleted file mode 100644 index 563f3d3c4..000000000 --- a/rust-zerotier-service/src/physicallink.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c)2013-2020 ZeroTier, Inc. - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file in the project's root directory. - * - * Change Date: 2025-01-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2.0 of the Apache License. - */ -/****/ - -use zerotier_core::InetAddress; -use std::ffi::CStr; -use std::ptr::{null_mut, copy_nonoverlapping}; -use std::mem::size_of; -use num_traits::cast::AsPrimitive; -use crate::osdep as osdep; - -pub struct PhysicalLink { - pub address: InetAddress, - pub device: String -} - -#[inline(always)] -fn s6_addr_as_ptr(a: &A) -> *const A { - a as *const A -} - -impl PhysicalLink { - #[cfg(unix)] - pub fn map(mut f: F) { - unsafe { - let mut ifap: *mut osdep::ifaddrs = null_mut(); - if osdep::getifaddrs((&mut ifap as *mut *mut osdep::ifaddrs).cast()) == 0 { - let mut i = ifap; - while !i.is_null() { - if !(*i).ifa_addr.is_null() { - let mut a = InetAddress::new(); - - let sa_family = (*(*i).ifa_addr).sa_family as u8; - if sa_family == osdep::AF_INET as u8 { - copy_nonoverlapping((*i).ifa_addr.cast::(), (&mut a as *mut InetAddress).cast::(), size_of::()); - } else if sa_family == osdep::AF_INET6 as u8 { - copy_nonoverlapping((*i).ifa_addr.cast::(), (&mut a as *mut InetAddress).cast::(), size_of::()); - } else { - continue; - } - - let mut netmask_bits: u16 = 0; - if !(*i).ifa_netmask.is_null() { - if sa_family == osdep::AF_INET as u8 { - let mut a = (*(*i).ifa_netmask.cast::()).sin_addr.s_addr as u32; - netmask_bits = a.leading_ones() as u16; - } else if sa_family == osdep::AF_INET6 as u8 { - let a = s6_addr_as_ptr(&((*(*i).ifa_netmask.cast::()).sin6_addr)).cast::(); - for i in 0..16 as isize { - let mut b = *a.offset(i); - if b == 0xff { - netmask_bits += 8; - } else { - netmask_bits += b.leading_ones() as u16; - break; - } - } - } - } - a.set_port(netmask_bits); - - f(PhysicalLink{ - address: a, - device: if (*i).ifa_name.is_null() { String::new() } else { String::from(CStr::from_ptr((*i).ifa_name).to_str().unwrap()) } - }); - } - i = (*i).ifa_next; - } - osdep::freeifaddrs(ifap.cast()); - } - } - } -} diff --git a/rust-zerotier-service/src/store.rs b/rust-zerotier-service/src/store.rs index d8f7c47da..e9183d03d 100644 --- a/rust-zerotier-service/src/store.rs +++ b/rust-zerotier-service/src/store.rs @@ -27,7 +27,7 @@ pub struct Store { impl Store { const MAX_OBJECT_SIZE: usize = 131072; // sanity limit - pub fn new(base_path: &str) -> Result { + pub fn new(base_path: &str) -> std::io::Result { let bp = Path::new(base_path); let md = bp.metadata()?; if !md.is_dir() || md.permissions().readonly() { diff --git a/rust-zerotier-service/src/vnic/common.rs b/rust-zerotier-service/src/vnic/common.rs new file mode 100644 index 000000000..593a3bb73 --- /dev/null +++ b/rust-zerotier-service/src/vnic/common.rs @@ -0,0 +1,53 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +use std::collections::BTreeSet; +use std::ptr::null_mut; +use std::os::raw::c_ulong; +use zerotier_core::{MAC, MulticastGroup}; +use crate::osdep as osdep; + +#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "ios"))] +pub(crate) fn bsd_get_multicast_groups(dev: &str) -> BTreeSet { + let dev = dev.as_bytes(); + let mut groups: BTreeSet = BTreeSet::new(); + unsafe { + let mut maddrs: *mut osdep::ifmaddrs = null_mut(); + if osdep::getifmaddrs(&mut maddrs as *mut *mut osdep::ifmaddrs) == 0 { + let mut i = maddrs; + while !i.is_null() { + if !(*i).ifma_name.is_null() && !(*i).ifma_addr.is_null() && (*(*i).ifma_addr).sa_family == osdep::AF_LINK as osdep::sa_family_t { + let in_: &osdep::sockaddr_dl = &*((*i).ifma_name.cast()); + let la: &osdep::sockaddr_dl = &*((*i).ifma_addr.cast()); + if la.sdl_alen == 6 && in_.sdl_nlen <= dev.len() as osdep::u_char && osdep::memcmp(dev.as_ptr().cast(), in_.sdl_data.as_ptr().cast(), in_.sdl_nlen as c_ulong) == 0 { + let mi = la.sdl_nlen as usize; + groups.insert(MulticastGroup{ + mac: MAC( + (la.sdl_data[mi] as u64) << 40 | + (la.sdl_data[mi+1] as u64) << 32 | + (la.sdl_data[mi+2] as u64) << 24 | + (la.sdl_data[mi+3] as u64) << 16 | + (la.sdl_data[mi+4] as u64) << 8 | + la.sdl_data[mi+5] as u64 + ), + adi: 0, + }); + } + } + i = (*i).ifma_next; + } + osdep::freeifmaddrs(maddrs); + } + } + groups +} diff --git a/rust-zerotier-service/src/vnic/mac_feth_tap.rs b/rust-zerotier-service/src/vnic/mac_feth_tap.rs index a087e3512..602d3ee22 100644 --- a/rust-zerotier-service/src/vnic/mac_feth_tap.rs +++ b/rust-zerotier-service/src/vnic/mac_feth_tap.rs @@ -1,3 +1,16 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + /* * This creates a pair of feth devices with the lower numbered device * being the ZeroTier virtual interface and the higher being the device @@ -28,7 +41,7 @@ use std::error::Error; use std::ffi::CString; use std::ptr::{null_mut, copy_nonoverlapping}; use std::mem::{transmute, zeroed}; -use std::os::raw::{c_int, c_uchar, c_ulong, c_void}; +use std::os::raw::{c_int, c_uchar, c_void}; use std::process::Command; use std::sync::Mutex; use std::thread::JoinHandle; @@ -39,7 +52,7 @@ use num_traits::cast::AsPrimitive; use zerotier_core::{InetAddress, MAC, MulticastGroup, NetworkId}; use crate::osdep as osdep; -use crate::physicallink::PhysicalLink; +use crate::getifaddrs; use crate::vnic::VNIC; const BPF_BUFFER_SIZE: usize = 131072; @@ -154,8 +167,8 @@ impl MacFethTap { device_name = format!("feth{}", device_feth_no); peer_device_name = format!("feth{}", device_feth_no + 5000); let mut already_allocated = false; - PhysicalLink::map(|link: PhysicalLink| { - if link.device.eq(&device_name) || link.device.eq(&peer_device_name) { + getifaddrs::for_each_address(|_: &InetAddress, dn: &str| { + if dn.eq(&device_name) || dn.eq(&peer_device_name) { already_allocated = true; } }); @@ -164,7 +177,7 @@ impl MacFethTap { } device_alloc_tries += 1; - if device_alloc_tries >= 4899 { + if device_alloc_tries >= 1000 { return Err(String::from("unable to find unallocated 'feth' device")); } device_feth_ctr += 1; @@ -394,60 +407,16 @@ impl VNIC for MacFethTap { self.device.name.clone() } + #[inline(always)] fn get_multicast_groups(&self) -> BTreeSet { - let dev = self.device.name.as_bytes(); - let mut groups: BTreeSet = BTreeSet::new(); - unsafe { - let mut maddrs: *mut osdep::ifmaddrs = null_mut(); - if osdep::getifmaddrs(&mut maddrs as *mut *mut osdep::ifmaddrs) == 0 { - let mut i = maddrs; - while !i.is_null() { - if !(*i).ifma_name.is_null() && !(*i).ifma_addr.is_null() && (*(*i).ifma_addr).sa_family == osdep::AF_LINK as osdep::sa_family_t { - let in_: &osdep::sockaddr_dl = &*((*i).ifma_name.cast()); - let la: &osdep::sockaddr_dl = &*((*i).ifma_addr.cast()); - if la.sdl_alen == 6 && in_.sdl_nlen <= dev.len() as osdep::u_char && osdep::memcmp(dev.as_ptr().cast(), in_.sdl_data.as_ptr().cast(), in_.sdl_nlen as c_ulong) == 0 { - let mi = la.sdl_nlen as usize; - groups.insert(MulticastGroup{ - mac: MAC( - (la.sdl_data[mi] as u64) << 40 | - (la.sdl_data[mi+1] as u64) << 32 | - (la.sdl_data[mi+2] as u64) << 24 | - (la.sdl_data[mi+3] as u64) << 16 | - (la.sdl_data[mi+4] as u64) << 8 | - la.sdl_data[mi+5] as u64 - ), - adi: 0, - }); - } - } - i = (*i).ifma_next; - } - osdep::freeifmaddrs(maddrs); - } - } - groups + crate::vnic::common::bsd_get_multicast_groups(self.device.name.as_str()) } #[inline(always)] fn put(&self, source_mac: &zerotier_core::MAC, dest_mac: &zerotier_core::MAC, ethertype: u16, vlan_id: u16, data: *const u8, len: usize) -> bool { let dm = dest_mac.0; let sm = source_mac.0; - let mut hdr: [u8; 14] = [ - (dm >> 40) as u8, - (dm >> 32) as u8, - (dm >> 24) as u8, - (dm >> 16) as u8, - (dm >> 8) as u8, - dm as u8, - (sm >> 40) as u8, - (sm >> 32) as u8, - (sm >> 24) as u8, - (sm >> 16) as u8, - (sm >> 8) as u8, - sm as u8, - (ethertype >> 8) as u8, - ethertype as u8 - ]; + let mut hdr: [u8; 14] = [(dm >> 40) as u8, (dm >> 32) as u8, (dm >> 24) as u8, (dm >> 16) as u8, (dm >> 8) as u8, dm as u8, (sm >> 40) as u8, (sm >> 32) as u8, (sm >> 24) as u8, (sm >> 16) as u8, (sm >> 8) as u8, sm as u8, (ethertype >> 8) as u8, ethertype as u8]; unsafe { let iov: [osdep::iovec; 2] = [ osdep::iovec { diff --git a/rust-zerotier-service/src/vnic/mod.rs b/rust-zerotier-service/src/vnic/mod.rs index e2b4bf053..efd97900d 100644 --- a/rust-zerotier-service/src/vnic/mod.rs +++ b/rust-zerotier-service/src/vnic/mod.rs @@ -1,11 +1,20 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +mod vnic; +mod common; + #[cfg(target_os = "macos")] mod mac_feth_tap; -pub trait VNIC { - fn add_ip(&self, ip: &zerotier_core::InetAddress) -> bool; - fn remove_ip(&self, ip: &zerotier_core::InetAddress) -> bool; - fn ips(&self) -> Vec; - fn device_name(&self) -> String; - fn get_multicast_groups(&self) -> std::collections::BTreeSet; - fn put(&self, source_mac: &zerotier_core::MAC, dest_mac: &zerotier_core::MAC, ethertype: u16, vlan_id: u16, data: *const u8, len: usize) -> bool; -} +pub use vnic::VNIC; diff --git a/rust-zerotier-service/src/vnic/vnic.rs b/rust-zerotier-service/src/vnic/vnic.rs new file mode 100644 index 000000000..d39cc8551 --- /dev/null +++ b/rust-zerotier-service/src/vnic/vnic.rs @@ -0,0 +1,21 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +pub trait VNIC { + fn add_ip(&self, ip: &zerotier_core::InetAddress) -> bool; + fn remove_ip(&self, ip: &zerotier_core::InetAddress) -> bool; + fn ips(&self) -> Vec; + fn device_name(&self) -> String; + fn get_multicast_groups(&self) -> std::collections::BTreeSet; + fn put(&self, source_mac: &zerotier_core::MAC, dest_mac: &zerotier_core::MAC, ethertype: u16, vlan_id: u16, data: *const u8, len: usize) -> bool; +}