diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 41c5b128c..a328075d9 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -25,6 +25,7 @@ import ( "tailscale.com/health" "tailscale.com/net/netmon" "tailscale.com/types/logger" + "tailscale.com/types/opt" "tailscale.com/types/preftype" "tailscale.com/util/linuxfw" "tailscale.com/util/multierr" @@ -58,9 +59,9 @@ type linuxRouter struct { ipRuleFixLimiter *rate.Limiter // Various feature checks for the network stack. - ipRuleAvailable bool // whether kernel was built with IP_MULTIPLE_TABLES - v6Available bool // whether the kernel supports IPv6 - fwmaskWorks bool // whether we can use 'ip rule...fwmark /' + ipRuleAvailable bool // whether kernel was built with IP_MULTIPLE_TABLES + v6Available bool // whether the kernel supports IPv6 + fwmaskWorksLazy opt.Bool // whether we can use 'ip rule...fwmark /'; set lazily // ipPolicyPrefBase is the base priority at which ip rules are installed. ipPolicyPrefBase int @@ -110,20 +111,6 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netMon *netmon } } - // To be a good denizen of the 4-byte 'fwmark' bitspace on every packet, we try to - // only use the third byte. However, support for masking to part of the fwmark bitspace - // was only added to busybox in 1.33.0. As such, we want to detect older versions and - // not issue such a stanza. - var err error - if r.fwmaskWorks, err = ipCmdSupportsFwmask(); err != nil { - r.logf("failed to determine ip command fwmask support: %v", err) - } - if r.fwmaskWorks { - r.logf("[v1] ip command supports fwmark masks") - } else { - r.logf("[v1] ip command does NOT support fwmark masks") - } - // A common installation of OpenWRT involves use of the 'mwan3' package. // This package installs ip-tables rules like: // -A mwan3_fallback_policy -m mark --mark 0x0/0x3f00 -j MARK --set-xmark 0x100/0x3f00 @@ -260,6 +247,31 @@ func (r *linuxRouter) useIPCommand() bool { return !ok } +// fwmaskWorks reports whether we can use 'ip rule...fwmark /'. +// This is computed lazily on first use. By default, we don't run the "ip" +// command, so never actually runs this. But the "ip" command is used in tests +// and can be forced. (see useIPCommand) +func (r *linuxRouter) fwmaskWorks() bool { + if v, ok := r.fwmaskWorksLazy.Get(); ok { + return v + } + // To be a good denizen of the 4-byte 'fwmark' bitspace on every packet, we try to + // only use the third byte. However, support for masking to part of the fwmark bitspace + // was only added to busybox in 1.33.0. As such, we want to detect older versions and + // not issue such a stanza. + v, err := ipCmdSupportsFwmask() + if err != nil { + r.logf("failed to determine ip command fwmask support: %v", err) + } + r.fwmaskWorksLazy.Set(v) + if v { + r.logf("[v1] ip command supports fwmark masks") + } else { + r.logf("[v1] ip command does NOT support fwmark masks") + } + return v +} + // onIPRuleDeleted is the callback from the network monitor for when an IP // policy rule is deleted. See Issue 1591. // @@ -1266,7 +1278,7 @@ func (r *linuxRouter) addIPRulesWithIPCommand() error { "pref", strconv.Itoa(rule.Priority + r.ipPolicyPrefBase), } if rule.Mark != 0 { - if r.fwmaskWorks { + if r.fwmaskWorks() { args = append(args, "fwmark", fmt.Sprintf("0x%x/%s", rule.Mark, linuxfw.TailscaleFwmarkMask)) } else { args = append(args, "fwmark", fmt.Sprintf("0x%x", rule.Mark))