From ddb1c4d7b0101ccc9d25923ede2731e6dd38582b Mon Sep 17 00:00:00 2001 From: sid Date: Mon, 13 Apr 2026 10:03:41 -0600 Subject: [PATCH] Initial commit --- Formula/fgj-sid.rb | 29 +++ Formula/qemu-iscsi.rb | 124 ++++++++++ Formula/tinc-feth.rb | 83 +++++++ README.md | 64 +++++ patches/feth-device.patch | 437 +++++++++++++++++++++++++++++++++++ patches/net-socket-fix.patch | 37 +++ patches/raw-socket-fix.patch | 27 +++ 7 files changed, 801 insertions(+) create mode 100644 Formula/fgj-sid.rb create mode 100644 Formula/qemu-iscsi.rb create mode 100644 Formula/tinc-feth.rb create mode 100644 README.md create mode 100644 patches/feth-device.patch create mode 100644 patches/net-socket-fix.patch create mode 100644 patches/raw-socket-fix.patch diff --git a/Formula/fgj-sid.rb b/Formula/fgj-sid.rb new file mode 100644 index 0000000..93917ec --- /dev/null +++ b/Formula/fgj-sid.rb @@ -0,0 +1,29 @@ +class FgjSid < Formula + desc "Forgejo/Gitea CLI tool - like gh for GitHub, but for Forgejo and Gitea" + homepage "https://forgejo.zerova.net/public/fgj-sid" + license "MIT" + + url "ssh://git@forgejo.zerova.net/public/fgj-sid.git", + tag: "v0.3.0f", + revision: "c2251d9932b9bc3014233f97cfe1c43249a0e22c" + head "ssh://git@forgejo.zerova.net/public/fgj-sid.git", branch: "main" + + depends_on "go" => :build + + def install + ldflags = "-s -w" + system "go", "build", *std_go_args(ldflags:) + + generate_completions_from_executable(bin/"fgj", "completion") + + # Generate man pages + mkdir "man" do + system bin/"fgj", "manpages", "--dir", "." + man1.install Dir["*.1"] + end + end + + test do + assert_match "0.3.0f", shell_output("#{bin}/fgj --version") + end +end diff --git a/Formula/qemu-iscsi.rb b/Formula/qemu-iscsi.rb new file mode 100644 index 0000000..cf015e6 --- /dev/null +++ b/Formula/qemu-iscsi.rb @@ -0,0 +1,124 @@ +class QemuIscsi < Formula + desc "Generic machine emulator and virtualizer (with libiscsi support)" + homepage "https://www.qemu.org/" + url "https://download.qemu.org/qemu-10.2.2.tar.xz" + sha256 "784b296ff29c1417aa72323abcb2d2ea9ab9771724f577dcd785c3b04f21e176" + license "GPL-2.0-only" + head "https://gitlab.com/qemu-project/qemu.git", branch: "master" + + livecheck do + url "https://www.qemu.org/download/" + regex(/href=.*?qemu[._-]v?(\d+(?:\.\d+)+)\.t/i) + end + + conflicts_with "qemu", because: "qemu-iscsi installs the same binaries as qemu" + + depends_on "libtool" => :build + depends_on "meson" => :build + depends_on "ninja" => :build + depends_on "pkgconf" => :build + depends_on "python@3.14" => :build + depends_on "spice-protocol" => :build + + depends_on "capstone" + depends_on "dtc" + depends_on "glib" + depends_on "gnutls" + depends_on "jpeg-turbo" + depends_on "libiscsi" + depends_on "libpng" + depends_on "libslirp" + depends_on "libssh" + depends_on "libusb" + depends_on "lzo" + depends_on "ncurses" + depends_on "pixman" + depends_on "snappy" + depends_on "vde" + depends_on "zstd" + + uses_from_macos "bison" => :build + uses_from_macos "flex" => :build + uses_from_macos "bzip2" + + on_linux do + depends_on "attr" + depends_on "cairo" + depends_on "elfutils" + depends_on "gdk-pixbuf" + depends_on "gtk+3" + depends_on "keyutils" + depends_on "libcap-ng" + depends_on "libepoxy" + depends_on "libx11" + depends_on "libxkbcommon" + depends_on "mesa" + depends_on "systemd" + depends_on "zlib-ng-compat" + end + + def install + ENV["LIBTOOL"] = "glibtool" + + rm(Dir["python/wheels/*"] - Dir["python/wheels/pycotap-*-none-any.whl"]) + + args = %W[ + --prefix=#{prefix} + --cc=#{ENV.cc} + --host-cc=#{ENV.cc} + --disable-bsd-user + --disable-download + --disable-guest-agent + --enable-slirp + --enable-capstone + --enable-curses + --enable-fdt=system + --enable-libiscsi + --enable-libssh + --enable-vde + --enable-virtfs + --enable-zstd + --extra-cflags=-DNCURSES_WIDECHAR=1 + --disable-sdl + ] + + args << "--smbd=#{HOMEBREW_PREFIX}/sbin/samba-dot-org-smbd" + + args += if OS.mac? + ["--disable-gtk", "--enable-cocoa"] + else + ["--enable-gtk"] + end + + system "./configure", *args + system "make", "V=1", "install" + end + + test do + resource "homebrew-test-image" do + url "https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.2/official/FD12FLOPPY.zip" + sha256 "81237c7b42dc0ffc8b32a2f5734e3480a3f9a470c50c14a9c4576a2561a35807" + end + + archs = %w[ + aarch64 alpha arm avr hppa i386 loongarch64 m68k microblaze microblazeel mips + mips64 mips64el mipsel or1k ppc ppc64 riscv32 riscv64 rx + s390x sh4 sh4eb sparc sparc64 tricore x86_64 xtensa xtensaeb + ] + archs.each do |guest_arch| + assert_match version.to_s, shell_output("#{bin}/qemu-system-#{guest_arch} --version") + end + + resource("homebrew-test-image").stage testpath + assert_match "file format: raw", shell_output("#{bin}/qemu-img info FLOPPY.img") + + assert_match "libiscsi", + shell_output("#{bin}/qemu-system-x86_64 -drive format=? 2>&1", 1) + + if OS.mac? + output = shell_output("codesign --verify --verbose #{bin}/qemu-system-x86_64 2>&1") + assert_match "valid on disk", output + assert_match "satisfies its Designated Requirement", output + end + end +end diff --git a/Formula/tinc-feth.rb b/Formula/tinc-feth.rb new file mode 100644 index 0000000..f905d9c --- /dev/null +++ b/Formula/tinc-feth.rb @@ -0,0 +1,83 @@ +class TincFeth < Formula + desc "Virtual Private Network (VPN) tool with feth-based TAP support for macOS" + homepage "https://www.tinc-vpn.org/" + url "https://www.tinc-vpn.org/packages/tinc-1.0.37.tar.gz" + sha256 "f63b7e21c32c4c637576d85f36bdd28ea678b5aa17fad02427645dea30e52ac7" + license "GPL-2.0-or-later" => { with: "OpenSSL-exception" } + + conflicts_with "tinc", because: "tinc-feth is a patched version of tinc with macOS feth device support" + + depends_on "lzo" + depends_on "openssl@3" + + def patches_dir + # Resolve patches relative to the tap root + File.expand_path("../patches", __dir__) + end + + def install + # Apply patches: macOS struct ifreq fixes + feth device support + system "patch", "-p1", "-i", "#{patches_dir}/net-socket-fix.patch" + system "patch", "-p1", "-i", "#{patches_dir}/raw-socket-fix.patch" + system "patch", "-p1", "-i", "#{patches_dir}/feth-device.patch" + + system "./configure", "--prefix=#{prefix}", + "--sysconfdir=#{etc}", + "--with-openssl=#{Formula["openssl@3"].opt_prefix}", + "--with-lzo=#{Formula["lzo"].opt_prefix}" + system "make", "install" + + # Install wrapper script that auto-detects the network name + (bin/"tinc-feth-start").write <<~SH + #!/bin/sh + TINC_DIR="#{etc}/tinc" + if [ -n "$1" ]; then + NETWORK="$1" + else + NETWORK=$(find "$TINC_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '.*' -exec basename {} \\; | head -1) + fi + if [ -z "$NETWORK" ]; then + echo "No tinc network found in $TINC_DIR" >&2 + exit 1 + fi + exec #{opt_sbin}/tincd -n "$NETWORK" -D + SH + end + + def caveats + <<~EOS + tinc-feth adds macOS feth (fake ethernet) device support for Layer 2 / switch mode. + + To use feth mode, set in your tinc.conf: + DeviceType = feth + Mode = switch + + Optionally specify a device number (default is feth0): + Device = feth0 + + This creates a feth interface pair: + feth0 - primary interface (configure IPs in tinc-up) + feth5000 - peer interface (used internally for packet I/O) + + Requires root privileges to create feth interfaces and open BPF devices. + + Example tinc-up script: + #!/bin/sh + ifconfig $INTERFACE 10.0.0.1 netmask 255.255.255.0 up + + Configuration directory: #{etc}/tinc/ + EOS + end + + service do + run [opt_bin/"tinc-feth-start"] + require_root true + keep_alive true + log_path var/"log/tinc/tinc.log" + error_log_path var/"log/tinc/tinc.log" + end + + test do + assert_match version.to_s, shell_output("#{sbin}/tincd --version") + end +end diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d6dd3b --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# Homebrew Tap — sid + +Personal Homebrew tap hosted on [Forgejo](https://forgejo.zerova.net/public/homebrew-sid). + +## Usage + +```bash +brew tap public/sid git@forgejo.zerova.net:public/homebrew-sid.git +``` + +## Available Formulae + +| Formula | Description | +|---------|-------------| +| [fgj-sid](https://forgejo.zerova.net/public/fgj-sid) | Forgejo/Gitea CLI tool with agentic dev features (fork) | +| [tinc-feth](https://forgejo.zerova.net/public/tinc-feth) | tinc 1.0.37 VPN with macOS feth device support | +| qemu-iscsi | QEMU built with `--enable-libiscsi` (iSCSI block backend) | + +```bash +brew install fgj-sid +brew install tinc-feth +brew install qemu-iscsi +``` + +### qemu-iscsi + +QEMU 10.2.2 built identically to `homebrew/core/qemu` but with `libiscsi` as a +runtime dependency and `--enable-libiscsi` passed to configure, so +`qemu-system-*` can attach `iscsi://` drives directly. Conflicts with the +upstream `qemu` formula. + +### tinc-feth + +Patched build of [tinc](https://www.tinc-vpn.org/) 1.0.37 with native macOS feth (fake ethernet) device support for Layer 2 / switch mode. No tuntap kext required. + +```bash +# install +brew install public/sid/tinc-feth + +# start as service (runs at boot) +sudo brew services start public/sid/tinc-feth + +# stop / restart +sudo brew services stop public/sid/tinc-feth +sudo brew services restart public/sid/tinc-feth + +# manual run with debug +sudo /opt/homebrew/opt/tinc-feth/sbin/tincd -n -D -d3 +``` + +Conflicts with the upstream `tinc` formula. + +## Update + +```bash +brew upgrade +``` + +## Uninstall + +```bash +brew uninstall +brew untap public/sid +``` diff --git a/patches/feth-device.patch b/patches/feth-device.patch new file mode 100644 index 0000000..c753fb7 --- /dev/null +++ b/patches/feth-device.patch @@ -0,0 +1,437 @@ +--- 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 + #endif + ++#ifdef HAVE_DARWIN ++#include ++#include ++#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 (primary) - gets IP assignments, used by tinc-up scripts ++ * feth (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; + } diff --git a/patches/net-socket-fix.patch b/patches/net-socket-fix.patch new file mode 100644 index 0000000..7f48388 --- /dev/null +++ b/patches/net-socket-fix.patch @@ -0,0 +1,37 @@ +--- tinc-1.0.37/src/net_socket.c 2025-11-08 15:46:26 ++++ tinc-1.0.37-patched/src/net_socket.c 2026-03-21 14:12:58 +@@ -102,14 +102,14 @@ + + #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) + memset(&ifr, 0, sizeof(ifr)); +- strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); +- ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; ++ strncpy(ifr.ifr_name, iface, IFNAMSIZ); ++ ifr.ifr_name[IFNAMSIZ - 1] = 0; + free(iface); + + status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)); + + if(status) { +- logger(LOG_ERR, "Can't bind to interface %s: %s", ifr.ifr_ifrn.ifrn_name, strerror(errno)); ++ logger(LOG_ERR, "Can't bind to interface %s: %s", ifr.ifr_name, strerror(errno)); + return false; + } + +@@ -157,13 +157,13 @@ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); +- strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); +- ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; ++ strncpy(ifr.ifr_name, iface, IFNAMSIZ); ++ ifr.ifr_name[IFNAMSIZ - 1] = 0; + free(iface); + + if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr))) { + closesocket(nfd); +- logger(LOG_ERR, "Can't bind to interface %s: %s", ifr.ifr_ifrn.ifrn_name, strerror(sockerrno)); ++ logger(LOG_ERR, "Can't bind to interface %s: %s", ifr.ifr_name, strerror(sockerrno)); + return -1; + } + diff --git a/patches/raw-socket-fix.patch b/patches/raw-socket-fix.patch new file mode 100644 index 0000000..ea39bb1 --- /dev/null +++ b/patches/raw-socket-fix.patch @@ -0,0 +1,27 @@ +--- tinc-1.0.37/src/raw_socket_device.c 2025-11-08 15:46:26 ++++ tinc-1.0.37-patched/src/raw_socket_device.c 2026-03-21 14:13:18 +@@ -61,12 +61,12 @@ + #endif + + memset(&ifr, 0, sizeof(ifr)); +- strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); +- ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; ++ strncpy(ifr.ifr_name, iface, IFNAMSIZ); ++ ifr.ifr_name[IFNAMSIZ - 1] = 0; + + if(ioctl(device_fd, SIOCGIFINDEX, &ifr)) { + close(device_fd); +- logger(LOG_ERR, "Can't find interface %s: %s", ifr.ifr_ifrn.ifrn_name, strerror(errno)); ++ logger(LOG_ERR, "Can't find interface %s: %s", ifr.ifr_name, strerror(errno)); + return false; + } + +@@ -76,7 +76,7 @@ + sa.sll_ifindex = ifr.ifr_ifindex; + + if(bind(device_fd, (struct sockaddr *) &sa, (socklen_t) sizeof(sa))) { +- logger(LOG_ERR, "Could not bind %s to %s: %s", device, ifr.ifr_ifrn.ifrn_name, strerror(errno)); ++ logger(LOG_ERR, "Could not bind %s to %s: %s", device, ifr.ifr_name, strerror(errno)); + return false; + } +