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/[email protected]

[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:

#!/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:

#!/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:

systemctl enable [email protected]
Written on October 28, 2023