First Commit
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Arp.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static const uint8_t ARP_REQUEST_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x01 };
|
||||
static const uint8_t ARP_RESPONSE_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x02 };
|
||||
|
||||
Arp::Arp() :
|
||||
_cache(256),
|
||||
_lastCleaned(OSUtils::now())
|
||||
{
|
||||
}
|
||||
|
||||
void Arp::addLocal(uint32_t ip,const MAC &mac)
|
||||
{
|
||||
_ArpEntry &e = _cache[ip];
|
||||
e.lastQuerySent = 0; // local IP
|
||||
e.lastResponseReceived = 0; // local IP
|
||||
e.mac = mac;
|
||||
e.local = true;
|
||||
}
|
||||
|
||||
void Arp::remove(uint32_t ip)
|
||||
{
|
||||
_cache.erase(ip);
|
||||
}
|
||||
|
||||
uint32_t Arp::processIncomingArp(const void *arp,unsigned int len,void *response,unsigned int &responseLen,MAC &responseDest)
|
||||
{
|
||||
const uint64_t now = OSUtils::now();
|
||||
uint32_t ip = 0;
|
||||
|
||||
responseLen = 0;
|
||||
responseDest.zero();
|
||||
|
||||
if (len >= 28) {
|
||||
if (!memcmp(arp,ARP_REQUEST_HEADER,8)) {
|
||||
// Respond to ARP requests for locally-known IPs
|
||||
_ArpEntry *targetEntry = _cache.get(reinterpret_cast<const uint32_t *>(arp)[6]);
|
||||
if ((targetEntry)&&(targetEntry->local)) {
|
||||
memcpy(response,ARP_RESPONSE_HEADER,8);
|
||||
targetEntry->mac.copyTo(reinterpret_cast<uint8_t *>(response) + 8,6);
|
||||
memcpy(reinterpret_cast<uint8_t *>(response) + 14,reinterpret_cast<const uint8_t *>(arp) + 24,4);
|
||||
memcpy(reinterpret_cast<uint8_t *>(response) + 18,reinterpret_cast<const uint8_t *>(arp) + 8,10);
|
||||
responseLen = 28;
|
||||
responseDest.setTo(reinterpret_cast<const uint8_t *>(arp) + 8,6);
|
||||
}
|
||||
} else if (!memcmp(arp,ARP_RESPONSE_HEADER,8)) {
|
||||
// Learn cache entries for remote IPs from relevant ARP replies
|
||||
uint32_t responseIp = 0;
|
||||
memcpy(&responseIp,reinterpret_cast<const uint8_t *>(arp) + 14,4);
|
||||
_ArpEntry *queryEntry = _cache.get(responseIp);
|
||||
if ((queryEntry)&&(!queryEntry->local)&&((now - queryEntry->lastQuerySent) <= ZT_ARP_QUERY_MAX_TTL)) {
|
||||
queryEntry->lastResponseReceived = now;
|
||||
queryEntry->mac.setTo(reinterpret_cast<const uint8_t *>(arp) + 8,6);
|
||||
ip = responseIp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((now - _lastCleaned) >= ZT_ARP_EXPIRE) {
|
||||
_lastCleaned = now;
|
||||
Hashtable< uint32_t,_ArpEntry >::Iterator i(_cache);
|
||||
uint32_t *k = (uint32_t *)0;
|
||||
_ArpEntry *v = (_ArpEntry *)0;
|
||||
while (i.next(k,v)) {
|
||||
if ((!v->local)&&((now - v->lastResponseReceived) >= ZT_ARP_EXPIRE))
|
||||
_cache.erase(*k);
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
MAC Arp::query(const MAC &localMac,uint32_t localIp,uint32_t targetIp,void *query,unsigned int &queryLen,MAC &queryDest)
|
||||
{
|
||||
const uint64_t now = OSUtils::now();
|
||||
|
||||
_ArpEntry &e = _cache[targetIp];
|
||||
|
||||
if ( ((e.mac)&&((now - e.lastResponseReceived) >= (ZT_ARP_EXPIRE / 3))) ||
|
||||
((!e.mac)&&((now - e.lastQuerySent) >= ZT_ARP_QUERY_INTERVAL)) ) {
|
||||
e.lastQuerySent = now;
|
||||
|
||||
uint8_t *q = reinterpret_cast<uint8_t *>(query);
|
||||
memcpy(q,ARP_REQUEST_HEADER,8); q += 8; // ARP request header information, always the same
|
||||
localMac.copyTo(q,6); q += 6; // sending host MAC address
|
||||
memcpy(q,&localIp,4); q += 4; // sending host IP (IP already in big-endian byte order)
|
||||
memset(q,0,6); q += 6; // sending zeros for target MAC address as thats what we want to find
|
||||
memcpy(q,&targetIp,4); // target IP address for resolution (IP already in big-endian byte order)
|
||||
queryLen = 28;
|
||||
if (e.mac)
|
||||
queryDest = e.mac; // confirmation query, send directly to address holder
|
||||
else queryDest = (uint64_t)0xffffffffffffULL; // broadcast query
|
||||
} else {
|
||||
queryLen = 0;
|
||||
queryDest.zero();
|
||||
}
|
||||
|
||||
return e.mac;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_ARP_HPP
|
||||
#define ZT_ARP_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Hashtable.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
|
||||
/**
|
||||
* Maximum possible ARP length
|
||||
*
|
||||
* ARPs are 28 bytes in length, but specify a 128 byte buffer since
|
||||
* some weird extensions we may support in the future can pad them
|
||||
* out to as long as 72 bytes.
|
||||
*/
|
||||
#define ZT_ARP_BUF_LENGTH 128
|
||||
|
||||
/**
|
||||
* Minimum permitted interval between sending ARP queries for a given IP
|
||||
*/
|
||||
#define ZT_ARP_QUERY_INTERVAL 2000
|
||||
|
||||
/**
|
||||
* Maximum time between query and response, otherwise responses are discarded to prevent poisoning
|
||||
*/
|
||||
#define ZT_ARP_QUERY_MAX_TTL 5000
|
||||
|
||||
/**
|
||||
* ARP expiration time
|
||||
*/
|
||||
#define ZT_ARP_EXPIRE 600000
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* ARP cache and resolver
|
||||
*
|
||||
* To implement ARP:
|
||||
*
|
||||
* (1) Call processIncomingArp() on all ARP packets received and then always
|
||||
* check responseLen after calling. If it is non-zero, send the contents
|
||||
* of response to responseDest.
|
||||
*
|
||||
* (2) Call query() to look up IP addresses, and then check queryLen. If it
|
||||
* is non-zero, send the contents of query to queryDest (usually broadcast).
|
||||
*
|
||||
* Note that either of these functions can technically generate a response or
|
||||
* a query at any time, so their result parameters for sending ARPs should
|
||||
* always be checked.
|
||||
*
|
||||
* This class is not thread-safe and must be guarded if used in multi-threaded
|
||||
* code.
|
||||
*/
|
||||
class Arp
|
||||
{
|
||||
public:
|
||||
Arp();
|
||||
|
||||
/**
|
||||
* Set a local IP entry that we should respond to ARPs for
|
||||
*
|
||||
* @param mac Our local MAC address
|
||||
* @param ip IP in big-endian byte order (sin_addr.s_addr)
|
||||
*/
|
||||
void addLocal(uint32_t ip,const MAC &mac);
|
||||
|
||||
/**
|
||||
* Delete a local IP entry or a cached ARP entry
|
||||
*
|
||||
* @param ip IP in big-endian byte order (sin_addr.s_addr)
|
||||
*/
|
||||
void remove(uint32_t ip);
|
||||
|
||||
/**
|
||||
* Process ARP packets
|
||||
*
|
||||
* For ARP queries, a response is generated and responseLen is set to its
|
||||
* frame payload length in bytes.
|
||||
*
|
||||
* For ARP responses, the cache is populated and the IP address entry that
|
||||
* was learned is returned.
|
||||
*
|
||||
* @param arp ARP frame data
|
||||
* @param len Length of ARP frame (usually 28)
|
||||
* @param response Response buffer -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size
|
||||
* @param responseLen Response length, or set to 0 if no response
|
||||
* @param responseDest Destination of response, or set to null if no response
|
||||
* @return IP address learned or 0 if no new IPs in cache
|
||||
*/
|
||||
uint32_t processIncomingArp(const void *arp,unsigned int len,void *response,unsigned int &responseLen,MAC &responseDest);
|
||||
|
||||
/**
|
||||
* Get the MAC corresponding to an IP, generating a query if needed
|
||||
*
|
||||
* This returns a MAC for a remote IP. The local MAC is returned for local
|
||||
* IPs as well. It may also generate a query if the IP is not known or the
|
||||
* entry needs to be refreshed. In this case queryLen will be set to a
|
||||
* non-zero value, so this should always be checked on return even if the
|
||||
* MAC returned is non-null.
|
||||
*
|
||||
* @param localMac Local MAC address of host interface
|
||||
* @param localIp Local IP address of host interface
|
||||
* @param targetIp IP to look up
|
||||
* @param query Buffer for generated query -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size
|
||||
* @param queryLen Length of generated query, or set to 0 if no query generated
|
||||
* @param queryDest Destination of query, or set to null if no query generated
|
||||
* @return MAC or 0 if no cached entry for this IP
|
||||
*/
|
||||
MAC query(const MAC &localMac,uint32_t localIp,uint32_t targetIp,void *query,unsigned int &queryLen,MAC &queryDest);
|
||||
|
||||
private:
|
||||
struct _ArpEntry
|
||||
{
|
||||
_ArpEntry() : lastQuerySent(0),lastResponseReceived(0),mac(),local(false) {}
|
||||
uint64_t lastQuerySent; // Time last query was sent or 0 for local IP
|
||||
uint64_t lastResponseReceived; // Time of last ARP response or 0 for local IP
|
||||
MAC mac; // MAC address of device responsible for IP or null if not known yet
|
||||
bool local; // True if this is a local ARP entry
|
||||
};
|
||||
|
||||
Hashtable< uint32_t,_ArpEntry > _cache;
|
||||
uint64_t _lastCleaned;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <net/route.h>
|
||||
#include <pthread_np.h>
|
||||
|
||||
#include <sched.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "BSDEthernetTap.hpp"
|
||||
|
||||
#define ZT_BASE32_CHARS "0123456789abcdefghijklmnopqrstuv"
|
||||
#define ZT_TAP_BUF_SIZE (1024 * 16)
|
||||
|
||||
// ff:ff:ff:ff:ff:ff with no ADI
|
||||
static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
BSDEthernetTap::BSDEthernetTap(
|
||||
const char *homePath,
|
||||
unsigned int concurrency,
|
||||
bool pinning,
|
||||
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),
|
||||
_concurrency(concurrency),
|
||||
_pinning(pinning),
|
||||
_arg(arg),
|
||||
_nwid(nwid),
|
||||
_mtu(mtu),
|
||||
_metric(metric),
|
||||
_fd(0),
|
||||
_enabled(true),
|
||||
_lastIfAddrsUpdate(0)
|
||||
{
|
||||
static Mutex globalTapCreateLock;
|
||||
char devpath[64],ethaddr[64],mtustr[32],metstr[32],tmpdevname[32];
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
/* FreeBSD allows long interface names and interface renaming */
|
||||
|
||||
_dev = "zt";
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 60) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 55) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 50) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 45) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 40) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 35) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 30) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 25) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 20) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 15) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 10) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 5) & 0x1f)]);
|
||||
_dev.push_back(ZT_BASE32_CHARS[(unsigned long)(nwid & 0x1f)]);
|
||||
|
||||
std::vector<std::string> devFiles(OSUtils::listDirectory("/dev"));
|
||||
for(int i=9993;i<(9993+128);++i) {
|
||||
OSUtils::ztsnprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i);
|
||||
OSUtils::ztsnprintf(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) {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s create" ZT_EOL_S, tmpdevname);
|
||||
#endif
|
||||
::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");
|
||||
|
||||
struct stat stattmp;
|
||||
if (!stat(devpath,&stattmp)) {
|
||||
cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s name %s" ZT_EOL_S, tmpdevname, _dev.c_str());
|
||||
#endif
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",tmpdevname,"name",_dev.c_str(),(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode)
|
||||
throw std::runtime_error("ifconfig rename operation failed");
|
||||
} else throw std::runtime_error("fork() failed");
|
||||
|
||||
_fd = ::open(devpath,O_RDWR);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Other BSDs like OpenBSD only have a limited number of tap devices that cannot be renamed */
|
||||
|
||||
for(int i=0;i<64;++i) {
|
||||
OSUtils::ztsnprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i);
|
||||
OSUtils::ztsnprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname);
|
||||
_fd = ::open(devpath,O_RDWR);
|
||||
if (_fd > 0) {
|
||||
_dev = tmpdevname;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
OSUtils::ztsnprintf(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]);
|
||||
OSUtils::ztsnprintf(mtustr,sizeof(mtustr),"%u",_mtu);
|
||||
OSUtils::ztsnprintf(metstr,sizeof(metstr),"%u",_metric);
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s lladdr %s mtu %s metric %s up" ZT_EOL_S, _dev.c_str(), ethaddr, mtustr, metstr);
|
||||
#endif
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode) {
|
||||
::close(_fd);
|
||||
throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
BSDEthernetTap::~BSDEthernetTap()
|
||||
{
|
||||
::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit
|
||||
::close(_fd);
|
||||
::close(_shutdownSignalPipe[0]);
|
||||
::close(_shutdownSignalPipe[1]);
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s destroy" ZT_EOL_S, _dev.c_str());
|
||||
#endif
|
||||
::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);
|
||||
}
|
||||
Thread::join(_thread);
|
||||
for (std::thread &t : _rxThreads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
void BSDEthernetTap::setEnabled(bool en)
|
||||
{
|
||||
_enabled = en;
|
||||
}
|
||||
|
||||
bool BSDEthernetTap::enabled() const
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
static bool ___removeIp(const std::string &_dev,const InetAddress &ip)
|
||||
{
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
char ipbuf[64];
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s inet %s -alias" ZT_EOL_S, _dev.c_str(), ip.toIpString(ipbuf));
|
||||
#endif
|
||||
execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString(ipbuf),"-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 BSDEthernetTap::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) {
|
||||
char tmp[128];
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s %s %s alias" ZT_EOL_S, _dev.c_str(), ip.isV4() ? "inet" : "inet6", ip.toString(tmp));
|
||||
#endif
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString(tmp),"alias",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BSDEthernetTap::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> BSDEthernetTap::ips() const
|
||||
{
|
||||
uint64_t now = OSUtils::now();
|
||||
|
||||
if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) {
|
||||
return _ifaddrs;
|
||||
}
|
||||
_lastIfAddrsUpdate = now;
|
||||
|
||||
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());
|
||||
|
||||
_ifaddrs = r;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void BSDEthernetTap::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;
|
||||
::write(_fd,putBuf,len);
|
||||
}
|
||||
}
|
||||
|
||||
std::string BSDEthernetTap::deviceName() const
|
||||
{
|
||||
return _dev;
|
||||
}
|
||||
|
||||
void BSDEthernetTap::setFriendlyName(const char *friendlyName)
|
||||
{
|
||||
}
|
||||
|
||||
void BSDEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
|
||||
{
|
||||
std::vector<MulticastGroup> newGroups;
|
||||
|
||||
#ifndef __OpenBSD__
|
||||
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);
|
||||
}
|
||||
#endif // __OpenBSD__
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void BSDEthernetTap::setMtu(unsigned int mtu)
|
||||
{
|
||||
if (mtu != _mtu) {
|
||||
_mtu = mtu;
|
||||
long cpid = (long)vfork();
|
||||
if (cpid == 0) {
|
||||
char tmp[64];
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: ifconfig %s mtu %s" ZT_EOL_S, _dev.c_str(), tmp);
|
||||
#endif
|
||||
execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",tmp,(const char *)0);
|
||||
_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
waitpid(cpid,&exitcode,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BSDEthernetTap::threadMain()
|
||||
throw()
|
||||
{
|
||||
// Wait for a moment after startup -- wait for Network to finish
|
||||
// constructing itself.
|
||||
Thread::sleep(500);
|
||||
|
||||
for (unsigned int i = 0; i < _concurrency; ++i) {
|
||||
_rxThreads.push_back(std::thread([this, i, _pinning] {
|
||||
|
||||
if (_pinning) {
|
||||
int pinCore = i % _concurrency;
|
||||
fprintf(stderr, "Pinning thread %d to core %d\n", i, pinCore);
|
||||
pthread_t self = pthread_self();
|
||||
cpu_set_t cpuset;
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(pinCore, &cpuset);
|
||||
//int rc = sched_setaffinity(self, sizeof(cpu_set_t), &cpuset);
|
||||
int rc = pthread_setaffinity_np(self, sizeof(cpu_set_t), &cpuset);
|
||||
if (rc != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to pin thread %d to core %d: %s\n", i, pinCore, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t b[ZT_TAP_BUF_SIZE];
|
||||
MAC to, from;
|
||||
fd_set readfds, nullfds;
|
||||
int n, nfds, r;
|
||||
|
||||
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,b + r,sizeof(b) - 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(b,6);
|
||||
from.setTo(b + 6,6);
|
||||
unsigned int etherType = ntohs(((const uint16_t *)b)[6]);
|
||||
_handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(b + 14),r - 14);
|
||||
}
|
||||
|
||||
r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_BSDETHERNETTAP_HPP
|
||||
#define ZT_BSDETHERNETTAP_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "EthernetTap.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class BSDEthernetTap : public EthernetTap
|
||||
{
|
||||
public:
|
||||
BSDEthernetTap(
|
||||
const char *homePath,
|
||||
unsigned int concurrency,
|
||||
bool pinning,
|
||||
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 ~BSDEthernetTap();
|
||||
|
||||
virtual void setEnabled(bool en);
|
||||
virtual bool enabled() const;
|
||||
virtual bool addIp(const InetAddress &ip);
|
||||
virtual bool removeIp(const InetAddress &ip);
|
||||
virtual std::vector<InetAddress> 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<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
virtual void setMtu(unsigned int mtu);
|
||||
virtual void setDns(const char *domain, const std::vector<InetAddress> &servers) {}
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
private:
|
||||
void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
unsigned int _concurrency;
|
||||
bool _pinning;
|
||||
uint64_t _nwid;
|
||||
Thread _thread;
|
||||
std::string _dev;
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
unsigned int _mtu;
|
||||
unsigned int _metric;
|
||||
int _fd;
|
||||
int _shutdownSignalPipe[2];
|
||||
volatile bool _enabled;
|
||||
mutable std::vector<InetAddress> _ifaddrs;
|
||||
mutable uint64_t _lastIfAddrsUpdate;
|
||||
std::vector<std::thread> _rxThreads;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
* 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: 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_BINDER_HPP
|
||||
#define ZT_BINDER_HPP
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include <shlobj.h>
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <netioapi.h>
|
||||
#else
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#ifdef __LINUX__
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/if_addr.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(__unix__) || defined(__APPLE__)) && !defined(__LINUX__) && !defined(ZT_SDK)
|
||||
#include <net/if.h>
|
||||
#if TARGET_OS_OSX
|
||||
#include <netinet6/in6_var.h>
|
||||
#endif
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "Phy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Period between refreshes of bindings
|
||||
#define ZT_BINDER_REFRESH_PERIOD 30000
|
||||
|
||||
// Max number of bindings
|
||||
#define ZT_BINDER_MAX_BINDINGS 256
|
||||
|
||||
// Maximum physical interface name length. This number is gigantic because of Windows.
|
||||
#define ZT_MAX_PHYSIFNAME 256
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Enumerates local devices and binds to all potential ZeroTier path endpoints
|
||||
*
|
||||
* This replaces binding to wildcard (0.0.0.0 and ::0) with explicit binding
|
||||
* as part of the path to default gateway support. Under the hood it uses
|
||||
* different queries on different OSes to enumerate devices, and also exposes
|
||||
* device enumeration and endpoint IP data for use elsewhere.
|
||||
*
|
||||
* On OSes that do not support local port enumeration or where this is not
|
||||
* meaningful, this degrades to binding to wildcard.
|
||||
*/
|
||||
class Binder {
|
||||
private:
|
||||
struct _Binding {
|
||||
_Binding() : udpSock((PhySocket*)0)
|
||||
{
|
||||
}
|
||||
PhySocket* udpSock;
|
||||
InetAddress address;
|
||||
char ifname[256] = {};
|
||||
};
|
||||
|
||||
public:
|
||||
Binder() : _bindingCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all bound ports, should be called on shutdown
|
||||
*
|
||||
* @param phy Physical interface
|
||||
*/
|
||||
template <typename PHY_HANDLER_TYPE> void closeAll(Phy<PHY_HANDLER_TYPE>& phy)
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
|
||||
phy.close(_bindings[b].udpSock, false);
|
||||
}
|
||||
_bindingCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan local devices and addresses and rebind TCP and UDP
|
||||
*
|
||||
* This should be called after wake from sleep, on detected network device
|
||||
* changes, on startup, or periodically (e.g. every 30-60s).
|
||||
*
|
||||
* @param phy Physical interface
|
||||
* @param ports Ports to bind on all interfaces
|
||||
* @param portCount Number of ports
|
||||
* @param explicitBind If present, override interface IP detection and bind to these (if possible)
|
||||
* @param ifChecker Interface checker function to see if an interface should be used
|
||||
* @tparam PHY_HANDLER_TYPE Type for Phy<> template
|
||||
* @tparam INTERFACE_CHECKER Type for class containing shouldBindInterface() method
|
||||
*/
|
||||
template <typename PHY_HANDLER_TYPE, typename INTERFACE_CHECKER> void refresh(Phy<PHY_HANDLER_TYPE>& phy, unsigned int* ports, unsigned int portCount, const std::vector<InetAddress> explicitBind, INTERFACE_CHECKER& ifChecker)
|
||||
{
|
||||
std::map<InetAddress, std::string> localIfAddrs;
|
||||
PhySocket *udps;
|
||||
Mutex::Lock _l(_lock);
|
||||
bool interfacesEnumerated = true;
|
||||
|
||||
if (explicitBind.empty()) {
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
char aabuf[32768];
|
||||
ULONG aalen = sizeof(aabuf);
|
||||
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, (void*)0, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(aabuf), &aalen) == NO_ERROR) {
|
||||
PIP_ADAPTER_ADDRESSES a = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(aabuf);
|
||||
while (a) {
|
||||
PIP_ADAPTER_UNICAST_ADDRESS ua = a->FirstUnicastAddress;
|
||||
while (ua) {
|
||||
// Don't bind temporary/random IPv6 addresses
|
||||
if (ua->SuffixOrigin != IpSuffixOriginRandom) {
|
||||
InetAddress ip(ua->Address.lpSockaddr);
|
||||
char strBuf[128] = { 0 };
|
||||
wcstombs(strBuf, a->FriendlyName, sizeof(strBuf));
|
||||
if (ifChecker.shouldBindInterface(strBuf, ip)) {
|
||||
switch (ip.ipScope()) {
|
||||
default:
|
||||
break;
|
||||
case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
|
||||
case InetAddress::IP_SCOPE_GLOBAL:
|
||||
case InetAddress::IP_SCOPE_SHARED:
|
||||
case InetAddress::IP_SCOPE_PRIVATE:
|
||||
for (int x = 0; x < (int)portCount; ++x) {
|
||||
ip.setPort(ports[x]);
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ua = ua->Next;
|
||||
}
|
||||
a = a->Next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
interfacesEnumerated = false;
|
||||
}
|
||||
|
||||
#else // not __WINDOWS__
|
||||
|
||||
/* On Linux we use an alternative method if available since getifaddrs()
|
||||
* gets very slow when there are lots of network namespaces. This won't
|
||||
* work unless /proc/PID/net/if_inet6 exists and it may not on some
|
||||
* embedded systems, so revert to getifaddrs() there. */
|
||||
|
||||
#ifdef __LINUX__
|
||||
char fn[256], tmp[256];
|
||||
std::set<std::string> ifnames;
|
||||
const unsigned long pid = (unsigned long)getpid();
|
||||
|
||||
// Get all device names
|
||||
OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/dev", pid);
|
||||
FILE* procf = fopen(fn, "r");
|
||||
if (procf) {
|
||||
while (fgets(tmp, sizeof(tmp), procf)) {
|
||||
tmp[255] = 0;
|
||||
char* saveptr = (char*)0;
|
||||
for (char* f = Utils::stok(tmp, " \t\r\n:|", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n:|", &saveptr)) {
|
||||
if ((strcmp(f, "Inter-") != 0) && (strcmp(f, "face") != 0) && (f[0] != 0))
|
||||
ifnames.insert(f);
|
||||
break; // we only want the first field
|
||||
}
|
||||
}
|
||||
fclose(procf);
|
||||
}
|
||||
else {
|
||||
interfacesEnumerated = false;
|
||||
}
|
||||
|
||||
// Get IPv6 addresses (and any device names we don't already know)
|
||||
OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/if_inet6", pid);
|
||||
procf = fopen(fn, "r");
|
||||
if (procf) {
|
||||
while (fgets(tmp, sizeof(tmp), procf)) {
|
||||
tmp[255] = 0;
|
||||
char* saveptr = (char*)0;
|
||||
unsigned char ipbits[16];
|
||||
memset(ipbits, 0, sizeof(ipbits));
|
||||
char* devname = (char*)0;
|
||||
int flags = 0;
|
||||
int n = 0;
|
||||
for (char* f = Utils::stok(tmp, " \t\r\n", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n", &saveptr)) {
|
||||
switch (n++) {
|
||||
case 0: // IP in hex
|
||||
Utils::unhex(f, 32, ipbits, 16);
|
||||
break;
|
||||
case 4:
|
||||
flags = atoi(f);
|
||||
break;
|
||||
case 5: // device name
|
||||
devname = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (flags & IFA_F_TEMPORARY) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (devname) {
|
||||
ifnames.insert(devname);
|
||||
InetAddress ip(ipbits, 16, 0);
|
||||
if (ifChecker.shouldBindInterface(devname, ip)) {
|
||||
switch (ip.ipScope()) {
|
||||
default:
|
||||
break;
|
||||
case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
|
||||
case InetAddress::IP_SCOPE_GLOBAL:
|
||||
case InetAddress::IP_SCOPE_SHARED:
|
||||
case InetAddress::IP_SCOPE_PRIVATE:
|
||||
for (int x = 0; x < (int)portCount; ++x) {
|
||||
ip.setPort(ports[x]);
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string(devname)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(procf);
|
||||
}
|
||||
|
||||
// Get IPv4 addresses for each device
|
||||
if (! ifnames.empty()) {
|
||||
const int controlfd = (int)socket(AF_INET, SOCK_DGRAM, 0);
|
||||
struct ifconf configuration;
|
||||
configuration.ifc_len = 0;
|
||||
configuration.ifc_buf = nullptr;
|
||||
|
||||
if (controlfd < 0)
|
||||
goto ip4_address_error;
|
||||
if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0)
|
||||
goto ip4_address_error;
|
||||
configuration.ifc_buf = (char*)malloc(configuration.ifc_len);
|
||||
if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0)
|
||||
goto ip4_address_error;
|
||||
|
||||
for (int i = 0; i < (int)(configuration.ifc_len / sizeof(ifreq)); i++) {
|
||||
struct ifreq& request = configuration.ifc_req[i];
|
||||
struct sockaddr* addr = &request.ifr_ifru.ifru_addr;
|
||||
if (addr->sa_family != AF_INET)
|
||||
continue;
|
||||
std::string ifname = request.ifr_ifrn.ifrn_name;
|
||||
// name can either be just interface name or interface name followed by ':' and arbitrary label
|
||||
if (ifname.find(':') != std::string::npos)
|
||||
ifname = ifname.substr(0, ifname.find(':'));
|
||||
|
||||
InetAddress ip(&(((struct sockaddr_in*)addr)->sin_addr), 4, 0);
|
||||
if (ifChecker.shouldBindInterface(ifname.c_str(), ip)) {
|
||||
switch (ip.ipScope()) {
|
||||
default:
|
||||
break;
|
||||
case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
|
||||
case InetAddress::IP_SCOPE_GLOBAL:
|
||||
case InetAddress::IP_SCOPE_SHARED:
|
||||
case InetAddress::IP_SCOPE_PRIVATE:
|
||||
for (int x = 0; x < (int)portCount; ++x) {
|
||||
ip.setPort(ports[x]);
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, ifname));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ip4_address_error:
|
||||
free(configuration.ifc_buf);
|
||||
if (controlfd > 0)
|
||||
close(controlfd);
|
||||
}
|
||||
|
||||
const bool gotViaProc = (! localIfAddrs.empty());
|
||||
#else
|
||||
const bool gotViaProc = false;
|
||||
#endif
|
||||
|
||||
//
|
||||
// prevent:
|
||||
// warning: unused variable 'gotViaProc'
|
||||
//
|
||||
(void)gotViaProc;
|
||||
|
||||
#if ! defined(__ANDROID__) // getifaddrs() freeifaddrs() not available on Android
|
||||
if (! gotViaProc) {
|
||||
struct ifaddrs* ifatbl = (struct ifaddrs*)0;
|
||||
struct ifaddrs* ifa;
|
||||
#if (defined(__unix__) || defined(__APPLE__)) && !defined(__LINUX__) && !defined(ZT_SDK)
|
||||
// set up an IPv6 socket so we can check the state of interfaces via SIOCGIFAFLAG_IN6
|
||||
int infoSock = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
#endif
|
||||
if ((getifaddrs(&ifatbl) == 0) && (ifatbl)) {
|
||||
ifa = ifatbl;
|
||||
while (ifa) {
|
||||
if ((ifa->ifa_name) && (ifa->ifa_addr)) {
|
||||
InetAddress ip = *(ifa->ifa_addr);
|
||||
#if (defined(__unix__) || defined(__APPLE__)) && !defined(__LINUX__) && !defined(ZT_SDK) && TARGET_OS_OSX
|
||||
// Check if the address is an IPv6 Temporary Address, macOS/BSD version
|
||||
if (ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
struct sockaddr_in6* sa6 = (struct sockaddr_in6*)ifa->ifa_addr;
|
||||
struct in6_ifreq ifr6;
|
||||
memset(&ifr6, 0, sizeof(ifr6));
|
||||
strcpy(ifr6.ifr_name, ifa->ifa_name);
|
||||
ifr6.ifr_ifru.ifru_addr = *sa6;
|
||||
|
||||
int flags = 0;
|
||||
if (ioctl(infoSock, SIOCGIFAFLAG_IN6, (unsigned long long)&ifr6) != -1) {
|
||||
flags = ifr6.ifr_ifru.ifru_flags6;
|
||||
}
|
||||
|
||||
// if this is a temporary IPv6 address, skip to the next address
|
||||
if (flags & IN6_IFF_TEMPORARY) {
|
||||
#ifdef ZT_TRACE
|
||||
char buf[64];
|
||||
fprintf(stderr, "skip binding to temporary IPv6 address: %s\n", ip.toIpString(buf));
|
||||
#endif
|
||||
ifa = ifa->ifa_next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ifChecker.shouldBindInterface(ifa->ifa_name, ip)) {
|
||||
switch (ip.ipScope()) {
|
||||
default:
|
||||
break;
|
||||
case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
|
||||
case InetAddress::IP_SCOPE_GLOBAL:
|
||||
case InetAddress::IP_SCOPE_SHARED:
|
||||
case InetAddress::IP_SCOPE_PRIVATE:
|
||||
for (int x = 0; x < (int)portCount; ++x) {
|
||||
ip.setPort(ports[x]);
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string(ifa->ifa_name)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ifa = ifa->ifa_next;
|
||||
}
|
||||
freeifaddrs(ifatbl);
|
||||
}
|
||||
else {
|
||||
interfacesEnumerated = false;
|
||||
}
|
||||
#if (defined(__unix__) || defined(__APPLE__)) && !defined(__LINUX__) && !defined(ZT_SDK)
|
||||
close(infoSock);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
for (std::vector<InetAddress>::const_iterator i(explicitBind.begin()); i != explicitBind.end(); ++i) {
|
||||
InetAddress ip = InetAddress(*i);
|
||||
for (int x = 0; x < (int)portCount; ++x) {
|
||||
ip.setPort(ports[x]);
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to binding to wildcard if we can't enumerate addresses
|
||||
if (! interfacesEnumerated && localIfAddrs.empty()) {
|
||||
for (int x = 0; x < (int)portCount; ++x) {
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(InetAddress((uint32_t)0, ports[x]), std::string()));
|
||||
localIfAddrs.insert(std::pair<InetAddress, std::string>(InetAddress((const void*)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16, ports[x]), std::string()));
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int oldBindingCount = _bindingCount;
|
||||
_bindingCount = 0;
|
||||
|
||||
// Save bindings that are still valid, close those that are not
|
||||
for (unsigned int b = 0; b < oldBindingCount; ++b) {
|
||||
if (localIfAddrs.find(_bindings[b].address) != localIfAddrs.end()) {
|
||||
if (_bindingCount != b)
|
||||
_bindings[(unsigned int)_bindingCount] = _bindings[b];
|
||||
++_bindingCount;
|
||||
}
|
||||
else {
|
||||
PhySocket* const udps = _bindings[b].udpSock;
|
||||
_bindings[b].udpSock = (PhySocket*)0;
|
||||
phy.close(udps, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new bindings for those not already bound
|
||||
for (std::map<InetAddress, std::string>::const_iterator ii(localIfAddrs.begin()); ii != localIfAddrs.end(); ++ii) {
|
||||
unsigned int bi = 0;
|
||||
while (bi != _bindingCount) {
|
||||
if (_bindings[bi].address == ii->first)
|
||||
break;
|
||||
++bi;
|
||||
}
|
||||
if (bi == _bindingCount) {
|
||||
udps = phy.udpBind(reinterpret_cast<const struct sockaddr*>(&(ii->first)), (void*)0, ZT_UDP_DESIRED_BUF_SIZE);
|
||||
if (udps) {
|
||||
#ifdef __LINUX__
|
||||
// Bind Linux sockets to their device so routes that we manage do not override physical routes (wish all platforms had this!)
|
||||
if (ii->second.length() > 0) {
|
||||
char tmp[256];
|
||||
Utils::scopy(tmp, sizeof(tmp), ii->second.c_str());
|
||||
int fd = (int)Phy<PHY_HANDLER_TYPE>::getDescriptor(udps);
|
||||
if (fd >= 0) {
|
||||
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp));
|
||||
}
|
||||
}
|
||||
#endif // __LINUX__
|
||||
if (_bindingCount < ZT_BINDER_MAX_BINDINGS) {
|
||||
_bindings[_bindingCount].udpSock = udps;
|
||||
_bindings[_bindingCount].address = ii->first;
|
||||
memcpy(_bindings[_bindingCount].ifname, (char*)ii->second.c_str(), (int)ii->second.length());
|
||||
++_bindingCount;
|
||||
}
|
||||
}
|
||||
else {
|
||||
phy.close(udps, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return All currently bound local interface addresses
|
||||
*/
|
||||
inline std::vector<InetAddress> allBoundLocalInterfaceAddresses() const
|
||||
{
|
||||
std::vector<InetAddress> aa;
|
||||
Mutex::Lock _l(_lock);
|
||||
for (unsigned int b = 0, c = _bindingCount; b < c; ++b)
|
||||
aa.push_back(_bindings[b].address);
|
||||
return aa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send from all bound UDP sockets
|
||||
*/
|
||||
template <typename PHY_HANDLER_TYPE> inline bool udpSendAll(Phy<PHY_HANDLER_TYPE>& phy, const struct sockaddr_storage* addr, const void* data, unsigned int len, unsigned int ttl)
|
||||
{
|
||||
bool r = false;
|
||||
Mutex::Lock _l(_lock);
|
||||
for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
|
||||
if (ttl)
|
||||
phy.setIp4UdpTtl(_bindings[b].udpSock, ttl);
|
||||
if (phy.udpSend(_bindings[b].udpSock, (const struct sockaddr*)addr, data, len))
|
||||
r = true;
|
||||
if (ttl)
|
||||
phy.setIp4UdpTtl(_bindings[b].udpSock, 255);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param addr Address to check
|
||||
* @return True if this is a bound local interface address
|
||||
*/
|
||||
inline bool isBoundLocalInterfaceAddress(const InetAddress& addr) const
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
for (unsigned int b = 0; b < _bindingCount; ++b) {
|
||||
if (_bindings[b].address == addr)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly check that a UDP socket is valid
|
||||
*
|
||||
* @param udpSock UDP socket to check
|
||||
* @return True if socket is currently bound/allocated
|
||||
*/
|
||||
inline bool isUdpSocketValid(PhySocket* const udpSock)
|
||||
{
|
||||
for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
|
||||
if (_bindings[b].udpSock == udpSock)
|
||||
return (b < _bindingCount); // double check atomic which may have changed
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param s Socket object
|
||||
* @param nameBuf Buffer to store name of interface which this Socket object is bound to
|
||||
* @param buflen Length of buffer to copy name into
|
||||
*/
|
||||
void getIfName(PhySocket* s, char* nameBuf, int buflen) const
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
|
||||
if (_bindings[b].udpSock == s) {
|
||||
memcpy(nameBuf, _bindings[b].ifname, buflen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
_Binding _bindings[ZT_BINDER_MAX_BINDINGS];
|
||||
std::atomic<unsigned int> _bindingCount;
|
||||
Mutex _lock;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_BLOCKINGQUEUE_HPP
|
||||
#define ZT_BLOCKINGQUEUE_HPP
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Simple C++11 thread-safe queue
|
||||
*
|
||||
* Do not use in node/ since we have not gone C++11 there yet.
|
||||
*/
|
||||
template <class T>
|
||||
class BlockingQueue
|
||||
{
|
||||
public:
|
||||
BlockingQueue(void) : r(true) {}
|
||||
|
||||
inline void post(T t)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
q.push(t);
|
||||
c.notify_one();
|
||||
}
|
||||
|
||||
inline void postLimit(T t,const unsigned long limit)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
for(;;) {
|
||||
if (q.size() < limit) {
|
||||
q.push(t);
|
||||
c.notify_one();
|
||||
break;
|
||||
}
|
||||
if (!r)
|
||||
break;
|
||||
gc.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
inline void stop(void)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
r = false;
|
||||
c.notify_all();
|
||||
gc.notify_all();
|
||||
}
|
||||
|
||||
inline bool get(T &value)
|
||||
{
|
||||
std::unique_lock<std::mutex> 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;
|
||||
}
|
||||
|
||||
inline std::vector<T> drain()
|
||||
{
|
||||
std::vector<T> v;
|
||||
while (!q.empty()) {
|
||||
v.push_back(q.front());
|
||||
q.pop();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
enum TimedWaitResult
|
||||
{
|
||||
OK,
|
||||
TIMED_OUT,
|
||||
STOP
|
||||
};
|
||||
|
||||
inline TimedWaitResult get(T &value,const unsigned long ms)
|
||||
{
|
||||
const std::chrono::milliseconds ms2{ms};
|
||||
std::unique_lock<std::mutex> 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;
|
||||
}
|
||||
|
||||
inline size_t size() const {
|
||||
return q.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> q;
|
||||
mutable std::mutex m;
|
||||
mutable std::condition_variable c,gc;
|
||||
std::atomic_bool r;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 "EthernetTap.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef ZT_SDK
|
||||
|
||||
#include "../controller/EmbeddedNetworkController.hpp"
|
||||
#include "../node/Node.hpp"
|
||||
#include "../include/VirtualTap.hpp"
|
||||
|
||||
#else
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/sysctl.h>
|
||||
#include "MacEthernetTap.hpp"
|
||||
#include "MacKextEthernetTap.hpp"
|
||||
#endif // __APPLE__
|
||||
|
||||
#ifdef __LINUX__
|
||||
#include "LinuxEthernetTap.hpp"
|
||||
#endif // __LINUX__
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include "WindowsEthernetTap.hpp"
|
||||
#endif // __WINDOWS__
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include "BSDEthernetTap.hpp"
|
||||
#endif // __FreeBSD__
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#include "NetBSDEthernetTap.hpp"
|
||||
#endif // __NetBSD__
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#include "BSDEthernetTap.hpp"
|
||||
#endif // __OpenBSD__
|
||||
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
std::shared_ptr<EthernetTap> EthernetTap::newInstance(
|
||||
const char *tapDeviceType, // OS-specific, NULL for default
|
||||
unsigned int concurrency,
|
||||
bool pinning,
|
||||
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)
|
||||
{
|
||||
|
||||
#ifdef ZT_SDK
|
||||
|
||||
return std::shared_ptr<EthernetTap>(new VirtualTap(homePath,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
|
||||
#else // not ZT_SDK
|
||||
|
||||
#ifdef __APPLE__
|
||||
char osrelease[256];
|
||||
size_t size = sizeof(osrelease);
|
||||
if (sysctlbyname("kern.osrelease",osrelease,&size,nullptr,0) == 0) {
|
||||
char *dotAt = strchr(osrelease,'.');
|
||||
if (dotAt) {
|
||||
*dotAt = (char)0;
|
||||
// The "feth" virtual Ethernet device type appeared in Darwin 17.x.x. Older versions
|
||||
// (Sierra and earlier) must use the a kernel extension.
|
||||
if (strtol(osrelease,(char **)0,10) < 17) {
|
||||
return std::shared_ptr<EthernetTap>(new MacKextEthernetTap(homePath,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
} else {
|
||||
return std::shared_ptr<EthernetTap>(new MacEthernetTap(homePath,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
#ifdef __LINUX__
|
||||
return std::shared_ptr<EthernetTap>(new LinuxEthernetTap(homePath,concurrency,pinning,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
#endif // __LINUX__
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
if (FAILED(hres)) {
|
||||
throw std::runtime_error("WinEthernetTap: COM initialization failed");
|
||||
}
|
||||
|
||||
static bool _comInit = false;
|
||||
static Mutex _comInit_m;
|
||||
|
||||
{
|
||||
Mutex::Lock l(_comInit_m);
|
||||
if (!_comInit) {
|
||||
hres = CoInitializeSecurity(
|
||||
NULL,
|
||||
-1,
|
||||
NULL,
|
||||
NULL,
|
||||
RPC_C_AUTHN_LEVEL_PKT,
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE,
|
||||
NULL,
|
||||
EOAC_NONE,
|
||||
NULL
|
||||
);
|
||||
if (FAILED(hres)) {
|
||||
CoUninitialize();
|
||||
fprintf(stderr, "WinEthernetTap: Failed to initialize security");
|
||||
throw std::runtime_error("WinEthernetTap: Failed to initialize security");
|
||||
}
|
||||
_comInit = true;
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<EthernetTap>(new WindowsEthernetTap(homePath,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
#endif // __WINDOWS__
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
return std::shared_ptr<EthernetTap>(new BSDEthernetTap(homePath,concurrency,pinning,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
#endif // __FreeBSD__
|
||||
|
||||
#ifdef __NetBSD__
|
||||
return std::shared_ptr<EthernetTap>(new NetBSDEthernetTap(homePath,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
#endif // __NetBSD__
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
return std::shared_ptr<EthernetTap>(new BSDEthernetTap(homePath,mac,mtu,metric,nwid,friendlyName,handler,arg));
|
||||
#endif // __OpenBSD__
|
||||
|
||||
#endif // ZT_SDK?
|
||||
|
||||
return std::shared_ptr<EthernetTap>();
|
||||
}
|
||||
|
||||
EthernetTap::EthernetTap() {}
|
||||
EthernetTap::~EthernetTap() {}
|
||||
|
||||
bool EthernetTap::addIps(std::vector<InetAddress> ips)
|
||||
{
|
||||
for(std::vector<InetAddress>::const_iterator i(ips.begin());i!=ips.end();++i) {
|
||||
if (!addIp(*i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string EthernetTap::friendlyName() const
|
||||
{
|
||||
// Most platforms do not have this.
|
||||
return std::string();
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_ETHERNETTAP_HPP
|
||||
#define ZT_ETHERNETTAP_HPP
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#define GETIFADDRS_CACHE_TIME 1000
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class EthernetTap
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<EthernetTap> newInstance(
|
||||
const char *tapDeviceType, // OS-specific, NULL for default
|
||||
unsigned int concurrency,
|
||||
bool pinning,
|
||||
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);
|
||||
|
||||
EthernetTap();
|
||||
virtual ~EthernetTap();
|
||||
|
||||
virtual void setEnabled(bool en) = 0;
|
||||
virtual bool enabled() const = 0;
|
||||
virtual bool addIp(const InetAddress &ip) = 0;
|
||||
virtual bool addIps(std::vector<InetAddress> ips); // uses addIp() unless overridden
|
||||
virtual bool removeIp(const InetAddress &ip) = 0;
|
||||
virtual std::vector<InetAddress> ips() const = 0;
|
||||
virtual void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) = 0;
|
||||
virtual std::string deviceName() const = 0;
|
||||
virtual void setFriendlyName(const char *friendlyName) = 0;
|
||||
virtual std::string friendlyName() const;
|
||||
virtual void scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed) = 0;
|
||||
virtual void setMtu(unsigned int mtu) = 0;
|
||||
virtual void setDns(const char *domain, const std::vector<InetAddress> &servers) = 0;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Http.hpp"
|
||||
#include "Phy.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
|
||||
#ifdef ZT_USE_SYSTEM_HTTP_PARSER
|
||||
#include <http_parser.h>
|
||||
#else
|
||||
#include "../ext/http-parser/http_parser.h"
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
namespace {
|
||||
|
||||
static int ShttpOnMessageBegin(http_parser *parser);
|
||||
static int ShttpOnUrl(http_parser *parser,const char *ptr,size_t length);
|
||||
#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2)
|
||||
static int ShttpOnStatus(http_parser *parser,const char *ptr,size_t length);
|
||||
#else
|
||||
static int ShttpOnStatus(http_parser *parser);
|
||||
#endif
|
||||
static int ShttpOnHeaderField(http_parser *parser,const char *ptr,size_t length);
|
||||
static int ShttpOnValue(http_parser *parser,const char *ptr,size_t length);
|
||||
static int ShttpOnHeadersComplete(http_parser *parser);
|
||||
static int ShttpOnBody(http_parser *parser,const char *ptr,size_t length);
|
||||
static int ShttpOnMessageComplete(http_parser *parser);
|
||||
|
||||
#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 1)
|
||||
static const struct http_parser_settings HTTP_PARSER_SETTINGS = {
|
||||
ShttpOnMessageBegin,
|
||||
ShttpOnUrl,
|
||||
ShttpOnStatus,
|
||||
ShttpOnHeaderField,
|
||||
ShttpOnValue,
|
||||
ShttpOnHeadersComplete,
|
||||
ShttpOnBody,
|
||||
ShttpOnMessageComplete
|
||||
};
|
||||
#else
|
||||
static const struct http_parser_settings HTTP_PARSER_SETTINGS = {
|
||||
ShttpOnMessageBegin,
|
||||
ShttpOnUrl,
|
||||
ShttpOnHeaderField,
|
||||
ShttpOnValue,
|
||||
ShttpOnHeadersComplete,
|
||||
ShttpOnBody,
|
||||
ShttpOnMessageComplete
|
||||
};
|
||||
#endif
|
||||
|
||||
struct HttpPhyHandler
|
||||
{
|
||||
// not used
|
||||
inline void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) {}
|
||||
inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) {}
|
||||
|
||||
inline void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success)
|
||||
{
|
||||
if (success) {
|
||||
phy->setNotifyWritable(sock,true);
|
||||
} else {
|
||||
*responseBody = "connection failed";
|
||||
error = true;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
inline void phyOnTcpClose(PhySocket *sock,void **uptr)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
inline void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len)
|
||||
{
|
||||
lastActivity = OSUtils::now();
|
||||
http_parser_execute(&parser,&HTTP_PARSER_SETTINGS,(const char *)data,len);
|
||||
if ((parser.upgrade)||(parser.http_errno != HPE_OK))
|
||||
phy->close(sock);
|
||||
}
|
||||
|
||||
inline void phyOnTcpWritable(PhySocket *sock,void **uptr)
|
||||
{
|
||||
if (writePtr < (unsigned long)writeBuf.length()) {
|
||||
long n = phy->streamSend(sock,writeBuf.data() + writePtr,(unsigned long)writeBuf.length() - writePtr,true);
|
||||
if (n > 0)
|
||||
writePtr += n;
|
||||
}
|
||||
if (writePtr >= (unsigned long)writeBuf.length())
|
||||
phy->setNotifyWritable(sock,false);
|
||||
}
|
||||
|
||||
inline void phyOnFileDescriptorActivity(PhySocket *sock,void **uptr,bool readable,bool writable) {}
|
||||
#ifdef __UNIX_LIKE__
|
||||
inline void phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) {}
|
||||
inline void phyOnUnixClose(PhySocket *sock,void **uptr) {}
|
||||
inline void phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) {}
|
||||
inline void phyOnUnixWritable(PhySocket *sock,void **uptr) {}
|
||||
#endif // __UNIX_LIKE__
|
||||
|
||||
http_parser parser;
|
||||
std::string currentHeaderField;
|
||||
std::string currentHeaderValue;
|
||||
unsigned long messageSize;
|
||||
unsigned long writePtr;
|
||||
uint64_t lastActivity;
|
||||
std::string writeBuf;
|
||||
|
||||
unsigned long maxResponseSize;
|
||||
std::map<std::string,std::string> *responseHeaders;
|
||||
std::string *responseBody;
|
||||
bool error;
|
||||
bool done;
|
||||
|
||||
Phy<HttpPhyHandler *> *phy;
|
||||
PhySocket *sock;
|
||||
};
|
||||
|
||||
static int ShttpOnMessageBegin(http_parser *parser)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static int ShttpOnUrl(http_parser *parser,const char *ptr,size_t length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2)
|
||||
static int ShttpOnStatus(http_parser *parser,const char *ptr,size_t length)
|
||||
#else
|
||||
static int ShttpOnStatus(http_parser *parser)
|
||||
#endif
|
||||
{
|
||||
/*
|
||||
HttpPhyHandler *hh = reinterpret_cast<HttpPhyHandler *>(parser->data);
|
||||
hh->messageSize += (unsigned long)length;
|
||||
if (hh->messageSize > hh->maxResponseSize)
|
||||
return -1;
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
static int ShttpOnHeaderField(http_parser *parser,const char *ptr,size_t length)
|
||||
{
|
||||
HttpPhyHandler *hh = reinterpret_cast<HttpPhyHandler *>(parser->data);
|
||||
hh->messageSize += (unsigned long)length;
|
||||
if (hh->messageSize > hh->maxResponseSize)
|
||||
return -1;
|
||||
if ((hh->currentHeaderField.length())&&(hh->currentHeaderValue.length())) {
|
||||
(*hh->responseHeaders)[hh->currentHeaderField] = hh->currentHeaderValue;
|
||||
hh->currentHeaderField = "";
|
||||
hh->currentHeaderValue = "";
|
||||
}
|
||||
for(size_t i=0;i<length;++i)
|
||||
hh->currentHeaderField.push_back(OSUtils::toLower(ptr[i]));
|
||||
return 0;
|
||||
}
|
||||
static int ShttpOnValue(http_parser *parser,const char *ptr,size_t length)
|
||||
{
|
||||
HttpPhyHandler *hh = reinterpret_cast<HttpPhyHandler *>(parser->data);
|
||||
hh->messageSize += (unsigned long)length;
|
||||
if (hh->messageSize > hh->maxResponseSize)
|
||||
return -1;
|
||||
hh->currentHeaderValue.append(ptr,length);
|
||||
return 0;
|
||||
}
|
||||
static int ShttpOnHeadersComplete(http_parser *parser)
|
||||
{
|
||||
HttpPhyHandler *hh = reinterpret_cast<HttpPhyHandler *>(parser->data);
|
||||
if ((hh->currentHeaderField.length())&&(hh->currentHeaderValue.length()))
|
||||
(*hh->responseHeaders)[hh->currentHeaderField] = hh->currentHeaderValue;
|
||||
return 0;
|
||||
}
|
||||
static int ShttpOnBody(http_parser *parser,const char *ptr,size_t length)
|
||||
{
|
||||
HttpPhyHandler *hh = reinterpret_cast<HttpPhyHandler *>(parser->data);
|
||||
hh->messageSize += (unsigned long)length;
|
||||
if (hh->messageSize > hh->maxResponseSize)
|
||||
return -1;
|
||||
hh->responseBody->append(ptr,length);
|
||||
return 0;
|
||||
}
|
||||
static int ShttpOnMessageComplete(http_parser *parser)
|
||||
{
|
||||
HttpPhyHandler *hh = reinterpret_cast<HttpPhyHandler *>(parser->data);
|
||||
hh->phy->close(hh->sock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
unsigned int Http::_do(
|
||||
const char *method,
|
||||
unsigned long maxResponseSize,
|
||||
unsigned long timeout,
|
||||
const struct sockaddr *remoteAddress,
|
||||
const char *path,
|
||||
const std::map<std::string,std::string> &requestHeaders,
|
||||
const void *requestBody,
|
||||
unsigned long requestBodyLength,
|
||||
std::map<std::string,std::string> &responseHeaders,
|
||||
std::string &responseBody)
|
||||
{
|
||||
try {
|
||||
responseHeaders.clear();
|
||||
responseBody = "";
|
||||
|
||||
HttpPhyHandler handler;
|
||||
|
||||
http_parser_init(&(handler.parser),HTTP_RESPONSE);
|
||||
handler.parser.data = (void *)&handler;
|
||||
handler.messageSize = 0;
|
||||
handler.writePtr = 0;
|
||||
handler.lastActivity = OSUtils::now();
|
||||
|
||||
try {
|
||||
char tmp[1024];
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s %s HTTP/1.1\r\n",method,path);
|
||||
handler.writeBuf.append(tmp);
|
||||
for(std::map<std::string,std::string>::const_iterator h(requestHeaders.begin());h!=requestHeaders.end();++h) {
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s: %s\r\n",h->first.c_str(),h->second.c_str());
|
||||
handler.writeBuf.append(tmp);
|
||||
}
|
||||
handler.writeBuf.append("\r\n");
|
||||
if ((requestBody)&&(requestBodyLength))
|
||||
handler.writeBuf.append((const char *)requestBody,requestBodyLength);
|
||||
} catch ( ... ) {
|
||||
responseBody = "request too large";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (maxResponseSize) {
|
||||
handler.maxResponseSize = maxResponseSize;
|
||||
} else {
|
||||
handler.maxResponseSize = 2147483647;
|
||||
}
|
||||
handler.responseHeaders = &responseHeaders;
|
||||
handler.responseBody = &responseBody;
|
||||
handler.error = false;
|
||||
handler.done = false;
|
||||
|
||||
Phy<HttpPhyHandler *> phy(&handler,true,true);
|
||||
|
||||
bool instantConnect = false;
|
||||
handler.phy = &phy;
|
||||
handler.sock = phy.tcpConnect((const struct sockaddr *)remoteAddress,instantConnect,(void *)0,true);
|
||||
if (!handler.sock) {
|
||||
responseBody = "connection failed (2)";
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (!handler.done) {
|
||||
phy.poll(timeout / 2);
|
||||
if ((timeout)&&((unsigned long)(OSUtils::now() - handler.lastActivity) > timeout)) {
|
||||
phy.close(handler.sock);
|
||||
responseBody = "timed out";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ((handler.error) ? 0 : ((handler.parser.http_errno != HPE_OK) ? 0 : handler.parser.status_code));
|
||||
} catch (std::exception &exc) {
|
||||
responseBody = exc.what();
|
||||
return 0;
|
||||
} catch ( ... ) {
|
||||
responseBody = "unknown exception";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_HTTP_HPP
|
||||
#define ZT_HTTP_HPP
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Simple synchronous HTTP client used for updater and cli
|
||||
*/
|
||||
class Http
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Make HTTP GET request
|
||||
*
|
||||
* The caller must set all headers, including Host.
|
||||
*
|
||||
* @return HTTP status code or 0 on error (responseBody will contain error message)
|
||||
*/
|
||||
static inline unsigned int GET(
|
||||
unsigned long maxResponseSize,
|
||||
unsigned long timeout,
|
||||
const struct sockaddr *remoteAddress,
|
||||
const char *path,
|
||||
const std::map<std::string,std::string> &requestHeaders,
|
||||
std::map<std::string,std::string> &responseHeaders,
|
||||
std::string &responseBody)
|
||||
{
|
||||
return _do(
|
||||
"GET",
|
||||
maxResponseSize,
|
||||
timeout,
|
||||
remoteAddress,
|
||||
path,
|
||||
requestHeaders,
|
||||
(const void *)0,
|
||||
0,
|
||||
responseHeaders,
|
||||
responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP DELETE request
|
||||
*
|
||||
* The caller must set all headers, including Host.
|
||||
*
|
||||
* @return HTTP status code or 0 on error (responseBody will contain error message)
|
||||
*/
|
||||
static inline unsigned int DEL(
|
||||
unsigned long maxResponseSize,
|
||||
unsigned long timeout,
|
||||
const struct sockaddr *remoteAddress,
|
||||
const char *path,
|
||||
const std::map<std::string,std::string> &requestHeaders,
|
||||
std::map<std::string,std::string> &responseHeaders,
|
||||
std::string &responseBody)
|
||||
{
|
||||
return _do(
|
||||
"DELETE",
|
||||
maxResponseSize,
|
||||
timeout,
|
||||
remoteAddress,
|
||||
path,
|
||||
requestHeaders,
|
||||
(const void *)0,
|
||||
0,
|
||||
responseHeaders,
|
||||
responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP POST request
|
||||
*
|
||||
* It is the responsibility of the caller to set all headers. With POST, the
|
||||
* Content-Length and Content-Type headers must be set or the POST will not
|
||||
* work.
|
||||
*
|
||||
* @return HTTP status code or 0 on error (responseBody will contain error message)
|
||||
*/
|
||||
static inline unsigned int POST(
|
||||
unsigned long maxResponseSize,
|
||||
unsigned long timeout,
|
||||
const struct sockaddr *remoteAddress,
|
||||
const char *path,
|
||||
const std::map<std::string,std::string> &requestHeaders,
|
||||
const void *postData,
|
||||
unsigned long postDataLength,
|
||||
std::map<std::string,std::string> &responseHeaders,
|
||||
std::string &responseBody)
|
||||
{
|
||||
return _do(
|
||||
"POST",
|
||||
maxResponseSize,
|
||||
timeout,
|
||||
remoteAddress,
|
||||
path,
|
||||
requestHeaders,
|
||||
postData,
|
||||
postDataLength,
|
||||
responseHeaders,
|
||||
responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP PUT request
|
||||
*
|
||||
* It is the responsibility of the caller to set all headers. With PUT, the
|
||||
* Content-Length and Content-Type headers must be set or the PUT will not
|
||||
* work.
|
||||
*
|
||||
* @return HTTP status code or 0 on error (responseBody will contain error message)
|
||||
*/
|
||||
static inline unsigned int PUT(
|
||||
unsigned long maxResponseSize,
|
||||
unsigned long timeout,
|
||||
const struct sockaddr *remoteAddress,
|
||||
const char *path,
|
||||
const std::map<std::string,std::string> &requestHeaders,
|
||||
const void *postData,
|
||||
unsigned long postDataLength,
|
||||
std::map<std::string,std::string> &responseHeaders,
|
||||
std::string &responseBody)
|
||||
{
|
||||
return _do(
|
||||
"PUT",
|
||||
maxResponseSize,
|
||||
timeout,
|
||||
remoteAddress,
|
||||
path,
|
||||
requestHeaders,
|
||||
postData,
|
||||
postDataLength,
|
||||
responseHeaders,
|
||||
responseBody);
|
||||
}
|
||||
|
||||
private:
|
||||
static unsigned int _do(
|
||||
const char *method,
|
||||
unsigned long maxResponseSize,
|
||||
unsigned long timeout,
|
||||
const struct sockaddr *remoteAddress,
|
||||
const char *path,
|
||||
const std::map<std::string,std::string> &requestHeaders,
|
||||
const void *requestBody,
|
||||
unsigned long requestBodyLength,
|
||||
std::map<std::string,std::string> &responseHeaders,
|
||||
std::string &responseBody);
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <linux/if_addr.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
|
||||
#ifndef IFNAMSIZ
|
||||
#define IFNAMSIZ 16
|
||||
#endif
|
||||
|
||||
#define ZT_TAP_BUF_SIZE (1024 * 16)
|
||||
|
||||
// ff:ff:ff:ff:ff:ff with no ADI
|
||||
static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
// determine if we're running a really old linux kernel.
|
||||
// Kernels in the 2.6.x series don't behave the same when bringing up
|
||||
// the tap devices.
|
||||
//
|
||||
// Returns true if the kernel major version is < 3
|
||||
bool isOldLinuxKernel() {
|
||||
struct utsname buffer;
|
||||
char *p;
|
||||
long ver[16];
|
||||
int i = 0;
|
||||
if (uname(&buffer) != 0) {
|
||||
perror("uname");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
p = buffer.release;
|
||||
|
||||
while (*p) {
|
||||
if (isdigit(*p)) {
|
||||
ver[i] = strtol(p, &p, 10);
|
||||
i++;
|
||||
} else {
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
return ver[0] < 3;
|
||||
}
|
||||
|
||||
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,
|
||||
unsigned int concurrency,
|
||||
bool pinning,
|
||||
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),
|
||||
_run(true),
|
||||
_lastIfAddrsUpdate(0)
|
||||
{
|
||||
static std::mutex s_tapCreateLock;
|
||||
char procpath[128],nwids[32];
|
||||
struct stat sbuf;
|
||||
|
||||
|
||||
// Create only one tap at a time globally.
|
||||
std::lock_guard<std::mutex> 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<std::string,std::string> 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<std::string,std::string>::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);
|
||||
|
||||
for (unsigned int i = 0; i < concurrency; ++i) {
|
||||
_rxThreads.push_back(std::thread([this, i, concurrency, pinning] {
|
||||
|
||||
if (pinning) {
|
||||
int pinCore = i % concurrency;
|
||||
fprintf(stderr, "Pinning tap thread %d to core %d\n", i, pinCore);
|
||||
pthread_t self = pthread_self();
|
||||
cpu_set_t cpuset;
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(pinCore, &cpuset);
|
||||
int rc = pthread_setaffinity_np(self, sizeof(cpu_set_t), &cpuset);
|
||||
if (rc != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to pin tap thread %d to core %d: %s\n", i, pinCore, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t b[ZT_TAP_BUF_SIZE];
|
||||
fd_set readfds, nullfds;
|
||||
int n, nfds, r;
|
||||
if (i == 0) {
|
||||
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_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;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
|
||||
if (isOldLinuxKernel()) {
|
||||
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;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
ifr.ifr_flags |= IFF_MULTICAST;
|
||||
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;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
|
||||
if (! isOldLinuxKernel()) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (! _run) {
|
||||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
break;
|
||||
}
|
||||
if (FD_ISSET(_fd, &readfds)) {
|
||||
for (;;) {
|
||||
// read until there are no more packets, then return to outer select() loop
|
||||
n = (int)::read(_fd, b + 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) {
|
||||
MAC to(b, 6), from(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)(r - 14));
|
||||
}
|
||||
|
||||
r = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
LinuxEthernetTap::~LinuxEthernetTap()
|
||||
{
|
||||
_run = false;
|
||||
(void)::write(_shutdownSignalPipe[1],"\0",1);
|
||||
::close(_fd);
|
||||
::close(_shutdownSignalPipe[0]);
|
||||
::close(_shutdownSignalPipe[1]);
|
||||
for (std::thread &t : _rxThreads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
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<InetAddress> 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<InetAddress> 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<InetAddress>::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<InetAddress> allIps(ips());
|
||||
if (std::find(allIps.begin(),allIps.end(),ip) != allIps.end()) {
|
||||
if (___removeIp(_dev,ip))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<InetAddress> LinuxEthernetTap::ips() const
|
||||
{
|
||||
|
||||
uint64_t now = OSUtils::now();
|
||||
|
||||
if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) {
|
||||
return _ifaddrs;
|
||||
}
|
||||
_lastIfAddrsUpdate = now;
|
||||
|
||||
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());
|
||||
r.erase(std::unique(r.begin(),r.end()),r.end());
|
||||
|
||||
_ifaddrs = r;
|
||||
|
||||
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<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
|
||||
{
|
||||
char *ptr,*ptr2;
|
||||
unsigned char mac[6];
|
||||
std::vector<MulticastGroup> 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<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());
|
||||
newGroups.erase(std::unique(newGroups.begin(),newGroups.end()),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);
|
||||
}
|
||||
|
||||
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));
|
||||
strcpy(ifr.ifr_name,_dev.c_str());
|
||||
ifr.ifr_ifru.ifru_mtu = (int)mtu;
|
||||
if (ioctl(sock,SIOCSIFMTU,(void *)&ifr) < 0) {
|
||||
printf("WARNING: ioctl() failed updating existing Linux tap device (set MTU)\n");
|
||||
}
|
||||
close(sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // __LINUX__
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_LINUXETHERNETTAP_HPP
|
||||
#define ZT_LINUXETHERNETTAP_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
#include "EthernetTap.hpp"
|
||||
#include "BlockingQueue.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class LinuxEthernetTap : public EthernetTap
|
||||
{
|
||||
public:
|
||||
LinuxEthernetTap(
|
||||
const char *homePath,
|
||||
unsigned int concurrency,
|
||||
bool pinning,
|
||||
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<InetAddress> ips);
|
||||
virtual bool removeIp(const InetAddress &ip);
|
||||
virtual std::vector<InetAddress> 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<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
virtual void setMtu(unsigned int mtu);
|
||||
virtual void setDns(const char *domain, const std::vector<InetAddress> &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<MulticastGroup> _multicastGroups;
|
||||
unsigned int _mtu;
|
||||
int _fd;
|
||||
int _shutdownSignalPipe[2];
|
||||
std::atomic_bool _enabled;
|
||||
std::atomic_bool _run;
|
||||
mutable std::vector<InetAddress> _ifaddrs;
|
||||
mutable uint64_t _lastIfAddrsUpdate;
|
||||
std::vector<std::thread> _rxThreads;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_LINUX_NETLINK_HPP
|
||||
#define ZT_LINUX_NETLINK_HPP
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
|
||||
#ifdef __LINUX__
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <asm/types.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <sys/socket.h>
|
||||
//#include <linux/if.h>
|
||||
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "../node/Hashtable.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Interface with Linux's RTNETLINK
|
||||
*/
|
||||
class LinuxNetLink
|
||||
{
|
||||
private:
|
||||
LinuxNetLink();
|
||||
~LinuxNetLink();
|
||||
|
||||
public:
|
||||
struct Route {
|
||||
InetAddress target;
|
||||
InetAddress via;
|
||||
InetAddress src;
|
||||
int ifidx;
|
||||
|
||||
inline bool operator==(const Route &r) const
|
||||
{ return ((target == r.target)&&(via == r.via)&&(src == r.src)&&(ifidx == r.ifidx)); }
|
||||
inline bool operator!=(const Route &r) const
|
||||
{ return (!(*this == r)); }
|
||||
inline bool operator<(const Route &r) const
|
||||
{
|
||||
if (target < r.target) {
|
||||
return true;
|
||||
} else if (target == r.target) {
|
||||
if (via < r.via) {
|
||||
return true;
|
||||
} else if (via == r.via) {
|
||||
if (src < r.src) {
|
||||
return true;
|
||||
} else if (src == r.src) {
|
||||
return (ifidx < r.ifidx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
inline bool operator>(const Route &r) const
|
||||
{ return (r < *this); }
|
||||
inline bool operator<=(const Route &r) const
|
||||
{ return !(r < *this); }
|
||||
inline bool operator>=(const Route &r) const
|
||||
{ return !(*this < r); }
|
||||
};
|
||||
|
||||
static LinuxNetLink& getInstance()
|
||||
{
|
||||
static LinuxNetLink instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
LinuxNetLink(LinuxNetLink const&) = delete;
|
||||
void operator=(LinuxNetLink const&) = delete;
|
||||
|
||||
void addRoute(const InetAddress &target, const InetAddress &via, const InetAddress &src, const char *ifaceName);
|
||||
void delRoute(const InetAddress &target, const InetAddress &via, const InetAddress &src, const char *ifaceName);
|
||||
|
||||
void addAddress(const InetAddress &addr, const char *iface);
|
||||
void removeAddress(const InetAddress &addr, const char *iface);
|
||||
|
||||
bool routeIsSet(const InetAddress &target, const InetAddress &via, const InetAddress &src, const char *ifname);
|
||||
|
||||
void threadMain() throw();
|
||||
|
||||
private:
|
||||
int _doRecv(int fd);
|
||||
|
||||
void _processMessage(struct nlmsghdr *nlp, int nll);
|
||||
void _routeAdded(struct nlmsghdr *nlp);
|
||||
void _routeDeleted(struct nlmsghdr *nlp);
|
||||
void _linkAdded(struct nlmsghdr *nlp);
|
||||
void _linkDeleted(struct nlmsghdr *nlp);
|
||||
void _ipAddressAdded(struct nlmsghdr *nlp);
|
||||
void _ipAddressDeleted(struct nlmsghdr *nlp);
|
||||
|
||||
void _requestInterfaceList();
|
||||
void _requestIPv4Routes();
|
||||
void _requestIPv6Routes();
|
||||
|
||||
int _indexForInterface(const char *iface);
|
||||
|
||||
void _setSocketTimeout(int fd, int seconds = 1);
|
||||
|
||||
Thread _t;
|
||||
bool _running;
|
||||
|
||||
uint32_t _seq;
|
||||
|
||||
std::map< InetAddress,std::set<LinuxNetLink::Route> > _routes;
|
||||
Mutex _routes_m;
|
||||
|
||||
struct iface_entry {
|
||||
iface_entry()
|
||||
{ memset(this,0,sizeof(iface_entry)); }
|
||||
int index;
|
||||
char ifacename[16]; // IFNAMSIZ on Linux == 16
|
||||
char mac[18];
|
||||
char mac_bin[6];
|
||||
unsigned int mtu;
|
||||
};
|
||||
Hashtable<int, iface_entry> _interfaces;
|
||||
Mutex _if_m;
|
||||
|
||||
// socket communication vars;
|
||||
int _fd;
|
||||
struct sockaddr_nl _la;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // ZT_LINUX_NETLINK_HPPS
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef MAC_DNS_HELPER
|
||||
#define MAC_DNS_HELPER
|
||||
|
||||
#include <vector>
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class MacDNSHelper
|
||||
{
|
||||
public:
|
||||
static void setDNS(uint64_t nwid, const char *domain, const std::vector<InetAddress> &servers);
|
||||
static void removeDNS(uint64_t nwid);
|
||||
static bool addIps4(uint64_t nwid, const MAC mac, const char *dev, const std::vector<InetAddress> &addrs);
|
||||
static bool addIps6(uint64_t nwid, const MAC mac, const char *dev, const std::vector<InetAddress> &addrs);
|
||||
static bool removeIps4(uint64_t nwid);
|
||||
static bool removeIps6(uint64_t nwid);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,347 @@
|
||||
#include "MacDNSHelper.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <SystemConfiguration/SystemConfiguration.h>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static void printKeys (const void* key, const void* value, void* context) {
|
||||
CFShow(key);
|
||||
CFShow(value);
|
||||
}
|
||||
|
||||
void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector<InetAddress> &servers)
|
||||
{
|
||||
SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
|
||||
|
||||
CFStringRef *s = new CFStringRef[4];
|
||||
for (unsigned int i = 0; i < servers.size(); ++i) {
|
||||
char buf[64];
|
||||
ZeroTier::InetAddress a = servers[i];
|
||||
const char *ipStr = a.toIpString(buf);
|
||||
s[i] = CFStringCreateWithCString(NULL, ipStr, kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
CFArrayRef serverArray = CFArrayCreate(NULL, (const void**)s, servers.size(), &kCFTypeArrayCallBacks);
|
||||
|
||||
CFStringRef keys[3];
|
||||
keys[0] = CFSTR("SupplementalMatchDomains");
|
||||
keys[1] = CFSTR("ServerAddresses");
|
||||
keys[2] = CFSTR("SearchDomains");
|
||||
|
||||
CFStringRef cfdomain = CFStringCreateWithCString(NULL, domain, kCFStringEncodingUTF8);
|
||||
CFStringRef cfdomain2 = CFStringCreateWithCString(NULL, domain, kCFStringEncodingUTF8);
|
||||
CFArrayRef domainArray = CFArrayCreate(NULL, (const void**)&cfdomain, 1, &kCFTypeArrayCallBacks);
|
||||
CFArrayRef domainArray2 = CFArrayCreate(NULL, (const void**)&cfdomain2, 1, &kCFTypeArrayCallBacks);
|
||||
|
||||
CFTypeRef values[3];
|
||||
values[0] = domainArray;
|
||||
values[1] = serverArray;
|
||||
values[2] = domainArray2;
|
||||
|
||||
CFDictionaryRef dict = CFDictionaryCreate(NULL,
|
||||
(const void**)keys, (const void**)values, 3, &kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
char buf[256] = {0};
|
||||
sprintf(buf, "State:/Network/Service/%.16llx/DNS", nwid);
|
||||
CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
|
||||
CFArrayRef list = SCDynamicStoreCopyKeyList(ds, key);
|
||||
CFIndex i = 0, j = CFArrayGetCount(list);
|
||||
bool dnsServersChanged = true;
|
||||
CFPropertyListRef oldDNSServers = NULL;
|
||||
if (j > 0) {
|
||||
oldDNSServers = SCDynamicStoreCopyValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i));
|
||||
dnsServersChanged = !CFEqual(oldDNSServers,dict);
|
||||
}
|
||||
if (dnsServersChanged) {
|
||||
bool ret = TRUE;
|
||||
if (j <= 0) {
|
||||
ret &= SCDynamicStoreAddValue(ds, key, dict);
|
||||
} else {
|
||||
ret &= SCDynamicStoreSetValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i), dict);
|
||||
}
|
||||
if (!ret) {
|
||||
fprintf(stderr, "Error writing DNS configuration\n");
|
||||
}
|
||||
}
|
||||
if (oldDNSServers != NULL) {
|
||||
CFRelease(oldDNSServers);
|
||||
}
|
||||
CFRelease(list);
|
||||
CFRelease(key);
|
||||
CFRelease(dict);
|
||||
CFRelease(domainArray);
|
||||
CFRelease(domainArray2);
|
||||
CFRelease(cfdomain);
|
||||
CFRelease(cfdomain2);
|
||||
CFRelease(serverArray);
|
||||
for (int i = 0; i < servers.size(); ++i) {
|
||||
CFRelease(s[i]);
|
||||
}
|
||||
delete[] s;
|
||||
CFRelease(ds);
|
||||
}
|
||||
|
||||
void MacDNSHelper::removeDNS(uint64_t nwid)
|
||||
{
|
||||
SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
|
||||
|
||||
char buf[256] = {0};
|
||||
sprintf(buf, "State:/Network/Service/%.16llx/DNS", nwid);
|
||||
CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
|
||||
SCDynamicStoreRemoveValue(ds, key);
|
||||
CFRelease(key);
|
||||
CFRelease(ds);
|
||||
}
|
||||
|
||||
// Make macOS believe we do in fact have ipv6 connectivity and that it should resolve dns names
|
||||
// over ipv6 if we ask for them.
|
||||
// Originally I planned to put all the v6 ip addresses from the network into the config.
|
||||
// But only the link local address is necessary and sufficient. Added other v6 addresses
|
||||
// doesn't do anything.
|
||||
//
|
||||
// As of Monterey we need IPv4 set up too.
|
||||
|
||||
bool MacDNSHelper::addIps4(uint64_t nwid, const MAC mac, const char *dev, const std::vector<InetAddress>& addrs)
|
||||
{
|
||||
const char* ipStr = {0};
|
||||
char buf2[256] = {0};
|
||||
|
||||
bool hasV4 = false;
|
||||
for (unsigned int i = 0; i < addrs.size(); ++i) {
|
||||
if (addrs[i].isV4()) {
|
||||
hasV4 = true;
|
||||
|
||||
ipStr = addrs[i].toIpString(buf2);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasV4) {
|
||||
MacDNSHelper::removeIps4(nwid);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
|
||||
char buf[256] = { 0 };
|
||||
sprintf(buf, "State:/Network/Service/%.16llx/IPv4", nwid);
|
||||
|
||||
|
||||
CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
|
||||
|
||||
CFStringRef cfaddr = CFStringCreateWithCString(NULL, ipStr, kCFStringEncodingUTF8);
|
||||
CFArrayRef addrArray = CFArrayCreate(NULL, (const void**)&cfaddr, 1, &kCFTypeArrayCallBacks);
|
||||
|
||||
CFStringRef cfdev = CFStringCreateWithCString(NULL, dev, kCFStringEncodingUTF8);
|
||||
|
||||
CFStringRef cfserver = CFStringCreateWithCString(NULL, "127.0.0.1", kCFStringEncodingUTF8);
|
||||
// using the ip from the zerotier network breaks routing on the mac
|
||||
CFStringRef cfrouter = CFStringCreateWithCString(NULL, "127.0.0.1", kCFStringEncodingUTF8);
|
||||
|
||||
const int SIZE = 4;
|
||||
CFStringRef keys[SIZE];
|
||||
keys[0] = CFSTR("Addresses");
|
||||
keys[1] = CFSTR("InterfaceName");
|
||||
keys[2] = CFSTR("ServerAddress");
|
||||
keys[3] = CFSTR("Router");
|
||||
|
||||
CFTypeRef values[SIZE];
|
||||
values[0] = addrArray;
|
||||
values[1] = cfdev;
|
||||
values[2] = cfserver;
|
||||
values[3] = cfrouter;
|
||||
|
||||
|
||||
CFDictionaryRef dict = CFDictionaryCreate(NULL,
|
||||
(const void**)keys, (const void**)values, SIZE, &kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
// CFDictionaryApplyFunction(dict, printKeys, NULL);
|
||||
|
||||
CFArrayRef list = SCDynamicStoreCopyKeyList(ds, key);
|
||||
CFIndex i = 0, j = CFArrayGetCount(list);
|
||||
bool addrsChanged = true;
|
||||
CFPropertyListRef oldAddrs = NULL;
|
||||
|
||||
bool ret = TRUE;
|
||||
if (j > 0) {
|
||||
oldAddrs = SCDynamicStoreCopyValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i));
|
||||
addrsChanged = !CFEqual(oldAddrs,dict);
|
||||
}
|
||||
if (addrsChanged) {
|
||||
if (j <= 0) {
|
||||
ret &= SCDynamicStoreAddValue(ds, key, dict);
|
||||
} else {
|
||||
ret &= SCDynamicStoreSetValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i), dict);
|
||||
}
|
||||
if (!ret) {
|
||||
fprintf(stderr, "Error writing IPv6 configuration\n");
|
||||
}
|
||||
}
|
||||
if (oldAddrs != NULL) {
|
||||
CFRelease(oldAddrs);
|
||||
}
|
||||
|
||||
CFRelease(cfaddr);
|
||||
|
||||
CFRelease(addrArray);
|
||||
CFRelease(cfdev);
|
||||
CFRelease(cfserver);
|
||||
CFRelease(cfrouter);
|
||||
|
||||
CFRelease(ds);
|
||||
CFRelease(key);
|
||||
|
||||
// for (unsigned int i = 0; i < SIZE; ++i) {
|
||||
// values[i] = NULL;
|
||||
// }
|
||||
|
||||
CFRelease(list);
|
||||
CFRelease(dict);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
bool MacDNSHelper::addIps6(uint64_t nwid, const MAC mac, const char *dev, const std::vector<InetAddress>& addrs)
|
||||
{
|
||||
bool hasV6 = false;
|
||||
for (unsigned int i = 0; i < addrs.size(); ++i) {
|
||||
if (addrs[i].isV6()) {
|
||||
hasV6 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasV6) {
|
||||
MacDNSHelper::removeIps6(nwid);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
|
||||
char buf[256] = { 0 };
|
||||
sprintf(buf, "State:/Network/Service/%.16llx/IPv6", nwid);
|
||||
|
||||
InetAddress ll = InetAddress::makeIpv6LinkLocal(mac);
|
||||
char buf2[256] = {0};
|
||||
const char* llStr = ll.toIpString(buf2);
|
||||
|
||||
|
||||
CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
|
||||
|
||||
CFStringRef cfaddr = CFStringCreateWithCString(NULL, llStr, kCFStringEncodingUTF8);
|
||||
CFStringRef cfprefixes = CFStringCreateWithCString(NULL, "64", kCFStringEncodingUTF8);
|
||||
CFStringRef cfdestaddrs = CFStringCreateWithCString(NULL, "::ffff:ffff:ffff:ffff:0:0", kCFStringEncodingUTF8);
|
||||
CFStringRef cfflags = CFStringCreateWithCString(NULL, "0", kCFStringEncodingUTF8);
|
||||
|
||||
CFArrayRef addrArray = CFArrayCreate(NULL, (const void**)&cfaddr, 1, &kCFTypeArrayCallBacks);
|
||||
CFArrayRef prefixArray = CFArrayCreate(NULL, (const void**)&cfprefixes, 1, &kCFTypeArrayCallBacks);
|
||||
CFArrayRef destArray = CFArrayCreate(NULL, (const void**)&cfdestaddrs, 1, &kCFTypeArrayCallBacks);
|
||||
CFArrayRef flagsArray = CFArrayCreate(NULL, (const void**)&cfflags, 1, &kCFTypeArrayCallBacks);
|
||||
CFStringRef cfdev = CFStringCreateWithCString(NULL, dev, kCFStringEncodingUTF8);
|
||||
|
||||
const int SIZE = 5;
|
||||
CFStringRef keys[SIZE];
|
||||
keys[0] = CFSTR("Addresses");
|
||||
keys[1] = CFSTR("DestAddresses");
|
||||
keys[2] = CFSTR("Flags");
|
||||
keys[3] = CFSTR("InterfaceName");
|
||||
keys[4] = CFSTR("PrefixLength");
|
||||
|
||||
CFTypeRef values[SIZE];
|
||||
values[0] = addrArray;
|
||||
values[1] = destArray;
|
||||
values[2] = flagsArray;
|
||||
// values[3] = devArray;
|
||||
values[3] = cfdev;
|
||||
values[4] = prefixArray;
|
||||
|
||||
|
||||
CFDictionaryRef dict = CFDictionaryCreate(NULL,
|
||||
(const void**)keys, (const void**)values, SIZE, &kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
// CFDictionaryApplyFunction(dict, printKeys, NULL);
|
||||
|
||||
CFArrayRef list = SCDynamicStoreCopyKeyList(ds, key);
|
||||
CFIndex i = 0, j = CFArrayGetCount(list);
|
||||
bool addrsChanged = true;
|
||||
CFPropertyListRef oldAddrs = NULL;
|
||||
|
||||
bool ret = TRUE;
|
||||
if (j > 0) {
|
||||
oldAddrs = SCDynamicStoreCopyValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i));
|
||||
addrsChanged = !CFEqual(oldAddrs,dict);
|
||||
}
|
||||
if (addrsChanged) {
|
||||
if (j <= 0) {
|
||||
ret &= SCDynamicStoreAddValue(ds, key, dict);
|
||||
} else {
|
||||
ret &= SCDynamicStoreSetValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i), dict);
|
||||
}
|
||||
if (!ret) {
|
||||
fprintf(stderr, "Error writing IPv6 configuration\n");
|
||||
}
|
||||
}
|
||||
if (oldAddrs != NULL) {
|
||||
CFRelease(oldAddrs);
|
||||
}
|
||||
|
||||
CFRelease(cfaddr);
|
||||
CFRelease(cfprefixes);
|
||||
CFRelease(cfdestaddrs);
|
||||
CFRelease(cfflags);
|
||||
|
||||
CFRelease(addrArray);
|
||||
CFRelease(prefixArray);
|
||||
CFRelease(destArray);
|
||||
CFRelease(flagsArray);
|
||||
CFRelease(cfdev);
|
||||
|
||||
CFRelease(ds);
|
||||
CFRelease(key);
|
||||
|
||||
// for (unsigned int i = 0; i < SIZE; ++i) {
|
||||
// values[i] = NULL;
|
||||
// }
|
||||
|
||||
CFRelease(list);
|
||||
CFRelease(dict);
|
||||
|
||||
return ret;
|
||||
}
|
||||
bool MacDNSHelper::removeIps6(uint64_t nwid)
|
||||
{
|
||||
SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
|
||||
|
||||
char buf[256] = {0};
|
||||
sprintf(buf, "State:/Network/Service/%.16llx/IPv6", nwid);
|
||||
CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
|
||||
bool res = SCDynamicStoreRemoveValue(ds, key);
|
||||
CFRelease(key);
|
||||
CFRelease(ds);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool MacDNSHelper::removeIps4(uint64_t nwid)
|
||||
{
|
||||
SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
|
||||
|
||||
char buf[256] = {0};
|
||||
sprintf(buf, "State:/Network/Service/%.16llx/IPv4", nwid);
|
||||
CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
|
||||
bool res = SCDynamicStoreRemoveValue(ds, key);
|
||||
CFRelease(key);
|
||||
CFRelease(ds);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* 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 "../node/Constants.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/Dictionary.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "MacEthernetTap.hpp"
|
||||
#include "MacEthernetTapAgent.h"
|
||||
#include "MacDNSHelper.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
|
||||
|
||||
#define MACOS_FETH_MAX_MTU_SYSCTL "net.link.fake.max_mtu"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static Mutex globalTapCreateLock;
|
||||
static bool globalTapInitialized = false;
|
||||
static bool fethMaxMtuAdjusted = false;
|
||||
|
||||
MacEthernetTap::MacEthernetTap(
|
||||
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 *data,unsigned int len),
|
||||
void *arg) :
|
||||
_handler(handler),
|
||||
_arg(arg),
|
||||
_nwid(nwid),
|
||||
_homePath(homePath),
|
||||
_mtu(mtu),
|
||||
_metric(metric),
|
||||
_devNo(0),
|
||||
_agentStdin(-1),
|
||||
_agentStdout(-1),
|
||||
_agentStderr(-1),
|
||||
_agentStdin2(-1),
|
||||
_agentStdout2(-1),
|
||||
_agentStderr2(-1),
|
||||
_agentPid(-1),
|
||||
_enabled(true),
|
||||
_lastIfAddrsUpdate(0)
|
||||
{
|
||||
char ethaddr[64],mtustr[16],devnostr[16],devstr[16],metricstr[16];
|
||||
OSUtils::ztsnprintf(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]);
|
||||
OSUtils::ztsnprintf(mtustr,sizeof(mtustr),"%u",mtu);
|
||||
OSUtils::ztsnprintf(metricstr,sizeof(metricstr),"%u",metric);
|
||||
|
||||
std::string agentPath(homePath);
|
||||
agentPath.push_back(ZT_PATH_SEPARATOR);
|
||||
agentPath.append("MacEthernetTapAgent");
|
||||
if (!OSUtils::fileExists(agentPath.c_str()))
|
||||
throw std::runtime_error("MacEthernetTapAgent not present in ZeroTier home");
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock); // only make one at a time
|
||||
|
||||
if (!fethMaxMtuAdjusted) {
|
||||
fethMaxMtuAdjusted = true;
|
||||
int old_mtu = 0;
|
||||
size_t old_mtu_len = sizeof(old_mtu);
|
||||
int mtu = 10000;
|
||||
sysctlbyname(MACOS_FETH_MAX_MTU_SYSCTL, &old_mtu, &old_mtu_len, &mtu, sizeof(mtu));
|
||||
}
|
||||
|
||||
// Destroy all feth devices on first tap start in case ZeroTier did not exit cleanly last time.
|
||||
// We leave interfaces less than feth100 alone in case something else is messing with feth devices.
|
||||
if (!globalTapInitialized) {
|
||||
globalTapInitialized = true;
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
std::set<std::string> deleted;
|
||||
if (!getifaddrs(&ifa)) {
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
int nameLen = (int)strlen(p->ifa_name);
|
||||
// Delete feth# from feth0 to feth9999, but don't touch >10000.
|
||||
if ((!strncmp(p->ifa_name,"feth",4))&&(nameLen >= 5)&&(nameLen <= 8)&&(deleted.count(std::string(p->ifa_name)) == 0)) {
|
||||
deleted.insert(std::string(p->ifa_name));
|
||||
const char *args[4];
|
||||
args[0] = "/sbin/ifconfig";
|
||||
args[1] = p->ifa_name;
|
||||
args[2] = "destroy";
|
||||
args[3] = (char *)0;
|
||||
const pid_t pid = vfork();
|
||||
if (pid == 0) {
|
||||
execv(args[0],const_cast<char **>(args));
|
||||
_exit(-1);
|
||||
} else if (pid > 0) {
|
||||
int rv = 0;
|
||||
waitpid(pid,&rv,0);
|
||||
}
|
||||
}
|
||||
p = p->ifa_next;
|
||||
}
|
||||
freeifaddrs(ifa);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int devNo = 100 + ((nwid ^ (nwid >> 32) ^ (nwid >> 48)) % 4900);
|
||||
for(;;) {
|
||||
OSUtils::ztsnprintf(devnostr,sizeof(devnostr),"%u",devNo);
|
||||
OSUtils::ztsnprintf(devstr,sizeof(devstr),"feth%u",devNo);
|
||||
bool duplicate = false;
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
if (!getifaddrs(&ifa)) {
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
if (!strcmp(p->ifa_name,devstr)) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
p = p->ifa_next;
|
||||
}
|
||||
freeifaddrs(ifa);
|
||||
}
|
||||
if (duplicate) {
|
||||
devNo = (devNo + 1) % 5000;
|
||||
if (devNo < 100)
|
||||
devNo = 100;
|
||||
} else {
|
||||
_dev = devstr;
|
||||
_devNo = devNo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (::pipe(_shutdownSignalPipe))
|
||||
throw std::runtime_error("pipe creation failed");
|
||||
|
||||
int agentStdin[2];
|
||||
int agentStdout[2];
|
||||
int agentStderr[2];
|
||||
if (::pipe(agentStdin))
|
||||
throw std::runtime_error("pipe creation failed");
|
||||
if (::pipe(agentStdout))
|
||||
throw std::runtime_error("pipe creation failed");
|
||||
if (::pipe(agentStderr))
|
||||
throw std::runtime_error("pipe creation failed");
|
||||
_agentStdin = agentStdin[1];
|
||||
_agentStdout = agentStdout[0];
|
||||
_agentStderr = agentStderr[0];
|
||||
_agentStdin2 = agentStdin[0];
|
||||
_agentStdout2 = agentStdout[1];
|
||||
_agentStderr2 = agentStderr[1];
|
||||
long apid = (long)fork();
|
||||
if (apid < 0) {
|
||||
throw std::runtime_error("fork failed");
|
||||
} else if (apid == 0) {
|
||||
::dup2(agentStdin[0],STDIN_FILENO);
|
||||
::dup2(agentStdout[1],STDOUT_FILENO);
|
||||
::dup2(agentStderr[1],STDERR_FILENO);
|
||||
::close(agentStdin[0]);
|
||||
::close(agentStdin[1]);
|
||||
::close(agentStdout[0]);
|
||||
::close(agentStdout[1]);
|
||||
::close(agentStderr[0]);
|
||||
::close(agentStderr[1]);
|
||||
::execl(agentPath.c_str(),agentPath.c_str(),devnostr,ethaddr,mtustr,metricstr,(char *)0);
|
||||
::_exit(-1);
|
||||
} else {
|
||||
_agentPid = apid;
|
||||
|
||||
// Wait up to 10 seconds for the subprocess to actually create the device. This prevents
|
||||
// things like routes from being created before the device exists.
|
||||
for(int waitLoops=0;;++waitLoops) {
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
if (!getifaddrs(&ifa)) {
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
if ((p->ifa_name)&&(!strcmp(devstr, p->ifa_name))) {
|
||||
waitLoops = -1;
|
||||
break;
|
||||
}
|
||||
p = p->ifa_next;
|
||||
}
|
||||
freeifaddrs(ifa);
|
||||
}
|
||||
if (waitLoops == -1) {
|
||||
break;
|
||||
} else if (waitLoops >= 100) { // 10 seconds
|
||||
throw std::runtime_error("feth device creation timed out");
|
||||
}
|
||||
Thread::sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
_thread = Thread::start(this);
|
||||
}
|
||||
|
||||
MacEthernetTap::~MacEthernetTap()
|
||||
{
|
||||
char tmp[64];
|
||||
const char *args[4];
|
||||
pid_t pid0,pid1;
|
||||
|
||||
MacDNSHelper::removeDNS(_nwid);
|
||||
MacDNSHelper::removeIps4(_nwid);
|
||||
MacDNSHelper::removeIps6(_nwid);
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit
|
||||
|
||||
int ec = 0;
|
||||
::kill(_agentPid,SIGKILL);
|
||||
::waitpid(_agentPid,&ec,0);
|
||||
|
||||
args[0] = "/sbin/ifconfig";
|
||||
args[1] = _dev.c_str();
|
||||
args[2] = "destroy";
|
||||
args[3] = (char *)0;
|
||||
pid0 = vfork();
|
||||
if (pid0 == 0) {
|
||||
execv(args[0],const_cast<char **>(args));
|
||||
_exit(-1);
|
||||
}
|
||||
|
||||
snprintf(tmp,sizeof(tmp),"feth%u",_devNo + 5000);
|
||||
//args[0] = "/sbin/ifconfig";
|
||||
args[1] = tmp;
|
||||
//args[2] = "destroy";
|
||||
//args[3] = (char *)0;
|
||||
pid1 = vfork();
|
||||
if (pid1 == 0) {
|
||||
execv(args[0],const_cast<char **>(args));
|
||||
_exit(-1);
|
||||
}
|
||||
|
||||
if (pid0 > 0) {
|
||||
int rv = 0;
|
||||
waitpid(pid0,&rv,0);
|
||||
}
|
||||
if (pid1 > 0) {
|
||||
int rv = 0;
|
||||
waitpid(pid1,&rv,0);
|
||||
}
|
||||
|
||||
Thread::join(_thread);
|
||||
}
|
||||
|
||||
void MacEthernetTap::setEnabled(bool en) { _enabled = en; }
|
||||
bool MacEthernetTap::enabled() const { return _enabled; }
|
||||
|
||||
bool MacEthernetTap::addIp(const InetAddress &ip)
|
||||
{
|
||||
char tmp[128];
|
||||
|
||||
if (!ip)
|
||||
return false;
|
||||
|
||||
std::string cmd;
|
||||
cmd.push_back((char)ZT_MACETHERNETTAPAGENT_STDIN_CMD_IFCONFIG);
|
||||
cmd.append((ip.ss_family == AF_INET6) ? "inet6" : "inet");
|
||||
cmd.push_back(0);
|
||||
cmd.append(ip.toString(tmp));
|
||||
cmd.push_back(0);
|
||||
cmd.append("alias");
|
||||
cmd.push_back(0);
|
||||
|
||||
uint16_t l = (uint16_t)cmd.length();
|
||||
_putLock.lock();
|
||||
write(_agentStdin,&l,2);
|
||||
write(_agentStdin,cmd.data(),cmd.length());
|
||||
_putLock.unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacEthernetTap::removeIp(const InetAddress &ip)
|
||||
{
|
||||
char tmp[128];
|
||||
|
||||
if (!ip)
|
||||
return false;
|
||||
|
||||
std::string cmd;
|
||||
cmd.push_back((char)ZT_MACETHERNETTAPAGENT_STDIN_CMD_IFCONFIG);
|
||||
cmd.append((ip.ss_family == AF_INET6) ? "inet6" : "inet");
|
||||
cmd.push_back(0);
|
||||
cmd.append(ip.toString(tmp));
|
||||
cmd.push_back(0);
|
||||
cmd.append("-alias");
|
||||
cmd.push_back(0);
|
||||
|
||||
uint16_t l = (uint16_t)cmd.length();
|
||||
_putLock.lock();
|
||||
write(_agentStdin,&l,2);
|
||||
write(_agentStdin,cmd.data(),cmd.length());
|
||||
_putLock.unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<InetAddress> MacEthernetTap::ips() const
|
||||
{
|
||||
uint64_t now = OSUtils::now();
|
||||
|
||||
if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) {
|
||||
return _ifaddrs;
|
||||
}
|
||||
_lastIfAddrsUpdate = now;
|
||||
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
std::vector<InetAddress> r;
|
||||
|
||||
if (!getifaddrs(&ifa)) {
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
if ((p->ifa_name)&&(!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)) {
|
||||
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;
|
||||
}
|
||||
freeifaddrs(ifa);
|
||||
}
|
||||
std::sort(r.begin(),r.end());
|
||||
r.erase(std::unique(r.begin(),r.end()),r.end());
|
||||
|
||||
_ifaddrs = r;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void MacEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len)
|
||||
{
|
||||
struct iovec iov[3];
|
||||
unsigned char hdr[15];
|
||||
uint16_t l;
|
||||
if ((_agentStdin > 0)&&(len <= _mtu)&&(_enabled)) {
|
||||
hdr[0] = ZT_MACETHERNETTAPAGENT_STDIN_CMD_PACKET;
|
||||
to.copyTo(hdr + 1,6);
|
||||
from.copyTo(hdr + 7,6);
|
||||
hdr[13] = (unsigned char)((etherType >> 8) & 0xff);
|
||||
hdr[14] = (unsigned char)(etherType & 0xff);
|
||||
l = (uint16_t)(len + 15);
|
||||
iov[0].iov_base = &l;
|
||||
iov[0].iov_len = 2;
|
||||
iov[1].iov_base = hdr;
|
||||
iov[1].iov_len = 15;
|
||||
iov[2].iov_base = const_cast<void *>(data);
|
||||
iov[2].iov_len = len;
|
||||
_putLock.lock();
|
||||
writev(_agentStdin,iov,3);
|
||||
_putLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
std::string MacEthernetTap::deviceName() const { return _dev; }
|
||||
void MacEthernetTap::setFriendlyName(const char *friendlyName) {}
|
||||
|
||||
void MacEthernetTap::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());
|
||||
newGroups.erase(std::unique(newGroups.begin(),newGroups.end()),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);
|
||||
}
|
||||
|
||||
void MacEthernetTap::setMtu(unsigned int mtu)
|
||||
{
|
||||
if (_mtu != mtu) {
|
||||
char tmp[16];
|
||||
std::string cmd;
|
||||
cmd.push_back((char)ZT_MACETHERNETTAPAGENT_STDIN_CMD_IFCONFIG);
|
||||
cmd.append("mtu");
|
||||
cmd.push_back(0);
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
|
||||
cmd.append(tmp);
|
||||
cmd.push_back(0);
|
||||
uint16_t l = (uint16_t)cmd.length();
|
||||
_putLock.lock();
|
||||
write(_agentStdin,&l,2);
|
||||
write(_agentStdin,cmd.data(),cmd.length());
|
||||
_putLock.unlock();
|
||||
_mtu = mtu;
|
||||
}
|
||||
}
|
||||
|
||||
#define ZT_MACETHERNETTAP_AGENT_READ_BUF_SIZE 131072
|
||||
|
||||
void MacEthernetTap::threadMain()
|
||||
throw()
|
||||
{
|
||||
char agentReadBuf[ZT_MACETHERNETTAP_AGENT_READ_BUF_SIZE];
|
||||
char agentStderrBuf[256];
|
||||
fd_set readfds,nullfds;
|
||||
MAC to,from;
|
||||
|
||||
Thread::sleep(250);
|
||||
|
||||
const int nfds = std::max(std::max(_shutdownSignalPipe[0],_agentStdout),_agentStderr) + 1;
|
||||
long agentReadPtr = 0;
|
||||
fcntl(_agentStdout,F_SETFL,fcntl(_agentStdout,F_GETFL)|O_NONBLOCK);
|
||||
fcntl(_agentStderr,F_SETFL,fcntl(_agentStderr,F_GETFL)|O_NONBLOCK);
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_ZERO(&nullfds);
|
||||
for(;;) {
|
||||
FD_SET(_shutdownSignalPipe[0],&readfds);
|
||||
FD_SET(_agentStdout,&readfds);
|
||||
FD_SET(_agentStderr,&readfds);
|
||||
select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0);
|
||||
|
||||
if (FD_ISSET(_shutdownSignalPipe[0],&readfds))
|
||||
break;
|
||||
|
||||
if (FD_ISSET(_agentStdout,&readfds)) {
|
||||
long n = (long)read(_agentStdout,agentReadBuf + agentReadPtr,ZT_MACETHERNETTAP_AGENT_READ_BUF_SIZE - agentReadPtr);
|
||||
if (n > 0) {
|
||||
agentReadPtr += n;
|
||||
while (agentReadPtr >= 2) {
|
||||
long len = *((uint16_t *)agentReadBuf);
|
||||
if (agentReadPtr >= (len + 2)) {
|
||||
char *msg = agentReadBuf + 2;
|
||||
|
||||
if ((len > 14)&&(_enabled)) {
|
||||
to.setTo(msg,6);
|
||||
from.setTo(msg + 6,6);
|
||||
_handler(_arg,(void *)0,_nwid,from,to,ntohs(((const uint16_t *)msg)[6]),0,(const void *)(msg + 14),(unsigned int)len - 14);
|
||||
}
|
||||
|
||||
if (agentReadPtr > (len + 2)) {
|
||||
memmove(agentReadBuf,agentReadBuf + len + 2,agentReadPtr -= (len + 2));
|
||||
} else {
|
||||
agentReadPtr = 0;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(_agentStderr,&readfds)) {
|
||||
read(_agentStderr,agentStderrBuf,sizeof(agentStderrBuf));
|
||||
/*
|
||||
const ssize_t n = read(_agentStderr,agentStderrBuf,sizeof(agentStderrBuf));
|
||||
if (n > 0)
|
||||
write(STDERR_FILENO,agentStderrBuf,(size_t)n);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
::close(_agentStdin);
|
||||
::close(_agentStdout);
|
||||
::close(_agentStderr);
|
||||
::close(_agentStdin2);
|
||||
::close(_agentStdout2);
|
||||
::close(_agentStderr2);
|
||||
::close(_shutdownSignalPipe[0]);
|
||||
::close(_shutdownSignalPipe[1]);
|
||||
}
|
||||
|
||||
void MacEthernetTap::setDns(const char *domain, const std::vector<InetAddress> &servers)
|
||||
{
|
||||
MacDNSHelper::setDNS(this->_nwid, domain, servers);
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_OSXETHERNETTAP_HPP
|
||||
#define ZT_OSXETHERNETTAP_HPP
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "EthernetTap.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class MacEthernetTap : public EthernetTap
|
||||
{
|
||||
public:
|
||||
MacEthernetTap(
|
||||
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 ~MacEthernetTap();
|
||||
|
||||
virtual void setEnabled(bool en);
|
||||
virtual bool enabled() const;
|
||||
virtual bool addIp(const InetAddress &ip);
|
||||
virtual bool removeIp(const InetAddress &ip);
|
||||
virtual std::vector<InetAddress> 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<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
virtual void setMtu(unsigned int mtu);
|
||||
virtual void setDns(const char *domain, const std::vector<InetAddress> &servers);
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
private:
|
||||
void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
uint64_t _nwid;
|
||||
Thread _thread;
|
||||
std::string _homePath;
|
||||
std::string _dev;
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
Mutex _putLock;
|
||||
unsigned int _mtu;
|
||||
unsigned int _metric;
|
||||
unsigned int _devNo;
|
||||
int _shutdownSignalPipe[2];
|
||||
int _agentStdin,_agentStdout,_agentStderr,_agentStdin2,_agentStdout2,_agentStderr2;
|
||||
long _agentPid;
|
||||
volatile bool _enabled;
|
||||
mutable std::vector<InetAddress> _ifaddrs;
|
||||
mutable uint64_t _lastIfAddrsUpdate;
|
||||
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
/*
|
||||
* This creates a pair of feth devices with the lower numbered device
|
||||
* being the ZeroTier virtual interface and the other being the device
|
||||
* used to actually read and write packets. The latter gets no IP config
|
||||
* and is only used for I/O. The behavior of feth is similar to the
|
||||
* veth pairs that exist on Linux.
|
||||
*
|
||||
* The feth device has only existed since MacOS Sierra, but that's fairly
|
||||
* long ago in Mac terms.
|
||||
*
|
||||
* I/O with feth must be done using two different sockets. The BPF socket
|
||||
* is used to receive packets, while an AF_NDRV (low-level network driver
|
||||
* access) socket must be used to inject. AF_NDRV can't read IP frames
|
||||
* since BSD doesn't forward packets out the NDRV tap if they've already
|
||||
* been handled, and while BPF can inject its MTU for injected packets
|
||||
* is limited to 2048. AF_NDRV packet injection is required to inject
|
||||
* ZeroTier's large MTU frames.
|
||||
*
|
||||
* All this stuff is basically undocumented. A lot of tracing through
|
||||
* the Darwin/XNU kernel source was required to figure out how to make
|
||||
* this actually work.
|
||||
*
|
||||
* We hope to develop a DriverKit-based driver in the near-mid future to
|
||||
* replace this weird hack, but it works for now through Big Sur in our
|
||||
* testing.
|
||||
*
|
||||
* See also:
|
||||
*
|
||||
* https://apple.stackexchange.com/questions/337715/fake-ethernet-interfaces-feth-if-fake-anyone-ever-seen-this
|
||||
* https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/net/if_fake.c.auto.html
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/bpf.h>
|
||||
#include <net/route.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <net/ndrv.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet6/in6_var.h>
|
||||
#include <netinet6/nd6.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
#include "../version.h"
|
||||
#include "MacEthernetTapAgent.h"
|
||||
|
||||
#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
|
||||
|
||||
#define P_IFCONFIG "/sbin/ifconfig"
|
||||
|
||||
static unsigned char s_pktReadBuf[131072] __attribute__ ((__aligned__(16)));
|
||||
static unsigned char s_stdinReadBuf[131072] __attribute__ ((__aligned__(16)));
|
||||
static char s_deviceName[IFNAMSIZ];
|
||||
static char s_peerDeviceName[IFNAMSIZ];
|
||||
static int s_bpffd = -1;
|
||||
static int s_ndrvfd = -1;
|
||||
static pid_t s_parentPid;
|
||||
|
||||
static void configureIpv6Parameters(const char *ifname,int performNUD,int acceptRouterAdverts)
|
||||
{
|
||||
struct in6_ndireq nd;
|
||||
struct in6_ifreq ifr;
|
||||
|
||||
int s = socket(AF_INET6,SOCK_DGRAM,0);
|
||||
if (s <= 0)
|
||||
return;
|
||||
|
||||
memset(&nd,0,sizeof(nd));
|
||||
strncpy(nd.ifname,ifname,sizeof(nd.ifname));
|
||||
|
||||
if (ioctl(s,SIOCGIFINFO_IN6,&nd)) {
|
||||
close(s);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
close(s);
|
||||
}
|
||||
|
||||
static int run(const char *path,...)
|
||||
{
|
||||
va_list ap;
|
||||
char *args[16];
|
||||
int argNo = 1;
|
||||
|
||||
va_start(ap,path);
|
||||
args[0] = (char *)path;
|
||||
for(;argNo<15;++argNo) {
|
||||
args[argNo] = va_arg(ap,char *);
|
||||
if (!args[argNo]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
args[argNo++] = (char *)0;
|
||||
va_end(ap);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
return -1;
|
||||
} else if (pid == 0) {
|
||||
dup2(STDERR_FILENO,STDOUT_FILENO);
|
||||
execv(args[0],args);
|
||||
_exit(-1);
|
||||
}
|
||||
int rv = 0;
|
||||
waitpid(pid,&rv,0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void die()
|
||||
{
|
||||
if (s_ndrvfd >= 0)
|
||||
close(s_ndrvfd);
|
||||
if (s_bpffd >= 0)
|
||||
close(s_bpffd);
|
||||
if (s_peerDeviceName[0])
|
||||
run("/sbin/ifconfig",s_peerDeviceName,"destroy",(char *)0);
|
||||
if (s_deviceName[0])
|
||||
run("/sbin/ifconfig",s_deviceName,"destroy",(char *)0);
|
||||
}
|
||||
|
||||
static inline void close_inherited_fds()
|
||||
{
|
||||
struct rlimit lim;
|
||||
getrlimit(RLIMIT_NOFILE, &lim);
|
||||
for (int i=3,j=(int)lim.rlim_cur;i<j;++i)
|
||||
close(i);
|
||||
}
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
char buf[128];
|
||||
struct ifreq ifr;
|
||||
u_int fl;
|
||||
fd_set rfds,wfds,efds;
|
||||
struct iovec iov[2];
|
||||
|
||||
s_deviceName[0] = 0;
|
||||
s_peerDeviceName[0] = 0;
|
||||
s_parentPid = getppid();
|
||||
|
||||
atexit(&die);
|
||||
signal(SIGIO,SIG_IGN);
|
||||
signal(SIGCHLD,SIG_IGN);
|
||||
signal(SIGPIPE,SIG_IGN);
|
||||
signal(SIGUSR1,SIG_IGN);
|
||||
signal(SIGUSR2,SIG_IGN);
|
||||
signal(SIGALRM,SIG_IGN);
|
||||
signal(SIGQUIT,&exit);
|
||||
signal(SIGTERM,&exit);
|
||||
signal(SIGKILL,&exit);
|
||||
signal(SIGINT,&exit);
|
||||
signal(SIGPIPE,&exit);
|
||||
|
||||
close_inherited_fds();
|
||||
|
||||
if (getuid() != 0) {
|
||||
if (setuid(0) != 0) {
|
||||
fprintf(stderr,"E must be run as root or with root setuid bit on executable\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_INVALID_REQUEST;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc < 5) {
|
||||
fprintf(stderr,"E invalid or missing argument(s) (usage: MacEthernetTapAgent <0-4999> <mac> <mtu> <metric>)\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_INVALID_REQUEST;
|
||||
}
|
||||
const int deviceNo = atoi(argv[1]);
|
||||
if ((deviceNo < 0)||(deviceNo > 4999)) {
|
||||
fprintf(stderr,"E invalid or missing argument(s) (usage: MacEthernetTapAgent <0-4999> <mac> <mtu> <metric>)\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_INVALID_REQUEST;
|
||||
}
|
||||
const char *mac = argv[2];
|
||||
const char *mtu = argv[3];
|
||||
const char *metric = argv[4];
|
||||
|
||||
s_ndrvfd = socket(AF_NDRV,SOCK_RAW,0);
|
||||
if (s_ndrvfd < 0) {
|
||||
fprintf(stderr,"E unable to open AF_NDRV socket\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
|
||||
snprintf(s_deviceName,sizeof(s_deviceName),"feth%d",deviceNo);
|
||||
snprintf(s_peerDeviceName,sizeof(s_peerDeviceName),"feth%d",deviceNo+5000);
|
||||
if (run(P_IFCONFIG,s_peerDeviceName,"create",(char *)0) != 0) {
|
||||
fprintf(stderr,"E unable to create %s\n",s_deviceName);
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
usleep(10);
|
||||
if (run(P_IFCONFIG,s_deviceName,"create",(char *)0) != 0) {
|
||||
fprintf(stderr,"E unable to create %s\n",s_deviceName);
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
run(P_IFCONFIG,s_deviceName,"lladdr",mac,(char *)0);
|
||||
usleep(10);
|
||||
run(P_IFCONFIG,s_peerDeviceName,"peer",s_deviceName,(char *)0);
|
||||
usleep(10);
|
||||
run(P_IFCONFIG,s_peerDeviceName,"mtu",mtu,"up",(char *)0);
|
||||
usleep(10);
|
||||
run(P_IFCONFIG,s_deviceName,"mtu",mtu,"metric",metric,"up",(char *)0);
|
||||
usleep(10);
|
||||
configureIpv6Parameters(s_deviceName,1,0);
|
||||
usleep(10);
|
||||
|
||||
struct sockaddr_ndrv nd;
|
||||
nd.snd_len = sizeof(struct sockaddr_ndrv);
|
||||
nd.snd_family = AF_NDRV;
|
||||
memcpy(nd.snd_name,s_peerDeviceName,sizeof(nd.snd_name));
|
||||
if (bind(s_ndrvfd,(struct sockaddr *)&nd,sizeof(nd)) != 0) {
|
||||
fprintf(stderr,"E unable to bind AF_NDRV socket\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
if (connect(s_ndrvfd,(struct sockaddr *)&nd,sizeof(nd)) != 0) {
|
||||
fprintf(stderr,"E unable to connect AF_NDRV socket\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
|
||||
/* Start at /dev/bpf1 since some simple bpf-using net utilities hard-code /dev/bpf0.
|
||||
* Things like libpcap are smart enough to search. */
|
||||
for(int bpfno=1;bpfno<5000;++bpfno) {
|
||||
char tmp[32];
|
||||
snprintf(tmp,sizeof(tmp),"/dev/bpf%d",bpfno);
|
||||
s_bpffd = open(tmp,O_RDWR);
|
||||
if (s_bpffd >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s_bpffd < 0) {
|
||||
fprintf(stderr,"E unable to open bpf device\n");
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
|
||||
fl = sizeof(s_pktReadBuf);
|
||||
if (ioctl(s_bpffd,BIOCSBLEN,&fl) != 0) {
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
const size_t readPktSize = (size_t)fl;
|
||||
fl = 1;
|
||||
if (ioctl(s_bpffd,BIOCIMMEDIATE,&fl) != 0) {
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
fl = 0;
|
||||
if (ioctl(s_bpffd,BIOCSSEESENT,&fl) != 0) {
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
memset(&ifr,0,sizeof(ifr));
|
||||
memcpy(ifr.ifr_name,s_peerDeviceName,IFNAMSIZ);
|
||||
if (ioctl(s_bpffd,BIOCSETIF,&ifr) != 0) {
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
fl = 1;
|
||||
if (ioctl(s_bpffd,BIOCSHDRCMPLT,&fl) != 0) {
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
fl = 1;
|
||||
if (ioctl(s_bpffd,BIOCPROMISC,&fl) != 0) {
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
|
||||
}
|
||||
|
||||
fprintf(stderr,"I %s %s %d.%d.%d.%d\n",s_deviceName,s_peerDeviceName,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION,ZEROTIER_ONE_VERSION_BUILD);
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_ZERO(&wfds);
|
||||
FD_ZERO(&efds);
|
||||
long stdinReadPtr = 0;
|
||||
for(;;) {
|
||||
FD_SET(STDIN_FILENO,&rfds);
|
||||
FD_SET(s_bpffd,&rfds);
|
||||
if (select(s_bpffd+1,&rfds,&wfds,&efds,(struct timeval *)0) < 0) {
|
||||
if ((errno == EAGAIN)||(errno == EINTR)) {
|
||||
usleep(10);
|
||||
continue;
|
||||
}
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_READ_ERROR;
|
||||
}
|
||||
|
||||
if (FD_ISSET(s_bpffd,&rfds)) {
|
||||
long n = (long)read(s_bpffd,s_pktReadBuf,readPktSize);
|
||||
if (n > 0) {
|
||||
for(unsigned char *p=s_pktReadBuf,*eof=p+n;p<eof;) {
|
||||
struct bpf_hdr *h = (struct bpf_hdr *)p;
|
||||
if ((h->bh_caplen > 0)&&((p + h->bh_hdrlen + h->bh_caplen) <= eof)) {
|
||||
uint16_t len = (uint16_t)h->bh_caplen;
|
||||
iov[0].iov_base = &len;
|
||||
iov[0].iov_len = 2;
|
||||
iov[1].iov_base = p + h->bh_hdrlen;
|
||||
iov[1].iov_len = h->bh_caplen;
|
||||
writev(STDOUT_FILENO,iov,2);
|
||||
}
|
||||
p += BPF_WORDALIGN(h->bh_hdrlen + h->bh_caplen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(STDIN_FILENO,&rfds)) {
|
||||
long n = (long)read(STDIN_FILENO,s_stdinReadBuf + stdinReadPtr,sizeof(s_stdinReadBuf) - stdinReadPtr);
|
||||
if (n > 0) {
|
||||
stdinReadPtr += n;
|
||||
while (stdinReadPtr >= 2) {
|
||||
long len = *((uint16_t *)s_stdinReadBuf);
|
||||
if (stdinReadPtr >= (len + 2)) {
|
||||
if (len > 0) {
|
||||
unsigned char *msg = s_stdinReadBuf + 2;
|
||||
|
||||
switch(msg[0]) {
|
||||
case ZT_MACETHERNETTAPAGENT_STDIN_CMD_PACKET:
|
||||
if (len > 1) {
|
||||
if (write(s_ndrvfd,msg+1,len-1) < 0) {
|
||||
fprintf(stderr,"E inject failed size==%ld errno==%d\n",len-1,errno);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ZT_MACETHERNETTAPAGENT_STDIN_CMD_IFCONFIG: {
|
||||
char *args[16];
|
||||
args[0] = P_IFCONFIG;
|
||||
args[1] = s_deviceName;
|
||||
int argNo = 2;
|
||||
for(int argPtr=0,k=1,l=(int)len;k<l;++k) {
|
||||
if (!msg[k]) {
|
||||
if (argPtr > 0) {
|
||||
argPtr = 0;
|
||||
++argNo;
|
||||
if (argNo >= 15) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (argPtr == 0) {
|
||||
args[argNo] = (char *)(msg + k);
|
||||
}
|
||||
argPtr++;
|
||||
}
|
||||
}
|
||||
args[argNo] = (char *)0;
|
||||
if (argNo > 2) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
return -1;
|
||||
} else if (pid == 0) {
|
||||
dup2(STDERR_FILENO,STDOUT_FILENO);
|
||||
execv(args[0],args);
|
||||
_exit(-1);
|
||||
}
|
||||
int rv = 0;
|
||||
waitpid(pid,&rv,0);
|
||||
}
|
||||
} break;
|
||||
|
||||
case ZT_MACETHERNETTAPAGENT_STDIN_CMD_EXIT:
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_SUCCESS;
|
||||
|
||||
default:
|
||||
fprintf(stderr,"E unrecognized message type over pipe from host process: %d (length: %d)\n",(int)msg[0],(int)len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stdinReadPtr > (len + 2)) {
|
||||
memmove(s_stdinReadBuf,s_stdinReadBuf + len + 2,stdinReadPtr -= (len + 2));
|
||||
} else {
|
||||
stdinReadPtr = 0;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c)2019 BackOne, 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_MACETHERNETTAPAGENT_H
|
||||
#define ZT_MACETHERNETTAPAGENT_H
|
||||
|
||||
#define ZT_MACETHERNETTAPAGENT_EXIT_CODE_SUCCESS 0
|
||||
#define ZT_MACETHERNETTAPAGENT_EXIT_CODE_INVALID_REQUEST -1
|
||||
#define ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE -2
|
||||
#define ZT_MACETHERNETTAPAGENT_EXIT_CODE_READ_ERROR -3
|
||||
|
||||
#define ZT_MACETHERNETTAPAGENT_STDIN_CMD_PACKET 0
|
||||
#define ZT_MACETHERNETTAPAGENT_STDIN_CMD_IFCONFIG 1
|
||||
#define ZT_MACETHERNETTAPAGENT_STDIN_CMD_EXIT 2
|
||||
|
||||
#define ZT_MACETHERNETTAPAGENT_DEFAULT_SYSTEM_PATH "/Library/Application Support/BackOne/MacEthernetTapAgent"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <netinet6/in6_var.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <netinet/icmp6.h>
|
||||
|
||||
#include "MacDNSHelper.hpp"
|
||||
|
||||
// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!?
|
||||
struct prf_ra {
|
||||
u_char onlink : 1;
|
||||
u_char autonomous : 1;
|
||||
u_char reserved : 6;
|
||||
} prf_ra;
|
||||
|
||||
#include <netinet6/nd6.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
// 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 <sys/socket.h> */
|
||||
/* 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 <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/Dictionary.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "MacKextEthernetTap.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 {
|
||||
|
||||
static long globalTapsRunning = 0;
|
||||
static Mutex globalTapCreateLock;
|
||||
|
||||
MacKextEthernetTap::MacKextEthernetTap(
|
||||
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 *data,unsigned int len),
|
||||
void *arg) :
|
||||
_handler(handler),
|
||||
_arg(arg),
|
||||
_nwid(nwid),
|
||||
_homePath(homePath),
|
||||
_mtu(mtu),
|
||||
_metric(metric),
|
||||
_fd(0),
|
||||
_enabled(true)
|
||||
{
|
||||
char devpath[64],ethaddr[64],mtustr[32],metstr[32],nwids[32];
|
||||
struct stat stattmp;
|
||||
|
||||
OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
|
||||
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
|
||||
if (::stat("/dev/zt0",&stattmp)) {
|
||||
long kextpid = (long)fork();
|
||||
if (kextpid == 0) {
|
||||
::chdir(homePath);
|
||||
OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
|
||||
::execl("/sbin/kextload","/sbin/kextload","-q","-repository",homePath,"tap.kext",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (kextpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(kextpid,&exitcode,0);
|
||||
}
|
||||
::usleep(500); // give tap device driver time to start up and try again
|
||||
if (::stat("/dev/zt0",&stattmp))
|
||||
throw std::runtime_error("/dev/zt# tap devices do not exist and cannot load tap.kext");
|
||||
}
|
||||
|
||||
// Try to reopen the last device we had, if we had one and it's still unused.
|
||||
std::map<std::string,std::string> 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<std::string,std::string>::const_iterator gdmEntry = globalDeviceMap.find(nwids);
|
||||
if (gdmEntry != globalDeviceMap.end()) {
|
||||
std::string devpath("/dev/"); devpath.append(gdmEntry->second);
|
||||
if (stat(devpath.c_str(),&stattmp) == 0) {
|
||||
_fd = ::open(devpath.c_str(),O_RDWR);
|
||||
if (_fd > 0) {
|
||||
_dev = gdmEntry->second;
|
||||
recalledDevice = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open the first unused tap device if we didn't recall a previous one.
|
||||
if (!recalledDevice) {
|
||||
for(int i=0;i<64;++i) {
|
||||
OSUtils::ztsnprintf(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];
|
||||
OSUtils::ztsnprintf(foo,sizeof(foo),"zt%d",i);
|
||||
_dev = foo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_fd <= 0)
|
||||
throw std::runtime_error("unable to open TAP device or no more devices available");
|
||||
|
||||
if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) {
|
||||
::close(_fd);
|
||||
throw std::runtime_error("unable to set flags on file descriptor for TAP device");
|
||||
}
|
||||
|
||||
// Configure MAC address and MTU, bring interface up
|
||||
OSUtils::ztsnprintf(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]);
|
||||
OSUtils::ztsnprintf(mtustr,sizeof(mtustr),"%u",_mtu);
|
||||
OSUtils::ztsnprintf(metstr,sizeof(metstr),"%u",_metric);
|
||||
long cpid = (long)fork();
|
||||
if (cpid == 0) {
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
if (exitcode) {
|
||||
::close(_fd);
|
||||
throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
|
||||
}
|
||||
}
|
||||
|
||||
_setIpv6Stuff(_dev.c_str(),true,false);
|
||||
|
||||
// Set close-on-exec so that devices cannot persist if we fork/exec for update
|
||||
fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC);
|
||||
|
||||
::pipe(_shutdownSignalPipe);
|
||||
|
||||
++globalTapsRunning;
|
||||
|
||||
globalDeviceMap[nwids] = _dev;
|
||||
devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"w");
|
||||
if (devmapf) {
|
||||
gdmEntry = globalDeviceMap.begin();
|
||||
while (gdmEntry != globalDeviceMap.end()) {
|
||||
fprintf(devmapf,"%s=%s\n",gdmEntry->first.c_str(),gdmEntry->second.c_str());
|
||||
++gdmEntry;
|
||||
}
|
||||
fclose(devmapf);
|
||||
}
|
||||
|
||||
_thread = Thread::start(this);
|
||||
}
|
||||
|
||||
MacKextEthernetTap::~MacKextEthernetTap()
|
||||
{
|
||||
MacDNSHelper::removeDNS(_nwid);
|
||||
|
||||
::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit
|
||||
Thread::join(_thread);
|
||||
for (std::thread &t : _rxThreads) {
|
||||
t.join();
|
||||
}
|
||||
::close(_fd);
|
||||
::close(_shutdownSignalPipe[0]);
|
||||
::close(_shutdownSignalPipe[1]);
|
||||
|
||||
{
|
||||
Mutex::Lock _gl(globalTapCreateLock);
|
||||
if (--globalTapsRunning <= 0) {
|
||||
globalTapsRunning = 0; // sanity check -- should not be possible
|
||||
|
||||
char tmp[16384];
|
||||
sprintf(tmp,"%s/%s",_homePath.c_str(),"tap.kext");
|
||||
long kextpid = (long)fork();
|
||||
if (kextpid == 0) {
|
||||
OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
|
||||
::execl("/sbin/kextunload","/sbin/kextunload",tmp,(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (kextpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(kextpid,&exitcode,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::setEnabled(bool en)
|
||||
{
|
||||
_enabled = en;
|
||||
// TODO: interface status change
|
||||
}
|
||||
|
||||
bool MacKextEthernetTap::enabled() const
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
bool MacKextEthernetTap::addIp(const InetAddress &ip)
|
||||
{
|
||||
if (!ip)
|
||||
return false;
|
||||
|
||||
long cpid = (long)fork();
|
||||
if (cpid == 0) {
|
||||
char tmp[128];
|
||||
::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toString(tmp),"alias",(const char *)0);
|
||||
::_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
} // else return false...
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacKextEthernetTap::removeIp(const InetAddress &ip)
|
||||
{
|
||||
if (!ip)
|
||||
return true;
|
||||
std::vector<InetAddress> allIps(ips());
|
||||
for(std::vector<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) {
|
||||
if (*i == ip) {
|
||||
long cpid = (long)fork();
|
||||
if (cpid == 0) {
|
||||
char tmp[128];
|
||||
execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toIpString(tmp),"-alias",(const char *)0);
|
||||
_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
waitpid(cpid,&exitcode,0);
|
||||
return (exitcode == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<InetAddress> MacKextEthernetTap::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());
|
||||
r.erase(std::unique(r.begin(),r.end()),r.end());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::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;
|
||||
::write(_fd,putBuf,len);
|
||||
}
|
||||
}
|
||||
|
||||
std::string MacKextEthernetTap::deviceName() const
|
||||
{
|
||||
return _dev;
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::setFriendlyName(const char *friendlyName)
|
||||
{
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
|
||||
{
|
||||
std::vector<MulticastGroup> 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.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0));
|
||||
}
|
||||
p = p->ifma_next;
|
||||
}
|
||||
_intl_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);
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::setMtu(unsigned int mtu)
|
||||
{
|
||||
if (mtu != _mtu) {
|
||||
_mtu = mtu;
|
||||
long cpid = (long)fork();
|
||||
if (cpid == 0) {
|
||||
char tmp[64];
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
|
||||
execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",tmp,(const char *)0);
|
||||
_exit(-1);
|
||||
} else if (cpid > 0) {
|
||||
int exitcode = -1;
|
||||
waitpid(cpid,&exitcode,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::threadMain()
|
||||
throw()
|
||||
{
|
||||
fd_set readfds,nullfds;
|
||||
MAC to,from;
|
||||
int n,nfds,r;
|
||||
char getBuf[ZT_MAX_MTU + 64];
|
||||
|
||||
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,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14);
|
||||
}
|
||||
|
||||
r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacKextEthernetTap::setDns(const char *domain, const std::vector<InetAddress> &servers)
|
||||
{
|
||||
MacDNSHelper::setDNS(_nwid, domain, servers);
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_MacKextEthernetTap_HPP
|
||||
#define ZT_MacKextEthernetTap_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
|
||||
#include "Thread.hpp"
|
||||
#include "EthernetTap.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class MacKextEthernetTap : public EthernetTap
|
||||
{
|
||||
public:
|
||||
MacKextEthernetTap(
|
||||
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 ~MacKextEthernetTap();
|
||||
|
||||
virtual void setEnabled(bool en);
|
||||
virtual bool enabled() const;
|
||||
virtual bool addIp(const InetAddress &ip);
|
||||
virtual bool removeIp(const InetAddress &ip);
|
||||
virtual std::vector<InetAddress> 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<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
virtual void setMtu(unsigned int mtu);
|
||||
virtual void setDns(const char *domain, const std::vector<InetAddress> &servers);
|
||||
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
private:
|
||||
void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
uint64_t _nwid;
|
||||
Thread _thread;
|
||||
std::string _homePath;
|
||||
std::string _dev;
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
unsigned int _mtu;
|
||||
unsigned int _metric;
|
||||
int _fd;
|
||||
int _shutdownSignalPipe[2];
|
||||
volatile bool _enabled;
|
||||
std::vector<std::thread> _rxThreads;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,628 @@
|
||||
/*
|
||||
* 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 "../node/Constants.hpp"
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <netioapi.h>
|
||||
#include <IPHlpApi.h>
|
||||
#endif
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
#include <unistd.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#ifndef ZT_SDK
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
#include <net/if.h>
|
||||
#ifdef __BSD__
|
||||
#include <net/if_dl.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "ManagedRoute.hpp"
|
||||
#ifdef __LINUX__
|
||||
#include "LinuxNetLink.hpp"
|
||||
#endif
|
||||
|
||||
#define ZT_BSD_ROUTE_CMD "/sbin/route"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
namespace {
|
||||
|
||||
// Fork a target into two more specific targets e.g. 0.0.0.0/0 -> 0.0.0.0/1, 128.0.0.0/1
|
||||
// If the target is already maximally-specific, 'right' will be unchanged and 'left' will be 't'
|
||||
static void _forkTarget(const InetAddress &t,InetAddress &left,InetAddress &right)
|
||||
{
|
||||
const unsigned int bits = t.netmaskBits() + 1;
|
||||
left = t;
|
||||
if (t.ss_family == AF_INET) {
|
||||
if (bits <= 32) {
|
||||
left.setPort(bits);
|
||||
right = t;
|
||||
reinterpret_cast<struct sockaddr_in *>(&right)->sin_addr.s_addr ^= Utils::hton((uint32_t)(1 << (32 - bits)));
|
||||
right.setPort(bits);
|
||||
} else {
|
||||
right.zero();
|
||||
}
|
||||
} else if (t.ss_family == AF_INET6) {
|
||||
if (bits <= 128) {
|
||||
left.setPort(bits);
|
||||
right = t;
|
||||
uint8_t *b = reinterpret_cast<uint8_t *>(reinterpret_cast<struct sockaddr_in6 *>(&right)->sin6_addr.s6_addr);
|
||||
b[bits / 8] ^= 1 << (8 - (bits % 8));
|
||||
right.setPort(bits);
|
||||
} else {
|
||||
right.zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct _RTE
|
||||
{
|
||||
InetAddress target;
|
||||
InetAddress via;
|
||||
char device[128];
|
||||
int metric;
|
||||
bool ifscope;
|
||||
bool isDefault;
|
||||
};
|
||||
|
||||
#ifdef __BSD__ // ------------------------------------------------------------
|
||||
#define ZT_ROUTING_SUPPORT_FOUND 1
|
||||
|
||||
#ifndef ZT_SDK
|
||||
static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains)
|
||||
{
|
||||
std::vector<_RTE> rtes;
|
||||
int mib[6];
|
||||
size_t needed;
|
||||
|
||||
mib[0] = CTL_NET;
|
||||
mib[1] = PF_ROUTE;
|
||||
mib[2] = 0;
|
||||
mib[3] = 0;
|
||||
mib[4] = NET_RT_DUMP;
|
||||
mib[5] = 0;
|
||||
if (!sysctl(mib,6,NULL,&needed,NULL,0)) {
|
||||
if (needed <= 0)
|
||||
return rtes;
|
||||
|
||||
char *buf = (char *)::malloc(needed);
|
||||
if (buf) {
|
||||
if (!sysctl(mib,6,buf,&needed,NULL,0)) {
|
||||
struct rt_msghdr *rtm;
|
||||
for(char *next=buf,*end=buf+needed;next<end;) {
|
||||
rtm = (struct rt_msghdr *)next;
|
||||
char *saptr = (char *)(rtm + 1);
|
||||
char *saend = next + rtm->rtm_msglen;
|
||||
|
||||
InetAddress sa_t,sa_v;
|
||||
int deviceIndex = -9999;
|
||||
bool isDefault = false;
|
||||
|
||||
if (((rtm->rtm_flags & RTF_LLINFO) == 0)&&((rtm->rtm_flags & RTF_HOST) == 0)&&((rtm->rtm_flags & RTF_UP) != 0)&&((rtm->rtm_flags & RTF_MULTICAST) == 0)) {
|
||||
int which = 0;
|
||||
while (saptr < saend) {
|
||||
struct sockaddr *sa = (struct sockaddr *)saptr;
|
||||
unsigned int salen = sa->sa_len;
|
||||
if (!salen)
|
||||
break;
|
||||
|
||||
// Skip missing fields in rtm_addrs bit field
|
||||
while ((rtm->rtm_addrs & 1) == 0) {
|
||||
rtm->rtm_addrs >>= 1;
|
||||
++which;
|
||||
if (which > 6)
|
||||
break;
|
||||
}
|
||||
if (which > 6)
|
||||
break;
|
||||
|
||||
rtm->rtm_addrs >>= 1;
|
||||
switch(which++) {
|
||||
case 0:
|
||||
//printf("RTA_DST\n");
|
||||
if (sa->sa_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
|
||||
if ((sin6->sin6_addr.s6_addr[0] == 0xfe)&&((sin6->sin6_addr.s6_addr[1] & 0xc0) == 0x80)) {
|
||||
// BSD uses this fucking strange in-band signaling method to encode device scope IDs for IPv6 addresses... probably a holdover from very early versions of the spec.
|
||||
unsigned int interfaceIndex = ((((unsigned int)sin6->sin6_addr.s6_addr[2]) << 8) & 0xff) | (((unsigned int)sin6->sin6_addr.s6_addr[3]) & 0xff);
|
||||
sin6->sin6_addr.s6_addr[2] = 0;
|
||||
sin6->sin6_addr.s6_addr[3] = 0;
|
||||
if (!sin6->sin6_scope_id)
|
||||
sin6->sin6_scope_id = interfaceIndex;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
isDefault = IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && !(rtm->rtm_flags & RTF_IFSCOPE);
|
||||
#endif
|
||||
} else {
|
||||
struct sockaddr_in *sin4 = (struct sockaddr_in *)sa;
|
||||
isDefault = sin4->sin_addr.s_addr == 0;
|
||||
}
|
||||
sa_t = *sa;
|
||||
break;
|
||||
case 1:
|
||||
//printf("RTA_GATEWAY\n");
|
||||
switch(sa->sa_family) {
|
||||
case AF_LINK:
|
||||
// deviceIndex = (int)((const struct sockaddr_dl *)sa)->sdl_index;
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
sa_v = *sa;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2: {
|
||||
//printf("RTA_NETMASK\n");
|
||||
if (sa_t.ss_family == AF_INET6) {
|
||||
salen = sizeof(struct sockaddr_in6);
|
||||
unsigned int bits = 0;
|
||||
for(int i=0;i<16;++i) {
|
||||
unsigned char c = (unsigned char)((const struct sockaddr_in6 *)sa)->sin6_addr.s6_addr[i];
|
||||
if (c == 0xff)
|
||||
bits += 8;
|
||||
else break;
|
||||
}
|
||||
sa_t.setPort(bits);
|
||||
} else if (sa_t.ss_family == AF_INET) {
|
||||
salen = sizeof(struct sockaddr_in);
|
||||
sa_t.setPort((unsigned int)Utils::countBits((uint32_t)((const struct sockaddr_in *)sa)->sin_addr.s_addr));
|
||||
}
|
||||
} break;
|
||||
/*
|
||||
case 3:
|
||||
//printf("RTA_GENMASK\n");
|
||||
break;
|
||||
case 4:
|
||||
//printf("RTA_IFP\n");
|
||||
break;
|
||||
case 5:
|
||||
//printf("RTA_IFA\n");
|
||||
break;
|
||||
case 6:
|
||||
//printf("RTA_AUTHOR\n");
|
||||
break;
|
||||
*/
|
||||
}
|
||||
|
||||
saptr += salen;
|
||||
}
|
||||
|
||||
|
||||
deviceIndex = rtm->rtm_index;
|
||||
|
||||
|
||||
if (((contains)&&(sa_t.containsAddress(target)))||(sa_t == target)) {
|
||||
rtes.push_back(_RTE());
|
||||
rtes.back().target = sa_t;
|
||||
rtes.back().via = sa_v;
|
||||
rtes.back().isDefault = isDefault;
|
||||
if (deviceIndex >= 0) {
|
||||
if_indextoname(deviceIndex,rtes.back().device);
|
||||
} else {
|
||||
rtes.back().device[0] = (char)0;
|
||||
}
|
||||
rtes.back().metric = ((int)rtm->rtm_rmx.rmx_hopcount < 0) ? 0 : (int)rtm->rtm_rmx.rmx_hopcount;
|
||||
}
|
||||
}
|
||||
|
||||
next = saend;
|
||||
}
|
||||
}
|
||||
|
||||
::free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
return rtes;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _routeCmd(const char *op,const InetAddress &target,const InetAddress &via,const char *ifscope,const char *localInterface)
|
||||
{
|
||||
// char f1[1024],f2[1024]; printf("cmd %s %s %s %s %s\n",op,target.toString(f1),via.toString(f2),ifscope,localInterface);
|
||||
long p = (long)fork();
|
||||
if (p > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(p,&exitcode,0);
|
||||
} else if (p == 0) {
|
||||
::close(STDOUT_FILENO);
|
||||
::close(STDERR_FILENO);
|
||||
char ttmp[64];
|
||||
char iptmp[64];
|
||||
if (via) {
|
||||
if ((ifscope)&&(ifscope[0])) {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: route %s -ifscope %s %s %s" ZT_EOL_S, ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),via.toIpString(iptmp));
|
||||
#endif
|
||||
::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),via.toIpString(iptmp),(const char *)0);
|
||||
} else {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: route %s %s %s %s" ZT_EOL_S, op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),via.toIpString(iptmp));
|
||||
#endif
|
||||
::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),via.toIpString(iptmp),(const char *)0);
|
||||
}
|
||||
} else if ((localInterface)&&(localInterface[0])) {
|
||||
if ((ifscope)&&(ifscope[0])) {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: route %s -ifscope %s %s %s -interface %s" ZT_EOL_S, op, ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),localInterface);
|
||||
#endif
|
||||
::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),"-interface",localInterface,(const char *)0);
|
||||
} else {
|
||||
#ifdef ZT_TRACE
|
||||
fprintf(stderr, "DEBUG: route %s %s %s -interface %s" ZT_EOL_S, op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),localInterface);
|
||||
#endif
|
||||
::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),"-interface",localInterface,(const char *)0);
|
||||
}
|
||||
}
|
||||
::_exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
#ifdef __LINUX__ // ----------------------------------------------------------
|
||||
#define ZT_ROUTING_SUPPORT_FOUND 1
|
||||
|
||||
// This has been replaced by LinuxNetLink
|
||||
|
||||
#endif // __LINUX__ ----------------------------------------------------------
|
||||
|
||||
#ifdef __WINDOWS__ // --------------------------------------------------------
|
||||
#define ZT_ROUTING_SUPPORT_FOUND 1
|
||||
|
||||
static bool _winRoute(bool del,const NET_LUID &interfaceLuid,const NET_IFINDEX &interfaceIndex,const InetAddress &target,const InetAddress &via)
|
||||
{
|
||||
MIB_IPFORWARD_ROW2 rtrow;
|
||||
InitializeIpForwardEntry(&rtrow);
|
||||
rtrow.InterfaceLuid.Value = interfaceLuid.Value;
|
||||
rtrow.InterfaceIndex = interfaceIndex;
|
||||
if (target.ss_family == AF_INET) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in *>(&target)->sin_addr.S_un.S_addr;
|
||||
if (via.ss_family == AF_INET) {
|
||||
rtrow.NextHop.si_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in *>(&via)->sin_addr.S_un.S_addr;
|
||||
}
|
||||
} else if (target.ss_family == AF_INET6) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET6;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte,reinterpret_cast<const struct sockaddr_in6 *>(&target)->sin6_addr.u.Byte,16);
|
||||
if (via.ss_family == AF_INET6) {
|
||||
rtrow.NextHop.si_family = AF_INET6;
|
||||
rtrow.NextHop.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte,reinterpret_cast<const struct sockaddr_in6 *>(&via)->sin6_addr.u.Byte,16);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
rtrow.DestinationPrefix.PrefixLength = target.netmaskBits();
|
||||
rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength;
|
||||
rtrow.ValidLifetime = 0xffffffff;
|
||||
rtrow.PreferredLifetime = 0xffffffff;
|
||||
rtrow.Metric = -1;
|
||||
rtrow.Protocol = MIB_IPPROTO_NETMGMT;
|
||||
rtrow.Loopback = FALSE;
|
||||
rtrow.AutoconfigureAddress = FALSE;
|
||||
rtrow.Publish = FALSE;
|
||||
rtrow.Immortal = FALSE;
|
||||
rtrow.Age = 0;
|
||||
rtrow.Origin = NlroManual;
|
||||
if (del) {
|
||||
return (DeleteIpForwardEntry2(&rtrow) == NO_ERROR);
|
||||
} else {
|
||||
NTSTATUS r = CreateIpForwardEntry2(&rtrow);
|
||||
if (r == NO_ERROR) {
|
||||
return true;
|
||||
} else if (r == ERROR_OBJECT_ALREADY_EXISTS) {
|
||||
return (SetIpForwardEntry2(&rtrow) == NO_ERROR);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _winHasRoute(const NET_LUID &interfaceLuid, const NET_IFINDEX &interfaceIndex, const InetAddress &target, const InetAddress &via)
|
||||
{
|
||||
MIB_IPFORWARD_ROW2 rtrow;
|
||||
InitializeIpForwardEntry(&rtrow);
|
||||
rtrow.InterfaceLuid.Value = interfaceLuid.Value;
|
||||
rtrow.InterfaceIndex = interfaceIndex;
|
||||
if (target.ss_family == AF_INET) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in *>(&target)->sin_addr.S_un.S_addr;
|
||||
if (via.ss_family == AF_INET) {
|
||||
rtrow.NextHop.si_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in *>(&via)->sin_addr.S_un.S_addr;
|
||||
}
|
||||
} else if (target.ss_family == AF_INET6) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET6;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte, reinterpret_cast<const struct sockaddr_in6 *>(&target)->sin6_addr.u.Byte, 16);
|
||||
if (via.ss_family == AF_INET6) {
|
||||
rtrow.NextHop.si_family = AF_INET6;
|
||||
rtrow.NextHop.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte, reinterpret_cast<const struct sockaddr_in6 *>(&via)->sin6_addr.u.Byte, 16);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
rtrow.DestinationPrefix.PrefixLength = target.netmaskBits();
|
||||
rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength;
|
||||
return (GetIpForwardEntry2(&rtrow) == NO_ERROR);
|
||||
}
|
||||
|
||||
#endif // __WINDOWS__ --------------------------------------------------------
|
||||
|
||||
#ifndef ZT_ROUTING_SUPPORT_FOUND
|
||||
#error "ManagedRoute.cpp has no support for managing routes on this platform! You'll need to check and see if one of the existing ones will work and make sure proper defines are set, or write one. Please do a GitHub pull request if you do this for a new OS."
|
||||
#endif
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
ManagedRoute::ManagedRoute(const InetAddress &target,const InetAddress &via,const InetAddress &src,const char *device)
|
||||
{
|
||||
_target = target;
|
||||
_via = via;
|
||||
_src = src;
|
||||
|
||||
if (_via.ss_family == AF_INET) {
|
||||
_via.setPort(32);
|
||||
} else if (_via.ss_family == AF_INET6) {
|
||||
_via.setPort(128);
|
||||
}
|
||||
|
||||
if (_src.ss_family == AF_INET) {
|
||||
_src.setPort(32);
|
||||
} else if (_src.ss_family == AF_INET6) {
|
||||
_src.setPort(128);
|
||||
}
|
||||
|
||||
Utils::scopy(_device,sizeof(_device),device);
|
||||
_systemDevice[0] = (char)0;
|
||||
}
|
||||
|
||||
ManagedRoute::~ManagedRoute()
|
||||
{
|
||||
this->remove();
|
||||
}
|
||||
|
||||
/* Linux NOTE: for default route override, some Linux distributions will
|
||||
* require a change to the rp_filter parameter. A value of '1' will prevent
|
||||
* default route override from working properly.
|
||||
*
|
||||
* sudo sysctl -w net.ipv4.conf.all.rp_filter=2
|
||||
*
|
||||
* Add to /etc/sysctl.conf or /etc/sysctl.d/... to make permanent.
|
||||
*
|
||||
* This is true of CentOS/RHEL 6+ and possibly others. This is because
|
||||
* Linux default route override implies asymmetric routes, which then
|
||||
* trigger Linux's "martian packet" filter. */
|
||||
|
||||
#ifndef ZT_SDK
|
||||
bool ManagedRoute::sync()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
NET_LUID interfaceLuid;
|
||||
interfaceLuid.Value = (ULONG64)Utils::hexStrToU64(_device); // on Windows we use the hex LUID as the "interface name" for ManagedRoute
|
||||
NET_IFINDEX interfaceIndex = -1;
|
||||
if (ConvertInterfaceLuidToIndex(&interfaceLuid,&interfaceIndex) != NO_ERROR)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
InetAddress leftt,rightt;
|
||||
if (_target.netmaskBits() == 0) // bifurcate only the default route
|
||||
_forkTarget(_target,leftt,rightt);
|
||||
else leftt = _target;
|
||||
|
||||
#ifdef __BSD__ // ------------------------------------------------------------
|
||||
|
||||
if (_device[0]) {
|
||||
bool haveDevice = false;
|
||||
struct ifaddrs *ifa = (struct ifaddrs *)0;
|
||||
if (!getifaddrs(&ifa)) {
|
||||
struct ifaddrs *p = ifa;
|
||||
while (p) {
|
||||
if ((p->ifa_name)&&(!strcmp(_device, p->ifa_name))) {
|
||||
haveDevice = true;
|
||||
break;
|
||||
}
|
||||
p = p->ifa_next;
|
||||
}
|
||||
freeifaddrs(ifa);
|
||||
}
|
||||
if (!haveDevice)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<_RTE> rtes(_getRTEs(_target,false));
|
||||
|
||||
bool hasRoute = false;
|
||||
for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) {
|
||||
hasRoute = _target == r->target && _via.ipOnly() == r->via.ipOnly() && (strcmp(r->device,_device) == 0);
|
||||
if (hasRoute) { break; }
|
||||
}
|
||||
|
||||
// char buf[255];
|
||||
// fprintf(stderr, "hasRoute %d %s\n", !!hasRoute, _target.toString(buf));
|
||||
|
||||
|
||||
if (!hasRoute) {
|
||||
if (_target && _target.netmaskBits() == 0) { // Allow Default
|
||||
InetAddress newSystemVia;
|
||||
char newSystemDevice[128];
|
||||
newSystemDevice[0] = (char)0;
|
||||
|
||||
// if our routes got deleted
|
||||
// delete the systemd via that we had added with -ifscope
|
||||
if (_systemVia && !!_systemDevice[0]) {
|
||||
_routeCmd("delete",_target,_systemVia,_systemDevice,(const char *)0);
|
||||
}
|
||||
|
||||
_systemVia = newSystemVia;
|
||||
Utils::scopy(_systemDevice,sizeof(_systemDevice),newSystemDevice);
|
||||
// If macos has a network hiccup, it deletes what the route we set, and it's own physical routes.
|
||||
// if !hasRoute (our 0.0.0.0 has been deleted), the OS has changed stuff
|
||||
// So don't assume _systemX are valid anymore. Always get for _system{Via,Device}
|
||||
|
||||
// Find system default route that this route should override
|
||||
// We need to put it back when default route is turned off
|
||||
for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) {
|
||||
if (r->via) {
|
||||
if ( r->isDefault == 1 && (strcmp(r->device,_device) != 0) ) {
|
||||
|
||||
// char buf[255];
|
||||
// fprintf(stderr, "system device1 %s %s\n", r->via.toString(buf), r->device);
|
||||
|
||||
newSystemVia = r->via;
|
||||
Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newSystemVia) { _systemVia = newSystemVia; }
|
||||
if (newSystemDevice[0]) {
|
||||
Utils::scopy(_systemDevice,sizeof(_systemDevice),newSystemDevice);
|
||||
}
|
||||
|
||||
// if there's no newSystemVia, the OS might not have
|
||||
// ipv4 or ipv6 connectivity.
|
||||
// we should still add our ZeroTier ipv4 or 6 routes though
|
||||
|
||||
if (!!_systemVia && !!_systemDevice[0]) {
|
||||
_routeCmd("delete",_target,_systemVia,(const char *)0,(const char *)0);
|
||||
}
|
||||
|
||||
_routeCmd("add",_target,_via,(const char *)0,(const char *)0);
|
||||
|
||||
if (!!_systemVia && !!_systemDevice[0]) {
|
||||
_routeCmd("add",_target,_systemVia,_systemDevice,(const char *)0);
|
||||
}
|
||||
|
||||
_applied[_target] = true;
|
||||
|
||||
} else {
|
||||
// Do Non-Default route commands
|
||||
_applied[_target] = true;
|
||||
_routeCmd("add",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
#ifdef __LINUX__ // ----------------------------------------------------------
|
||||
|
||||
if ((leftt)&&(!LinuxNetLink::getInstance().routeIsSet(leftt,_via,_src,_device))) {
|
||||
_applied[leftt] = false; // boolean unused
|
||||
LinuxNetLink::getInstance().addRoute(leftt, _via, _src, _device);
|
||||
}
|
||||
if ((rightt)&&(!LinuxNetLink::getInstance().routeIsSet(rightt,_via,_src,_device))) {
|
||||
_applied[rightt] = false; // boolean unused
|
||||
LinuxNetLink::getInstance().addRoute(rightt, _via, _src, _device);
|
||||
}
|
||||
|
||||
#endif // __LINUX__ ----------------------------------------------------------
|
||||
|
||||
#ifdef __WINDOWS__ // --------------------------------------------------------
|
||||
|
||||
if ( (!_applied.count(leftt)) || (!_winHasRoute(interfaceLuid,interfaceIndex,leftt,_via)) ) {
|
||||
_applied[leftt] = false; // boolean unused
|
||||
_winRoute(false,interfaceLuid,interfaceIndex,leftt,_via);
|
||||
}
|
||||
if ( (rightt) && ( (!_applied.count(rightt)) || (!_winHasRoute(interfaceLuid,interfaceIndex,rightt,_via)) ) ) {
|
||||
_applied[rightt] = false; // boolean unused
|
||||
_winRoute(false,interfaceLuid,interfaceIndex,rightt,_via);
|
||||
}
|
||||
|
||||
#endif // __WINDOWS__ --------------------------------------------------------
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ManagedRoute::remove()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
NET_LUID interfaceLuid;
|
||||
interfaceLuid.Value = (ULONG64)Utils::hexStrToU64(_device); // on Windows we use the hex LUID as the "interface name" for ManagedRoute
|
||||
NET_IFINDEX interfaceIndex = -1;
|
||||
if (ConvertInterfaceLuidToIndex(&interfaceLuid,&interfaceIndex) != NO_ERROR)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef __BSD__
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
for(std::map<InetAddress,bool>::iterator r(_applied.begin());r!=_applied.end();++r) {
|
||||
#ifdef __BSD__ // ------------------------------------------------------------
|
||||
if (_target && _target.netmaskBits() == 0) {
|
||||
_routeCmd("delete",_target,_via,(const char *)0,(const char *)0);
|
||||
if (_systemVia && _systemDevice[0]) {
|
||||
_routeCmd("delete",_target,_systemVia,_systemDevice,(const char *)0);
|
||||
|
||||
_routeCmd("add",_target,_systemVia,(const char *)0,(const char *)0);
|
||||
|
||||
}
|
||||
} else {
|
||||
_routeCmd("delete",_target,_via, (const char *)0, _via ? (const char *)0 : _device);
|
||||
}
|
||||
break;
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
#ifdef __LINUX__ // ----------------------------------------------------------
|
||||
//_routeCmd("del",r->first,_via,(_via) ? (const char *)0 : _device);
|
||||
LinuxNetLink::getInstance().delRoute(r->first,_via,_src,(_via) ? (const char *)0 : _device);
|
||||
#endif // __LINUX__ ----------------------------------------------------------
|
||||
|
||||
#ifdef __WINDOWS__ // --------------------------------------------------------
|
||||
_winRoute(true,interfaceLuid,interfaceIndex,r->first,_via);
|
||||
#endif // __WINDOWS__ --------------------------------------------------------
|
||||
}
|
||||
|
||||
_target.zero();
|
||||
_via.zero();
|
||||
_systemVia.zero();
|
||||
_device[0] = (char)0;
|
||||
_systemDevice[0] = (char)0;
|
||||
_applied.clear();
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_MANAGEDROUTE_HPP
|
||||
#define ZT_MANAGEDROUTE_HPP
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/SharedPtr.hpp"
|
||||
#include "../node/AtomicCounter.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* A ZT-managed route that used C++ RAII semantics to automatically clean itself up on deallocate
|
||||
*/
|
||||
class ManagedRoute
|
||||
{
|
||||
friend class SharedPtr<ManagedRoute>;
|
||||
|
||||
public:
|
||||
ManagedRoute(const InetAddress &target,const InetAddress &via,const InetAddress &src,const char *device);
|
||||
~ManagedRoute();
|
||||
|
||||
/**
|
||||
* Set or update currently set route
|
||||
*
|
||||
* This must be called periodically for routes that shadow others so that
|
||||
* shadow routes can be updated. In some cases it has no effect
|
||||
*
|
||||
* @return True if route add/update was successful
|
||||
*/
|
||||
bool sync();
|
||||
|
||||
/**
|
||||
* Remove and clear this ManagedRoute
|
||||
*
|
||||
* This does nothing if this ManagedRoute is not set or has already been
|
||||
* removed. If this is not explicitly called it is called automatically on
|
||||
* destruct.
|
||||
*/
|
||||
void remove();
|
||||
|
||||
inline const InetAddress &target() const { return _target; }
|
||||
inline const InetAddress &via() const { return _via; }
|
||||
inline const InetAddress &src() const { return _src; }
|
||||
inline const char *device() const { return _device; }
|
||||
|
||||
private:
|
||||
ManagedRoute(const ManagedRoute &) {}
|
||||
inline ManagedRoute &operator=(const ManagedRoute &) { return *this; }
|
||||
|
||||
InetAddress _target;
|
||||
InetAddress _via;
|
||||
InetAddress _src;
|
||||
InetAddress _systemVia; // for route overrides
|
||||
std::map<InetAddress,bool> _applied; // routes currently applied
|
||||
char _device[128];
|
||||
char _systemDevice[128]; // for route overrides
|
||||
|
||||
AtomicCounter __refCount;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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 "NeighborDiscovery.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
|
||||
#include "../include/ZeroTierOne.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
uint16_t calc_checksum (uint16_t *addr, int len)
|
||||
{
|
||||
int count = len;
|
||||
uint32_t sum = 0;
|
||||
uint16_t answer = 0;
|
||||
|
||||
// Sum up 2-byte values until none or only one byte left.
|
||||
while (count > 1) {
|
||||
sum += *(addr++);
|
||||
count -= 2;
|
||||
}
|
||||
|
||||
// Add left-over byte, if any.
|
||||
if (count > 0) {
|
||||
sum += *(uint8_t *) addr;
|
||||
}
|
||||
|
||||
// Fold 32-bit sum into 16 bits; we lose information by doing this,
|
||||
// increasing the chances of a collision.
|
||||
// sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits)
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
}
|
||||
|
||||
// Checksum is one's compliment of sum.
|
||||
answer = ~sum;
|
||||
|
||||
return (answer);
|
||||
}
|
||||
|
||||
struct _pseudo_header {
|
||||
uint8_t sourceAddr[16];
|
||||
uint8_t targetAddr[16];
|
||||
uint32_t length;
|
||||
uint8_t zeros[3];
|
||||
uint8_t next; // 58
|
||||
};
|
||||
|
||||
struct _option {
|
||||
_option(int optionType)
|
||||
: type(optionType)
|
||||
, length(8)
|
||||
{
|
||||
memset(mac, 0, sizeof(mac));
|
||||
}
|
||||
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
uint8_t mac[6];
|
||||
};
|
||||
|
||||
struct _neighbor_solicitation {
|
||||
_neighbor_solicitation()
|
||||
: type(135)
|
||||
, code(0)
|
||||
, checksum(0)
|
||||
, option(1)
|
||||
{
|
||||
memset(&reserved, 0, sizeof(reserved));
|
||||
memset(target, 0, sizeof(target));
|
||||
}
|
||||
|
||||
void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) {
|
||||
_pseudo_header ph;
|
||||
memset(&ph, 0, sizeof(_pseudo_header));
|
||||
const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp;
|
||||
const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp;
|
||||
|
||||
memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr));
|
||||
memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr));
|
||||
ph.next = 58;
|
||||
ph.length = htonl(sizeof(_neighbor_solicitation));
|
||||
|
||||
size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_solicitation);
|
||||
uint8_t *tmp = (uint8_t*)malloc(len);
|
||||
memcpy(tmp, &ph, sizeof(_pseudo_header));
|
||||
memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_solicitation));
|
||||
|
||||
checksum = calc_checksum((uint16_t*)tmp, (int)len);
|
||||
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
}
|
||||
|
||||
uint8_t type; // 135
|
||||
uint8_t code; // 0
|
||||
uint16_t checksum;
|
||||
uint32_t reserved;
|
||||
uint8_t target[16];
|
||||
_option option;
|
||||
};
|
||||
|
||||
struct _neighbor_advertisement {
|
||||
_neighbor_advertisement()
|
||||
: type(136)
|
||||
, code(0)
|
||||
, checksum(0)
|
||||
, rso(0x40)
|
||||
, option(2)
|
||||
{
|
||||
memset(padding, 0, sizeof(padding));
|
||||
memset(target, 0, sizeof(target));
|
||||
}
|
||||
|
||||
void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) {
|
||||
_pseudo_header ph;
|
||||
memset(&ph, 0, sizeof(_pseudo_header));
|
||||
const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp;
|
||||
const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp;
|
||||
|
||||
memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr));
|
||||
memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr));
|
||||
ph.next = 58;
|
||||
ph.length = htonl(sizeof(_neighbor_advertisement));
|
||||
|
||||
size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_advertisement);
|
||||
uint8_t *tmp = (uint8_t*)malloc(len);
|
||||
memcpy(tmp, &ph, sizeof(_pseudo_header));
|
||||
memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_advertisement));
|
||||
|
||||
checksum = calc_checksum((uint16_t*)tmp, (int)len);
|
||||
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
}
|
||||
|
||||
uint8_t type; // 136
|
||||
uint8_t code; // 0
|
||||
uint16_t checksum;
|
||||
uint8_t rso;
|
||||
uint8_t padding[3];
|
||||
uint8_t target[16];
|
||||
_option option;
|
||||
};
|
||||
|
||||
NeighborDiscovery::NeighborDiscovery()
|
||||
: _cache(256)
|
||||
, _lastCleaned(OSUtils::now())
|
||||
{}
|
||||
|
||||
void NeighborDiscovery::addLocal(const sockaddr_storage &address, const MAC &mac)
|
||||
{
|
||||
_NDEntry &e = _cache[InetAddress(address)];
|
||||
e.lastQuerySent = 0;
|
||||
e.lastResponseReceived = 0;
|
||||
e.mac = mac;
|
||||
e.local = true;
|
||||
}
|
||||
|
||||
void NeighborDiscovery::remove(const sockaddr_storage &address)
|
||||
{
|
||||
_cache.erase(InetAddress(address));
|
||||
}
|
||||
|
||||
sockaddr_storage NeighborDiscovery::processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest)
|
||||
{
|
||||
assert(sizeof(_neighbor_solicitation) == 28);
|
||||
assert(sizeof(_neighbor_advertisement) == 32);
|
||||
|
||||
const uint64_t now = OSUtils::now();
|
||||
sockaddr_storage ip = {0};
|
||||
|
||||
if (len >= sizeof(_neighbor_solicitation) && nd[0] == 0x87) {
|
||||
// respond to Neighbor Solicitation request for local address
|
||||
_neighbor_solicitation solicitation;
|
||||
memcpy(&solicitation, nd, len);
|
||||
InetAddress targetAddress(solicitation.target, 16, 0);
|
||||
_NDEntry *targetEntry = _cache.get(targetAddress);
|
||||
if (targetEntry && targetEntry->local) {
|
||||
_neighbor_advertisement adv;
|
||||
targetEntry->mac.copyTo(adv.option.mac, 6);
|
||||
memcpy(adv.target, solicitation.target, 16);
|
||||
adv.calculateChecksum(localIp, targetAddress);
|
||||
memcpy(response, &adv, sizeof(_neighbor_advertisement));
|
||||
responseLen = sizeof(_neighbor_advertisement);
|
||||
responseDest.setTo(solicitation.option.mac, 6);
|
||||
}
|
||||
} else if (len >= sizeof(_neighbor_advertisement) && nd[0] == 0x88) {
|
||||
_neighbor_advertisement adv;
|
||||
memcpy(&adv, nd, len);
|
||||
InetAddress responseAddress(adv.target, 16, 0);
|
||||
_NDEntry *queryEntry = _cache.get(responseAddress);
|
||||
if(queryEntry && !queryEntry->local && (now - queryEntry->lastQuerySent <= ZT_ND_QUERY_MAX_TTL)) {
|
||||
queryEntry->lastResponseReceived = now;
|
||||
queryEntry->mac.setTo(adv.option.mac, 6);
|
||||
ip = responseAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if ((now - _lastCleaned) >= ZT_ND_EXPIRE) {
|
||||
_lastCleaned = now;
|
||||
Hashtable<InetAddress, _NDEntry>::Iterator i(_cache);
|
||||
InetAddress *k = NULL;
|
||||
_NDEntry *v = NULL;
|
||||
while (i.next(k, v)) {
|
||||
if(!v->local && (now - v->lastResponseReceived) >= ZT_ND_EXPIRE) {
|
||||
_cache.erase(*k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
MAC NeighborDiscovery::query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest)
|
||||
{
|
||||
const uint64_t now = OSUtils::now();
|
||||
|
||||
InetAddress localAddress(localIp);
|
||||
localAddress.setPort(0);
|
||||
InetAddress targetAddress(targetIp);
|
||||
targetAddress.setPort(0);
|
||||
|
||||
_NDEntry &e = _cache[targetAddress];
|
||||
|
||||
if ( (e.mac && ((now - e.lastResponseReceived) >= (ZT_ND_EXPIRE / 3))) ||
|
||||
(!e.mac && ((now - e.lastQuerySent) >= ZT_ND_QUERY_INTERVAL))) {
|
||||
e.lastQuerySent = now;
|
||||
|
||||
_neighbor_solicitation ns;
|
||||
memcpy(ns.target, targetAddress.rawIpData(), 16);
|
||||
localMac.copyTo(ns.option.mac, 6);
|
||||
ns.calculateChecksum(localIp, targetIp);
|
||||
if (e.mac) {
|
||||
queryDest = e.mac;
|
||||
} else {
|
||||
queryDest = (uint64_t)0xffffffffffffULL;
|
||||
}
|
||||
} else {
|
||||
queryLen = 0;
|
||||
queryDest.zero();
|
||||
}
|
||||
|
||||
return e.mac;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_NEIGHBORDISCOVERY_HPP
|
||||
#define ZT_NEIGHBORDISCOVERY_HPP
|
||||
|
||||
#include "../node/Hashtable.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
|
||||
|
||||
#define ZT_ND_QUERY_INTERVAL 2000
|
||||
|
||||
#define ZT_ND_QUERY_MAX_TTL 5000
|
||||
|
||||
#define ZT_ND_EXPIRE 600000
|
||||
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class NeighborDiscovery
|
||||
{
|
||||
public:
|
||||
NeighborDiscovery();
|
||||
|
||||
/**
|
||||
* Set a local IP entry that we should respond to Neighbor Requests withPrefix64k
|
||||
*
|
||||
* @param mac Our local MAC address
|
||||
* @param ip Our IPv6 address
|
||||
*/
|
||||
void addLocal(const sockaddr_storage &address, const MAC &mac);
|
||||
|
||||
/**
|
||||
* Delete a local IP entry or cached Neighbor entry
|
||||
*
|
||||
* @param address IPv6 address to remove
|
||||
*/
|
||||
void remove(const sockaddr_storage &address);
|
||||
|
||||
sockaddr_storage processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest);
|
||||
|
||||
MAC query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest);
|
||||
|
||||
private:
|
||||
struct _NDEntry
|
||||
{
|
||||
_NDEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false) {}
|
||||
uint64_t lastQuerySent;
|
||||
uint64_t lastResponseReceived;
|
||||
MAC mac;
|
||||
bool local;
|
||||
};
|
||||
|
||||
Hashtable<InetAddress, _NDEntry> _cache;
|
||||
uint64_t _lastCleaned;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <net/route.h>
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "freebsd_getifmaddrs.h"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "NetBSDEthernetTap.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)
|
||||
{
|
||||
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
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_NetBSDEthernetTap_HPP
|
||||
#define ZT_NetBSDEthernetTap_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
#include "../node/MAC.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "EthernetTap.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class NetBSDEthernetTap : public EthernetTap
|
||||
{
|
||||
public:
|
||||
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);
|
||||
|
||||
virtual ~NetBSDEthernetTap();
|
||||
|
||||
virtual void setEnabled(bool en);
|
||||
virtual bool enabled() const;
|
||||
virtual bool addIp(const InetAddress &ip);
|
||||
virtual bool removeIp(const InetAddress &ip);
|
||||
virtual std::vector<InetAddress> 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<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
virtual void setDns(const char *domain, const std::vector<InetAddress> &servers) {}
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
private:
|
||||
void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
uint64_t _nwid;
|
||||
Thread _thread;
|
||||
std::string _dev;
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
unsigned int _mtu;
|
||||
unsigned int _metric;
|
||||
int _fd;
|
||||
int _shutdownSignalPipe[2];
|
||||
volatile bool _enabled;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,582 @@
|
||||
/*
|
||||
* Copyright (c)2013-2020 BackOne, 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <dirent.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#include <shlobj.h>
|
||||
#include <netioapi.h>
|
||||
#include <iphlpapi.h>
|
||||
#endif
|
||||
|
||||
#include "OSUtils.hpp"
|
||||
|
||||
#ifdef __GCC__
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
unsigned int OSUtils::ztsnprintf(char *buf,unsigned int len,const char *fmt,...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
int n = (int)vsnprintf(buf,len,fmt,ap);
|
||||
va_end(ap);
|
||||
|
||||
if ((n >= (int)len)||(n < 0)) {
|
||||
if (len)
|
||||
buf[len - 1] = (char)0;
|
||||
throw std::length_error("buf[] overflow");
|
||||
}
|
||||
|
||||
return (unsigned int)n;
|
||||
}
|
||||
|
||||
std::string OSUtils::networkIDStr(const uint64_t nwid) {
|
||||
char tmp[32] = {};
|
||||
ztsnprintf(tmp, sizeof(tmp), "%.16" PRIx64, nwid);
|
||||
return std::string(tmp);
|
||||
}
|
||||
|
||||
std::string OSUtils::nodeIDStr(const uint64_t nid) {
|
||||
char tmp[32] = {};
|
||||
ztsnprintf(tmp, sizeof(tmp), "%.10" PRIx64, nid);
|
||||
return std::string(tmp);
|
||||
}
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
bool OSUtils::redirectUnixOutputs(const char *stdoutPath,const char *stderrPath)
|
||||
throw()
|
||||
{
|
||||
int fdout = ::open(stdoutPath,O_WRONLY|O_CREAT,0600);
|
||||
if (fdout > 0) {
|
||||
int fderr;
|
||||
if (stderrPath) {
|
||||
fderr = ::open(stderrPath,O_WRONLY|O_CREAT,0600);
|
||||
if (fderr <= 0) {
|
||||
::close(fdout);
|
||||
return false;
|
||||
}
|
||||
} else fderr = fdout;
|
||||
::close(STDOUT_FILENO);
|
||||
::close(STDERR_FILENO);
|
||||
::dup2(fdout,STDOUT_FILENO);
|
||||
::dup2(fderr,STDERR_FILENO);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // __UNIX_LIKE__
|
||||
|
||||
std::vector<std::string> OSUtils::listDirectory(const char *path,bool includeDirectories)
|
||||
{
|
||||
std::vector<std::string> r;
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ( (strcmp(ffd.cFileName,".")) && (strcmp(ffd.cFileName,"..")) && (((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)||(((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)&&(includeDirectories))) )
|
||||
r.push_back(std::string(ffd.cFileName));
|
||||
} while (FindNextFileA(hFind,&ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
#else
|
||||
struct dirent de;
|
||||
struct dirent *dptr;
|
||||
DIR *d = opendir(path);
|
||||
if (!d)
|
||||
return r;
|
||||
dptr = (struct dirent *)0;
|
||||
for(;;) {
|
||||
if (readdir_r(d,&de,&dptr))
|
||||
break;
|
||||
if (dptr) {
|
||||
if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&((dptr->d_type != DT_DIR)||(includeDirectories)))
|
||||
r.push_back(std::string(dptr->d_name));
|
||||
} else break;
|
||||
}
|
||||
closedir(d);
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
long OSUtils::cleanDirectory(const char *path,const int64_t olderThan)
|
||||
{
|
||||
long cleaned = 0;
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
LARGE_INTEGER date,adjust;
|
||||
adjust.QuadPart = 11644473600000 * 10000;
|
||||
char tmp[4096];
|
||||
if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((strcmp(ffd.cFileName,"."))&&(strcmp(ffd.cFileName,".."))&&((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)) {
|
||||
date.HighPart = ffd.ftLastWriteTime.dwHighDateTime;
|
||||
date.LowPart = ffd.ftLastWriteTime.dwLowDateTime;
|
||||
if (date.QuadPart > 0) {
|
||||
date.QuadPart -= adjust.QuadPart;
|
||||
if ((int64_t)((date.QuadPart / 10000000) * 1000) < olderThan) {
|
||||
ztsnprintf(tmp, sizeof(tmp), "%s\\%s", path, ffd.cFileName);
|
||||
if (DeleteFileA(tmp))
|
||||
++cleaned;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (FindNextFileA(hFind,&ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
#else
|
||||
struct dirent de;
|
||||
struct dirent *dptr;
|
||||
struct stat st;
|
||||
char tmp[4096];
|
||||
DIR *d = opendir(path);
|
||||
if (!d)
|
||||
return -1;
|
||||
dptr = (struct dirent *)0;
|
||||
for(;;) {
|
||||
if (readdir_r(d,&de,&dptr))
|
||||
break;
|
||||
if (dptr) {
|
||||
if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&(dptr->d_type == DT_REG)) {
|
||||
ztsnprintf(tmp,sizeof(tmp),"%s/%s",path,dptr->d_name);
|
||||
if (stat(tmp,&st) == 0) {
|
||||
int64_t mt = (int64_t)(st.st_mtime);
|
||||
if ((mt > 0)&&((mt * 1000) < olderThan)) {
|
||||
if (unlink(tmp) == 0)
|
||||
++cleaned;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else break;
|
||||
}
|
||||
closedir(d);
|
||||
#endif
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
bool OSUtils::rmDashRf(const char *path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((strcmp(ffd.cFileName,".") != 0)&&(strcmp(ffd.cFileName,"..") != 0)) {
|
||||
if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
if (DeleteFileA((std::string(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()) == FALSE)
|
||||
return false;
|
||||
} else {
|
||||
if (!rmDashRf((std::string(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (FindNextFileA(hFind,&ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
return (RemoveDirectoryA(path) != FALSE);
|
||||
#else
|
||||
struct dirent de;
|
||||
struct dirent *dptr;
|
||||
DIR *d = opendir(path);
|
||||
if (!d)
|
||||
return true;
|
||||
dptr = (struct dirent *)0;
|
||||
for(;;) {
|
||||
if (readdir_r(d,&de,&dptr) != 0)
|
||||
break;
|
||||
if (!dptr)
|
||||
break;
|
||||
if ((strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)&&(strlen(dptr->d_name) > 0)) {
|
||||
std::string p(path);
|
||||
p.push_back(ZT_PATH_SEPARATOR);
|
||||
p.append(dptr->d_name);
|
||||
if (unlink(p.c_str()) != 0) { // unlink first will remove symlinks instead of recursing them
|
||||
if (!rmDashRf(p.c_str()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return (rmdir(path) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OSUtils::lockDownFile(const char *path,bool isDir)
|
||||
{
|
||||
#ifdef __UNIX_LIKE__
|
||||
chmod(path,isDir ? 0700 : 0600);
|
||||
#else
|
||||
#ifdef __WINDOWS__
|
||||
{
|
||||
STARTUPINFOA startupInfo;
|
||||
PROCESS_INFORMATION processInfo;
|
||||
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
memset(&startupInfo,0,sizeof(STARTUPINFOA));
|
||||
memset(&processInfo,0,sizeof(PROCESS_INFORMATION));
|
||||
if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /inheritance:d /Q").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) {
|
||||
WaitForSingleObject(processInfo.hProcess,INFINITE);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
}
|
||||
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
memset(&startupInfo,0,sizeof(STARTUPINFOA));
|
||||
memset(&processInfo,0,sizeof(PROCESS_INFORMATION));
|
||||
if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /remove *S-1-5-32-545 /Q").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) {
|
||||
WaitForSingleObject(processInfo.hProcess,INFINITE);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
}
|
||||
|
||||
// Remove 'Everyone' group from R/RX access
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
|
||||
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
|
||||
if (CreateProcessA(NULL, (LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /remove:g Everyone /t /c /Q").c_str(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo)) {
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t OSUtils::getLastModified(const char *path)
|
||||
{
|
||||
struct stat s;
|
||||
if (stat(path,&s))
|
||||
return 0;
|
||||
return (((uint64_t)s.st_mtime) * 1000ULL);
|
||||
}
|
||||
|
||||
bool OSUtils::fileExists(const char *path,bool followLinks)
|
||||
{
|
||||
struct stat s;
|
||||
#ifdef __UNIX_LIKE__
|
||||
if (!followLinks)
|
||||
return (lstat(path,&s) == 0);
|
||||
#endif
|
||||
return (stat(path,&s) == 0);
|
||||
}
|
||||
|
||||
int64_t OSUtils::getFileSize(const char *path)
|
||||
{
|
||||
struct stat s;
|
||||
if (stat(path,&s))
|
||||
return -1;
|
||||
#ifdef __WINDOWS__
|
||||
return s.st_size;
|
||||
#else
|
||||
if (S_ISREG(s.st_mode))
|
||||
return s.st_size;
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool OSUtils::readFile(const char *path,std::string &buf)
|
||||
{
|
||||
char tmp[16384];
|
||||
FILE *f = fopen(path,"rb");
|
||||
if (f) {
|
||||
for(;;) {
|
||||
long n = (long)fread(tmp,1,sizeof(tmp),f);
|
||||
if (n > 0)
|
||||
buf.append(tmp,n);
|
||||
else break;
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OSUtils::writeFile(const char *path,const void *buf,unsigned int len)
|
||||
{
|
||||
FILE *f = fopen(path,"wb");
|
||||
if (f) {
|
||||
if ((long)fwrite(buf,1,len,f) != (long)len) {
|
||||
fclose(f);
|
||||
return false;
|
||||
} else {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> OSUtils::split(const char *s,const char *const sep,const char *esc,const char *quot)
|
||||
{
|
||||
std::vector<std::string> fields;
|
||||
std::string buf;
|
||||
|
||||
if (!esc)
|
||||
esc = "";
|
||||
if (!quot)
|
||||
quot = "";
|
||||
|
||||
bool escapeState = false;
|
||||
char quoteState = 0;
|
||||
while (*s) {
|
||||
if (escapeState) {
|
||||
escapeState = false;
|
||||
buf.push_back(*s);
|
||||
} else if (quoteState) {
|
||||
if (*s == quoteState) {
|
||||
quoteState = 0;
|
||||
fields.push_back(buf);
|
||||
buf.clear();
|
||||
} else buf.push_back(*s);
|
||||
} else {
|
||||
const char *quotTmp;
|
||||
if (strchr(esc,*s))
|
||||
escapeState = true;
|
||||
else if ((buf.size() <= 0)&&((quotTmp = strchr(quot,*s))))
|
||||
quoteState = *quotTmp;
|
||||
else if (strchr(sep,*s)) {
|
||||
if (!buf.empty()) {
|
||||
fields.push_back(buf);
|
||||
buf.clear();
|
||||
} // else skip runs of separators
|
||||
} else buf.push_back(*s);
|
||||
}
|
||||
++s;
|
||||
}
|
||||
|
||||
if (buf.size())
|
||||
fields.push_back(buf);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
std::string OSUtils::platformDefaultHomePath()
|
||||
{
|
||||
#ifdef __QNAP__
|
||||
char *cmd = "/sbin/getcfg backone 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;
|
||||
}
|
||||
std::string homeDir = std::string(buf);
|
||||
homeDir.erase(std::remove(homeDir.begin(), homeDir.end(), '\n'), homeDir.end());
|
||||
return homeDir;
|
||||
#endif
|
||||
#ifdef __UBIQUITI__
|
||||
// Only persistent location after firmware upgrades
|
||||
return std::string("/config/backone");
|
||||
#endif
|
||||
|
||||
// Check for user-defined environment variable before using defaults
|
||||
#ifdef __WINDOWS__
|
||||
DWORD bufferSize = 65535;
|
||||
std::string userDefinedPath;
|
||||
bufferSize = GetEnvironmentVariable("ZEROTIER_HOME", &userDefinedPath[0], bufferSize);
|
||||
if (bufferSize) {
|
||||
return userDefinedPath;
|
||||
}
|
||||
#else
|
||||
if(const char* userDefinedPath = getenv("ZEROTIER_HOME")) {
|
||||
return std::string(userDefinedPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Finally, resort to using default paths if no user-defined path was provided
|
||||
#ifdef __UNIX_LIKE__
|
||||
|
||||
#ifdef __APPLE__
|
||||
// /Library/... on Apple
|
||||
return std::string("/Library/Application Support/BackOne");
|
||||
#else
|
||||
|
||||
#ifdef __BSD__
|
||||
// BSD likes /var/db instead of /var/lib
|
||||
return std::string("/var/db/backone");
|
||||
#else
|
||||
// Use /var/lib for Linux and other *nix
|
||||
return std::string("/var/lib/backone");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#else // not __UNIX_LIKE__
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
// Look up app data folder on Windows, e.g. C:\ProgramData\...
|
||||
char buf[16384];
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL,CSIDL_COMMON_APPDATA,NULL,0,buf)))
|
||||
return (std::string(buf) + "\\ZeroTier\\One");
|
||||
else return std::string("C:\\ZeroTier\\One");
|
||||
#else
|
||||
|
||||
return (std::string(ZT_PATH_SEPARATOR_S) + "ZeroTier" + ZT_PATH_SEPARATOR_S + "One"); // UNKNOWN PLATFORM
|
||||
|
||||
#endif
|
||||
|
||||
#endif // __UNIX_LIKE__ or not...
|
||||
}
|
||||
|
||||
#ifndef OMIT_JSON_SUPPORT
|
||||
// Inline these massive JSON operations in one place only to reduce binary footprint and compile time
|
||||
nlohmann::json OSUtils::jsonParse(const std::string &buf) { return nlohmann::json::parse(buf.c_str()); }
|
||||
std::string OSUtils::jsonDump(const nlohmann::json &j,int indentation) { return j.dump(indentation); }
|
||||
|
||||
uint64_t OSUtils::jsonInt(const nlohmann::json &jv,const uint64_t dfl)
|
||||
{
|
||||
try {
|
||||
if (jv.is_number()) {
|
||||
return (uint64_t)jv;
|
||||
} else if (jv.is_string()) {
|
||||
std::string s = jv;
|
||||
return Utils::strToU64(s.c_str());
|
||||
} else if (jv.is_boolean()) {
|
||||
return ((bool)jv ? 1ULL : 0ULL);
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
return dfl;
|
||||
}
|
||||
|
||||
double OSUtils::jsonDouble(const nlohmann::json &jv,const double dfl)
|
||||
{
|
||||
try {
|
||||
if (jv.is_number()) {
|
||||
return (double)jv;
|
||||
}
|
||||
else if (jv.is_string()) {
|
||||
std::string s = jv;
|
||||
return Utils::strToDouble(s.c_str());
|
||||
} else if (jv.is_boolean()) {
|
||||
return (double)jv;
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
return dfl;
|
||||
}
|
||||
|
||||
uint64_t OSUtils::jsonIntHex(const nlohmann::json &jv,const uint64_t dfl)
|
||||
{
|
||||
try {
|
||||
if (jv.is_number()) {
|
||||
return (uint64_t)jv;
|
||||
} else if (jv.is_string()) {
|
||||
std::string s = jv;
|
||||
return Utils::hexStrToU64(s.c_str());
|
||||
} else if (jv.is_boolean()) {
|
||||
return ((bool)jv ? 1ULL : 0ULL);
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
return dfl;
|
||||
}
|
||||
|
||||
bool OSUtils::jsonBool(const nlohmann::json &jv,const bool dfl)
|
||||
{
|
||||
try {
|
||||
if (jv.is_boolean()) {
|
||||
return (bool)jv;
|
||||
} else if (jv.is_number()) {
|
||||
return ((uint64_t)jv > 0ULL);
|
||||
} else if (jv.is_string()) {
|
||||
std::string s = jv;
|
||||
if (s.length() > 0) {
|
||||
switch(s[0]) {
|
||||
case 't':
|
||||
case 'T':
|
||||
case '1':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
return dfl;
|
||||
}
|
||||
|
||||
std::string OSUtils::jsonString(const nlohmann::json &jv,const char *dfl)
|
||||
{
|
||||
try {
|
||||
if (jv.is_string()) {
|
||||
return jv;
|
||||
} else if (jv.is_number()) {
|
||||
char tmp[64];
|
||||
ztsnprintf(tmp,sizeof(tmp),"%llu",(uint64_t)jv);
|
||||
return tmp;
|
||||
} else if (jv.is_boolean()) {
|
||||
return ((bool)jv ? std::string("1") : std::string("0"));
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
return std::string((dfl) ? dfl : "");
|
||||
}
|
||||
|
||||
std::string OSUtils::jsonBinFromHex(const nlohmann::json &jv)
|
||||
{
|
||||
std::string s(jsonString(jv,""));
|
||||
if (s.length() > 0) {
|
||||
unsigned int buflen = (unsigned int)((s.length() / 2) + 1);
|
||||
char *buf = new char[buflen];
|
||||
try {
|
||||
unsigned int l = Utils::unhex(s.c_str(),buf,buflen);
|
||||
std::string b(buf,l);
|
||||
delete [] buf;
|
||||
return b;
|
||||
} catch ( ... ) {
|
||||
delete [] buf;
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
#endif // OMIT_JSON_SUPPORT
|
||||
|
||||
// Used to convert HTTP header names to ASCII lower case
|
||||
const unsigned char OSUtils::TOLOWER_TABLE[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', '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', '{', '|', '}', '~', '_', '`', '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', '{', '|', '}', '~', 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* 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: 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_OSUTILS_HPP
|
||||
#define ZT_OSUTILS_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <arpa/inet.h>
|
||||
#ifdef __LINUX__
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef OMIT_JSON_SUPPORT
|
||||
#include <nlohmann/json.hpp>
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Miscellaneous utility functions and global constants
|
||||
*/
|
||||
class OSUtils
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Variant of snprintf that is portable and throws an exception
|
||||
*
|
||||
* This just wraps the local implementation whatever it's called, while
|
||||
* performing a few other checks and adding exceptions for overflow.
|
||||
*
|
||||
* @param buf Buffer to write to
|
||||
* @param len Length of buffer in bytes
|
||||
* @param fmt Format string
|
||||
* @param ... Format arguments
|
||||
* @throws std::length_error buf[] too short (buf[] will still be left null-terminated)
|
||||
*/
|
||||
static unsigned int ztsnprintf(char *buf,unsigned int len,const char *fmt,...);
|
||||
|
||||
/**
|
||||
* Converts a uint64_t network ID into a string
|
||||
*
|
||||
* @param nwid network ID
|
||||
* @throws std::length_error buf[] too short (buf[] will still be left null-terminated)
|
||||
*/
|
||||
static std::string networkIDStr(const uint64_t nwid);
|
||||
|
||||
/**
|
||||
* Converts a uint64_t node ID into a string
|
||||
*
|
||||
* @param nid node ID
|
||||
* @throws std::length_error buf[] too short (buf[] will still be left null-terminated)
|
||||
*/
|
||||
static std::string nodeIDStr(const uint64_t nid);
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
/**
|
||||
* Close STDOUT_FILENO and STDERR_FILENO and replace them with output to given path
|
||||
*
|
||||
* This can be called after fork() and prior to exec() to suppress output
|
||||
* from a subprocess, such as auto-update.
|
||||
*
|
||||
* @param stdoutPath Path to file to use for stdout
|
||||
* @param stderrPath Path to file to use for stderr, or NULL for same as stdout (default)
|
||||
* @return True on success
|
||||
*/
|
||||
static bool redirectUnixOutputs(const char *stdoutPath,const char *stderrPath = (const char *)0)
|
||||
throw();
|
||||
#endif // __UNIX_LIKE__
|
||||
|
||||
/**
|
||||
* Delete a file
|
||||
*
|
||||
* @param path Path to delete
|
||||
* @return True if delete was successful
|
||||
*/
|
||||
static inline bool rm(const char *path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
return (DeleteFileA(path) != FALSE);
|
||||
#else
|
||||
return (unlink(path) == 0);
|
||||
#endif
|
||||
}
|
||||
static inline bool rm(const std::string &path) { return rm(path.c_str()); }
|
||||
|
||||
static inline bool mkdir(const char *path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
if (::PathIsDirectoryA(path))
|
||||
return true;
|
||||
return (::CreateDirectoryA(path,NULL) == TRUE);
|
||||
#else
|
||||
if (::mkdir(path,0755) != 0)
|
||||
return (errno == EEXIST);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
static inline bool mkdir(const std::string &path) { return OSUtils::mkdir(path.c_str()); }
|
||||
|
||||
static inline bool rename(const char *o,const char *n)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
DeleteFileA(n);
|
||||
return (::rename(o,n) == 0);
|
||||
#else
|
||||
return (::rename(o,n) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* List a directory's contents
|
||||
*
|
||||
* @param path Path to list
|
||||
* @param includeDirectories If true, include directories as well as files
|
||||
* @return Names of files in directory (without path prepended)
|
||||
*/
|
||||
static std::vector<std::string> listDirectory(const char *path,bool includeDirectories = false);
|
||||
|
||||
/**
|
||||
* Clean a directory of files whose last modified time is older than this
|
||||
*
|
||||
* This ignores directories, symbolic links, and other special files.
|
||||
*
|
||||
* @param olderThan Last modified older than timestamp (ms since epoch)
|
||||
* @return Number of cleaned files or negative on fatal error
|
||||
*/
|
||||
static long cleanDirectory(const char *path,const int64_t olderThan);
|
||||
|
||||
/**
|
||||
* Delete a directory and all its files and subdirectories recursively
|
||||
*
|
||||
* @param path Path to delete
|
||||
* @return True on success
|
||||
*/
|
||||
static bool rmDashRf(const char *path);
|
||||
|
||||
/**
|
||||
* Set modes on a file to something secure
|
||||
*
|
||||
* This locks a file so that only the owner can access it. What it actually
|
||||
* does varies by platform.
|
||||
*
|
||||
* @param path Path to lock
|
||||
* @param isDir True if this is a directory
|
||||
*/
|
||||
static void lockDownFile(const char *path,bool isDir);
|
||||
|
||||
/**
|
||||
* Get file last modification time
|
||||
*
|
||||
* Resolution is often only second, not millisecond, but the return is
|
||||
* always in ms for comparison against now().
|
||||
*
|
||||
* @param path Path to file to get time
|
||||
* @return Last modification time in ms since epoch or 0 if not found
|
||||
*/
|
||||
static uint64_t getLastModified(const char *path);
|
||||
|
||||
/**
|
||||
* @param path Path to check
|
||||
* @param followLinks Follow links (on platforms with that concept)
|
||||
* @return True if file or directory exists at path location
|
||||
*/
|
||||
static bool fileExists(const char *path,bool followLinks = true);
|
||||
|
||||
/**
|
||||
* @param path Path to file
|
||||
* @return File size or -1 if nonexistent or other failure
|
||||
*/
|
||||
static int64_t getFileSize(const char *path);
|
||||
|
||||
/**
|
||||
* Get IP (v4 and/or v6) addresses for a given host
|
||||
*
|
||||
* This is a blocking resolver.
|
||||
*
|
||||
* @param name Host name
|
||||
* @return IP addresses in InetAddress sort order or empty vector if not found
|
||||
*/
|
||||
static std::vector<InetAddress> resolve(const char *name);
|
||||
|
||||
/**
|
||||
* @return Current time in milliseconds since epoch
|
||||
*/
|
||||
static inline int64_t now()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
FILETIME ft;
|
||||
SYSTEMTIME st;
|
||||
ULARGE_INTEGER tmp;
|
||||
GetSystemTime(&st);
|
||||
SystemTimeToFileTime(&st,&ft);
|
||||
tmp.LowPart = ft.dwLowDateTime;
|
||||
tmp.HighPart = ft.dwHighDateTime;
|
||||
return (int64_t)( ((tmp.QuadPart - 116444736000000000LL) / 10000L) + st.wMilliseconds );
|
||||
#else
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,(struct timezone *)0);
|
||||
return ( (1000LL * (int64_t)tv.tv_sec) + (int64_t)(tv.tv_usec / 1000) );
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the full contents of a file into a string buffer
|
||||
*
|
||||
* The buffer isn't cleared, so if it already contains data the file's data will
|
||||
* be appended.
|
||||
*
|
||||
* @param path Path of file to read
|
||||
* @param buf Buffer to fill
|
||||
* @return True if open and read successful
|
||||
*/
|
||||
static bool readFile(const char *path,std::string &buf);
|
||||
|
||||
/**
|
||||
* Write a block of data to disk, replacing any current file contents
|
||||
*
|
||||
* @param path Path to write
|
||||
* @param buf Buffer containing data
|
||||
* @param len Length of buffer
|
||||
* @return True if entire file was successfully written
|
||||
*/
|
||||
static bool writeFile(const char *path,const void *buf,unsigned int len);
|
||||
|
||||
/**
|
||||
* Split a string by delimiter, with optional escape and quote characters
|
||||
*
|
||||
* @param s String to split
|
||||
* @param sep One or more separators
|
||||
* @param esc Zero or more escape characters
|
||||
* @param quot Zero or more quote characters
|
||||
* @return Vector of tokens
|
||||
*/
|
||||
static std::vector<std::string> split(const char *s,const char *const sep,const char *esc,const char *quot);
|
||||
|
||||
/**
|
||||
* Write a block of data to disk, replacing any current file contents
|
||||
*
|
||||
* @param path Path to write
|
||||
* @param s Data to write
|
||||
* @return True if entire file was successfully written
|
||||
*/
|
||||
static inline bool writeFile(const char *path,const std::string &s) { return writeFile(path,s.data(),(unsigned int)s.length()); }
|
||||
|
||||
/**
|
||||
* @param c ASCII character to convert
|
||||
* @return Lower case ASCII character or unchanged if not a letter
|
||||
*/
|
||||
static inline char toLower(char c) throw() { return (char)OSUtils::TOLOWER_TABLE[(unsigned long)c]; }
|
||||
|
||||
/**
|
||||
* @return Platform default ZeroTier One home path
|
||||
*/
|
||||
static std::string platformDefaultHomePath();
|
||||
|
||||
#ifndef OMIT_JSON_SUPPORT
|
||||
static nlohmann::json jsonParse(const std::string &buf);
|
||||
static std::string jsonDump(const nlohmann::json &j,int indentation = 1);
|
||||
static uint64_t jsonInt(const nlohmann::json &jv,const uint64_t dfl);
|
||||
static double jsonDouble(const nlohmann::json &jv,const double dfl);
|
||||
static uint64_t jsonIntHex(const nlohmann::json &jv,const uint64_t dfl);
|
||||
static bool jsonBool(const nlohmann::json &jv,const bool dfl);
|
||||
static std::string jsonString(const nlohmann::json &jv,const char *dfl);
|
||||
static std::string jsonBinFromHex(const nlohmann::json &jv);
|
||||
#endif // OMIT_JSON_SUPPORT
|
||||
|
||||
private:
|
||||
static const unsigned char TOLOWER_TABLE[256];
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
+1208
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifdef ZT_USE_MINIUPNPC
|
||||
|
||||
// Uncomment to dump debug messages
|
||||
//#define ZT_PORTMAPPER_TRACE 1
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#define PM_TRACE(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "PortMapper", __VA_ARGS__))
|
||||
#else
|
||||
#define PM_TRACE(...) fprintf(stderr, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../node/Utils.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
#include "PortMapper.hpp"
|
||||
|
||||
// These must be defined to get rid of dynamic export stuff in libminiupnpc and libnatpmp
|
||||
#ifdef __WINDOWS__
|
||||
#ifndef MINIUPNP_STATICLIB
|
||||
#define MINIUPNP_STATICLIB
|
||||
#endif
|
||||
#ifndef STATICLIB
|
||||
#define STATICLIB
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ZT_USE_SYSTEM_MINIUPNPC
|
||||
#include <miniupnpc/miniupnpc.h>
|
||||
#include <miniupnpc/upnpcommands.h>
|
||||
#else
|
||||
#include "../ext/miniupnpc/miniupnpc.h"
|
||||
#include "../ext/miniupnpc/upnpcommands.h"
|
||||
#endif
|
||||
|
||||
#ifdef ZT_USE_SYSTEM_NATPMP
|
||||
#include <natpmp.h>
|
||||
#else
|
||||
#ifdef __ANDROID__
|
||||
#include "natpmp.h"
|
||||
#else
|
||||
#include "../ext/libnatpmp/natpmp.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class PortMapperImpl
|
||||
{
|
||||
public:
|
||||
PortMapperImpl(int localUdpPortToMap,const char *un) :
|
||||
run(true),
|
||||
localPort(localUdpPortToMap),
|
||||
uniqueName(un)
|
||||
{
|
||||
}
|
||||
|
||||
~PortMapperImpl() {}
|
||||
|
||||
void threadMain()
|
||||
throw()
|
||||
{
|
||||
int mode = 0; // 0 == NAT-PMP, 1 == UPnP
|
||||
int retrytime = 500;
|
||||
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
fprintf(stderr,"PortMapper: started for UDP port %d" ZT_EOL_S,localPort);
|
||||
#endif
|
||||
|
||||
while (run) {
|
||||
|
||||
{
|
||||
// use initnatpmp to check if we can bind a port at all
|
||||
natpmp_t _natpmp;
|
||||
int result = initnatpmp(&_natpmp,0,0);
|
||||
if (result == NATPMP_ERR_CANNOTGETGATEWAY || result == NATPMP_ERR_SOCKETERROR) {
|
||||
closenatpmp(&_natpmp);
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: init failed %d. You might not have an internet connection yet. Trying again in %d" ZT_EOL_S, result, retrytime);
|
||||
#endif
|
||||
Thread::sleep(retrytime);
|
||||
retrytime = retrytime * 2;
|
||||
if (retrytime > ZT_PORTMAPPER_REFRESH_DELAY / 10) {
|
||||
retrytime = ZT_PORTMAPPER_REFRESH_DELAY / 10;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
closenatpmp(&_natpmp);
|
||||
retrytime = 500;
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
// NAT-PMP mode (preferred)
|
||||
// ---------------------------------------------------------------------
|
||||
if (mode == 0) {
|
||||
natpmp_t natpmp;
|
||||
natpmpresp_t response;
|
||||
int r = 0;
|
||||
|
||||
bool natPmpSuccess = false;
|
||||
for(int tries=0;tries<60;++tries) {
|
||||
int tryPort = (int)localPort + tries;
|
||||
if (tryPort >= 65535)
|
||||
tryPort = (tryPort - 65535) + 1025;
|
||||
|
||||
memset(&natpmp,0,sizeof(natpmp));
|
||||
memset(&response,0,sizeof(response));
|
||||
|
||||
if (initnatpmp(&natpmp,0,0) != 0) {
|
||||
mode = 1;
|
||||
closenatpmp(&natpmp);
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: NAT-PMP: init failed, switching to UPnP mode" ZT_EOL_S);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
InetAddress publicAddress;
|
||||
sendpublicaddressrequest(&natpmp);
|
||||
int64_t myTimeout = OSUtils::now() + 5000;
|
||||
do {
|
||||
fd_set fds;
|
||||
struct timeval timeout;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(natpmp.s, &fds);
|
||||
getnatpmprequesttimeout(&natpmp, &timeout);
|
||||
select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
|
||||
r = readnatpmpresponseorretry(&natpmp, &response);
|
||||
if (OSUtils::now() >= myTimeout)
|
||||
break;
|
||||
} while (r == NATPMP_TRYAGAIN);
|
||||
if (r == 0) {
|
||||
publicAddress = InetAddress((uint32_t)response.pnu.publicaddress.addr.s_addr,0);
|
||||
} else {
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: NAT-PMP: request for external address failed, aborting..." ZT_EOL_S);
|
||||
#endif
|
||||
closenatpmp(&natpmp);
|
||||
break;
|
||||
}
|
||||
|
||||
sendnewportmappingrequest(&natpmp,NATPMP_PROTOCOL_UDP,localPort,tryPort,(ZT_PORTMAPPER_REFRESH_DELAY * 2) / 1000);
|
||||
myTimeout = OSUtils::now() + 10000;
|
||||
do {
|
||||
fd_set fds;
|
||||
struct timeval timeout;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(natpmp.s, &fds);
|
||||
getnatpmprequesttimeout(&natpmp, &timeout);
|
||||
select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
|
||||
r = readnatpmpresponseorretry(&natpmp, &response);
|
||||
if (OSUtils::now() >= myTimeout)
|
||||
break;
|
||||
} while (r == NATPMP_TRYAGAIN);
|
||||
if (r == 0) {
|
||||
publicAddress.setPort(response.pnu.newportmapping.mappedpublicport);
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
char paddr[128];
|
||||
PM_TRACE("PortMapper: NAT-PMP: mapped %u to %s" ZT_EOL_S,(unsigned int)localPort,publicAddress.toString(paddr));
|
||||
#endif
|
||||
Mutex::Lock sl(surface_l);
|
||||
surface.clear();
|
||||
surface.push_back(publicAddress);
|
||||
natPmpSuccess = true;
|
||||
closenatpmp(&natpmp);
|
||||
break;
|
||||
} else {
|
||||
closenatpmp(&natpmp);
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!natPmpSuccess) {
|
||||
mode = 1;
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: NAT-PMP: request failed, switching to UPnP mode" ZT_EOL_S);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// UPnP mode
|
||||
// ---------------------------------------------------------------------
|
||||
if (mode == 1) {
|
||||
char lanaddr[4096];
|
||||
char externalip[4096]; // no range checking? so make these buffers larger than any UDP packet a uPnP server could send us as a precaution :P
|
||||
char inport[16];
|
||||
char outport[16];
|
||||
struct UPNPUrls urls;
|
||||
struct IGDdatas data;
|
||||
|
||||
int upnpError = 0;
|
||||
UPNPDev *devlist = upnpDiscoverAll(5000,(const char *)0,(const char *)0,0,0,2,&upnpError);
|
||||
if (devlist) {
|
||||
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
{
|
||||
UPNPDev *dev = devlist;
|
||||
while (dev) {
|
||||
PM_TRACE("PortMapper: found UPnP device at URL '%s': %s" ZT_EOL_S,dev->descURL,dev->st);
|
||||
dev = dev->pNext;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(lanaddr,0,sizeof(lanaddr));
|
||||
memset(externalip,0,sizeof(externalip));
|
||||
memset(&urls,0,sizeof(urls));
|
||||
memset(&data,0,sizeof(data));
|
||||
OSUtils::ztsnprintf(inport,sizeof(inport),"%d",localPort);
|
||||
|
||||
int foundValidIGD = 0;
|
||||
if ((foundValidIGD = UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) {
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: my LAN IP address: %s" ZT_EOL_S,lanaddr);
|
||||
#endif
|
||||
if ((UPNP_GetExternalIPAddress(urls.controlURL,data.first.servicetype,externalip) == UPNPCOMMAND_SUCCESS)&&(externalip[0])) {
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: my external IP address: %s" ZT_EOL_S,externalip);
|
||||
#endif
|
||||
|
||||
for(int tries=0;tries<60;++tries) {
|
||||
int tryPort = (int)localPort + tries;
|
||||
if (tryPort >= 65535)
|
||||
tryPort = (tryPort - 65535) + 1025;
|
||||
OSUtils::ztsnprintf(outport,sizeof(outport),"%u",tryPort);
|
||||
|
||||
// First check and see if this port is already mapped to the
|
||||
// same unique name. If so, keep this mapping and don't try
|
||||
// to map again since this can break buggy routers. But don't
|
||||
// fail if this command fails since not all routers support it.
|
||||
{
|
||||
char haveIntClient[128]; // 128 == big enough for all these as per miniupnpc "documentation"
|
||||
char haveIntPort[128];
|
||||
char haveDesc[128];
|
||||
char haveEnabled[128];
|
||||
char haveLeaseDuration[128];
|
||||
memset(haveIntClient,0,sizeof(haveIntClient));
|
||||
memset(haveIntPort,0,sizeof(haveIntPort));
|
||||
memset(haveDesc,0,sizeof(haveDesc));
|
||||
memset(haveEnabled,0,sizeof(haveEnabled));
|
||||
memset(haveLeaseDuration,0,sizeof(haveLeaseDuration));
|
||||
if ((UPNP_GetSpecificPortMappingEntry(urls.controlURL,data.first.servicetype,outport,"UDP",(const char *)0,haveIntClient,haveIntPort,haveDesc,haveEnabled,haveLeaseDuration) == UPNPCOMMAND_SUCCESS)&&(uniqueName == haveDesc)) {
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: reusing previously reserved external port: %s" ZT_EOL_S,outport);
|
||||
#endif
|
||||
Mutex::Lock sl(surface_l);
|
||||
surface.clear();
|
||||
InetAddress tmp(externalip);
|
||||
tmp.setPort(tryPort);
|
||||
surface.push_back(tmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to map this port
|
||||
int mapResult = 0;
|
||||
if ((mapResult = UPNP_AddPortMapping(urls.controlURL,data.first.servicetype,outport,inport,lanaddr,uniqueName.c_str(),"UDP",(const char *)0,"0")) == UPNPCOMMAND_SUCCESS) {
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: reserved external port: %s" ZT_EOL_S,outport);
|
||||
#endif
|
||||
Mutex::Lock sl(surface_l);
|
||||
surface.clear();
|
||||
InetAddress tmp(externalip);
|
||||
tmp.setPort(tryPort);
|
||||
surface.push_back(tmp);
|
||||
break;
|
||||
} else {
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: UPNP_AddPortMapping(%s) failed: %d" ZT_EOL_S,outport,mapResult);
|
||||
#endif
|
||||
Thread::sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
mode = 0;
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: UPNP_GetExternalIPAddress failed, returning to NAT-PMP mode" ZT_EOL_S);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
mode = 0;
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: UPnP: UPNP_GetValidIGD failed, returning to NAT-PMP mode" ZT_EOL_S);
|
||||
#endif
|
||||
}
|
||||
freeUPNPDevlist(devlist);
|
||||
|
||||
if(foundValidIGD) {
|
||||
FreeUPNPUrls(&urls);
|
||||
}
|
||||
} else {
|
||||
mode = 0;
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("PortMapper: upnpDiscover failed, returning to NAT-PMP mode: %d" ZT_EOL_S,upnpError);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
#ifdef ZT_PORTMAPPER_TRACE
|
||||
PM_TRACE("UPNPClient: rescanning in %d ms" ZT_EOL_S,ZT_PORTMAPPER_REFRESH_DELAY);
|
||||
#endif
|
||||
Thread::sleep(ZT_PORTMAPPER_REFRESH_DELAY);
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
volatile bool run;
|
||||
int localPort;
|
||||
std::string uniqueName;
|
||||
|
||||
Mutex surface_l;
|
||||
std::vector<InetAddress> surface;
|
||||
};
|
||||
|
||||
PortMapper::PortMapper(int localUdpPortToMap,const char *uniqueName)
|
||||
{
|
||||
_impl = new PortMapperImpl(localUdpPortToMap,uniqueName);
|
||||
Thread::start(_impl);
|
||||
}
|
||||
|
||||
PortMapper::~PortMapper()
|
||||
{
|
||||
_impl->run = false;
|
||||
}
|
||||
|
||||
std::vector<InetAddress> PortMapper::get() const
|
||||
{
|
||||
Mutex::Lock _l(_impl->surface_l);
|
||||
return _impl->surface;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_USE_MINIUPNPC
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifdef ZT_USE_MINIUPNPC
|
||||
|
||||
#ifndef ZT_PORTMAPPER_HPP
|
||||
#define ZT_PORTMAPPER_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "Thread.hpp"
|
||||
|
||||
/**
|
||||
* How frequently should we refresh our UPNP/NAT-PnP/whatever state?
|
||||
*/
|
||||
#define ZT_PORTMAPPER_REFRESH_DELAY 300000
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class PortMapperImpl;
|
||||
|
||||
/**
|
||||
* UPnP/NAT-PnP port mapping "daemon"
|
||||
*/
|
||||
class PortMapper
|
||||
{
|
||||
friend class PortMapperImpl;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create and start port mapper service
|
||||
*
|
||||
* @param localUdpPortToMap Port we want visible to the outside world
|
||||
* @param name Unique name of this endpoint (based on ZeroTier address)
|
||||
*/
|
||||
PortMapper(int localUdpPortToMap,const char *uniqueName);
|
||||
|
||||
~PortMapper();
|
||||
|
||||
/**
|
||||
* @return All current external mappings for our port
|
||||
*/
|
||||
std::vector<InetAddress> get() const;
|
||||
|
||||
private:
|
||||
PortMapperImpl *_impl;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
|
||||
#endif // ZT_USE_MINIUPNPC
|
||||
@@ -0,0 +1,6 @@
|
||||
OS-Dependent and OS-Interface Things
|
||||
======
|
||||
|
||||
This folder contains stuff that interfaces with the base operating system
|
||||
like Phy for network access and the various OS-specific Ethernet tap
|
||||
drivers.
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_THREAD_HPP
|
||||
#define ZT_THREAD_HPP
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../node/Mutex.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
template<typename C>
|
||||
static DWORD WINAPI ___zt_threadMain(LPVOID lpParam)
|
||||
{
|
||||
try {
|
||||
((C *)lpParam)->threadMain();
|
||||
} catch ( ... ) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class Thread
|
||||
{
|
||||
public:
|
||||
Thread()
|
||||
{
|
||||
_th = NULL;
|
||||
_tid = 0;
|
||||
}
|
||||
|
||||
template<typename C>
|
||||
static inline Thread start(C *instance)
|
||||
{
|
||||
Thread t;
|
||||
t._th = CreateThread(NULL,0,&___zt_threadMain<C>,(LPVOID)instance,0,&t._tid);
|
||||
if (t._th == NULL)
|
||||
throw std::runtime_error("CreateThread() failed");
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline void join(const Thread &t)
|
||||
{
|
||||
if (t._th != NULL) {
|
||||
for(;;) {
|
||||
DWORD ec = STILL_ACTIVE;
|
||||
GetExitCodeThread(t._th,&ec);
|
||||
if (ec == STILL_ACTIVE)
|
||||
WaitForSingleObject(t._th,1000);
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sleep(unsigned long ms)
|
||||
{
|
||||
Sleep((DWORD)ms);
|
||||
}
|
||||
|
||||
// Not available on *nix platforms
|
||||
static inline void cancelIO(const Thread &t)
|
||||
{
|
||||
#if !defined(__MINGW32__) && !defined(__MINGW64__) // CancelSynchronousIo not available in MSYS2
|
||||
if (t._th != NULL)
|
||||
CancelSynchronousIo(t._th);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline operator bool() const { return (_th != NULL); }
|
||||
|
||||
private:
|
||||
HANDLE _th;
|
||||
DWORD _tid;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
template<typename C>
|
||||
static void *___zt_threadMain(void *instance)
|
||||
{
|
||||
try {
|
||||
((C *)instance)->threadMain();
|
||||
} catch ( ... ) {}
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread identifier, and static methods to start and join threads
|
||||
*/
|
||||
class Thread
|
||||
{
|
||||
public:
|
||||
Thread()
|
||||
{
|
||||
memset(this,0,sizeof(Thread));
|
||||
}
|
||||
|
||||
Thread(const Thread &t)
|
||||
{
|
||||
memcpy(this,&t,sizeof(Thread));
|
||||
}
|
||||
|
||||
inline Thread &operator=(const Thread &t)
|
||||
{
|
||||
memcpy(this,&t,sizeof(Thread));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new thread
|
||||
*
|
||||
* @param instance Instance whose threadMain() method gets called by new thread
|
||||
* @return Thread identifier
|
||||
* @throws std::runtime_error Unable to create thread
|
||||
* @tparam C Class containing threadMain()
|
||||
*/
|
||||
template<typename C>
|
||||
static inline Thread start(C *instance)
|
||||
{
|
||||
Thread t;
|
||||
pthread_attr_t tattr;
|
||||
pthread_attr_init(&tattr);
|
||||
// This corrects for systems with abnormally small defaults (musl) and also
|
||||
// shrinks the stack on systems with large defaults to save a bit of memory.
|
||||
pthread_attr_setstacksize(&tattr,ZT_THREAD_MIN_STACK_SIZE);
|
||||
if (pthread_create(&t._tid,&tattr,&___zt_threadMain<C>,instance)) {
|
||||
pthread_attr_destroy(&tattr);
|
||||
throw std::runtime_error("pthread_create() failed, unable to create thread");
|
||||
} else {
|
||||
t._started = true;
|
||||
pthread_attr_destroy(&tattr);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join to a thread, waiting for it to terminate (does nothing on null Thread values)
|
||||
*
|
||||
* @param t Thread to join
|
||||
*/
|
||||
static inline void join(const Thread &t)
|
||||
{
|
||||
if (t._started)
|
||||
pthread_join(t._tid,(void **)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep the current thread
|
||||
*
|
||||
* @param ms Number of milliseconds to sleep
|
||||
*/
|
||||
static inline void sleep(unsigned long ms) { usleep(ms * 1000); }
|
||||
|
||||
inline operator bool() const { return (_started); }
|
||||
|
||||
private:
|
||||
pthread_t _tid;
|
||||
volatile bool _started;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // __WINDOWS__ / !__WINDOWS__
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,353 @@
|
||||
#include "WinDNSHelper.hpp"
|
||||
|
||||
#include <comdef.h>
|
||||
#include <WbemIdl.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <strsafe.h>
|
||||
|
||||
#define MAX_KEY_LENGTH 255
|
||||
#define MAX_VALUE_NAME 16383
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
BOOL RegDelnodeRecurse(HKEY hKeyRoot, LPTSTR lpSubKey)
|
||||
{
|
||||
LPTSTR lpEnd;
|
||||
LONG lResult;
|
||||
DWORD dwSize;
|
||||
TCHAR szName[MAX_PATH];
|
||||
HKEY hKey;
|
||||
FILETIME ftWrite;
|
||||
|
||||
// First, see if we can delete the key without having
|
||||
// to recurse.
|
||||
|
||||
lResult = RegDeleteKey(hKeyRoot, lpSubKey);
|
||||
|
||||
if (lResult == ERROR_SUCCESS)
|
||||
return TRUE;
|
||||
|
||||
lResult = RegOpenKeyEx(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey);
|
||||
|
||||
if (lResult != ERROR_SUCCESS)
|
||||
{
|
||||
if (lResult == ERROR_FILE_NOT_FOUND) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an ending slash and add one if it is missing.
|
||||
|
||||
lpEnd = lpSubKey + lstrlen(lpSubKey);
|
||||
|
||||
if (*(lpEnd - 1) != TEXT('\\'))
|
||||
{
|
||||
*lpEnd = TEXT('\\');
|
||||
lpEnd++;
|
||||
*lpEnd = TEXT('\0');
|
||||
}
|
||||
|
||||
// Enumerate the keys
|
||||
|
||||
dwSize = MAX_PATH;
|
||||
lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL,
|
||||
NULL, NULL, &ftWrite);
|
||||
|
||||
if (lResult == ERROR_SUCCESS)
|
||||
{
|
||||
do {
|
||||
|
||||
*lpEnd = TEXT('\0');
|
||||
StringCchCat(lpSubKey, MAX_PATH * 2, szName);
|
||||
|
||||
if (!RegDelnodeRecurse(hKeyRoot, lpSubKey)) {
|
||||
break;
|
||||
}
|
||||
|
||||
dwSize = MAX_PATH;
|
||||
|
||||
lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL,
|
||||
NULL, NULL, &ftWrite);
|
||||
|
||||
} while (lResult == ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
lpEnd--;
|
||||
*lpEnd = TEXT('\0');
|
||||
|
||||
RegCloseKey(hKey);
|
||||
|
||||
// Try again to delete the key.
|
||||
|
||||
lResult = RegDeleteKey(hKeyRoot, lpSubKey);
|
||||
|
||||
if (lResult == ERROR_SUCCESS)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//*************************************************************
|
||||
//
|
||||
// RegDelnode()
|
||||
//
|
||||
// Purpose: Deletes a registry key and all its subkeys / values.
|
||||
//
|
||||
// Parameters: hKeyRoot - Root key
|
||||
// lpSubKey - SubKey to delete
|
||||
//
|
||||
// Return: TRUE if successful.
|
||||
// FALSE if an error occurs.
|
||||
//
|
||||
//*************************************************************
|
||||
|
||||
BOOL RegDelnode(HKEY hKeyRoot, LPCTSTR lpSubKey)
|
||||
{
|
||||
TCHAR szDelKey[MAX_PATH * 2];
|
||||
|
||||
StringCchCopy(szDelKey, MAX_PATH * 2, lpSubKey);
|
||||
return RegDelnodeRecurse(hKeyRoot, szDelKey);
|
||||
|
||||
}
|
||||
std::vector<std::string> getSubKeys(const char* key)
|
||||
{
|
||||
std::vector<std::string> subkeys;
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
|
||||
key,
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS) {
|
||||
|
||||
TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name
|
||||
DWORD cbName; // size of name string
|
||||
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
|
||||
DWORD cchClassName = MAX_PATH; // size of class string
|
||||
DWORD cSubKeys = 0; // number of subkeys
|
||||
DWORD cbMaxSubKey; // longest subkey size
|
||||
DWORD cchMaxClass; // longest class string
|
||||
DWORD cValues; // number of values for key
|
||||
DWORD cchMaxValue; // longest value name
|
||||
DWORD cbMaxValueData; // longest value data
|
||||
DWORD cbSecurityDescriptor; // size of security descriptor
|
||||
FILETIME ftLastWriteTime; // last write time
|
||||
|
||||
DWORD i, retCode;
|
||||
|
||||
TCHAR achValue[MAX_VALUE_NAME];
|
||||
DWORD cchValue = MAX_VALUE_NAME;
|
||||
|
||||
retCode = RegQueryInfoKey(
|
||||
hKey, // key handle
|
||||
achClass, // buffer for class name
|
||||
&cchClassName, // size of class string
|
||||
NULL, // reserved
|
||||
&cSubKeys, // number of subkeys
|
||||
&cbMaxSubKey, // longest subkey size
|
||||
&cchMaxClass, // longest class string
|
||||
&cValues, // number of values for this key
|
||||
&cchMaxValue, // longest value name
|
||||
&cbMaxValueData, // longest value data
|
||||
&cbSecurityDescriptor, // security descriptor
|
||||
&ftLastWriteTime); // last write time
|
||||
|
||||
for (i = 0; i < cSubKeys; ++i) {
|
||||
cbName = MAX_KEY_LENGTH;
|
||||
retCode = RegEnumKeyEx(
|
||||
hKey,
|
||||
i,
|
||||
achKey,
|
||||
&cbName,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&ftLastWriteTime);
|
||||
if (retCode == ERROR_SUCCESS) {
|
||||
subkeys.push_back(achKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
return subkeys;
|
||||
}
|
||||
|
||||
std::vector<std::string> getValueList(const char* key) {
|
||||
std::vector<std::string> values;
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
|
||||
key,
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS) {
|
||||
|
||||
TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name
|
||||
DWORD cbName; // size of name string
|
||||
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
|
||||
DWORD cchClassName = MAX_PATH; // size of class string
|
||||
DWORD cSubKeys = 0; // number of subkeys
|
||||
DWORD cbMaxSubKey; // longest subkey size
|
||||
DWORD cchMaxClass; // longest class string
|
||||
DWORD cValues; // number of values for key
|
||||
DWORD cchMaxValue; // longest value name
|
||||
DWORD cbMaxValueData; // longest value data
|
||||
DWORD cbSecurityDescriptor; // size of security descriptor
|
||||
FILETIME ftLastWriteTime; // last write time
|
||||
|
||||
DWORD i, retCode;
|
||||
|
||||
TCHAR achValue[MAX_VALUE_NAME];
|
||||
DWORD cchValue = MAX_VALUE_NAME;
|
||||
|
||||
retCode = RegQueryInfoKey(
|
||||
hKey, // key handle
|
||||
achClass, // buffer for class name
|
||||
&cchClassName, // size of class string
|
||||
NULL, // reserved
|
||||
&cSubKeys, // number of subkeys
|
||||
&cbMaxSubKey, // longest subkey size
|
||||
&cchMaxClass, // longest class string
|
||||
&cValues, // number of values for this key
|
||||
&cchMaxValue, // longest value name
|
||||
&cbMaxValueData, // longest value data
|
||||
&cbSecurityDescriptor, // security descriptor
|
||||
&ftLastWriteTime); // last write time
|
||||
|
||||
for (i = 0, retCode = ERROR_SUCCESS; i < cValues; ++i) {
|
||||
cchValue = MAX_VALUE_NAME;
|
||||
achValue[0] = '\0';
|
||||
retCode = RegEnumValue(
|
||||
hKey,
|
||||
i,
|
||||
achValue,
|
||||
&cchValue,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
if (retCode == ERROR_SUCCESS) {
|
||||
values.push_back(achValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
return values;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> WinDNSHelper::hasDNSConfig(uint64_t nwid)
|
||||
{
|
||||
char networkStr[20] = { 0 };
|
||||
sprintf(networkStr, "%.16llx", nwid);
|
||||
|
||||
const char* baseKey = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig";
|
||||
auto subkeys = getSubKeys(baseKey);
|
||||
for (auto it = subkeys.begin(); it != subkeys.end(); ++it) {
|
||||
char sub[MAX_KEY_LENGTH] = { 0 };
|
||||
sprintf(sub, "%s\\%s", baseKey, it->c_str());
|
||||
auto dnsRecords = getValueList(sub);
|
||||
for (auto it2 = dnsRecords.begin(); it2 != dnsRecords.end(); ++it2) {
|
||||
if ((*it2) == "Comment") {
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
|
||||
sub,
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS) {
|
||||
|
||||
char buf[16384] = { 0 };
|
||||
DWORD size = sizeof(buf);
|
||||
DWORD retCode = RegGetValueA(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
sub,
|
||||
it2->c_str(),
|
||||
RRF_RT_REG_SZ,
|
||||
NULL,
|
||||
&buf,
|
||||
&size);
|
||||
if (retCode == ERROR_SUCCESS) {
|
||||
if (std::string(networkStr) == std::string(buf)) {
|
||||
RegCloseKey(hKey);
|
||||
return std::make_pair(true, std::string(sub));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(false, std::string());
|
||||
}
|
||||
|
||||
void WinDNSHelper::setDNS(uint64_t nwid, const char* domain, const std::vector<InetAddress>& servers)
|
||||
{
|
||||
auto hasConfig = hasDNSConfig(nwid);
|
||||
|
||||
std::stringstream ss;
|
||||
for (auto it = servers.begin(); it != servers.end(); ++it) {
|
||||
char ipaddr[256] = { 0 };
|
||||
ss << it->toIpString(ipaddr);
|
||||
if ((it + 1) != servers.end()) {
|
||||
ss << ";";
|
||||
}
|
||||
}
|
||||
std::string serverValue = ss.str();
|
||||
|
||||
if (hasConfig.first) {
|
||||
// update existing config
|
||||
HKEY dnsKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, hasConfig.second.c_str(), 0, KEY_READ | KEY_WRITE, &dnsKey) == ERROR_SUCCESS) {
|
||||
auto retCode = RegSetKeyValueA(dnsKey, NULL, "GenericDNSServers", REG_SZ, serverValue.data(), (DWORD)serverValue.length());
|
||||
if (retCode != ERROR_SUCCESS) {
|
||||
fprintf(stderr, "Error writing dns servers: %d\n", retCode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// add new config
|
||||
const char* baseKey = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig";
|
||||
GUID guid;
|
||||
CoCreateGuid(&guid);
|
||||
wchar_t guidTmp[128] = { 0 };
|
||||
char guidStr[128] = { 0 };
|
||||
StringFromGUID2(guid, guidTmp, 128);
|
||||
wcstombs(guidStr, guidTmp, 128);
|
||||
char fullKey[MAX_KEY_LENGTH] = { 0 };
|
||||
sprintf(fullKey, "%s\\%s", baseKey, guidStr);
|
||||
HKEY dnsKey;
|
||||
RegCreateKeyA(HKEY_LOCAL_MACHINE, fullKey, &dnsKey);
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, fullKey, 0, KEY_READ | KEY_WRITE, &dnsKey) == ERROR_SUCCESS) {
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
RegSetKeyValueA(dnsKey, NULL, "Comment", REG_SZ, nwString, strlen(nwString));
|
||||
|
||||
DWORD configOpts = 8;
|
||||
RegSetKeyValueA(dnsKey, NULL, "ConfigOptions", REG_DWORD, &configOpts, sizeof(DWORD));
|
||||
RegSetKeyValueA(dnsKey, NULL, "DisplayName", REG_SZ, "", 0);
|
||||
RegSetKeyValueA(dnsKey, NULL, "GenericDNSServers", REG_SZ, serverValue.data(), serverValue.length());
|
||||
RegSetKeyValueA(dnsKey, NULL, "IPSECCARestriction", REG_SZ, "", 0);
|
||||
std::string d = "." + std::string(domain);
|
||||
RegSetKeyValueA(dnsKey, NULL, "Name", REG_MULTI_SZ, d.data(), d.length());
|
||||
DWORD version = 2;
|
||||
RegSetKeyValueA(dnsKey, NULL, "Version", REG_DWORD, &version, sizeof(DWORD));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WinDNSHelper::removeDNS(uint64_t nwid)
|
||||
{
|
||||
auto hasConfig = hasDNSConfig(nwid);
|
||||
if (hasConfig.first) {
|
||||
RegDelnode(HKEY_LOCAL_MACHINE, hasConfig.second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef WIN_DNS_HELPER_H_
|
||||
#define WIN_DNS_HELPER_H_
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include "../node/InetAddress.hpp"
|
||||
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
class WinDNSHelper
|
||||
{
|
||||
public:
|
||||
static void setDNS(uint64_t nwid, const char* domain, const std::vector<InetAddress>& servers);
|
||||
static void removeDNS(uint64_t nwid);
|
||||
|
||||
private:
|
||||
static std::pair<bool, std::string> hasDNSConfig(uint64_t nwid);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,172 @@
|
||||
#include "WinFWHelper.hpp"
|
||||
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
|
||||
|
||||
void ZeroTier::WinFWHelper::newICMPRule(const InetAddress& ip, uint64_t nwid)
|
||||
{
|
||||
char nwString[32] = { 0 };
|
||||
char ipbuf[64];
|
||||
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
ip.toString(ipbuf);
|
||||
|
||||
if (ip.isV4()) {
|
||||
WinFWHelper::newICMPv4Rule(ipbuf, nwid);
|
||||
}
|
||||
else {
|
||||
WinFWHelper::newICMPv6Rule(ipbuf, nwid);
|
||||
}
|
||||
}
|
||||
|
||||
void ZeroTier::WinFWHelper::removeICMPRule(const InetAddress& ip, uint64_t nwid)
|
||||
{
|
||||
char nwString[32] = { 0 };
|
||||
char ipbuf[64];
|
||||
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
ip.toString(ipbuf);
|
||||
|
||||
if (ip.isV4()) {
|
||||
WinFWHelper::removeICMPv4Rule(ipbuf, nwid);
|
||||
}
|
||||
else {
|
||||
WinFWHelper::removeICMPv6Rule(ipbuf, nwid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WinFWHelper::newICMPv4Rule(std::string address, uint64_t nwid)
|
||||
{
|
||||
// allows icmp, scoped to a specific ip address and interface name
|
||||
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "New-NetFirewallRule -DisplayName zerotier-icmpv4-)" + nwString2 + address +
|
||||
R"( -InterfaceAlias 'ZeroTier One `[)" + nwString2 + R"(`]')" +
|
||||
" -Protocol ICMPv4 -Action Allow" +
|
||||
" -LocalAddress " + address + "\"\r\n";
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::newICMPv6Rule(std::string address, uint64_t nwid)
|
||||
{
|
||||
// allows icmp, scoped to a specific ip address and interface name
|
||||
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "New-NetFirewallRule -DisplayName zerotier-icmpv6-)" + nwString2 + address +
|
||||
R"( -InterfaceAlias 'ZeroTier One `[)" + nwString2 + R"(`]')" +
|
||||
" -Protocol ICMPv6 -Action Allow" +
|
||||
" -LocalAddress " + address + "\"\r\n";
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::removeICMPv4Rule(std::string addr, uint64_t nwid)
|
||||
{
|
||||
// removes 1 icmp firewall rule
|
||||
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "Remove-NetFirewallRule -DisplayName zerotier-icmpv4-)" + nwString2 + addr +
|
||||
"\"\r\n";
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::removeICMPv6Rule(std::string addr, uint64_t nwid)
|
||||
{
|
||||
// removes 1 icmp firewall rule
|
||||
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "Remove-NetFirewallRule -DisplayName zerotier-icmpv6-)" + nwString2 + addr +
|
||||
"\"\r\n";
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::removeICMPv4Rules(uint64_t nwid)
|
||||
{
|
||||
// removes all icmp firewall rules for this network id
|
||||
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "Remove-NetFirewallRule -DisplayName zerotier-icmpv4-)" + nwString2 + "*\" \r\n";
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::removeICMPv6Rules(uint64_t nwid)
|
||||
{
|
||||
// removes all icmp firewall rules for this network id
|
||||
|
||||
char nwString[32] = { 0 };
|
||||
sprintf(nwString, "%.16llx", nwid);
|
||||
std::string nwString2 = { nwString };
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "Remove-NetFirewallRule -DisplayName zerotier-icmpv6-)" + nwString2 + "*\" \r\n";
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::removeICMPRules()
|
||||
{
|
||||
// removes all icmp firewall rules for all networks
|
||||
|
||||
std::string cmd = R"(C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe "Remove-NetFirewallRule -DisplayName zerotier-icmp*)" + std::string("\r\n");
|
||||
|
||||
_run(cmd);
|
||||
}
|
||||
|
||||
void WinFWHelper::removeICMPRules(uint64_t nwid)
|
||||
{
|
||||
// removes all icmp firewall rules for this network
|
||||
WinFWHelper::removeICMPv4Rules(nwid);
|
||||
WinFWHelper::removeICMPv6Rules(nwid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WinFWHelper::_run(std::string cmd)
|
||||
{
|
||||
|
||||
#ifdef ZT_DEBUG
|
||||
fprintf(stderr, cmd.c_str());
|
||||
#endif
|
||||
|
||||
STARTUPINFOA startupInfo;
|
||||
PROCESS_INFORMATION processInfo;
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
|
||||
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
|
||||
|
||||
if (CreateProcessA(NULL, (LPSTR)cmd.c_str(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo)) {
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace ZeroTier
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef WIN_FW_HELPER_H_
|
||||
#define WIN_FW_HELPER_H_
|
||||
|
||||
#include "../node/InetAddress.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class WinFWHelper {
|
||||
public:
|
||||
static void newICMPRule(const InetAddress& ip, uint64_t nwid);
|
||||
static void removeICMPRule(const InetAddress& ip, uint64_t nwid);
|
||||
static void removeICMPRules(uint64_t nwid);
|
||||
static void removeICMPRules();
|
||||
|
||||
|
||||
private:
|
||||
static void _run(std::string cmd);
|
||||
static void newICMPv4Rule(std::string address, uint64_t nwid);
|
||||
static void newICMPv6Rule(std::string address, uint64_t nwid);
|
||||
static void removeICMPv4Rule(std::string address, uint64_t nwid);
|
||||
static void removeICMPv6Rule(std::string address, uint64_t nwid);
|
||||
static void removeICMPv4Rules(uint64_t nwid);
|
||||
static void removeICMPv6Rules(uint64_t nwid);
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_WINDOWSETHERNETTAP_HPP
|
||||
#define ZT_WINDOWSETHERNETTAP_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <ifdef.h>
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/MulticastGroup.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../osdep/Thread.hpp"
|
||||
#include "EthernetTap.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class WindowsEthernetTap : public EthernetTap
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Installs a new instance of the ZT tap driver
|
||||
*
|
||||
* @param pathToInf Path to zttap driver .inf file
|
||||
* @param deviceInstanceId Buffer to fill with device instance ID on success (and if SetupDiGetDeviceInstanceIdA succeeds, which it should)
|
||||
* @return Empty string on success, otherwise an error message
|
||||
*/
|
||||
static std::string addNewPersistentTapDevice(const char *pathToInf,std::string &deviceInstanceId);
|
||||
|
||||
/**
|
||||
* Uninstalls all persistent tap devices that have legacy drivers
|
||||
*
|
||||
* @return Empty string on success, otherwise an error message
|
||||
*/
|
||||
static std::string destroyAllLegacyPersistentTapDevices();
|
||||
|
||||
/**
|
||||
* Uninstalls all persistent tap devices on the system
|
||||
*
|
||||
* @return Empty string on success, otherwise an error message
|
||||
*/
|
||||
static std::string destroyAllPersistentTapDevices();
|
||||
|
||||
/**
|
||||
* Uninstalls a specific persistent tap device by instance ID
|
||||
*
|
||||
* @param instanceId Device instance ID
|
||||
* @return Empty string on success, otherwise an error message
|
||||
*/
|
||||
static std::string deletePersistentTapDevice(const char *instanceId);
|
||||
|
||||
/**
|
||||
* Disable a persistent tap device by instance ID
|
||||
*
|
||||
* @param instanceId Device instance ID
|
||||
* @param enabled Enable device?
|
||||
* @return True if device was found and disabled
|
||||
*/
|
||||
static bool setPersistentTapDeviceState(const char *instanceId,bool enabled);
|
||||
|
||||
WindowsEthernetTap(
|
||||
const char *hp,
|
||||
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 ~WindowsEthernetTap();
|
||||
|
||||
virtual void setEnabled(bool en);
|
||||
virtual bool enabled() const;
|
||||
virtual bool addIp(const InetAddress &ip);
|
||||
virtual bool removeIp(const InetAddress &ip);
|
||||
virtual std::vector<InetAddress> 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 std::string friendlyName() const;
|
||||
virtual void scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
|
||||
virtual void setMtu(unsigned int mtu);
|
||||
virtual void setDns(const char* domain, const std::vector<InetAddress> &servers);
|
||||
|
||||
inline const NET_LUID &luid() const { return _deviceLuid; }
|
||||
inline const GUID &guid() const { return _deviceGuid; }
|
||||
inline const std::string &instanceId() const { return _deviceInstanceId; }
|
||||
NET_IFINDEX interfaceIndex() const;
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
bool isInitialized() const { return _initialized; };
|
||||
|
||||
private:
|
||||
NET_IFINDEX _getDeviceIndex(); // throws on failure
|
||||
std::vector<std::string> _getRegistryIPv4Value(const char *regKey);
|
||||
void _setRegistryIPv4Value(const char *regKey,const std::vector<std::string> &value);
|
||||
void _syncIps();
|
||||
|
||||
void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
|
||||
void *_arg;
|
||||
MAC _mac;
|
||||
uint64_t _nwid;
|
||||
volatile unsigned int _mtu;
|
||||
Thread _thread;
|
||||
|
||||
volatile HANDLE _tap;
|
||||
HANDLE _injectSemaphore;
|
||||
|
||||
GUID _deviceGuid;
|
||||
NET_LUID _deviceLuid;
|
||||
std::string _netCfgInstanceId;
|
||||
std::string _deviceInstanceId;
|
||||
std::string _mySubkeyName;
|
||||
std::string _friendlyName;
|
||||
Mutex _friendlyName_m;
|
||||
|
||||
std::vector<InetAddress> _assignedIps; // IPs assigned with addIp
|
||||
Mutex _assignedIps_m;
|
||||
|
||||
std::vector<MulticastGroup> _multicastGroups;
|
||||
|
||||
struct _InjectPending
|
||||
{
|
||||
unsigned int len;
|
||||
char data[ZT_MAX_MTU + 32];
|
||||
};
|
||||
std::queue<_InjectPending> _injectPending;
|
||||
Mutex _injectPending_m;
|
||||
|
||||
std::string _pathToHelpers;
|
||||
|
||||
volatile bool _run;
|
||||
volatile bool _initialized;
|
||||
volatile bool _enabled;
|
||||
|
||||
mutable std::vector<InetAddress> _ifaddrs;
|
||||
mutable uint64_t _lastIfAddrsUpdate;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
this BSD licensed code is from https://github.com/freebsd/freebsd/blob/386ddae58459341ec567604707805814a2128a57/lib/libc/net/getifmaddrs.c
|
||||
as in older OS X there is no getifmaddrs() and related functions is NetBSD
|
||||
*/
|
||||
|
||||
#define NET_RT_IFMALIST 4 /* return multicast address list */
|
||||
#define RTM_NEWMADDR 0xf /* mcast group membership being added to if */
|
||||
|
||||
|
||||
/*
|
||||
* Copyright (c) 2003 Bruce M. Simpson.
|
||||
* All rights reserved
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
/*
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include "namespace.h"*/
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/route.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
//#include "un-namespace.h"
|
||||
|
||||
#define SALIGN (sizeof(long) - 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)
|
||||
|
||||
int
|
||||
getifmaddrs(struct 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_msghdr *ifmam;
|
||||
struct 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] = NET_RT_IFMALIST;
|
||||
mib[5] = 0; /* no flags */
|
||||
do {
|
||||
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
|
||||
return (-1);
|
||||
if ((buf = 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 RTM_NEWMADDR:
|
||||
ifmam = (struct ifma_msghdr *)(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 = malloc(sizeof(struct ifmaddrs) * icnt + dcnt);
|
||||
if (data == NULL) {
|
||||
free(buf);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
ifa = (struct ifmaddrs *)(void *)data;
|
||||
data += sizeof(struct ifmaddrs) * icnt;
|
||||
|
||||
memset(ifa, 0, sizeof(struct 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 RTM_NEWMADDR:
|
||||
ifmam = (struct ifma_msghdr *)(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);
|
||||
}
|
||||
|
||||
void
|
||||
freeifmaddrs(struct ifmaddrs *ifmp)
|
||||
{
|
||||
|
||||
free(ifmp);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
this BSD code is from https://github.com/freebsd/freebsd/blob/386ddae58459341ec567604707805814a2128a57/include/ifaddrs.h
|
||||
as in older OS X there is no getifmaddrs() and related functions is NetBSD
|
||||
*/
|
||||
|
||||
/* $FreeBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 1995, 1999
|
||||
* Berkeley Software Design, Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp
|
||||
*/
|
||||
|
||||
#ifndef _freebsd_getifmaddrs.h_
|
||||
#define _freebsd_getifmaddrs.h_
|
||||
|
||||
/*
|
||||
* This may have been defined in <net/if.h>. Note that if <net/if.h> is
|
||||
* to be included it must be included before this header file.
|
||||
*/
|
||||
#ifndef ifa_broadaddr
|
||||
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
|
||||
#endif
|
||||
|
||||
struct ifmaddrs {
|
||||
struct ifmaddrs *ifma_next;
|
||||
struct sockaddr *ifma_name;
|
||||
struct sockaddr *ifma_addr;
|
||||
struct sockaddr *ifma_lladdr;
|
||||
};
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
|
||||
/*
|
||||
* Message format for use in obtaining information about multicast addresses
|
||||
* from the routing socket.
|
||||
*/
|
||||
struct ifma_msghdr {
|
||||
int ifmam_msglen; /* to skip over non-understood messages */
|
||||
int ifmam_version; /* future binary compatibility */
|
||||
int ifmam_type; /* message type */
|
||||
int ifmam_addrs; /* like rtm_addrs */
|
||||
int ifmam_flags; /* value of ifa_flags */
|
||||
int ifmam_index; /* index for associated ifp */
|
||||
};
|
||||
|
||||
|
||||
extern int getifaddrs(struct ifaddrs **);
|
||||
extern void freeifaddrs(struct ifaddrs *);
|
||||
extern int getifmaddrs(struct ifmaddrs **);
|
||||
extern void freeifmaddrs(struct ifmaddrs *);
|
||||
#include "freebsd_getifmaddrs.c"
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user