summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2023-11-10 19:44:48 -0500
committerFreya Murphy <freya@freyacat.org>2023-11-10 19:44:48 -0500
commit8b7fe69ca362bf7f78fee7207ddd10d8697ae02a (patch)
treee9c38ea4589360bafd434603feb6bcd95629556b
downloadfreyanet-8b7fe69ca362bf7f78fee7207ddd10d8697ae02a.tar.gz
freyanet-8b7fe69ca362bf7f78fee7207ddd10d8697ae02a.tar.bz2
freyanet-8b7fe69ca362bf7f78fee7207ddd10d8697ae02a.zip
things
-rw-r--r--Dockerfile12
-rwxr-xr-xbin/config.awk35
-rwxr-xr-xbin/mkwgconfig.sh51
-rwxr-xr-xinet2.initd108
-rwxr-xr-xsetup.sh147
-rwxr-xr-xwait.initd4
6 files changed, 357 insertions, 0 deletions
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..0008476
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,12 @@
+FROM alpine
+RUN apk add --no-cache wireguard-tools bind-tools bird openrc udev-init-scripts-openrc
+COPY ./wait.initd /etc/init.d/wait
+COPY ./inet2.initd /etc/init.d/inet2
+RUN sed -i 's/#rc_sys=""/rc_sys="docker"/' /etc/rc.conf && \
+ rc-update add wait && \
+ rc-update add inet2
+COPY ./setup.sh /setup.sh
+COPY ./bin /usr/local/bin
+
+ENTRYPOINT ["/setup.sh"]
+
diff --git a/bin/config.awk b/bin/config.awk
new file mode 100755
index 0000000..f4833ae
--- /dev/null
+++ b/bin/config.awk
@@ -0,0 +1,35 @@
+#!/run/current-system/profile/bin/awk -f
+
+BEGIN {
+ FS = "[ ]"; # use a single space as field separator and don't trim input
+ ind = 0; # indentation level
+ last = ARGC - 3; # last argument index
+ exitcode = 1; # whether anything has been matched
+ if(last < 0) { # there should be at least one argument after the filename
+ exit 1;
+ }
+ ARGC = 2; # don't read ARGV[2] and onward as files
+}
+
+END {
+ exit exitcode;
+}
+
+$0 != "" { # exit when the indentation block is exited
+ for(i = 0; i < ind; i++) {
+ if(! sub(/^\t/, "")) {
+ exit exitcode;
+ }
+ }
+}
+
+# if on the last argument, interpret it as a key and print the value
+ind == last && $1 == ARGV[ind + 2] {
+ exitcode = 0;
+ print substr($0, length($1) + 2);
+}
+# if not on the last argument, find the string exactly and increment indentation
+ind != last && $0 == ARGV[ind + 2] {
+ ind++;
+}
+
diff --git a/bin/mkwgconfig.sh b/bin/mkwgconfig.sh
new file mode 100755
index 0000000..3afa221
--- /dev/null
+++ b/bin/mkwgconfig.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env sh
+# args: /path/to/interface-config /path/to/output.conf
+
+inter="$1"
+configfile="$3"
+if [ -z "$configfile" ]; then
+ configfile=/run/inet2/inet2.conf
+fi
+
+getval() {
+ /usr/local/bin/config.awk "$configfile" "$@"
+}
+
+k() {
+ while read -r line; do
+ echo "$1 = $line"
+ done
+}
+
+(
+ echo "[Interface]"
+ getval "interface $inter" ListenPort | k ListenPort
+ getval "interface $inter" PrivateKey | k PrivateKey
+ getval PrivateKey | k PrivateKey
+ echo
+
+ getval "interface $inter" peer | while read -r peer; do
+ echo "[Peer]"
+ getval "interface $inter" "peer $peer" PublicKey | k PublicKey
+ getval "interface $inter" "peer $peer" AllowedIPs | k AllowedIPs
+
+ domain="$(getval "interface $inter" "peer $peer" Domain)"
+ if [ -n "$domain" ]; then
+ # it doesn't like domain names in the Endpoint field, so resolve dns here
+ v4="$(dig +short "$domain")"
+ [ ! "$?" = "0" ] && v4=""
+ v6="$(dig +short -t aaaa "$domain")"
+ [ ! "$?" = "0" ] && v6=""
+ if getval "interface $inter" "peer $peer" IPv4; then
+ v6=""
+ fi
+ addr="[$v6]"
+ [ "$addr" = "[]" ] && addr="$v4"
+ echo "Endpoint = $addr:$(getval "interface $inter" "peer $peer" Port)"
+ else
+ getval "interface $inter" "peer $peer" Endpoint | k Endpoint
+ fi
+ getval "interface $inter" "peer $peer" PersistentKeepalive | k PersistentKeepalive
+ echo
+ done
+) > "$2"
diff --git a/inet2.initd b/inet2.initd
new file mode 100755
index 0000000..7824bdc
--- /dev/null
+++ b/inet2.initd
@@ -0,0 +1,108 @@
+#!/sbin/openrc-run
+name="inet2"
+description="Sets up wireguard interfaces connected via the host's internet connection"
+
+extra_started_commands="reloadwg"
+
+run() {
+ printf '$ \x1b[32;1m%s\x1b[0m\n' "$*"
+ "$@"
+}
+step() {
+ printf '\x1b[34;1m>> %s\x1b[0m\n' "$*"
+}
+
+getval() {
+ /usr/local/bin/config.awk /run/inet2/inet2.conf "$@"
+}
+
+runscripts() {
+ if [ -n "$(getval "interface $2" "$1")" ]; then
+ step "Running $1 for $2"
+ getval "interface $2" "$1" | while read -r line; do
+ (eval "$line")
+ done
+ fi
+}
+
+start() {
+ rm -rf /run/inet2/config 2>/dev/null
+ rm -rf /run/inet2/wg 2>/dev/null
+ cp /config/inet2.conf /run/inet2/inet2.conf
+ mkdir /run/inet2/wg
+
+ getval Loopback | while read -r addr; do
+ run ip addr add "$addr" dev lo
+ done
+
+ getval interface | while read -r inter; do
+ step "Generating config for $inter"
+ run mkwgconfig.sh "$inter" /run/inet2/wg/"$inter"
+
+ # create the wireguard interface *in the default namespace*
+ step "Adding Wireguard interface $inter"
+ run ip link add name "$inter" type wireguard
+
+ # set up the new network from the config
+ step "Setting Wireguard config for $inter"
+ run wg setconf "$inter" /run/inet2/wg/"$inter"
+
+ # the config doesn't actually add any addresses, do that here
+ step "Adding host addresses for $inter"
+ getval "interface $inter" Address | while read -r addr; do
+ run ip addr add "$addr" dev "$inter"
+ done
+
+ runscripts PreUp "$inter"
+
+ step "Bringing interface up"
+ run ip link set dev "$inter" up
+
+ getval "interface $inter" Route | while read -r line; do
+ read -r route via addr2 < <(printf "%s" "$line")
+ if [ "$via" = "via" ]; then
+ run ip route add "$route" via "$addr2" dev "$inter"
+ else
+ run ip route add "$route" dev "$inter"
+ fi
+ done
+
+ runscripts PostUp "$inter"
+ done
+ step "Done!"
+}
+
+stop() {
+ if [ -f /run/inet2/inet2.conf ]; then
+ getval Loopback | while read -r addr; do
+ run ip addr del "$addr" dev lo
+ done
+
+ getval interface | while read -r inter; do
+ runscripts PreDown "$inter"
+
+ step "Bringing $inter down"
+ run ip link del "$inter"
+
+ runscripts PostDown "$inter"
+ done
+
+ rm -rf /run/inet2/inet2.conf
+ fi
+}
+
+# just reloads the wireguard configs for existing interfaces
+# for if a peer's domain name resolves to a different ip address now
+# and it needs to be re-resolved without taking down the connection
+reloadwg() {
+ if [ -f /run/inet2/inet2.conf ]; then
+ getval interface | while read -r inter; do
+ step "Generating config for $inter"
+ run mkwgconfig.sh "$inter" /run/inet2/wg/"$inter" /config/inet2.conf
+
+ step "Setting Wireguard config for $inter"
+ run wg setconf "$inter" /run/inet2/wg/"$inter"
+ done
+ fi
+}
+
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000..c03fb10
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,147 @@
+#!/bin/sh
+
+run() {
+ printf '$ \x1b[32;1m%s\x1b[0m\n' "$*"
+ "$@"
+}
+step() {
+ printf '\x1b[34;1m>> %s\x1b[0m\n' "$*"
+}
+
+getval() {
+ /usr/local/bin/config.awk /config/inet2.conf "$@"
+}
+
+haskey() {
+ getval interface | while read -r inter; do
+ if getval "interface $inter" "$1"; then
+ echo "true"
+ return
+ fi
+ done
+}
+
+# ensure the /run/inet2 directory is empty (docker doesn't mount tmpfs to /run)
+# /run/inet2 is used for storage during runtime - restarting the container should clear it
+rm -rf /run/inet2 2>/dev/null
+mkdir /run/inet2
+
+# ensure the /var/lib/inet2 directory exists
+# /var/lib/inet2 is used for storage for the entire lifetime of the container - restarting the container shouldn't clear it
+if [ ! -d /var/lib/inet2 ]; then
+ mkdir -p /var/lib/inet2
+fi
+
+# these are disabled in the docker netns
+step "Enabling IPv6"
+run sysctl net.ipv6.conf.all.disable_ipv6=0 net.ipv6.conf.default.disable_ipv6=0 net.ipv6.conf.all.forwarding=1
+
+ospf="$(haskey OSPF)"
+
+escapebird() {
+ sed -e 's/\\/\\\\/g;s/"/\\"/g'
+}
+
+if [ -n "$ospf" ]; then
+ step "Creating Bird configuration"
+
+ touch /var/log/bird.log
+ chown bird:bird /var/log/bird.log
+
+ selfas=$(getval AS)
+ (
+ cat <<EOF
+log "/var/log/bird.log" all;
+
+$(getval RouterID | while read -r line; do echo "router id $line;"; done)
+
+protocol kernel {
+ ipv4 {
+ export filter { if source ~ [RTS_BGP, RTS_OSPF, RTS_OSPF_IA, RTS_OSPF_EXT1, RTS_OSPF_EXT2] then accept; else reject; };
+ import all;
+ };
+ learn;
+ scan time 10;
+}
+protocol kernel {
+ ipv6 {
+ export filter { if source ~ [RTS_BGP, RTS_OSPF, RTS_OSPF_IA, RTS_OSPF_EXT1, RTS_OSPF_EXT2] then accept; else reject; };
+ import all;
+ };
+ learn;
+ scan time 10;
+}
+protocol device {
+}
+
+protocol direct {
+ ipv4;
+ ipv6;
+}
+
+
+EOF
+ if [ -n "$ospf" ]; then
+ interfacelist=$(
+ echo " area 0 {"
+ echo " interface \"lo\" { stub; };"
+ getval interface | while read -r inter; do
+ val="$(getval "interface $inter" OSPF)"
+ if [ "$?" = "0" ]; then
+ echo " interface \"$(printf "%s" "$inter" | escapebird)\" {"
+ if [ -n "$val" ]; then
+ echo " $val;";
+ fi
+ echo " };"
+ fi
+ done
+ echo " };"
+ )
+
+ cat <<EOF
+protocol ospf v3 ospf4 {
+ ipv4 {
+ import all;
+ export filter { if source ~ [RTS_DEVICE, RTS_INHERIT] then accept; else reject; };
+ };
+$interfacelist
+}
+protocol ospf v3 ospf6 {
+ ipv6 {
+ import all;
+ export filter { if source ~ [RTS_DEVICE, RTS_INHERIT] then accept; else reject; };
+ };
+$interfacelist
+}
+EOF
+ ) > /etc/bird.conf
+ chown root:bird /etc/bird.conf
+ chmod 640 /etc/bird.conf
+
+ step "Enabling BIRD"
+ run rc-update add bird
+fi
+
+if [ ! -f /var/lib/inet2/setupDone ]; then
+ if [ -f /config/setup.sh ]; then
+ step "Running /config/setup.sh"
+ /config/setup.sh
+ fi
+ touch /var/lib/inet2/setupDone
+fi
+
+if [ -f /config/start.sh ]; then
+ step "Running /config/start.sh"
+ /config/start.sh
+fi
+
+if [ "$#" = "0" ]; then
+ step "Starting OpenRC"
+ rm -rf /run/openrc 2>/dev/null
+ mkdir /run/openrc
+ touch /run/openrc/softlevel
+ exec /sbin/openrc
+else
+ "$@"
+fi
+
diff --git a/wait.initd b/wait.initd
new file mode 100755
index 0000000..b033ac1
--- /dev/null
+++ b/wait.initd
@@ -0,0 +1,4 @@
+#!/sbin/openrc-run
+# this container may need openrc to keep running without any services
+# ordinarily that would cause it to stop, so this is a dummy service
+start(){ sleep infinity & }