mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-08-02 07:02:49 +02:00
- Add comprehensive VL1 (ZeroTier protocol) fragmentation metrics: * Track fragmented packets, fragments, reassembly failures * Monitor fragment ordering issues and duplicates * Histogram for fragments per packet distribution - Add VL2 (TAP/Ethernet) fragmentation metrics for virtual ethernet interfaces: * Track oversized frames from TAP devices * Monitor frames that would fragment or drop * Histogram for frame size distribution with common MTU buckets - Integration across all TAP implementations (Linux, Mac, BSD, Windows) This allows monitoring of fragmentation patterns for nodes participating as members in ZeroTier networks, helping identify MTU mismatches and optimize virtual ethernet performance.
502 lines
14 KiB
C++
502 lines
14 KiB
C++
/*
|
|
* 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: 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.
|
|
*/
|
|
/****/
|
|
|
|
#include "NetBSDEthernetTap.hpp"
|
|
|
|
#include "../node/Constants.hpp"
|
|
#include "../node/Mutex.hpp"
|
|
#include "../node/Utils.hpp"
|
|
#include "OSUtils.hpp"
|
|
#include "freebsd_getifmaddrs.h"
|
|
|
|
#include <algorithm>
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ifaddrs.h>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
#include <net/route.h>
|
|
#include <netinet/in.h>
|
|
#include <set>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <sys/cdefs.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/param.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <utility>
|
|
|
|
#include "../node/Constants.hpp"
|
|
#include "../node/Utils.hpp"
|
|
#include "../node/Mutex.hpp"
|
|
#include "OSUtils.hpp"
|
|
#include "NetBSDEthernetTap.hpp"
|
|
#include "../node/Metrics.hpp"
|
|
|
|
#include <iostream>
|
|
using namespace std;
|
|
#define ZT_BASE32_CHARS "0123456789abcdefghijklmnopqrstuv"
|
|
|
|
// ff:ff:ff:ff:ff:ff with no ADI
|
|
static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff), 0);
|
|
|
|
namespace ZeroTier {
|
|
|
|
NetBSDEthernetTap::NetBSDEthernetTap(
|
|
const char* homePath,
|
|
const MAC& mac,
|
|
unsigned int mtu,
|
|
unsigned int metric,
|
|
uint64_t nwid,
|
|
const char* friendlyName,
|
|
void (*handler)(void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
|
|
void* arg)
|
|
: _handler(handler)
|
|
, _arg(arg)
|
|
, _nwid(nwid)
|
|
, _mtu(mtu)
|
|
, _metric(metric)
|
|
, _fd(0)
|
|
, _enabled(true)
|
|
{
|
|
static Mutex globalTapCreateLock;
|
|
char devpath[64], ethaddr[64], mtustr[32], metstr[32], tmpdevname[32];
|
|
struct stat stattmp;
|
|
|
|
Mutex::Lock _gl(globalTapCreateLock);
|
|
|
|
if (mtu > 2800)
|
|
throw std::runtime_error("max tap MTU is 2800");
|
|
|
|
// we can create /dev/tap*
|
|
std::vector<std::string> devFiles(OSUtils::listDirectory("/dev"));
|
|
for (int i = 9993; i < (9993 + 128); ++i) {
|
|
Utils::snprintf(tmpdevname, sizeof(tmpdevname), "tap%d", i);
|
|
Utils::snprintf(devpath, sizeof(devpath), "/dev/%s", tmpdevname);
|
|
if (std::find(devFiles.begin(), devFiles.end(), std::string(tmpdevname)) == devFiles.end()) {
|
|
long cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
::execl("/sbin/ifconfig", "/sbin/ifconfig", tmpdevname, "create", (const char*)0);
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
}
|
|
else
|
|
throw std::runtime_error("fork() failed");
|
|
|
|
cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
string tmp;
|
|
sprintf((char*)tmp.c_str(), "%d", i);
|
|
string minor = tmp.c_str();
|
|
::execl("/sbin/mknod", "/sbin/mknod", devpath, "c", "169", minor.c_str(), (const char*)0);
|
|
// http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/conf/majors
|
|
// major 169 => tap
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
}
|
|
else
|
|
throw std::runtime_error("fork() failed");
|
|
|
|
cerr << "created device " << devpath << endl;
|
|
|
|
_dev = tmpdevname;
|
|
_fd = ::open(devpath, O_RDWR);
|
|
if (! stat(devpath, &stattmp)) {
|
|
if (_fd > 0)
|
|
break;
|
|
else
|
|
throw std::runtime_error("unable to open created tap device ");
|
|
}
|
|
else {
|
|
throw std::runtime_error("cannot find /dev node for newly created tap device");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_fd <= 0)
|
|
throw std::runtime_error("unable to open TAP device or no more devices available");
|
|
|
|
if (fcntl(_fd, F_SETFL, fcntl(_fd, F_GETFL) & ~O_NONBLOCK) == -1) {
|
|
::close(_fd);
|
|
throw std::runtime_error("unable to set flags on file descriptor for TAP device");
|
|
}
|
|
|
|
// Configure MAC address and MTU, bring interface up
|
|
Utils::snprintf(ethaddr, sizeof(ethaddr), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", (int)mac[0], (int)mac[1], (int)mac[2], (int)mac[3], (int)mac[4], (int)mac[5]);
|
|
Utils::snprintf(mtustr, sizeof(mtustr), "%u", _mtu);
|
|
Utils::snprintf(metstr, sizeof(metstr), "%u", _metric);
|
|
long cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
::execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "link", ethaddr, "mtu", mtustr, "metric", metstr, "up", (const char*)0);
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
if (exitcode) {
|
|
::close(_fd);
|
|
throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
|
|
}
|
|
}
|
|
|
|
// ifconfig link seems to be different from address
|
|
// https://wiki.netbsd.org/tutorials/faking_a_mac_address/
|
|
cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
string cmdline = "net.link.tap." + string(_dev);
|
|
cmdline += "=" + string(ethaddr);
|
|
::execl("/sbin/sysctl", "/sbin/sysctl", "-w", cmdline.c_str(), (const char*)0);
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
if (exitcode) {
|
|
::close(_fd);
|
|
throw std::runtime_error("sysctl failure setting link-layer address and activating tap interface");
|
|
}
|
|
}
|
|
|
|
// Set close-on-exec so that devices cannot persist if we fork/exec for update
|
|
fcntl(_fd, F_SETFD, fcntl(_fd, F_GETFD) | FD_CLOEXEC);
|
|
|
|
::pipe(_shutdownSignalPipe);
|
|
|
|
_thread = Thread::start(this);
|
|
}
|
|
|
|
NetBSDEthernetTap::~NetBSDEthernetTap()
|
|
{
|
|
::write(_shutdownSignalPipe[1], "\0", 1); // causes thread to exit
|
|
Thread::join(_thread);
|
|
::close(_fd);
|
|
::close(_shutdownSignalPipe[0]);
|
|
::close(_shutdownSignalPipe[1]);
|
|
|
|
long cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
::execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "destroy", (const char*)0);
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
}
|
|
|
|
cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
string tmp = "/dev/";
|
|
tmp += _dev.c_str();
|
|
::execl("/bin/rm", "/bin/rm", tmp.c_str(), (const char*)0);
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
}
|
|
else
|
|
throw std::runtime_error("fork() failed");
|
|
}
|
|
|
|
void NetBSDEthernetTap::setEnabled(bool en)
|
|
{
|
|
_enabled = en;
|
|
}
|
|
|
|
bool NetBSDEthernetTap::enabled() const
|
|
{
|
|
return _enabled;
|
|
}
|
|
|
|
static bool ___removeIp(const std::string& _dev, const InetAddress& ip)
|
|
{
|
|
long cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "inet", ip.toIpString().c_str(), "-alias", (const char*)0);
|
|
_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
waitpid(cpid, &exitcode, 0);
|
|
return (exitcode == 0);
|
|
}
|
|
return false; // never reached, make compiler shut up about return value
|
|
}
|
|
|
|
bool NetBSDEthernetTap::addIp(const InetAddress& ip)
|
|
{
|
|
if (! ip)
|
|
return false;
|
|
|
|
std::vector<InetAddress> allIps(ips());
|
|
if (std::find(allIps.begin(), allIps.end(), ip) != allIps.end())
|
|
return true; // IP/netmask already assigned
|
|
|
|
// Remove and reconfigure if address is the same but netmask is different
|
|
for (std::vector<InetAddress>::iterator i(allIps.begin()); i != allIps.end(); ++i) {
|
|
if ((i->ipsEqual(ip)) && (i->netmaskBits() != ip.netmaskBits())) {
|
|
if (___removeIp(_dev, *i))
|
|
break;
|
|
}
|
|
}
|
|
|
|
long cpid = (long)vfork();
|
|
if (cpid == 0) {
|
|
::execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), ip.isV4() ? "inet" : "inet6", ip.toString().c_str(), "alias", (const char*)0);
|
|
::_exit(-1);
|
|
}
|
|
else if (cpid > 0) {
|
|
int exitcode = -1;
|
|
::waitpid(cpid, &exitcode, 0);
|
|
return (exitcode == 0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool NetBSDEthernetTap::removeIp(const InetAddress& ip)
|
|
{
|
|
if (! ip)
|
|
return false;
|
|
std::vector<InetAddress> allIps(ips());
|
|
if (std::find(allIps.begin(), allIps.end(), ip) != allIps.end()) {
|
|
if (___removeIp(_dev, ip))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<InetAddress> NetBSDEthernetTap::ips() const
|
|
{
|
|
struct ifaddrs* ifa = (struct ifaddrs*)0;
|
|
if (getifaddrs(&ifa))
|
|
return std::vector<InetAddress>();
|
|
|
|
std::vector<InetAddress> r;
|
|
|
|
struct ifaddrs* p = ifa;
|
|
while (p) {
|
|
if ((! strcmp(p->ifa_name, _dev.c_str())) && (p->ifa_addr) && (p->ifa_netmask) && (p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) {
|
|
switch (p->ifa_addr->sa_family) {
|
|
case AF_INET: {
|
|
struct sockaddr_in* sin = (struct sockaddr_in*)p->ifa_addr;
|
|
struct sockaddr_in* nm = (struct sockaddr_in*)p->ifa_netmask;
|
|
r.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());
|
|
std::unique(r.begin(), r.end());
|
|
|
|
return r;
|
|
}
|
|
|
|
void NetBSDEthernetTap::put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len)
|
|
{
|
|
// VL2 frame size histogram
|
|
Metrics::vl2_frame_size_hist.Observe(len);
|
|
|
|
if (len > this->_mtu) {
|
|
Metrics::vl2_would_fragment_or_drop_rx++;
|
|
}
|
|
char putBuf[4096];
|
|
if ((_fd > 0) && (len <= _mtu) && (_enabled)) {
|
|
to.copyTo(putBuf, 6);
|
|
from.copyTo(putBuf + 6, 6);
|
|
*((uint16_t*)(putBuf + 12)) = htons((uint16_t)etherType);
|
|
memcpy(putBuf + 14, data, len);
|
|
len += 14;
|
|
::write(_fd, putBuf, len);
|
|
}
|
|
}
|
|
|
|
std::string NetBSDEthernetTap::deviceName() const
|
|
{
|
|
return _dev;
|
|
}
|
|
|
|
void NetBSDEthernetTap::setFriendlyName(const char* friendlyName)
|
|
{
|
|
}
|
|
|
|
void NetBSDEthernetTap::scanMulticastGroups(std::vector<MulticastGroup>& added, std::vector<MulticastGroup>& removed)
|
|
{
|
|
std::vector<MulticastGroup> newGroups;
|
|
|
|
struct ifmaddrs* ifmap = (struct ifmaddrs*)0;
|
|
if (! getifmaddrs(&ifmap)) {
|
|
struct ifmaddrs* p = ifmap;
|
|
while (p) {
|
|
if (p->ifma_addr->sa_family == AF_LINK) {
|
|
struct sockaddr_dl* in = (struct sockaddr_dl*)p->ifma_name;
|
|
struct sockaddr_dl* la = (struct sockaddr_dl*)p->ifma_addr;
|
|
if ((la->sdl_alen == 6) && (in->sdl_nlen <= _dev.length()) && (! memcmp(_dev.data(), in->sdl_data, in->sdl_nlen)))
|
|
newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen, 6), 0));
|
|
}
|
|
p = p->ifma_next;
|
|
}
|
|
freeifmaddrs(ifmap);
|
|
}
|
|
|
|
std::vector<InetAddress> allIps(ips());
|
|
for (std::vector<InetAddress>::iterator ip(allIps.begin()); ip != allIps.end(); ++ip)
|
|
newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip));
|
|
|
|
std::sort(newGroups.begin(), newGroups.end());
|
|
std::unique(newGroups.begin(), newGroups.end());
|
|
|
|
for (std::vector<MulticastGroup>::iterator m(newGroups.begin()); m != newGroups.end(); ++m) {
|
|
if (! std::binary_search(_multicastGroups.begin(), _multicastGroups.end(), *m))
|
|
added.push_back(*m);
|
|
}
|
|
for (std::vector<MulticastGroup>::iterator m(_multicastGroups.begin()); m != _multicastGroups.end(); ++m) {
|
|
if (! std::binary_search(newGroups.begin(), newGroups.end(), *m))
|
|
removed.push_back(*m);
|
|
}
|
|
|
|
_multicastGroups.swap(newGroups);
|
|
}
|
|
|
|
/*
|
|
bool NetBSDEthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups)
|
|
{
|
|
std::set<MulticastGroup> newGroups;
|
|
struct ifmaddrs *ifmap = (struct ifmaddrs *)0;
|
|
if (!getifmaddrs(&ifmap)) {
|
|
struct ifmaddrs *p = ifmap;
|
|
while (p) {
|
|
if (p->ifma_addr->sa_family == AF_LINK) {
|
|
struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name;
|
|
struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr;
|
|
if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen)))
|
|
newGroups.insert(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0));
|
|
}
|
|
p = p->ifma_next;
|
|
}
|
|
freeifmaddrs(ifmap);
|
|
}
|
|
|
|
{
|
|
std::set<InetAddress> allIps(ips());
|
|
for(std::set<InetAddress>::const_iterator i(allIps.begin());i!=allIps.end();++i)
|
|
newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i));
|
|
}
|
|
|
|
bool changed = false;
|
|
|
|
for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) {
|
|
if (!groups.count(*mg)) {
|
|
groups.insert(*mg);
|
|
changed = true;
|
|
}
|
|
}
|
|
for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) {
|
|
if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) {
|
|
groups.erase(mg++);
|
|
changed = true;
|
|
} else ++mg;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
*/
|
|
|
|
void NetBSDEthernetTap::threadMain() throw()
|
|
{
|
|
fd_set readfds, nullfds;
|
|
MAC to, from;
|
|
int n, nfds, r;
|
|
char getBuf[8194];
|
|
|
|
// Wait for a moment after startup -- wait for Network to finish
|
|
// constructing itself.
|
|
Thread::sleep(500);
|
|
|
|
FD_ZERO(&readfds);
|
|
FD_ZERO(&nullfds);
|
|
nfds = (int)std::max(_shutdownSignalPipe[0], _fd) + 1;
|
|
|
|
r = 0;
|
|
for (;;) {
|
|
FD_SET(_shutdownSignalPipe[0], &readfds);
|
|
FD_SET(_fd, &readfds);
|
|
select(nfds, &readfds, &nullfds, &nullfds, (struct timeval*)0);
|
|
|
|
if (FD_ISSET(_shutdownSignalPipe[0], &readfds)) // writes to shutdown pipe terminate thread
|
|
break;
|
|
|
|
if (FD_ISSET(_fd, &readfds)) {
|
|
n = (int)::read(_fd, getBuf + r, sizeof(getBuf) - r);
|
|
if (n < 0) {
|
|
if ((errno != EINTR) && (errno != ETIMEDOUT))
|
|
break;
|
|
}
|
|
else {
|
|
// Some tap drivers like to send the ethernet frame and the
|
|
// payload in two chunks, so handle that by accumulating
|
|
// data until we have at least a frame.
|
|
r += n;
|
|
if (r > 14) {
|
|
if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms
|
|
r = _mtu + 14;
|
|
|
|
if (_enabled) {
|
|
to.setTo(getBuf, 6);
|
|
from.setTo(getBuf + 6, 6);
|
|
unsigned int etherType = ntohs(((const uint16_t*)getBuf)[6]);
|
|
// TODO: VLAN support
|
|
_handler(_arg, _nwid, from, to, etherType, 0, (const void*)(getBuf + 14), r - 14);
|
|
}
|
|
|
|
r = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace ZeroTier
|