]> tinc-vpn.org Git - tinc/commitdiff
Support vmnet on macOS
authorEric Karge <eric.karge@innoq.com>
Fri, 11 Oct 2024 13:41:55 +0000 (15:41 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Mon, 30 Mar 2026 20:32:34 +0000 (22:32 +0200)
meson.build
meson_options.txt
src/bsd/darwin/meson.build
src/bsd/darwin/vmnet.c [new file with mode: 0644]
src/bsd/darwin/vmnet.h [new file with mode: 0644]
src/bsd/device.c
src/tincd.c
test/integration/device.py
test/integration/testlib/proc.py

index 85a1b3ea6f6b67cf7775121da903d87d8e0decd5..7cd58e6f942dac603d8b4b3c5815720946cf5bc0 100644 (file)
@@ -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')
index 0df57950cfde1d66269c91f08ad24b989cd9bd49..412ee5430871bc8a457dae793b8d685cdfc2baf9 100644 (file)
@@ -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',
index f7dc99e25bff9b8752b9f111236d6f66acf98a4b..3b5e4b9e00bb24d0903f41bcba2af315420a26ac 100644 (file)
@@ -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 (file)
index 0000000..5eceba2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "vmnet.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <vmnet/vmnet.h>
+#include <dispatch/dispatch.h>
+#include "../../logger.h"
+#include <errno.h>
+
+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 (file)
index 0000000..fd07778
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ *  vmnet - Tun device emulation for Darwin
+ *  Copyright (C) 2009 Friedrich Schöller <friedrich.schoeller@gmail.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/>.
+ *
+ */
+
+#ifndef VMNET_H
+#define VMNET_H
+
+#include <sys/types.h>
+
+int macos_vmnet_open(void);
+int macos_vmnet_close(int fd);
+ssize_t macos_vmnet_write(uint8_t *buffer, size_t buflen);
+
+#endif
index c0642f57c9d18f87b35b4bdaa4b9f6b70b7a72d9..af6274702b62f95c0c415f148f21ecdb52a616f5 100644 (file)
 #include "darwin/tunemu.h"
 #endif
 
+#ifdef ENABLE_VMNET
+#include "darwin/vmnet.h"
+#endif
+
 #ifdef HAVE_NET_IF_UTUN_H
 #include <sys/sys_domain.h>
 #include <sys/kern_control.h>
@@ -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;
index 928737bdd440bf217f6c0acd43854b967a700efd..bf856e21519aa40467ec6c575b9d4dbd00d6d275 100644 (file)
@@ -496,6 +496,9 @@ int main(int argc, char **argv) {
 #ifdef ENABLE_TUNEMU
                        " tunemu"
 #endif
+#ifdef ENABLE_VMNET
+                       " vmnet"
+#endif
 #ifdef HAVE_MINIUPNPC
                        " miniupnpc"
 #endif
index 1db6d21492fd87d9f5beb16b517906a29cd89e23..6c50000f782ceaa1ebbe80a2ba306adfd855b0a1 100755 (executable)
@@ -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"
index dab335ad3f107f6858f076a9d4a341ca360c4706..aadafb8bf0099765bcd75612b2cff23664c2d70b 100755 (executable)
@@ -55,6 +55,7 @@ class Feature(Enum):
     READLINE = "readline"
     SANDBOX = "sandbox"
     TUNEMU = "tunemu"
+    VMNET = "vmnet"
     UML = "uml"
     VDE = "vde"
     WATCHDOG = "watchdog"