├── .gitignore ├── .travis.yml ├── Dockerfile.runner ├── Dockerfile.runner-build ├── Dockerfile.runner-debian-build ├── Dockerfile.runner-qemu ├── LICENSE ├── Makefile ├── README.md ├── check-tests.sh ├── docker-mirage.sh ├── src ├── Makefile ├── ptrvec.c ├── ptrvec.h ├── runner.c └── vendor │ ├── libcap-ng-0.7.8.tar.gz │ └── libnl-3.2.25.tar.gz └── tests ├── mirage-solo5 ├── Dockerfile.stackv4-qemu ├── Dockerfile.stackv4-ukvm ├── Dockerfile.stackv4-ukvm-build ├── Dockerfile.stackv4-virtio-build ├── Makefile ├── test-stackv4-qemu.sh └── test-stackv4-ukvm.sh └── mirage-unix ├── Dockerfile.stackv4 ├── Dockerfile.stackv4-build ├── Dockerfile.static_website ├── Dockerfile.static_website-build ├── Makefile ├── test-stackv4.sh └── test-static_website.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.tar.gz 2 | !src/vendor/*.tar.gz 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | services: 4 | - docker 5 | language: generic 6 | script: make tests 7 | after_script: sudo sysctl -w net.ipv4.conf.docker0.arp_accept=1 && make run-tests 8 | -------------------------------------------------------------------------------- /Dockerfile.runner: -------------------------------------------------------------------------------- 1 | # "Runtime" container for mirage-unix runner. 2 | 3 | FROM alpine:3.4 4 | RUN apk add --update --no-cache libnl3 libcap-ng 5 | ADD runner.tar.gz /runtime/ 6 | 7 | # Should be set explicitly downstream. 8 | ENTRYPOINT [] 9 | CMD [] 10 | -------------------------------------------------------------------------------- /Dockerfile.runner-build: -------------------------------------------------------------------------------- 1 | # mir-runner build container (alpine version) 2 | FROM alpine:3.4 3 | RUN apk add --update --no-cache build-base linux-headers libnl3-dev libcap-ng-dev bison flex tar file coreutils 4 | ADD ./src /src/runner 5 | WORKDIR /src/runner 6 | RUN make 7 | CMD tar -czf - runner 8 | -------------------------------------------------------------------------------- /Dockerfile.runner-debian-build: -------------------------------------------------------------------------------- 1 | # mir-runner build container (debian version) 2 | FROM debian:jessie 3 | RUN apt-get update && \ 4 | DEBIAN_FRONTEND=noninteractive apt-get install -q -y \ 5 | --no-install-recommends \ 6 | bison \ 7 | build-essential \ 8 | flex \ 9 | libcap-ng-dev \ 10 | libnl-3-dev \ 11 | libnl-route-3-dev \ 12 | linux-libc-dev \ 13 | pkg-config \ 14 | && apt-get clean 15 | ADD ./src /src/runner 16 | WORKDIR /src/runner 17 | RUN make 18 | CMD tar -czf - runner 19 | -------------------------------------------------------------------------------- /Dockerfile.runner-qemu: -------------------------------------------------------------------------------- 1 | # "Runtime" container for mirage-unix runner. 2 | 3 | FROM debian:jessie 4 | RUN apt-get update && \ 5 | DEBIAN_FRONTEND=noninteractive apt-get install -q -y \ 6 | --no-install-recommends \ 7 | libcap-ng0 \ 8 | libnl-3-200 \ 9 | libnl-route-3-200 \ 10 | qemu-system-x86 \ 11 | && apt-get clean 12 | ADD runner-debian.tar.gz /runtime/ 13 | 14 | # Should be set explicitly downstream. 15 | ENTRYPOINT [] 16 | CMD [] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Authors 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 13 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: runner 3 | 4 | .PHONY: runner 5 | runner: mir-runner mir-runner-qemu 6 | 7 | .PHONY: tests 8 | tests: runner 9 | $(MAKE) -C tests/mirage-unix 10 | $(MAKE) -C tests/mirage-solo5 11 | 12 | .PHONY: run-tests 13 | run-tests: tests 14 | ./check-tests.sh 15 | $(MAKE) -C tests/mirage-unix run 16 | $(MAKE) -C tests/mirage-solo5 run 17 | 18 | # Runner base image: intermediate build container. 19 | runner.tar.gz: src/*.c src/Makefile Dockerfile.runner-build 20 | docker build -t mir-runner-build -f Dockerfile.runner-build . 21 | docker run --rm mir-runner-build > runner.tar.gz 22 | 23 | # Runner base image for UKVM, UNIX: mir-runner. 24 | .PHONY: mir-runner 25 | mir-runner: runner.tar.gz Dockerfile.runner 26 | docker build -t mir-runner -f Dockerfile.runner . 27 | 28 | # Runner base image: intermediate build container (Debian build). 29 | runner-debian.tar.gz: src/*.c src/Makefile Dockerfile.runner-debian-build 30 | docker build -t mir-runner-debian-build -f Dockerfile.runner-debian-build . 31 | docker run --rm mir-runner-debian-build > runner-debian.tar.gz 32 | 33 | # Runner base image for QEMU, QEMU/KVM: mir-runner-qemu 34 | .PHONY: mir-runner-qemu 35 | mir-runner-qemu: runner-debian.tar.gz Dockerfile.runner-qemu 36 | docker build -t mir-runner-qemu -f Dockerfile.runner-qemu . 37 | 38 | .PHONY: clean clobber 39 | clean: 40 | $(RM) runner.tar.gz runner-debian.tar.gz 41 | $(MAKE) -C tests/mirage-unix clean 42 | $(MAKE) -C tests/mirage-solo5 clean 43 | 44 | # Run to clean all images, include intermediate containers. 45 | clobber: clean 46 | -docker rmi -f mir-runner mir-runner-build \ 47 | mir-runner-qemu mir-runner-debian-build 48 | $(MAKE) -C tests/mirage-unix clobber 49 | $(MAKE) -C tests/mirage-solo5 clobber 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker unikernel runner for Mirage OS [![Build status](https://travis-ci.org/mato/docker-unikernel-runner.svg?branch=master)](https://travis-ci.org/mato/docker-unikernel-runner) 2 | 3 | **UPDATE February 2018** 4 | 5 | Docker unikernel runner was a nice experiment, but I am no longer developing or maintaining this code. This repository is now archived, feel free to fork it. 6 | 7 | **END UPDATE** 8 | 9 | This is an experimental unikernel runner for running 10 | [Mirage OS](https://mirage.io) unikernels in Docker containers. Currently the 11 | following Mirage OS targets are supported: 12 | 13 | * `unix`: UNIX userspace using the `direct` network stack. 14 | * `ukvm`: Mirage OS/[Solo5](https://github.com/solo5/solo5) using ukvm as the hypervisor. 15 | * `qemu`, `kvm` (_experimental_): Mirage OS/[Solo5](https://github.com/solo5/solo5) 16 | using software emulation (`qemu`) or QEMU/KVM (`kvm`) as the hypervisor. 17 | 18 | # Quick start with a Mirage application 19 | 20 | You will need `docker` (obviously) and `make` to drive the top-level build 21 | process. The build itself is all run in containers so there are no other host 22 | requirements. 23 | 24 | 1. Clone this repository, run `make`. This will build the `mir-runner` and 25 | `mir-runner-qemu` base images. 26 | 2. Place `docker-mirage.sh` somewhere in your $PATH. 27 | 3. In the directory containing your built Mirage application, run 28 | `docker-mirage.sh build HYPERVISOR -t my-unikernel`, where _HYPERVISOR_ is 29 | one of the supported targets (see **note**). 30 | 4. Run the unikernel with `docker-mirage.sh run --rm -ti my-unikernel`. 31 | 32 | **Note:** If you're using Docker for Mac or Docker for Windows, then you will 33 | only be able to _run_ images built for the `qemu` HYPERVISOR locally. 34 | 35 | # Detailed instructions 36 | 37 | This section covers more about how runner works, including how to manually 38 | build your own unikernel images without the `docker-mirage` wrapper script. 39 | 40 | ## Building 41 | 42 | 43 | To build the runner and all example containers, run: 44 | 45 | ```` 46 | make tests 47 | ```` 48 | 49 | See the `Makefile`s under the `tests/` directory for an example of how to 50 | manually build unikernel images. 51 | 52 | ## Running the example containers 53 | 54 | Use `make run-tests` to run all tests available on your host. The Mirage/Solo5 55 | tests require KVM and access to `/dev/kvm`. 56 | 57 | ### Mirage OS/unix 58 | 59 | Two containers which build Mirage OS samples from the `mirage-skeleton` 60 | repository are included, `mir-stackv4` and `mir-static_website`. 61 | 62 | Each is run as a normal Docker container, however you must pass `/dev/net/tun` 63 | to the container and run with the `CAP_NET_ADMIN` capability. For example: 64 | 65 | ```` 66 | docker run -ti --rm \ 67 | --device=/dev/net/tun:/dev/net/tun \ 68 | --cap-add=NET_ADMIN mir-stackv4 69 | ```` 70 | `CAP_NET_ADMIN` and access to `/dev/net/tun` are required for runner to be able 71 | to wire L2 network connectivity from Docker to the unikernel. Runner will drop 72 | all capabilities with the exception of `CAP_NET_BIND_SERVICE` before launching 73 | the unikernel. 74 | 75 | ### Mirage OS/Solo5 76 | 77 | To run the `mir-stackv4` sample using `ukvm` as a hypervisor: 78 | 79 | ```` 80 | docker run -ti --rm \ 81 | --device=/dev/kvm:/dev/kvm \ 82 | --device=/dev/net/tun:/dev/net/tun \ 83 | --cap-add=NET_ADMIN mir-stackv4-ukvm 84 | ```` 85 | In addition to the requirements for the `unix` target, access to `/dev/kvm` is 86 | required. 87 | 88 | ## Known issues 89 | 90 | * ([#1](https://github.com/mato/docker-unikernel-runner/issues/1)) Network delays due to random MAC address use. Workaround is: `sysctl -w net.ipv4.conf.docker0.arp_accept=1`. 91 | * `qemu` and `kvm` support is experimental, currently uses Debian to build the containers due to unknown issues with the Alpine toolchain. 92 | -------------------------------------------------------------------------------- /check-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ARP_ACCEPT=$(sysctl -n net.ipv4.conf.docker0.arp_accept) 4 | if [ "${ARP_ACCEPT}" != "1" ]; then 5 | cat <&2 6 | >>> WARNING: Run "sysctl -w net.ipv4.conf.docker0.arp_accept=1" to enable 7 | >>> WARNING: gratuitous ARP on the Docker bridge, otherwise you will experience 8 | >>> WARNING: intermittent network failures. 9 | >>> WARNING: Now sleeping 10 seconds before running tests. 10 | EOM 11 | sleep 10 12 | fi 13 | -------------------------------------------------------------------------------- /docker-mirage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() 4 | { 5 | ME=$(basename $0) 6 | cat <&2 7 | usage: ${ME} COMMAND ARGS... 8 | 9 | Simple wrapper around 'docker build' and 'docker run' to build and run 10 | Mirage unikernels using docker-unikernel-runner. 11 | 12 | Available COMMANDs: 13 | 14 | run [ OPTIONS ] IMAGE -- Wrapper for 'docker run': 15 | Adds CAP_NET_ADMIN, passes through host /dev/net/tun and /dev/kvm (if 16 | available). 17 | 18 | build HYPERVISOR [ OPTIONS ] -- Wrapper for 'docker build': 19 | HYPERVISOR: one of qemu | kvm | ukvm | unix. 20 | OPTIONS: passed through to 'docker build'. 21 | EOM 22 | exit 1 23 | } 24 | 25 | do_build() 26 | { 27 | if [ ! -f ./config.ml ]; then 28 | echo error: No configuration file config.ml found. 1>&2 29 | echo error: This does not look like a Mirage unikernel source directory. 1>&2 30 | exit 1 31 | fi 32 | [ $# -lt 1 ] && usage 33 | HYPERVISOR=$1 34 | case ${HYPERVISOR} in 35 | ukvm) 36 | SUFFIX=.ukvm 37 | BASE=mir-runner 38 | ADD="ADD ./ukvm-bin /unikernel/ukvm" 39 | ;; 40 | kvm|qemu) 41 | SUFFIX=.virtio 42 | BASE=mir-runner-qemu 43 | ;; 44 | unix) 45 | SUFFIX= 46 | BASE=mir-runner 47 | ;; 48 | *) 49 | usage 50 | ;; 51 | esac 52 | shift 53 | 54 | BIN=$(mirage describe | awk -- '/^Name/{print $2}')${SUFFIX} 55 | if [ ! -f ${BIN} ]; then 56 | echo error: Unikernel binary \"./${BIN}\" not found. 1>&2 57 | exit 1 58 | fi 59 | if [ "${HYPERVISOR}" = "ukvm" ]; then 60 | if ! ldd ./ukvm-bin | grep -q "not a dynamic executable"; then 61 | echo error: ./ukvm-bin must be statically linked. 1>&2 62 | echo error: Rebuild with \"make UKVM_STATIC=1\" and re-run this command. 1>&2 63 | exit 1 64 | fi 65 | fi 66 | 67 | DOCKERFILE=$(mktemp Dockerfile.XXXXXXXXXX) 68 | cat <${DOCKERFILE} 69 | FROM ${BASE} 70 | ${ADD} 71 | ADD ./${BIN} /unikernel/${BIN} 72 | ENTRYPOINT [ "/runtime/runner", "${HYPERVISOR}", "/unikernel/${BIN}" ] 73 | EOM 74 | BUILDIT="docker build -f ${DOCKERFILE} $@ ." 75 | ${BUILDIT} 76 | if [ $? -ne 0 ]; then 77 | echo error: \"${BUILDIT}\" failed. 1>&2 78 | echo error: Generated Dockerfile left in ${DOCKERFILE}. 1>&2 79 | exit 1 80 | fi 81 | rm -f ${DOCKERFILE} 82 | } 83 | 84 | check_arp() 85 | { 86 | ARP_ACCEPT=$(sysctl -n net.ipv4.conf.docker0.arp_accept) 87 | [ "${ARP_ACCEPT}" = "1" ] && return 88 | cat <&2 89 | >>> WARNING: Run "sysctl -w net.ipv4.conf.docker0.arp_accept=1" to enable 90 | >>> WARNING: gratuitous ARP on the Docker bridge, otherwise you will experience 91 | >>> WARNING: intermittent network failures. 92 | >>> WARNING: Now sleeping 10 seconds before continuing. 93 | EOM 94 | sleep 10 95 | } 96 | 97 | do_run() 98 | { 99 | check_arp 100 | if [ -c /dev/kvm -a -w /dev/kvm ]; then 101 | DEV_KVM="--device /dev/kvm:/dev/kvm" 102 | else 103 | DEV_KVM= 104 | fi 105 | exec docker run --cap-add NET_ADMIN \ 106 | --device /dev/net/tun:/dev/net/tun \ 107 | ${DEV_KVM} \ 108 | "$@" 109 | } 110 | 111 | if [ "$#" -lt 2 ]; then 112 | usage 113 | fi 114 | 115 | case $1 in 116 | run) 117 | shift 118 | do_run "$@" 119 | ;; 120 | build) 121 | shift 122 | do_build "$@" 123 | ;; 124 | *) 125 | usage 126 | ;; 127 | esac 128 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | VENDOR=$(abspath .)/vendor 2 | 3 | # -Wno-cpp needed to silence complaints in libnl3 headers on musl. 4 | CFLAGS=-Wall -Wno-cpp -Werror -O2 -g -std=gnu99 -D_GNU_SOURCE 5 | CFLAGS+=-I$(VENDOR)/install/usr/local/include 6 | CFLAGS+=-I$(VENDOR)/install/usr/local/include/libnl3 7 | LDLIBS+=-L$(VENDOR)/install/usr/local/lib 8 | LDLIBS+=-lnl-route-3 -lnl-3 -lcap-ng -lm 9 | 10 | .PHONY: all 11 | all: runner 12 | 13 | $(VENDOR)/libnl/stamp-build: $(VENDOR)/libnl/Makefile 14 | $(MAKE) -C $(VENDOR)/libnl 15 | $(MAKE) -C $(VENDOR)/libnl DESTDIR=$(VENDOR)/install install 16 | touch $@ 17 | 18 | $(VENDOR)/libnl/Makefile: $(VENDOR)/libnl/configure 19 | cd $(VENDOR)/libnl && CFLAGS=-Wno-cpp ./configure --disable-shared \ 20 | --disable-cli \ 21 | --disable-pthreads 22 | 23 | $(VENDOR)/libnl/configure: 24 | mkdir -p $(VENDOR)/libnl 25 | tar -C $(VENDOR)/libnl --strip-components=1 \ 26 | -xzf $(VENDOR)/libnl-3.2.25.tar.gz 27 | 28 | $(VENDOR)/libcap-ng/stamp-build: $(VENDOR)/libcap-ng/Makefile 29 | $(MAKE) -C $(VENDOR)/libcap-ng 30 | $(MAKE) -C $(VENDOR)/libcap-ng DESTDIR=$(VENDOR)/install install 31 | touch $@ 32 | 33 | $(VENDOR)/libcap-ng/Makefile: $(VENDOR)/libcap-ng/configure 34 | cd $(VENDOR)/libcap-ng && ./configure --disable-shared \ 35 | --without-python --without-python3 36 | 37 | $(VENDOR)/libcap-ng/configure: 38 | mkdir -p $(VENDOR)/libcap-ng 39 | tar -C $(VENDOR)/libcap-ng --strip-components=1 \ 40 | -xzf $(VENDOR)/libcap-ng-0.7.8.tar.gz 41 | 42 | runner.o: $(VENDOR)/libnl/stamp-build $(VENDOR)/libcap-ng/stamp-build 43 | 44 | runner: runner.o ptrvec.o 45 | $(CC) $(CFLAGS) -static -o $@ runner.o ptrvec.o $(LDLIBS) 46 | 47 | .PHONY: clean 48 | clean: 49 | $(RM) runner runner.o ptrvec.o 50 | -$(MAKE) -C $(VENDOR)/libnl clean 51 | -$(MAKE) -C $(VENDOR)/libcap-ng clean 52 | $(RM) $(VENDOR)/libnl/stamp-build 53 | $(RM) $(VENDOR)/libcap-ng/stamp-build 54 | $(RM) -r $(VENDOR)/install 55 | 56 | .PHONY: distclean 57 | distclean: clean 58 | $(RM) -r $(VENDOR)/libnl/ $(VENDOR)/libcap-ng/ 59 | -------------------------------------------------------------------------------- /src/ptrvec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file comes from the jsoncvt-1.0.9 distribution, and is covered by 3 | * the following license: 4 | * 5 | * Copyright ⓒ 2014, 2015 Robert S. Krzaczek. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining 8 | * a copy of this software and associated documentation files (the 9 | * “Software”), to deal in the Software without restriction, including 10 | * without limitation the rights to use, copy, modify, merge, publish, 11 | * distribute, sublicense, and/or sell copies of the Software, and to 12 | * permit persons to whom the Software is furnished to do so, subject to 13 | * the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | * IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY 22 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | #define _POSIX_C_SOURCE 200112L 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "ptrvec.h" 32 | 33 | enum { 34 | /** Ptrvecs start out with space for this many pointers. The number 35 | * is pretty much arbitrary; if you think all of your ptrvecs are 36 | * going to be extensive, free free to bump this value up to a 37 | * bigger initial size to reduce the load on realloc(3). */ 38 | pv_initial_size = 8 39 | }; 40 | 41 | /** Allocate a new ptrvec from the heap, initialize it as zero, and 42 | * return a pointer to it. */ 43 | ptrvec * 44 | pvnew() 45 | { 46 | ptrvec *p = malloc( sizeof( *p )); 47 | *p = (ptrvec){0}; 48 | return p; 49 | } 50 | 51 | /** If the supplied ptrvec has any storage allocated, return it to the 52 | * heap. The ptrvec itself is not freed. Whereas pvdel() is useful for 53 | * entirely heap-based objects (typically obtained from pvnew()), 54 | * pvclear() is useful at tne end of functions that use a stack-based 55 | * ptrvec object. */ 56 | ptrvec * 57 | pvclear( ptrvec *pv ) 58 | { 59 | if( !pv ) 60 | return 0; 61 | if( pv->p ) 62 | free( pv->p ); 63 | *pv = (ptrvec){ 0 }; 64 | return pv; 65 | } 66 | 67 | /** Return a new void** that is a copy of the one we've been building 68 | * in our ptrvec. Unlike our member p, this one will be allocated from 69 | * the heap and contains just enough space to hold the current 70 | * contents of p including its terminating null. */ 71 | void ** 72 | pvdup( const ptrvec *pv ) 73 | { 74 | void **v; 75 | 76 | if( !pv ) { 77 | v = malloc( sizeof( *v )); 78 | *v = 0; 79 | } else { 80 | size_t nb = sizeof( void* ) * ( pv->len + 1 ); 81 | v = malloc( nb ); 82 | if( pv->p ) 83 | memcpy( v, pv->p, nb ); 84 | else 85 | memset( v, 0, nb ); 86 | } 87 | 88 | return v; 89 | } 90 | 91 | /** A wrapper for the common case at the end of working with a ptrvec. 92 | * Return a null terminated void** ready for storage somewhere, and 93 | * kill our own storage so that the next thing to come along can use 94 | * our memory. */ 95 | void ** 96 | pvfinal( ptrvec *pv ) 97 | { 98 | void **v = pvdup( pv ); 99 | pvclear( pv ); 100 | return v; 101 | } 102 | 103 | /** Return a ptrvec and its pointers to the head. Once called, the 104 | * supplied pointer is no longer valid. Memory at this old 105 | * ptrvec is zeroed prior to being freed. */ 106 | void 107 | pvdel( ptrvec *pv ) 108 | { 109 | if( pv ) 110 | free( pvclear( pv )); 111 | } 112 | 113 | /** Force the supplied ptrvec to contain exactly some number of 114 | * pointers. */ 115 | ptrvec * 116 | pvsize( ptrvec *pv, size_t sz ) 117 | { 118 | if( !sz ) 119 | return pvclear( pv ); 120 | 121 | pv->p = realloc( pv->p, ( pv->sz = sz ) * sizeof( *pv->p )); 122 | if( pv->len >= pv->sz ) { 123 | pv->len = pv->sz - 1; 124 | pv->p[ pv->len ] = 0; 125 | } 126 | return pv; 127 | } 128 | 129 | /** Ensures that the supplied ptrvec has at least some number of 130 | * pointers. If it doesn't, the region of pointers in the ptrvec are 131 | * reallocated. Unlike pvsize(), pvensure() grows the ptrvec in a way 132 | * that hopefully avoids constant reallocations. */ 133 | ptrvec * 134 | pvensure( ptrvec *pv, size_t sz ) 135 | { 136 | size_t newsz; 137 | 138 | if( !pv ) 139 | return 0; 140 | else if( !sz || sz <= pv->sz ) 141 | return pv; 142 | else if( !pv->sz && sz <= pv_initial_size ) 143 | return pvsize( pv, pv_initial_size ); 144 | 145 | /* Choose the next size up for this ptrvec as either 150% of its 146 | current size, or if that's not big enough, 150% of the 147 | requested size. Either is meant to add enough padding so that 148 | we hopefully don't come back here too soon. */ 149 | newsz = pv->sz * 3 / 2; 150 | if( newsz < sz ) 151 | newsz = sz * 3 / 2; 152 | 153 | /* Imperfect, but should catch most overflows, when newsz has 154 | rolled past SIZE_MAX. */ 155 | if( newsz < pv->sz ) 156 | errx( 1, "ptrvec overflow" ); 157 | 158 | return pvsize( pv, newsz ); 159 | } 160 | 161 | /** Add a pointer to the end of the set of pointers managed in this 162 | * ptrvec. The size of the region is managed. The sz might grow a lot, 163 | * but len will only ever grow by one. */ 164 | ptrvec * 165 | pvadd( ptrvec *pv, void *v ) 166 | { 167 | pvensure( pv, pv->len + 2 ); 168 | pv->p[ pv->len++ ] = v; 169 | pv->p[ pv->len ] = 0; 170 | return pv; 171 | } 172 | -------------------------------------------------------------------------------- /src/ptrvec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file comes from the jsoncvt-1.0.9 distribution, and is covered by 3 | * the following license: 4 | * 5 | * Copyright ⓒ 2014, 2015 Robert S. Krzaczek. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining 8 | * a copy of this software and associated documentation files (the 9 | * “Software”), to deal in the Software without restriction, including 10 | * without limitation the rights to use, copy, modify, merge, publish, 11 | * distribute, sublicense, and/or sell copies of the Software, and to 12 | * permit persons to whom the Software is furnished to do so, subject to 13 | * the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | * IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY 22 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | #ifndef jsoncvt_ptrvec_h 27 | #define jsoncvt_ptrvec_h 28 | #pragma once 29 | #include 30 | 31 | /** ptrvec is just used to make creating pointer-to-pointer lists 32 | * (like an argv) easy to build. It automatically manages its memory, 33 | * reallocating when necessary, and so on. 34 | * 35 | * Expected usage is something like 36 | * 37 | * 1. Obtain new ptrvec via pvnew(), or initialize one to all zeroes. 38 | * 39 | * 2. Use pvadd() to add pointers to the vector. The underlying 40 | * vector is always null terminated, even while building, so you can 41 | * access the in-progress vector safely via p. 42 | * 43 | * 3. Use pvdup() to create a new void** that is exactly the size 44 | * needed for the resulting string, or use pvfinal() below. The 45 | * elements of the vector are just copied; only the vector itself is 46 | * allocated anew. 47 | * 48 | * 4. Free up any current space space via pvclear(), resetting things 49 | * to "empty" again. Set a hard size via pvsize(). 50 | * 51 | * 5. if you called pvnew() earlier, call pvdel() to free it. If you 52 | * just want to free up the memory it uses but not the ptrvec itself, 53 | * call pvclear(). pvfinal() combines both pvdup() and pvclear(). */ 54 | typedef struct ptrvec { 55 | /** A table of pointers to anything living here. The number of 56 | * actual pointers allocated is tracked in sz, and the number of 57 | * active pointers is tracked in len. */ 58 | void **p; 59 | 60 | /** How many of the pointers at p are in use? */ 61 | size_t len; 62 | 63 | /** How many pointers have been allocated at p? */ 64 | size_t sz; 65 | } ptrvec; 66 | 67 | extern ptrvec *pvnew(); 68 | extern ptrvec *pvclear( ptrvec * ); 69 | extern void **pvfinal( ptrvec * ); 70 | extern void pvdel( ptrvec * ); 71 | extern void **pvdup( const ptrvec * ); 72 | extern ptrvec *pvsize( ptrvec *, size_t ); 73 | extern ptrvec *pvadd( ptrvec *, void * ); 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /src/runner.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Martin Lucina 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 14 | * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | #include "ptrvec.h" 43 | 44 | /* Container-side network interface to use */ 45 | #define VETH_LINK_NAME "eth0" 46 | /* Name of bridge interface to create */ 47 | #define BRIDGE_LINK_NAME "br0" 48 | /* Name of tap interface to create */ 49 | #define TAP_LINK_NAME "tap0" 50 | /* Buffer size large enough to hold IPv4 adress with CIDR prefix */ 51 | #define AF_INET_BUFSIZE 19 52 | 53 | /* 54 | * Create a tap interface. Returns 0 if successful, system errno if not. 55 | * 56 | * If fd_out is NULL then creates a persistent interface, otherwise 57 | * returns the tap fd as *fd_out. 58 | */ 59 | static int create_tap_link(const char *name, int *fd_out) 60 | { 61 | struct ifreq ifr; 62 | int fd; 63 | 64 | if (strlen(name) > IFNAMSIZ) 65 | return ENAMETOOLONG; 66 | 67 | fd = open("/dev/net/tun", O_RDWR); 68 | if (fd < 0) 69 | return errno; 70 | 71 | ifr.ifr_flags = IFF_TAP | IFF_NO_PI; 72 | strncpy(ifr.ifr_name, name, IFNAMSIZ); 73 | if (ioctl(fd, TUNSETIFF, &ifr) < 0) 74 | return errno; 75 | 76 | if (fd_out) { 77 | *fd_out = fd; 78 | } else { 79 | if (ioctl(fd, TUNSETPERSIST, 1) < 0) 80 | return errno; 81 | 82 | close(fd); 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | /* 89 | * Create a bridge interface. Returns 0 if successful, libnl error if not. 90 | */ 91 | static int create_bridge_link(struct nl_sock *sk, const char *name) 92 | { 93 | struct rtnl_link *l_bridge; 94 | int err; 95 | 96 | l_bridge = rtnl_link_bridge_alloc(); 97 | assert(l_bridge); 98 | rtnl_link_set_name(l_bridge, name); 99 | 100 | err = rtnl_link_add(sk, l_bridge, NLM_F_CREATE); 101 | if (err < 0) { 102 | rtnl_link_put(l_bridge); 103 | return err; 104 | } 105 | 106 | rtnl_link_put(l_bridge); 107 | return 0; 108 | } 109 | 110 | static void match_first_addr(struct nl_object *obj, void *arg) 111 | { 112 | static int found = 0; 113 | struct nl_addr **addr = (struct nl_addr **)arg; 114 | 115 | if (found) 116 | return; 117 | 118 | *addr = rtnl_addr_get_local((struct rtnl_addr *)obj); 119 | nl_addr_get(*addr); /* Found, keep reference */ 120 | found = 1; 121 | } 122 | 123 | /* 124 | * Get the first AF_INET address on 'link'. Returns 0 if successful. Caller 125 | * must release reference to *addr. 126 | */ 127 | static int get_link_inet_addr(struct nl_sock *sk, struct rtnl_link *link, 128 | struct nl_addr **addr) 129 | { 130 | struct nl_cache *addr_cache; 131 | int err; 132 | err = rtnl_addr_alloc_cache(sk, &addr_cache); 133 | if (err < 0) { 134 | warnx("rtnl_addr_alloc_cache() failed: %s", nl_geterror(err)); 135 | return 1; 136 | } 137 | 138 | /* Retrieve the first AF_INET address on the requested interface. */ 139 | struct rtnl_addr *filter; 140 | filter = rtnl_addr_alloc(); 141 | assert(filter); 142 | rtnl_addr_set_ifindex(filter, rtnl_link_get_ifindex(link)); 143 | rtnl_addr_set_family(filter, AF_INET); 144 | 145 | *addr = NULL; 146 | nl_cache_foreach_filter(addr_cache, (struct nl_object *)filter, 147 | match_first_addr, addr); 148 | if (*addr == NULL) { 149 | warnx("No AF_INET address found on veth"); 150 | 151 | rtnl_addr_put(filter); 152 | nl_cache_free(addr_cache); 153 | return 1; 154 | } 155 | 156 | rtnl_addr_put(filter); 157 | nl_cache_free(addr_cache); 158 | return 0; 159 | } 160 | 161 | static void match_first_nh_gw(struct nl_object *obj, void *arg) 162 | { 163 | static int found = 0; 164 | struct rtnl_route *route = (struct rtnl_route *)obj; 165 | struct nl_addr **gw = (struct nl_addr **)arg; 166 | 167 | if (found) 168 | return; 169 | 170 | struct rtnl_nexthop *nh = rtnl_route_nexthop_n(route, 0); 171 | if (nh == NULL) 172 | return; 173 | *gw = rtnl_route_nh_get_gateway(nh); 174 | nl_addr_get(*gw); /* Found, keep reference */ 175 | found = 1; 176 | } 177 | 178 | /* 179 | * Get the nexthop for the first default AF_INET route. Sets 180 | * *addr if found, caller must release reference to *addr. Returns 1 if an 181 | * error occurs, NULL *addr if no error but no AF_INET default route exists. 182 | */ 183 | static int get_default_gw_inet_addr(struct nl_sock *sk, struct nl_addr **addr) 184 | { 185 | struct nl_cache *route_cache; 186 | int err; 187 | err = rtnl_route_alloc_cache(sk, AF_INET, 0, &route_cache); 188 | if (err < 0) { 189 | warnx("rtnl_addr_alloc_cache() failed: %s", nl_geterror(err)); 190 | return 1; 191 | } 192 | 193 | /* Retrieve the first AF_INET default route. */ 194 | struct rtnl_route *filter; 195 | filter = rtnl_route_alloc(); 196 | assert(filter); 197 | rtnl_route_set_type(filter, 1); /* XXX RTN_UNICAST from linux/rtnetlink.h */ 198 | struct nl_addr *filter_addr; 199 | err = nl_addr_parse("default", AF_INET, &filter_addr); 200 | if (err < 0) { 201 | warnx("nl_addr_parse(default) failed: %s", nl_geterror(err)); 202 | 203 | rtnl_route_put(filter); 204 | nl_cache_free(route_cache); 205 | return 1; 206 | } 207 | rtnl_route_set_dst(filter, filter_addr); 208 | 209 | *addr = NULL; 210 | nl_cache_foreach_filter(route_cache, (struct nl_object *)filter, 211 | match_first_nh_gw, addr); 212 | 213 | /* No default gateway is not an error, so always return 0 here */ 214 | nl_addr_put(filter_addr); 215 | rtnl_route_put(filter); 216 | nl_cache_free(route_cache); 217 | return 0; 218 | } 219 | 220 | /* 221 | * Generate a random, locally-administered, unicast MAC address and return 222 | * a pointer to an allocated string representation of it or NULL if an error 223 | * occured. 224 | */ 225 | static char *generate_mac(void) 226 | { 227 | int fd = open("/dev/urandom", O_RDONLY); 228 | if (fd == -1) { 229 | warn("error: Could not open /dev/urandom"); 230 | return NULL; 231 | } 232 | 233 | unsigned char guest_mac[6]; 234 | int rc = read(fd, guest_mac, sizeof(guest_mac)); 235 | assert(rc == sizeof(guest_mac)); 236 | close(fd); 237 | guest_mac[0] &= 0xfe; 238 | guest_mac[0] |= 0x02; 239 | 240 | char *str_mac; 241 | rc = asprintf(&str_mac, "%02x:%02x:%02x:%02x:%02x:%02x", 242 | guest_mac[0], guest_mac[1], guest_mac[2], 243 | guest_mac[3], guest_mac[4], guest_mac[5]); 244 | assert(rc != -1); 245 | return str_mac; 246 | } 247 | 248 | int main(int argc, char *argv[]) 249 | { 250 | char *unikernel; 251 | enum { 252 | QEMU, 253 | KVM, 254 | UKVM, 255 | UNIX 256 | } hypervisor; 257 | 258 | if (argc < 3) { 259 | fprintf(stderr, "usage: runner HYPERVISOR UNIKERNEL [ ARGS... ]\n"); 260 | fprintf(stderr, "HYPERVISOR: qemu | kvm | ukvm | unix\n"); 261 | return 1; 262 | } 263 | if (strcmp(argv[1], "qemu") == 0) 264 | hypervisor = QEMU; 265 | else if (strcmp(argv[1], "kvm") == 0) 266 | hypervisor = KVM; 267 | else if (strcmp(argv[1], "ukvm") == 0) 268 | hypervisor = UKVM; 269 | else if (strcmp(argv[1], "unix") == 0) 270 | hypervisor = UNIX; 271 | else { 272 | warnx("error: Invalid hypervisor: %s", argv[1]); 273 | return 1; 274 | } 275 | unikernel = argv[2]; 276 | /* 277 | * Remaining arguments are to be passed on to the unikernel. 278 | */ 279 | argv += 3; 280 | argc -= 3; 281 | 282 | /* 283 | * Check we have CAP_NET_ADMIN. 284 | */ 285 | if (capng_get_caps_process() != 0) { 286 | warnx("error: capng_get_caps_process() failed"); 287 | return 1; 288 | } 289 | if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_NET_ADMIN)) { 290 | warnx("error: CAP_NET_ADMIN is required"); 291 | return 1; 292 | } 293 | 294 | /* 295 | * Connect to netlink, load link cache from kernel. 296 | */ 297 | struct nl_sock *sk; 298 | struct nl_cache *link_cache; 299 | int err; 300 | 301 | sk = nl_socket_alloc(); 302 | assert(sk); 303 | err = nl_connect(sk, NETLINK_ROUTE); 304 | if (err < 0) { 305 | warnx("nl_connect() failed: %s", nl_geterror(err)); 306 | return 1; 307 | } 308 | err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache); 309 | if (err < 0) { 310 | warnx("rtnl_link_alloc_cache() failed: %s", nl_geterror(err)); 311 | return 1; 312 | } 313 | 314 | /* 315 | * Retrieve container network configuration -- IP address and 316 | * default gateway. 317 | */ 318 | struct rtnl_link *l_veth; 319 | l_veth = rtnl_link_get_by_name(link_cache, VETH_LINK_NAME); 320 | if (l_veth == NULL) { 321 | warnx("error: Could not get link information for %s", VETH_LINK_NAME); 322 | return 1; 323 | } 324 | struct nl_addr *veth_addr; 325 | err = get_link_inet_addr(sk, l_veth, &veth_addr); 326 | if (err) { 327 | warnx("error: Unable to determine IP address of %s", 328 | VETH_LINK_NAME); 329 | return 1; 330 | } 331 | struct nl_addr *gw_addr; 332 | err = get_default_gw_inet_addr(sk, &gw_addr); 333 | if (err) { 334 | warnx("error: get_deGfault_gw_inet_addr() failed"); 335 | return 1; 336 | } 337 | if (gw_addr == NULL) { 338 | warnx("error: No default gateway found. This is currently " 339 | "not supported"); 340 | return 1; 341 | } 342 | 343 | /* 344 | * Create bridge and tap interface, enslave veth and tap interfaces to 345 | * bridge. 346 | */ 347 | err = create_bridge_link(sk, BRIDGE_LINK_NAME); 348 | if (err < 0) { 349 | warnx("create_bridge_link(%s) failed: %s", BRIDGE_LINK_NAME, 350 | nl_geterror(err)); 351 | return 1; 352 | } 353 | int tap_fd; 354 | 355 | if (hypervisor == UKVM) 356 | err = create_tap_link(TAP_LINK_NAME, &tap_fd); 357 | else 358 | err = create_tap_link(TAP_LINK_NAME, NULL); 359 | if (err != 0) { 360 | warnx("create_tap_link(%s) failed: %s", TAP_LINK_NAME, strerror(err)); 361 | return 1; 362 | } 363 | 364 | /* Refill link cache with newly-created interfaces */ 365 | nl_cache_refill(sk, link_cache); 366 | 367 | struct rtnl_link *l_bridge; 368 | l_bridge = rtnl_link_get_by_name(link_cache, BRIDGE_LINK_NAME); 369 | if (l_bridge == NULL) { 370 | warnx("error: Could not get link information for %s", BRIDGE_LINK_NAME); 371 | return 1; 372 | } 373 | struct rtnl_link *l_tap; 374 | l_tap = rtnl_link_get_by_name(link_cache, TAP_LINK_NAME); 375 | if (l_tap == NULL) { 376 | warnx("error: Could not get link information for %s", TAP_LINK_NAME); 377 | return 1; 378 | } 379 | err = rtnl_link_enslave(sk, l_bridge, l_veth); 380 | if (err < 0) { 381 | warnx("error: Unable to enslave %s to %s: %s", VETH_LINK_NAME, 382 | BRIDGE_LINK_NAME, nl_geterror(err)); 383 | return 1; 384 | } 385 | err = rtnl_link_enslave(sk, l_bridge, l_tap); 386 | if (err < 0) { 387 | warnx("error: Unable to enslave %s to %s: %s", TAP_LINK_NAME, 388 | BRIDGE_LINK_NAME, nl_geterror(err)); 389 | return 1; 390 | } 391 | 392 | /* 393 | * Flush all IPv4 addresses from the veth interface. This is now safe 394 | * as we are good to commit and have retrieved the existing configuration. 395 | */ 396 | struct rtnl_addr *flush_addr; 397 | flush_addr = rtnl_addr_alloc(); 398 | assert(flush_addr); 399 | rtnl_addr_set_ifindex(flush_addr, rtnl_link_get_ifindex(l_veth)); 400 | rtnl_addr_set_family(flush_addr, AF_INET); 401 | rtnl_addr_set_local(flush_addr, veth_addr); 402 | err = rtnl_addr_delete(sk, flush_addr, 0); 403 | if (err < 0) { 404 | warnx("error: Could not flush addresses on %s: %s", VETH_LINK_NAME, 405 | nl_geterror(err)); 406 | return 1; 407 | } 408 | rtnl_addr_put(flush_addr); 409 | 410 | /* 411 | * Bring up the tap and bridge interfaces. 412 | */ 413 | struct rtnl_link *l_up; 414 | l_up = rtnl_link_alloc(); 415 | assert(l_up); 416 | /* You'd think set_operstate was the thing to do here. It's not. */ 417 | rtnl_link_set_flags(l_up, IFF_UP); 418 | err = rtnl_link_change(sk, l_tap, l_up, 0); 419 | if (err < 0) { 420 | warnx("error: rtnl_link_change(%s, UP) failed: %s", TAP_LINK_NAME, 421 | nl_geterror(err)); 422 | return 1; 423 | } 424 | err = rtnl_link_change(sk, l_bridge, l_up, 0); 425 | if (err < 0) { 426 | warnx("error: rtnl_link_change(%s, UP) failed: %s", BRIDGE_LINK_NAME, 427 | nl_geterror(err)); 428 | return 1; 429 | } 430 | rtnl_link_put(l_up); 431 | 432 | /* 433 | * Collect network configuration data. 434 | */ 435 | char ip[AF_INET_BUFSIZE]; 436 | if (inet_ntop(AF_INET, nl_addr_get_binary_addr(veth_addr), ip, 437 | sizeof ip) == NULL) { 438 | perror("inet_ntop()"); 439 | return 1; 440 | } 441 | char uarg_ip[AF_INET_BUFSIZE]; 442 | unsigned int prefixlen = nl_addr_get_prefixlen(veth_addr); 443 | snprintf(uarg_ip, sizeof uarg_ip, "%s/%u", ip, prefixlen); 444 | 445 | char uarg_gw[AF_INET_BUFSIZE]; 446 | if (inet_ntop(AF_INET, nl_addr_get_binary_addr(gw_addr), uarg_gw, 447 | sizeof uarg_gw) == NULL) { 448 | perror("inet_ntop()"); 449 | return 1; 450 | } 451 | 452 | /* 453 | * Build unikernel and hypervisor arguments. 454 | */ 455 | ptrvec* uargpv = pvnew(); 456 | char *uarg_buf; 457 | /* 458 | * QEMU/KVM: 459 | * /usr/bin/qemu-system-x86_64 -kernel -append "" 460 | */ 461 | if (hypervisor == QEMU || hypervisor == KVM) { 462 | pvadd(uargpv, "/usr/bin/qemu-system-x86_64"); 463 | pvadd(uargpv, "-nodefaults"); 464 | pvadd(uargpv, "-no-acpi"); 465 | pvadd(uargpv, "-display"); 466 | pvadd(uargpv, "none"); 467 | pvadd(uargpv, "-serial"); 468 | pvadd(uargpv, "stdio"); 469 | pvadd(uargpv, "-m"); 470 | pvadd(uargpv, "512"); 471 | if (hypervisor == KVM) { 472 | pvadd(uargpv, "-enable-kvm"); 473 | pvadd(uargpv, "-cpu"); 474 | pvadd(uargpv, "host"); 475 | } 476 | else { 477 | /* 478 | * Required for AESNI use in Mirage. 479 | */ 480 | pvadd(uargpv, "-cpu"); 481 | pvadd(uargpv, "Westmere"); 482 | } 483 | pvadd(uargpv, "-device"); 484 | char *guest_mac = generate_mac(); 485 | assert(guest_mac); 486 | err = asprintf(&uarg_buf, "virtio-net-pci,netdev=n0,mac=%s", guest_mac); 487 | assert(err != -1); 488 | pvadd(uargpv, uarg_buf); 489 | pvadd(uargpv, "-netdev"); 490 | err = asprintf(&uarg_buf, "tap,id=n0,ifname=%s,script=no,downscript=no", 491 | TAP_LINK_NAME); 492 | assert(err != -1); 493 | pvadd(uargpv, uarg_buf); 494 | pvadd(uargpv, "-kernel"); 495 | pvadd(uargpv, unikernel); 496 | pvadd(uargpv, "-append"); 497 | /* 498 | * TODO: Replace any occurences of ',' with ',,' in -append, because 499 | * QEMU arguments are insane. 500 | */ 501 | char cmdline[1024]; 502 | char *cmdline_p = cmdline; 503 | size_t cmdline_free = sizeof cmdline; 504 | for (; *argv; argc--, argv++) { 505 | size_t alen = snprintf(cmdline_p, cmdline_free, "%s%s", *argv, 506 | (argc > 1) ? " " : ""); 507 | if (alen >= cmdline_free) { 508 | warnx("error: Command line too long"); 509 | return 1; 510 | } 511 | cmdline_free -= alen; 512 | cmdline_p += alen; 513 | } 514 | size_t alen = snprintf(cmdline_p, cmdline_free, 515 | "--ipv4=%s --ipv4-gateway=%s", uarg_ip, uarg_gw); 516 | if (alen >= cmdline_free) { 517 | warnx("error: Command line too long"); 518 | return 1; 519 | } 520 | pvadd(uargpv, cmdline); 521 | } 522 | /* 523 | * UKVM: 524 | * /unikernel/ukvm -- 525 | */ 526 | else if (hypervisor == UKVM) { 527 | pvadd(uargpv, "/unikernel/ukvm"); 528 | err = asprintf(&uarg_buf, "--net=@%d", tap_fd); 529 | assert(err != -1); 530 | pvadd(uargpv, uarg_buf); 531 | pvadd(uargpv, "--"); 532 | pvadd(uargpv, unikernel); 533 | for (; *argv; argc--, argv++) { 534 | pvadd(uargpv, *argv); 535 | } 536 | err = asprintf(&uarg_buf, "--ipv4=%s", uarg_ip); 537 | assert(err != -1); 538 | pvadd(uargpv, uarg_buf); 539 | err = asprintf(&uarg_buf, "--ipv4-gateway=%s", uarg_gw); 540 | assert(err != -1); 541 | pvadd(uargpv, uarg_buf); 542 | } 543 | /* 544 | * UNIX: 545 | * 546 | */ 547 | else if (hypervisor == UNIX) { 548 | pvadd(uargpv, unikernel); 549 | err = asprintf(&uarg_buf, "--interface=%s", TAP_LINK_NAME); 550 | assert(err != -1); 551 | pvadd(uargpv, uarg_buf); 552 | for (; *argv; argc--, argv++) { 553 | pvadd(uargpv, *argv); 554 | } 555 | err = asprintf(&uarg_buf, "--ipv4=%s", uarg_ip); 556 | assert(err != -1); 557 | pvadd(uargpv, uarg_buf); 558 | err = asprintf(&uarg_buf, "--ipv4-gateway=%s", uarg_gw); 559 | assert(err != -1); 560 | pvadd(uargpv, uarg_buf); 561 | } 562 | char **uargv = (char **)pvfinal(uargpv); 563 | 564 | /* 565 | * Done with netlink, free all resources and close socket. 566 | */ 567 | rtnl_link_put(l_veth); 568 | rtnl_link_put(l_bridge); 569 | rtnl_link_put(l_tap); 570 | nl_addr_put(veth_addr); 571 | nl_addr_put(gw_addr); 572 | 573 | nl_cache_free(link_cache); 574 | nl_close(sk); 575 | nl_socket_free(sk); 576 | 577 | /* 578 | * Drop all capabilities except CAP_NET_BIND_SERVICE. 579 | */ 580 | capng_clear(CAPNG_SELECT_BOTH); 581 | capng_update(CAPNG_ADD, 582 | CAPNG_EFFECTIVE | CAPNG_PERMITTED | CAPNG_INHERITABLE, 583 | CAP_NET_BIND_SERVICE); 584 | if (capng_apply(CAPNG_SELECT_BOTH) != 0) { 585 | warnx("error: Could not drop capabilities"); 586 | return 1; 587 | } 588 | 589 | /* 590 | * Run the unikernel. 591 | */ 592 | err = execv(uargv[0], uargv); 593 | warn("error: execv() of %s failed", uargv[0]); 594 | return 1; 595 | } 596 | -------------------------------------------------------------------------------- /src/vendor/libcap-ng-0.7.8.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mato/docker-unikernel-runner/4ddadd350f838014a3ac5b9461544344f46af658/src/vendor/libcap-ng-0.7.8.tar.gz -------------------------------------------------------------------------------- /src/vendor/libnl-3.2.25.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mato/docker-unikernel-runner/4ddadd350f838014a3ac5b9461544344f46af658/src/vendor/libnl-3.2.25.tar.gz -------------------------------------------------------------------------------- /tests/mirage-solo5/Dockerfile.stackv4-qemu: -------------------------------------------------------------------------------- 1 | # Runtime container for Mirage 'stackv4' example (qemu version). 2 | 3 | FROM mir-runner-qemu 4 | ADD mir-stackv4-virtio.tar.gz /unikernel/ 5 | 6 | CMD ["/runtime/runner", "qemu", "/unikernel/stackv4.virtio"] 7 | -------------------------------------------------------------------------------- /tests/mirage-solo5/Dockerfile.stackv4-ukvm: -------------------------------------------------------------------------------- 1 | # Runtime container for Mirage 'stackv4' example (ukvm version). 2 | 3 | FROM mir-runner 4 | ADD mir-stackv4-ukvm.tar.gz /unikernel/ 5 | 6 | CMD ["/runtime/runner", "ukvm", "/unikernel/stackv4.ukvm"] 7 | -------------------------------------------------------------------------------- /tests/mirage-solo5/Dockerfile.stackv4-ukvm-build: -------------------------------------------------------------------------------- 1 | # Build container for Mirage 'stackv4' example (ukvm version). 2 | 3 | # Build distro must match that used to build 'mir-runner'. 4 | FROM ocaml/opam:alpine-3.4_ocaml-4.03.0 5 | 6 | # Mirage/Solo5 is not released yet. 7 | RUN opam repo add mirage-dev git://github.com/mirage/mirage-dev 8 | RUN opam depext -i mirage 9 | RUN git clone -b mirage-dev http://github.com/mirage/mirage-skeleton 10 | WORKDIR /home/opam/mirage-skeleton/stackv4 11 | RUN opam config exec -- mirage configure -t ukvm 12 | RUN opam config exec make depend 13 | RUN opam config exec make 14 | # runner expects ukvm binary in /unikernel/ukvm 15 | RUN cp ./ukvm-bin ./ukvm 16 | 17 | # "Run" phase outputs built unikernel and ukvm as a .tar.gz. 18 | CMD tar -C /home/opam/mirage-skeleton/stackv4 -czh -f - \ 19 | ukvm \ 20 | stackv4.ukvm 21 | -------------------------------------------------------------------------------- /tests/mirage-solo5/Dockerfile.stackv4-virtio-build: -------------------------------------------------------------------------------- 1 | # Build container for Mirage 'stackv4' example (virtio version). 2 | 3 | # Build distro must match that used to build 'mir-runner'. 4 | FROM ocaml/opam:debian-8_ocaml-4.03.0 5 | 6 | # Mirage/Solo5 is not released yet. 7 | RUN opam repo add mirage-dev git://github.com/mirage/mirage-dev 8 | RUN opam depext -i mirage 9 | RUN git clone -b mirage-dev http://github.com/mirage/mirage-skeleton 10 | WORKDIR /home/opam/mirage-skeleton/stackv4 11 | RUN opam config exec -- mirage configure -t virtio 12 | RUN opam config exec make depend 13 | RUN opam config exec make 14 | 15 | # "Run" phase outputs built unikernel as a .tar.gz. 16 | CMD tar -C /home/opam/mirage-skeleton/stackv4 -czh -f - \ 17 | stackv4.virtio 18 | -------------------------------------------------------------------------------- /tests/mirage-solo5/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: mir-stackv4-ukvm mir-stackv4-qemu 3 | 4 | .PHONY: clean clobber 5 | clean: 6 | $(RM) mir-stackv4-ukvm.tar.gz 7 | $(RM) mir-stackv4-virtio.tar.gz 8 | 9 | clobber: clean 10 | -docker rmi -f \ 11 | mir-stackv4-ukvm mir-stackv4-ukvm-build \ 12 | mir-stackv4-qemu mir-stackv4-virtio-build 13 | 14 | .PHONY: run 15 | run: 16 | ./test-stackv4-ukvm.sh 17 | ./test-stackv4-qemu.sh 18 | 19 | # Mirage 'stackv4' sample (ukvm): intermediate build container. 20 | mir-stackv4-ukvm.tar.gz: Dockerfile.stackv4-ukvm-build 21 | docker build -t mir-stackv4-ukvm-build -f Dockerfile.stackv4-ukvm-build . 22 | docker run --rm mir-stackv4-ukvm-build > mir-stackv4-ukvm.tar.gz 23 | 24 | # Mirage 'stackv4' sample (virtio): intermediate build container. 25 | mir-stackv4-virtio.tar.gz: Dockerfile.stackv4-virtio-build 26 | docker build -t mir-stackv4-virtio-build -f Dockerfile.stackv4-virtio-build . 27 | docker run --rm mir-stackv4-virtio-build > mir-stackv4-virtio.tar.gz 28 | 29 | # Mirage 'stackv4' sample (ukvm): mir-stackv4-ukvm. 30 | .PHONY: mir-stackv4-ukvm 31 | mir-stackv4-ukvm: mir-stackv4-ukvm.tar.gz Dockerfile.stackv4-ukvm 32 | docker build -t mir-stackv4-ukvm -f Dockerfile.stackv4-ukvm . 33 | 34 | # Mirage 'stackv4' sample (qemu): mir-stackv4-qemu. 35 | .PHONY: mir-stackv4-qemu 36 | mir-stackv4-qemu: mir-stackv4-virtio.tar.gz Dockerfile.stackv4-qemu 37 | docker build -t mir-stackv4-qemu -f Dockerfile.stackv4-qemu . 38 | -------------------------------------------------------------------------------- /tests/mirage-solo5/test-stackv4-qemu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | docker run -d --name test-mir-stackv4-qemu \ 4 | --device=/dev/net/tun:/dev/net/tun \ 5 | --cap-add=NET_ADMIN mir-stackv4-qemu 6 | IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" test-mir-stackv4-qemu) 7 | echo -n Hello | nc ${IP} 8080 8 | docker logs test-mir-stackv4-qemu | tail -10 9 | docker kill test-mir-stackv4-qemu 10 | docker rm test-mir-stackv4-qemu 11 | 12 | -------------------------------------------------------------------------------- /tests/mirage-solo5/test-stackv4-ukvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | if [ ! \( -c /dev/kvm -a -w /dev/kvm \) ]; then 4 | echo "$0: /dev/kvm not available, not running test" 5 | exit 0 6 | fi 7 | docker run -d --name test-mir-stackv4-ukvm \ 8 | --device=/dev/kvm:/dev/kvm \ 9 | --device=/dev/net/tun:/dev/net/tun \ 10 | --cap-add=NET_ADMIN mir-stackv4-ukvm 11 | IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" test-mir-stackv4-ukvm) 12 | echo -n Hello | nc ${IP} 8080 13 | docker logs test-mir-stackv4-ukvm | tail -10 14 | docker kill test-mir-stackv4-ukvm 15 | docker rm test-mir-stackv4-ukvm 16 | 17 | -------------------------------------------------------------------------------- /tests/mirage-unix/Dockerfile.stackv4: -------------------------------------------------------------------------------- 1 | # Runtime container for Mirage 'stackv4' example. 2 | 3 | FROM mir-runner 4 | ADD mir-stackv4.tar.gz /unikernel/ 5 | 6 | CMD ["/runtime/runner", "unix", "/unikernel/stackv4"] 7 | -------------------------------------------------------------------------------- /tests/mirage-unix/Dockerfile.stackv4-build: -------------------------------------------------------------------------------- 1 | # Build container for Mirage 'stackv4' example. 2 | 3 | # Build distro must match that used to build 'mir-runner'. 4 | FROM ocaml/opam:alpine-3.4_ocaml-4.03.0 5 | 6 | RUN opam repo add mirage-dev git://github.com/mirage/mirage-dev 7 | RUN opam depext -i mirage 8 | RUN git clone -b mirage-dev http://github.com/mirage/mirage-skeleton 9 | WORKDIR /home/opam/mirage-skeleton/stackv4 10 | RUN opam config exec -- mirage configure -t unix 11 | RUN opam config exec make depend 12 | RUN opam config exec make 13 | 14 | # "Run" phase outputs built unikernel as a .tar.gz. 15 | CMD tar -C /home/opam/mirage-skeleton/stackv4 -czh -f - stackv4 16 | -------------------------------------------------------------------------------- /tests/mirage-unix/Dockerfile.static_website: -------------------------------------------------------------------------------- 1 | # Runtime container for Mirage 'static_website' example. 2 | 3 | FROM mir-runner 4 | # mirage-http requires libgmp 5 | RUN apk add --update --no-cache gmp 6 | ADD mir-static_website.tar.gz /unikernel/ 7 | 8 | CMD ["/runtime/runner", "unix", "/unikernel/www"] 9 | -------------------------------------------------------------------------------- /tests/mirage-unix/Dockerfile.static_website-build: -------------------------------------------------------------------------------- 1 | # Build container for Mirage 'static_website' example. 2 | 3 | # Build distro must match that used to build 'mir-runner'. 4 | FROM ocaml/opam:alpine-3.4_ocaml-4.03.0 5 | 6 | RUN opam repo add mirage-dev git://github.com/mirage/mirage-dev 7 | RUN opam depext -i mirage 8 | RUN git clone -b mirage-dev http://github.com/mirage/mirage-skeleton 9 | WORKDIR /home/opam/mirage-skeleton/static_website 10 | RUN opam config exec -- mirage configure -t unix 11 | RUN opam config exec make depend 12 | RUN opam config exec make 13 | 14 | # "Run" phase outputs built unikernel as a .tar.gz. 15 | CMD tar -C /home/opam/mirage-skeleton/static_website -czh -f - www 16 | -------------------------------------------------------------------------------- /tests/mirage-unix/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: mir-stackv4 mir-static_website 3 | 4 | .PHONY: clean clobber 5 | clean: 6 | $(RM) mir-stackv4.tar.gz mir-static_website.tar.gz 7 | 8 | clobber: clean 9 | -docker rmi -f \ 10 | mir-stackv4 mir-stackv4-build \ 11 | mir-static_website mir-static_website-build 12 | 13 | .PHONY: run 14 | run: 15 | ./test-stackv4.sh 16 | ./test-static_website.sh 17 | 18 | # Mirage 'stackv4' sample: intermediate build container. 19 | mir-stackv4.tar.gz: Dockerfile.stackv4-build 20 | docker build -t mir-stackv4-build -f Dockerfile.stackv4-build . 21 | docker run --rm mir-stackv4-build > mir-stackv4.tar.gz 22 | 23 | # Mirage 'stackv4' sample: mir-stackv4. 24 | .PHONY: mir-stackv4 25 | mir-stackv4: mir-stackv4.tar.gz Dockerfile.stackv4 26 | docker build -t mir-stackv4 -f Dockerfile.stackv4 . 27 | 28 | # Mirage 'static_website' sample: intermediate build container. 29 | mir-static_website.tar.gz: Dockerfile.static_website-build 30 | docker build -t mir-static_website-build -f Dockerfile.static_website-build . 31 | docker run --rm mir-static_website-build > mir-static_website.tar.gz 32 | 33 | # Mirage 'static_website' sample: mir-static_website. 34 | .PHONY: mir-static_website 35 | mir-static_website: mir-static_website.tar.gz Dockerfile.static_website 36 | docker build -t mir-static_website -f Dockerfile.static_website . 37 | 38 | -------------------------------------------------------------------------------- /tests/mirage-unix/test-stackv4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | docker run -d --name test-mir-stackv4 --device=/dev/net/tun:/dev/net/tun \ 4 | --cap-add=NET_ADMIN mir-stackv4 5 | IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" test-mir-stackv4) 6 | echo -n Hello | nc ${IP} 8080 7 | docker logs test-mir-stackv4 | tail -10 8 | docker kill test-mir-stackv4 9 | docker rm test-mir-stackv4 10 | 11 | -------------------------------------------------------------------------------- /tests/mirage-unix/test-static_website.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | docker run -d --name test-mir-www --device=/dev/net/tun:/dev/net/tun \ 4 | --cap-add=NET_ADMIN mir-static_website 5 | IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" test-mir-www) 6 | curl -v http://${IP}:8080/ 7 | docker logs test-mir-www | tail -10 8 | docker kill test-mir-www 9 | docker rm test-mir-www 10 | 11 | --------------------------------------------------------------------------------