From ddb263614170b87dd811e5022591963902607563 Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:03:36 +1000 Subject: [PATCH 1/9] rename "bridge" to "router" 1. Rename files with "bridge" in the name to "router". 2. Edit file contents referring to "bridge" so they refer to "router". 3. Update README to reflect this change, plus point to the relevant link in the ZeroTier documentation. Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- .github/workflows/multiarch.yml | 4 ++-- .github/workflows/{bridge.yml => router.yml} | 24 +++++++++---------- Dockerfile.bridge => Dockerfile.router | 8 +++---- README.md | 8 +++---- k8s/deployment.yaml | 2 +- ...rypoint-bridge.sh => entrypoint-router.sh} | 0 6 files changed, 23 insertions(+), 23 deletions(-) rename .github/workflows/{bridge.yml => router.yml} (87%) rename Dockerfile.bridge => Dockerfile.router (65%) rename scripts/{entrypoint-bridge.sh => entrypoint-router.sh} (100%) diff --git a/.github/workflows/multiarch.yml b/.github/workflows/multiarch.yml index 59bd0b9..af8515c 100644 --- a/.github/workflows/multiarch.yml +++ b/.github/workflows/multiarch.yml @@ -103,8 +103,8 @@ jobs: buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:latest buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }} - bridge: - uses: ./.github/workflows/bridge.yml + router: + uses: ./.github/workflows/router.yml needs: push with: tag: ${{ github.event.release.tag_name }} diff --git a/.github/workflows/bridge.yml b/.github/workflows/router.yml similarity index 87% rename from .github/workflows/bridge.yml rename to .github/workflows/router.yml index 4f34a8c..2292607 100644 --- a/.github/workflows/bridge.yml +++ b/.github/workflows/router.yml @@ -1,4 +1,4 @@ -name: Bridge Mode build +name: Router Mode build on: workflow_call: inputs: @@ -43,11 +43,11 @@ jobs: - name: Build unstable image if: ${{ inputs.event == 'push' }} - run: buildah bud --platform ${{ matrix.platform.name }} --build-arg FROM_IMAGE=${{ format('ghcr.io/zyclonite/{0}', env.IMAGE_NAME) }} --build-arg FROM_VERSION=main -f ./Dockerfile.bridge -t ${{ env.IMAGE_NAME }}:${{ matrix.platform.tag }} . + run: buildah bud --platform ${{ matrix.platform.name }} --build-arg FROM_IMAGE=${{ format('ghcr.io/zyclonite/{0}', env.IMAGE_NAME) }} --build-arg FROM_VERSION=main -f ./Dockerfile.router -t ${{ env.IMAGE_NAME }}:${{ matrix.platform.tag }} . - name: Build stable image if: ${{ inputs.event == 'release' }} - run: buildah bud --platform ${{ matrix.platform.name }} --build-arg FROM_IMAGE=${{ format('ghcr.io/zyclonite/{0}', env.IMAGE_NAME) }} --build-arg FROM_VERSION=${{ inputs.tag }} -f ./Dockerfile.bridge -t ${{ env.IMAGE_NAME }}:${{ matrix.platform.tag }} . + run: buildah bud --platform ${{ matrix.platform.name }} --build-arg FROM_IMAGE=${{ format('ghcr.io/zyclonite/{0}', env.IMAGE_NAME) }} --build-arg FROM_VERSION=${{ inputs.tag }} -f ./Dockerfile.router -t ${{ env.IMAGE_NAME }}:${{ matrix.platform.tag }} . - name: Check images created run: buildah images | grep '${{ env.IMAGE_NAME }}' @@ -98,16 +98,16 @@ jobs: - name: Push unstable images if: ${{ inputs.event == 'push' }} run: | - buildah manifest push --all --format v2s2 --creds zyclonite:${{ secrets.REGISTRY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://docker.io/zyclonite/${{ env.IMAGE_NAME }}:bridge-main - buildah manifest push --all --creds zyclonite:${{ secrets.QUAY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://quay.io/zyclonite/${{ env.IMAGE_NAME }}:bridge-main - buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:bridge-main + buildah manifest push --all --format v2s2 --creds zyclonite:${{ secrets.REGISTRY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://docker.io/zyclonite/${{ env.IMAGE_NAME }}:router-main + buildah manifest push --all --creds zyclonite:${{ secrets.QUAY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://quay.io/zyclonite/${{ env.IMAGE_NAME }}:router-main + buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:router-main - name: Push stable images if: ${{ inputs.event == 'release' }} run: | - buildah manifest push --all --format v2s2 --creds zyclonite:${{ secrets.REGISTRY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://docker.io/zyclonite/${{ env.IMAGE_NAME }}:bridge - buildah manifest push --all --format v2s2 --creds zyclonite:${{ secrets.REGISTRY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://docker.io/zyclonite/${{ env.IMAGE_NAME }}:bridge-${{ github.event.release.tag_name }} - buildah manifest push --all --creds zyclonite:${{ secrets.QUAY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://quay.io/zyclonite/${{ env.IMAGE_NAME }}:bridge - buildah manifest push --all --creds zyclonite:${{ secrets.QUAY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://quay.io/zyclonite/${{ env.IMAGE_NAME }}:bridge-${{ github.event.release.tag_name }} - buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:bridge - buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:bridge-${{ github.event.release.tag_name }} + buildah manifest push --all --format v2s2 --creds zyclonite:${{ secrets.REGISTRY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://docker.io/zyclonite/${{ env.IMAGE_NAME }}:router + buildah manifest push --all --format v2s2 --creds zyclonite:${{ secrets.REGISTRY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://docker.io/zyclonite/${{ env.IMAGE_NAME }}:router-${{ github.event.release.tag_name }} + buildah manifest push --all --creds zyclonite:${{ secrets.QUAY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://quay.io/zyclonite/${{ env.IMAGE_NAME }}:router + buildah manifest push --all --creds zyclonite:${{ secrets.QUAY_PASSWORD }} ${{ env.IMAGE_NAME }}:latest docker://quay.io/zyclonite/${{ env.IMAGE_NAME }}:router-${{ github.event.release.tag_name }} + buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:router + buildah manifest push --all --creds zyclonite:${{ secrets.GITHUB_TOKEN }} ${{ env.IMAGE_NAME }}:latest docker://ghcr.io/zyclonite/${{ env.IMAGE_NAME }}:router-${{ github.event.release.tag_name }} diff --git a/Dockerfile.bridge b/Dockerfile.router similarity index 65% rename from Dockerfile.bridge rename to Dockerfile.router index b7126b6..ddc9eb8 100644 --- a/Dockerfile.bridge +++ b/Dockerfile.router @@ -4,20 +4,20 @@ ARG FROM_VERSION=latest FROM ${FROM_IMAGE}:${FROM_VERSION} LABEL org.opencontainers.image.title="zerotier" \ - org.opencontainers.image.version="bridge-${ZT_VERSION}" \ - org.opencontainers.image.description="ZeroTier One as Docker Image" \ + org.opencontainers.image.version="router-${ZT_VERSION}" \ + org.opencontainers.image.description="ZeroTier One router as Docker Image" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.source="https://github.com/zyclonite/zerotier-docker" ENV LOG_PATH=/var/log/supervisor -COPY scripts/entrypoint-bridge.sh /usr/sbin/ +COPY scripts/entrypoint-router.sh /usr/sbin/ RUN apk add --no-cache --purge --clean-protected iptables \ && rm -rf /var/cache/apk/* EXPOSE 9993/udp -ENTRYPOINT ["entrypoint-bridge.sh"] +ENTRYPOINT ["entrypoint-router.sh"] CMD ["-U"] diff --git a/README.md b/README.md index 1d92209..8e7e24e 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,14 @@ or create an empty file with the network as name /var/lib/zerotier-one/networks.d/8056c2e21c000001.conf -#### Bridge mode -It is the implementation of the local network bridge [paper](https://zerotier.atlassian.net/wiki/spaces/SD/pages/193134593/Bridge+your+ZeroTier+and+local+network+with+a+RaspberryPi) +#### Router mode +It is the implementation of the local network router [paper](https://zerotier.atlassian.net/wiki/spaces/SD/pages/224395274/Route+between+ZeroTier+and+Physical+Networks) docker run --name zerotier-one --device=/dev/net/tun \ --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SYS_ADMIN \ - -v /var/lib/zerotier-one:/var/lib/zerotier-one zyclonite/zerotier:bridge + -v /var/lib/zerotier-one:/var/lib/zerotier-one zyclonite/zerotier:router -That will start the zero-one, establish connection and build the bridge once the `zt` interface is up. +That will start the zero-one, establish connection and build the NAT+router once the `zt` interface is up. #### Source diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 6e53ca0..f15edbb 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -25,7 +25,7 @@ spec: containers: - name: zerotier - image: zyclonite/zerotier:bridge + image: zyclonite/zerotier:router resources: limits: memory: "128Mi" diff --git a/scripts/entrypoint-bridge.sh b/scripts/entrypoint-router.sh similarity index 100% rename from scripts/entrypoint-bridge.sh rename to scripts/entrypoint-router.sh From bf15adc4cedc74833432331dcd46ee6302ac582e Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Tue, 19 Jul 2022 15:58:18 +1000 Subject: [PATCH 2/9] timezone support Adding `tzdata` to Dockerfile.router activates TZ environment variable. Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- Dockerfile.router | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.router b/Dockerfile.router index ddc9eb8..2d3b3e6 100644 --- a/Dockerfile.router +++ b/Dockerfile.router @@ -13,7 +13,7 @@ ENV LOG_PATH=/var/log/supervisor COPY scripts/entrypoint-router.sh /usr/sbin/ -RUN apk add --no-cache --purge --clean-protected iptables \ +RUN apk add --no-cache --purge --clean-protected iptables tzdata \ && rm -rf /var/cache/apk/* EXPOSE 9993/udp From f28b665afda6c68693af88dc8f556f34d07ebe70 Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:41:38 +1000 Subject: [PATCH 3/9] Extend router launch script: 1. Support `ZEROTIER_ONE_USE_IPTABLES_NFT` environment variable. If omitted or has any value other than `true`, `iptables` is used (maintains backwards compatibility). If `true`, substitutes `iptables-nft`. This definitely seems to be necessary on Raspberry Pi running Bullseye. 2. Support `ZEROTIER_ONE_LOCAL_PHYS` environment variable. Defaults to `eth0` if omitted (maintains backwards compatibility). Allows for overriding to `wlan0` (eg Raspberry Pi Zero 2W), or both `eth0 wlan0` to support multiple subnets or failover modes, or similar situations (eg extra network interface cards). 3. Support `ZEROTIER_ONE_NETWORK_ID` as an alternative to the `join` command. Means container will always fail safe if its persistent storage is erased - will look like a new identity but can be authorised and will then be reachable for additional configuration. 4. Support `PUID` + `PGID` environment variables. Default to 999 and 994, respectively, mimicking what happens on a "native" install of ZeroTier-One (on a Raspberry Pi). 5. Perform unconditional reset of ownership (PUID:PGID) throughout persistent store on each launch. This avoids many permission problems that can sometimes occur in docker environments. 6. Add launch message with date. Assists in assessing recency of "sendto: Network unreachable" messages that can occur after a reboot where the container resumes before networking is available. Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- scripts/entrypoint-router.sh | 49 ++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/scripts/entrypoint-router.sh b/scripts/entrypoint-router.sh index 1d89214..f8fdbdc 100755 --- a/scripts/entrypoint-router.sh +++ b/scripts/entrypoint-router.sh @@ -1,14 +1,53 @@ #!/usr/bin/env sh set -Eeo pipefail +echo "$(date) - launching ZeroTier-One in routing mode" + if [ "${1:0:1}" = '-' ]; then set -- zerotier-one "$@" fi -PHY_IFACE=eth0 -ZT_IFACE="zt+" -iptables -t nat -A POSTROUTING -o $PHY_IFACE -j MASQUERADE -iptables -A FORWARD -i $PHY_IFACE -o $ZT_IFACE -m state --state RELATED,ESTABLISHED -j ACCEPT -iptables -A FORWARD -i $ZT_IFACE -o $PHY_IFACE -j ACCEPT +# useful paths +CONFIG_DIR="/var/lib/zerotier-one" +NETWORKS_DIR="$CONFIG_DIR/networks.d" +# set up network auto-join if (a) the networks directory does not exist +# and (b) the ZEROTIER_ONE_NETWORK_ID environment variable is non-null. +if [ ! -d "$NETWORKS_DIR" -a -n "$ZEROTIER_ONE_NETWORK_ID" ] ; then + echo "Assuming container first run. Configuring auto-join of network ID:" + echo " $ZEROTIER_ONE_NETWORK_ID" + echo "You will need to authorize this host at:" + echo " https://my.zerotier.com/network/$ZEROTIER_ONE_NETWORK_ID" + mkdir -p "$NETWORKS_DIR" + touch "$NETWORKS_DIR/$ZEROTIER_ONE_NETWORK_ID.conf" +fi + +# make sure permissions are correct +PUID="${PUID:-"999"}" +PGID="${PGID:-"994"}" +if [ "$(id -u)" = '0' -a -d "$CONFIG_DIR" ]; then + chown -Rc "$PUID:$PGID" "$CONFIG_DIR" +fi + +# use an appropriate default for a local physical interface +PHY_IFACES="${ZEROTIER_ONE_LOCAL_PHYS:-"eth0"}" + +# default to iptables (maintain compatibility for existing systems) +IPTABLES_CMD=iptables +# but support override to use iptables-nft +[ "$ZEROTIER_ONE_USE_IPTABLES_NFT" = "true" ] && IPTABLES_CMD=iptables-nft + +# the wildcard for the local zerotier interface is +ZT_IFACE="zt+" + +# iterate the local interface(s) and enable NAT services +for PHY_IFACE in $PHY_IFACES ; do + echo "Using $IPTABLES_CMD to enable NAT services on $PHY_IFACE" + $IPTABLES_CMD -t nat -A POSTROUTING -o $PHY_IFACE -j MASQUERADE + $IPTABLES_CMD -A FORWARD -i $PHY_IFACE -o $ZT_IFACE -m state --state RELATED,ESTABLISHED -j ACCEPT + $IPTABLES_CMD -A FORWARD -i $ZT_IFACE -o $PHY_IFACE -j ACCEPT +done + +# launch zerotier-one exec "$@" + From 226cf83004cf26b6a8eac77e07f0427e0d50433e Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:42:58 +1000 Subject: [PATCH 4/9] add example docker-compose service definition for router Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- docker-compose-router.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docker-compose-router.yml diff --git a/docker-compose-router.yml b/docker-compose-router.yml new file mode 100644 index 0000000..2b0c4a3 --- /dev/null +++ b/docker-compose-router.yml @@ -0,0 +1,22 @@ +version: '3' +services: + zerotier: + image: "zyclonite/zerotier:router" + container_name: zerotier-one + devices: + - /dev/net/tun + network_mode: host + volumes: + - '/var/lib/zerotier-one:/var/lib/zerotier-one' + cap_add: + - NET_ADMIN + - SYS_ADMIN + - NET_RAW + restart: unless-stopped + environment: + - TZ=Etc/UTC + - PUID=999 + - PGID=994 + - ZEROTIER_ONE_LOCAL_PHYS=eth0 + - ZEROTIER_ONE_USE_IPTABLES_NFT=false + # - ZEROTIER_ONE_NETWORK_ID=yourNetworkID From 7f6239fae47232dcd474f87e48d72d5daed46bed Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:44:28 +1000 Subject: [PATCH 5/9] update documentation to describe router-mode environment variables Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/README.md b/README.md index 8e7e24e..de59b63 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,78 @@ It is the implementation of the local network router [paper](https://zerotier.at That will start the zero-one, establish connection and build the NAT+router once the `zt` interface is up. +##### Environment variables + +The following environment variables are supported: + +* `TZ` – timezone support. Example: + + ``` yaml + TZ=Australia/Sydney + ``` + + Defaults to `Etc/UTC` if omitted. + +* `PUID` + `PGID` – user and group IDs for ownership of persistent store. Example: + + ``` yaml + PUID=1000 + PGID=1000 + ``` + + If omitted, `PUID` defaults to user ID 999, while `PGID` defaults to group ID 994. These variables are only used to ensure consistent ownership on each launch. They do not affect how the container *runs.* Absent a `user:` directive, the container runs as root and does not downgrade its privileges. + +* `ZEROTIER_ONE_LOCAL_PHYS` - controls which physical interfaces participate in network address translation (NAT). Examples: + + - Use only the physical Ethernet interface (this is also the default of the variable is omitted): + + ``` yaml + ZEROTIER_ONE_LOCAL_PHYS=eth0 + ``` + + - If your computer only has WiFi active: + + ``` yaml + ZEROTIER_ONE_LOCAL_PHYS=wlan0 + ``` + + - If your computer has both Ethernet and WiFi interfaces active and you wish to be able to route through each interface: + + - if using `docker run`: + + ``` console + --env ZEROTIER_ONE_LOCAL_PHYS="eth0 wlan0" + ``` + + - if using `docker-compose`: + + ``` yaml + environment: + - ZEROTIER_ONE_LOCAL_PHYS=eth0 wlan0 + ``` + +* `ZEROTIER_ONE_USE_IPTABLES_NFT` - controls the command the container uses to set up NAT forwarding. Example: + + ``` yaml + ZEROTIER_ONE_USE_IPTABLES_NFT=true + ``` + + Defaults to `false` if omitted. Try `true` if NAT does not seem to be working. + +* `ZEROTIER_ONE_NETWORK_ID` – auto-join network on first launch. Example: + + ``` yaml + ZEROTIER_ONE_NETWORK_ID=565758596a6b6c44 + ``` + + This variable is only effective on first launch. There is no default if it is omitted. It is the equivalent of running the following command after the container first starts: + + ``` + $ docker exec zerotier zerotier-cli join 565758596a6b6c44 + ``` + + It does not matter whether you use this environment variable or the `join` command, you still need to authorize the computer in ZeroTier Central. + #### Source https://github.com/zyclonite/zerotier-docker From 9b98b470f8967f2728c477c0d33b9ce881527329 Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Fri, 22 Jul 2022 13:50:28 +1000 Subject: [PATCH 6/9] Change `ZEROTIER_ONE_NETWORK_ID` to `ZEROTIER_ONE_NETWORK_IDS` so more than one network can be specified as defaults. First cut at improved launch/tear-down so iptables entries do not get duplicated, especially if the zerotier daemon quits because networking has not come up properly after a reboot. Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- README.md | 24 +++++--- docker-compose-router.yml | 2 +- scripts/entrypoint-router.sh | 103 ++++++++++++++++++++++++++++------- 3 files changed, 100 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index de59b63..0aa90ed 100644 --- a/README.md +++ b/README.md @@ -99,19 +99,29 @@ The following environment variables are supported: Defaults to `false` if omitted. Try `true` if NAT does not seem to be working. -* `ZEROTIER_ONE_NETWORK_ID` – auto-join network on first launch. Example: +* `ZEROTIER_ONE_NETWORK_IDS` – auto-join network(s). This variable is only effective on first launch. There is no default if it is omitted. Examples: - ``` yaml - ZEROTIER_ONE_NETWORK_ID=565758596a6b6c44 - ``` + - if using `docker run`: + + ``` console + --env ZEROTIER_ONE_NETWORK_IDS="aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb" + ``` - This variable is only effective on first launch. There is no default if it is omitted. It is the equivalent of running the following command after the container first starts: + - if using `docker-compose`: + + ``` yaml + environment: + - ZEROTIER_ONE_NETWORK_IDS=aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb + ``` + + In each case, it is the equivalent of running the following commands after the container first starts: ``` - $ docker exec zerotier zerotier-cli join 565758596a6b6c44 + $ docker exec zerotier zerotier-cli join aaaaaaaaaaaaaaaa + $ docker exec zerotier zerotier-cli join bbbbbbbbbbbbbbbb ``` - It does not matter whether you use this environment variable or the `join` command, you still need to authorize the computer in ZeroTier Central. + It does not matter whether you use this environment variable or the `join` command, you still need to authorize the computer for each network in ZeroTier Central. #### Source diff --git a/docker-compose-router.yml b/docker-compose-router.yml index 2b0c4a3..35dfcfa 100644 --- a/docker-compose-router.yml +++ b/docker-compose-router.yml @@ -19,4 +19,4 @@ services: - PGID=994 - ZEROTIER_ONE_LOCAL_PHYS=eth0 - ZEROTIER_ONE_USE_IPTABLES_NFT=false - # - ZEROTIER_ONE_NETWORK_ID=yourNetworkID + # - ZEROTIER_ONE_NETWORK_IDS=yourNetworkID diff --git a/scripts/entrypoint-router.sh b/scripts/entrypoint-router.sh index f8fdbdc..dd137f2 100755 --- a/scripts/entrypoint-router.sh +++ b/scripts/entrypoint-router.sh @@ -2,6 +2,7 @@ set -Eeo pipefail echo "$(date) - launching ZeroTier-One in routing mode" +echo "command and args: $@" if [ "${1:0:1}" = '-' ]; then set -- zerotier-one "$@" @@ -9,24 +10,26 @@ fi # useful paths CONFIG_DIR="/var/lib/zerotier-one" -NETWORKS_DIR="$CONFIG_DIR/networks.d" +NETWORKS_DIR="${CONFIG_DIR}/networks.d" # set up network auto-join if (a) the networks directory does not exist -# and (b) the ZEROTIER_ONE_NETWORK_ID environment variable is non-null. -if [ ! -d "$NETWORKS_DIR" -a -n "$ZEROTIER_ONE_NETWORK_ID" ] ; then - echo "Assuming container first run. Configuring auto-join of network ID:" - echo " $ZEROTIER_ONE_NETWORK_ID" - echo "You will need to authorize this host at:" - echo " https://my.zerotier.com/network/$ZEROTIER_ONE_NETWORK_ID" - mkdir -p "$NETWORKS_DIR" - touch "$NETWORKS_DIR/$ZEROTIER_ONE_NETWORK_ID.conf" +# and (b) the ZEROTIER_ONE_NETWORK_IDS environment variable is non-null. +if [ ! -d "${NETWORKS_DIR}" -a -n "${ZEROTIER_ONE_NETWORK_IDS}" ] ; then + echo "Assuming container first run." + mkdir -p "${NETWORKS_DIR}" + for NETWORK_ID in ${ZEROTIER_ONE_NETWORK_IDS} ; do + echo "Configuring auto-join of network ID: ${NETWORK_ID}" + touch "${NETWORKS_DIR}/${NETWORK_ID}.conf" + echo "You will need to authorize this host at:" + echo " https://my.zerotier.com/network/${NETWORK_ID}" + done fi # make sure permissions are correct PUID="${PUID:-"999"}" PGID="${PGID:-"994"}" -if [ "$(id -u)" = '0' -a -d "$CONFIG_DIR" ]; then - chown -Rc "$PUID:$PGID" "$CONFIG_DIR" +if [ "$(id -u)" = '0' -a -d "${CONFIG_DIR}" ]; then + chown -Rc "${PUID}:${PGID}" "${CONFIG_DIR}" fi # use an appropriate default for a local physical interface @@ -35,19 +38,77 @@ PHY_IFACES="${ZEROTIER_ONE_LOCAL_PHYS:-"eth0"}" # default to iptables (maintain compatibility for existing systems) IPTABLES_CMD=iptables # but support override to use iptables-nft -[ "$ZEROTIER_ONE_USE_IPTABLES_NFT" = "true" ] && IPTABLES_CMD=iptables-nft +[ "${ZEROTIER_ONE_USE_IPTABLES_NFT}" = "true" ] && IPTABLES_CMD=iptables-nft # the wildcard for the local zerotier interface is ZT_IFACE="zt+" -# iterate the local interface(s) and enable NAT services -for PHY_IFACE in $PHY_IFACES ; do - echo "Using $IPTABLES_CMD to enable NAT services on $PHY_IFACE" - $IPTABLES_CMD -t nat -A POSTROUTING -o $PHY_IFACE -j MASQUERADE - $IPTABLES_CMD -A FORWARD -i $PHY_IFACE -o $ZT_IFACE -m state --state RELATED,ESTABLISHED -j ACCEPT - $IPTABLES_CMD -A FORWARD -i $ZT_IFACE -o $PHY_IFACE -j ACCEPT -done +# a script to add and remove the requisite rules - $1 is either "A" or "D" +update_iptables() { -# launch zerotier-one -exec "$@" + # iterate the local interface(s) and enable NAT services + for PHY_IFACE in ${PHY_IFACES} ; do + ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${PHY_IFACE} -j MASQUERADE + ${IPTABLES_CMD} -${1} FORWARD -i ${PHY_IFACE} -o ${ZT_IFACE} -m state --state RELATED,ESTABLISHED -j ACCEPT + ${IPTABLES_CMD} -${1} FORWARD -i ${ZT_IFACE} -o ${PHY_IFACE} -j ACCEPT + done +} + +# add rules to set up routing +echo "Using ${IPTABLES_CMD} to enable NAT services on ${PHY_IFACES}" +update_iptables "A" + +# define where the ZeroTier daemon will write its output (if any) +PIPE=$(mktemp /tmp/zerotier-ipc-XXXXXX) + +# start listening and echoing anything that appears there into this process +tail -f "${PIPE}" & + +# make a note of the process ID for tail +TAIL_PIPE_PID=${!} + +# report +echo "tail has started with PID=${TAIL_PIPE_PID} listening to ${PIPE}" + +# now start the ZeroTier daemon in detached state +nohup "$@" "${PIPE}" 2>&1 & + +# make a note of the process ID +ZEROTIER_DAEMON_PID=${!} + +# report +echo "ZeroTier daemon has PID ${ZEROTIER_DAEMON_PID}" + +echo "Setting up trap" +trap 'echo "**INT" ; kill -TERM ${ZEROTIER_DAEMON_PID}' INT +trap 'echo "**TERM" ; kill -TERM ${ZEROTIER_DAEMON_PID}' TERM +trap 'echo "**HUP" ; kill -TERM ${ZEROTIER_DAEMON_PID}' HUP + +trap 'echo "**EXIT-nohandler"' EXIT +trap 'echo "**ABRT-nohandler"' ABRT +trap 'echo "**QUIT-nohandler"' QUIT +trap 'echo "**TRAP-nohandler"' TRAP + +echo "now waiting on ZeroTier daemon" +wait ${ZEROTIER_DAEMON_PID} +echo "the ZeroTier daemon has gone away - cleaning up" + +# kill the tail listener +echo "Killing tail listener" +kill -TERM ${TAIL_PIPE_PID} + +# wait for it to go away +echo "Waiting for tail listener to go away" +wait ${TAIL_PIPE_PID} + +# which means we are done with the pipe +echo "removing pipe" +rm "${PIPE}" + +# remove rules used to set up routing +echo "Using ${IPTABLES_CMD} to disable NAT services on ${PHY_IFACES}" +update_iptables "D" + +# using the sigterm is a normal exit for us so exit with 0 +echo "all done" From 41d4501593d1e7dbeb620444466729b78af2187c Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Mon, 25 Jul 2022 11:09:06 +1000 Subject: [PATCH 7/9] Improve `entrypoint-router.sh`: - Remove redundant echo statements - Improve comments - Rename `PIPE` to `TAIL_PIPE` to clarify purpose - Remove traps used during testing and consolidate on INT TERM and HUP (TERM being the usual case) - Normal exit when ZeroTier aborts or exits just calls termination handler - Termination handler unconditionally removes iptables rules but conditionally clobbers processes. - All logging includes date. Tested combination of waiting on ZeroTier + TERM trap correctly handling: 1. Docker commands to restart, terminate, recreate, stack down, etc. 2. A reboot while the container is running (assuming restart unless stopped). 3. External action clobbering the ZeroTier daemon. Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- scripts/entrypoint-router.sh | 90 ++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/scripts/entrypoint-router.sh b/scripts/entrypoint-router.sh index dd137f2..f991a3b 100755 --- a/scripts/entrypoint-router.sh +++ b/scripts/entrypoint-router.sh @@ -2,7 +2,6 @@ set -Eeo pipefail echo "$(date) - launching ZeroTier-One in routing mode" -echo "command and args: $@" if [ "${1:0:1}" = '-' ]; then set -- zerotier-one "$@" @@ -25,7 +24,7 @@ if [ ! -d "${NETWORKS_DIR}" -a -n "${ZEROTIER_ONE_NETWORK_IDS}" ] ; then done fi -# make sure permissions are correct +# make sure permissions are always as expected (self-repair) PUID="${PUID:-"999"}" PGID="${PGID:-"994"}" if [ "$(id -u)" = '0' -a -d "${CONFIG_DIR}" ]; then @@ -33,82 +32,83 @@ if [ "$(id -u)" = '0' -a -d "${CONFIG_DIR}" ]; then fi # use an appropriate default for a local physical interface +# (using eth0 maintains backwards compatibility) PHY_IFACES="${ZEROTIER_ONE_LOCAL_PHYS:-"eth0"}" -# default to iptables (maintain compatibility for existing systems) +# default to iptables (maintains backwards compatibility) IPTABLES_CMD=iptables -# but support override to use iptables-nft +# but support an override to use iptables-nft [ "${ZEROTIER_ONE_USE_IPTABLES_NFT}" = "true" ] && IPTABLES_CMD=iptables-nft # the wildcard for the local zerotier interface is ZT_IFACE="zt+" -# a script to add and remove the requisite rules - $1 is either "A" or "D" +# function to add and remove the requisite rules +# - $1 is either "A" (add) or "D" (delete) update_iptables() { - - # iterate the local interface(s) and enable NAT services for PHY_IFACE in ${PHY_IFACES} ; do ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${PHY_IFACE} -j MASQUERADE ${IPTABLES_CMD} -${1} FORWARD -i ${PHY_IFACE} -o ${ZT_IFACE} -m state --state RELATED,ESTABLISHED -j ACCEPT ${IPTABLES_CMD} -${1} FORWARD -i ${ZT_IFACE} -o ${PHY_IFACE} -j ACCEPT done - } -# add rules to set up routing -echo "Using ${IPTABLES_CMD} to enable NAT services on ${PHY_IFACES}" +# add rules to set up NAT-routing update_iptables "A" # define where the ZeroTier daemon will write its output (if any) -PIPE=$(mktemp /tmp/zerotier-ipc-XXXXXX) +TAIL_PIPE=$(mktemp /tmp/zerotier-ipc-XXXXXX) # start listening and echoing anything that appears there into this process -tail -f "${PIPE}" & +tail -f "${TAIL_PIPE}" & # make a note of the process ID for tail TAIL_PIPE_PID=${!} -# report -echo "tail has started with PID=${TAIL_PIPE_PID} listening to ${PIPE}" - -# now start the ZeroTier daemon in detached state -nohup "$@" "${PIPE}" 2>&1 & +# start the ZeroTier daemon in detached state +nohup "$@" "${TAIL_PIPE}" 2>&1 & # make a note of the process ID ZEROTIER_DAEMON_PID=${!} # report -echo "ZeroTier daemon has PID ${ZEROTIER_DAEMON_PID}" +echo "$(date) - ZeroTier daemon is running as process ${ZEROTIER_DAEMON_PID}" -echo "Setting up trap" -trap 'echo "**INT" ; kill -TERM ${ZEROTIER_DAEMON_PID}' INT -trap 'echo "**TERM" ; kill -TERM ${ZEROTIER_DAEMON_PID}' TERM -trap 'echo "**HUP" ; kill -TERM ${ZEROTIER_DAEMON_PID}' HUP +# function to handle cleanup +termination_handler() { -trap 'echo "**EXIT-nohandler"' EXIT -trap 'echo "**ABRT-nohandler"' ABRT -trap 'echo "**QUIT-nohandler"' QUIT -trap 'echo "**TRAP-nohandler"' TRAP + echo "$(date) - terminating ZeroTier-One" -echo "now waiting on ZeroTier daemon" + # remove rules + update_iptables "D" + + # relay the termination message to the daemon + if [ -d "/proc/${ZEROTIER_DAEMON_PID}" ] ; then + kill -TERM ${ZEROTIER_DAEMON_PID} + wait ${ZEROTIER_DAEMON_PID} + fi + + # tell the pipe listener to go away too + if [ -d "/proc/${TAIL_PIPE_PID}" ] ; then + kill -TERM ${TAIL_PIPE_PID} + wait ${TAIL_PIPE_PID} + fi + + # clean up the pipe file + rm "${TAIL_PIPE}" + +} + +# set up termination handler (usually catches TERM) +trap termination_handler INT TERM HUP + +# suspend this script while the zerotier daemon is running wait ${ZEROTIER_DAEMON_PID} -echo "the ZeroTier daemon has gone away - cleaning up" -# kill the tail listener -echo "Killing tail listener" -kill -TERM ${TAIL_PIPE_PID} +# would not usually expect to arrive here inside a Docker container but +# it can happen if the user does a "sudo killall zerotier-one" rather +# that use Docker commands +echo "$(date) - the ZeroTier daemon has quit unexpectedly - cleaning up" -# wait for it to go away -echo "Waiting for tail listener to go away" -wait ${TAIL_PIPE_PID} - -# which means we are done with the pipe -echo "removing pipe" -rm "${PIPE}" - -# remove rules used to set up routing -echo "Using ${IPTABLES_CMD} to disable NAT services on ${PHY_IFACES}" -update_iptables "D" - -# using the sigterm is a normal exit for us so exit with 0 -echo "all done" +# run the termination handler +termination_handler From 1ae368044b4a39a968b3188db604669de28a4f61 Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Mon, 25 Jul 2022 11:13:36 +1000 Subject: [PATCH 8/9] refactor documentation to separate zerotier-router into separate readme Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- README-router.md | 172 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 89 +----------------------- 2 files changed, 174 insertions(+), 87 deletions(-) create mode 100644 README-router.md diff --git a/README-router.md b/README-router.md new file mode 100644 index 0000000..b215618 --- /dev/null +++ b/README-router.md @@ -0,0 +1,172 @@ +## zerotier router + +### Description + +This is a variation built on top of the zyclonite/zerotier container which implements a local network router. It is based upon the ZeroTier Knowledge Base article: + +* [Route between ZeroTier and Physical Networks](https://zerotier.atlassian.net/wiki/spaces/SD/pages/224395274/Route+between+ZeroTier+and+Physical+Networks) + +Technically, this could be described as a *half-router*: + +* You can initiate connections *from* a remote client *to* devices on the LAN; but +* You can't initiate connections *to* the remote client *from* devices on the LAN. + +### Command line example + +``` console +$ docker run --name zerotier-one --device=/dev/net/tun \ + --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SYS_ADMIN \ + --env TZ=Etc/UTC --env PUID=999 -env PGID=994 \ + --env ZEROTIER_ONE_LOCAL_PHYS=eth0 \ + --env ZEROTIER_ONE_USE_IPTABLES_NFT=false \ + --env ZEROTIER_ONE_NETWORK_IDS=«yourDefaultNetworkID(s)» \ + -v /var/lib/zerotier-one:/var/lib/zerotier-one zyclonite/zerotier:router +``` + +Note: + +* Environment variables that can contain multiple values should be enclosed in quotes with the components separated by spaces. Example: + + ``` console + --env ZEROTIER_ONE_LOCAL_PHYS="eth0 wlan0" + ``` + +### Compose file example + +``` yaml +version: '3' +services: + zerotier: + image: "zyclonite/zerotier:router" + container_name: zerotier-one + devices: + - /dev/net/tun + network_mode: host + volumes: + - '/var/lib/zerotier-one:/var/lib/zerotier-one' + cap_add: + - NET_ADMIN + - SYS_ADMIN + - NET_RAW + restart: unless-stopped + environment: + - TZ=Etc/UTC + - PUID=999 + - PGID=994 + - ZEROTIER_ONE_LOCAL_PHYS=eth0 + - ZEROTIER_ONE_USE_IPTABLES_NFT=false + # - ZEROTIER_ONE_NETWORK_IDS=«yourDefaultNetworkID(s)» +``` + +Note: + +* The right hand sides of environment variables should *never* be enclosed in quotes. If you need to pass multiple values, separate them with spaces. Example: + + ``` yaml + environment: + - ZEROTIER_ONE_LOCAL_PHYS=eth0 wlan0 + ``` + +### Environment variables + +* `TZ` – timezone support. Example: + + ``` yaml + environment: + - TZ=Australia/Sydney + ``` + + Defaults to `Etc/UTC` if omitted. + +* `PUID` + `PGID` – user and group IDs for ownership of persistent store. Example: + + ``` yaml + environment: + - PUID=1000 + - PGID=1000 + ``` + + If omitted, `PUID` defaults to user ID 999, while `PGID` defaults to group ID 994. + + These variables are only used to ensure consistent ownership of persistent storage on each launch. They do not affect how the container *runs.* Absent a `user:` directive, the container runs as root and does not downgrade its privileges. + +* `ZEROTIER_ONE_LOCAL_PHYS` - a space-separated list of physical interfaces that should be configured to participate in NAT-based routing. Examples: + + - Use only the physical Ethernet interface (this is also the default of the variable is omitted): + + ``` yaml + environment: + - ZEROTIER_ONE_LOCAL_PHYS=eth0 + ``` + + - If your computer only has WiFi active (eg Raspberry Pi Zero W2): + + ``` yaml + environment: + - ZEROTIER_ONE_LOCAL_PHYS=wlan0 + ``` + + - If your computer has both Ethernet and WiFi interfaces active and you wish to be able to route through each interface: + + ``` yaml + environment: + - ZEROTIER_ONE_LOCAL_PHYS=eth0 wlan0 + ``` + + This scheme could be appropriate where the physical interfaces were: + + 1. In the same broadcast domain (subnet). Disconnecting Ethernet would fail-over to WiFi. + 2. In different broadcast domains, such as if you allocated different subnets for Ethernet and WiFi. + +* `ZEROTIER_ONE_USE_IPTABLES_NFT` - controls the command the container uses to set up NAT forwarding. Example: + + ``` yaml + environment: + - ZEROTIER_ONE_USE_IPTABLES_NFT=true + ``` + + - `false` means the container uses `iptables`. This is the default. + - `true` means the container uses `iptables-nft`. + + Try `true` if NAT does not seem to be working. This is needed on Raspberry Pi Bullseye. + +* `ZEROTIER_ONE_NETWORK_IDS` – a space-separated list of ZeroTier network IDs. + + This variable is *only* effective on first launch. There is no default if it is omitted. Examples: + + - to join a single network: + + ``` yaml + environment: + - ZEROTIER_ONE_NETWORK_IDS=aaaaaaaaaaaaaaaa + ``` + + Equivalent of running the following command after the container first starts: + + ``` + $ docker exec zerotier zerotier-cli join aaaaaaaaaaaaaaaa + ``` + + - to join a multiple networks: + + ``` yaml + environment: + - ZEROTIER_ONE_NETWORK_IDS=aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb + ``` + + Equivalent of running the following commands after the container first starts: + + ``` + $ docker exec zerotier zerotier-cli join aaaaaaaaaaaaaaaa + $ docker exec zerotier zerotier-cli join bbbbbbbbbbbbbbbb + ``` + + It does not matter whether you use this environment variable or the `join` command, you still need to use ZeroTier Central to approve the computer for each network it joins. + +### Managed route(s) + +For each ZeroTier container that is configured as a router, ZeroTier needs at least one *Managed Route*. + +The [ZeroTier Wiki](https://zerotier.atlassian.net/wiki/spaces/SD/pages/224395274/Route+between+ZeroTier+and+Physical+Networks#Configure-the-ZeroTier-managed-route) explains how to design managed routes. + +You configure Managed Routes in ZeroTier Central. diff --git a/README.md b/README.md index 0aa90ed..06d8641 100644 --- a/README.md +++ b/README.md @@ -33,95 +33,10 @@ or create an empty file with the network as name /var/lib/zerotier-one/networks.d/8056c2e21c000001.conf #### Router mode -It is the implementation of the local network router [paper](https://zerotier.atlassian.net/wiki/spaces/SD/pages/224395274/Route+between+ZeroTier+and+Physical+Networks) - docker run --name zerotier-one --device=/dev/net/tun \ - --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SYS_ADMIN \ - -v /var/lib/zerotier-one:/var/lib/zerotier-one zyclonite/zerotier:router +A variation on the container which implements a local network router. See: -That will start the zero-one, establish connection and build the NAT+router once the `zt` interface is up. - -##### Environment variables - -The following environment variables are supported: - -* `TZ` – timezone support. Example: - - ``` yaml - TZ=Australia/Sydney - ``` - - Defaults to `Etc/UTC` if omitted. - -* `PUID` + `PGID` – user and group IDs for ownership of persistent store. Example: - - ``` yaml - PUID=1000 - PGID=1000 - ``` - - If omitted, `PUID` defaults to user ID 999, while `PGID` defaults to group ID 994. These variables are only used to ensure consistent ownership on each launch. They do not affect how the container *runs.* Absent a `user:` directive, the container runs as root and does not downgrade its privileges. - -* `ZEROTIER_ONE_LOCAL_PHYS` - controls which physical interfaces participate in network address translation (NAT). Examples: - - - Use only the physical Ethernet interface (this is also the default of the variable is omitted): - - ``` yaml - ZEROTIER_ONE_LOCAL_PHYS=eth0 - ``` - - - If your computer only has WiFi active: - - ``` yaml - ZEROTIER_ONE_LOCAL_PHYS=wlan0 - ``` - - - If your computer has both Ethernet and WiFi interfaces active and you wish to be able to route through each interface: - - - if using `docker run`: - - ``` console - --env ZEROTIER_ONE_LOCAL_PHYS="eth0 wlan0" - ``` - - - if using `docker-compose`: - - ``` yaml - environment: - - ZEROTIER_ONE_LOCAL_PHYS=eth0 wlan0 - ``` - -* `ZEROTIER_ONE_USE_IPTABLES_NFT` - controls the command the container uses to set up NAT forwarding. Example: - - ``` yaml - ZEROTIER_ONE_USE_IPTABLES_NFT=true - ``` - - Defaults to `false` if omitted. Try `true` if NAT does not seem to be working. - -* `ZEROTIER_ONE_NETWORK_IDS` – auto-join network(s). This variable is only effective on first launch. There is no default if it is omitted. Examples: - - - if using `docker run`: - - ``` console - --env ZEROTIER_ONE_NETWORK_IDS="aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb" - ``` - - - if using `docker-compose`: - - ``` yaml - environment: - - ZEROTIER_ONE_NETWORK_IDS=aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb - ``` - - In each case, it is the equivalent of running the following commands after the container first starts: - - ``` - $ docker exec zerotier zerotier-cli join aaaaaaaaaaaaaaaa - $ docker exec zerotier zerotier-cli join bbbbbbbbbbbbbbbb - ``` - - It does not matter whether you use this environment variable or the `join` command, you still need to authorize the computer for each network in ZeroTier Central. +* [router README](./README-router.md) #### Source From 09df8bf66ed04eb3130feed8a6aaa27e984a4e7a Mon Sep 17 00:00:00 2001 From: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> Date: Wed, 27 Jul 2022 21:58:39 +1000 Subject: [PATCH 9/9] support three routing modes as proposed by @bfg100k Adds `ZEROTIER_ONE_GATEWAY_MODE` variable. Supported values are `inbound` (forward traffic from ZeroTier cloud to local interfaces), `outbound` (forward traffic from local interfaces to ZeroTier cloud) and `both` (bi-directional). Defaults to `inbound`. Also checks for `net.ipv4.ip_forward=1`. If not enabled, falls back to standard client mode. Signed-off-by: Phill Kelley <34226495+Paraphraser@users.noreply.github.com> --- README-router.md | 27 +++++++++++++++++ docker-compose-router.yml | 1 + scripts/entrypoint-router.sh | 58 ++++++++++++++++++++++++++++++------ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/README-router.md b/README-router.md index b215618..0fe1c83 100644 --- a/README-router.md +++ b/README-router.md @@ -19,6 +19,7 @@ $ docker run --name zerotier-one --device=/dev/net/tun \ --env TZ=Etc/UTC --env PUID=999 -env PGID=994 \ --env ZEROTIER_ONE_LOCAL_PHYS=eth0 \ --env ZEROTIER_ONE_USE_IPTABLES_NFT=false \ + --env ZEROTIER_ONE_GATEWAY_MODE=inbound \ --env ZEROTIER_ONE_NETWORK_IDS=«yourDefaultNetworkID(s)» \ -v /var/lib/zerotier-one:/var/lib/zerotier-one zyclonite/zerotier:router ``` @@ -55,6 +56,7 @@ services: - PGID=994 - ZEROTIER_ONE_LOCAL_PHYS=eth0 - ZEROTIER_ONE_USE_IPTABLES_NFT=false + - ZEROTIER_ONE_GATEWAY_MODE=inbound # - ZEROTIER_ONE_NETWORK_IDS=«yourDefaultNetworkID(s)» ``` @@ -130,6 +132,31 @@ Note: Try `true` if NAT does not seem to be working. This is needed on Raspberry Pi Bullseye. +* `ZEROTIER_ONE_GATEWAY_MODE` - controls the traffic direction. Examples: + + - Only permit traffic *from* the ZeroTier cloud *to* the local physical interfaces: + + ``` yaml + environment: + - ZEROTIER_ONE_GATEWAY_MODE=inbound + ``` + + - Only permit traffic *from* the local physical interfaces *to* the ZeroTier cloud: + + ``` yaml + environment: + - ZEROTIER_ONE_GATEWAY_MODE=outbound + ``` + + - Permit bi-directional traffic between the local physical interfaces and the ZeroTier cloud: + + ``` yaml + environment: + - ZEROTIER_ONE_GATEWAY_MODE=both + ``` + + Defaults to `inbound` if omitted. Note that you will probably need one or more static routes configured in your local LAN router so that traffic originating in a local host which is not running the ZeroTier client can be directed to the gateway host. + * `ZEROTIER_ONE_NETWORK_IDS` – a space-separated list of ZeroTier network IDs. This variable is *only* effective on first launch. There is no default if it is omitted. Examples: diff --git a/docker-compose-router.yml b/docker-compose-router.yml index 35dfcfa..c09319e 100644 --- a/docker-compose-router.yml +++ b/docker-compose-router.yml @@ -19,4 +19,5 @@ services: - PGID=994 - ZEROTIER_ONE_LOCAL_PHYS=eth0 - ZEROTIER_ONE_USE_IPTABLES_NFT=false + - ZEROTIER_ONE_GATEWAY_MODE=inbound # - ZEROTIER_ONE_NETWORK_IDS=yourNetworkID diff --git a/scripts/entrypoint-router.sh b/scripts/entrypoint-router.sh index f991a3b..8672be8 100755 --- a/scripts/entrypoint-router.sh +++ b/scripts/entrypoint-router.sh @@ -1,8 +1,6 @@ #!/usr/bin/env sh set -Eeo pipefail -echo "$(date) - launching ZeroTier-One in routing mode" - if [ "${1:0:1}" = '-' ]; then set -- zerotier-one "$@" fi @@ -31,6 +29,19 @@ if [ "$(id -u)" = '0' -a -d "${CONFIG_DIR}" ]; then chown -Rc "${PUID}:${PGID}" "${CONFIG_DIR}" fi +# is routing enabled? +if [ $(sysctl -n net.ipv4.ip_forward) -ne 1 ] ; then + + # no! there is no point in setting up rules or termination handler + echo "$(date) - IPv4 forwarding not enabled - launching ZeroTier-One in non-routing mode" + + # just exec the client (this script ends here) + exec "$@" + +fi + +echo "$(date) - launching ZeroTier-One in routing mode" + # use an appropriate default for a local physical interface # (using eth0 maintains backwards compatibility) PHY_IFACES="${ZEROTIER_ONE_LOCAL_PHYS:-"eth0"}" @@ -40,21 +51,50 @@ IPTABLES_CMD=iptables # but support an override to use iptables-nft [ "${ZEROTIER_ONE_USE_IPTABLES_NFT}" = "true" ] && IPTABLES_CMD=iptables-nft +# the default forwarding mode is inbound (backwards compatible) +GATEWAY_MODE="${ZEROTIER_ONE_GATEWAY_MODE:-"inbound"}" + # the wildcard for the local zerotier interface is ZT_IFACE="zt+" # function to add and remove the requisite rules # - $1 is either "A" (add) or "D" (delete) +# - $2 is comment update_iptables() { - for PHY_IFACE in ${PHY_IFACES} ; do - ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${PHY_IFACE} -j MASQUERADE - ${IPTABLES_CMD} -${1} FORWARD -i ${PHY_IFACE} -o ${ZT_IFACE} -m state --state RELATED,ESTABLISHED -j ACCEPT - ${IPTABLES_CMD} -${1} FORWARD -i ${ZT_IFACE} -o ${PHY_IFACE} -j ACCEPT - done + case "${GATEWAY_MODE}" in + "inbound" ) + echo "$2 ${IPTABLES_CMD} rules for inbound traffic (ZeroTier to local interfaces ${PHY_IFACES})" + for PHY_IFACE in ${PHY_IFACES} ; do + ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${PHY_IFACE} -j MASQUERADE + ${IPTABLES_CMD} -${1} FORWARD -i ${PHY_IFACE} -o ${ZT_IFACE} -m state --state RELATED,ESTABLISHED -j ACCEPT + ${IPTABLES_CMD} -${1} FORWARD -i ${ZT_IFACE} -o ${PHY_IFACE} -j ACCEPT + done + ;; + "outbound" ) + echo "$2 ${IPTABLES_CMD} rules for outbound traffic (local interfaces ${PHY_IFACES} to ZeroTier)" + ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${ZT_IFACE} -j MASQUERADE + for PHY_IFACE in ${PHY_IFACES} ; do + ${IPTABLES_CMD} -${1} FORWARD -i ${ZT_IFACE} -o ${PHY_IFACE} -m state --state RELATED,ESTABLISHED -j ACCEPT + ${IPTABLES_CMD} -${1} FORWARD -i ${PHY_IFACE} -o ${ZT_IFACE} -j ACCEPT + done + ;; + "both" ) + echo "$2 ${IPTABLES_CMD} rules for bi-directional traffic (local interfaces ${PHY_IFACES} to/from ZeroTier)" + ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${ZT_IFACE} -j MASQUERADE + for PHY_IFACE in ${PHY_IFACES} ; do + ${IPTABLES_CMD} -t nat -${1} POSTROUTING -o ${PHY_IFACE} -j MASQUERADE + ${IPTABLES_CMD} -${1} FORWARD -i ${ZT_IFACE} -o ${PHY_IFACE} -j ACCEPT + ${IPTABLES_CMD} -${1} FORWARD -i ${PHY_IFACE} -o ${ZT_IFACE} -j ACCEPT + done + ;; + * ) + echo "Warning: ZEROTIER_ONE_GATEWAY_MODE=${GATEWAY_MODE} is not supported - ignored" + ;; + esac } # add rules to set up NAT-routing -update_iptables "A" +update_iptables "A" "adding" # define where the ZeroTier daemon will write its output (if any) TAIL_PIPE=$(mktemp /tmp/zerotier-ipc-XXXXXX) @@ -80,7 +120,7 @@ termination_handler() { echo "$(date) - terminating ZeroTier-One" # remove rules - update_iptables "D" + update_iptables "D" "removing" # relay the termination message to the daemon if [ -d "/proc/${ZEROTIER_DAEMON_PID}" ] ; then