@ -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 b ool // whether we can use 'ip rule...fwmark <mark>/<mask>'
ipRuleAvailable bool // whether kernel was built with IP_MULTIPLE_TABLES
v6Available bool // whether the kernel supports IPv6
fwmaskWorks Lazy opt . B ool // whether we can use 'ip rule...fwmark <mark>/<mask>' ; 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 <mark>/<mask>'.
// 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 ) )