diff --git a/Dockerfile.rpi b/Dockerfile.rpi new file mode 100644 index 0000000..8688a4d --- /dev/null +++ b/Dockerfile.rpi @@ -0,0 +1,12 @@ +FROM scratch +COPY . . +ARG ROOT_PW +RUN echo -e "${ROOT_PW}\n${ROOT_PW}" | passwd +RUN mkdir -p /var/lock +RUN opkg update && \ + opkg install \ + iperf3 \ + ip-full && \ + opkg list-upgradable | awk '{print $1}' | xargs opkg upgrade + +CMD [ "/sbin/init" ] \ No newline at end of file diff --git a/Makefile b/Makefile index 22b4858..a4c04d2 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,17 @@ -.PHONY: build run clean install uninstall +.PHONY: build build-rpi run clean install uninstall include openwrt.conf +export build: - @docker build \ - --build-arg ROOT_PW=${ROOT_PW} \ - --build-arg OPENWRT_TAG=${OPENWRT_TAG} \ + docker build \ + --build-arg ROOT_PW \ + --build-arg OPENWRT_TAG \ -t ${BUILD_TAG} . +build-rpi: + ./build-rpi.sh ${RPI_SOURCE_IMG} + run: ./run.sh diff --git a/README.md b/README.md index 838c6c6..18c89c8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# OpenWRT in Docker +# OpenWrt in Docker -Inspired by other projects that run `hostapd` in a Docker container. This goes one step further and boots a full network OS intended for embedded devices called [OpenWRT](https://openwrt.org/), so you can manage all aspects of your network from a user-friendly web UI. +Inspired by other projects that run `hostapd` in a Docker container. This goes one step further and boots a full network OS intended for embedded devices called [OpenWrt](https://openwrt.org/), so you can manage all aspects of your network from a user-friendly web UI. -I only tested this on x86_64, but it might work on ARM too with some minor tweaking. +For Raspberry Pi-specific build instructions, see [Building on Raspberry Pi](./rpi.md). ## Dependencies @@ -17,13 +17,13 @@ I only tested this on x86_64, but it might work on ARM too with some minor tweak ``` $ make build ``` -If you want additional OpenWRT packages to be present in the base image, add them to the Dockerfile. Otherwise you can install them with `opkg` after bringing up the container. +If you want additional OpenWrt packages to be present in the base image, add them to the Dockerfile. Otherwise you can install them with `opkg` after bringing up the container. A searchable package list is available on [openwrt.org](https://openwrt.org/packages/table/start). ## Configure -Initial configuration is performed using a config file, `openwrt.conf`. Values read from this file at runtime are used to generate OpenWRT format config files. +Initial configuration is performed using a config file, `openwrt.conf`. Values read from this file at runtime are used to generate OpenWrt format config files. To add or change the base configuration, modify the config templates in `etc/config/
.tpl`. @@ -58,10 +58,8 @@ This will delete the container and all associated Docker networks so you can sta ### Hairpinning -This took a couple of tries to get working. The most challenging issue was getting traffic from WLAN clients to reach each other. -In order for this to work, OpenWRT bridges all interfaces in the LAN zone and sets hairpin mode (aka [reflective relay](https://lwn.net/Articles/347344/)) on the WLAN interface, meaning packets arriving on that interface can be 'reflected' back out through the same interface. -OpenWRT is not able to set this mode from inside the container even with `NET_ADMIN` capabilities, so this must be done from the host. +In order for WLAN clients to see one another, OpenWrt bridges all interfaces in the LAN zone and sets hairpin mode (aka [reflective relay](https://lwn.net/Articles/347344/)) on the WLAN interface, meaning packets arriving on that interface can be 'reflected' back out through the same interface. `run.sh` tries to handle this, and prints a warning if it fails. diff --git a/build-rpi.sh b/build-rpi.sh new file mode 100755 index 0000000..dcd337f --- /dev/null +++ b/build-rpi.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Extracts the rootfs from OpenWRT Raspberry Pi image available from +# https://downloads.openwrt.org/releases/19.07.2/targets/brcm2708/bcm2708/ +# and builds a Docker container out of it +# +# Refer to https://openwrt.org/toh/raspberry_pi_foundation/raspberry_pi +# to choose the right image +# +# If building on x86, you must have qemu-arm and binfmt-support installed +set -e + +IMG=${1:-'x'} + + +mount_rootfs() { + echo "* mounting image" + offset=$(sfdisk -d ${IMG} | grep "${IMG}2" | sed -E 's/.*start=\s+([0-9]+).*/\1/g') + tmpdir=$(mktemp -u -p .) + mkdir -p "${tmpdir}" + sudo mount -o loop,offset=$((512 * $offset)) -t ext4 ${IMG} ${tmpdir} +} + +docker_build() { + echo "* building Docker image" + sudo docker build \ + --build-arg ROOT_PW="${ROOT_PW}" \ + -t ${BUILD_TAG} -f Dockerfile.rpi ${tmpdir} +} + + +cleanup() { + echo "* cleaning up" + sudo umount ${tmpdir} + rm -rf ${tmpdir} +} + +test -f ${IMG} || { echo 'no image file found'; exit 1; } +trap cleanup EXIT +mount_rootfs +docker_build \ No newline at end of file diff --git a/openwrt.conf.example b/openwrt.conf.example index 8d248b7..39d0ba0 100644 --- a/openwrt.conf.example +++ b/openwrt.conf.example @@ -1,24 +1,31 @@ ### Sample OpenWRT config file ### -# general +## general +# source image for Raspberry Pi build target +SOURCE_IMG=openwrt-19.07.2-brcm2708-bcm2708-rpi-ext4-factory.img +# source tag for build (x86) target OPENWRT_TAG=x86-64-19.07.2 +# final tag for built Docker image BUILD_TAG=openwrt +# container name CONTAINER=openwrt_1 ROOT_PW=changeme123 # docker network settings # wan WAN_NAME=openwrt-wan -WAN_PARENT=enp0s20f0u4 +# host interface which will provide the WAN link for OpenWRT +WAN_PARENT=eth0 UPSTREAM_DNS_SERVER=8.8.8.8 -# lan +# Static IP address configuration for OpenWRT LAN LAN_NAME=openwrt-lan LAN_DOMAIN=home LAN_SUBNET=192.168.16.0/24 LAN6_SUBNET=fd99:1234::/48 +# Set LAN_ADDR to something other than the first available address +# in the subnet - Docker will claim this address for the host LAN_ADDR=192.168.16.2 -LAN_HOST=192.168.16.1 # openwrt doesn't accept CIDR notation; must match LAN_SUBNET LAN_NETMASK=255.255.255.0 diff --git a/rpi.md b/rpi.md new file mode 100644 index 0000000..5fd95c1 --- /dev/null +++ b/rpi.md @@ -0,0 +1,34 @@ +# Building on Raspberry Pi + +Turn your Pi into a pretty okay-ish travel router (or a very slow main router)! + +OpenWrt officially supports Raspberry Pi hardware if you want to run it as your OS. But running in a container brings many advantages, one of which is not having to re-flash your SD card. + +This has been tested on a Raspberry Pi Zero W running Raspbian Lite, but should work for other versions too. Just make sure you download the right image for your Pi version (refer to the notes in [build-rpi.sh](./build-rpi.sh)). + + +## IPv6 +By default Raspbian does not load the kernel module for IPv6 `iptables` on boot. + +Run `sudo modprobe ip6_tables` to load it immediately. + +To persist on reboot, run + + $ echo 'ip6_tables' | sudo tee /etc/modules-load.d/ip6-tables.conf + +--- +## Build +You can build the OpenWRT docker image on the Pi itself, or on your x86 PC with `qemu-arm` installed. + +First download and extract the OpenWRT factory image for your Pi. Refer to the [OpenWrt Table of Hardware](https://openwrt.org/toh/raspberry_pi_foundation/raspberry_pi) to choose the right image. Then run the `make` target. + +The variable `RPI_SOURCE_IMG` can be specified in openwrt.conf or on the command line: +``` +$ https://downloads.openwrt.org/releases/19.07.2/targets/brcm2708/bcm2708/openwrt-19.07.2-brcm2708-bcm2708-rpi-ext4-factory.img.gz +$ gzip -d openwrt-*.img.gz +$ make build-rpi RPI_SOURCE_IMG=openwrt-19.07.2-brcm2708-bcm2708-rpi-ext4-factory.img +``` + +If you built the image on your PC, send it to your Raspberry Pi (`$BUILD_TAG` is a config variable): +``` +$ docker save $BUILD_TAG | ssh docker load \ No newline at end of file diff --git a/run.sh b/run.sh index 4c63391..9c1fedf 100755 --- a/run.sh +++ b/run.sh @@ -69,7 +69,7 @@ function _set_hairpin() { for i in {1..10}; do echo -n '.' sudo ip netns exec $CONTAINER ip link set $WIFI_IFACE type bridge_slave hairpin on 2>/dev/null && { echo 'ok'; break; } - sleep 1 + sleep 3 done if [[ $i -ge 10 ]]; then echo -e "\ncouldn't set hairpin mode, wifi clients will probably be unable to talk to each other" @@ -78,8 +78,8 @@ function _set_hairpin() { function _create_or_start_container() { docker inspect $BUILD_TAG >/dev/null 2>&1 || { echo "no image '$BUILD_TAG' found, did you forget to run 'make build'?"; exit 1; } - docker inspect $CONTAINER >/dev/null 2>&1 - if [[ $? -eq 0 ]]; then + + if docker inspect $CONTAINER >/dev/null 2>&1; then echo "* starting container '$CONTAINER'" docker start $CONTAINER else @@ -103,9 +103,20 @@ function _create_or_start_container() { fi } +function _reload_fw() { + echo "* reloading firewall rules" + docker exec -it $CONTAINER sh -c ' + for iptables in iptables ip6tables; do + for table in filter nat mangle; do + $iptables -t $table -F + done + done + /sbin/fw3 -q restart' +} + function main() { test -z $WIFI_IFACE && _usage - cd $SCRIPT_DIR + cd "${SCRIPT_DIR}" _get_phy_from_dev _nmcli _create_or_start_container @@ -124,6 +135,7 @@ function main() { echo "* getting address via DHCP" sudo dhcpcd -q "br-${LAN_ID:0:12}" + _reload_fw echo "* ready" }