OSX no longer requires the kext due to feth black magic! The MacEthernetTapAgent must be installed in /Library/Application Support/ZeroTier/One for ZT to work now. Eventually this can let us do an app bundle, get rid of the pkg, and have ZT itself run with normal or reduced privileges. Also fixes GitHub issue #870 (at least for me) and may be faster than the old kext.

This commit is contained in:
Adam Ierymenko 2018-10-25 12:43:30 -07:00
commit 2e44b90f63
7 changed files with 957 additions and 10 deletions

404
osdep/MacEthernetTapAgent.c Normal file
View file

@ -0,0 +1,404 @@
/*
* ZeroTier One - Network Virtualization Everywhere
* Copyright (C) 2011-2018 ZeroTier, Inc. https://www.zerotier.com/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial closed-source software that incorporates or links
* directly against ZeroTier software without disclosing the source code
* of your own application.
*/
/* This is the agent program that is executed with setuid privileges to
* actually manage feth pairs. Its execution in this manner allows ZT
* itself to drop privileges on Mac. */
#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 <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[262144] __attribute__ ((__aligned__(16)));
static unsigned char s_stdinReadBuf[262144] __attribute__ ((__aligned__(16)));
static char s_deviceName[IFNAMSIZ];
static char s_peerDeviceName[IFNAMSIZ];
static int s_bpffd = -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 = vfork();
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_bpffd >= 0)
close(s_bpffd);
if (s_deviceName[0])
run("/sbin/ifconfig",s_deviceName,"destroy",(char *)0);
if (s_peerDeviceName[0])
run("/sbin/ifconfig",s_peerDeviceName,"destroy",(char *)0);
}
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);
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];
int ndrvSocket = socket(AF_NDRV,SOCK_RAW,0);
if (ndrvSocket < 0) {
fprintf(stderr,"E unable to open AF_NDRV socket\n");
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_UNABLE_TO_CREATE;
}
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);
snprintf(s_deviceName,sizeof(s_deviceName),"feth%d",deviceNo);
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","10000","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(ndrvSocket,(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(ndrvSocket,(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;
}
fcntl(STDIN_FILENO,F_SETFL,fcntl(STDIN_FILENO,F_GETFL)|O_NONBLOCK);
fcntl(ndrvSocket,F_SETFL,fcntl(ndrvSocket,F_GETFL)|O_NONBLOCK);
fcntl(s_bpffd,F_SETFL,fcntl(s_bpffd,F_GETFL)|O_NONBLOCK);
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) {
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);
}
} else {
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_READ_ERROR;
}
}
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(ndrvSocket,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 = vfork();
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;
}
}
if (stdinReadPtr > (len + 2)) {
memmove(s_stdinReadBuf,s_stdinReadBuf + len + 2,stdinReadPtr -= (len + 2));
} else {
stdinReadPtr = 0;
}
} else {
break;
}
}
} else {
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_READ_ERROR;
}
}
}
return ZT_MACETHERNETTAPAGENT_EXIT_CODE_SUCCESS;
}