Skip to content

tailscaled is unsupervised: a daemon panic leaves /etc/resolv.conf pointing at MagicDNS and breaks all DNS in the container #61

Description

@nocnokneo

What happened

tailscaled (1.102.3, installed by this feature) panicked inside a long-running dev container and was never restarted. Because it crashed rather than shut down, it did not restore /etc/resolv.conf, which it had pointed at MagicDNS. The container was left with:

nameserver 100.100.100.100
nameserver fd7a:115c:a1e0::53

and no daemon to answer it, so every DNS lookup in the container black-holed: getent hosts registry.npmjs.org failed, npm/pnpm failed with EAI_AGAIN, and curl hung for its full timeout. The container itself stayed up and looked healthy, so this went unnoticed for hours.

From /var/log/tailscaled.log:

health(warnable=derp-timed-out): error: Tailscale hasn't heard from the '<region>' relay
  server in 5m51s. The server might be temporarily unavailable, or your Internet
  connection might be down.
panic: ipnlocal: watchdog timeout

goroutine 240320 [running]:
tailscale.com/ipn/ipnlocal.(*LocalBackend).reportDeadlock(0x...?)
	tailscale.com@v1.102.3/ipn/ipnlocal/watchdog.go:146 +0xb2
created by time.goFunc
	time/sleep.go:215 +0x2d

Two containers on the same host, both using this feature, panicked within the same minute — one panic each, both preceded by the same DERP stall. So whatever the underlying deadlock is, it is reachable from an ordinary network blip and can hit every container at once.

Why this is a feature-level problem

The panic itself belongs to tailscale/tailscale, but the blast radius is this feature's, for two reasons:

  1. Nothing supervises the daemon. tailscaled-devcontainer-start runs exec setsid /usr/local/sbin/tailscaled in a backgrounded subshell and never looks at it again, and tailscaled-entrypoint calls that script once before exec "$@". If tailscaled exits for any reason, it stays exited for the life of the container.
  2. The crash takes DNS down with it, and the fix is already on disk. The feature's own backup, /etc/resolv.pre-tailscale-backup.conf, was intact and correct the whole time. Nothing consults it unless tailscaled shuts down cleanly.

The result is the worst failure shape: a container whose networking is silently broken in a way that looks like a registry outage, a proxy problem, or a broken image.

Suggestions

  • Supervise tailscaled — a restart loop in the start script, or run it under whatever init the image has. A crash then costs seconds of connectivity instead of the rest of the container's life.
  • Failing that, make the state self-healing: on start (and ideally periodically), if tailscaled is not running, /etc/resolv.conf names 100.100.100.100, and /etc/resolv.pre-tailscale-backup.conf exists, restore the backup. That alone converts "all DNS is dead" into "the tailnet is down", which is both recoverable and diagnosable.

Possibly the same root cause as #57 (rebuild leaves DNS broken, backup missing) and #11.

Environment

  • Feature: ghcr.io/tailscale/codespace/tailscale
  • tailscale/tailscaled 1.102.3 (commit 9329c3677031109ff6d0b80abee0cddc8f35ff6f)
  • Base image: Ubuntu 22.04.5 LTS, Docker (not Codespaces), containers up 7-14 days

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions