====== OpenWrt Tailscale Notes ====== ===== Tailscale Exit Node with NAT Hairpin (Loopback) ===== Prerequisite: [[https://openwrt.org/docs/guide-user/services/vpn/tailscale/start|OpenWrt Tailscale guide]] done (zone, masq, forwarding, exit node). All steps via SSH. - 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 - Make it executable: chmod 755 /etc/firewall.tailscale-reflect.sh - 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 - Apply: fw4 reload - Verify: nft list chain inet fw4 dstnat | grep tailscale nft list ruleset | grep -A6 'chain dstnat_tailscale' - 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.