diff --git a/meson.build b/meson.build
index 155421986..8fab6160a 100644
--- a/meson.build
+++ b/meson.build
@@ -25,6 +25,7 @@ opt_static = get_option('static')
opt_systemd = get_option('systemd')
opt_tests = get_option('tests')
opt_tunemu = get_option('tunemu')
+opt_vmnet = get_option('vmnet')
opt_uml = get_option('uml')
opt_vde = get_option('vde')
opt_zlib = get_option('zlib')
diff --git a/meson_options.txt b/meson_options.txt
index 0df57950c..412ee5430 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -79,6 +79,11 @@ option('tunemu',
value: 'auto',
description: 'support for the tunemu driver')
+option('vmnet',
+ type: 'feature',
+ value: 'auto',
+ description: 'support for the vmnet driver')
+
option('vde',
type: 'feature',
value: 'auto',
diff --git a/src/bsd/darwin/meson.build b/src/bsd/darwin/meson.build
index f7dc99e25..3b5e4b9e0 100644
--- a/src/bsd/darwin/meson.build
+++ b/src/bsd/darwin/meson.build
@@ -1,5 +1,6 @@
dep_tunemu = dependency('tunemu', required: opt_tunemu, static: static)
dep_pcap = dependency('pcap', required: opt_tunemu, static: static)
+dep_vmnet = dependency('vmnet', required: opt_vmnet, static: static)
if dep_tunemu.found() and dep_pcap.found()
deps_tincd += [dep_tunemu, dep_pcap]
@@ -7,6 +8,11 @@ if dep_tunemu.found() and dep_pcap.found()
cdata.set('ENABLE_TUNEMU', 1)
endif
+if dep_vmnet.found()
+ deps_tincd += [dep_vmnet]
+ src_tincd += files('vmnet.c')
+ cdata.set('ENABLE_VMNET', 1)
+endif
+
# macOS apparently doesn't support kqueue with TAP devices
src_tincd += src_event_select
-
diff --git a/src/bsd/darwin/vmnet.c b/src/bsd/darwin/vmnet.c
new file mode 100644
index 000000000..5eceba201
--- /dev/null
+++ b/src/bsd/darwin/vmnet.c
@@ -0,0 +1,204 @@
+/*
+ * vmnet - Tun device emulation for Darwin
+ * Copyright (C) 2024 Eric Karge
+ *
+ * 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 .
+ *
+ */
+
+#include "vmnet.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include "../../logger.h"
+#include
+
+static volatile vmnet_return_t if_status = VMNET_SETUP_INCOMPLETE;
+static dispatch_queue_t if_queue;
+static interface_ref vmnet_if;
+static size_t max_packet_size;
+static struct iovec read_iov_in;
+static int read_socket[2];
+
+static void macos_vmnet_read(void);
+static const char *str_vmnet_status(vmnet_return_t status);
+
+int macos_vmnet_open(void) {
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, read_socket)) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to create socket: %s", strerror(errno));
+ return -1;
+ }
+
+ if_queue = dispatch_queue_create("org.tinc-vpn.vmnet.if_queue", DISPATCH_QUEUE_SERIAL);
+
+ xpc_object_t if_desc = xpc_dictionary_create(NULL, NULL, 0);
+ xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_HOST_MODE);
+ xpc_dictionary_set_bool(if_desc, vmnet_enable_isolation_key, 0);
+ xpc_dictionary_set_bool(if_desc, vmnet_allocate_mac_address_key, false);
+
+ dispatch_semaphore_t if_started_sem = dispatch_semaphore_create(0);
+ vmnet_if = vmnet_start_interface(
+ if_desc, if_queue,
+ ^(vmnet_return_t status, xpc_object_t interface_param) {
+ if_status = status;
+ if (status == VMNET_SUCCESS && interface_param) {
+ max_packet_size = xpc_dictionary_get_uint64(interface_param, vmnet_max_packet_size_key);
+ }
+ dispatch_semaphore_signal(if_started_sem);
+ });
+ dispatch_semaphore_wait(if_started_sem, DISPATCH_TIME_FOREVER);
+ dispatch_release(if_started_sem);
+
+ xpc_release(if_desc);
+
+ if(if_status != VMNET_SUCCESS) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to create vmnet device: %s", str_vmnet_status(if_status));
+ return -1;
+ }
+
+ read_iov_in.iov_base = malloc(max_packet_size);
+ read_iov_in.iov_len = max_packet_size;
+
+ vmnet_interface_set_event_callback(
+ vmnet_if, VMNET_INTERFACE_PACKETS_AVAILABLE, if_queue,
+ ^(interface_event_t event_type, xpc_object_t event) {
+ macos_vmnet_read();
+ });
+
+ return read_socket[0];
+}
+
+int macos_vmnet_close(int fd) {
+ if (vmnet_if == NULL || fd != read_socket[0]) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to close vmnet device: device not setup properly");
+ return -1;
+ }
+
+ vmnet_interface_set_event_callback(vmnet_if, VMNET_INTERFACE_PACKETS_AVAILABLE, NULL, NULL);
+
+ dispatch_semaphore_t if_stopped_sem = dispatch_semaphore_create(0);
+ vmnet_stop_interface(
+ vmnet_if, if_queue,
+ ^(vmnet_return_t status) {
+ if_status = status;
+ dispatch_semaphore_signal(if_stopped_sem);
+ });
+ dispatch_semaphore_wait(if_stopped_sem, DISPATCH_TIME_FOREVER);
+ dispatch_release(if_stopped_sem);
+
+ if (if_status != VMNET_SUCCESS) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to close vmnet device: %s", str_vmnet_status(if_status));
+ return -1;
+ }
+ if_status = VMNET_SETUP_INCOMPLETE;
+
+ dispatch_release(if_queue);
+
+ read_iov_in.iov_len = 0;
+ free(read_iov_in.iov_base);
+ read_iov_in.iov_base = NULL;
+
+ close(read_socket[0]);
+ close(read_socket[1]);
+
+ return 0;
+}
+
+void macos_vmnet_read(void) {
+ if (if_status != VMNET_SUCCESS) {
+ return;
+ }
+
+ int pkt_count = 1;
+ struct vmpktdesc packet = {
+ .vm_flags = 0,
+ .vm_pkt_size = max_packet_size,
+ .vm_pkt_iov = &read_iov_in,
+ .vm_pkt_iovcnt = 1,
+ };
+
+ if_status = vmnet_read(vmnet_if, &packet, &pkt_count);
+ if (if_status != VMNET_SUCCESS) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read packet: %s", str_vmnet_status(if_status));
+ return;
+ }
+
+ if ( pkt_count && packet.vm_pkt_iovcnt ) {
+ struct iovec iov_out = {
+ .iov_base = packet.vm_pkt_iov->iov_base,
+ .iov_len = packet.vm_pkt_size,
+ };
+ if(writev(read_socket[1], &iov_out, 1) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to write to read socket: %s", strerror(errno));
+ return;
+ }
+ }
+}
+
+ssize_t macos_vmnet_write(uint8_t *buffer, size_t buflen) {
+ if (buflen > max_packet_size) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Max packet size (%zd) exceeded: %zd", max_packet_size, buflen);
+ return -1;
+ }
+
+ struct iovec iov = {
+ .iov_base = (char *) buffer,
+ .iov_len = buflen,
+ };
+ struct vmpktdesc packet = {
+ .vm_pkt_iovcnt = 1,
+ .vm_flags = 0,
+ .vm_pkt_size = buflen,
+ .vm_pkt_iov = &iov,
+ };
+ int pkt_count = 1;
+
+ vmnet_return_t result = vmnet_write(vmnet_if, &packet, &pkt_count);
+ if (result != VMNET_SUCCESS) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Write failed: %s", str_vmnet_status(result));
+ return -1;
+ }
+
+ return pkt_count ? buflen : 00;
+}
+
+const char *str_vmnet_status(vmnet_return_t status) {
+ switch (status) {
+ case VMNET_SUCCESS:
+ return "success";
+ case VMNET_FAILURE:
+ return "general failure (possibly not enough privileges)";
+ case VMNET_MEM_FAILURE:
+ return "memory allocation failure";
+ case VMNET_INVALID_ARGUMENT:
+ return "invalid argument specified";
+ case VMNET_SETUP_INCOMPLETE:
+ return "interface setup is not complete";
+ case VMNET_INVALID_ACCESS:
+ return "invalid access, permission denied";
+ case VMNET_PACKET_TOO_BIG:
+ return "packet size is larger than MTU";
+ case VMNET_BUFFER_EXHAUSTED:
+ return "buffers exhausted in kernel";
+ case VMNET_TOO_MANY_PACKETS:
+ return "packet count exceeds limit";
+ case VMNET_SHARING_SERVICE_BUSY:
+ return "conflict, sharing service is in use";
+ default:
+ return "unknown vmnet error";
+ }
+}
diff --git a/src/bsd/darwin/vmnet.h b/src/bsd/darwin/vmnet.h
new file mode 100644
index 000000000..39360e7f1
--- /dev/null
+++ b/src/bsd/darwin/vmnet.h
@@ -0,0 +1,29 @@
+/*
+ * vmnet - Tun device emulation for Darwin
+ * Copyright (C) 2024 Eric Karge
+ *
+ * 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 .
+ *
+ */
+
+#ifndef VMNET_H
+#define VMNET_H
+
+#include
+
+int macos_vmnet_open(void);
+int macos_vmnet_close(int fd);
+ssize_t macos_vmnet_write(uint8_t *buffer, size_t buflen);
+
+#endif
diff --git a/src/bsd/device.c b/src/bsd/device.c
index c0642f57c..af6274702 100644
--- a/src/bsd/device.c
+++ b/src/bsd/device.c
@@ -31,6 +31,10 @@
#include "darwin/tunemu.h"
#endif
+#ifdef ENABLE_VMNET
+#include "darwin/vmnet.h"
+#endif
+
#ifdef HAVE_NET_IF_UTUN_H
#include
#include
@@ -51,6 +55,9 @@ typedef enum device_type {
DEVICE_TYPE_TAP,
#ifdef ENABLE_TUNEMU
DEVICE_TYPE_TUNEMU,
+#endif
+#ifdef ENABLE_VMNET
+ DEVICE_TYPE_VMNET,
#endif
DEVICE_TYPE_UTUN,
} device_type_t;
@@ -59,7 +66,9 @@ int device_fd = -1;
char *device = NULL;
char *iface = NULL;
static const char *device_info = "OS X utun device";
-#if defined(ENABLE_TUNEMU)
+#if defined(ENABLE_VMNET)
+static device_type_t device_type = DEVICE_TYPE_VMNET;
+#elif defined(ENABLE_TUNEMU)
static device_type_t device_type = DEVICE_TYPE_TUNEMU;
#elif defined(HAVE_OPENBSD) || defined(HAVE_FREEBSD) || defined(HAVE_DRAGONFLY)
static device_type_t device_type = DEVICE_TYPE_TUNIFHEAD;
@@ -142,6 +151,13 @@ static bool setup_device(void) {
device_type = DEVICE_TYPE_TUNEMU;
}
+#endif
+#ifdef ENABLE_VMNET
+ else if(!strcasecmp(type, "vmnet")) {
+ device = xstrdup("vmnet");
+ device_type = DEVICE_TYPE_VMNET;
+ }
+
#endif
#ifdef HAVE_NET_IF_UTUN_H
else if(!strcasecmp(type, "utun")) {
@@ -171,7 +187,12 @@ static bool setup_device(void) {
}
}
- if(routing_mode == RMODE_SWITCH && device_type != DEVICE_TYPE_TAP) {
+ if(routing_mode == RMODE_SWITCH
+ && device_type != DEVICE_TYPE_TAP
+#ifdef ENABLE_VMNET
+ && device_type != DEVICE_TYPE_VMNET
+#endif
+ ) {
logger(DEBUG_ALWAYS, LOG_ERR, "Only tap devices support switch mode!");
return false;
}
@@ -197,6 +218,13 @@ static bool setup_device(void) {
}
break;
#endif
+#ifdef ENABLE_VMNET
+
+ case DEVICE_TYPE_VMNET: {
+ device_fd = macos_vmnet_open();
+ }
+ break;
+#endif
#ifdef HAVE_NET_IF_UTUN_H
case DEVICE_TYPE_UTUN:
@@ -314,6 +342,12 @@ static bool setup_device(void) {
case DEVICE_TYPE_TUNEMU:
device_info = "BSD tunemu device";
break;
+#endif
+#ifdef ENABLE_VMNET
+
+ case DEVICE_TYPE_VMNET:
+ device_info = "macOS vmnet device";
+ break;
#endif
}
@@ -338,6 +372,12 @@ static void close_device(void) {
tunemu_close(device_fd);
break;
#endif
+#ifdef ENABLE_VMNET
+
+ case DEVICE_TYPE_VMNET:
+ macos_vmnet_close(device_fd);
+ break;
+#endif
default:
close(device_fd);
@@ -424,6 +464,9 @@ static bool read_packet(vpn_packet_t *packet) {
break;
}
+#ifdef ENABLE_VMNET
+ case DEVICE_TYPE_VMNET:
+#endif
case DEVICE_TYPE_TAP:
if((inlen = read(device_fd, DATA(packet), MTU)) <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
@@ -510,6 +553,17 @@ static bool write_packet(vpn_packet_t *packet) {
break;
#endif
+#ifdef ENABLE_VMNET
+
+ case DEVICE_TYPE_VMNET:
+ if(macos_vmnet_write(DATA(packet), packet->len) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info,
+ device, strerror(errno));
+ return false;
+ }
+
+ break;
+#endif
default:
return false;
diff --git a/src/tincd.c b/src/tincd.c
index 4c33dc070..0fbe35bef 100644
--- a/src/tincd.c
+++ b/src/tincd.c
@@ -492,6 +492,9 @@ int main(int argc, char **argv) {
#ifdef ENABLE_TUNEMU
" tunemu"
#endif
+#ifdef ENABLE_VMNET
+ " vmnet"
+#endif
#ifdef HAVE_MINIUPNPC
" miniupnpc"
#endif
diff --git a/test/integration/device.py b/test/integration/device.py
index 1db6d2149..6c50000f7 100755
--- a/test/integration/device.py
+++ b/test/integration/device.py
@@ -28,6 +28,9 @@ def unknown_device_types(
if Feature.TUNEMU not in features:
yield "tunemu"
+ if Feature.VMNET not in features:
+ yield "vmnet"
+
if system != "Darwin":
if not system.endswith("BSD"):
yield "tunnohead"
diff --git a/test/integration/testlib/proc.py b/test/integration/testlib/proc.py
index 970c80cc5..c8e8fbe36 100755
--- a/test/integration/testlib/proc.py
+++ b/test/integration/testlib/proc.py
@@ -55,6 +55,7 @@ class Feature(Enum):
READLINE = "readline"
SANDBOX = "sandbox"
TUNEMU = "tunemu"
+ VMNET = "vmnet"
UML = "uml"
VDE = "vde"
WATCHDOG = "watchdog"