131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
||
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
||
//go:build linux
|
||
|
||
package linuxfw
|
||
|
||
import (
|
||
"errors"
|
||
"os/exec"
|
||
|
||
"tailscale.com/envknob"
|
||
"tailscale.com/hostinfo"
|
||
"tailscale.com/types/logger"
|
||
"tailscale.com/version/distro"
|
||
)
|
||
|
||
func detectFirewallMode(logf logger.Logf, prefHint string) FirewallMode {
|
||
if 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")
|
||
return FirewallModeNfTables
|
||
}
|
||
|
||
mode := envknob.String("TS_DEBUG_FIREWALL_MODE")
|
||
// If the envknob isn't set, fall back to the pref suggested by c2n or
|
||
// nodeattrs.
|
||
if mode == "" {
|
||
mode = prefHint
|
||
logf("using firewall mode pref %s", prefHint)
|
||
} else if prefHint != "" {
|
||
logf("TS_DEBUG_FIREWALL_MODE set, overriding firewall mode from %s to %s", prefHint, mode)
|
||
}
|
||
|
||
var det linuxFWDetector
|
||
if mode == "" {
|
||
// We have no preference, so check if `iptables` is even available.
|
||
_, err := det.iptDetect()
|
||
if err != nil && errors.Is(err, exec.ErrNotFound) {
|
||
logf("iptables not found: %v; falling back to nftables", err)
|
||
mode = "nftables"
|
||
}
|
||
}
|
||
|
||
// We now use iptables as default and have "auto" and "nftables" as
|
||
// options for people to test further.
|
||
switch mode {
|
||
case "auto":
|
||
return pickFirewallModeFromInstalledRules(logf, det)
|
||
case "nftables":
|
||
hostinfo.SetFirewallMode("nft-forced")
|
||
return FirewallModeNfTables
|
||
case "iptables":
|
||
hostinfo.SetFirewallMode("ipt-forced")
|
||
default:
|
||
logf("default choosing iptables")
|
||
hostinfo.SetFirewallMode("ipt-default")
|
||
}
|
||
return FirewallModeIPTables
|
||
}
|
||
|
||
// 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 detectIptables()
|
||
}
|
||
|
||
// nftDetect returns the number of nftables rules in the current namespace.
|
||
func (l linuxFWDetector) nftDetect() (int, error) {
|
||
return detectNetfilter()
|
||
}
|
||
|
||
// pickFirewallModeFromInstalledRules returns the firewall mode to use based on
|
||
// the environment and the system's capabilities.
|
||
func pickFirewallModeFromInstalledRules(logf logger.Logf, det tableDetector) FirewallMode {
|
||
if distro.Get() == distro.Gokrazy {
|
||
// Reduce startup logging on gokrazy. There's no way to do iptables on
|
||
// gokrazy anyway.
|
||
return 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 FirewallModeNfTables
|
||
case iptRuleCount > 0 && nftRuleCount == 0:
|
||
logf("iptables is currently in use")
|
||
hostinfo.SetFirewallMode("ipt-inuse")
|
||
return 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 FirewallModeNfTables
|
||
case iptAva:
|
||
logf("iptables is available")
|
||
hostinfo.SetFirewallMode("ipt")
|
||
return 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 FirewallModeIPTables
|
||
}
|
||
}
|