diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 0f154fd09..c7763229d 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -42,6 +42,7 @@ type linuxRouter struct { logf func(fmt string, args ...any) tunname string netMon *netmon.Monitor + health *health.Tracker unregNetMon func() addrs map[netip.Prefix]bool routes map[netip.Prefix]bool @@ -81,15 +82,16 @@ func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Moni ambientCapNetAdmin: useAmbientCaps(), } - return newUserspaceRouterAdvanced(logf, tunname, netMon, cmd) + return newUserspaceRouterAdvanced(logf, tunname, netMon, cmd, health) } -func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netMon *netmon.Monitor, cmd commandRunner) (Router, error) { +func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netMon *netmon.Monitor, cmd commandRunner, health *health.Tracker) (Router, error) { r := &linuxRouter{ logf: logf, tunname: tunname, netfilterMode: netfilterOff, netMon: netMon, + health: health, cmd: cmd, @@ -420,6 +422,7 @@ func (r *linuxRouter) Set(cfg *Config) error { } } r.statefulFiltering = cfg.StatefulFiltering + r.updateStatefulFilteringWithDockerWarning(cfg) // Issue 11405: enable IP forwarding on gokrazy. advertisingRoutes := len(cfg.SubnetRoutes) > 0 @@ -430,6 +433,53 @@ func (r *linuxRouter) Set(cfg *Config) error { return multierr.New(errs...) } +var warnStatefulFilteringWithDocker = health.NewWarnable() + +func (r *linuxRouter) updateStatefulFilteringWithDockerWarning(cfg *Config) { + // If stateful filtering is disabled, clear the warning. + if !r.statefulFiltering { + r.health.SetWarnable(warnStatefulFilteringWithDocker, nil) + return + } + + advertisingRoutes := len(cfg.SubnetRoutes) > 0 + + // TODO(andrew-d,maisem): we might want to check if we're running in a + // container, since, if so, stateful filtering might prevent other + // containers from connecting through the Tailscale in this container. + // + // For now, just check for the case where we're running Tailscale on + // the host and Docker is also running. + + // If this node isn't a subnet router or exit node, then we would never + // have allowed traffic from a Docker container in to Tailscale, since + // there wouldn't be an AllowedIP for the container's source IP. So we + // don't need to warn in this case. + // + // cfg.SubnetRoutes contains all subnet routes for the node, including + // the default route (0.0.0.0/0 or ::/0) if this node is an exit node. + if advertisingRoutes { + // Check for the presence of a Docker interface and warn if it's found + // on the system. + // + // TODO(andrew-d): do a better job at detecting Docker, e.g. by looking + // for it in the $PATH or by checking for the presence of the Docker + // socket/daemon/etc. + ifstate := r.netMon.InterfaceState() + if _, found := ifstate.Interface["docker0"]; found { + r.health.SetWarnable(warnStatefulFilteringWithDocker, fmt.Errorf(""+ + "Stateful filtering is enabled and Docker was detected; this may prevent Docker containers "+ + "on this host from connecting to Tailscale nodes. "+ + "See https://tailscale.com/s/stateful-docker", + )) + return + } + } + + // If we get here, then we have no warnings; clear anything existing. + r.health.SetWarnable(warnStatefulFilteringWithDocker, nil) +} + // UpdateMagicsockPort implements the Router interface. func (r *linuxRouter) UpdateMagicsockPort(port uint16, network string) error { if r.nfr == nil { diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go index 3dc5a4b8e..a62855e8a 100644 --- a/wgengine/router/router_linux_test.go +++ b/wgengine/router/router_linux_test.go @@ -22,6 +22,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "github.com/vishvananda/netlink" "go4.org/netipx" + "tailscale.com/health" "tailscale.com/net/netmon" "tailscale.com/net/tsaddr" "tailscale.com/tstest" @@ -369,7 +370,8 @@ ip route add throw 192.168.0.0/24 table 52` + basic, defer mon.Close() fake := NewFakeOS(t) - router, err := newUserspaceRouterAdvanced(t.Logf, "tailscale0", mon, fake) + ht := new(health.Tracker) + router, err := newUserspaceRouterAdvanced(t.Logf, "tailscale0", mon, fake, ht) router.(*linuxRouter).nfr = fake.nfr if err != nil { t.Fatalf("failed to create router: %v", err)