field noteWireGuard · Tailscale · Linux routing

Two VPNs fighting over the same packets

With a full-tunnel WireGuard VPN up, SSH over the tailnet timed out. The fix was one routing rule in the right place.

An admin laptop ran a full-tunnel WireGuard VPN and Tailscale at the same time. Both came up cleanly, yet SSH to any tailnet address (100.64.0.0/10) timed out.

What was going on

A full-tunnel VPN sends everything through itself using Linux policy routing: a rule, consulted before the normal routing table, that points traffic at the VPN's own table. Tailscale also uses policy routing, with its routes in table 52. Whichever rule matches first wins, and here the VPN's rule caught the tailnet traffic before Tailscale's table was ever looked at, so the packets left through the wrong tunnel.

ip rule show                 # the order rules are tried in
ip route get 100.64.0.12     # which table and device win for one address
Two commands that show the problem in seconds.

The fix

Add one rule, ahead of the VPN's, that sends the tailnet range to Tailscale's table, and keep it with the VPN config so it comes and goes with the tunnel.

[Interface]
# ...
PostUp  = ip rule add to 100.64.0.0/10 lookup 52 priority 2500
PreDown = ip rule del to 100.64.0.0/10 lookup 52 priority 2500
In the WireGuard config, so the rule lives exactly as long as the tunnel.

What I took from it

  • When two VPNs disagree, read ip rule before anything else: rule order decides, not the order the tunnels came up.
  • Put the fix where the cause lives. A rule added by hand disappears on the next reconnect.