日本語 | English
Engineered for Reliability: Dynamic, High-Performance Firewall Hole Punching for ZeroTier
This tool dramatically increases the success rate of ZeroTier P2P (Direct) connections by dynamically managing UFW firewall rules via IPSet. It solves the critical problem of UDP packet blocking in strict Linux firewall environments without compromising security by opening wide port ranges.
- Architecture & Data Flow
- Key Features
- Components & Configuration
- Observability & Troubleshooting
- Security Model
- Uninstall
- License
Detailed interaction between the system components: ZeroTier, the Python update logic, Systemd timers, and the Kernel's Netfilter subsystem.
graph TD
subgraph "ZeroTier Layer"
ZT["ZeroTier One Daemon"]
API["Local API :9993"]
Peer["Remote Peer"]
ZT <-->|UDP P2P| Peer
ZT -->|Expose Status| API
end
subgraph "Control Plane (User Space)"
Timer["Systemd Timer"] -->|Trigger 1min| Scriptor["update-zt-firewall.py"]
Scriptor -->|GET /peer| API
Scriptor -->|Parse IPs| Logic{"Extract External IPs"}
Logic -->|Update| IPSetUtils["ipset Command"]
end
subgraph "Data Plane (Kernel Space)"
IPSetUtils -->|Swap Atomic| KernelSet["Kernel IPSet (Hash:IP)"]
Netfilter["Netfilter / UFW Chain"]
Netfilter -->|Match src| KernelSet
KernelSet -->|Allow/Drop| Netfilter
end
Peer -.->|UDP Packet| Netfilter
Unlike traditional firewall scripts that append iptables rules linearly (O(n) complexity), this tool utilizes IPSet (hash:ip type).
- Linear Rules (Bad): 100 peers = 100 sequential rule checks for every incoming packet.
- IPSet (Good): 100 peers = 1 hash lookup (O(1)). This ensures that CPU load remains negligible even if your ZeroTier network scales to thousands of peers.
A common pitfall in persisting ipsets with UFW is the circular dependency/race condition during boot:
ufw.serviceloads rules.- Rules reference
ipsetsets (e.g.,zt-peers-v4). - If the sets don't exist yet, UFW fails to start.
- Standard persistence services often start after networking or in parallel with UFW, leading to unpredictable boot failures.
Our Solution:
We utilize the UFW before.init hook (/etc/ufw/before.init).
This shell script is executed synchronously immediately before UFW commands are run. By injecting the ipset create commands here, we guarantee:
- Sets exist before any rule references them.
- Zero dependency on Systemd service ordering.
- 100% Reboot Safety.
The Python script updates the IPSet using a Swap-Describe pattern:
- Create a temporary set (
zt-peers-v4-tmp). - Populate it with fresh IPs.
- Atomically swap the temp set with the live set (
ipset swap). - Destroy the temp set. This ensures there is zero downtime or "open window" where packets could be dropped during an update.
- Linux (Debian/Ubuntu based) with
systemd,ufw,ipset,python3. - ZeroTier One installed and running.
git clone https://github.com/jassdack/zt-udp-puncher.git
cd zt-udp-puncher
chmod +x install_zt_puncher.sh
sudo ./install_zt_puncher.shThe core logic is in /usr/local/bin/update-zt-firewall.py. You can modify the generated systemd service file to override these variables if needed.
| Variable | Default | Description |
|---|---|---|
ZT_API_URL |
http://localhost:9993/peer |
Local ZeroTier API Endpoint |
ZT_TOKEN_PATH |
/var/lib/zerotier-one/authtoken.secret |
Path to the API authentication token |
ZT_TIMEOUT |
10 |
Timeout (seconds) for API calls and subprocs |
ZT_IPSET_V4 |
zt-peers-v4 |
Name of the IPv4 ipset |
ZT_IPSET_V6 |
zt-peers-v6 |
Name of the IPv6 ipset |
systemctl status zt-firewall-update.timer
systemctl status zt-firewall-update.serviceVerify that the before.rules injection is working and matching packets:
# Check UFW status (look for "zt-peers-v4")
sudo ufw status verbose
# List the actual IPs currently whitelisted
sudo ipset list zt-peers-v4Structured logging is sent to journald via stdout/stderr.
journalctl -u zt-firewall-update.service -fWe consciously chose hash:ip (single IP addresses) over hash:net (subnets).
ZeroTier peers often reside on dynamic residential IPs. Whitelisting entire subnets (e.g., /24) would expose your UDP ports to potential neighbors or other users on the same ISP node.
By strictly limiting the whitelist to the exact detected global IP of the authenticated peer, we maintain the "Least Privilege" principle.
Cleanly remove all services, timers, scripts, and revert UFW/IPSet configurations:
sudo ./uninstall_zt_puncher.shThis project is licensed under the MIT License - see the LICENSE file for details.