diff --git a/_posts/2023-10-28-zerotier-hooks-linux.md b/_posts/2023-10-28-zerotier-hooks-linux.md new file mode 100644 index 0000000000000..07ff2365489c7 --- /dev/null +++ b/_posts/2023-10-28-zerotier-hooks-linux.md @@ -0,0 +1,78 @@ +--- +layout: post +title: ZeroTier Hooks on Linux +--- + +ZeroTier on Linux lacks any facilities for running hooks when it connects to or disconnects from a network. +Other VPN software provides this feature so the user can do things like setting up and tearing down firewall rules. +Luckily, systemd provides a trivial way to solve this problem by starting and stopping a service when a device shows up or disappears. + +---- + +# The Code + +The following `systemd` unit should be placed in a file like `/etc/systemd/system/network-hooks@.service` + +```yaml +[Unit] +Description=Hooks for Network Interfaces +BindsTo=sys-subsystem-net-devices-%i.device +After=sys-subsystem-net-devices-%i.device + +[Service] +Type=oneshot +ExecStartPre=-/usr/local/lib/%i.down +ExecStart=/usr/local/lib/%i.up +ExecStop=/usr/local/lib/%i.down +RemainAfterExit=yes + +[Install] +WantedBy=sys-subsystem-net-devices-%i.device +``` + +An example "up" hook for an example `ztFooBar` interface for a network with subnet `10.11.12.0/24` would be placed in `/usr/local/lib/ztFooBar.up` and look like: + +```bash +#!/usr/bin/env bash + +# This forwards TCP and UDP traffic on port 1234 to 10.11.12.13:1234. It's assumed the forwardee is in the ZeroTier network. +iptables -t nat -I PREROUTING 1 -i eno1 -p tcp --dport 1234 -j DNAT --to-destination 10.11.12.13:1234 +iptables -t nat -I PREROUTING 1 -i eno1 -p udp --dport 1234 -j DNAT --to-destination 10.11.12.13:1234 +# These rules are placed in the DOCKER-USER chain, which is where forwarding rules need to go if you have Docker installed. Without Docker installed, these would go in the FORWARD chain instead. +iptables -I DOCKER-USER 1 -i eno1 -p tcp -d 10.11.12.13 --dport 1234 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT +iptables -I DOCKER-USER 1 -i eno1 -p udp -d 10.11.12.13 --dport 1234 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT + +# This allows all traffic to be forwarded from the ZeroTier network into eno1 +iptables -I DOCKER-USER 1 -i ztFooBar -o eno1 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT + +# This allows established and related packets to flow from eno1 into ZeroTier, so packets in the other direction belonging to connections allowed by the rule above can reach the right host. +iptables -I DOCKER-USER 1 -i eno1 -o ztFooBar -m state --state ESTABLISHED,RELATED -j ACCEPT + +# This changes the source IP address of the traffic leaving through eno1, forwarded from the ZeroTier network. +iptables -t nat -I POSTROUTING 1 -o eno1 -s 10.11.12.0/24 -j MASQUERADE +``` + +The corresponding `/usr/local/lib/ztFooBar.down` file would look like: + +```bash +#!/usr/bin/env bash + +iptables -D DOCKER-USER -i eno1 -o ztFooBar -m state --state ESTABLISHED,RELATED -j ACCEPT +iptables -D DOCKER-USER -i ztFooBar -o eno1 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT +iptables -t nat -D POSTROUTING -o eno1 -s 10.11.12.0/24 -j MASQUERADE + +iptables -t nat -D PREROUTING -i eno1 -p tcp --dport 1234 -j DNAT --to-destination 10.11.12.13:1234 +iptables -t nat -D PREROUTING -i eno1 -p udp --dport 1234 -j DNAT --to-destination 10.11.12.13:1234 +iptables -D DOCKER-USER -i eno1 -p tcp -d 10.11.12.13 --dport 6881 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT +iptables -D DOCKER-USER -i eno1 -p udp -d 10.11.12.13 --dport 6881 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT + +exit 0 +``` + +It's best if the `down` hook is idempotent. + +You can enable hooks for the example network interface by running this command: + +```sh +systemctl enable network-hooks@ztFooBar.service +```