438 lines
10 KiB
Diff
438 lines
10 KiB
Diff
|
|
--- tinc-1.0.37/src/bsd/device.c 2025-11-08 15:46:26
|
||
|
|
+++ tinc-1.0.37-patched/src/bsd/device.c 2026-03-21 14:13:44
|
||
|
|
@@ -39,6 +39,11 @@
|
||
|
|
#include <net/if_utun.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+#include <net/bpf.h>
|
||
|
|
+#include <net/ndrv.h>
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
#define DEFAULT_TUN_DEVICE "/dev/tun0"
|
||
|
|
#define DEFAULT_TAP_DEVICE "/dev/tap0"
|
||
|
|
|
||
|
|
@@ -50,6 +55,9 @@
|
||
|
|
DEVICE_TYPE_TUNEMU,
|
||
|
|
#endif
|
||
|
|
DEVICE_TYPE_UTUN,
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+ DEVICE_TYPE_FETH,
|
||
|
|
+#endif
|
||
|
|
} device_type_t;
|
||
|
|
|
||
|
|
int device_fd = -1;
|
||
|
|
@@ -64,8 +72,302 @@
|
||
|
|
static device_type_t device_type = DEVICE_TYPE_TUNIFHEAD;
|
||
|
|
#else
|
||
|
|
static device_type_t device_type = DEVICE_TYPE_TUN;
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+/* feth (fake ethernet) device support for macOS 10.13+
|
||
|
|
+ *
|
||
|
|
+ * Uses undocumented feth interfaces (like Linux veth pairs) to provide
|
||
|
|
+ * Layer 2 / TAP functionality without kernel extensions.
|
||
|
|
+ *
|
||
|
|
+ * Architecture:
|
||
|
|
+ * feth<N> (primary) - gets IP assignments, used by tinc-up scripts
|
||
|
|
+ * feth<N+5000> (peer) - used for packet I/O via BPF read + AF_NDRV write
|
||
|
|
+ *
|
||
|
|
+ * Based on ZeroTier's MacEthernetTapAgent approach.
|
||
|
|
+ */
|
||
|
|
+
|
||
|
|
+#define FETH_BPF_BUFSIZE 131072
|
||
|
|
+#define FETH_PEER_OFFSET 5000
|
||
|
|
+#define DEFAULT_FETH_NUM 0
|
||
|
|
+
|
||
|
|
+static int feth_ndrv_fd = -1;
|
||
|
|
+static int feth_num = -1;
|
||
|
|
+static char feth_primary[IFNAMSIZ];
|
||
|
|
+static char feth_peer[IFNAMSIZ];
|
||
|
|
+static uint8_t feth_bpf_buf[FETH_BPF_BUFSIZE];
|
||
|
|
+static int feth_bpf_buf_len = 0;
|
||
|
|
+static int feth_bpf_buf_offset = 0;
|
||
|
|
+
|
||
|
|
+static bool feth_run_cmd(const char *cmd) {
|
||
|
|
+ ifdebug(TRAFFIC) logger(LOG_DEBUG, "Executing: %s", cmd);
|
||
|
|
+
|
||
|
|
+ int ret = system(cmd);
|
||
|
|
+
|
||
|
|
+ if(ret != 0) {
|
||
|
|
+ logger(LOG_ERR, "Command failed (exit %d): %s", ret, cmd);
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return true;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static bool setup_feth(void) {
|
||
|
|
+ char cmd[512];
|
||
|
|
+
|
||
|
|
+ /* Parse device number from Device config (e.g. "feth0" -> 0) */
|
||
|
|
+ feth_num = DEFAULT_FETH_NUM;
|
||
|
|
+
|
||
|
|
+ if(device) {
|
||
|
|
+ char *p = strstr(device, "feth");
|
||
|
|
+
|
||
|
|
+ if(p) {
|
||
|
|
+ int n = atoi(p + 4);
|
||
|
|
+
|
||
|
|
+ if(n >= 0 && n < FETH_PEER_OFFSET) {
|
||
|
|
+ feth_num = n;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ snprintf(feth_primary, sizeof(feth_primary), "feth%d", feth_num);
|
||
|
|
+ snprintf(feth_peer, sizeof(feth_peer), "feth%d", feth_num + FETH_PEER_OFFSET);
|
||
|
|
+
|
||
|
|
+ /* Create feth pair */
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s create", feth_peer);
|
||
|
|
+
|
||
|
|
+ if(!feth_run_cmd(cmd)) {
|
||
|
|
+ logger(LOG_ERR, "Could not create feth peer interface %s", feth_peer);
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ usleep(10000); /* Brief delay for interface to stabilize */
|
||
|
|
+
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s create", feth_primary);
|
||
|
|
+
|
||
|
|
+ if(!feth_run_cmd(cmd)) {
|
||
|
|
+ logger(LOG_ERR, "Could not create feth primary interface %s", feth_primary);
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s destroy 2>/dev/null", feth_peer);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /* Peer the interfaces */
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s peer %s", feth_peer, feth_primary);
|
||
|
|
+
|
||
|
|
+ if(!feth_run_cmd(cmd)) {
|
||
|
|
+ logger(LOG_ERR, "Could not peer %s with %s", feth_peer, feth_primary);
|
||
|
|
+ goto cleanup;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /* Set MTU */
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s mtu 1500", feth_primary);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s mtu 1500", feth_peer);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+
|
||
|
|
+ /* Bring interfaces up */
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s up", feth_primary);
|
||
|
|
+
|
||
|
|
+ if(!feth_run_cmd(cmd)) {
|
||
|
|
+ goto cleanup;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s up", feth_peer);
|
||
|
|
+
|
||
|
|
+ if(!feth_run_cmd(cmd)) {
|
||
|
|
+ goto cleanup;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /* Open BPF device */
|
||
|
|
+ char bpf_path[32];
|
||
|
|
+
|
||
|
|
+ for(int i = 1; i < 5000; i++) {
|
||
|
|
+ snprintf(bpf_path, sizeof(bpf_path), "/dev/bpf%d", i);
|
||
|
|
+ device_fd = open(bpf_path, O_RDWR);
|
||
|
|
+
|
||
|
|
+ if(device_fd >= 0) {
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if(device_fd < 0) {
|
||
|
|
+ logger(LOG_ERR, "Could not open any BPF device: %s", strerror(errno));
|
||
|
|
+ goto cleanup;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+#ifdef FD_CLOEXEC
|
||
|
|
+ fcntl(device_fd, F_SETFD, FD_CLOEXEC);
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
+ /* Configure BPF - order matters: BIOCSBLEN must come before BIOCSETIF */
|
||
|
|
+ int bpf_bufsize = FETH_BPF_BUFSIZE;
|
||
|
|
+
|
||
|
|
+ if(ioctl(device_fd, BIOCSBLEN, &bpf_bufsize) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not set BPF buffer size: %s", strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ struct ifreq ifr;
|
||
|
|
+ memset(&ifr, 0, sizeof(ifr));
|
||
|
|
+ strlcpy(ifr.ifr_name, feth_peer, sizeof(ifr.ifr_name));
|
||
|
|
+
|
||
|
|
+ if(ioctl(device_fd, BIOCSETIF, &ifr) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not bind BPF to %s: %s", feth_peer, strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ int enable = 1;
|
||
|
|
+
|
||
|
|
+ if(ioctl(device_fd, BIOCIMMEDIATE, &enable) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not set BIOCIMMEDIATE: %s", strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ int disable = 0;
|
||
|
|
+
|
||
|
|
+ if(ioctl(device_fd, BIOCSSEESENT, &disable) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not set BIOCSSEESENT: %s", strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if(ioctl(device_fd, BIOCSHDRCMPLT, &enable) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not set BIOCSHDRCMPLT: %s", strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if(ioctl(device_fd, BIOCPROMISC, NULL) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not set BPF promiscuous mode: %s", strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /* Set non-blocking mode for integration with tinc's event loop */
|
||
|
|
+ fcntl(device_fd, F_SETFL, O_NONBLOCK);
|
||
|
|
+
|
||
|
|
+ /* Open AF_NDRV socket for writing packets */
|
||
|
|
+ feth_ndrv_fd = socket(PF_NDRV, SOCK_RAW, 0);
|
||
|
|
+
|
||
|
|
+ if(feth_ndrv_fd < 0) {
|
||
|
|
+ logger(LOG_ERR, "Could not create AF_NDRV socket: %s", strerror(errno));
|
||
|
|
+ goto cleanup_bpf;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+#ifdef FD_CLOEXEC
|
||
|
|
+ fcntl(feth_ndrv_fd, F_SETFD, FD_CLOEXEC);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
+ struct sockaddr_ndrv sa_ndrv;
|
||
|
|
+ memset(&sa_ndrv, 0, sizeof(sa_ndrv));
|
||
|
|
+ sa_ndrv.snd_len = sizeof(sa_ndrv);
|
||
|
|
+ sa_ndrv.snd_family = AF_NDRV;
|
||
|
|
+ strlcpy((char *)sa_ndrv.snd_name, feth_peer, sizeof(sa_ndrv.snd_name));
|
||
|
|
+
|
||
|
|
+ if(bind(feth_ndrv_fd, (struct sockaddr *)&sa_ndrv, sizeof(sa_ndrv)) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not bind AF_NDRV socket to %s: %s", feth_peer, strerror(errno));
|
||
|
|
+ goto cleanup_ndrv;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if(connect(feth_ndrv_fd, (struct sockaddr *)&sa_ndrv, sizeof(sa_ndrv)) == -1) {
|
||
|
|
+ logger(LOG_ERR, "Could not connect AF_NDRV socket to %s: %s", feth_peer, strerror(errno));
|
||
|
|
+ goto cleanup_ndrv;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /* Set interface name for tinc-up scripts */
|
||
|
|
+ iface = xstrdup(feth_primary);
|
||
|
|
+ device_info = "macOS feth device";
|
||
|
|
+
|
||
|
|
+ /* Initialize BPF read buffer state */
|
||
|
|
+ feth_bpf_buf_len = 0;
|
||
|
|
+ feth_bpf_buf_offset = 0;
|
||
|
|
+
|
||
|
|
+ logger(LOG_INFO, "%s is a %s (peer: %s, bpf: %s)", feth_primary, device_info, feth_peer, bpf_path);
|
||
|
|
+
|
||
|
|
+ return true;
|
||
|
|
+
|
||
|
|
+cleanup_ndrv:
|
||
|
|
+ close(feth_ndrv_fd);
|
||
|
|
+ feth_ndrv_fd = -1;
|
||
|
|
+cleanup_bpf:
|
||
|
|
+ close(device_fd);
|
||
|
|
+ device_fd = -1;
|
||
|
|
+cleanup:
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s destroy 2>/dev/null", feth_primary);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s destroy 2>/dev/null", feth_peer);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+ return false;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void close_feth(void) {
|
||
|
|
+ char cmd[256];
|
||
|
|
+
|
||
|
|
+ if(feth_ndrv_fd >= 0) {
|
||
|
|
+ close(feth_ndrv_fd);
|
||
|
|
+ feth_ndrv_fd = -1;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if(device_fd >= 0) {
|
||
|
|
+ close(device_fd);
|
||
|
|
+ device_fd = -1;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s destroy 2>/dev/null", feth_primary);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s destroy 2>/dev/null", feth_peer);
|
||
|
|
+ feth_run_cmd(cmd);
|
||
|
|
+
|
||
|
|
+ logger(LOG_INFO, "Destroyed feth pair %s/%s", feth_primary, feth_peer);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static bool read_feth_packet(vpn_packet_t *packet) {
|
||
|
|
+ /* BPF returns multiple packets per read() in a buffer with bpf_hdr prefixes.
|
||
|
|
+ * We maintain a static buffer and return one packet per call. */
|
||
|
|
+
|
||
|
|
+ if(feth_bpf_buf_offset >= feth_bpf_buf_len) {
|
||
|
|
+ /* Buffer exhausted, read more from BPF */
|
||
|
|
+ feth_bpf_buf_len = read(device_fd, feth_bpf_buf, FETH_BPF_BUFSIZE);
|
||
|
|
+
|
||
|
|
+ if(feth_bpf_buf_len <= 0) {
|
||
|
|
+ if(errno == EAGAIN || errno == EWOULDBLOCK) {
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ logger(LOG_ERR, "Error reading from BPF: %s", strerror(errno));
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ feth_bpf_buf_offset = 0;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /* Parse next packet from buffer */
|
||
|
|
+ struct bpf_hdr *bh = (struct bpf_hdr *)(feth_bpf_buf + feth_bpf_buf_offset);
|
||
|
|
+ uint8_t *frame = feth_bpf_buf + feth_bpf_buf_offset + bh->bh_hdrlen;
|
||
|
|
+ int caplen = bh->bh_caplen;
|
||
|
|
+
|
||
|
|
+ /* Advance offset to next packet (word-aligned) */
|
||
|
|
+ feth_bpf_buf_offset += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
|
||
|
|
+
|
||
|
|
+ if(caplen <= 0 || caplen > MTU) {
|
||
|
|
+ ifdebug(TRAFFIC) logger(LOG_DEBUG, "Dropping oversized feth packet (%d bytes)", caplen);
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ memcpy(packet->data, frame, caplen);
|
||
|
|
+ packet->len = caplen;
|
||
|
|
+
|
||
|
|
+ return true;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static bool write_feth_packet(vpn_packet_t *packet) {
|
||
|
|
+ if(write(feth_ndrv_fd, packet->data, packet->len) < 0) {
|
||
|
|
+ logger(LOG_ERR, "Error writing to feth AF_NDRV: %s", strerror(errno));
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return true;
|
||
|
|
+}
|
||
|
|
+#endif /* HAVE_DARWIN */
|
||
|
|
+
|
||
|
|
#ifdef HAVE_NET_IF_UTUN_H
|
||
|
|
static bool setup_utun(void) {
|
||
|
|
device_fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
|
||
|
|
@@ -154,6 +456,12 @@
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+ else if(!strcasecmp(type, "feth")) {
|
||
|
|
+ device_type = DEVICE_TYPE_FETH;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+#endif
|
||
|
|
else if(!strcasecmp(type, "tunnohead")) {
|
||
|
|
device_type = DEVICE_TYPE_TUN;
|
||
|
|
} else if(!strcasecmp(type, "tunifhead")) {
|
||
|
|
@@ -169,6 +477,12 @@
|
||
|
|
|
||
|
|
if(strncmp(device, "utun", 4) == 0 || strncmp(device, "/dev/utun", 9) == 0) {
|
||
|
|
device_type = DEVICE_TYPE_UTUN;
|
||
|
|
+ } else
|
||
|
|
+#endif
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+
|
||
|
|
+ if(strncmp(device, "feth", 4) == 0) {
|
||
|
|
+ device_type = DEVICE_TYPE_FETH;
|
||
|
|
} else
|
||
|
|
#endif
|
||
|
|
if(strstr(device, "tap") || routing_mode != RMODE_ROUTER) {
|
||
|
|
@@ -176,11 +490,22 @@
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+
|
||
|
|
+ if(routing_mode == RMODE_SWITCH && device_type != DEVICE_TYPE_TAP && device_type != DEVICE_TYPE_FETH) {
|
||
|
|
+ logger(LOG_ERR, "Only tap or feth devices support switch mode!");
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+#else
|
||
|
|
+
|
||
|
|
if(routing_mode == RMODE_SWITCH && device_type != DEVICE_TYPE_TAP) {
|
||
|
|
logger(LOG_ERR, "Only tap devices support switch mode!");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
// Open the device
|
||
|
|
|
||
|
|
switch(device_type) {
|
||
|
|
@@ -197,7 +522,12 @@
|
||
|
|
case DEVICE_TYPE_UTUN:
|
||
|
|
return setup_utun();
|
||
|
|
#endif
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
|
||
|
|
+ case DEVICE_TYPE_FETH:
|
||
|
|
+ return setup_feth();
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
default:
|
||
|
|
device_fd = open(device, O_RDWR | O_NONBLOCK);
|
||
|
|
}
|
||
|
|
@@ -336,7 +666,13 @@
|
||
|
|
tunemu_close(device_fd);
|
||
|
|
break;
|
||
|
|
#endif
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
|
||
|
|
+ case DEVICE_TYPE_FETH:
|
||
|
|
+ close_feth();
|
||
|
|
+ break;
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
default:
|
||
|
|
close(device_fd);
|
||
|
|
}
|
||
|
|
@@ -427,6 +763,16 @@
|
||
|
|
packet->len = lenin;
|
||
|
|
break;
|
||
|
|
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+
|
||
|
|
+ case DEVICE_TYPE_FETH:
|
||
|
|
+ if(!read_feth_packet(packet)) {
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ break;
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
@@ -506,6 +852,16 @@
|
||
|
|
break;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
+#ifdef HAVE_DARWIN
|
||
|
|
+
|
||
|
|
+ case DEVICE_TYPE_FETH:
|
||
|
|
+ if(!write_feth_packet(packet)) {
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ break;
|
||
|
|
+#endif
|
||
|
|
+
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|