From ebdf3714213e73672af24eb8553ee9533e0bae98 Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Mon, 27 Mar 2017 17:33:50 +0200 Subject: [PATCH 1/6] miragesdk: fork pkg/init to experiment with removal of dhcpcd Signed-off-by: Thomas Gazagnaire --- projects/miragesdk/init/.gitignore | 2 + projects/miragesdk/init/Dockerfile | 10 + projects/miragesdk/init/Makefile | 52 + projects/miragesdk/init/etc/init.d/containerd | 9 + projects/miragesdk/init/etc/init.d/containers | 31 + projects/miragesdk/init/etc/init.d/rcS | 106 ++ projects/miragesdk/init/etc/inittab | 15 + projects/miragesdk/init/etc/issue | 12 + projects/miragesdk/init/init | 44 + projects/miragesdk/init/start-stop-daemon.c | 1054 +++++++++++++++++ 10 files changed, 1335 insertions(+) create mode 100644 projects/miragesdk/init/.gitignore create mode 100644 projects/miragesdk/init/Dockerfile create mode 100644 projects/miragesdk/init/Makefile create mode 100755 projects/miragesdk/init/etc/init.d/containerd create mode 100755 projects/miragesdk/init/etc/init.d/containers create mode 100755 projects/miragesdk/init/etc/init.d/rcS create mode 100644 projects/miragesdk/init/etc/inittab create mode 100644 projects/miragesdk/init/etc/issue create mode 100755 projects/miragesdk/init/init create mode 100644 projects/miragesdk/init/start-stop-daemon.c diff --git a/projects/miragesdk/init/.gitignore b/projects/miragesdk/init/.gitignore new file mode 100644 index 000000000..cf40cde3b --- /dev/null +++ b/projects/miragesdk/init/.gitignore @@ -0,0 +1,2 @@ +sbin/ +usr/ diff --git a/projects/miragesdk/init/Dockerfile b/projects/miragesdk/init/Dockerfile new file mode 100644 index 000000000..ee1ebe3cd --- /dev/null +++ b/projects/miragesdk/init/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:3.5 + +RUN \ + apk --no-cache update && \ + apk --no-cache upgrade -a && \ + apk --no-cache add \ + dhcpcd \ + && rm -rf /var/cache/apk/* + +COPY . ./ diff --git a/projects/miragesdk/init/Makefile b/projects/miragesdk/init/Makefile new file mode 100644 index 000000000..cc0353dca --- /dev/null +++ b/projects/miragesdk/init/Makefile @@ -0,0 +1,52 @@ +CONTAINERD_IMAGE=mobylinux/containerd:a688df6aee1e3700eb8d54dbc81070361df397a2@sha256:59ee3da05fe4dad4fbecff582c86fc30ce75e19a225eeeb07e203c9cc36fe34f +CONTAINERD_BINARIES=usr/bin/containerd usr/bin/containerd-shim usr/bin/ctr usr/bin/dist + +RUNC_IMAGE=mobylinux/runc:94c54debf9a3ebb6d31311bdddb881ea07486dcd@sha256:13cabc1017c6448498e74bae9892ebc9dbad9e5d68f7df6b3855a15522e3a86b +RUNC_BINARY=usr/bin/runc + +C_COMPILE=mobylinux/c-compile:81a6bd8ff45d769b60a2ee1acdaccda11ab835c8@sha256:eac250997a3b9784d3285a03c0c8311d4ca6fb63dc75164c987411ba93006487 +START_STOP_DAEMON=sbin/start-stop-daemon + +default: push + +$(RUNC_BINARY): + mkdir -p $(dir $@) + docker run --rm --net=none $(RUNC_IMAGE) tar cf - $@ | tar xf - + +$(CONTAINERD_BINARIES): + mkdir -p $(dir $@) + docker run --rm --net=none $(CONTAINERD_IMAGE) tar cf - $@ | tar xf - + +$(START_STOP_DAEMON): start-stop-daemon.c + mkdir -p $(dir $@) + tar cf - $^ | docker run --rm --net=none --log-driver=none -i $(C_COMPILE) -o $@ | tar xf - + +.PHONY: tag push + +BASE=alpine:3.5 +IMAGE=init + +ETC=$(shell find etc -type f) + +hash: Dockerfile $(ETC) init $(RUNC_BINARY) $(CONTAINERD_BINARIES) $(START_STOP_DAEMON) + DOCKER_CONTENT_TRUST=1 docker pull $(BASE) + tar cf - $^ | docker build --no-cache -t $(IMAGE):build - + docker run --rm $(IMAGE):build sh -c 'cat $^ /lib/apk/db/installed | sha1sum' | sed 's/ .*//' > $@ + +push: hash + docker pull mobylinux/$(IMAGE):$(shell cat hash) || \ + (docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) && \ + docker push mobylinux/$(IMAGE):$(shell cat hash)) + docker rmi $(IMAGE):build + rm -f hash + +tag: hash + docker pull mobylinux/$(IMAGE):$(shell cat hash) || \ + docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) + docker rmi $(IMAGE):build + rm -f hash + +clean: + rm -rf hash sbin usr + +.DELETE_ON_ERROR: diff --git a/projects/miragesdk/init/etc/init.d/containerd b/projects/miragesdk/init/etc/init.d/containerd new file mode 100755 index 000000000..3580608dd --- /dev/null +++ b/projects/miragesdk/init/etc/init.d/containerd @@ -0,0 +1,9 @@ +#!/bin/sh + +# bring up containerd +ulimit -n 1048576 +ulimit -p unlimited + +printf "\nStarting containerd\n" +mkdir -p /var/log +/sbin/start-stop-daemon --start --exec /usr/bin/containerd diff --git a/projects/miragesdk/init/etc/init.d/containers b/projects/miragesdk/init/etc/init.d/containers new file mode 100755 index 000000000..fc0afa4d4 --- /dev/null +++ b/projects/miragesdk/init/etc/init.d/containers @@ -0,0 +1,31 @@ +#!/bin/sh + +# TODO more robust +# while [ ! -S /run/containerd/containerd.sock ]; do sleep 1; done +# while ! ctr list 2> /dev/null; do sleep 1; done + +# start system containers +# temporarily using runc not containerd + +if [ -d /containers/system ] +then + for f in $(find /containers/system -mindepth 1 -maxdepth 1 | sort) + do + base="$(basename $f)" + /usr/bin/runc run --bundle "$f" "$(basename $f)" + printf " - $base\n" + done +fi + +if [ -d /containers/daemon ] +then + for f in $(find /containers/daemon -mindepth 1 -maxdepth 1 | sort) + do + base="$(basename $f)" + log="/var/log/$base.log" + /sbin/start-stop-daemon --start --pidfile /run/$base.pid --exec /usr/bin/runc -- run --bundle "$f" --pid-file /run/$base.pid "$(basename $f)" $log >$log & + printf " - $base\n" + done +fi + +wait diff --git a/projects/miragesdk/init/etc/init.d/rcS b/projects/miragesdk/init/etc/init.d/rcS new file mode 100755 index 000000000..fa27c3934 --- /dev/null +++ b/projects/miragesdk/init/etc/init.d/rcS @@ -0,0 +1,106 @@ +#!/bin/sh + +# mount filesystems +mkdir -p -m 0755 /proc /run /tmp /sys /dev + +mount -n -t proc proc /proc -o ndodev,nosuid,noexec,relatime + +mount -n -t tmpfs tmpfs /run -o nodev,nosuid,noexec,relatime,size=10%,mode=755 +mount -n -t tmpfs tmpfs /tmp -o nodev,nosuid,noexec,relatime,size=10%,mode=1777 + +# mount devfs +mount -n -t devtmpfs dev /dev -o nosuid,noexec,relatime,size=10m,nr_inodes=248418,mode=755 +# devices +[ -c /dev/console ] || mknod -m 600 /dev/console c 5 1 +[ -c /dev/tty1 ] || mknod -m 620 /dev/tty1 c 4 1 +[ -c /dev/tty ] || mknod -m 666 /dev/tty c 5 0 + +[ -c /dev/null ] || mknod -m 666 /dev/null c 1 3 +[ -c /dev/kmsg ] || mknod -m 660 /dev/kmsg c 1 11 + +# extra symbolic links not provided by default +[ -e /dev/fd ] || ln -snf /proc/self/fd /dev/fd +[ -e /dev/stdin ] || ln -snf /proc/self/fd/0 /dev/stdin +[ -e /dev/stdout ] || ln -snf /proc/self/fd/1 /dev/stdout +[ -e /dev/stderr ] || ln -snf /proc/self/fd/2 /dev/stderr +[ -e /proc/kcore ] && ln -snf /proc/kcore /dev/core + +# devfs filesystems +mkdir -p -m 1777 /dev/mqueue +mkdir -p -m 1777 /dev/shm +mkdir -p -m 0755 /dev/pts +mount -n -t mqueue -o noexec,nosuid,nodev mqueue /dev/mqueue +mount -n -t tmpfs -o noexec,nosuid,nodev,mode=1777 shm /dev/shm +mount -n -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts + +# mount sysfs +sysfs_opts=nodev,noexec,nosuid +mount -n -t sysfs -o ${sysfs_opts} sysfs /sys +[ -d /sys/kernel/security ] && mount -n -t securityfs -o ${sysfs_opts} securityfs /sys/kernel/security +[ -d /sys/kernel/debug ] && mount -n -t debugfs -o ${sysfs_opts} debugfs /sys/kernel/debug +[ -d /sys/kernel/config ] && mount -n -t configfs -o ${sysfs_opts} configfs /sys/kernel/config +[ -d /sys/fs/fuse/connections ] && mount -n -t fusectl -o ${sysfs_opts} fusectl /sys/fs/fuse/connections +[ -d /sys/fs/selinux ] && mount -n -t selinuxfs -o nosuid,noexec selinuxfs /sys/fs/selinux +[ -d /sys/fs/pstore ] && mount -n -t pstore pstore -o ${sysfs_opts} /sys/fs/pstore +[ -d /sys/firmware/efi/efivars ] && mount -n -t efivarfs -o ro,${sysfs_opts} efivarfs /sys/firmware/efi/efivars + +# misc /proc mounted fs +[ -d /proc/sys/fs/binfmt_misc ] && mount -t binfmt_misc -o nodev,noexec,nosuid binfmt_misc /proc/sys/fs/binfmt_misc + +# mount cgroups +mount -n -t tmpfs -o nodev,noexec,nosuid,mode=755,size=10m cgroup_root /sys/fs/cgroup + +while read name hier groups enabled rest +do + case "${enabled}" in + 1) mkdir -p /sys/fs/cgroup/${name} + mount -n -t cgroup -o ${sysfs_opts},${name} ${name} /sys/fs/cgroup/${name} + ;; + esac +done < /proc/cgroups + +# for compatibility +mkdir -p /sys/fs/cgroup/systemd +mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd + +# set SELinux contexts +if [ -x /sbin/restorecon ] +then + restorecon -F /sys/devices/system/cpu/online >/dev/null 2>&1 + restorecon -rF /sys/fs/cgroup >/dev/null 2>&1 + restorecon -rF /dev >/dev/null 2>&1 +fi + +# start mdev for hotplug +echo "/sbin/mdev" > /proc/sys/kernel/hotplug + +# mdev -s will not create /dev/usb[1-9] devices with recent kernels +# so we trigger hotplug events for usb for now +for i in $(find /sys/devices -name 'usb[0-9]*'); do + [ -e $i/uevent ] && echo add > $i/uevent +done + +mdev -s + +# set hostname +if [ -s /etc/hostname ] +then + hostname -F /etc/hostname +fi + +if [ $(hostname) = "moby" -a -f /sys/class/net/eth0/address ] +then + mac=$(cat /sys/class/net/eth0/address) + hostname moby-$(echo $mac | sed 's/://g') +fi + +# set system clock from hwclock +hwclock --hctosys --utc + +# bring up loopback interface +ip addr add 127.0.0.1/8 dev lo brd + scope host +ip route add 127.0.0.0/8 dev lo scope host +ip link set lo up + +# will be containerised +/sbin/dhcpcd diff --git a/projects/miragesdk/init/etc/inittab b/projects/miragesdk/init/etc/inittab new file mode 100644 index 000000000..8ef3e8565 --- /dev/null +++ b/projects/miragesdk/init/etc/inittab @@ -0,0 +1,15 @@ +# /etc/inittab + +::sysinit:/etc/init.d/rcS +::once:/etc/init.d/containerd +::once:/etc/init.d/containers + +# Stuff to do for the 3-finger salute +::ctrlaltdel:/sbin/reboot + +# Stuff to do before rebooting +::shutdown:/usr/sbin/killall5 -15 +::shutdown:/bin/sleep 5 +::shutdown:/usr/sbin/killall5 -9 +::shutdown:/bin/echo "Unmounting filesystems" +::shutdown:/bin/umount -a -r diff --git a/projects/miragesdk/init/etc/issue b/projects/miragesdk/init/etc/issue new file mode 100644 index 000000000..f5a95ea49 --- /dev/null +++ b/projects/miragesdk/init/etc/issue @@ -0,0 +1,12 @@ + +Welcome to Moby + + ## . + ## ## ## == + ## ## ## ## ## === + /"""""""""""""""""\___/ === + ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~ + \______ o __/ + \ \ __/ + \____\_______/ + diff --git a/projects/miragesdk/init/init b/projects/miragesdk/init/init new file mode 100755 index 000000000..46c52bceb --- /dev/null +++ b/projects/miragesdk/init/init @@ -0,0 +1,44 @@ +#!/bin/sh + +setup_console() { + tty=${1%,*} + speed=${1#*,} + inittab="$2" + securetty="$3" + line= + term="linux" + [ "$speed" = "$1" ] && speed=115200 + + case "$tty" in + ttyS*|ttyAMA*|ttyUSB*|ttyMFD*) + line="-L" + term="vt100" + ;; + tty0) + # skip current console + return 0 + ;; + esac + # skip consoles already in inittab + grep -q "^$tty:" "$inittab" && return + + echo "$tty::once:cat /etc/issue" >> "$inittab" + echo "$tty::respawn:/sbin/getty -n -l /bin/sh $line $speed $tty $term" >> "$inittab" + if ! grep -q -w "$tty" "$securetty"; then + echo "$tty" >> "$securetty" + fi +} + +/bin/mount -t tmpfs tmpfs /mnt + +/bin/cp -a / /mnt 2>/dev/null + +/bin/mount -t proc -o noexec,nosuid,nodev proc /proc +for opt in $(cat /proc/cmdline); do + case "$opt" in + console=*) + setup_console ${opt#console=} /mnt/etc/inittab /mnt/etc/securetty;; + esac +done + +exec /bin/busybox switch_root /mnt /sbin/init diff --git a/projects/miragesdk/init/start-stop-daemon.c b/projects/miragesdk/init/start-stop-daemon.c new file mode 100644 index 000000000..f27406746 --- /dev/null +++ b/projects/miragesdk/init/start-stop-daemon.c @@ -0,0 +1,1054 @@ +/* + * A rewrite of the original Debian's start-stop-daemon Perl script + * in C (faster - it is executed many times during system startup). + * + * Written by Marek Michalkiewicz , + * public domain. Based conceptually on start-stop-daemon.pl, by Ian + * Jackson . May be used and distributed + * freely for any purpose. Changes by Christian Schwarz + * , to make output conform to the Debian + * Console Message Standard, also placed in public domain. Minor + * changes by Klee Dienes , also placed in the Public + * Domain. + * + * Changes by Ben Collins , added --chuid, --background + * and --make-pidfile options, placed in public domain aswell. + * + * Port to OpenBSD by Sontri Tomo Huynh + * and Andreas Schuldei + * + * Changes by Ian Jackson: added --retry (and associated rearrangements). + * + * Modified for Gentoo rc-scripts by Donny Davies : + * I removed the BSD/Hurd/OtherOS stuff, added #include + * and stuck in a #define VERSION "1.9.18". Now it compiles without + * the whole automake/config.h dance. + * + * Modified to compile on Alpine by Justin Cormack + */ + +#include +#define VERSION "1.9.18" + +#define MIN_POLL_INTERVAL 20000 /*us*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int testmode = 0; +static int quietmode = 0; +static int exitnodo = 1; +static int start = 0; +static int stop = 0; +static int background = 0; +static int mpidfile = 0; +static int signal_nr = 15; +static const char *signal_str = NULL; +static int user_id = -1; +static int runas_uid = -1; +static int runas_gid = -1; +static const char *userspec = NULL; +static char *changeuser = NULL; +static const char *changegroup = NULL; +static char *changeroot = NULL; +static const char *cmdname = NULL; +static char *execname = NULL; +static char *startas = NULL; +static const char *pidfile = NULL; +static char what_stop[1024]; +static const char *schedule_str = NULL; +static const char *progname = ""; +static int nicelevel = 0; + +static struct stat exec_stat; + +struct pid_list { + struct pid_list *next; + pid_t pid; +}; + +static struct pid_list *found = NULL; +static struct pid_list *killed = NULL; + +struct schedule_item { + enum { sched_timeout, sched_signal, sched_goto, sched_forever } type; + int value; /* seconds, signal no., or index into array */ + /* sched_forever is only seen within parse_schedule and callees */ +}; + +static int schedule_length; +static struct schedule_item *schedule = NULL; + +LIST_HEAD(namespace_head, namespace); + +struct namespace { + LIST_ENTRY(namespace) list; + char *path; + int nstype; +}; + +static struct namespace_head namespace_head; + +static void *xmalloc(int size); +static void push(struct pid_list **list, pid_t pid); +static void do_help(void); +static void parse_options(int argc, char * const *argv); +static int pid_is_user(pid_t pid, uid_t uid); +static int pid_is_cmd(pid_t pid, const char *name); +static void check(pid_t pid); +static void do_pidfile(const char *name); +static void do_stop(int signal_nr, int quietmode, + int *n_killed, int *n_notkilled, int retry_nr); +static int pid_is_exec(pid_t pid, const struct stat *esb); + +#ifdef __GNUC__ +static void fatal(const char *format, ...) + __attribute__((noreturn, format(printf, 1, 2))); +static void badusage(const char *msg) + __attribute__((noreturn)); +#else +static void fatal(const char *format, ...); +static void badusage(const char *msg); +#endif + +/* This next part serves only to construct the TVCALC macro, which + * is used for doing arithmetic on struct timeval's. It works like this: + * TVCALC(result, expression); + * where result is a struct timeval (and must be an lvalue) and + * expression is the single expression for both components. In this + * expression you can use the special values TVELEM, which when fed a + * const struct timeval* gives you the relevant component, and + * TVADJUST. TVADJUST is necessary when subtracting timevals, to make + * it easier to renormalise. Whenver you subtract timeval elements, + * you must make sure that TVADJUST is added to the result of the + * subtraction (before any resulting multiplication or what have you). + * TVELEM must be linear in TVADJUST. + */ +typedef long tvselector(const struct timeval*); +static long tvselector_sec(const struct timeval *tv) { return tv->tv_sec; } +static long tvselector_usec(const struct timeval *tv) { return tv->tv_usec; } +#define TVCALC_ELEM(result, expr, sec, adj) \ +{ \ + const long TVADJUST = adj; \ + long (*const TVELEM)(const struct timeval*) = tvselector_##sec; \ + (result).tv_##sec = (expr); \ +} +#define TVCALC(result,expr) \ +do { \ + TVCALC_ELEM(result, expr, sec, (-1)); \ + TVCALC_ELEM(result, expr, usec, (+1000000)); \ + (result).tv_sec += (result).tv_usec / 1000000; \ + (result).tv_usec %= 1000000; \ +} while(0) + + +static void +fatal(const char *format, ...) +{ + va_list arglist; + + fprintf(stderr, "%s: ", progname); + va_start(arglist, format); + vfprintf(stderr, format, arglist); + va_end(arglist); + putc('\n', stderr); + exit(2); +} + + +static void * +xmalloc(int size) +{ + void *ptr; + + ptr = malloc(size); + if (ptr) + return ptr; + fatal("malloc(%d) failed", size); +} + +static void +xgettimeofday(struct timeval *tv) +{ + if (gettimeofday(tv,0) != 0) + fatal("gettimeofday failed: %s", strerror(errno)); +} + +static void +push(struct pid_list **list, pid_t pid) +{ + struct pid_list *p; + + p = xmalloc(sizeof(*p)); + p->next = *list; + p->pid = pid; + *list = p; +} + +static void +clear(struct pid_list **list) +{ + struct pid_list *here, *next; + + for (here = *list; here != NULL; here = next) { + next = here->next; + free(here); + } + + *list = NULL; +} + +static char * +next_dirname(const char *s) +{ + char *cur; + + cur = (char *)s; + + if (*cur != '\0') { + for (; *cur != '/'; ++cur) + if (*cur == '\0') + return cur; + + for (; *cur == '/'; ++cur) + ; + } + + return cur; +} + +static void +add_namespace(const char *path) +{ + int nstype; + char *nsdirname, *nsname, *cur; + struct namespace *namespace; + + cur = (char *)path; + nsdirname = nsname = ""; + + while ((cur = next_dirname(cur))[0] != '\0') { + nsdirname = nsname; + nsname = cur; + } + + if (!memcmp(nsdirname, "ipcns/", strlen("ipcns/"))) + nstype = CLONE_NEWIPC; + else if (!memcmp(nsdirname, "netns/", strlen("netns/"))) + nstype = CLONE_NEWNET; + else if (!memcmp(nsdirname, "utcns/", strlen("utcns/"))) + nstype = CLONE_NEWUTS; + else + badusage("invalid namepspace path"); + + namespace = xmalloc(sizeof(*namespace)); + namespace->path = (char *)path; + namespace->nstype = nstype; + LIST_INSERT_HEAD(&namespace_head, namespace, list); +} + +#ifdef HAVE_LXC +static void +set_namespaces() +{ + struct namespace *namespace; + int fd; + + LIST_FOREACH(namespace, &namespace_head, list) { + if ((fd = open(namespace->path, O_RDONLY)) == -1) + fatal("open namespace %s: %s", namespace->path, strerror(errno)); + if (setns(fd, namespace->nstype) == -1) + fatal("setns %s: %s", namespace->path, strerror(errno)); + } +} +#else +static void +set_namespaces() +{ + if (!LIST_EMPTY(&namespace_head)) + fatal("LCX namespaces not supported"); +} +#endif + +static void +do_help(void) +{ + printf( +"start-stop-daemon " VERSION " for Debian - small and fast C version written by\n" +"Marek Michalkiewicz , public domain.\n" +"\n" +"Usage:\n" +" start-stop-daemon -S|--start options ... -- arguments ...\n" +" start-stop-daemon -K|--stop options ...\n" +" start-stop-daemon -H|--help\n" +" start-stop-daemon -V|--version\n" +"\n" +"Options (at least one of --exec|--pidfile|--user is required):\n" +" -x|--exec program to start/check if it is running\n" +" -p|--pidfile pid file to check\n" +" -c|--chuid \n" +" change to this user/group before starting process\n" +" -u|--user | stop processes owned by this user\n" +" -n|--name stop processes with this name\n" +" -s|--signal signal to send (default TERM)\n" +" -a|--startas program to start (default is )\n" +" -N|--nicelevel add incr to the process's nice level\n" +" -b|--background force the process to detach\n" +" -m|--make-pidfile create the pidfile before starting\n" +" -R|--retry check whether processes die, and retry\n" +" -t|--test test mode, don't do anything\n" +" -o|--oknodo exit status 0 (not 1) if nothing done\n" +" -q|--quiet be more quiet\n" +" -v|--verbose be more verbose\n" +"Retry is |//... where is one of\n" +" -|[-] send that signal\n" +" wait that many seconds\n" +" forever repeat remainder forever\n" +"or may be just , meaning //KILL/\n" +"\n" +"Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n" +" 3 = trouble 2 = with --retry, processes wouldn't die\n"); +} + + +static void +badusage(const char *msg) +{ + if (msg) + fprintf(stderr, "%s: %s\n", progname, msg); + fprintf(stderr, "Try `%s --help' for more information.\n", progname); + exit(3); +} + +struct sigpair { + const char *name; + int signal; +}; + +const struct sigpair siglist[] = { + { "ABRT", SIGABRT }, + { "ALRM", SIGALRM }, + { "FPE", SIGFPE }, + { "HUP", SIGHUP }, + { "ILL", SIGILL }, + { "INT", SIGINT }, + { "KILL", SIGKILL }, + { "PIPE", SIGPIPE }, + { "QUIT", SIGQUIT }, + { "SEGV", SIGSEGV }, + { "TERM", SIGTERM }, + { "USR1", SIGUSR1 }, + { "USR2", SIGUSR2 }, + { "CHLD", SIGCHLD }, + { "CONT", SIGCONT }, + { "STOP", SIGSTOP }, + { "TSTP", SIGTSTP }, + { "TTIN", SIGTTIN }, + { "TTOU", SIGTTOU } +}; + +static int parse_integer (const char *string, int *value_r) { + unsigned long ul; + char *ep; + + if (!string[0]) + return -1; + + ul= strtoul(string,&ep,10); + if (ul > INT_MAX || *ep != '\0') + return -1; + + *value_r= ul; + return 0; +} + +static int parse_signal (const char *signal_str, int *signal_nr) +{ + unsigned int i; + + if (parse_integer(signal_str, signal_nr) == 0) + return 0; + + for (i = 0; i < sizeof (siglist) / sizeof (siglist[0]); i++) { + if (strcmp (signal_str, siglist[i].name) == 0) { + *signal_nr = siglist[i].signal; + return 0; + } + } + return -1; +} + +static void +parse_schedule_item(const char *string, struct schedule_item *item) { + const char *after_hyph; + + if (!strcmp(string,"forever")) { + item->type = sched_forever; + } else if (isdigit(string[0])) { + item->type = sched_timeout; + if (parse_integer(string, &item->value) != 0) + badusage("invalid timeout value in schedule"); + } else if ((after_hyph = string + (string[0] == '-')) && + parse_signal(after_hyph, &item->value) == 0) { + item->type = sched_signal; + } else { + badusage("invalid schedule item (must be [-], " + "-, or `forever'"); + } +} + +static void +parse_schedule(const char *schedule_str) { + char item_buf[20]; + const char *slash; + int count, repeatat; + ptrdiff_t str_len; + + count = 0; + for (slash = schedule_str; *slash; slash++) + if (*slash == '/') + count++; + + schedule_length = (count == 0) ? 4 : count+1; + schedule = xmalloc(sizeof(*schedule) * schedule_length); + + if (count == 0) { + schedule[0].type = sched_signal; + schedule[0].value = signal_nr; + parse_schedule_item(schedule_str, &schedule[1]); + if (schedule[1].type != sched_timeout) { + badusage ("--retry takes timeout, or schedule list" + " of at least two items"); + } + schedule[2].type = sched_signal; + schedule[2].value = SIGKILL; + schedule[3]= schedule[1]; + } else { + count = 0; + repeatat = -1; + while (schedule_str != NULL) { + slash = strchr(schedule_str,'/'); + str_len = slash ? slash - schedule_str : strlen(schedule_str); + if (str_len >= (ptrdiff_t)sizeof(item_buf)) + badusage("invalid schedule item: far too long" + " (you must delimit items with slashes)"); + memcpy(item_buf, schedule_str, str_len); + item_buf[str_len] = 0; + schedule_str = slash ? slash+1 : NULL; + + parse_schedule_item(item_buf, &schedule[count]); + if (schedule[count].type == sched_forever) { + if (repeatat >= 0) + badusage("invalid schedule: `forever'" + " appears more than once"); + repeatat = count; + continue; + } + count++; + } + if (repeatat >= 0) { + schedule[count].type = sched_goto; + schedule[count].value = repeatat; + count++; + } + assert(count == schedule_length); + } +} + +static void +parse_options(int argc, char * const *argv) +{ + static struct option longopts[] = { + { "help", 0, NULL, 'H'}, + { "stop", 0, NULL, 'K'}, + { "start", 0, NULL, 'S'}, + { "version", 0, NULL, 'V'}, + { "startas", 1, NULL, 'a'}, + { "name", 1, NULL, 'n'}, + { "oknodo", 0, NULL, 'o'}, + { "pidfile", 1, NULL, 'p'}, + { "quiet", 0, NULL, 'q'}, + { "signal", 1, NULL, 's'}, + { "test", 0, NULL, 't'}, + { "user", 1, NULL, 'u'}, + { "chroot", 1, NULL, 'r'}, + { "namespace", 1, NULL, 'd'}, + { "verbose", 0, NULL, 'v'}, + { "exec", 1, NULL, 'x'}, + { "chuid", 1, NULL, 'c'}, + { "nicelevel", 1, NULL, 'N'}, + { "background", 0, NULL, 'b'}, + { "make-pidfile", 0, NULL, 'm'}, + { "retry", 1, NULL, 'R'}, + { NULL, 0, NULL, 0} + }; + int c; + + for (;;) { + c = getopt_long(argc, argv, "HKSVa:n:op:qr:d:s:tu:vx:c:N:bmR:", + longopts, (int *) 0); + if (c == -1) + break; + switch (c) { + case 'H': /* --help */ + do_help(); + exit(0); + case 'K': /* --stop */ + stop = 1; + break; + case 'S': /* --start */ + start = 1; + break; + case 'V': /* --version */ + printf("start-stop-daemon " VERSION "\n"); + exit(0); + case 'a': /* --startas */ + startas = optarg; + break; + case 'n': /* --name */ + cmdname = optarg; + break; + case 'o': /* --oknodo */ + exitnodo = 0; + break; + case 'p': /* --pidfile */ + pidfile = optarg; + break; + case 'q': /* --quiet */ + quietmode = 1; + break; + case 's': /* --signal */ + signal_str = optarg; + break; + case 't': /* --test */ + testmode = 1; + break; + case 'u': /* --user | */ + userspec = optarg; + break; + case 'v': /* --verbose */ + quietmode = -1; + break; + case 'x': /* --exec */ + execname = optarg; + break; + case 'c': /* --chuid | */ + /* we copy the string just in case we need the + * argument later. */ + changeuser = strdup(optarg); + changeuser = strtok(changeuser, ":"); + changegroup = strtok(NULL, ":"); + break; + case 'r': /* --chroot /new/root */ + changeroot = optarg; + break; + case 'd': /* --namespace /.../||/name */ + add_namespace(optarg); + break; + case 'N': /* --nice */ + nicelevel = atoi(optarg); + break; + case 'b': /* --background */ + background = 1; + break; + case 'm': /* --make-pidfile */ + mpidfile = 1; + break; + case 'R': /* --retry | */ + schedule_str = optarg; + break; + default: + badusage(NULL); /* message printed by getopt */ + } + } + + if (signal_str != NULL) { + if (parse_signal (signal_str, &signal_nr) != 0) + badusage("signal value must be numeric or name" + " of signal (KILL, INTR, ...)"); + } + + if (schedule_str != NULL) { + parse_schedule(schedule_str); + } + + if (start == stop) + badusage("need one of --start or --stop"); + + if (!execname && !pidfile && !userspec && !cmdname) + badusage("need at least one of --exec, --pidfile, --user or --name"); + + if (!startas) + startas = execname; + + if (start && !startas) + badusage("--start needs --exec or --startas"); + + if (mpidfile && pidfile == NULL) + badusage("--make-pidfile is only relevant with --pidfile"); + + if (background && !start) + badusage("--background is only relevant with --start"); + +} + +static int +pid_is_exec(pid_t pid, const struct stat *esb) +{ + struct stat sb; + char buf[32]; + + sprintf(buf, "/proc/%d/exe", pid); + if (stat(buf, &sb) != 0) + return 0; + return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); +} + + +static int +pid_is_user(pid_t pid, uid_t uid) +{ + struct stat sb; + char buf[32]; + + sprintf(buf, "/proc/%d", pid); + if (stat(buf, &sb) != 0) + return 0; + return (sb.st_uid == uid); +} + + +static int +pid_is_cmd(pid_t pid, const char *name) +{ + char buf[32]; + FILE *f; + int c; + + sprintf(buf, "/proc/%d/stat", pid); + f = fopen(buf, "r"); + if (!f) + return 0; + while ((c = getc(f)) != EOF && c != '(') + ; + if (c != '(') { + fclose(f); + return 0; + } + /* this hopefully handles command names containing ')' */ + while ((c = getc(f)) != EOF && c == *name) + name++; + fclose(f); + return (c == ')' && *name == '\0'); +} + + +static void +check(pid_t pid) +{ + if (execname && !pid_is_exec(pid, &exec_stat)) + return; + if (userspec && !pid_is_user(pid, user_id)) + return; + if (cmdname && !pid_is_cmd(pid, cmdname)) + return; + push(&found, pid); +} + +static void +do_pidfile(const char *name) +{ + FILE *f; + pid_t pid; + + f = fopen(name, "r"); + if (f) { + if (fscanf(f, "%d", &pid) == 1) + check(pid); + fclose(f); + } else if (errno != ENOENT) + fatal("open pidfile %s: %s", name, strerror(errno)); + +} + +/* WTA: this needs to be an autoconf check for /proc/pid existance. + */ +static void +do_procinit(void) +{ + DIR *procdir; + struct dirent *entry; + int foundany; + pid_t pid; + + procdir = opendir("/proc"); + if (!procdir) + fatal("opendir /proc: %s", strerror(errno)); + + foundany = 0; + while ((entry = readdir(procdir)) != NULL) { + if (sscanf(entry->d_name, "%d", &pid) != 1) + continue; + foundany++; + check(pid); + } + closedir(procdir); + if (!foundany) + fatal("nothing in /proc - not mounted?"); +} + +static void +do_findprocs(void) +{ + clear(&found); + + if (pidfile) + do_pidfile(pidfile); + else + do_procinit(); +} + +/* return 1 on failure */ +static void +do_stop(int signal_nr, int quietmode, int *n_killed, int *n_notkilled, int retry_nr) +{ + struct pid_list *p; + + do_findprocs(); + + *n_killed = 0; + *n_notkilled = 0; + + if (!found) + return; + + clear(&killed); + + for (p = found; p; p = p->next) { + if (testmode) + printf("Would send signal %d to %d.\n", + signal_nr, p->pid); + else if (kill(p->pid, signal_nr) == 0) { + push(&killed, p->pid); + (*n_killed)++; + } else { + printf("%s: warning: failed to kill %d: %s\n", + progname, p->pid, strerror(errno)); + (*n_notkilled)++; + } + } + if (quietmode < 0 && killed) { + printf("Stopped %s (pid", what_stop); + for (p = killed; p; p = p->next) + printf(" %d", p->pid); + putchar(')'); + if (retry_nr > 0) + printf(", retry #%d", retry_nr); + printf(".\n"); + } +} + + +static void +set_what_stop(const char *str) +{ + strncpy(what_stop, str, sizeof(what_stop)); + what_stop[sizeof(what_stop)-1] = '\0'; +} + +static int +run_stop_schedule(void) +{ + int r, position, n_killed, n_notkilled, value, ratio, anykilled, retry_nr; + struct timeval stopat, before, after, interval, maxinterval; + + if (testmode) { + if (schedule != NULL) { + printf("Ignoring --retry in test mode\n"); + schedule = NULL; + } + } + + if (cmdname) + set_what_stop(cmdname); + else if (execname) + set_what_stop(execname); + else if (pidfile) + sprintf(what_stop, "process in pidfile `%.200s'", pidfile); + else if (userspec) + sprintf(what_stop, "process(es) owned by `%.200s'", userspec); + else + fatal("internal error, please report"); + + anykilled = 0; + retry_nr = 0; + + if (schedule == NULL) { + do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0); + if (n_notkilled > 0 && quietmode <= 0) + printf("%d pids were not killed\n", n_notkilled); + if (n_killed) + anykilled = 1; + goto x_finished; + } + + for (position = 0; position < schedule_length; ) { + value= schedule[position].value; + n_notkilled = 0; + + switch (schedule[position].type) { + + case sched_goto: + position = value; + continue; + + case sched_signal: + do_stop(value, quietmode, &n_killed, &n_notkilled, retry_nr++); + if (!n_killed) + goto x_finished; + else + anykilled = 1; + goto next_item; + + case sched_timeout: + /* We want to keep polling for the processes, to see if they've exited, + * or until the timeout expires. + * + * This is a somewhat complicated algorithm to try to ensure that we + * notice reasonably quickly when all the processes have exited, but + * don't spend too much CPU time polling. In particular, on a fast + * machine with quick-exiting daemons we don't want to delay system + * shutdown too much, whereas on a slow one, or where processes are + * taking some time to exit, we want to increase the polling + * interval. + * + * The algorithm is as follows: we measure the elapsed time it takes + * to do one poll(), and wait a multiple of this time for the next + * poll. However, if that would put us past the end of the timeout + * period we wait only as long as the timeout period, but in any case + * we always wait at least MIN_POLL_INTERVAL (20ms). The multiple + * (`ratio') starts out as 2, and increases by 1 for each poll to a + * maximum of 10; so we use up to between 30% and 10% of the + * machine's resources (assuming a few reasonable things about system + * performance). + */ + xgettimeofday(&stopat); + stopat.tv_sec += value; + ratio = 1; + for (;;) { + xgettimeofday(&before); + if (timercmp(&before,&stopat,>)) + goto next_item; + + do_stop(0, 1, &n_killed, &n_notkilled, 0); + if (!n_killed) + goto x_finished; + + xgettimeofday(&after); + + if (!timercmp(&after,&stopat,<)) + goto next_item; + + if (ratio < 10) + ratio++; + + TVCALC(interval, ratio * (TVELEM(&after) - TVELEM(&before) + TVADJUST)); + TVCALC(maxinterval, TVELEM(&stopat) - TVELEM(&after) + TVADJUST); + + if (timercmp(&interval,&maxinterval,>)) + interval = maxinterval; + + if (interval.tv_sec == 0 && + interval.tv_usec <= MIN_POLL_INTERVAL) + interval.tv_usec = MIN_POLL_INTERVAL; + + r = select(0,0,0,0,&interval); + if (r < 0 && errno != EINTR) + fatal("select() failed for pause: %s", + strerror(errno)); + } + + default: + assert(!"schedule[].type value must be valid"); + + } + + next_item: + position++; + } + + if (quietmode <= 0) + printf("Program %s, %d process(es), refused to die.\n", + what_stop, n_killed); + + return 2; + +x_finished: + if (!anykilled) { + if (quietmode <= 0) + printf("No %s found running; none killed.\n", what_stop); + return exitnodo; + } else { + return 0; + } +} + +/* +int main(int argc, char **argv) NONRETURNING; +*/ + +int +main(int argc, char **argv) +{ + progname = argv[0]; + + LIST_INIT(&namespace_head); + + parse_options(argc, argv); + argc -= optind; + argv += optind; + + if (execname && stat(execname, &exec_stat)) + fatal("stat %s: %s", execname, strerror(errno)); + + if (userspec && sscanf(userspec, "%d", &user_id) != 1) { + struct passwd *pw; + + pw = getpwnam(userspec); + if (!pw) + fatal("user `%s' not found\n", userspec); + + user_id = pw->pw_uid; + } + + if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) { + struct group *gr = getgrnam(changegroup); + if (!gr) + fatal("group `%s' not found\n", changegroup); + runas_gid = gr->gr_gid; + } + if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) { + struct passwd *pw = getpwnam(changeuser); + if (!pw) + fatal("user `%s' not found\n", changeuser); + runas_uid = pw->pw_uid; + if (changegroup == NULL) { /* pass the default group of this user */ + changegroup = ""; /* just empty */ + runas_gid = pw->pw_gid; + } + } + + if (stop) { + int i = run_stop_schedule(); + exit(i); + } + + do_findprocs(); + + if (found) { + if (quietmode <= 0) + printf("%s already running.\n", execname); + exit(exitnodo); + } + if (testmode) { + printf("Would start %s ", startas); + while (argc-- > 0) + printf("%s ", *argv++); + if (changeuser != NULL) { + printf(" (as user %s[%d]", changeuser, runas_uid); + if (changegroup != NULL) + printf(", and group %s[%d])", changegroup, runas_gid); + else + printf(")"); + } + if (changeroot != NULL) + printf(" in directory %s", changeroot); + if (nicelevel) + printf(", and add %i to the priority", nicelevel); + printf(".\n"); + exit(0); + } + if (quietmode < 0) + printf("Starting %s...\n", startas); + *--argv = startas; + if (changeroot != NULL) { + if (chdir(changeroot) < 0) + fatal("Unable to chdir() to %s", changeroot); + if (chroot(changeroot) < 0) + fatal("Unable to chroot() to %s", changeroot); + } + if (changeuser != NULL) { + if (setgid(runas_gid)) + fatal("Unable to set gid to %d", runas_gid); + if (initgroups(changeuser, runas_gid)) + fatal("Unable to set initgroups() with gid %d", runas_gid); + if (setuid(runas_uid)) + fatal("Unable to set uid to %s", changeuser); + } + + if (background) { /* ok, we need to detach this process */ + int i, fd; + if (quietmode < 0) + printf("Detatching to start %s...", startas); + i = fork(); + if (i<0) { + fatal("Unable to fork.\n"); + } + if (i) { /* parent */ + if (quietmode < 0) + printf("done.\n"); + exit(0); + } + /* child continues here */ + /* now close all extra fds */ + for (i=getdtablesize()-1; i>=0; --i) close(i); + /* change tty */ + fd = open("/dev/tty", O_RDWR); + ioctl(fd, TIOCNOTTY, 0); + close(fd); + chdir("/"); + umask(022); /* set a default for dumb programs */ + setpgid(0,0); /* set the process group */ + fd=open("/dev/null", O_RDWR); /* stdin */ + dup(fd); /* stdout */ + dup(fd); /* stderr */ + } + if (nicelevel) { + errno = 0; + if (nice(nicelevel) < 0 && errno) + fatal("Unable to alter nice level by %i: %s", nicelevel, + strerror(errno)); + } + if (mpidfile && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */ + FILE *pidf = fopen(pidfile, "w"); + pid_t pidt = getpid(); + if (pidf == NULL) + fatal("Unable to open pidfile `%s' for writing: %s", pidfile, + strerror(errno)); + fprintf(pidf, "%d\n", pidt); + fclose(pidf); + } + set_namespaces(); + execv(startas, argv); + fatal("Unable to start %s: %s", startas, strerror(errno)); +} From 18635ed2e19b7a9c83f858a83487852f56888fb2 Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Mon, 27 Mar 2017 18:04:14 +0200 Subject: [PATCH 2/6] miragesdk: remove dhccpd in the init container Signed-off-by: Thomas Gazagnaire --- projects/miragesdk/init/Dockerfile | 1 - projects/miragesdk/init/etc/init.d/rcS | 3 --- 2 files changed, 4 deletions(-) diff --git a/projects/miragesdk/init/Dockerfile b/projects/miragesdk/init/Dockerfile index ee1ebe3cd..92dea3588 100644 --- a/projects/miragesdk/init/Dockerfile +++ b/projects/miragesdk/init/Dockerfile @@ -4,7 +4,6 @@ RUN \ apk --no-cache update && \ apk --no-cache upgrade -a && \ apk --no-cache add \ - dhcpcd \ && rm -rf /var/cache/apk/* COPY . ./ diff --git a/projects/miragesdk/init/etc/init.d/rcS b/projects/miragesdk/init/etc/init.d/rcS index fa27c3934..27ff786f9 100755 --- a/projects/miragesdk/init/etc/init.d/rcS +++ b/projects/miragesdk/init/etc/init.d/rcS @@ -101,6 +101,3 @@ hwclock --hctosys --utc ip addr add 127.0.0.1/8 dev lo brd + scope host ip route add 127.0.0.0/8 dev lo scope host ip link set lo up - -# will be containerised -/sbin/dhcpcd From b0f758a20deb65b2bd402350785d07ea8de6b93f Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Mon, 27 Mar 2017 18:04:58 +0200 Subject: [PATCH 3/6] miragesdk: add strace in the init image Signed-off-by: Thomas Gazagnaire --- projects/miragesdk/examples/mirage-dhcp.yml | 2 +- projects/miragesdk/init/Dockerfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/miragesdk/examples/mirage-dhcp.yml b/projects/miragesdk/examples/mirage-dhcp.yml index 1b1830323..a321085ef 100644 --- a/projects/miragesdk/examples/mirage-dhcp.yml +++ b/projects/miragesdk/examples/mirage-dhcp.yml @@ -1,7 +1,7 @@ kernel: image: "mobylinux/kernel:4.9.x" cmdline: "console=ttyS0 page_poison=1" -init: "mobylinux/init:d6d115d601e78f7909d4a2ff7eb4caa3fff65271" +init: "mobylinux/init:67913d76e75bebd78b4b2cc3843178c290405547" system: - name: sysctl image: "mobylinux/sysctl:2cf2f9d5b4d314ba1bfc22b2fe931924af666d8c" diff --git a/projects/miragesdk/init/Dockerfile b/projects/miragesdk/init/Dockerfile index 92dea3588..68f6fec25 100644 --- a/projects/miragesdk/init/Dockerfile +++ b/projects/miragesdk/init/Dockerfile @@ -4,6 +4,7 @@ RUN \ apk --no-cache update && \ apk --no-cache upgrade -a && \ apk --no-cache add \ + strace \ && rm -rf /var/cache/apk/* COPY . ./ From 1bee082c6c7514bbd25b366836e261047a8c784f Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Thu, 23 Mar 2017 14:03:06 +0100 Subject: [PATCH 4/6] miragesdk: use a custom BPF filter to allow the calf to get a DHCP lease Plus a few more minor improvements: - compile with jbuilder. - start working on the control path. Signed-off-by: Thomas Gazagnaire --- projects/miragesdk/dhcp-client/.gitignore | 6 +- .../miragesdk/dhcp-client/Dockerfile.build | 8 +- projects/miragesdk/dhcp-client/Dockerfile.dev | 6 +- projects/miragesdk/dhcp-client/Dockerfile.pkg | 2 +- projects/miragesdk/dhcp-client/Makefile | 9 +- projects/miragesdk/dhcp-client/src/.merlin | 74 ++++++++++++++- projects/miragesdk/dhcp-client/src/_tags | 6 -- projects/miragesdk/dhcp-client/src/bpf/dhcp.c | 89 +++++++++++++++++++ projects/miragesdk/dhcp-client/src/bpf/jbuild | 6 ++ projects/miragesdk/dhcp-client/src/jbuild | 10 +++ projects/miragesdk/dhcp-client/src/main.ml | 76 ++++++++++++---- projects/miragesdk/examples/mirage-dhcp.yml | 22 ++--- projects/miragesdk/roadmap.md | 3 +- 13 files changed, 269 insertions(+), 48 deletions(-) delete mode 100644 projects/miragesdk/dhcp-client/src/_tags create mode 100644 projects/miragesdk/dhcp-client/src/bpf/dhcp.c create mode 100644 projects/miragesdk/dhcp-client/src/bpf/jbuild create mode 100644 projects/miragesdk/dhcp-client/src/jbuild diff --git a/projects/miragesdk/dhcp-client/.gitignore b/projects/miragesdk/dhcp-client/.gitignore index 820517ae9..46d095063 100644 --- a/projects/miragesdk/dhcp-client/.gitignore +++ b/projects/miragesdk/dhcp-client/.gitignore @@ -3,9 +3,13 @@ .dev obj/ +hash + # Generated by `make dev` _build/ main.native +calf/dhcp_client +src/bpf/.merlin # Generated by the mirage tool calf/_build @@ -20,4 +24,4 @@ calf/.mirage.config \#* .#* *~ -.*~ \ No newline at end of file +.*~ diff --git a/projects/miragesdk/dhcp-client/Dockerfile.build b/projects/miragesdk/dhcp-client/Dockerfile.build index 2a68a417b..cfd7c56ed 100644 --- a/projects/miragesdk/dhcp-client/Dockerfile.build +++ b/projects/miragesdk/dhcp-client/Dockerfile.build @@ -5,7 +5,7 @@ RUN opam pin -n add conduit https://github.com/samoht/ocaml-conduit.git#fd RUN opam pin -n add mirage-net-unix https://github.com/samoht/mirage-net-unix.git#fd RUN opam depext -iy mirage-net-unix logs-syslog irmin-unix cohttp decompress -RUN opam depext -iy rawlink +RUN opam depext -iy rawlink tuntap.1.0.0 jbuilder irmin-watcher inotify RUN sudo mkdir -p /src /bin COPY ./src /src @@ -14,5 +14,7 @@ RUN sudo chown opam -R /src USER opam WORKDIR /src -RUN opam config exec -- ocamlbuild -use-ocamlfind -lflags -cclib,-static main.native -RUN sudo cp /src/_build/main.native /dhcp-client +RUN opam pin add tuntap 1.0.0 + +RUN opam config exec -- jbuilder build main.exe +RUN sudo cp /src/_build/default/main.exe /dhcp-client diff --git a/projects/miragesdk/dhcp-client/Dockerfile.dev b/projects/miragesdk/dhcp-client/Dockerfile.dev index 98e7d8962..c237b7ad0 100644 --- a/projects/miragesdk/dhcp-client/Dockerfile.dev +++ b/projects/miragesdk/dhcp-client/Dockerfile.dev @@ -5,7 +5,9 @@ RUN opam pin -n add conduit https://github.com/samoht/ocaml-conduit.git#fd RUN opam pin -n add mirage-net-unix https://github.com/samoht/mirage-net-unix.git#fd RUN opam depext -iy mirage-net-unix logs-syslog cohttp decompress -RUN opam depext -iy rawlink +RUN opam depext -iy rawlink tuntap.1.0.0 jbuilder + +RUN opam pin add tuntap 1.0.0 RUN sudo mkdir -p /src /bin @@ -18,4 +20,4 @@ COPY init-dev.sh /home/opam/init-dev.sh USER opam WORKDIR /src -ENTRYPOINT ["/bin/sh", "/home/opam/init-dev.sh"] \ No newline at end of file +ENTRYPOINT ["/bin/sh", "/home/opam/init-dev.sh"] diff --git a/projects/miragesdk/dhcp-client/Dockerfile.pkg b/projects/miragesdk/dhcp-client/Dockerfile.pkg index bfe815767..d1f06492e 100644 --- a/projects/miragesdk/dhcp-client/Dockerfile.pkg +++ b/projects/miragesdk/dhcp-client/Dockerfile.pkg @@ -1,4 +1,4 @@ #FROM ocaml/opam:alpine-3.5_ocaml-4.04.0 FROM scratch COPY obj ./ -CMD ["/dhcp-client"] \ No newline at end of file +CMD ["/dhcp-client"] diff --git a/projects/miragesdk/dhcp-client/Makefile b/projects/miragesdk/dhcp-client/Makefile index e2b6885b2..29bf8bed0 100644 --- a/projects/miragesdk/dhcp-client/Makefile +++ b/projects/miragesdk/dhcp-client/Makefile @@ -1,5 +1,6 @@ BASE=ocaml/opam:alpine-3.5_ocaml-4.04.0 -FILES=src/main.ml src/inflator.ml src/io_fs.ml src/_tags +FILES=src/main.ml src/inflator.ml src/io_fs.ml src/bpf/dhcp.c \ + src/jbuild src/bpf/jbuild IMAGE=dhcp-client OBJS=obj/dhcp-client @@ -65,7 +66,9 @@ clean:: dev: cd calf && mirage configure && make - ocamlbuild -use-ocamlfind -lflags -cclib,-static src/main.native - sudo ./_build/src/main.native -vv --cmd 'calf/_build/main.native -l debug --store 10 --net 12' + jbuilder build src/main.exe +# _build/default/src/main.exe -vv \ +# --cmd 'calf/_build/main.native -l debug --store 10 --net 12' \ +# --ethif eno1 .DELETE_ON_ERROR: diff --git a/projects/miragesdk/dhcp-client/src/.merlin b/projects/miragesdk/dhcp-client/src/.merlin index 559e0c1b7..3232165af 100644 --- a/projects/miragesdk/dhcp-client/src/.merlin +++ b/projects/miragesdk/dhcp-client/src/.merlin @@ -1,4 +1,74 @@ -PKG mirage-net-unix logs-syslog.lwt irmin-unix webmachine cmdliner decompress +B ../_build/default/src +B ../_build/default/src/bpf +FLG -cclib -static +PKG astring +PKG base64 +PKG bigarray +PKG bytes +PKG calendar +PKG cmdliner +PKG cohttp +PKG cohttp.lwt +PKG cohttp.lwt-core +PKG conduit +PKG conduit.lwt +PKG conduit.lwt-unix +PKG cstruct +PKG cstruct.lwt +PKG cstruct.ppx +PKG decompress +PKG dispatch +PKG fieldslib +PKG fmt +PKG fmt.cli +PKG fmt.tty +PKG git +PKG hex +PKG inotify +PKG inotify.lwt +PKG ipaddr +PKG ipaddr.unix +PKG irmin +PKG irmin-git +PKG irmin-http +PKG irmin-watcher +PKG irmin-watcher.core +PKG irmin-watcher.inotify +PKG irmin-watcher.polling +PKG jsonm +PKG logs +PKG logs-syslog +PKG logs-syslog.lwt +PKG logs.cli +PKG logs.fmt +PKG logs.lwt +PKG lwt +PKG lwt.log +PKG lwt.unix +PKG magic-mime +PKG mstruct +PKG ocamlgraph +PKG ocplib-endian +PKG ocplib-endian.bigstring +PKG ptime +PKG ptime.clock.os PKG rawlink +PKG re +PKG re.emacs +PKG re.posix +PKG re.str +PKG result +PKG sexplib +PKG str +PKG stringext +PKG syslog-message +PKG threads +PKG threads.posix +PKG tuntap +PKG uchar +PKG unix +PKG uri +PKG uri.services +PKG uutf +PKG webmachine S . -B _build/ \ No newline at end of file diff --git a/projects/miragesdk/dhcp-client/src/_tags b/projects/miragesdk/dhcp-client/src/_tags deleted file mode 100644 index 92f097bf8..000000000 --- a/projects/miragesdk/dhcp-client/src/_tags +++ /dev/null @@ -1,6 +0,0 @@ -true: bin_annot, debug, strict_sequence -true: warn_error(+1..49+60), warn(A-4-41-44-7) -true: thread -true: package(mirage-net-unix,logs-syslog.lwt,threads,cohttp.lwt) -true: package(cmdliner,fmt.cli,logs.fmt,logs.cli,fmt.tty,decompress) -true: package(irmin,irmin-git,irmin-http,lwt.unix,rawlink) diff --git a/projects/miragesdk/dhcp-client/src/bpf/dhcp.c b/projects/miragesdk/dhcp-client/src/bpf/dhcp.c new file mode 100644 index 000000000..a81d51cbb --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/bpf/dhcp.c @@ -0,0 +1,89 @@ +/* dhcp_bpf_filter taken from bpf.c in dhcp-3.1.0 + * + * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * + * http://www.isc.org/ + */ + +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +#include "caml/memory.h" +#include "caml/fail.h" +#include "caml/unixsupport.h" +#include "caml/signals.h" +#include "caml/alloc.h" +#include "caml/custom.h" +#include "caml/bigarray.h" + +#define BOOTPC 68 +#define BPF_WHOLEPACKET 0x0fffffff + +#ifndef BPF_ETHCOOK +# define BPF_ETHCOOK 0 +#endif + +static const struct sock_filter bootp_bpf_filter [] = { + /* Make sure this is an IP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), + /* Make sure it's a UDP packet... */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + /* Get the IP header length... */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 + BPF_ETHCOOK), + /* Make sure it's to the right port... */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTPC, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; + +/* Filters */ +CAMLprim value bpf_filter(value vunit) +{ + CAMLparam0(); + CAMLlocal1(vfilter); + vfilter = caml_alloc_string(sizeof(bootp_bpf_filter)); + memcpy(String_val(vfilter), bootp_bpf_filter, sizeof(bootp_bpf_filter)); + CAMLreturn (vfilter); +} diff --git a/projects/miragesdk/dhcp-client/src/bpf/jbuild b/projects/miragesdk/dhcp-client/src/bpf/jbuild new file mode 100644 index 000000000..e4afaccb1 --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/bpf/jbuild @@ -0,0 +1,6 @@ +(jbuild_version 1) + +(library + ((name bpf_dhcp) + (c_names (dhcp)) +)) diff --git a/projects/miragesdk/dhcp-client/src/jbuild b/projects/miragesdk/dhcp-client/src/jbuild new file mode 100644 index 000000000..c2a718233 --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/jbuild @@ -0,0 +1,10 @@ +(jbuild_version 1) + +(executables + ((names (main)) + (libraries (logs-syslog.lwt threads cohttp.lwt cstruct.lwt + cmdliner fmt.cli logs.fmt logs.cli fmt.tty decompress + irmin irmin-git irmin-http lwt.unix rawlink tuntap bpf_dhcp + irmin-watcher inotify)) + (flags (-cclib -static)) + )) diff --git a/projects/miragesdk/dhcp-client/src/main.ml b/projects/miragesdk/dhcp-client/src/main.ml index 7a42a144a..dc34ec563 100644 --- a/projects/miragesdk/dhcp-client/src/main.ml +++ b/projects/miragesdk/dhcp-client/src/main.ml @@ -39,7 +39,7 @@ let dup2 ~src ~dst = close src let proxy_rawlink ~rawlink ~fd = - Log.debug (fun l -> l "proxy-netif tap0 <=> %a" pp_fd fd); + Log.debug (fun l -> l "proxy-netif eth0 <=> %a" pp_fd fd); let rec listen_rawlink () = Lwt_rawlink.read_packet rawlink >>= fun buf -> Log.debug (fun l -> l "PROXY-NETIF: => %a" Cstruct.hexdump_pp buf); @@ -171,10 +171,13 @@ module Store = struct module KV = Store(Irmin.Contents.String) let client () = - let config = Irmin_git.config "/data/git" in + let config = Irmin_git.config "/data" in KV.Repo.v config >>= fun repo -> KV.of_branch repo "calf" + let set_listen_dir_hook () = + Irmin.Private.Watch.set_listen_dir_hook Irmin_watcher.hook + module HTTP = struct module Wm = struct @@ -265,7 +268,7 @@ module Store = struct (Uri.path (Request.uri request))); Log.debug (fun l -> l "path=%a" Fmt.(Dump.list string) path); (* Finally, send the response to the client *) - Cohttp_lwt_unix.Server.respond ~headers ~body ~status () + Cohttp_lwt_unix.Server.respond ~flush:true ~headers ~body ~status () in (* create the server and handle requests with the function defined above *) let conn_closed (_, conn) = @@ -273,21 +276,56 @@ module Store = struct l "connection %s closed\n%!" (Cohttp.Connection.to_string conn)) in Cohttp_lwt_unix.Server.make ~callback ~conn_closed () - end - let start () = + let serve () = client () >>= fun db -> let http = HTTP.v db in let fd = fst store in + let on_exn e = Log.err (fun l -> l "XXX %a" Fmt.exn e) in Log.info (fun l -> l "serving KV store on %a" pp_fd fd); - Cohttp_lwt_unix.Server.create ~mode:(`Fd fd.fd) http + Cohttp_lwt_unix.Server.create ~on_exn ~mode:(`Fd fd.fd) http end -let rawlink () = - (* FIXME: enable DHCP filtering via eBPF *) - Lwt_rawlink.open_link (* ~filter:(Rawlink.dhcp_filter ())*) "eth0" +module Handlers = struct + + (* System handlers *) + + let contents_of_diff = function + | `Added (_, `Contents (v, _)) + | `Updated (_, (_, `Contents (v, _))) -> Some v + | _ -> None + + let ip t = + Store.KV.watch_key t ["ip"] (fun diff -> + match contents_of_diff diff with + | Some ip -> + Log.info (fun l -> l "SET IP to %s" ip); + Lwt.return () + | _ -> + Lwt.return () + ) + + let handlers = [ + ip; + ] + + let install () = + Store.client () >>= fun db -> + Lwt_list.map_p (fun f -> f db) handlers >>= fun _ -> + let t, _ = Lwt.task () in + t + +end + +external bpf_filter: unit -> string = "bpf_filter" + +let rawlink ethif = + Log.debug (fun l -> l "bringing up %s" ethif); + (try Tuntap.set_up_and_running ethif + with e -> Log.err (fun l -> l "rawling: %a" Fmt.exn e)); + Lwt_rawlink.open_link ~filter:(bpf_filter ()) ethif let check_exit_status cmd status = let cmds = String.concat " " cmd in @@ -297,9 +335,9 @@ let check_exit_status cmd status = | Unix.WSIGNALED i -> failf "%s: signal %d" cmds i | Unix.WSTOPPED i -> failf "%s: stopped %d" cmds i -let parent cmd pid = +let parent cmd pid ethif = (* network traffic *) - let rawlink = rawlink () in + let rawlink = rawlink ethif in (* close child fds *) close_and_dup stdin >>= fun () -> @@ -325,15 +363,17 @@ let parent cmd pid = forward ~src:(fst logs_out) ~dst:stdout; forward ~src:(fst logs_err) ~dst:stderr; (* metrics: TODO *) - (* store: TODO *) + + Store.serve (); + Handlers.install (); ] -let run () cmd = +let run () cmd ethif = Lwt_main.run ( Lwt_io.flush_all () >>= fun () -> match Lwt_unix.fork () with | 0 -> child cmd - | pid -> parent cmd pid + | pid -> parent cmd pid ethif ) (* CLI *) @@ -368,8 +408,14 @@ let cmd = in Arg.(value & opt (list ~sep:' ' string) default_cmd & doc) +let ethif = + let doc = + Arg.info ~docv:"NAME" ~doc:"The interface to listen too." ["ethif"] + in + Arg.(value & opt string "eth0" & doc) + let run = - Term.(const run $ setup_log $ cmd), + Term.(const run $ setup_log $ cmd $ ethif), Term.info "dhcp-client" ~version:"0.0" let () = match Term.eval run with diff --git a/projects/miragesdk/examples/mirage-dhcp.yml b/projects/miragesdk/examples/mirage-dhcp.yml index a321085ef..2d80c260d 100644 --- a/projects/miragesdk/examples/mirage-dhcp.yml +++ b/projects/miragesdk/examples/mirage-dhcp.yml @@ -19,10 +19,13 @@ system: command: [/usr/bin/binfmt, -dir, /etc/binfmt.d/, -mount, /binfmt_misc] - name: dhcp-client network_mode: host - image: "mobylinux/dhcp-client:dc3fd177a588ca9a850cfc75dd9083fb26d278dc" + image: "mobylinux/dhcp-client:d910ad5712a86cf2848753eecd1b7d07d80ad095" capabilities: - - CAP_NET_RAW - command: [/dhcp-client] + - CAP_NET_ADMIN # to bring eth0 up + - CAP_NET_RAW # to read /dev/eth0 + binds: + - /var/run/dhcp-client:/data + command: [/dhcp-client, -vv] read_only: true daemon: - name: rngd @@ -32,18 +35,9 @@ daemon: oom_score_adj: -800 read_only: true command: [/bin/tini, /usr/sbin/rngd, -f] - - name: nginx - image: "nginx:alpine" - capabilities: - - CAP_NET_BIND_SERVICE - - CAP_CHOWN - - CAP_SETUID - - CAP_SETGID - - CAP_DAC_OVERRIDE - network_mode: host files: - - path: etc/docker/daemon.json - contents: '{"debug": true}' + - path: /var/run/dhcp-client/README + contents: 'data for dhcp-client' outputs: - format: kernel+initrd - format: iso-bios diff --git a/projects/miragesdk/roadmap.md b/projects/miragesdk/roadmap.md index d027e3a33..1c50fcb11 100644 --- a/projects/miragesdk/roadmap.md +++ b/projects/miragesdk/roadmap.md @@ -125,6 +125,7 @@ and is able to get a DHCP lease on boot. ##### TODO - use runc to isolate the calf +- system handler (see https://github.com/kobolabs/dhcpcd/tree/kobo/dhcpcd-hooks) - eBPF filtering - use seccomp to isolate the privileged container - use the DHCP results to actually update the system @@ -135,4 +136,4 @@ and is able to get a DHCP lease on boot. ### Second iteration: NTP -TODO \ No newline at end of file +TODO From 7fa21377b5d30174edffa41cdb575a3c708f65cb Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Mon, 27 Mar 2017 18:11:01 +0200 Subject: [PATCH 5/6] miragesdk: update TODO list Signed-off-by: Thomas Gazagnaire --- projects/miragesdk/roadmap.md | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/miragesdk/roadmap.md b/projects/miragesdk/roadmap.md index 1c50fcb11..7cb9df32f 100644 --- a/projects/miragesdk/roadmap.md +++ b/projects/miragesdk/roadmap.md @@ -126,7 +126,6 @@ and is able to get a DHCP lease on boot. - use runc to isolate the calf - system handler (see https://github.com/kobolabs/dhcpcd/tree/kobo/dhcpcd-hooks) -- eBPF filtering - use seccomp to isolate the privileged container - use the DHCP results to actually update the system - add metrics aggregation (using prometheus) From 56085a3e6c0f8f00185ed26dd4fca0dc7ffd02c7 Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Tue, 28 Mar 2017 14:10:58 +0200 Subject: [PATCH 6/6] miragesdk: re-org source code Split the bits which can be re-used in other services (e.g. init dance and the server-side of the control path). `main.ml` now only contains what is specific to the DHCP logic (+ the /caf directory). Signed-off-by: Thomas Gazagnaire --- projects/miragesdk/dhcp-client/Makefile | 2 +- projects/miragesdk/dhcp-client/calf/config.ml | 6 +- .../miragesdk/dhcp-client/calf/unikernel.ml | 8 +- projects/miragesdk/dhcp-client/src/ctl.ml | 128 ++++++ projects/miragesdk/dhcp-client/src/ctl.mli | 16 + projects/miragesdk/dhcp-client/src/init.ml | 216 ++++++++++ projects/miragesdk/dhcp-client/src/init.mli | 109 +++++ projects/miragesdk/dhcp-client/src/main.ml | 374 +----------------- projects/miragesdk/examples/mirage-dhcp.yml | 2 +- 9 files changed, 499 insertions(+), 362 deletions(-) create mode 100644 projects/miragesdk/dhcp-client/src/ctl.ml create mode 100644 projects/miragesdk/dhcp-client/src/ctl.mli create mode 100644 projects/miragesdk/dhcp-client/src/init.ml create mode 100644 projects/miragesdk/dhcp-client/src/init.mli diff --git a/projects/miragesdk/dhcp-client/Makefile b/projects/miragesdk/dhcp-client/Makefile index 29bf8bed0..c1457ee1b 100644 --- a/projects/miragesdk/dhcp-client/Makefile +++ b/projects/miragesdk/dhcp-client/Makefile @@ -1,5 +1,5 @@ BASE=ocaml/opam:alpine-3.5_ocaml-4.04.0 -FILES=src/main.ml src/inflator.ml src/io_fs.ml src/bpf/dhcp.c \ +FILES=$(shell find src/ -regex '.*\.mli?') src/bpf/dhcp.c \ src/jbuild src/bpf/jbuild IMAGE=dhcp-client OBJS=obj/dhcp-client diff --git a/projects/miragesdk/dhcp-client/calf/config.ml b/projects/miragesdk/dhcp-client/calf/config.ml index 18127b5e4..890a9e43b 100644 --- a/projects/miragesdk/dhcp-client/calf/config.ml +++ b/projects/miragesdk/dhcp-client/calf/config.ml @@ -33,9 +33,9 @@ let net = let key = Key.(create "input" Arg.(opt int 3 doc)) in netif_of_fd key -let store = +let ctl = let doc = - Key.Arg.info ~docv:"FD" ~doc:"Store interface" ["store"] + Key.Arg.info ~docv:"FD" ~doc:"Control interface" ["ctl"] in let key = Key.(create "output" Arg.(opt int 4 doc)) in netif_of_fd key @@ -54,4 +54,4 @@ let main = foreign ~keys ~packages "Unikernel.Main" (time @-> network @-> network @-> job) -let () = register "dhcp-client" [main $ default_time $ net $ store] +let () = register "dhcp-client" [main $ default_time $ net $ ctl] diff --git a/projects/miragesdk/dhcp-client/calf/unikernel.ml b/projects/miragesdk/dhcp-client/calf/unikernel.ml index 7d677f93b..5c3437d74 100644 --- a/projects/miragesdk/dhcp-client/calf/unikernel.ml +++ b/projects/miragesdk/dhcp-client/calf/unikernel.ml @@ -199,13 +199,13 @@ end module Main (Time :Mirage_time_lwt.S) (Net : Mirage_net_lwt.S) - (Store: Mirage_net_lwt.S) = + (Ctl : Mirage_net_lwt.S) = struct - module API = API(Store) + module API = API(Ctl) module Dhcp_client = Dhcp_client_mirage.Make(Time)(Net) - let start () net store = + let start () net ctl = let requests = match Key_gen.codes () with | [] -> default_options | l -> @@ -220,6 +220,6 @@ struct Lwt_stream.last_new stream >>= fun result -> let result = of_ipv4_config result in Log.info (fun l -> l "found lease: %a" pp result); - API.set_ip store result.address + API.set_ip ctl result.address end diff --git a/projects/miragesdk/dhcp-client/src/ctl.ml b/projects/miragesdk/dhcp-client/src/ctl.ml new file mode 100644 index 000000000..4fadf1891 --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/ctl.ml @@ -0,0 +1,128 @@ +open Lwt.Infix + +let src = Logs.Src.create "init" ~doc:"Init steps" +module Log = (val Logs.src_log src : Logs.LOG) + +(* FIXME: to avoid linking with gmp *) +module IO = struct + type ic = unit + type oc = unit + type ctx = unit + let with_connection ?ctx:_ _uri ?init:_ _f = Lwt.fail_with "not allowed" + let read_all _ic = Lwt.fail_with "not allowed" + let read_exactly _ic _n = Lwt.fail_with "not allowed" + let write _oc _buf = Lwt.fail_with "not allowed" + let flush _oc = Lwt.fail_with "not allowed" + let ctx () = Lwt.return_none +end + +(* FIXME: we don't use Irmin_unix.Git.FS.KV to avoid linking with gmp *) +module Store = Irmin_git.FS.KV(IO)(Inflator)(Io_fs) +module KV = Store(Irmin.Contents.String) + +let v path = + let config = Irmin_git.config path in + KV.Repo.v config >>= fun repo -> + KV.of_branch repo "calf" + +let set_listen_dir_hook () = + Irmin.Private.Watch.set_listen_dir_hook Irmin_watcher.hook + +module HTTP = struct + + module Wm = struct + module Rd = Webmachine.Rd + include Webmachine.Make(Cohttp_lwt_unix.Server.IO) + end + + let with_key rd f = + match KV.Key.of_string rd.Wm.Rd.dispatch_path with + | Ok x -> f x + | Error _ -> Wm.respond 404 rd + + let infof fmt = + Fmt.kstrf (fun msg () -> + let date = Int64.of_float (Unix.gettimeofday ()) in + Irmin.Info.v ~date ~author:"calf" msg + ) fmt + + let ok = "{\"status\": \"ok\"}" + + class item db = object(self) + + inherit [Cohttp_lwt_body.t] Wm.resource + + method private of_string rd = + Cohttp_lwt_body.to_string rd.Wm.Rd.req_body >>= fun value -> + with_key rd (fun key -> + let info = infof "Updating %a" KV.Key.pp key in + KV.set db ~info key value >>= fun () -> + let resp_body = `String ok in + let rd = { rd with Wm.Rd.resp_body } in + Wm.continue true rd + ) + + method private to_string rd = + with_key rd (fun key -> + KV.find db key >>= function + | Some value -> Wm.continue (`String value) rd + | None -> assert false + ) + + method resource_exists rd = + with_key rd (fun key -> + KV.mem db key >>= fun mem -> + Wm.continue mem rd + ) + + method allowed_methods rd = + Wm.continue [`GET; `HEAD; `PUT; `DELETE] rd + + method content_types_provided rd = + Wm.continue [ + "plain", self#to_string + ] rd + + method content_types_accepted rd = + Wm.continue [ + "plain", self#of_string + ] rd + + method delete_resource rd = + with_key rd (fun key -> + let info = infof "Deleting %a" KV.Key.pp key in + KV.remove db ~info key >>= fun () -> + let resp_body = `String ok in + Wm.continue true { rd with Wm.Rd.resp_body } + ) + end + + let v db routes = + let routes = List.map (fun r -> r, fun () -> new item db) routes in + let callback (_ch, _conn) request body = + let open Cohttp in + (Wm.dispatch' routes ~body ~request >|= function + | None -> (`Not_found, Header.init (), `String "Not found", []) + | Some result -> result) + >>= fun (status, headers, body, path) -> + Log.info (fun l -> + l "%d - %s %s" + (Code.code_of_status status) + (Code.string_of_method (Request.meth request)) + (Uri.path (Request.uri request))); + Log.debug (fun l -> l "path=%a" Fmt.(Dump.list string) path); + (* Finally, send the response to the client *) + Cohttp_lwt_unix.Server.respond ~flush:true ~headers ~body ~status () + in + (* create the server and handle requests with the function defined above *) + let conn_closed (_, conn) = + Log.info (fun l -> + l "connection %s closed\n%!" (Cohttp.Connection.to_string conn)) + in + Cohttp_lwt_unix.Server.make ~callback ~conn_closed () +end + +let serve ~routes db fd = + let http = HTTP.v db routes in + let on_exn e = Log.err (fun l -> l "ERROR: %a" Fmt.exn e) in + Cohttp_lwt_unix.Server.create ~on_exn ~mode:(`Fd fd) http diff --git a/projects/miragesdk/dhcp-client/src/ctl.mli b/projects/miragesdk/dhcp-client/src/ctl.mli new file mode 100644 index 000000000..b88e4a7cf --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/ctl.mli @@ -0,0 +1,16 @@ +(** [Control] handle the server part of the control path, running in + the privileged container. *) + +module KV: Irmin.KV with type contents = string + +val v: string -> KV.t Lwt.t +(** [v p] is the KV store storing the control state, located at path + [p] in the filesystem of the privileged container. *) + +val serve: routes:string list -> KV.t -> Lwt_unix.file_descr -> unit Lwt.t +(** [serve ~routes kv fd] is the thread exposing the KV store [kv], + holding control state, running inside the privileged container. + [routes] are the routes exposed by the server (currently over a + simple HTTP server -- but will change to something else later, + probably protobuf) to the calf and [kv] is the control state + handler. *) diff --git a/projects/miragesdk/dhcp-client/src/init.ml b/projects/miragesdk/dhcp-client/src/init.ml new file mode 100644 index 000000000..729ff111f --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/init.ml @@ -0,0 +1,216 @@ +open Lwt.Infix + +let src = Logs.Src.create "init" ~doc:"Init steps" +module Log = (val Logs.src_log src : Logs.LOG) + +let failf fmt = Fmt.kstrf Lwt.fail_with fmt + +module Fd = struct + + type t = { + name: string; + fd : Lwt_unix.file_descr; + } + + let fd t = t.fd + let stdout = { name = "stdout"; fd = Lwt_unix.stdout } + let stderr = { name = "stderr"; fd = Lwt_unix.stderr } + let stdin = { name = "stdin" ; fd = Lwt_unix.stdin } + + let to_int t = + (Obj.magic (Lwt_unix.unix_file_descr t.fd): int) + + let pp ppf fd = Fmt.pf ppf "%s:%d" fd.name (to_int fd) + + let close fd = + Log.debug (fun l -> l "close %a" pp fd); + Lwt_unix.close fd.fd + + let dev_null = + Lwt_unix.of_unix_file_descr ~blocking:false + (Unix.openfile "/dev/null" [Unix.O_RDWR] 0) + + let redirect_to_dev_null fd = + Log.debug (fun l -> l "redirect-stdin-to-dev-null"); + Lwt_unix.close fd.fd >>= fun () -> + Lwt_unix.dup2 dev_null fd.fd; + Lwt_unix.close dev_null + + let dup2 ~src ~dst = + Log.debug (fun l -> l "dup2 %a => %a" pp src pp dst); + Lwt_unix.dup2 src.fd dst.fd; + close src + + let proxy_net ~net fd = + Log.debug (fun l -> l "proxy-net eth0 <=> %a" pp fd); + let rec listen_rawlink () = + Lwt_rawlink.read_packet net >>= fun buf -> + Log.debug (fun l -> l "PROXY-NET: => %a" Cstruct.hexdump_pp buf); + Log.debug (fun l -> l "PROXY-NET: => %S" (Cstruct.to_string buf)); + let rec write buf = + Lwt_cstruct.write fd.fd buf >>= function + | 0 -> Lwt.return_unit + | n -> write (Cstruct.shift buf n) + in + write buf >>= fun () -> + listen_rawlink () + in + let listen_socket () = + let len = 16 * 1024 in + let buf = Cstruct.create len in + let rec loop () = + Lwt_cstruct.read fd.fd buf >>= fun len -> + let buf = Cstruct.sub buf 0 len in + Log.debug (fun l -> l "PROXY-NET: <= %a" Cstruct.hexdump_pp buf); + Lwt_rawlink.send_packet net buf >>= fun () -> + loop () + in + loop () + in + Lwt.pick [ + listen_rawlink (); + listen_socket (); + ] + + let rec really_write dst buf off len = + match len with + | 0 -> Lwt.return_unit + | len -> + Lwt_unix.write dst.fd buf off len >>= fun n -> + really_write dst buf (off+n) (len-n) + + let forward ~src ~dst = + Log.debug (fun l -> l "forward %a => %a" pp src pp dst); + let len = 16 * 1024 in + let buf = Bytes.create len in + let rec loop () = + Lwt_unix.read src.fd buf 0 len >>= fun len -> + if len = 0 then + (* FIXME: why this ever happen *) + Fmt.kstrf Lwt.fail_with "FORWARD[%a => %a]: EOF" pp src pp dst + else ( + Log.debug (fun l -> + l "FORWARD[%a => %a]: %S (%d)" + pp src pp dst (Bytes.sub buf 0 len) len); + really_write dst buf 0 len >>= fun () -> + loop () + ) + in + loop () + + let proxy x y = + Lwt.pick [ + forward ~src:x ~dst:y; + forward ~src:y ~dst:x; + ] + +end + +module Pipe = struct + + type t = Fd.t * Fd.t + + let priv = fst + let calf = snd + + let socketpair name = + let priv, calf = Lwt_unix.(socketpair PF_UNIX SOCK_STREAM 0) in + Lwt_unix.clear_close_on_exec priv; + Lwt_unix.clear_close_on_exec calf; + { Fd.name = name; fd = priv }, { Fd.name = name ^ "-calf"; fd = calf } + + let pipe name = + let priv, calf = Lwt_unix.pipe () in + Lwt_unix.clear_close_on_exec priv; + Lwt_unix.clear_close_on_exec calf; + { Fd.name = name; fd = priv }, { Fd.name = name ^ "-calf"; fd = calf } + + (* logs pipe *) + let stdout = pipe "logs-out" + let stderr = pipe "logs-err" + + (* store pipe *) + let ctl = socketpair "ctl" + + (* network pipe *) + let net = socketpair "net" + + (* metrics pipe *) + let metrics = pipe "metrics" + +end + +let exec_calf cmd = + Fd.(redirect_to_dev_null stdin) >>= fun () -> + + (* close parent fds *) + Fd.close Pipe.(priv stdout) >>= fun () -> + Fd.close Pipe.(priv stderr) >>= fun () -> + Fd.close Pipe.(priv ctl) >>= fun () -> + Fd.close Pipe.(priv net) >>= fun () -> + Fd.close Pipe.(priv metrics) >>= fun () -> + + let cmds = String.concat " " cmd in + + let calf_net = Pipe.(calf net) in + let calf_ctl = Pipe.(calf ctl) in + let calf_stdout = Pipe.(calf stdout) in + let calf_stderr = Pipe.(calf stderr) in + + Log.info (fun l -> l "Executing %s" cmds); + Log.debug (fun l -> l "net-fd=%a store-fd=%a" Fd.pp calf_net Fd.pp calf_ctl); + + Fd.dup2 ~src:calf_stdout ~dst:Fd.stdout >>= fun () -> + Fd.dup2 ~src:calf_stderr ~dst:Fd.stderr >>= fun () -> + + (* exec the calf *) + Unix.execve (List.hd cmd) (Array.of_list cmd) [||] + +let rawlink ?filter ethif = + Log.debug (fun l -> l "bringing up %s" ethif); + (try Tuntap.set_up_and_running ethif + with e -> Log.err (fun l -> l "rawlink: %a" Fmt.exn e)); + Lwt_rawlink.open_link ?filter ethif + +let check_exit_status cmd status = + let cmds = String.concat " " cmd in + match status with + | Unix.WEXITED 0 -> Lwt.return_unit + | Unix.WEXITED i -> failf "%s: exit %d" cmds i + | Unix.WSIGNALED i -> failf "%s: signal %d" cmds i + | Unix.WSTOPPED i -> failf "%s: stopped %d" cmds i + +let exec_priv ~pid ~cmd ~net ~ctl ~handlers = + + (* close child fds *) + Fd.(redirect_to_dev_null stdin) >>= fun () -> + Fd.close Pipe.(calf stdout) >>= fun () -> + Fd.close Pipe.(calf stderr) >>= fun () -> + Fd.close Pipe.(calf net) >>= fun () -> + Fd.close Pipe.(calf ctl) >>= fun () -> + Fd.close Pipe.(calf metrics) >>= fun () -> + + let wait () = + Lwt_unix.waitpid [] pid >>= fun (_pid, w) -> + Lwt_io.flush_all () >>= fun () -> + + check_exit_status cmd w + in + Lwt.pick ([ + wait (); + (* data *) + Fd.proxy_net ~net Pipe.(priv net); + + (* redirect the calf stdout to the shim stdout *) + Fd.forward ~src:Pipe.(priv stdout) ~dst:Fd.stdout; + Fd.forward ~src:Pipe.(priv stderr) ~dst:Fd.stderr; + (* TODO: Init.Fd.forward ~src:Init.Pipe.(priv metrics) ~dst:Init.Fd.metric; *) + ctl (); + handlers (); + ]) + +let run ~net ~ctl ~handlers cmd = + Lwt_io.flush_all () >>= fun () -> + match Lwt_unix.fork () with + | 0 -> exec_calf cmd + | pid -> exec_priv ~pid ~cmd ~net ~ctl ~handlers diff --git a/projects/miragesdk/dhcp-client/src/init.mli b/projects/miragesdk/dhcp-client/src/init.mli new file mode 100644 index 000000000..3fb6abb68 --- /dev/null +++ b/projects/miragesdk/dhcp-client/src/init.mli @@ -0,0 +1,109 @@ +(** Init functions. + + [Init] contains funcitons to initialise the state of the + privileged container. + + {ul + + {- fowrard and filter the network traffic using BPF (for instance + to allow only DHCP traffic).} + {- open pipes for forwarding the calf's stdout and stderr + to the privileged container's ones.} + {- open a pipe to forward the metrics.} + {- open a socket pair with the calf to be able to transmit control + data, e.g. the IP address once a DHCP lease is obtained.} + }*) + + +module Fd: sig + + type t + (** The type for file descriptors. *) + + val pp: t Fmt.t + (** [pp_fd] pretty prints a file descriptor. *) + + val fd: t -> Lwt_unix.file_descr + (** [fd t] is [t]'s underlying unix file descriptor. *) + + val to_int: t -> int +(** [to_int fd] is [fd]'s number. *) + + val redirect_to_dev_null: t -> unit Lwt.t + (** [redirect_to_dev_null fd] redirects [fd] [/dev/null]. *) + + val close: t -> unit Lwt.t + (** [close fd] closes [fd]. *) + + val dup2: src:t -> dst:t -> unit Lwt.t + (** [dup2 ~src ~dst] calls [Unix.dup2] on [src] and [dst]. *) + + val proxy_net: net:Lwt_rawlink.t -> t -> unit Lwt.t + (** [proxy_net ~net fd] proxies the traffic between the raw net link + [net] and [fd]. *) + + val forward: src:t -> dst:t -> unit Lwt.t + (** [forward ~src ~dst] forwards the flow from [src] to [dst]. *) + + (** {1 Usefull File Descriptors} *) + + val stdin: t + (** [stdin] is the standart input. *) + + val stdout: t + (** [stdout] is the standard output. *) + + val stderr: t + (** [stderr] is the standard error. *) + +end + +module Pipe: sig + + type t + (** The type for pipes. Could be either uni-directional (normal + pipes) or a bi-directional (socket pairs). *) + + val priv: t -> Fd.t + (** [priv p] is the private side of the pipe [p]. *) + + val calf: t -> Fd.t + (** [calf p] is the calf side of the pipe [p]. *) + + (** {1 Useful Pipes} *) + + val stdout: t + (** [stdout] is the uni-directional pipe from the calf's stdout . *) + + val stderr: t + (** [stderr] is the uni-directional pipe from the calf's stderr. *) + + val metrics: t + (** [metrics] is the uni-directional pipe fomr the calf's metric + endpoint. *) + + val ctl: t + (** [ctl] is the bi-directional pipe used to exchange control + data between the calf and the priv containers. *) + + val net: t + (** [net] is the bi-directional pipe used to exchange network + traffic between the calf and the priv containers. *) + +end + +val rawlink: ?filter:string -> string -> Lwt_rawlink.t +(** [rawlink ?filter i] is the net raw link to the interface [i] using + the (optional) BPF filter [filter]. *) + +val run: + net:Lwt_rawlink.t -> + ctl:(unit -> unit Lwt.t) -> + handlers:(unit -> unit Lwt.t) -> + string list -> unit Lwt.t +(** [run ~net ~ctl ~handlers cmd] runs [cmd] in a unprivileged calf + process. [ctl] is the control thread connected to the {Pipe.ctl} + pipe. [net] is the net raw link which will be connected to the + calf via the {!Pipe.net} socket pair. [handlers] are the system + handler thread which will react to control data to perform + privileged system actions. *) diff --git a/projects/miragesdk/dhcp-client/src/main.ml b/projects/miragesdk/dhcp-client/src/main.ml index dc34ec563..a0e2b4a13 100644 --- a/projects/miragesdk/dhcp-client/src/main.ml +++ b/projects/miragesdk/dhcp-client/src/main.ml @@ -5,288 +5,6 @@ module Log = (val Logs.src_log src : Logs.LOG) let failf fmt = Fmt.kstrf Lwt.fail_with fmt -type fd = { - name: string; - fd : Lwt_unix.file_descr; -} - -let stdout = { name = "stdout"; fd = Lwt_unix.stdout } -let stderr = { name = "stderr"; fd = Lwt_unix.stderr } -let stdin = { name = "stdin" ; fd = Lwt_unix.stdin } - -let int_of_fd (fd:Lwt_unix.file_descr) = - (Obj.magic (Lwt_unix.unix_file_descr fd): int) - -let pp_fd ppf fd = Fmt.pf ppf "%s:%d" fd.name (int_of_fd fd.fd) - -let close fd = - Log.debug (fun l -> l "close %a" pp_fd fd); - Lwt_unix.close fd.fd - -let dev_null = - Lwt_unix.of_unix_file_descr ~blocking:false - (Unix.openfile "/dev/null" [Unix.O_RDWR] 0) - -let close_and_dup fd = - Log.debug (fun l -> l "close-and-dup %a" pp_fd fd); - Lwt_unix.close fd.fd >>= fun () -> - Lwt_unix.dup2 dev_null fd.fd; - Lwt_unix.close dev_null - -let dup2 ~src ~dst = - Log.debug (fun l -> l "dup2 %a => %a" pp_fd src pp_fd dst); - Lwt_unix.dup2 src.fd dst.fd; - close src - -let proxy_rawlink ~rawlink ~fd = - Log.debug (fun l -> l "proxy-netif eth0 <=> %a" pp_fd fd); - let rec listen_rawlink () = - Lwt_rawlink.read_packet rawlink >>= fun buf -> - Log.debug (fun l -> l "PROXY-NETIF: => %a" Cstruct.hexdump_pp buf); - Log.debug (fun l -> l "PROXY-NETIF: => %S" (Cstruct.to_string buf)); - let rec write buf = - Lwt_cstruct.write fd.fd buf >>= function - | 0 -> Lwt.return_unit - | n -> write (Cstruct.shift buf n) - in - write buf >>= fun () -> - listen_rawlink () - in - let listen_socket () = - let len = 16 * 1024 in - let buf = Cstruct.create len in - let rec loop () = - Lwt_cstruct.read fd.fd buf >>= fun len -> - let buf = Cstruct.sub buf 0 len in - Log.debug (fun l -> l "PROXY-NETIF: <= %a" Cstruct.hexdump_pp buf); - Lwt_rawlink.send_packet rawlink buf >>= fun () -> - loop () - in - loop () - in - Lwt.pick [ - listen_rawlink (); - listen_socket (); - ] - -let rec really_write dst buf off len = - match len with - | 0 -> Lwt.return_unit - | len -> - Lwt_unix.write dst.fd buf off len >>= fun n -> - really_write dst buf (off+n) (len-n) - -let forward ~src ~dst = - Log.debug (fun l -> l "forward %a => %a" pp_fd src pp_fd dst); - let len = 16 * 1024 in - let buf = Bytes.create len in - let rec loop () = - Lwt_unix.read src.fd buf 0 len >>= fun len -> - if len = 0 then - (* FIXME: why this ever happen *) - Fmt.kstrf Lwt.fail_with "FORWARD[%a => %a]: EOF" pp_fd src pp_fd dst - else ( - Log.debug (fun l -> - l "FORWARD[%a => %a]: %S (%d)" - pp_fd src pp_fd dst (Bytes.sub buf 0 len) len); - really_write dst buf 0 len >>= fun () -> - loop () - ) - in - loop () - -let proxy x y = - Lwt.pick [ - forward ~src:x ~dst:y; - forward ~src:y ~dst:x; - ] - -(* Prepare the fd space before we fork to run the calf *) - -let socketpair name = - let priv, calf = Lwt_unix.(socketpair PF_UNIX SOCK_STREAM 0) in - Lwt_unix.clear_close_on_exec priv; - Lwt_unix.clear_close_on_exec calf; - { name = name; fd = priv }, { name = name ^ "-calf"; fd = calf } - -let pipe name = - let priv, calf = Lwt_unix.pipe () in - Lwt_unix.clear_close_on_exec priv; - Lwt_unix.clear_close_on_exec calf; - { name = name; fd = priv }, { name = name ^ "-calf"; fd = calf } - -(* logs pipe *) -let logs_out = pipe "logs-out" -let logs_err = pipe "logs-err" - -(* store pipe *) -let store = socketpair "store" - -(* network pipe *) -let net = socketpair "net" - -(* metrics pipe *) -(* let metrics = make "metrics" *) - -let child cmd = - close_and_dup stdin >>= fun () -> - - (* close parent fds *) - close (fst logs_out) >>= fun () -> - close (fst logs_err) >>= fun () -> - close (fst store) >>= fun () -> - close (fst net) >>= fun () -> - (* - close (fst metrics) >>= fun () -> - *) - - let cmds = String.concat " " cmd in - Log.info (fun l -> l "Executing %s" cmds); - Log.debug (fun l -> - l "net-fd=%a store-fd=%a" pp_fd (snd net) pp_fd (snd store)); - - dup2 ~src:(snd logs_out) ~dst:stdout >>= fun () -> - dup2 ~src:(snd logs_err) ~dst:stderr >>= fun () -> - - (* exec the calf *) - Unix.execve (List.hd cmd) (Array.of_list cmd) [||] - -module Store = struct - - (* FIXME: to avoid linking with gmp *) - module IO = struct - type ic = unit - type oc = unit - type ctx = unit - let with_connection ?ctx:_ _uri ?init:_ _f = Lwt.fail_with "not allowed" - let read_all _ic = Lwt.fail_with "not allowed" - let read_exactly _ic _n = Lwt.fail_with "not allowed" - let write _oc _buf = Lwt.fail_with "not allowed" - let flush _oc = Lwt.fail_with "not allowed" - let ctx () = Lwt.return_none - end - - (* FIXME: we don't use Irmin_unix.Git.FS.KV to avoid linking with gmp *) - module Store = Irmin_git.FS.KV(IO)(Inflator)(Io_fs) - module KV = Store(Irmin.Contents.String) - - let client () = - let config = Irmin_git.config "/data" in - KV.Repo.v config >>= fun repo -> - KV.of_branch repo "calf" - - let set_listen_dir_hook () = - Irmin.Private.Watch.set_listen_dir_hook Irmin_watcher.hook - - module HTTP = struct - - module Wm = struct - module Rd = Webmachine.Rd - include Webmachine.Make(Cohttp_lwt_unix.Server.IO) - end - - let with_key rd f = - match KV.Key.of_string rd.Wm.Rd.dispatch_path with - | Ok x -> f x - | Error _ -> Wm.respond 404 rd - - let infof fmt = - Fmt.kstrf (fun msg () -> - let date = Int64.of_float (Unix.gettimeofday ()) in - Irmin.Info.v ~date ~author:"calf" msg - ) fmt - - let ok = "{\"status\": \"ok\"}" - - class item db = object(self) - - inherit [Cohttp_lwt_body.t] Wm.resource - - method private of_string rd = - Cohttp_lwt_body.to_string rd.Wm.Rd.req_body >>= fun value -> - with_key rd (fun key -> - let info = infof "Updating %a" KV.Key.pp key in - KV.set db ~info key value >>= fun () -> - let resp_body = `String ok in - let rd = { rd with Wm.Rd.resp_body } in - Wm.continue true rd - ) - - method private to_string rd = - with_key rd (fun key -> - KV.find db key >>= function - | Some value -> Wm.continue (`String value) rd - | None -> assert false - ) - - method resource_exists rd = - with_key rd (fun key -> - KV.mem db key >>= fun mem -> - Wm.continue mem rd - ) - - method allowed_methods rd = - Wm.continue [`GET; `HEAD; `PUT; `DELETE] rd - - method content_types_provided rd = - Wm.continue [ - "plain", self#to_string - ] rd - - method content_types_accepted rd = - Wm.continue [ - "plain", self#of_string - ] rd - - method delete_resource rd = - with_key rd (fun key -> - let info = infof "Deleting %a" KV.Key.pp key in - KV.remove db ~info key >>= fun () -> - let resp_body = `String ok in - Wm.continue true { rd with Wm.Rd.resp_body } - ) - end - - let v db = - let routes = [ - ("/ip" , fun () -> new item db); - ("/domain" , fun () -> new item db); - ("/search" , fun () -> new item db); - ("/mtu" , fun () -> new item db); - ("/nameserver/*", fun () -> new item db); - ] in - let callback (_ch, _conn) request body = - let open Cohttp in - (Wm.dispatch' routes ~body ~request >|= function - | None -> (`Not_found, Header.init (), `String "Not found", []) - | Some result -> result) - >>= fun (status, headers, body, path) -> - Log.info (fun l -> - l "%d - %s %s" - (Code.code_of_status status) - (Code.string_of_method (Request.meth request)) - (Uri.path (Request.uri request))); - Log.debug (fun l -> l "path=%a" Fmt.(Dump.list string) path); - (* Finally, send the response to the client *) - Cohttp_lwt_unix.Server.respond ~flush:true ~headers ~body ~status () - in - (* create the server and handle requests with the function defined above *) - let conn_closed (_, conn) = - Log.info (fun l -> - l "connection %s closed\n%!" (Cohttp.Connection.to_string conn)) - in - Cohttp_lwt_unix.Server.make ~callback ~conn_closed () - end - - let serve () = - client () >>= fun db -> - let http = HTTP.v db in - let fd = fst store in - let on_exn e = Log.err (fun l -> l "XXX %a" Fmt.exn e) in - Log.info (fun l -> l "serving KV store on %a" pp_fd fd); - Cohttp_lwt_unix.Server.create ~on_exn ~mode:(`Fd fd.fd) http - -end module Handlers = struct @@ -298,7 +16,7 @@ module Handlers = struct | _ -> None let ip t = - Store.KV.watch_key t ["ip"] (fun diff -> + Ctl.KV.watch_key t ["ip"] (fun diff -> match contents_of_diff diff with | Some ip -> Log.info (fun l -> l "SET IP to %s" ip); @@ -311,8 +29,8 @@ module Handlers = struct ip; ] - let install () = - Store.client () >>= fun db -> + let watch path = + Ctl.v path >>= fun db -> Lwt_list.map_p (fun f -> f db) handlers >>= fun _ -> let t, _ = Lwt.task () in t @@ -321,59 +39,21 @@ end external bpf_filter: unit -> string = "bpf_filter" -let rawlink ethif = - Log.debug (fun l -> l "bringing up %s" ethif); - (try Tuntap.set_up_and_running ethif - with e -> Log.err (fun l -> l "rawling: %a" Fmt.exn e)); - Lwt_rawlink.open_link ~filter:(bpf_filter ()) ethif - -let check_exit_status cmd status = - let cmds = String.concat " " cmd in - match status with - | Unix.WEXITED 0 -> Lwt.return_unit - | Unix.WEXITED i -> failf "%s: exit %d" cmds i - | Unix.WSIGNALED i -> failf "%s: signal %d" cmds i - | Unix.WSTOPPED i -> failf "%s: stopped %d" cmds i - -let parent cmd pid ethif = - (* network traffic *) - let rawlink = rawlink ethif in - - (* close child fds *) - close_and_dup stdin >>= fun () -> - close (snd logs_out) >>= fun () -> - close (snd logs_err) >>= fun () -> - close (snd net) >>= fun () -> - close (snd store) >>= fun () -> - (* - close (snd metrics) >>= fun () -> - *) - let wait () = - Lwt_unix.waitpid [] pid >>= fun (_pid, w) -> - Lwt_io.flush_all () >>= fun () -> - - check_exit_status cmd w - in - Lwt.pick [ - wait (); - (* data *) - proxy_rawlink ~rawlink ~fd:(fst net); - - (* redirect the calf stdout to the shim stdout *) - forward ~src:(fst logs_out) ~dst:stdout; - forward ~src:(fst logs_err) ~dst:stderr; - (* metrics: TODO *) - - Store.serve (); - Handlers.install (); - ] - -let run () cmd ethif = +let run () cmd ethif path = Lwt_main.run ( - Lwt_io.flush_all () >>= fun () -> - match Lwt_unix.fork () with - | 0 -> child cmd - | pid -> parent cmd pid ethif + let net = Init.rawlink ~filter:(bpf_filter ()) ethif in + let routes = [ + "/ip"; + "/domain"; + "/search"; + "/mtu"; + "/nameservers/*" + ] in + Ctl.v "/data" >>= fun ctl -> + let fd = Init.(Fd.fd @@ Pipe.(priv ctl)) in + let ctl () = Ctl.serve ~routes ctl fd in + let handlers () = Handlers.watch path in + Init.run ~net ~ctl ~handlers cmd ) (* CLI *) @@ -392,6 +72,9 @@ let setup_log style_renderer level = let setup_log = Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) +let ctl = string_of_int Init.(Fd.to_int Pipe.(calf ctl)) +let net = string_of_int Init.(Fd.to_int Pipe.(calf net)) + let cmd = (* FIXME: use runc isolation let default_cmd = [ @@ -401,7 +84,7 @@ let cmd = ] in *) let default_cmd = [ - "/dhcp-client-calf"; "--store=10"; "--net=12" + "/dhcp-client-calf"; "--ctl="^ctl; "--net="^net ] in let doc = Arg.info ~docv:"CMD" ~doc:"Command to run the calf process." ["cmd"] @@ -421,18 +104,3 @@ let run = let () = match Term.eval run with | `Error _ -> exit 1 | _ -> exit 0 - -(* - -let kv_store = Unix.pipe () - -let install_logger () = - Logs_syslog_lwt.udp_reporter (Unix.inet_addr_of_string "127.0.0.1") () - >|= fun r -> - Logs.set_reporter r - -let () = Lwt_main.run ( - install_logger () >>= fun () -> - fd_of_tap0 >>= fun fd -> - ) -*) diff --git a/projects/miragesdk/examples/mirage-dhcp.yml b/projects/miragesdk/examples/mirage-dhcp.yml index 2d80c260d..82418a0ba 100644 --- a/projects/miragesdk/examples/mirage-dhcp.yml +++ b/projects/miragesdk/examples/mirage-dhcp.yml @@ -19,7 +19,7 @@ system: command: [/usr/bin/binfmt, -dir, /etc/binfmt.d/, -mount, /binfmt_misc] - name: dhcp-client network_mode: host - image: "mobylinux/dhcp-client:d910ad5712a86cf2848753eecd1b7d07d80ad095" + image: "mobylinux/dhcp-client:f6ef2cc4c3bf7dcad643f22fbd3d355af1725105" capabilities: - CAP_NET_ADMIN # to bring eth0 up - CAP_NET_RAW # to read /dev/eth0