You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
434 lines
12 KiB
434 lines
12 KiB
/* |
|
* ZeroTier One - Network Virtualization Everywhere |
|
* Copyright (C) 2011-2019 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 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. |
|
* |
|
* Benchmarks show that this performs similarly to the old tap.kext driver, |
|
* and a kext is no longer required. Splitting it off into an agent will |
|
* also make it easier to have zerotier-one itself drop permissions. |
|
* |
|
* 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. |
|
* |
|
* 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 <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 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 = 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_ndrvfd >= 0) |
|
close(s_ndrvfd); |
|
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]; |
|
|
|
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_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(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; |
|
} |
|
} |
|
|
|
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; |
|
}
|
|
|