Site Tools


Sidebar

Work

Projects Travel Singapore Socials (External)

projects:homelab:openwrt_tailscale_notes

OpenWrt Tailscale Notes

Tailscale Exit Node with NAT Hairpin (Loopback)

Prerequisite: OpenWrt Tailscale guide done (zone, masq, forwarding, exit node).

All steps via SSH.

  1. Create /etc/firewall.tailscale-reflect.sh:
#!/bin/sh
nft insert rule inet fw4 dstnat iifname tailscale0 jump dstnat_tailscale 2>/dev/null
 
. /lib/functions/network.sh
network_get_ipaddr WANIP wan
[ -n "$WANIP" ] || exit 0
 
i=0
while uci -q get firewall.@redirect[$i] >/dev/null 2>&1; do
	if [ "$(uci -q get firewall.@redirect[$i].src)" = "wan" ] \
	   && [ "$(uci -q get firewall.@redirect[$i].target)" = "DNAT" ]; then
		dest_ip=$(uci -q get firewall.@redirect[$i].dest_ip)
		dport=$(uci -q get firewall.@redirect[$i].src_dport)
		dest_port=$(uci -q get firewall.@redirect[$i].dest_port)
		[ -n "$dest_port" ] || dest_port="$dport"
		protos=$(uci -q get firewall.@redirect[$i].proto)
		[ -n "$protos" ] || protos="tcp"
		for proto in $protos; do
			nft add rule inet fw4 dstnat_tailscale \
				ip daddr "$WANIP" "$proto" dport "$dport" \
				dnat ip to "${dest_ip}:${dest_port}" \
				comment "tailscale-reflect-auto" 2>/dev/null
			nft add rule inet fw4 srcnat_lan \
				ip daddr "$dest_ip" "$proto" dport "$dest_port" \
				snat ip to "$WANIP" \
				comment "tailscale-reflect-auto" 2>/dev/null
		done
	fi
	i=$((i + 1))
done
  1. Make it executable:
chmod 755 /etc/firewall.tailscale-reflect.sh
  1. Register as firewall include:
uci add firewall include
uci set firewall.@include[-1].type='script'
uci set firewall.@include[-1].path='/etc/firewall.tailscale-reflect.sh'
uci set firewall.@include[-1].fw4_compatible='1'
uci commit firewall
  1. Apply:
fw4 reload
  1. Verify:
nft list chain inet fw4 dstnat | grep tailscale
nft list ruleset | grep -A6 'chain dstnat_tailscale'
  1. Test from a device using this router as Tailscale exit node.

Notes

  • Adding/removing a wan-sourced port forward auto-updates on next reload – nothing else to do.
  • Self-heals on WAN IP change (reload already triggers via stock /etc/hotplug.d/iface/20-firewall).
  • Don't use “Enable NAT Loopback” / reflection_zone for the tailscale zone – fails silently, fw4 can't detect the interface's subnet.
  • fw4 only auto-wires the dstnat dispatch rule for a zone if a UCI redirect uses src = that zone. Since none do here, the script inserts it manually every reload – required, not optional.
projects/homelab/openwrt_tailscale_notes.txt · Last modified: by Andrew Yong