@ -22,7 +22,6 @@ import (
"golang.org/x/sys/unix"
"golang.org/x/time/rate"
"tailscale.com/envknob"
"tailscale.com/hostinfo"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
@ -37,145 +36,6 @@ const (
netfilterOn = preftype . NetfilterOn
)
// netfilterRunner abstracts helpers to run netfilter commands. It is
// implemented by linuxfw.IPTablesRunner and linuxfw.NfTablesRunner.
type netfilterRunner interface {
AddLoopbackRule ( addr netip . Addr ) error
DelLoopbackRule ( addr netip . Addr ) error
AddHooks ( ) error
DelHooks ( logf logger . Logf ) error
AddChains ( ) error
DelChains ( ) error
AddBase ( tunname string ) error
DelBase ( ) error
AddSNATRule ( ) error
DelSNATRule ( ) error
HasIPV6 ( ) bool
HasIPV6NAT ( ) bool
}
// tableDetector abstracts helpers to detect the firewall mode.
// It is implemented for testing purposes.
type tableDetector interface {
iptDetect ( ) ( int , error )
nftDetect ( ) ( int , error )
}
type linuxFWDetector struct { }
// iptDetect returns the number of iptables rules in the current namespace.
func ( l * linuxFWDetector ) iptDetect ( ) ( int , error ) {
return linuxfw . DetectIptables ( )
}
// nftDetect returns the number of nftables rules in the current namespace.
func ( l * linuxFWDetector ) nftDetect ( ) ( int , error ) {
return linuxfw . DetectNetfilter ( )
}
// chooseFireWallMode returns the firewall mode to use based on the
// environment and the system's capabilities.
func chooseFireWallMode ( logf logger . Logf , det tableDetector ) linuxfw . FirewallMode {
if distro . Get ( ) == distro . Gokrazy {
// Reduce startup logging on gokrazy. There's no way to do iptables on
// gokrazy anyway.
return linuxfw . FirewallModeNfTables
}
iptAva , nftAva := true , true
iptRuleCount , err := det . iptDetect ( )
if err != nil {
logf ( "detect iptables rule: %v" , err )
iptAva = false
}
nftRuleCount , err := det . nftDetect ( )
if err != nil {
logf ( "detect nftables rule: %v" , err )
nftAva = false
}
logf ( "nftables rule count: %d, iptables rule count: %d" , nftRuleCount , iptRuleCount )
switch {
case nftRuleCount > 0 && iptRuleCount == 0 :
logf ( "nftables is currently in use" )
hostinfo . SetFirewallMode ( "nft-inuse" )
return linuxfw . FirewallModeNfTables
case iptRuleCount > 0 && nftRuleCount == 0 :
logf ( "iptables is currently in use" )
hostinfo . SetFirewallMode ( "ipt-inuse" )
return linuxfw . FirewallModeIPTables
case nftAva :
// if both iptables and nftables are available but
// neither/both are currently used, use nftables.
logf ( "nftables is available" )
hostinfo . SetFirewallMode ( "nft" )
return linuxfw . FirewallModeNfTables
case iptAva :
logf ( "iptables is available" )
hostinfo . SetFirewallMode ( "ipt" )
return linuxfw . FirewallModeIPTables
default :
// if neither iptables nor nftables are available, use iptablesRunner as a dummy
// runner which exists but won't do anything. Creating iptablesRunner errors only
// if the iptables command is missing or doesn’ t support "--version", as long as it
// can determine a version then it’ ll carry on.
hostinfo . SetFirewallMode ( "ipt-fb" )
return linuxfw . FirewallModeIPTables
}
}
// newNetfilterRunner creates a netfilterRunner using either nftables or iptables.
// As nftables is still experimental, iptables will be used unless TS_DEBUG_USE_NETLINK_NFTABLES is set.
func newNetfilterRunner ( logf logger . Logf ) ( netfilterRunner , error ) {
tableDetector := & linuxFWDetector { }
var mode linuxfw . FirewallMode
// We now use iptables as default and have "auto" and "nftables" as
// options for people to test further.
switch {
case distro . Get ( ) == distro . Gokrazy :
// Reduce startup logging on gokrazy. There's no way to do iptables on
// gokrazy anyway.
logf ( "GoKrazy should use nftables." )
hostinfo . SetFirewallMode ( "nft-gokrazy" )
mode = linuxfw . FirewallModeNfTables
case envknob . String ( "TS_DEBUG_FIREWALL_MODE" ) == "nftables" :
logf ( "envknob TS_DEBUG_FIREWALL_MODE=nftables set" )
hostinfo . SetFirewallMode ( "nft-forced" )
mode = linuxfw . FirewallModeNfTables
case envknob . String ( "TS_DEBUG_FIREWALL_MODE" ) == "auto" :
mode = chooseFireWallMode ( logf , tableDetector )
case envknob . String ( "TS_DEBUG_FIREWALL_MODE" ) == "iptables" :
logf ( "envknob TS_DEBUG_FIREWALL_MODE=iptables set" )
hostinfo . SetFirewallMode ( "ipt-forced" )
mode = linuxfw . FirewallModeIPTables
default :
logf ( "default choosing iptables" )
hostinfo . SetFirewallMode ( "ipt-default" )
mode = linuxfw . FirewallModeIPTables
}
var nfr netfilterRunner
var err error
switch mode {
case linuxfw . FirewallModeIPTables :
logf ( "using iptables" )
nfr , err = linuxfw . NewIPTablesRunner ( logf )
if err != nil {
return nil , err
}
case linuxfw . FirewallModeNfTables :
logf ( "using nftables" )
nfr , err = linuxfw . NewNfTablesRunner ( logf )
if err != nil {
return nil , err
}
default :
return nil , fmt . Errorf ( "unknown firewall mode: %v" , mode )
}
return nfr , nil
}
type linuxRouter struct {
closed atomic . Bool
logf func ( fmt string , args ... any )
@ -200,7 +60,7 @@ type linuxRouter struct {
// ipPolicyPrefBase is the base priority at which ip rules are installed.
ipPolicyPrefBase int
nfr netfilterRunner
nfr li nuxfw. N etfilterRunner
cmd commandRunner
}
@ -210,7 +70,7 @@ func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Moni
return nil , err
}
nfr , err := newNetfilterRunner ( logf )
nfr , err := linuxfw . New ( logf )
if err != nil {
return nil , err
}
@ -222,7 +82,7 @@ func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Moni
return newUserspaceRouterAdvanced ( logf , tunname , netMon , nfr , cmd )
}
func newUserspaceRouterAdvanced ( logf logger . Logf , tunname string , netMon * netmon . Monitor , nfr netfilterRunner, cmd commandRunner ) ( Router , error ) {
func newUserspaceRouterAdvanced ( logf logger . Logf , tunname string , netMon * netmon . Monitor , nfr li nuxfw. N etfilterRunner, cmd commandRunner ) ( Router , error ) {
r := & linuxRouter {
logf : logf ,
tunname : tunname ,