From ad06eaea1cfba538227d5ffc52cf16b8e72306e9 Mon Sep 17 00:00:00 2001 From: "Else, Someone" Date: Thu, 15 Jan 2026 08:24:08 +0200 Subject: [PATCH 1/9] taps: wip: init --- pkgs/ch-proxy/sendfd.h | 9 ++ pkgs/taps/.envrc | 1 + pkgs/taps/main.c | 241 +++++++++++++++++++++++++++++++++++++++++ pkgs/taps/meson.build | 4 + pkgs/taps/package.nix | 44 ++++++++ shell.nix | 10 ++ 6 files changed, 309 insertions(+) create mode 100644 pkgs/ch-proxy/sendfd.h create mode 100644 pkgs/taps/.envrc create mode 100644 pkgs/taps/main.c create mode 100644 pkgs/taps/meson.build create mode 100644 pkgs/taps/package.nix create mode 100644 shell.nix diff --git a/pkgs/ch-proxy/sendfd.h b/pkgs/ch-proxy/sendfd.h new file mode 100644 index 0000000..65042d8 --- /dev/null +++ b/pkgs/ch-proxy/sendfd.h @@ -0,0 +1,9 @@ +#ifndef _CH_PROXY_SENFD +#define _CH_PROXY_SENFD + +#include + +ssize_t send_fd(int dst_fd, int fd); + +#endif _CH_PROXY_SENFD + diff --git a/pkgs/taps/.envrc b/pkgs/taps/.envrc new file mode 100644 index 0000000..35f8c10 --- /dev/null +++ b/pkgs/taps/.envrc @@ -0,0 +1 @@ +use nix ../../ -A pkgs.taps diff --git a/pkgs/taps/main.c b/pkgs/taps/main.c new file mode 100644 index 0000000..c921bd0 --- /dev/null +++ b/pkgs/taps/main.c @@ -0,0 +1,241 @@ +#define _GNU_SOURCE + +#include /* secure_getenv */ +#include +#include +#include +#include +#include +#include +#include +#include +#include /* open, O_NONBLOCK, &c */ +#include /* perror */ +#include +#include + + +#define __UAPI_DEF_IF_IFNAMSIZ 1 +#include +#include + +#include "sendfd.h" + +// From `man unix` +#define SUN_PATH_SZ 108 +#define N_CONNS 16 + +#define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE) +#define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR) + +#define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) +#define DO_OR_DIE_X(expr) \ + do if (!(expr)) { \ + perror((#expr)); \ + exit(EXIT_FAILURE); \ + } while(false) + +struct allow_pattern { + // enum { USER = 1, GROUP = 2 } type; + // union { uid_t uid, gid_t gid } xid; + char *name; +}; +struct allow_patterns { + size_t n; + struct allow_pattern *patterns; +}; + +/* Running on the same host, not caring for alignment */ +struct tap_request { + short ifrFlags; /* 0 to use defaults: IFF_TAP | IFF_NO_PI | IFF_VNET_HDR */ + char name[IFNAMSIZ]; +}; + +struct tap_reply { + enum { OK = 0, AUTH_ERROR = 1 } status; + char name[IFNAMSIZ]; +}; + +int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd); + +bool match_mask(const char *test_addr, const char *expected_addr, const char *mask, int n) { + for (int octet = 0; octet < n; ++octet) { + if ((test_addr[octet] & mask[octet]) != expected_addr[octet]) { + return false; + } + } + return true; +} + +/* + * Adapted from spectrum's `mktuntap.c` (2019 Alyssa Ross + * GPL-2.0-only), which in turn adapts `tun_alloc` from + * `linux/Documentation/networking/tuntap.rst`. + * + * ifrFlags: IFF_TUN - TUN device (no Ethernet headers) + * IFF_TAP - TAP device + * + * IFF_NO_PI - Do not provide packet information + */ +int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { + struct ifreq ifr; + int fd, err; + + // if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { + // return tun_alloc_old(dev); + // } + + DO_OR_DIE_X((fd = open("/dev/net/tun", openFlags)) >= 0); + + memset(&ifr, 0, sizeof(ifr)); + + if (*dev) { + int devLen = strlen(dev); + if (devLen >= IFNAMSIZ) { + /* If client requests a name, we do want the entire name to fit */ + errno = EINVAL; + return EINVAL; + } + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + } + + if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) { + close(fd); + return err; + } + + strcpy(dev, ifr.ifr_name); + *out_fd = fd; + return 0; +} + +int acceptRequests(const char *requestsPath, const struct allow_patterns *patterns) { + int listener; + struct sockaddr_un addr; + const bool t = 1; + + DO_OR_DIE(listener = socket(AF_UNIX, SOCK_SEQPACKET, 0)); + DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, 1) != 0); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ); + DO_OR_DIE (bind(listener, &addr, sizeof(addr)) == -1); + + DO_OR_DIE(listen(listener, N_CONNS)); + + for (;;) { + int sock = -1; + struct ucred cred = { 0 }; + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg = NULL; + struct iovec iov = { 0 }; + struct tap_request req = { 0 }; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = &req; + iov.iov_len = sizeof(struct tap_request); + + DO_OR_DIE(sock = accept(listener, NULL, NULL)); + + DO_OR_DIE_X(recvmsg(sock, &msg, 0) > 0); + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (!(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS)) { + continue; + } + memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); + break; + } + + if (req.ifrFlags == 0) { + req.ifrFlags = IFR_FLAGS_DEFAULT; + } + + bool allowed = false; + for (int i = 0; !allowed && i < patterns->n; ++i) { + bool ifnameOk = fnmatch(patterns->patterns[i].name, req.name, 0) == 0; + bool flagsOk = (req.ifrFlags & IFR_FLAGS_ALLOWED) == req.ifrFlags; + allowed = ifnameOk && flagsOk; + } + + struct tap_reply reply = { 0 }; + if (!allowed) { reply.status = AUTH_ERROR; } + if (allowed) { + /* O_CLOEXEC? */ + int fd = 0; + DO_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd)); + struct iovec iov = { 0 }; + iov.iov_base = &reply; + iov.iov_len = sizeof(struct tap_reply); + DO_OR_DIE_X(send_fd(sock, fd, &iov) > 0); + } + close(sock); + } + close(listener); +} + +struct allow_patterns parsePatterns(const char *raw) { + const size_t rawLen = strlen(raw); + + size_t nPatterns = 0; + for (int i = 0; i < rawLen; ++i) { + const int start = i; + if (isspace(raw[i])) { continue; } + for (; i < rawLen && !isspace(raw[i]); ++i) { } + if (start < i) { ++nPatterns; } + } + + struct allow_pattern *patterns = calloc(nPatterns, sizeof(struct allow_pattern)); + + int iPattern = 0; + for (int i = 0; i < rawLen; ++i) { + if (isspace(raw[i])) { continue; } + /* used to have per-group/per-user patterns, "u:$username:$pattern", &c - gone */ + { + const int start = i; + for (; i < rawLen && !isspace(raw[i]); ++i) { } + if (i < rawLen) { + patterns[iPattern].name = strndup(&raw[start], i - start); + iPattern += 1; + } + } + } + struct allow_patterns out = { + .n = nPatterns, + .patterns = patterns + }; + return out; +} + +int main(int argc, char **argv) { + bool cmdServe = false; + bool cmdGet = false; + + char **rest = argv + 1; + + if (strcmp(rest[0], "serve") == 0) { + cmdServe = true; + ++rest; + } else if (strcmp(rest[0], "get") == 0) { + cmdGet = true; + ++rest; + } else { + exit(EXIT_FAILURE); + } + + const char *patternsRaw = secure_getenv("TAPS_ALLOW"); + struct allow_patterns patterns = { 0 }; + if (cmdServe && patternsRaw != NULL) { patterns = parsePatterns(patternsRaw); } + DO_OR_DIE_X(patterns.patterns != NULL); + + const char *servePath = secure_getenv("TAPS_SOCK"); + + if (cmdServe && patterns.patterns != NULL) { + acceptRequests(servePath, &patterns); + } else if (cmdGet) { + } else { + exit(EXIT_FAILURE); } + + return 0; +} diff --git a/pkgs/taps/meson.build b/pkgs/taps/meson.build new file mode 100644 index 0000000..06057b4 --- /dev/null +++ b/pkgs/taps/meson.build @@ -0,0 +1,4 @@ +project('taps', 'c') + +sendfd = dependency('sendfd') +executable('taps', 'main.c', dependencies: [sendfd], install: true) diff --git a/pkgs/taps/package.nix b/pkgs/taps/package.nix new file mode 100644 index 0000000..c666cd9 --- /dev/null +++ b/pkgs/taps/package.nix @@ -0,0 +1,44 @@ +{ + lib, + stdenv, + meson, + pkg-config, + rustc, + ninja, + ch-proxy, +}: + +stdenv.mkDerivation { + pname = "taps"; + version = "0.0.0"; + src = + let + fs = lib.fileset; + in + fs.toSource { + root = ./.; + fileset = fs.unions [ + ./meson.build + ./main.c + ]; + }; + + nativeBuildInputs = [ + ninja + meson + pkg-config + rustc + ]; + buildInputs = [ ch-proxy ]; +} +# { lib, rustPlatform }: +# +# rustPlatform.buildRustPackage { +# pname = "taps"; +# version = "0.0.0"; +# src = let fs = lib.filesystem; in fs.toSource { +# root = ./.; +# fileset = fs.unions [ +# ]; +# }; +# }; diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..497375b --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +with import { }; + +mkShell.override { stdenv = stdenvNoCC; } { + packages = map lib.getBin [ + cloud-hypervisor + virtiofsd + crosvm # virtio-gpu + npins + ]; +} From a75d0b8e5d71335bcbe62c4c570c344c70be6957 Mon Sep 17 00:00:00 2001 From: "Else, Someone" Date: Thu, 22 Jan 2026 14:29:45 +0200 Subject: [PATCH 2/9] fixup! taps: wip: init --- pkgs/ch-proxy/meson.build | 8 ++++++- pkgs/ch-proxy/package.nix | 3 +++ pkgs/ch-proxy/proxy.c | 47 +++------------------------------------ pkgs/ch-proxy/sendfd.c | 38 +++++++++++++++++++++++++++++++ pkgs/ch-proxy/sendfd.h | 8 ++++--- profiles/uvms-guest.nix | 1 + profiles/uvms-users.nix | 1 - 7 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 pkgs/ch-proxy/sendfd.c diff --git a/pkgs/ch-proxy/meson.build b/pkgs/ch-proxy/meson.build index e3976a2..379f96f 100644 --- a/pkgs/ch-proxy/meson.build +++ b/pkgs/ch-proxy/meson.build @@ -1,3 +1,9 @@ project('ch-proxy', 'c') -executable('ch-proxy', 'proxy.c', install: true) +pkg = import('pkgconfig') + +sendfd = library('sendfd', [ 'sendfd.c', 'sendfd.h' ], install: true) +pkg.generate(sendfd) +install_headers('sendfd.h') + +executable('ch-proxy', 'proxy.c', link_with: [sendfd], install: true) diff --git a/pkgs/ch-proxy/package.nix b/pkgs/ch-proxy/package.nix index 9fd3b21..e94eec8 100644 --- a/pkgs/ch-proxy/package.nix +++ b/pkgs/ch-proxy/package.nix @@ -8,6 +8,7 @@ stdenv.mkDerivation { pname = "ch-proxy"; version = "0.0.0"; + outputs = [ "out" "lib" ]; nativeBuildInputs = [ meson ninja @@ -19,6 +20,8 @@ stdenv.mkDerivation { fs.toSource { fileset = fs.unions [ ./proxy.c + ./sendfd.c + ./sendfd.h ./meson.build ]; root = ./.; diff --git a/pkgs/ch-proxy/proxy.c b/pkgs/ch-proxy/proxy.c index ed1dea0..46730e6 100644 --- a/pkgs/ch-proxy/proxy.c +++ b/pkgs/ch-proxy/proxy.c @@ -9,9 +9,9 @@ #include -struct msghdr mk_msghdr(); +#include "sendfd.h" + int ch_connect(const char*, const char*); -ssize_t send_fd(int, int); #define _WRITE_CONFIRM(fd, buf, buflen) {if (write((fd), (buf), (buflen)) != (buflen)) { perror("ch-proxy/write/partial write"); exit(EXIT_FAILURE); }} @@ -168,19 +168,13 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - if (send_fd(1, s) == -1) { + if (send_fd(1, s, NULL) == -1) { perror("ssh-vsock-proxy/main/send_fd"); return EXIT_FAILURE; } return 0; } -struct msghdr mk_msghdr() { - struct msghdr msg; - memset(&msg, 0, sizeof(msg)); - - return msg; -} int ch_connect(const char *path, const char *port) { int s = socket(AF_UNIX, SOCK_STREAM, 0); @@ -212,38 +206,3 @@ int ch_connect(const char *path, const char *port) { return s; } - -ssize_t send_fd(int dst_fd, int fd) { - struct msghdr msg = mk_msghdr(); - - /* openssh expects to receive a dummy length=1 iovec? */ - char ch; - struct iovec vec; - vec.iov_base = &ch; - vec.iov_len = 1; - msg.msg_iov = &vec; - msg.msg_iovlen = 1; - - union { - struct cmsghdr align; - char buf[CMSG_SPACE(sizeof(int))]; - } u; - - msg.msg_control = u.buf; - msg.msg_controllen = sizeof(u.buf); - - struct cmsghdr *cmptr; - cmptr = CMSG_FIRSTHDR(&msg); - - if (cmptr == NULL) { - fprintf(stderr, "ch-proxy/send_fd/CMSG_FIRSTHDR: failed to initialize msg_control\n"); - exit(EXIT_FAILURE); - } - - cmptr->cmsg_len = CMSG_LEN(sizeof(int)); - cmptr->cmsg_level = SOL_SOCKET; - cmptr->cmsg_type = SCM_RIGHTS; - *((int*) CMSG_DATA(cmptr)) = fd; - - return (sendmsg(dst_fd, &msg, 0)); -} diff --git a/pkgs/ch-proxy/sendfd.c b/pkgs/ch-proxy/sendfd.c new file mode 100644 index 0000000..c649316 --- /dev/null +++ b/pkgs/ch-proxy/sendfd.c @@ -0,0 +1,38 @@ +#include "sendfd.h" +#include "sys/socket.h" /* cmsghdr */ +#include "stdio.h" /* perror */ + + +ssize_t send_fd(int dst_fd, int fd, const struct iovec *iov) { + struct msghdr msg = { 0 }; + + /* openssh expects to receive a dummy length=1 iovec? */ + char ch = 0; + struct iovec vecDefault = { 0 }; + vecDefault.iov_base = &ch; + vecDefault.iov_len = 1; + msg.msg_iov = iov == NULL ? &vecDefault : iov; + msg.msg_iovlen = 1; + + union { + struct cmsghdr align; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + struct cmsghdr *cmptr; + cmptr = CMSG_FIRSTHDR(&msg); + + if (cmptr == NULL) { + perror("ch-proxy/send_fd/CMSG_FIRSTHDR: failed to initialize msg_control\n"); + } + + cmptr->cmsg_len = CMSG_LEN(sizeof(int)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + *((int*) CMSG_DATA(cmptr)) = fd; + + return (sendmsg(dst_fd, &msg, 0)); +} diff --git a/pkgs/ch-proxy/sendfd.h b/pkgs/ch-proxy/sendfd.h index 65042d8..8c99389 100644 --- a/pkgs/ch-proxy/sendfd.h +++ b/pkgs/ch-proxy/sendfd.h @@ -1,9 +1,11 @@ #ifndef _CH_PROXY_SENFD #define _CH_PROXY_SENFD -#include +#include /* size_t */ +#include /* ssize_t */ +#include /* iovec */ -ssize_t send_fd(int dst_fd, int fd); +ssize_t send_fd(int dst_fd, int fd, const struct iovec *); -#endif _CH_PROXY_SENFD +#endif /* _CH_PROXY_SENFD */ diff --git a/profiles/uvms-guest.nix b/profiles/uvms-guest.nix index e8c307d..281f343 100644 --- a/profiles/uvms-guest.nix +++ b/profiles/uvms-guest.nix @@ -41,6 +41,7 @@ in volumes = [ { image = "swapfile.img"; + serial = "swapfiles"; mountPoint = "/var/swapfiles"; size = 1024; } diff --git a/profiles/uvms-users.nix b/profiles/uvms-users.nix index e75ac8f..e7bbacf 100644 --- a/profiles/uvms-users.nix +++ b/profiles/uvms-users.nix @@ -29,7 +29,6 @@ in }; config = mergeIf cfg.enable [ { - services.getty.autologinUser = "user"; security.sudo.wheelNeedsPassword = false; users.mutableUsers = false; users.users.user = { From 0016fba207aad82f3c21d1258ccf7181cbf98428 Mon Sep 17 00:00:00 2001 From: Else Someone Date: Thu, 22 Jan 2026 15:08:38 +0200 Subject: [PATCH 3/9] taps: fix error messages --- pkgs/taps/main.c | 21 +++++++++++++-------- shell.nix | 3 +++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pkgs/taps/main.c b/pkgs/taps/main.c index c921bd0..82a02cd 100644 --- a/pkgs/taps/main.c +++ b/pkgs/taps/main.c @@ -10,7 +10,7 @@ #include #include #include /* open, O_NONBLOCK, &c */ -#include /* perror */ +#include #include #include @@ -31,8 +31,7 @@ #define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) #define DO_OR_DIE_X(expr) \ do if (!(expr)) { \ - perror((#expr)); \ - exit(EXIT_FAILURE); \ + error(EXIT_FAILURE, errno, ("Failed assertion: " #expr)); \ } while(false) struct allow_pattern { @@ -214,6 +213,7 @@ int main(int argc, char **argv) { char **rest = argv + 1; + DO_OR_DIE_X(argc > 1); if (strcmp(rest[0], "serve") == 0) { cmdServe = true; ++rest; @@ -221,21 +221,26 @@ int main(int argc, char **argv) { cmdGet = true; ++rest; } else { - exit(EXIT_FAILURE); + error(EINVAL, EINVAL, "no subcommand \"%s\"", rest[0]); } const char *patternsRaw = secure_getenv("TAPS_ALLOW"); struct allow_patterns patterns = { 0 }; - if (cmdServe && patternsRaw != NULL) { patterns = parsePatterns(patternsRaw); } - DO_OR_DIE_X(patterns.patterns != NULL); + if (cmdServe && patternsRaw != NULL) { + patterns = parsePatterns(patternsRaw); + DO_OR_DIE_X(patterns.patterns != NULL); + } const char *servePath = secure_getenv("TAPS_SOCK"); - if (cmdServe && patterns.patterns != NULL) { + if (cmdServe) { + error(patterns.patterns == NULL, EINVAL, "TAPS_ALLOW"); acceptRequests(servePath, &patterns); } else if (cmdGet) { + error(ENOSYS, ENOSYS, "get"); } else { - exit(EXIT_FAILURE); } + error(EINVAL, EINVAL, "subcommand args"); + } return 0; } diff --git a/shell.nix b/shell.nix index 497375b..f8bb9a7 100644 --- a/shell.nix +++ b/shell.nix @@ -6,5 +6,8 @@ mkShell.override { stdenv = stdenvNoCC; } { virtiofsd crosvm # virtio-gpu npins + ] ++ [ + man-pages + linux-manual ]; } From 4feb8d41264429bf9d7583190e2f80cb3744c05f Mon Sep 17 00:00:00 2001 From: Else Someone Date: Wed, 28 Jan 2026 07:04:57 +0200 Subject: [PATCH 4/9] taps: MVE - TUNSETIFF works, the interface is indeed created. - An fd is indeed passed over the unix socket, - and is a valid (enough) fd because it can fed into `dup2`. - `nix run -f . --offline pkgs.taps -- pass sleep 5` works, the interface exists for 5 seconds and disappears - `nix run -f . --offline pkgs.taps -- pass ch-remote --api-socket=$HOME/uvms/nixos/vmm.sock add-net fd=3` obscurely fails, killing the VMM with: ```console [root@nixos:~]# cloud-hypervisor: 12.388270s: <_net1_qp0> ERROR:/build/source/net_util/src/queue_pair.rs:112 -- net: tx: failed writing to tap: Input/output error (os er ror 5) cloud-hypervisor: 12.388459s: <_net1_qp0> ERROR:virtio-devices/src/thread_helper.rs:54 -- Error running worker: HandleEvent(Error processing TX queue: NetQueuePair(Write Tap(Os { code: 5, kind: Uncategorized, message: "Input/output error" }))) ``` --- pkgs/ch-proxy/sendfd.c | 36 +++++++++ pkgs/ch-proxy/sendfd.h | 20 ++++- pkgs/taps/main.c | 172 ++++++++++++++++++++++++++++++----------- 3 files changed, 180 insertions(+), 48 deletions(-) diff --git a/pkgs/ch-proxy/sendfd.c b/pkgs/ch-proxy/sendfd.c index c649316..b20e284 100644 --- a/pkgs/ch-proxy/sendfd.c +++ b/pkgs/ch-proxy/sendfd.c @@ -36,3 +36,39 @@ ssize_t send_fd(int dst_fd, int fd, const struct iovec *iov) { return (sendmsg(dst_fd, &msg, 0)); } + +int recv_fd(int sock, int flags) { + int out = -1; + + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg = NULL; + struct iovec iov = { 0 }; + char dummy = 0; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = &dummy; + iov.iov_len = sizeof(dummy); + + union { + struct cmsghdr align; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + int bytes = 0; + if ((bytes = recvmsg(sock, &msg, flags)) < 0) { + perror("recv_fd: recvmsg"); + return -1; + } + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) { continue; } + if (cmsg->cmsg_type != SCM_RIGHTS) { continue; } + if (CMSG_LEN(cmsg) < sizeof(out)) { continue; } + out = *(int*)CMSG_DATA(cmsg); + } + return out; +} diff --git a/pkgs/ch-proxy/sendfd.h b/pkgs/ch-proxy/sendfd.h index 8c99389..fc1d2f8 100644 --- a/pkgs/ch-proxy/sendfd.h +++ b/pkgs/ch-proxy/sendfd.h @@ -5,7 +5,23 @@ #include /* ssize_t */ #include /* iovec */ -ssize_t send_fd(int dst_fd, int fd, const struct iovec *); + +/* send_fd(chanFd, fd, *iov) + * + * chanFd: fd to sendmsg over; + * fd: fd to send; + * iov: extra data to send or NULL; + * + * returns: result of sendmsg, + * i.e. the number of bytes sent */ +ssize_t send_fd(int chanFd, int fd, const struct iovec *); + +/* recv_fd(chanFd, flags) + * + * chanFd: fd to recvmsg from; + * flags: recvmsg flags e.g. 0, or MSG_CMSG_CLOEXEC? + * + * returns: the received fd or -1 */ +int recv_fd(int chanFd, int flags); #endif /* _CH_PROXY_SENFD */ - diff --git a/pkgs/taps/main.c b/pkgs/taps/main.c index 82a02cd..05edb64 100644 --- a/pkgs/taps/main.c +++ b/pkgs/taps/main.c @@ -10,10 +10,11 @@ #include #include #include /* open, O_NONBLOCK, &c */ +#include #include #include #include - +#include #define __UAPI_DEF_IF_IFNAMSIZ 1 #include @@ -25,13 +26,19 @@ #define SUN_PATH_SZ 108 #define N_CONNS 16 -#define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE) -#define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR) +char *TEMP_PATHS[1024] = { 0 }; +int LAST_TEMP_PATH = -1; -#define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) -#define DO_OR_DIE_X(expr) \ +#define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE | IFF_PERSIST) +#define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR | IFF_PERSIST) + +#define PTR_OR_DIE(expr) TRUE_OR_DIE((expr) != NULL) +#define DO_OR_DIE(expr) TRUE_OR_DIE((expr) != -1) +#define TRUE_OR_DIE(expr, ...) TRUE_OR_(EXIT_FAILURE, expr, __VA_ARGS__) +#define TRUE_OR_WARN(expr, ...) TRUE_OR_(0, expr, __VA_ARGS__) +#define TRUE_OR_(status, expr, ...) \ do if (!(expr)) { \ - error(EXIT_FAILURE, errno, ("Failed assertion: " #expr)); \ + error(status, errno, "Failed assertion: " #expr "." __VA_ARGS__); \ } while(false) struct allow_pattern { @@ -72,38 +79,34 @@ bool match_mask(const char *test_addr, const char *expected_addr, const char *ma * `linux/Documentation/networking/tuntap.rst`. * * ifrFlags: IFF_TUN - TUN device (no Ethernet headers) - * IFF_TAP - TAP device + * IFF_TAP - TAP device * - * IFF_NO_PI - Do not provide packet information + * IFF_NO_PI - Do not provide packet information */ int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { - struct ifreq ifr; - int fd, err; + struct ifreq ifr = { 0 }; + int fd = -1, err = 0; - // if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { - // return tun_alloc_old(dev); - // } + DO_OR_DIE(fd = open("/dev/net/tun", openFlags)); - DO_OR_DIE_X((fd = open("/dev/net/tun", openFlags)) >= 0); - - memset(&ifr, 0, sizeof(ifr)); - - if (*dev) { + if (dev != NULL) { int devLen = strlen(dev); if (devLen >= IFNAMSIZ) { /* If client requests a name, we do want the entire name to fit */ errno = EINVAL; return EINVAL; } - strncpy(ifr.ifr_name, dev, IFNAMSIZ); + strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1); } + ifr.ifr_flags = ifrFlags; - if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) { + TRUE_OR_WARN((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) == 0); + if (err != 0) { close(fd); return err; } - strcpy(dev, ifr.ifr_name); + strncpy(dev, ifr.ifr_name, IFNAMSIZ); *out_fd = fd; return 0; } @@ -111,18 +114,20 @@ int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { int acceptRequests(const char *requestsPath, const struct allow_patterns *patterns) { int listener; struct sockaddr_un addr; - const bool t = 1; + const int t = 1; DO_OR_DIE(listener = socket(AF_UNIX, SOCK_SEQPACKET, 0)); - DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, 1) != 0); + DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, sizeof(t))); addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ); - DO_OR_DIE (bind(listener, &addr, sizeof(addr)) == -1); + strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ - 1); + DO_OR_DIE (bind(listener, &addr, sizeof(addr))); + PTR_OR_DIE(TEMP_PATHS[++LAST_TEMP_PATH] = strdup(requestsPath)); DO_OR_DIE(listen(listener, N_CONNS)); for (;;) { + /* Already changed my mind about looking at ucred, but keeping the code around for now */ int sock = -1; struct ucred cred = { 0 }; struct msghdr msg = { 0 }; @@ -136,13 +141,14 @@ int acceptRequests(const char *requestsPath, const struct allow_patterns *patter iov.iov_base = &req; iov.iov_len = sizeof(struct tap_request); - DO_OR_DIE(sock = accept(listener, NULL, NULL)); + DO_OR_DIE((sock = accept(listener, NULL, NULL))); - DO_OR_DIE_X(recvmsg(sock, &msg, 0) > 0); + TRUE_OR_DIE(recvmsg(sock, &msg, 0) > 0); + req.name[IFNAMSIZ] = 0; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (!(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS)) { - continue; - } + if (cmsg->cmsg_level != SOL_SOCKET) { continue; } + if (cmsg->cmsg_type != SCM_CREDENTIALS) { continue; } + if (CMSG_LEN(cmsg) < sizeof(struct ucred)) { continue; } memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); break; } @@ -162,12 +168,13 @@ int acceptRequests(const char *requestsPath, const struct allow_patterns *patter if (!allowed) { reply.status = AUTH_ERROR; } if (allowed) { /* O_CLOEXEC? */ - int fd = 0; - DO_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd)); + int fd = -1; + TRUE_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd) == 0); struct iovec iov = { 0 }; iov.iov_base = &reply; iov.iov_len = sizeof(struct tap_reply); - DO_OR_DIE_X(send_fd(sock, fd, &iov) > 0); + TRUE_OR_DIE(send_fd(sock, fd, &iov) > 0); + close(fd); } close(sock); } @@ -185,7 +192,8 @@ struct allow_patterns parsePatterns(const char *raw) { if (start < i) { ++nPatterns; } } - struct allow_pattern *patterns = calloc(nPatterns, sizeof(struct allow_pattern)); + struct allow_pattern *patterns = NULL; + PTR_OR_DIE(patterns = calloc(nPatterns, sizeof(struct allow_pattern))); int iPattern = 0; for (int i = 0; i < rawLen; ++i) { @@ -194,8 +202,8 @@ struct allow_patterns parsePatterns(const char *raw) { { const int start = i; for (; i < rawLen && !isspace(raw[i]); ++i) { } - if (i < rawLen) { - patterns[iPattern].name = strndup(&raw[start], i - start); + if (start < i) { + PTR_OR_DIE(patterns[iPattern].name = strndup(&raw[start], i - start)); iPattern += 1; } } @@ -207,37 +215,109 @@ struct allow_patterns parsePatterns(const char *raw) { return out; } +int get(const char *servePath, const char *ifname, short ifrFlags) { + /* TODO: sock: move out */ + int sock; + struct sockaddr_un addr; + + DO_OR_DIE(sock = socket(AF_UNIX, SOCK_SEQPACKET, 0)); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, servePath, SUN_PATH_SZ - 1); + DO_OR_DIE (connect(sock, &addr, sizeof(addr))); + + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg = NULL; + struct iovec iov = { 0 }; + struct tap_request req = { 0 }; + strncpy(req.name, ifname, IFNAMSIZ - 1); + req.ifrFlags = ifrFlags; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = &req; + iov.iov_len = sizeof(struct tap_request); + + TRUE_OR_DIE(sendmsg(sock, &msg, 0) > 0); + + int tunFd = -1; + DO_OR_DIE(tunFd = recv_fd(sock, 0)); + close(sock); + return tunFd; +} + +void cleanup(int signo, siginfo_t *info, void *_context) { + for (int i = 0; i <= LAST_TEMP_PATH; ++i) { + TRUE_OR_DIE(unlink(TEMP_PATHS[i]) != -1 || errno == ENOENT); + } + if (signo == SIGINT) { + exit(EXIT_SUCCESS); + } + errx(EXIT_FAILURE, "Exiting with signal %d", signo); +} + int main(int argc, char **argv) { + struct sigaction act = { 0 }; + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = cleanup; + DO_OR_DIE(sigaction(SIGINT, &act, NULL)); + DO_OR_DIE(sigaction(SIGSEGV, &act, NULL)); + bool cmdServe = false; - bool cmdGet = false; + bool cmdPass = false; + char *ifname = "vt%d"; char **rest = argv + 1; + char **end = argv + argc; - DO_OR_DIE_X(argc > 1); + TRUE_OR_DIE(argc > 1); if (strcmp(rest[0], "serve") == 0) { cmdServe = true; ++rest; - } else if (strcmp(rest[0], "get") == 0) { - cmdGet = true; + } else if (strcmp(rest[0], "pass") == 0) { + cmdPass = true; ++rest; + for (; rest != end && rest[0][0] == '-'; ++rest) { + if (strcmp(rest[0], "--")) { break; } + else if (strncmp(rest[0], "--ifname=", sizeof("--ifname="))) { + ifname = rest[0] + sizeof("--ifname="); + } + } } else { error(EINVAL, EINVAL, "no subcommand \"%s\"", rest[0]); } + int nextArgc = argc - (rest - argv); + char * const* nextArgv = rest; + const char *patternsRaw = secure_getenv("TAPS_ALLOW"); + if (patternsRaw == NULL) { + patternsRaw = "*"; + } + struct allow_patterns patterns = { 0 }; - if (cmdServe && patternsRaw != NULL) { - patterns = parsePatterns(patternsRaw); - DO_OR_DIE_X(patterns.patterns != NULL); + if (cmdServe) { + PTR_OR_DIE((patterns = parsePatterns(patternsRaw)).patterns); } const char *servePath = secure_getenv("TAPS_SOCK"); + if (servePath == NULL) { + servePath = "taps.sock"; + } if (cmdServe) { - error(patterns.patterns == NULL, EINVAL, "TAPS_ALLOW"); acceptRequests(servePath, &patterns); - } else if (cmdGet) { - error(ENOSYS, ENOSYS, "get"); + } else if (cmdPass) { + TRUE_OR_DIE(nextArgc > 0); + int fd = -1; + DO_OR_DIE(fd = get(servePath, ifname, 0)); + if (fd != 3) { + DO_OR_DIE(dup2(fd, 3)); + close(fd); + fd = 3; + } + DO_OR_DIE(execvp(nextArgv[0], nextArgv)); } else { error(EINVAL, EINVAL, "subcommand args"); } From db5f9a1ac8a692f7c6bd9e3f1d2dc59e4397bbce Mon Sep 17 00:00:00 2001 From: Else Someone Date: Wed, 28 Jan 2026 07:19:18 +0200 Subject: [PATCH 5/9] README: update & shuffle --- README.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 1faffc4..e90f8c6 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,32 @@ μVMs === -Slowly comprehending [spectrum-os](https://spectrum-os.org) and [microvm.nix](https://github.com/microvm-nix/microvm.nix), by reproducing bits and pieces using [NixOS](https://nixos.org), [systemd](https://nixos.org/manual/nixos/stable/options#opt-systemd.services), and [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor). +Slowly comprehending [spectrum-os](https://spectrum-os.org) and [microvm.nix](https://github.com/microvm-nix/microvm.nix), by reproducing bits and pieces using [NixOS](https://nixos.org) and [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor). Prior Art --- +### Projects + +- Obvious: Qubes, Spectrum, Genode, ... +- Google ChromeOS and [AWS Firecracker](https://firecracker-microvm.github.io): + - rust-vmm, [crosvm](https://crosvm.dev/book), and [sommelier](https://chromium.googlesource.com/chromiumos/platform2/+/master/vm_tools/sommelier); +- [Asahi muvm](https://github.com/AsahiLinux/muvm): microvms using [libkrun](https://github.com/containers/libkrun) and virtio-gpu "native context". +- [AppVM](https://github.com/jollheef/appvm): apparently available in Nixpkgs under `nixos/modules/virtualisation/appvm.nix`. Based on (NixOS,) qemu and libvirt. +- [valpackett/munix](https://git.clan.lol/clan/munix) +- ... + ### People -Following the right people is one of the most effective ways to maintain bibliography, a form of "importance sampling". +Following the right people might one of the most effective ways to maintain bibliography, a form of "importance sampling". The following are the people whose work, at the time of writing, I already know to watch out for. Whom am I missing? - [Alyssa Ross](https://github.com/alyssais) - Via [spectrum.org]https://spectrum-os.org/bibliography.html) and [Nixpkgs](https://github.com/NixOS/Nixpkgs). - [Demi Marie](https://demimarie.github.io): - Via [spectrum-devel](https://spectrum-os.org/lists/archives/spectrum-devel/2ff17d00-6603-46ae-9eb0-d4fa179db86c@gmail.com/) and Qubes. +- [valpackett](https://val.packett.cool) +- yureka - [Thomas Leonard](https://roscidus.com/blog/blog/2021/03/07/qubes-lite-with-kvm-and-wayland) - Via [spectrum-discuss](https://spectrum-os.org/lists/archives/spectrum-discuss/CAG4opy8BZn2pXDRBHOjcENFBHJON1LoG7A8GPdP0Wt_3KLaHyw@mail.gmail.com/), [qubes-lite, and `wayland-proxy-virtwl`](https://roscidus.com/blog/blog/2021/03/07/qubes-lite-with-kvm-and-wayland). - [Astro](https://spaceboyz.net/~astro/): @@ -25,16 +37,6 @@ The following are the people whose work, at the time of writing, I already know - Via [Qubes](https://doc.qubes-os.org/en/latest/developer/general/devel-books.html) [OS](https://theinvisiblethings.blogspot.com/2012/09/how-is-qubes-os-different-from.html). - ... -### Projects - -- Obvious: Qubes, Spectrum, Genode, ... -- Google ChromeOS and [AWS Firecracker](https://firecracker-microvm.github.io): - - rust-vmm, [crosvm](https://crosvm.dev/book), and [sommelier](https://chromium.googlesource.com/chromiumos/platform2/+/master/vm_tools/sommelier); -- [Asahi muvm](https://github.com/AsahiLinux/muvm): microvms using [libkrun](https://github.com/containers/libkrun) and virtio-gpu "native context". -- [AppVM](https://github.com/jollheef/appvm): apparently available in Nixpkgs under `nixos/modules/virtualisation/appvm.nix`. Based on (NixOS,) qemu and libvirt. -- ... - - ### Timeline The following are the questions I'd like to eventually answer about how virtualization happened: @@ -76,7 +78,10 @@ The following are some of the current "am I holding this right?" questions: - [ ] Filesystems: Virtio-blk appears to be the way to allocate persistent storage for VMs that require it. - In practice this means allocating a zvol or a contiguous file on the hypervisor, + The alternative is virtiofsd, which is potentially more RAM-hungry, and which significantly + limits bandwidth at least when used with explicitly enabling DAX (`microvm.nix`). + + In practice the virtio-blk way means allocating a zvol or a contiguous file on the hypervisor, to be exposed to the guest as a block device. One suspicion I have is that allocating a CoW filesystem (e.g. xfs, btrfs) on top of another CoW filesystem (e.g. zfs, as in xfs-on-zvol) may have non-trivial implications for fragmentation, depending on parameters like the chunksizes. @@ -88,14 +93,13 @@ The following are some of the current "am I holding this right?" questions: #### `spectrum-os` -...is in active development and not advertised as user-ready yet. -Spectrum OS appears to be a balance-shifting project, building up towards a principled solution, which must require patience... -It does not, for example, reuse NixOS systemd modules, but uses s6 instead. +Yes. Soon. #### `microvm.nix` Is inherently static. -A cynical spin on `microvm.nix` would be, and I mean it with utmost respect, that it's a glorified qemu flags generator, written in Nix. +A cynical spin on `microvm.nix` would be, and I mean it with utmost respect, that it's a glorified qemu flags generator, written in Nix, +and Nix is slow. When using `microvm.nix` you write, for example, each TAP's `hwaddr` by hand, and then rebuild the "runner script". When using the "fully-declarative mode" you also engangle the guest's and the hypervisor's life cycles, and double the NixOS evaluation time. Microvm-nix ships support for a wide selection of different hypervisors, but you may only care about e.g. `cloud-hypervisor`. @@ -105,3 +109,5 @@ An instructive reference implementation and a convenient entry point, `microvm.n I only noticed the option in `man configuration.nix` a few days ago, so I just never tried. Long-term I'd definitely prefer not to use qemu. + +The golang tool relies on obscure Nix CLI options and "channels" (as in tarballs). From 8bbafe0f0ac0a8131fb330bf82c32b68a144cdf5 Mon Sep 17 00:00:00 2001 From: "Else, Someone" Date: Thu, 15 Jan 2026 08:24:08 +0200 Subject: [PATCH 6/9] taps: wip: init --- pkgs/ch-proxy/sendfd.h | 9 ++ pkgs/taps/.envrc | 1 + pkgs/taps/main.c | 241 +++++++++++++++++++++++++++++++++++++++++ pkgs/taps/meson.build | 4 + pkgs/taps/package.nix | 44 ++++++++ shell.nix | 10 ++ 6 files changed, 309 insertions(+) create mode 100644 pkgs/ch-proxy/sendfd.h create mode 100644 pkgs/taps/.envrc create mode 100644 pkgs/taps/main.c create mode 100644 pkgs/taps/meson.build create mode 100644 pkgs/taps/package.nix create mode 100644 shell.nix diff --git a/pkgs/ch-proxy/sendfd.h b/pkgs/ch-proxy/sendfd.h new file mode 100644 index 0000000..65042d8 --- /dev/null +++ b/pkgs/ch-proxy/sendfd.h @@ -0,0 +1,9 @@ +#ifndef _CH_PROXY_SENFD +#define _CH_PROXY_SENFD + +#include + +ssize_t send_fd(int dst_fd, int fd); + +#endif _CH_PROXY_SENFD + diff --git a/pkgs/taps/.envrc b/pkgs/taps/.envrc new file mode 100644 index 0000000..35f8c10 --- /dev/null +++ b/pkgs/taps/.envrc @@ -0,0 +1 @@ +use nix ../../ -A pkgs.taps diff --git a/pkgs/taps/main.c b/pkgs/taps/main.c new file mode 100644 index 0000000..c921bd0 --- /dev/null +++ b/pkgs/taps/main.c @@ -0,0 +1,241 @@ +#define _GNU_SOURCE + +#include /* secure_getenv */ +#include +#include +#include +#include +#include +#include +#include +#include +#include /* open, O_NONBLOCK, &c */ +#include /* perror */ +#include +#include + + +#define __UAPI_DEF_IF_IFNAMSIZ 1 +#include +#include + +#include "sendfd.h" + +// From `man unix` +#define SUN_PATH_SZ 108 +#define N_CONNS 16 + +#define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE) +#define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR) + +#define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) +#define DO_OR_DIE_X(expr) \ + do if (!(expr)) { \ + perror((#expr)); \ + exit(EXIT_FAILURE); \ + } while(false) + +struct allow_pattern { + // enum { USER = 1, GROUP = 2 } type; + // union { uid_t uid, gid_t gid } xid; + char *name; +}; +struct allow_patterns { + size_t n; + struct allow_pattern *patterns; +}; + +/* Running on the same host, not caring for alignment */ +struct tap_request { + short ifrFlags; /* 0 to use defaults: IFF_TAP | IFF_NO_PI | IFF_VNET_HDR */ + char name[IFNAMSIZ]; +}; + +struct tap_reply { + enum { OK = 0, AUTH_ERROR = 1 } status; + char name[IFNAMSIZ]; +}; + +int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd); + +bool match_mask(const char *test_addr, const char *expected_addr, const char *mask, int n) { + for (int octet = 0; octet < n; ++octet) { + if ((test_addr[octet] & mask[octet]) != expected_addr[octet]) { + return false; + } + } + return true; +} + +/* + * Adapted from spectrum's `mktuntap.c` (2019 Alyssa Ross + * GPL-2.0-only), which in turn adapts `tun_alloc` from + * `linux/Documentation/networking/tuntap.rst`. + * + * ifrFlags: IFF_TUN - TUN device (no Ethernet headers) + * IFF_TAP - TAP device + * + * IFF_NO_PI - Do not provide packet information + */ +int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { + struct ifreq ifr; + int fd, err; + + // if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { + // return tun_alloc_old(dev); + // } + + DO_OR_DIE_X((fd = open("/dev/net/tun", openFlags)) >= 0); + + memset(&ifr, 0, sizeof(ifr)); + + if (*dev) { + int devLen = strlen(dev); + if (devLen >= IFNAMSIZ) { + /* If client requests a name, we do want the entire name to fit */ + errno = EINVAL; + return EINVAL; + } + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + } + + if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) { + close(fd); + return err; + } + + strcpy(dev, ifr.ifr_name); + *out_fd = fd; + return 0; +} + +int acceptRequests(const char *requestsPath, const struct allow_patterns *patterns) { + int listener; + struct sockaddr_un addr; + const bool t = 1; + + DO_OR_DIE(listener = socket(AF_UNIX, SOCK_SEQPACKET, 0)); + DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, 1) != 0); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ); + DO_OR_DIE (bind(listener, &addr, sizeof(addr)) == -1); + + DO_OR_DIE(listen(listener, N_CONNS)); + + for (;;) { + int sock = -1; + struct ucred cred = { 0 }; + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg = NULL; + struct iovec iov = { 0 }; + struct tap_request req = { 0 }; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = &req; + iov.iov_len = sizeof(struct tap_request); + + DO_OR_DIE(sock = accept(listener, NULL, NULL)); + + DO_OR_DIE_X(recvmsg(sock, &msg, 0) > 0); + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (!(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS)) { + continue; + } + memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); + break; + } + + if (req.ifrFlags == 0) { + req.ifrFlags = IFR_FLAGS_DEFAULT; + } + + bool allowed = false; + for (int i = 0; !allowed && i < patterns->n; ++i) { + bool ifnameOk = fnmatch(patterns->patterns[i].name, req.name, 0) == 0; + bool flagsOk = (req.ifrFlags & IFR_FLAGS_ALLOWED) == req.ifrFlags; + allowed = ifnameOk && flagsOk; + } + + struct tap_reply reply = { 0 }; + if (!allowed) { reply.status = AUTH_ERROR; } + if (allowed) { + /* O_CLOEXEC? */ + int fd = 0; + DO_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd)); + struct iovec iov = { 0 }; + iov.iov_base = &reply; + iov.iov_len = sizeof(struct tap_reply); + DO_OR_DIE_X(send_fd(sock, fd, &iov) > 0); + } + close(sock); + } + close(listener); +} + +struct allow_patterns parsePatterns(const char *raw) { + const size_t rawLen = strlen(raw); + + size_t nPatterns = 0; + for (int i = 0; i < rawLen; ++i) { + const int start = i; + if (isspace(raw[i])) { continue; } + for (; i < rawLen && !isspace(raw[i]); ++i) { } + if (start < i) { ++nPatterns; } + } + + struct allow_pattern *patterns = calloc(nPatterns, sizeof(struct allow_pattern)); + + int iPattern = 0; + for (int i = 0; i < rawLen; ++i) { + if (isspace(raw[i])) { continue; } + /* used to have per-group/per-user patterns, "u:$username:$pattern", &c - gone */ + { + const int start = i; + for (; i < rawLen && !isspace(raw[i]); ++i) { } + if (i < rawLen) { + patterns[iPattern].name = strndup(&raw[start], i - start); + iPattern += 1; + } + } + } + struct allow_patterns out = { + .n = nPatterns, + .patterns = patterns + }; + return out; +} + +int main(int argc, char **argv) { + bool cmdServe = false; + bool cmdGet = false; + + char **rest = argv + 1; + + if (strcmp(rest[0], "serve") == 0) { + cmdServe = true; + ++rest; + } else if (strcmp(rest[0], "get") == 0) { + cmdGet = true; + ++rest; + } else { + exit(EXIT_FAILURE); + } + + const char *patternsRaw = secure_getenv("TAPS_ALLOW"); + struct allow_patterns patterns = { 0 }; + if (cmdServe && patternsRaw != NULL) { patterns = parsePatterns(patternsRaw); } + DO_OR_DIE_X(patterns.patterns != NULL); + + const char *servePath = secure_getenv("TAPS_SOCK"); + + if (cmdServe && patterns.patterns != NULL) { + acceptRequests(servePath, &patterns); + } else if (cmdGet) { + } else { + exit(EXIT_FAILURE); } + + return 0; +} diff --git a/pkgs/taps/meson.build b/pkgs/taps/meson.build new file mode 100644 index 0000000..06057b4 --- /dev/null +++ b/pkgs/taps/meson.build @@ -0,0 +1,4 @@ +project('taps', 'c') + +sendfd = dependency('sendfd') +executable('taps', 'main.c', dependencies: [sendfd], install: true) diff --git a/pkgs/taps/package.nix b/pkgs/taps/package.nix new file mode 100644 index 0000000..c666cd9 --- /dev/null +++ b/pkgs/taps/package.nix @@ -0,0 +1,44 @@ +{ + lib, + stdenv, + meson, + pkg-config, + rustc, + ninja, + ch-proxy, +}: + +stdenv.mkDerivation { + pname = "taps"; + version = "0.0.0"; + src = + let + fs = lib.fileset; + in + fs.toSource { + root = ./.; + fileset = fs.unions [ + ./meson.build + ./main.c + ]; + }; + + nativeBuildInputs = [ + ninja + meson + pkg-config + rustc + ]; + buildInputs = [ ch-proxy ]; +} +# { lib, rustPlatform }: +# +# rustPlatform.buildRustPackage { +# pname = "taps"; +# version = "0.0.0"; +# src = let fs = lib.filesystem; in fs.toSource { +# root = ./.; +# fileset = fs.unions [ +# ]; +# }; +# }; diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..497375b --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +with import { }; + +mkShell.override { stdenv = stdenvNoCC; } { + packages = map lib.getBin [ + cloud-hypervisor + virtiofsd + crosvm # virtio-gpu + npins + ]; +} From 564913c123a87e9db2c9c133d1403823f925cdb0 Mon Sep 17 00:00:00 2001 From: "Else, Someone" Date: Thu, 22 Jan 2026 14:29:45 +0200 Subject: [PATCH 7/9] fixup! taps: wip: init --- pkgs/ch-proxy/meson.build | 8 ++++++- pkgs/ch-proxy/package.nix | 3 +++ pkgs/ch-proxy/proxy.c | 47 +++------------------------------------ pkgs/ch-proxy/sendfd.c | 38 +++++++++++++++++++++++++++++++ pkgs/ch-proxy/sendfd.h | 8 ++++--- profiles/uvms-guest.nix | 1 + profiles/uvms-users.nix | 1 - 7 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 pkgs/ch-proxy/sendfd.c diff --git a/pkgs/ch-proxy/meson.build b/pkgs/ch-proxy/meson.build index e3976a2..379f96f 100644 --- a/pkgs/ch-proxy/meson.build +++ b/pkgs/ch-proxy/meson.build @@ -1,3 +1,9 @@ project('ch-proxy', 'c') -executable('ch-proxy', 'proxy.c', install: true) +pkg = import('pkgconfig') + +sendfd = library('sendfd', [ 'sendfd.c', 'sendfd.h' ], install: true) +pkg.generate(sendfd) +install_headers('sendfd.h') + +executable('ch-proxy', 'proxy.c', link_with: [sendfd], install: true) diff --git a/pkgs/ch-proxy/package.nix b/pkgs/ch-proxy/package.nix index 9fd3b21..e94eec8 100644 --- a/pkgs/ch-proxy/package.nix +++ b/pkgs/ch-proxy/package.nix @@ -8,6 +8,7 @@ stdenv.mkDerivation { pname = "ch-proxy"; version = "0.0.0"; + outputs = [ "out" "lib" ]; nativeBuildInputs = [ meson ninja @@ -19,6 +20,8 @@ stdenv.mkDerivation { fs.toSource { fileset = fs.unions [ ./proxy.c + ./sendfd.c + ./sendfd.h ./meson.build ]; root = ./.; diff --git a/pkgs/ch-proxy/proxy.c b/pkgs/ch-proxy/proxy.c index ed1dea0..46730e6 100644 --- a/pkgs/ch-proxy/proxy.c +++ b/pkgs/ch-proxy/proxy.c @@ -9,9 +9,9 @@ #include -struct msghdr mk_msghdr(); +#include "sendfd.h" + int ch_connect(const char*, const char*); -ssize_t send_fd(int, int); #define _WRITE_CONFIRM(fd, buf, buflen) {if (write((fd), (buf), (buflen)) != (buflen)) { perror("ch-proxy/write/partial write"); exit(EXIT_FAILURE); }} @@ -168,19 +168,13 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - if (send_fd(1, s) == -1) { + if (send_fd(1, s, NULL) == -1) { perror("ssh-vsock-proxy/main/send_fd"); return EXIT_FAILURE; } return 0; } -struct msghdr mk_msghdr() { - struct msghdr msg; - memset(&msg, 0, sizeof(msg)); - - return msg; -} int ch_connect(const char *path, const char *port) { int s = socket(AF_UNIX, SOCK_STREAM, 0); @@ -212,38 +206,3 @@ int ch_connect(const char *path, const char *port) { return s; } - -ssize_t send_fd(int dst_fd, int fd) { - struct msghdr msg = mk_msghdr(); - - /* openssh expects to receive a dummy length=1 iovec? */ - char ch; - struct iovec vec; - vec.iov_base = &ch; - vec.iov_len = 1; - msg.msg_iov = &vec; - msg.msg_iovlen = 1; - - union { - struct cmsghdr align; - char buf[CMSG_SPACE(sizeof(int))]; - } u; - - msg.msg_control = u.buf; - msg.msg_controllen = sizeof(u.buf); - - struct cmsghdr *cmptr; - cmptr = CMSG_FIRSTHDR(&msg); - - if (cmptr == NULL) { - fprintf(stderr, "ch-proxy/send_fd/CMSG_FIRSTHDR: failed to initialize msg_control\n"); - exit(EXIT_FAILURE); - } - - cmptr->cmsg_len = CMSG_LEN(sizeof(int)); - cmptr->cmsg_level = SOL_SOCKET; - cmptr->cmsg_type = SCM_RIGHTS; - *((int*) CMSG_DATA(cmptr)) = fd; - - return (sendmsg(dst_fd, &msg, 0)); -} diff --git a/pkgs/ch-proxy/sendfd.c b/pkgs/ch-proxy/sendfd.c new file mode 100644 index 0000000..c649316 --- /dev/null +++ b/pkgs/ch-proxy/sendfd.c @@ -0,0 +1,38 @@ +#include "sendfd.h" +#include "sys/socket.h" /* cmsghdr */ +#include "stdio.h" /* perror */ + + +ssize_t send_fd(int dst_fd, int fd, const struct iovec *iov) { + struct msghdr msg = { 0 }; + + /* openssh expects to receive a dummy length=1 iovec? */ + char ch = 0; + struct iovec vecDefault = { 0 }; + vecDefault.iov_base = &ch; + vecDefault.iov_len = 1; + msg.msg_iov = iov == NULL ? &vecDefault : iov; + msg.msg_iovlen = 1; + + union { + struct cmsghdr align; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + struct cmsghdr *cmptr; + cmptr = CMSG_FIRSTHDR(&msg); + + if (cmptr == NULL) { + perror("ch-proxy/send_fd/CMSG_FIRSTHDR: failed to initialize msg_control\n"); + } + + cmptr->cmsg_len = CMSG_LEN(sizeof(int)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + *((int*) CMSG_DATA(cmptr)) = fd; + + return (sendmsg(dst_fd, &msg, 0)); +} diff --git a/pkgs/ch-proxy/sendfd.h b/pkgs/ch-proxy/sendfd.h index 65042d8..8c99389 100644 --- a/pkgs/ch-proxy/sendfd.h +++ b/pkgs/ch-proxy/sendfd.h @@ -1,9 +1,11 @@ #ifndef _CH_PROXY_SENFD #define _CH_PROXY_SENFD -#include +#include /* size_t */ +#include /* ssize_t */ +#include /* iovec */ -ssize_t send_fd(int dst_fd, int fd); +ssize_t send_fd(int dst_fd, int fd, const struct iovec *); -#endif _CH_PROXY_SENFD +#endif /* _CH_PROXY_SENFD */ diff --git a/profiles/uvms-guest.nix b/profiles/uvms-guest.nix index e8c307d..281f343 100644 --- a/profiles/uvms-guest.nix +++ b/profiles/uvms-guest.nix @@ -41,6 +41,7 @@ in volumes = [ { image = "swapfile.img"; + serial = "swapfiles"; mountPoint = "/var/swapfiles"; size = 1024; } diff --git a/profiles/uvms-users.nix b/profiles/uvms-users.nix index e75ac8f..e7bbacf 100644 --- a/profiles/uvms-users.nix +++ b/profiles/uvms-users.nix @@ -29,7 +29,6 @@ in }; config = mergeIf cfg.enable [ { - services.getty.autologinUser = "user"; security.sudo.wheelNeedsPassword = false; users.mutableUsers = false; users.users.user = { From 691a193bba4f2c9df5fe9aec049ca24ee887a866 Mon Sep 17 00:00:00 2001 From: Else Someone Date: Thu, 22 Jan 2026 15:08:38 +0200 Subject: [PATCH 8/9] taps: fix error messages --- pkgs/taps/main.c | 21 +++++++++++++-------- shell.nix | 3 +++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pkgs/taps/main.c b/pkgs/taps/main.c index c921bd0..82a02cd 100644 --- a/pkgs/taps/main.c +++ b/pkgs/taps/main.c @@ -10,7 +10,7 @@ #include #include #include /* open, O_NONBLOCK, &c */ -#include /* perror */ +#include #include #include @@ -31,8 +31,7 @@ #define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) #define DO_OR_DIE_X(expr) \ do if (!(expr)) { \ - perror((#expr)); \ - exit(EXIT_FAILURE); \ + error(EXIT_FAILURE, errno, ("Failed assertion: " #expr)); \ } while(false) struct allow_pattern { @@ -214,6 +213,7 @@ int main(int argc, char **argv) { char **rest = argv + 1; + DO_OR_DIE_X(argc > 1); if (strcmp(rest[0], "serve") == 0) { cmdServe = true; ++rest; @@ -221,21 +221,26 @@ int main(int argc, char **argv) { cmdGet = true; ++rest; } else { - exit(EXIT_FAILURE); + error(EINVAL, EINVAL, "no subcommand \"%s\"", rest[0]); } const char *patternsRaw = secure_getenv("TAPS_ALLOW"); struct allow_patterns patterns = { 0 }; - if (cmdServe && patternsRaw != NULL) { patterns = parsePatterns(patternsRaw); } - DO_OR_DIE_X(patterns.patterns != NULL); + if (cmdServe && patternsRaw != NULL) { + patterns = parsePatterns(patternsRaw); + DO_OR_DIE_X(patterns.patterns != NULL); + } const char *servePath = secure_getenv("TAPS_SOCK"); - if (cmdServe && patterns.patterns != NULL) { + if (cmdServe) { + error(patterns.patterns == NULL, EINVAL, "TAPS_ALLOW"); acceptRequests(servePath, &patterns); } else if (cmdGet) { + error(ENOSYS, ENOSYS, "get"); } else { - exit(EXIT_FAILURE); } + error(EINVAL, EINVAL, "subcommand args"); + } return 0; } diff --git a/shell.nix b/shell.nix index 497375b..f8bb9a7 100644 --- a/shell.nix +++ b/shell.nix @@ -6,5 +6,8 @@ mkShell.override { stdenv = stdenvNoCC; } { virtiofsd crosvm # virtio-gpu npins + ] ++ [ + man-pages + linux-manual ]; } From 1c5e2b7e89930da572a10588a298fc9077c629b8 Mon Sep 17 00:00:00 2001 From: Else Someone Date: Wed, 28 Jan 2026 07:04:57 +0200 Subject: [PATCH 9/9] taps: MVE - TUNSETIFF works, the interface is indeed created. - An fd is indeed passed over the unix socket, - and is a valid (enough) fd because it can fed into `dup2`. - `nix run -f . --offline pkgs.taps -- pass sleep 5` works, the interface exists for 5 seconds and disappears - `nix run -f . --offline pkgs.taps -- pass ch-remote --api-socket=$HOME/uvms/nixos/vmm.sock add-net fd=3` obscurely fails, killing the VMM with: ```console [root@nixos:~]# cloud-hypervisor: 12.388270s: <_net1_qp0> ERROR:/build/source/net_util/src/queue_pair.rs:112 -- net: tx: failed writing to tap: Input/output error (os er ror 5) cloud-hypervisor: 12.388459s: <_net1_qp0> ERROR:virtio-devices/src/thread_helper.rs:54 -- Error running worker: HandleEvent(Error processing TX queue: NetQueuePair(Write Tap(Os { code: 5, kind: Uncategorized, message: "Input/output error" }))) ``` --- pkgs/ch-proxy/sendfd.c | 36 +++++++++ pkgs/ch-proxy/sendfd.h | 20 ++++- pkgs/taps/main.c | 172 ++++++++++++++++++++++++++++++----------- 3 files changed, 180 insertions(+), 48 deletions(-) diff --git a/pkgs/ch-proxy/sendfd.c b/pkgs/ch-proxy/sendfd.c index c649316..b20e284 100644 --- a/pkgs/ch-proxy/sendfd.c +++ b/pkgs/ch-proxy/sendfd.c @@ -36,3 +36,39 @@ ssize_t send_fd(int dst_fd, int fd, const struct iovec *iov) { return (sendmsg(dst_fd, &msg, 0)); } + +int recv_fd(int sock, int flags) { + int out = -1; + + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg = NULL; + struct iovec iov = { 0 }; + char dummy = 0; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = &dummy; + iov.iov_len = sizeof(dummy); + + union { + struct cmsghdr align; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + int bytes = 0; + if ((bytes = recvmsg(sock, &msg, flags)) < 0) { + perror("recv_fd: recvmsg"); + return -1; + } + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) { continue; } + if (cmsg->cmsg_type != SCM_RIGHTS) { continue; } + if (CMSG_LEN(cmsg) < sizeof(out)) { continue; } + out = *(int*)CMSG_DATA(cmsg); + } + return out; +} diff --git a/pkgs/ch-proxy/sendfd.h b/pkgs/ch-proxy/sendfd.h index 8c99389..fc1d2f8 100644 --- a/pkgs/ch-proxy/sendfd.h +++ b/pkgs/ch-proxy/sendfd.h @@ -5,7 +5,23 @@ #include /* ssize_t */ #include /* iovec */ -ssize_t send_fd(int dst_fd, int fd, const struct iovec *); + +/* send_fd(chanFd, fd, *iov) + * + * chanFd: fd to sendmsg over; + * fd: fd to send; + * iov: extra data to send or NULL; + * + * returns: result of sendmsg, + * i.e. the number of bytes sent */ +ssize_t send_fd(int chanFd, int fd, const struct iovec *); + +/* recv_fd(chanFd, flags) + * + * chanFd: fd to recvmsg from; + * flags: recvmsg flags e.g. 0, or MSG_CMSG_CLOEXEC? + * + * returns: the received fd or -1 */ +int recv_fd(int chanFd, int flags); #endif /* _CH_PROXY_SENFD */ - diff --git a/pkgs/taps/main.c b/pkgs/taps/main.c index 82a02cd..05edb64 100644 --- a/pkgs/taps/main.c +++ b/pkgs/taps/main.c @@ -10,10 +10,11 @@ #include #include #include /* open, O_NONBLOCK, &c */ +#include #include #include #include - +#include #define __UAPI_DEF_IF_IFNAMSIZ 1 #include @@ -25,13 +26,19 @@ #define SUN_PATH_SZ 108 #define N_CONNS 16 -#define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE) -#define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR) +char *TEMP_PATHS[1024] = { 0 }; +int LAST_TEMP_PATH = -1; -#define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) -#define DO_OR_DIE_X(expr) \ +#define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE | IFF_PERSIST) +#define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR | IFF_PERSIST) + +#define PTR_OR_DIE(expr) TRUE_OR_DIE((expr) != NULL) +#define DO_OR_DIE(expr) TRUE_OR_DIE((expr) != -1) +#define TRUE_OR_DIE(expr, ...) TRUE_OR_(EXIT_FAILURE, expr, __VA_ARGS__) +#define TRUE_OR_WARN(expr, ...) TRUE_OR_(0, expr, __VA_ARGS__) +#define TRUE_OR_(status, expr, ...) \ do if (!(expr)) { \ - error(EXIT_FAILURE, errno, ("Failed assertion: " #expr)); \ + error(status, errno, "Failed assertion: " #expr "." __VA_ARGS__); \ } while(false) struct allow_pattern { @@ -72,38 +79,34 @@ bool match_mask(const char *test_addr, const char *expected_addr, const char *ma * `linux/Documentation/networking/tuntap.rst`. * * ifrFlags: IFF_TUN - TUN device (no Ethernet headers) - * IFF_TAP - TAP device + * IFF_TAP - TAP device * - * IFF_NO_PI - Do not provide packet information + * IFF_NO_PI - Do not provide packet information */ int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { - struct ifreq ifr; - int fd, err; + struct ifreq ifr = { 0 }; + int fd = -1, err = 0; - // if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { - // return tun_alloc_old(dev); - // } + DO_OR_DIE(fd = open("/dev/net/tun", openFlags)); - DO_OR_DIE_X((fd = open("/dev/net/tun", openFlags)) >= 0); - - memset(&ifr, 0, sizeof(ifr)); - - if (*dev) { + if (dev != NULL) { int devLen = strlen(dev); if (devLen >= IFNAMSIZ) { /* If client requests a name, we do want the entire name to fit */ errno = EINVAL; return EINVAL; } - strncpy(ifr.ifr_name, dev, IFNAMSIZ); + strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1); } + ifr.ifr_flags = ifrFlags; - if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) { + TRUE_OR_WARN((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) == 0); + if (err != 0) { close(fd); return err; } - strcpy(dev, ifr.ifr_name); + strncpy(dev, ifr.ifr_name, IFNAMSIZ); *out_fd = fd; return 0; } @@ -111,18 +114,20 @@ int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { int acceptRequests(const char *requestsPath, const struct allow_patterns *patterns) { int listener; struct sockaddr_un addr; - const bool t = 1; + const int t = 1; DO_OR_DIE(listener = socket(AF_UNIX, SOCK_SEQPACKET, 0)); - DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, 1) != 0); + DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, sizeof(t))); addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ); - DO_OR_DIE (bind(listener, &addr, sizeof(addr)) == -1); + strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ - 1); + DO_OR_DIE (bind(listener, &addr, sizeof(addr))); + PTR_OR_DIE(TEMP_PATHS[++LAST_TEMP_PATH] = strdup(requestsPath)); DO_OR_DIE(listen(listener, N_CONNS)); for (;;) { + /* Already changed my mind about looking at ucred, but keeping the code around for now */ int sock = -1; struct ucred cred = { 0 }; struct msghdr msg = { 0 }; @@ -136,13 +141,14 @@ int acceptRequests(const char *requestsPath, const struct allow_patterns *patter iov.iov_base = &req; iov.iov_len = sizeof(struct tap_request); - DO_OR_DIE(sock = accept(listener, NULL, NULL)); + DO_OR_DIE((sock = accept(listener, NULL, NULL))); - DO_OR_DIE_X(recvmsg(sock, &msg, 0) > 0); + TRUE_OR_DIE(recvmsg(sock, &msg, 0) > 0); + req.name[IFNAMSIZ] = 0; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (!(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS)) { - continue; - } + if (cmsg->cmsg_level != SOL_SOCKET) { continue; } + if (cmsg->cmsg_type != SCM_CREDENTIALS) { continue; } + if (CMSG_LEN(cmsg) < sizeof(struct ucred)) { continue; } memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); break; } @@ -162,12 +168,13 @@ int acceptRequests(const char *requestsPath, const struct allow_patterns *patter if (!allowed) { reply.status = AUTH_ERROR; } if (allowed) { /* O_CLOEXEC? */ - int fd = 0; - DO_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd)); + int fd = -1; + TRUE_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd) == 0); struct iovec iov = { 0 }; iov.iov_base = &reply; iov.iov_len = sizeof(struct tap_reply); - DO_OR_DIE_X(send_fd(sock, fd, &iov) > 0); + TRUE_OR_DIE(send_fd(sock, fd, &iov) > 0); + close(fd); } close(sock); } @@ -185,7 +192,8 @@ struct allow_patterns parsePatterns(const char *raw) { if (start < i) { ++nPatterns; } } - struct allow_pattern *patterns = calloc(nPatterns, sizeof(struct allow_pattern)); + struct allow_pattern *patterns = NULL; + PTR_OR_DIE(patterns = calloc(nPatterns, sizeof(struct allow_pattern))); int iPattern = 0; for (int i = 0; i < rawLen; ++i) { @@ -194,8 +202,8 @@ struct allow_patterns parsePatterns(const char *raw) { { const int start = i; for (; i < rawLen && !isspace(raw[i]); ++i) { } - if (i < rawLen) { - patterns[iPattern].name = strndup(&raw[start], i - start); + if (start < i) { + PTR_OR_DIE(patterns[iPattern].name = strndup(&raw[start], i - start)); iPattern += 1; } } @@ -207,37 +215,109 @@ struct allow_patterns parsePatterns(const char *raw) { return out; } +int get(const char *servePath, const char *ifname, short ifrFlags) { + /* TODO: sock: move out */ + int sock; + struct sockaddr_un addr; + + DO_OR_DIE(sock = socket(AF_UNIX, SOCK_SEQPACKET, 0)); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, servePath, SUN_PATH_SZ - 1); + DO_OR_DIE (connect(sock, &addr, sizeof(addr))); + + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg = NULL; + struct iovec iov = { 0 }; + struct tap_request req = { 0 }; + strncpy(req.name, ifname, IFNAMSIZ - 1); + req.ifrFlags = ifrFlags; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = &req; + iov.iov_len = sizeof(struct tap_request); + + TRUE_OR_DIE(sendmsg(sock, &msg, 0) > 0); + + int tunFd = -1; + DO_OR_DIE(tunFd = recv_fd(sock, 0)); + close(sock); + return tunFd; +} + +void cleanup(int signo, siginfo_t *info, void *_context) { + for (int i = 0; i <= LAST_TEMP_PATH; ++i) { + TRUE_OR_DIE(unlink(TEMP_PATHS[i]) != -1 || errno == ENOENT); + } + if (signo == SIGINT) { + exit(EXIT_SUCCESS); + } + errx(EXIT_FAILURE, "Exiting with signal %d", signo); +} + int main(int argc, char **argv) { + struct sigaction act = { 0 }; + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = cleanup; + DO_OR_DIE(sigaction(SIGINT, &act, NULL)); + DO_OR_DIE(sigaction(SIGSEGV, &act, NULL)); + bool cmdServe = false; - bool cmdGet = false; + bool cmdPass = false; + char *ifname = "vt%d"; char **rest = argv + 1; + char **end = argv + argc; - DO_OR_DIE_X(argc > 1); + TRUE_OR_DIE(argc > 1); if (strcmp(rest[0], "serve") == 0) { cmdServe = true; ++rest; - } else if (strcmp(rest[0], "get") == 0) { - cmdGet = true; + } else if (strcmp(rest[0], "pass") == 0) { + cmdPass = true; ++rest; + for (; rest != end && rest[0][0] == '-'; ++rest) { + if (strcmp(rest[0], "--")) { break; } + else if (strncmp(rest[0], "--ifname=", sizeof("--ifname="))) { + ifname = rest[0] + sizeof("--ifname="); + } + } } else { error(EINVAL, EINVAL, "no subcommand \"%s\"", rest[0]); } + int nextArgc = argc - (rest - argv); + char * const* nextArgv = rest; + const char *patternsRaw = secure_getenv("TAPS_ALLOW"); + if (patternsRaw == NULL) { + patternsRaw = "*"; + } + struct allow_patterns patterns = { 0 }; - if (cmdServe && patternsRaw != NULL) { - patterns = parsePatterns(patternsRaw); - DO_OR_DIE_X(patterns.patterns != NULL); + if (cmdServe) { + PTR_OR_DIE((patterns = parsePatterns(patternsRaw)).patterns); } const char *servePath = secure_getenv("TAPS_SOCK"); + if (servePath == NULL) { + servePath = "taps.sock"; + } if (cmdServe) { - error(patterns.patterns == NULL, EINVAL, "TAPS_ALLOW"); acceptRequests(servePath, &patterns); - } else if (cmdGet) { - error(ENOSYS, ENOSYS, "get"); + } else if (cmdPass) { + TRUE_OR_DIE(nextArgc > 0); + int fd = -1; + DO_OR_DIE(fd = get(servePath, ifname, 0)); + if (fd != 3) { + DO_OR_DIE(dup2(fd, 3)); + close(fd); + fd = 3; + } + DO_OR_DIE(execvp(nextArgv[0], nextArgv)); } else { error(EINVAL, EINVAL, "subcommand args"); }