/* * Copyright (c)2013-2021 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: 2026-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. */ /****/ /** * @file * * Virtual Ethernet tap device and combined network stack driver */ #include "Constants.hpp" #include "InetAddress.hpp" #include "MAC.hpp" #include "MulticastGroup.hpp" #include "Mutex.hpp" #include "OSUtils.hpp" #include "lwip/etharp.h" #include "lwip/ethip6.h" #include "lwip/netif.h" #include "lwip/sys.h" #include "lwip/tcpip.h" #include "netif/ethernet.h" #ifdef LWIP_STATS #include "lwip/stats.h" #endif #include "Events.hpp" #include "VirtualTap.hpp" #include "ZeroTierSockets.h" #if defined(__WINDOWS__) #include "Synchapi.h" #include #endif #define ZTS_TAP_THREAD_POLLING_INTERVAL 50 #define LWIP_DRIVER_LOOP_INTERVAL 250 namespace ZeroTier { extern Events* zts_events; /** * Virtual tap device. ZeroTier will create one per joined network. It will * then be destroyed upon leaving the network. */ VirtualTap::VirtualTap( const char* homePath, const MAC& mac, unsigned int mtu, unsigned int metric, uint64_t net_id, void (*handler)( void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int), void* arg) : _handler(handler) , _homePath(homePath) , _arg(arg) , _initialized(false) , _enabled(true) , _run(true) , _mac(mac) , _mtu(mtu) , _net_id(net_id) , _unixListenSocket((PhySocket*)0) , _phy(this, false, true) { OSUtils::ztsnprintf(vtap_full_name, VTAP_NAME_LEN, "libzt-vtap-%llx", _net_id); #ifndef __WINDOWS__ ::pipe(_shutdownSignalPipe); #endif // Start virtual tap thread and stack I/O loops _thread = Thread::start(this); } VirtualTap::~VirtualTap() { _run = false; #ifndef __WINDOWS__ ::write(_shutdownSignalPipe[1], "\0", 1); #endif _phy.whack(); _lwip_remove_netif(netif4); netif4 = NULL; _lwip_remove_netif(netif6); netif6 = NULL; Thread::join(_thread); #ifndef __WINDOWS__ ::close(_shutdownSignalPipe[0]); ::close(_shutdownSignalPipe[1]); #endif } void VirtualTap::lastConfigUpdate(uint64_t lastConfigUpdateTime) { _lastConfigUpdateTime = lastConfigUpdateTime; } void VirtualTap::setEnabled(bool en) { _enabled = en; } bool VirtualTap::enabled() const { return _enabled; } void VirtualTap::setUserEventSystem(Events* events) { _events = events; } bool VirtualTap::hasIpv4Addr() { Mutex::Lock _l(_ips_m); std::vector::iterator it(_ips.begin()); while (it != _ips.end()) { if ((*it).isV4()) { return true; } ++it; } return false; } bool VirtualTap::hasIpv6Addr() { Mutex::Lock _l(_ips_m); std::vector::iterator it(_ips.begin()); while (it != _ips.end()) { if ((*it).isV6()) { return true; } ++it; } return false; } bool VirtualTap::addIp(const InetAddress& ip) { // TODO: Rewrite to allow for more addresses char ipbuf[128] = { 0 }; /* Limit address assignments to one per type. This limitation can be removed if some changes are made in the netif driver. */ if (ip.isV4() && hasIpv4Addr()) { ip.toString(ipbuf); // DEBUG_INFO("failed to add IP (%s), only one per type per netif // allowed\n", ipbuf); return false; } if (ip.isV6() && hasIpv6Addr()) { ip.toString(ipbuf); // DEBUG_INFO("failed to add IP (%s), only one per type per netif // allowed\n", ipbuf); return false; } Mutex::Lock _l(_ips_m); if (_ips.size() >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) { return false; } if (std::find(_ips.begin(), _ips.end(), ip) == _ips.end()) { _lwip_init_interface((void*)this, ip); _ips.push_back(ip); std::sort(_ips.begin(), _ips.end()); } return true; } bool VirtualTap::removeIp(const InetAddress& ip) { Mutex::Lock _l(_ips_m); if (std::find(_ips.begin(), _ips.end(), ip) != _ips.end()) { std::vector::iterator i(std::find(_ips.begin(), _ips.end(), ip)); _lwip_remove_address_from_netif((void*)this, ip); _ips.erase(i); } return true; } std::vector VirtualTap::ips() const { Mutex::Lock _l(_ips_m); return _ips; } void VirtualTap::put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len) { if (len && _enabled) { _lwip_eth_rx(this, from, to, etherType, data, len); } } void VirtualTap::scanMulticastGroups(std::vector& added, std::vector& removed) { std::vector newGroups; Mutex::Lock _l(_multicastGroups_m); // TODO: get multicast subscriptions 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()); 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 VirtualTap::setMtu(unsigned int mtu) { _mtu = mtu; } void VirtualTap::threadMain() throw() { fd_set readfds, nullfds; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&readfds); FD_ZERO(&nullfds); int nfds = (int)std::max(_shutdownSignalPipe[0], 0) + 1; #if defined(__linux__) pthread_setname_np(pthread_self(), vtap_full_name); #endif #if defined(__APPLE__) pthread_setname_np(vtap_full_name); #endif while (true) { FD_SET(_shutdownSignalPipe[0], &readfds); select(nfds, &readfds, &nullfds, &nullfds, &tv); // writes to shutdown pipe terminate thread if (FD_ISSET(_shutdownSignalPipe[0], &readfds)) { break; } #if defined(__WINDOWS__) Sleep(ZTS_TAP_THREAD_POLLING_INTERVAL); #else struct timespec sleepValue = { 0 }; sleepValue.tv_nsec = ZTS_TAP_THREAD_POLLING_INTERVAL * 500000; nanosleep(&sleepValue, NULL); #endif } } void VirtualTap::phyOnDatagram( PhySocket* sock, void** uptr, const struct sockaddr* local_address, const struct sockaddr* from, void* data, unsigned long len) { // Intentionally empty } void VirtualTap::phyOnTcpConnect(PhySocket* sock, void** uptr, bool success) { } void VirtualTap::phyOnTcpAccept( PhySocket* sockL, PhySocket* sockN, void** uptrL, void** uptrN, const struct sockaddr* from) { } void VirtualTap::phyOnTcpClose(PhySocket* sock, void** uptr) { } void VirtualTap::phyOnTcpData(PhySocket* sock, void** uptr, void* data, unsigned long len) { } void VirtualTap::phyOnTcpWritable(PhySocket* sock, void** uptr) { } void VirtualTap::phyOnUnixClose(PhySocket* sock, void** uptr) { } //----------------------------------------------------------------------------// // Netif driver code for lwIP network stack // //----------------------------------------------------------------------------// bool _has_exited = false; bool _has_started = false; // Used to generate enumerated lwIP interface names int netifCount = 0; // Lock to guard access to network stack state changes Mutex stackLock; // Callback for when the TCPIP thread has been successfully started static void _tcpip_init_done(void* arg) { sys_sem_t* sem; sem = (sys_sem_t*)arg; zts_events->setState(ZTS_STATE_STACK_RUNNING); _has_started = true; zts_events->enqueue(ZTS_EVENT_STACK_UP, NULL); sys_sem_signal(sem); } static void _main_lwip_driver_loop(void* arg) { #if defined(__linux__) pthread_setname_np(pthread_self(), ZTS_LWIP_THREAD_NAME); #endif #if defined(__APPLE__) pthread_setname_np(ZTS_LWIP_THREAD_NAME); #endif sys_sem_t sem; LWIP_UNUSED_ARG(arg); if (sys_sem_new(&sem, 0) != ERR_OK) { // DEBUG_ERROR("failed to create semaphore"); } tcpip_init(_tcpip_init_done, &sem); sys_sem_wait(&sem); // Main loop while (zts_events->getState(ZTS_STATE_STACK_RUNNING)) { zts_util_delay(LWIP_DRIVER_LOOP_INTERVAL); } _has_exited = true; zts_events->enqueue(ZTS_EVENT_STACK_DOWN, NULL); } bool _lwip_is_up() { Mutex::Lock _l(stackLock); return zts_events->getState(ZTS_STATE_STACK_RUNNING); } void _lwip_driver_init() { if (_lwip_is_up()) { return; } if (_has_exited) { return; } Mutex::Lock _l(stackLock); #if defined(__WINDOWS__) sys_init(); // Required for win32 init of critical sections #endif sys_thread_new(ZTS_LWIP_THREAD_NAME, _main_lwip_driver_loop, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO); } void _lwip_driver_shutdown() { if (_has_exited) { return; } Mutex::Lock _l(stackLock); // Set flag to stop sending frames into the core zts_events->clrState(ZTS_STATE_STACK_RUNNING); // Wait until the main lwIP thread has exited if (_has_started) { while (! _has_exited) { zts_util_delay(LWIP_DRIVER_LOOP_INTERVAL); } } } void _lwip_remove_netif(void* netif) { if (! netif) { return; } struct netif* n = (struct netif*)netif; LOCK_TCPIP_CORE(); netif_remove(n); netif_set_down(n); netif_set_link_down(n); UNLOCK_TCPIP_CORE(); } err_t _lwip_eth_tx(struct netif* n, struct pbuf* p) { if (! n) { return ERR_IF; } struct pbuf* q; char buf[ZT_MAX_MTU + 32] = { 0 }; char* bufptr; int totalLength = 0; VirtualTap* tap = (VirtualTap*)n->state; bufptr = buf; for (q = p; q != NULL; q = q->next) { memcpy(bufptr, q->payload, q->len); bufptr += q->len; totalLength += q->len; } struct eth_hdr* ethhdr; ethhdr = (struct eth_hdr*)buf; MAC src_mac; MAC dest_mac; src_mac.setTo(ethhdr->src.addr, 6); dest_mac.setTo(ethhdr->dest.addr, 6); char* data = buf + sizeof(struct eth_hdr); int len = totalLength - sizeof(struct eth_hdr); int proto = Utils::ntoh((uint16_t)ethhdr->type); tap->_handler(tap->_arg, NULL, tap->_net_id, src_mac, dest_mac, proto, 0, data, len); return ERR_OK; } void _lwip_eth_rx( VirtualTap* tap, const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len) { #ifdef LWIP_STATS stats_display(); #endif if (! zts_events->getState(ZTS_STATE_STACK_RUNNING)) { return; } struct pbuf *p, *q; struct eth_hdr ethhdr; from.copyTo(ethhdr.src.addr, 6); to.copyTo(ethhdr.dest.addr, 6); ethhdr.type = Utils::hton((uint16_t)etherType); p = pbuf_alloc(PBUF_RAW, (uint16_t)len + sizeof(struct eth_hdr), PBUF_RAM); if (! p) { // DEBUG_ERROR("dropped packet: unable to allocate memory for // pbuf"); return; } // First pbuf gets Ethernet header at start q = p; if (q->len < sizeof(ethhdr)) { pbuf_free(p); p = NULL; // DEBUG_ERROR("dropped packet: first pbuf smaller than Ethernet // header"); return; } // Copy frame data into pbuf const char* dataptr = reinterpret_cast(data); memcpy(q->payload, ðhdr, sizeof(ethhdr)); int remainingPayloadSpace = q->len - sizeof(ethhdr); memcpy((char*)q->payload + sizeof(ethhdr), dataptr, remainingPayloadSpace); dataptr += remainingPayloadSpace; // Remaining pbufs (if any) get rest of data while ((q = q->next)) { memcpy(q->payload, dataptr, q->len); dataptr += q->len; } // Feed packet into stack int err; if (Utils::ntoh(ethhdr.type) == 0x800 || Utils::ntoh(ethhdr.type) == 0x806) { if (tap->netif4) { if ((err = ((struct netif*)tap->netif4)->input(p, (struct netif*)tap->netif4)) != ERR_OK) { // DEBUG_ERROR("packet input error (%d)", err); pbuf_free(p); } } } if (Utils::ntoh(ethhdr.type) == 0x86DD) { if (tap->netif6) { if ((err = ((struct netif*)tap->netif6)->input(p, (struct netif*)tap->netif6)) != ERR_OK) { // DEBUG_ERROR("packet input error (%d)", err); pbuf_free(p); } } } } bool _lwip_is_netif_up(void* n) { if (! n) { return false; } LOCK_TCPIP_CORE(); bool result = netif_is_up((struct netif*)n); UNLOCK_TCPIP_CORE(); return result; } static err_t _netif_init4(struct netif* n) { if (! n || ! n->state) { return ERR_IF; } // Called from core, no need to lock VirtualTap* tap = (VirtualTap*)(n->state); n->hwaddr_len = 6; n->name[0] = '4'; n->name[1] = 'a' + netifCount; n->linkoutput = _lwip_eth_tx; n->output = etharp_output; n->mtu = std::min(LWIP_MTU, (int)tap->_mtu); n->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6 | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; n->hwaddr_len = sizeof(n->hwaddr); tap->_mac.copyTo(n->hwaddr, n->hwaddr_len); return ERR_OK; } static err_t _netif_init6(struct netif* n) { if (! n || ! n->state) { return ERR_IF; } n->hwaddr_len = sizeof(n->hwaddr); VirtualTap* tap = (VirtualTap*)(n->state); tap->_mac.copyTo(n->hwaddr, n->hwaddr_len); // Called from core, no need to lock n->hwaddr_len = 6; n->name[0] = '6'; n->name[1] = 'a' + netifCount; n->linkoutput = _lwip_eth_tx; n->output_ip6 = ethip6_output; n->mtu = std::min(LWIP_MTU, (int)tap->_mtu); n->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6 | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; return ERR_OK; } void _lwip_init_interface(void* tapref, const InetAddress& ip) { char macbuf[ZTS_MAC_ADDRSTRLEN] = { 0 }; VirtualTap* vtap = (VirtualTap*)tapref; struct netif* n = NULL; bool isNewNetif = false; if (ip.isV4()) { if (vtap->netif4) { n = (struct netif*)vtap->netif4; } else { n = new struct netif; isNewNetif = true; netifCount++; } static ip4_addr_t ip4, netmask, gw; IP4_ADDR(&gw, 127, 0, 0, 1); ip4.addr = *((u32_t*)ip.rawIpData()); netmask.addr = *((u32_t*)ip.netmask().rawIpData()); LOCK_TCPIP_CORE(); netif_add(n, &ip4, &netmask, &gw, (void*)vtap, _netif_init4, tcpip_input); vtap->netif4 = (void*)n; UNLOCK_TCPIP_CORE(); snprintf( macbuf, ZTS_MAC_ADDRSTRLEN, "%02x:%02x:%02x:%02x:%02x:%02x", n->hwaddr[0], n->hwaddr[1], n->hwaddr[2], n->hwaddr[3], n->hwaddr[4], n->hwaddr[5]); } if (ip.isV6()) { if (vtap->netif6) { n = (struct netif*)vtap->netif6; } else { n = new struct netif; isNewNetif = true; netifCount++; } static ip6_addr_t ip6; memcpy(&(ip6.addr), ip.rawIpData(), sizeof(ip6.addr)); LOCK_TCPIP_CORE(); if (isNewNetif) { vtap->netif6 = (void*)n; netif_add(n, NULL, NULL, NULL, (void*)vtap, _netif_init6, ethernet_input); n->ip6_autoconfig_enabled = 1; vtap->_mac.copyTo(n->hwaddr, n->hwaddr_len); netif_create_ip6_linklocal_address(n, 1); netif_set_link_up(n); netif_set_up(n); netif_set_default(n); } netif_add_ip6_address(n, &ip6, NULL); n->output_ip6 = ethip6_output; UNLOCK_TCPIP_CORE(); snprintf( macbuf, ZTS_MAC_ADDRSTRLEN, "%02x:%02x:%02x:%02x:%02x:%02x", n->hwaddr[0], n->hwaddr[1], n->hwaddr[2], n->hwaddr[3], n->hwaddr[4], n->hwaddr[5]); } } void _lwip_remove_address_from_netif(void* tapref, const InetAddress& ip) { if (! tapref) { return; } VirtualTap* vtap = (VirtualTap*)tapref; struct netif* n = NULL; /* When true multi-homing is implemented this will need to be a bit more sophisticated */ if (ip.isV4()) { if (vtap->netif4) { n = (struct netif*)vtap->netif4; } } if (ip.isV6()) { if (vtap->netif6) { n = (struct netif*)vtap->netif6; } } if (! n) { return; } _lwip_remove_netif(n); } } // namespace ZeroTier