Compare commits

...

6 Commits

Author SHA1 Message Date
Nick O'Neill
88e23b6cfd VERSION.txt: this is v1.66.1
Signed-off-by: Nick O'Neill <nick@tailscale.com>
2024-05-09 13:21:31 -07:00
Andrew Dunham
d77499e101 wgengine/router: print Docker warning when stateful filtering is enabled
When Docker is detected on the host and stateful filtering is enabled,
Docker containers may be unable to reach Tailscale nodes (depending on
the network settings of a container). Detect Docker when stateful
filtering is enabled and print a health warning to aid users in noticing
this issue.

We avoid printing the warning if the current node isn't advertising any
subnet routes and isn't an exit node, since without one of those being
true, the node wouldn't have the correct AllowedIPs in WireGuard to
allow a Docker container to connect to another Tailscale node anyway.

Updates #12070

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Idef538695f4d101b0ef6f3fb398c0eaafc3ae281
(cherry picked from commit 5708fc0639)
2024-05-09 11:27:09 -07:00
Andrew Dunham
d904990c14 util/linuxfw: fix table name in DelStatefulRule
Updates #12061
Follow-up to #12072

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I2ba8c4bff14d93816760ff5eaa1a16f17bad13c1
(cherry picked from commit 25e32cc3ae)
2024-05-09 10:46:30 -07:00
Maisem Ali
b10ee74e24 cmd/tailscale: add missing set flags for linux
We were missing `snat-subnet-routes`, `stateful-filtering`
and `netfilter-mode`. Add those to set too.

Fixes #12061

Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit 21abb7f402)
2024-05-09 09:38:17 -07:00
Anton Tolchanov
60d8965df0 util/linuxfw: fix stateful packet filtering in nftables mode
To match iptables:
b5dbf155b1/util/linuxfw/iptables_runner.go (L536)

Updates #12066

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
(cherry picked from commit ac638f32c0)
2024-05-09 07:46:24 -07:00
Nick O'Neill
e2a0fc0bc8 VERSION.txt: this is v1.66.0
Signed-off-by: Nick O'Neill <nick@tailscale.com>
2024-05-08 13:52:24 -07:00
6 changed files with 113 additions and 21 deletions

View File

@@ -1 +1 @@
1.65.0
1.66.1

View File

@@ -58,6 +58,9 @@ type setArgsT struct {
updateCheck bool
updateApply bool
postureChecking bool
snat bool
statefulFiltering bool
netfilterMode string
}
func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
@@ -98,6 +101,10 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")
}
switch goos {
case "linux":
setf.BoolVar(&setArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with --advertise-routes")
setf.BoolVar(&setArgs.statefulFiltering, "stateful-filtering", true, "apply stateful filtering to forwarded packets (subnet routers, exit nodes, etc.)")
setf.StringVar(&setArgs.netfilterMode, "netfilter-mode", defaultNetfilterMode(), "netfilter mode (one of on, nodivert, off)")
case "windows":
setf.BoolVar(&setArgs.forceDaemon, "unattended", false, "run in \"Unattended Mode\" where Tailscale keeps running even after the current GUI user logs out (Windows-only)")
}
@@ -121,6 +128,9 @@ func runSet(ctx context.Context, args []string) (retErr error) {
return err
}
// Note that even though we set the values here regardless of whether the
// user passed the flag, the value is only used if the user passed the flag.
// See updateMaskedPrefsFromUpOrSetFlag.
maskedPrefs := &ipn.MaskedPrefs{
Prefs: ipn.Prefs{
ProfileName: setArgs.profileName,
@@ -132,6 +142,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
RunWebClient: setArgs.runWebClient,
Hostname: setArgs.hostname,
OperatorUser: setArgs.opUser,
NoSNAT: !setArgs.snat,
ForceDaemon: setArgs.forceDaemon,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: setArgs.updateCheck,
@@ -140,10 +151,22 @@ func runSet(ctx context.Context, args []string) (retErr error) {
AppConnector: ipn.AppConnectorPrefs{
Advertise: setArgs.advertiseConnector,
},
PostureChecking: setArgs.postureChecking,
PostureChecking: setArgs.postureChecking,
NoStatefulFiltering: opt.NewBool(!setArgs.statefulFiltering),
},
}
if effectiveGOOS() == "linux" {
nfMode, warning, err := netfilterModeFromFlag(setArgs.netfilterMode)
if err != nil {
return err
}
if warning != "" {
warnf(warning)
}
maskedPrefs.Prefs.NetfilterMode = nfMode
}
if setArgs.exitNodeIP != "" {
if err := maskedPrefs.Prefs.SetExitNodeIP(setArgs.exitNodeIP, st); err != nil {
var e ipn.ExitNodeLocalIPError

View File

@@ -295,25 +295,42 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
// Backfills for NoStatefulFiltering occur when loading a profile; just set it explicitly here.
prefs.NoStatefulFiltering.Set(!upArgs.statefulFiltering)
switch upArgs.netfilterMode {
case "on":
prefs.NetfilterMode = preftype.NetfilterOn
case "nodivert":
prefs.NetfilterMode = preftype.NetfilterNoDivert
warnf("netfilter=nodivert; add iptables calls to ts-* chains manually.")
case "off":
prefs.NetfilterMode = preftype.NetfilterOff
if defaultNetfilterMode() != "off" {
warnf("netfilter=off; configure iptables yourself.")
}
default:
return nil, fmt.Errorf("invalid value --netfilter-mode=%q", upArgs.netfilterMode)
v, warning, err := netfilterModeFromFlag(upArgs.netfilterMode)
if err != nil {
return nil, err
}
prefs.NetfilterMode = v
if warning != "" {
warnf(warning)
}
}
return prefs, nil
}
// netfilterModeFromFlag returns the preftype.NetfilterMode for the provided
// flag value. It returns a warning if there is something the user should know
// about the value.
func netfilterModeFromFlag(v string) (_ preftype.NetfilterMode, warning string, _ error) {
switch v {
case "on", "nodivert", "off":
default:
return preftype.NetfilterOn, "", fmt.Errorf("invalid value --netfilter-mode=%q", v)
}
m, err := preftype.ParseNetfilterMode(v)
if err != nil {
return preftype.NetfilterOn, "", err
}
switch m {
case preftype.NetfilterNoDivert:
warning = "netfilter=nodivert; add iptables calls to ts-* chains manually."
case preftype.NetfilterOff:
if defaultNetfilterMode() != "off" {
warning = "netfilter=off; configure iptables yourself."
}
}
return m, warning, nil
}
// updatePrefs returns how to edit preferences based on the
// flag-provided 'prefs' and the currently active 'curPrefs'.
//

View File

@@ -1773,7 +1773,7 @@ func makeStatefulRuleExprs(tunname string) []expr.Any {
// going to our TUN.
&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
&expr.Cmp{
Op: expr.CmpOpNeq,
Op: expr.CmpOpEq,
Register: 1,
Data: []byte(tunname),
},
@@ -1926,7 +1926,7 @@ func (n *nftablesRunner) DelStatefulRule(tunname string) error {
return fmt.Errorf("get forward chain: %w", err)
}
rule, err := findRule(conn, &nftables.Rule{
Table: table.Nat,
Table: table.Filter,
Chain: chain,
Exprs: exprs,
})

View File

@@ -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 {

View File

@@ -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)