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 + ]; +}