cmd/tailscaled, version/distro: default to userspace-networking on Crostini

cros-garcon NULL-derefs on cold-boot netlink enumeration when
tailscale0 is present, preventing the Crostini container and
ChromeOS Terminal from starting cleanly. This is an upstream
ChromiumOS bug in cros-garcon; tailscaled can work around it
by defaulting to userspace-networking mode on Crostini.

Tailscale SSH continues to work via tailscaled's netstack.
Users can override with --tun=tailscale0 on ChromeOS builds
where cros-garcon is fixed.

Crostini is detected via /opt/google/cros-containers/bin/garcon,
which is present in every Crostini penguin container.

ssh/tailssh extends the existing Debian default-PATH case to
cover Crostini, since Crostini is Debian-based and benefits
from the same SSH PATH defaults.

RELNOTE: Crostini now defaults to userspace-networking.

Fixes #19488
Updates #12090

Signed-off-by: ferrumclaudepilgrim <ferrumclaudepilgrim@users.noreply.github.com>
This commit is contained in:
ferrumclaudepilgrim 2026-05-03 20:14:30 -05:00 committed by Brad Fitzpatrick
parent a6ab7efa4f
commit 3f70abdc6f
3 changed files with 17 additions and 1 deletions

View File

@ -87,6 +87,16 @@ func defaultTunName() string {
// See https://github.com/tailscale/tailscale-synology/issues/35
return "tailscale0,userspace-networking"
}
if buildfeatures.HasNetstack && distro.Get() == distro.Crostini {
// cros-garcon NULL-derefs on cold-boot netlink interface
// enumeration when tailscale0 is present, preventing the
// Crostini container and ChromeOS Terminal from starting
// cleanly. Default to userspace-networking until the
// upstream ChromiumOS bug is fixed.
// See https://github.com/tailscale/tailscale/issues/12090
// See https://issuetracker.google.com/issues/517069318
return "userspace-networking"
}
}
return "tailscale0"
}

View File

@ -93,7 +93,7 @@ func defaultPathForUser(u *user.User) string {
}
isRoot := u.Uid == "0"
switch distro.Get() {
case distro.Debian:
case distro.Debian, distro.Crostini:
hi := hostinfo.New()
if hi.Distro == "ubuntu" {
// distro.Get's Debian includes Ubuntu. But see if it's actually Ubuntu.

View File

@ -19,6 +19,7 @@
const (
Debian = Distro("debian")
Crostini = Distro("crostini") // ChromeOS Crostini Linux container; Debian-based
Arch = Distro("arch")
Synology = Distro("synology")
OpenWrt = Distro("openwrt")
@ -84,6 +85,11 @@ func linuxDistro() Distro {
// Currently supported product families:
// - UDM (UniFi Dream Machine, UDM-Pro)
return UBNT
case have("/opt/google/cros-containers/bin/garcon"):
// ChromeOS Crostini ships /opt/google/cros-containers/bin/garcon
// in every penguin container. MUST be checked before Debian since
// Crostini is Debian-based.
return Crostini
case have("/etc/debian_version"):
return Debian
case have("/etc/arch-release"):