To ensure network reliability, I run both Tailscale and ZeroTier on my OpenWrt router. I primarily use Tailscale, but ZeroTier serves as an excellent failover that works wonders when Tailscale struggles with connectivity.
The Symptom: Mysterious Packet Drop Logs
It all started with a seemingly minor anomaly. While checking the OpenWrt system logs, I noticed recurring error messages printed by tailscaled (the Tailscale daemon):
Fri Jul 11 21:10:43 2025 daemon.err tailscaled[3274]: Drop: IPProto-44{[fd7a:...] > [fd7a:...]} ... unknown-protocol-44Fri Jul 11 21:10:53 2025 daemon.err tailscaled[3274]: [RATELIMIT] format("%s: %s %d %s\n%s") (8 dropped)The logs indicated that Tailscale was dropping packets of a protocol it didn’t recognize (IPProto-44). Both the source and destination IPs belonged to devices within my internal network. Even stranger, this traffic appeared unrelated to Tailscale itself. After some troubleshooting, I identified the culprit: these “abnormal” packets were actually generated by the ZeroTier client attempting to communicate with its Moon relay servers.
Deep Dive: It’s the Routing Table
Why was ZeroTier traffic leaking into Tailscale’s domain? The answer lies in the Linux routing rules.
By SSHing into the OpenWrt instance and running ip rule show, the root cause became clear:
root@ImmortalWrt:~# ip rule show0: from all lookup local...5270: from all lookup 5232766: from all lookup main...Linux routing rules are executed based on priority (prio), where a lower number indicates higher priority. This output reveals the following logic:
The system first checks if the traffic is destined for the router itself (lookup local). If not, it moves to the rule with priority 5270. This rule states: “For all traffic, first check routing table 52.” Table 52 is a dedicated routing table created by Tailscale upon installation, which only contains routes to other Tailscale nodes. Only if no match is found in table 52 does the traffic proceed to the main routing table (where our standard internet gateway resides).
The conclusion is obvious: Tailscale implements an “aggressive” high-priority rule that intercepts all outgoing traffic from the router, including ZeroTier’s. When ZeroTier’s specific signaling packets are forced into the Tailscale network, Tailscale—unfamiliar with the protocol—simply drops them.
The Solution
To resolve this, we shouldn’t modify Tailscale’s rules directly, as they are managed by the daemon. Instead, we should establish a “dedicated bypass” for ZeroTier with an even higher priority.
Our strategy: Create a new routing rule that explicitly tells the system: “Any traffic originating from a ZeroTier virtual interface must look up the main routing table directly,” ensuring this rule has a higher priority (a lower number) than Tailscale’s 5270.
Implementation on OpenWrt
Step 1: (Recommended) Install the Full ip Utility
The default ip command in OpenWrt (provided by busybox) may lack certain features. Installing ip-full prevents various compatibility issues.
opkg updateopkg install ip-fullStep 2: Create the Service Script
Create a new init script using vi or nano.
nano /etc/init.d/zerotier-policy-routePaste the following script into the file.
WARNINGPlease modify the
ZT_IFACEvariable to match your environment. You can find your ZeroTier interface name by runningifconfig | grep zt.
#!/bin/sh /etc/rc.common
## This script creates policy routing rules for ZeroTier to coexist with Tailscale.# It prevents ZeroTier's traffic from being intercepted by Tailscale's high-priority rule.# Version 3: Intelligently handles IPv4 and IPv6 addresses.#
START=99STOP=15
# --- CONFIGURATION ---# The name of your ZeroTier virtual network interface. Find it with `ifconfig | grep zt`.ZT_IFACE="ztypia4gba"
# The priority for the new routing rule. Must be lower (higher priority) than Tailscale's (usually 5270).RULE_PRIO="5260"
# Tag for system log entries.LOG_TAG="zerotier-policy-route"# --- END CONFIGURATION ---
add_rules() { # Get all global/unique IP addresses (v4 and v6), excluding link-local (fe80::) addresses. IP_LIST=$(ip addr show dev "${ZT_IFACE}" | grep -E 'inet|inet6' | grep -v ' fe80::' | awk '{print $2}' | cut -d'/' -f1)
if [ -z "${IP_LIST}" ]; then logger -t "${LOG_TAG}" "Warning: No usable IP addresses found on interface ${ZT_IFACE}." return fi
for ip in ${IP_LIST}; do # Check if a rule for this IP already exists to avoid duplication. if ! ip rule | grep -q "from ${ip} lookup main"; then logger -t "${LOG_TAG}" "Adding rule for ${ip} with priority ${RULE_PRIO}."
# CRITICAL FIX: Use the '-6' flag for IPv6 addresses. if echo "${ip}" | grep -q ":"; then ip -6 rule add from "${ip}" lookup main prio "${RULE_PRIO}" else ip rule add from "${ip}" lookup main prio "${RULE_PRIO}" fi else logger -t "${LOG_TAG}" "Rule for ${ip} already exists. Skipping." fi done}
remove_rules() { IP_LIST=$(ip addr show dev "${ZT_IFACE}" | grep -E 'inet|inet6' | grep -v ' fe80::' | awk '{print $2}' | cut -d'/' -f1) if [ -z "${IP_LIST}" ]; then return; fi
for ip in ${IP_LIST}; do if ip rule | grep -q "from ${ip} lookup main"; then logger -t "${LOG_TAG}" "Removing rule for ${ip}."
# Use the '-6' flag for deletion of IPv6 rules as well. if echo "${ip}" | grep -q ":"; then ip -6 rule del from "${ip}" lookup main prio "${RULE_PRIO}" else ip rule del from "${ip}" lookup main prio "${RULE_PRIO}" fi fi done}
start() { logger -t "${LOG_TAG}" "Service starting, applying rules..." add_rules}
stop() { logger -t "${LOG_TAG}" "Service stopping, removing rules..." remove_rules}
reload() { logger -t "${LOG_TAG}" "Service reloading..." stop start}Step 3: Authorize and Enable the Service
After saving the file, execute the following commands in the terminal:
# Grant execution permissionschmod +x /etc/init.d/zerotier-policy-route
# Enable the service to run on boot/etc/init.d/zerotier-policy-route enable
# Start the service immediately/etc/init.d/zerotier-policy-route startStep 4: Verification
Run ip rule show. You should see output similar to the following, where the 5260 rules for each ZeroTier IP are correctly positioned above Tailscale’s rules.
0: from all lookup local...5260: from 172.27.72.251 lookup main5260: from fddd:ec77:... lookup main5260: from fce1:9571:... lookup main5270: from all lookup 52...Implementation on Linux Servers
Since these errors appeared on OpenWrt, it’s highly likely that another Linux server in your Tailscale network is also running both services. We can adapt the script for standard Linux distributions.
Policy Routing Script
sudo nano /usr/local/sbin/zerotier-policy-route.sh#!/bin/bash
## This script creates policy routing rules for all ZeroTier interfaces# to coexist with Tailscale on a standard Linux system.#
set -e # Exit immediately if a command exits with a non-zero status.
# --- CONFIGURATION ---# The priority for the new routing rule.# This MUST be a lower number (higher priority) than Tailscale's rule (usually 5270).RULE_PRIO="5260"LOG_TAG="zerotier-policy-route"# --- END CONFIGURATION ---
# Find all ZeroTier network interfaces (names starting with 'zt').ZT_INTERFACES=$(ip -o link show | awk -F': ' '{print $2}' | grep '^zt')
if [ -z "$ZT_INTERFACES" ]; then logger -t "$LOG_TAG" "No ZeroTier interfaces found. Exiting." exit 0fi
# Function to add rules for all found interfacesadd_rules() { logger -t "$LOG_TAG" "Applying policy routing rules..." for iface in $ZT_INTERFACES; do # Get all global/unique IP addresses (v4 and v6), excluding link-local (fe80::) IP_LIST=$(ip addr show dev "$iface" | grep -E 'inet|inet6' | grep -v 'fe80::' | awk '{print $2}' | cut -d'/' -f1)
if [ -z "$IP_LIST" ]; then logger -t "$LOG_TAG" "No usable IP addresses on interface $iface. Skipping." continue fi
for ip in $IP_LIST; do if ! ip rule | grep -q "from $ip lookup main"; then logger -t "$LOG_TAG" "Adding rule for $ip (on $iface) with priority $RULE_PRIO." if [[ "$ip" == *":"* ]]; then ip -6 rule add from "$ip" lookup main prio "$RULE_PRIO" else ip rule add from "$ip" lookup main prio "$RULE_PRIO" fi fi done done}
# Function to remove rulesremove_rules() { logger -t "$LOG_TAG" "Removing policy routing rules..." while ip rule | grep -q "prio $RULE_PRIO"; do RULE_TO_DELETE=$(ip rule | grep "prio $RULE_PRIO" | head -n 1) logger -t "$LOG_TAG" "Deleting rule: $RULE_TO_DELETE" # We need to parse the rule string to delete it correctly ip rule del $(echo ${RULE_TO_DELETE} | sed 's/[0-9]*:\s*//') done}
case "$1" in start) add_rules ;; stop) remove_rules ;; restart) remove_rules add_rules ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 ;;esac
exit 0Set the permissions:
sudo chmod +x /usr/local/sbin/zerotier-policy-route.shSystemd Service Unit
sudo nano /etc/systemd/system/zerotier-policy-route.service[Unit]Description=ZeroTier Policy Routing Fix for Tailscale CoexistenceAfter=network-online.targetWants=network-online.targetAfter=zerotier-one.service tailscaled.service
[Service]Type=oneshotRemainAfterExit=yesExecStart=/usr/local/sbin/zerotier-policy-route.sh startExecStop=/usr/local/sbin/zerotier-policy-route.sh stop
[Install]WantedBy=multi-user.targetFinally, enable and start the service:
sudo systemctl enable zerotier-policy-route.servicesudo systemctl start zerotier-policy-route.serviceVerify with ip rule show; you should see the entries mapped:
0: from all lookup local5210: from all fwmark 0x80000/0xff0000 lookup main...5260: from 172.27.72.101 lookup main <-- Fixed5260: from 172.27.62.101 lookup main <-- Fixed5270: from all lookup 52...With this setup, the annoying packet drop logs in logread will finally vanish.