--------------------------Rumprun Unikernels-------------------------- ...with a hint of network namespaces.
Oh, hello, hello my knowledge junkies! Long time no seen!
I hope you had a nice and sunny Summer. I am just in time for ...
oh, sweet couch potatoes! even X-mas is gone! Obviously, I'm late again...
So allow me to pour some Vodka in my coffee, and we'll be going on a journey:
today we have a small presentation about *apotheotic music* Unikernels!
And not just any kind of Unikernels, Rumprun Unikernels!!1!
(they make everything else feel like a walk in the park).
No need to get scared though, they are full of potential, extraordinary phun to play with,
and even if they are made of C and assembly code (oh, so much win!),
they are amazing even in the cloud (wow, I went full mainstream over here) ;
truth be told, the Cl0ud vendors will fear unikernels the most. Money talks.
And Unikernels will be able (if not already) to trick the bill like a bunch of gangstas going after meth.
Ah, sweet Chaos!
Now, time to get busy! So, grab your baked cookies and warm coffee,
the time has come to feed your hungry mind.
-------------------] Here be dragonZ
Time to quote the Rumprun Wiki:
"The Rumprun unikernel enables running POSIX applications as unikernels on top of
embedded systems and cloud hypervisors such as Xen and KVM."
Uuu..sounds exciting, doesn't it?!
You probably now regret you didn't spend more time with POSIX during this
last summer, huh?
-------------------] Prepare thy Environment
... but wait, what about Docker containers?!
Fear not, I iz here!
For now, let's build a tiny image for our future container:
root@tr0n:/home/buildz# more Dockerfile
FROM ubuntu:16.04
RUN apt-get update -y
RUN apt-get install gcc -y
RUN apt-get update -y && apt-get install git -y
# install a bunch of necessary packages
RUN apt-get install libc6-armel-cross libc6-dev-armel-cross \
binutils-arm-linux-gnueabi \
libncurses5-dev \
qemu-user-static \
inotify-tools qemu -y
RUN apt-get install genisoimage -y
# ...well, more packages
RUN apt-get install -y pkg-config \
&& apt-get install -y openjdk-8-jdk \
&& apt-get install -y cpio \
&& apt-get install -y mercurial \
&& apt-get install -y unzip \
&& apt-get install -y zip \
&& apt-get install zlib1g-dev -y
RUN cd /opt/ && git clone http://repo.rumpkernel.org/rumprun &&\
cd /opt/rumprun &&\
git submodule update --init
WORKDIR /opt/rumprun
#build it
RUN ./build-rr.sh hw
WORKDIR /opt/rumprun
#RUN echo "export PATH=${PATH}:$(pwd)/rumprun/bin" >> /etc/environment
And build it as usual...
root@tr0n:/home/buildz# docker build -t unikernix .
[ .... Wild break to sip more coffee .... ]
...and once it's done, do a flip from excitement, and a check:
root@tr0n:/home/buildz# docker images | grep '.rnix*'
unikernix latest 36280uc0832 2 days ago 2.23GB
...gawd, that's one big image!
I'm onto the next one! Onto the next one!
Now, it's time to build a tiny vicious container, which will host the unikernel.
We should provide some access to devices filesystem (you'll see why...)
root@tr0n:~# docker run --name kek1 --privileged=true \
--hostname=kek1 -v /dev:/dev -ti unikernix /bin/bash
root@kek1:/opt/rumprun#
My environment:
root@kek1:/opt/rumprun# more /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/rumprun"
export PATH=$PATH:rumprun/bin
root@kek1:/opt/rumprun#
And do some checking:
root@kek1:/opt/rumprun#
root@kek1:/opt/rumprun# x[press tab]
x86_64 x86_64-linux-gnu-gcc x86_64-linux-gnu-gcov-tool-5 x86_64-linux-gnu-size xzcat
x86_64-linux-gnu-addr2line x86_64-linux-gnu-gcc-5 x86_64-linux-gnu-gprof x86_64-linux-gnu-strings xzcmp
x86_64-linux-gnu-ar x86_64-linux-gnu-gcc-ar x86_64-linux-gnu-ld x86_64-linux-gnu-strip xzdiff
x86_64-linux-gnu-as x86_64-linux-gnu-gcc-ar-5 x86_64-linux-gnu-ld.bfd x86_64-pc-linux-gnu-pkg-config xzegrep
x86_64-linux-gnu-c++filt x86_64-linux-gnu-gcc-nm x86_64-linux-gnu-ld.gold xargs xzfgrep
x86_64-linux-gnu-cpp x86_64-linux-gnu-gcc-nm-5 x86_64-linux-gnu-nm xauth xzgrep
x86_64-linux-gnu-cpp-5 x86_64-linux-gnu-gcc-ranlib x86_64-linux-gnu-objcopy xdg-user-dir xzless
x86_64-linux-gnu-dwp x86_64-linux-gnu-gcc-ranlib-5 x86_64-linux-gnu-objdump xdg-user-dirs-update xzmore
x86_64-linux-gnu-elfedit x86_64-linux-gnu-gcov x86_64-linux-gnu-pkg-config xjc
x86_64-linux-gnu-g++ x86_64-linux-gnu-gcov-5 x86_64-linux-gnu-ranlib xsubpp
x86_64-linux-gnu-g++-5 x86_64-linux-gnu-gcov-tool x86_64-linux-gnu-readelf xz
root@kek1:/opt/rumprun# x
We are good to go!
root@kek1:/opt/rumprun# mkdir -p rumpish
root@kek1:/opt/rumprun# cd rumpish/
root@kek1:/opt/rumprun/rumpish# export PATH=${PATH}:$(pwd)/rumprun/bin
root@kek1:/opt/rumprun/rumpish#
Our 1337 code (rofl!) that will be used for this small testing:
root@kek1:/opt/rumprun/rumpish# more 44.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/types.h>
#include<netinet/in.h>
char webish[]="HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: 2333\n\n\
<html><head><title>FeelinGumpy</title> <style>body{background-color:#F444FF}</style></head> \
<body><center><h2>Hiya, Gump!</h2><br> \
<img src=\"https://66.media.tumblr.com/090a03541b6464732e5ce44be0c9216f/tumblr_oto03dhbkv1ww0aaho2_500.gif\"> \
</center></body></html>";
int main(int argc, char **argv)
{
struct sockaddr_in server_addr,client_addr;
socklen_t sin_len=sizeof(client_addr);
int server,client;
char buf[666];
int on=1;
server=socket(AF_INET,SOCK_STREAM,0);
setsockopt(server,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=INADDR_ANY;
server_addr.sin_port=htons(80);
bind(server,(struct sockaddr*)&server_addr,sizeof(server_addr))==0;
listen(server,10)==0;
client=accept(server,(struct sockaddr*)&client_addr,&sin_len);
printf("client connection\n");
// close(server);
memset(buf,0,sizeof(buf));
read(client,buf,sizeof(buf)-1);
printf("%s\n",buf);
write(client,webish,sizeof(webish)-1);
// close(client);
// printf("closing....\n");
return 0;
}
For all those OCD fans out-there, when it comes to counting characters, use dirty tricks!
(there's a Content-Length over there!)
root@tr0n:~/chi# tr -d [,] < ciu.txt | wc -m
328
root@tr0n:~/chi#
root@tr0n:~/chi#
-------------------] Time to bake pies!
Let's build our environment:
1) Build:
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# x86_64-rumprun-netbsd-gcc 44.c -o 44
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# ls
44 44.c
root@kek1:/opt/rumprun/rumpish#
2) Bake:
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# rumprun-bake hw_generic 44.bin 44
!!!
!!! NOTE: rumprun-bake is experimental. syntax may change in the future
!!!
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# ls
44 44.bin 44.c
root@kek1:/opt/rumprun/rumpish#
3) ... and run:
Let's test if no errors:
root@kek1:/opt/rumprun/rumpish# rumprun qemu -g '-curses' -i 44.bin
!!!
!!! NOTE: rumprun is experimental. syntax may change in the future
!!!
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
You should see below screen:
Press ESC+2, then q, to get out from qemu.
Noice! Now, provide a network - it must launch on a network - create a tap device:
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# ip tuntap add tap0 mode tap
root@kek1:/opt/rumprun/rumpish# ip addr add 10.0.120.100/24 dev tap0
root@kek1:/opt/rumprun/rumpish# ip link set dev tap0 up
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# ping -c 2 10.0.120.100
PING 10.0.120.100 (10.0.120.100) 56(84) bytes of data.
64 bytes from 10.0.120.100: icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from 10.0.120.100: icmp_seq=2 ttl=64 time=0.083 ms
--- 10.0.120.100 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.044/0.063/0.083/0.021 ms
Now, let's run it again:
rumprun qemu -g '-curses' \
-I if,vioif,'-net tap,script=no,ifname=tap0'\
-W if,inet,static,10.0.120.102/24 \
-i 44.bin
Same purple on black output should appear, with no errors! Ha! ...
Only this time, we provided an IP, and even port 80 declared in our code:
So, from another terminal, login in again onto kek1 container, and check:
root@kek1:/opt/rumprun# curl -v http://10.0.120.102:80
* Rebuilt URL to: http://10.0.120.102:80/
* Trying 10.0.120.102...
* Connected to 10.0.120.102 (10.0.120.102) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.0.120.102
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Content-Length: 2333
<
* transfer closed with 2070 bytes remaining to read
* Closing connection 0
curl: (18) transfer closed with 2070 bytes remaining to read
<html><head><title>FeelinGumpy</title> <style>body{background-color:#F444FF}</style></head> <body><center><h2>Hiya, Gump!</h2><br>
<img src="https://66.media.tumblr.com/090a03541b6464732e5ce44be0c9216f/tumblr_oto03dhbkv1ww0aaho2_500.gif">
</center></body></html>
root@kek1:/opt/rumprun#
It works! Yey!
Yeah, it works... but what's the point?! How can I access the page?
Well, luckily, we can apply port forwarding.
First thing first, install netcat traditional ( apt-get install netcat-traditional )
[From terminal one]- forward trafic from port 80 to 172.17.0.2 (our container) on port 8080
root@kek1:/opt/rumprun/rumpish# nc -l -p 8080 -c 'nc 10.0.120.102 80'
[From terminal two - from vm ] - forward traffic from port 8080 container to localhost vm on port 8666
root@tr0n:/makey# nc -l -p 8666 -c 'nc 172.17.0.2 8080'
You now should be able to access the page content from http://localhost:8666
Now, don't quit qemu, just yet ... and from another terminal, let's see the process:
root@kek1:/opt/rumprun# ps -ef | grep qemu
root 801 776 7 21:50 pts/9 00:00:01 qemu-system-x86_64 -net
nic,model=virtio,macaddr=52:54:00:8d:20:50
-net tap,script=no,ifname=tap0 -no -kvm -m 64 -curses -kernel
44.bin -append {,, ."net" : {,, .."if":.."vioif0",, .."type":."inet",,
.."method":."static",,
.."addr":."10.0.120.102",, .."mask":."24",, .},, ."cmdline": "44.bin",,
},,
root 807 608 0 21:50 pts/11 00:00:00 grep --color=auto qemu
Hmm... this looks like different from how we ran the qemu command.
Let's launch it now with a script based on how that ps output looked like:
root@kek1:/opt/rumprun/rumpish#
root@kek1:/opt/rumprun/rumpish# more run_rumpy.sh
#!/bin/bash
RUMPCONFIG=$(cat <<EOM
{
"rc": [
],
"net": {
"if": "vioif0",
"type": "inet",
"method": "static",
"addr": "10.0.120.102",
"mask": "24" },
"cmdline": "44.bin"
}
EOM
)
RUMP_CONFIG=$(echo "${RUMPCONFIG}" | sed -e 's/,/,,/g' | tr '\n' ' ')
qemu-system-x86_64 \
-no-kvm -m 64 \
-curses \
-kernel 44.bin \
-net nic,model=virtio,macaddr=52:54:00:8d:20:50 \
-net tap,script=no,ifname=tap0 \
-append "${RUMP_CONFIG}"
Provide the execution rights:
root@kek1:/opt/rumprun/rumpish# chmod +x run_rumpy.sh
... and run:
root@kek1:/opt/rumprun/rumpish# ./run_rumpy.sh
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
root@kek1:/opt/rumprun/rumpish#
Magic!
-------------------] No hops, please!
Maybe all that port forwarding isn't quite the ultimate pièce de résistance,
but it surely is handy!
So, we need to take another approach!
-------------------] I need to retreat in my bubble!!!
Here comes network namespaces! And there is a lot ... as in *a lot* to talk about namespaces ...
And no worries, we will take time, and take a better look at them in our next zines.
So we'll just focus a bit on the "ip netns" command.
Briefly put, ip netns will allow you to execute commands in a space that is considered isolated
(in the namespace, that is):
Why would you do this:
root@tr0n:/home/makey# docker exec 7c5c364fea11 cat /sys/devices/virtual/net/eth0/address
02:42:ac:11:00:02
When you can do this:
root@tr0n# ip netns exec 7c5c364fea11 cat /sys/devices/virtual/net/eth0/address
02:42:ac:11:00:02
The laziness:
root@tr0n:/home/makey# ls -ltr /sys/fs/cgroup/devices/docker/ | grep '^d'
drwxr-xr-x 2 root root 0 Feb 3 02:34 7c5c364fea114a125cd49739e25fb88d97d8fbdf832953e9d094473555855ecc
So today, you have to check this small Makefile... run it on the vm's side:
(...it might not work from a first try ... but what does anywayz?!)
root@tr0n:/home/makey# more Makefile
.PHONY: all step_1 step_2 step_3 step_4 step_5
MY_PID=$(shell docker inspect -f '{{.State.Pid}}' kek1)
DOKY_ID=$(shell docker ps | grep kek1 | awk {'print $$1'})
DOKY_VT=$(shell ip netns exec $(DOKY_ID) cat /sys/devices/virtual/net/eth0/address | tr -d ':')
DOKY_VTP=vtap$(shell ip netns exec $(DOKY_ID) cat /sys/devices/virtual/net/eth0/address | tr -d ':' | grep -o '.\{6\}$$' )
DOKY_IP=$(shell ip netns exec $(DOKY_ID) ip addr show dev eth0 | grep 'inet ' | cut -d' ' -f6)
GW=$(shell ip netns exec $(DOKY_ID) ip -o route get 8.8.8.8 | cut -d' ' -f3)
TAPDEV_SYSFS=$(shell ip netns exec $(DOKY_ID) find /sys/devices/virtual/net -name dev -type f)
TAPDEV=$(shell ip netns exec $(DOKY_ID) cat $(TAPDEV_SYSFS))
DOKY_CGRP=/sys/fs/cgroup/devices/docker/7c5c364fea114a125cd49739e25fb88d97d8fbdf832953e9d094473555855ecc
WRITE=$(shell echo "c $(TAPDEV) rwm" >${DOKY_CGRP}/devices.allow)
HEH=$(shell echo $(TAPDEV) | tr : ' ' )
HUE=$(shell docker exec $(DOKY_ID) mknod /dev/tap0 c $(HEH) )
all: step_1 step_2 step_3 step_4 step_5
help:
@echo ""
@echo "-- Help Menu"
@echo ""
@echo " 1. make step_1 - create folder"
@echo " 2. make step_2 - create soft link"
@echo " 3. make step_3 - new interface and dns"
@echo " 4. make step_4 - allow devices"
@echo " 5. make step_5 - create device tap0"
step_1:
@echo "create /var/run/netns"
@mkdir -p /var/run/netns
@echo "ln -sfT /proc/$(MY_PID)/ns/net /var/run/netns/$(DOKY_ID)"
step_2:
@echo "create soft link"
@echo "create softlink /proc/$(MY_PID)/ns/net /var/run/netns/$(DOKY_ID)"
@ln -sfT /proc/$(MY_PID)/ns/net /var/run/netns/$(DOKY_ID)
@echo "show ip"
@ip netns exec $(DOKY_ID) ip -s link show eth0
step_3:
@ip netns exec $(DOKY_ID) ip link add link eth0 name $(DOKY_VTP) type macvtap mode bridge
@ip netns exec $(DOKY_ID) ip link set dev $(DOKY_VTP) up
@ip netns exec $(DOKY_ID) ip -o route get 8.8.8.8 | cut -d' ' -f3
step_4:
@ echo "c ${TAPDEV} rwm" >${DOKY_CGRP}/devices.allow
step_5:
@$(HUE)
check:
@echo "Starting starting container"
@docker ps
@echo "Started as daemon. "
docker_ip_mask:
@echo "$(DOKY_IP)"
pid:
@echo "get PID:"
@(docker inspect -f '{{.State.Pid}}' kek1)
check_vtap:
@echo "check vtap name"
@echo "$(DOKY_VT)"
@echo "$(DOKY_VTP)"
@echo "$(DOKY_CGRP)"
check_gateway:
echo "$(GW)"
docker_pid:
@printf "docker pid $(MY_PID) \n"
list:
@echo "ls /proc/$(MY_PID)"
@ls /proc/$(MY_PID)/
@echo "list /var/run/netns/$(DOKY_ID)"
@ls -ltr /var/run/netns/$(DOKY_ID)
ccc:
@echo "$(HEH)"
Create properties with:
root@tr0n:/home/makey#
root@tr0n:/home/makey# make step_1 step_2 step_3 step_4 step_5
Wild checks:
root@tr0n:/home/makey# make docker_pid
docker pid 32407
root@tr0n:/home/makey#
root@tr0n:/home/makey# make list
ls /proc/32407
attr clear_refs cpuset fd limits mem net oom_score personality schedstat smaps_rollup status timerslack_ns
autogroup cmdline cwd fdinfo loginuid mountinfo ns oom_score_adj projid_map sessionid stack syscall uid_map
auxv comm environ gid_map map_files mounts numa_maps pagemap root setgroups stat task wchan
cgroup coredump_filter exe io maps mountstats oom_adj patch_state sched smaps statm timers
list /var/run/netns/7c5c364fea11
lrwxrwxrwx 1 root root 18 Feb 3 00:45 /var/run/netns/7c5c364fea11 -> /proc/32407/ns/net
root@tr0n:/home/makey# make check_gateway
echo "172.17.0.1"
172.17.0.1
root@tr0n:/home/makey# make docker_ip_mask
172.17.0.2/16
root@tr0n:/home/makey#
And some checkin' on container's side:
root@kek1:/opt/rumprun/rumpish# ls /sys/devices/virtual/net/
eth0 lo vtap110002
root@kek1:/opt/rumprun/rumpish# more /sys/devices/virtual/net/vtap110002/address
c2:1e:2f:b4:3b:41
-------------------] Re-invent the wheel!
So, after applying all the changes with Makefile,
our previous run_rumpish.sh script can be re-written as below script:
root@kek1:/opt/rumprun/rumpish# more run_conty_rumpy.sh
#!/bin/bash
RUMPCONFIG=$(cat <<EOM
{
"env": "DNS_RESOLVER=8.8.8.8",
"rc": [
],
"net": {
"if": "vioif0",
"type": "inet",
"method": "static",
"addr": "172.17.0.2",
"gw" : "172.17.0.1",
"mask": "16"
},
"cmdline": "33.bin"
}
EOM
)
RUMP_CONFIG=$(echo "${RUMPCONFIG}" | sed -e 's/,/,,/g' | tr '\n' ' ')
qemu-system-x86_64 \
-no-kvm -m 500 \
-curses \
-kernel 44.bin \
-net nic,model=virtio,macaddr=c2:1e:2f:b4:3b:41 \
-net tap,fd=3 \
-append "${RUMP_CONFIG}" 3<>/dev/tap0
DNS looks pretty important here, huh?
root@kek1:/opt/rumprun/rumpish# more /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 8.8.8.8
nameserver 8.8.4.4
Gibt rights, and execute:
root@kek1:/opt/rumprun/rumpish# chmod +x run_conty_rumpy.sh
root@kek1:/opt/rumprun/rumpish# ./run_conty_rumpy.sh
Let's see if it works, from another terminal (on the virtual machine, not on container):
root@tr0n:/home/makey# curl -v http://172.17.0.2:80
* Rebuilt URL to: http://172.17.0.2:80/
* Trying 172.17.0.2...
* Connected to 172.17.0.2 (172.17.0.2) port 80 (#0)
> GET / HTTP/1.1
> Host: 172.17.0.2
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
<Content-Type: text/html
<Content-Length: 2333
<
* transfer closed with 2070 bytes remaining to read
* Closing connection 0
curl: (18) transfer closed with 2070 bytes remaining to read
<html><head><title>FeelinGumpy</title> <style>body{background-color:#F444FF}</style></head> <body><center><h2>Hiya, Gump!</h2><br>
<img src="https://66.media.tumblr.com/090a03541b6464732e5ce44be0c9216f/tumblr_oto03dhbkv1ww0aaho2_500.gif">
</center></body></html> |