Files
docker-tailscale-network-fix/docker-tailscale-route.sh
KoloMl 3886d956cc Add docker-tailscale-route: fix Docker losing internet under Tailscale exit-node
Tailscale's exit-node "route all traffic" mode captures Docker's
forwarded container traffic into its tunnel and drops it as an
unrecognized flow, causing every outbound request from inside a
container to hang until timeout. This installs a systemd timer that
keeps an ip rule in place routing Docker's bridge subnet around
Tailscale's catch-all route instead.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-30 12:34:35 +04:00

58 lines
2.7 KiB
Bash
Executable File

#!/bin/bash
#
# Written by an LLM (Claude Code) — review before trusting on a machine
# other than the one this was diagnosed on. See README.md.
#
# Ensures Docker's own bridge traffic bypasses Tailscale's exit-node
# catch-all route.
#
# Background: a machine running Tailscale with an exit node and "route
# all traffic" enabled installs a catch-all `ip rule` that captures
# every packet not explicitly exempted and sends it out via the
# tailscale0 interface. Tailscale exempts its own daemon traffic from
# that capture with a firewall mark, but it has no way to exempt
# traffic that only passes THROUGH the host (forwarded) rather than
# originating from it.
#
# Docker containers' outbound traffic (dev environments, package
# managers, anything containers do that talks to the internet) is
# exactly that: forwarded traffic, not locally-originated. With no
# exemption, it gets swept into Tailscale's catch-all route, sent out
# via tailscale0, and dropped by Tailscale's own anti-spoofing firewall
# rule because it isn't a recognized Tailscale-tunneled flow. Symptom:
# every DNS lookup / outbound HTTP request from inside a container
# hangs until timeout whenever Tailscale is connected.
#
# The fix: keep a higher-priority `ip rule` in place that routes
# Docker's private bridge subnet through the normal routing table (the
# real gateway/NIC) instead of Tailscale's catch-all. This does NOT
# weaken or bypass the VPN for anything else on the host — it only
# affects packets forwarded from Docker's own bridge networks, which
# were never meant to be tunneled through the exit node in the first
# place.
#
# Defaults assume Docker's default address-pool behavior (bridge
# networks allocated from 172.17.0.0/16 through 172.31.0.0/16, all
# within 172.16.0.0/12). If you have more than ~15 concurrent Docker
# networks on a machine, Docker falls back to other private ranges
# (192.168.0.0/20 blocks, then 10.0.0.0/8) that this default won't
# cover — override DOCKER_SUBNET in /etc/default/docker-tailscale-route
# if that applies to you. Widening the range casually isn't advisable:
# 192.168.0.0/16 and 10.0.0.0/8 are also real LAN/VPN-subnet-route
# space, and blanket-exempting them could route traffic around the
# tunnel that's actually supposed to go through it.
set -euo pipefail
DOCKER_SUBNET="172.16.0.0/12"
RULE_PRIORITY="5200"
TARGET_TABLE="main"
# Optional overrides, e.g. if Docker's address pool differs on this
# machine or the priority collides with something else.
[ -f /etc/default/docker-tailscale-route ] && . /etc/default/docker-tailscale-route
if ! ip rule list | grep -q "from ${DOCKER_SUBNET} lookup ${TARGET_TABLE}"; then
ip rule add from "$DOCKER_SUBNET" lookup "$TARGET_TABLE" priority "$RULE_PRIORITY"
fi