feat(firewall): auto-detect which iptables
- On `iptables` error, try to use `iptables-nft` - On `ip6tables` error, try to use `ip6tables-nft`
This commit is contained in:
@@ -205,8 +205,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
if *allSettings.Firewall.Debug { // To remove in v4
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
firewallLogger.PatchLevel(logging.LevelDebug)
|
firewallLogger.PatchLevel(logging.LevelDebug)
|
||||||
}
|
}
|
||||||
firewallConf := firewall.NewConfig(firewallLogger, cmder,
|
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
|
||||||
defaultInterface, defaultGateway, localNetworks, defaultIP)
|
defaultInterface, defaultGateway, localNetworks, defaultIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if *allSettings.Firewall.Enabled {
|
if *allSettings.Firewall.Enabled {
|
||||||
err = firewallConf.SetEnabled(ctx, true)
|
err = firewallConf.SetEnabled(ctx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ type Config struct { //nolint:maligned
|
|||||||
localIP net.IP
|
localIP net.IP
|
||||||
|
|
||||||
// Fixed state
|
// Fixed state
|
||||||
ip6Tables bool
|
ipTables string
|
||||||
|
ip6Tables string
|
||||||
customRulesPath string
|
customRulesPath string
|
||||||
|
|
||||||
// State
|
// State
|
||||||
@@ -45,20 +46,28 @@ type Config struct { //nolint:maligned
|
|||||||
stateMutex sync.Mutex
|
stateMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig creates a new Config instance.
|
// NewConfig creates a new Config instance and returns an error
|
||||||
func NewConfig(logger Logger, runner command.Runner,
|
// if no iptables implementation is available.
|
||||||
defaultInterface string, defaultGateway net.IP,
|
func NewConfig(ctx context.Context, logger Logger,
|
||||||
localNetworks []routing.LocalNetwork, localIP net.IP) *Config {
|
runner command.Runner, defaultInterface string,
|
||||||
|
defaultGateway net.IP, localNetworks []routing.LocalNetwork,
|
||||||
|
localIP net.IP) (config *Config, err error) {
|
||||||
|
iptables, err := findIptablesSupported(ctx, runner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
runner: runner,
|
runner: runner,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
allowedInputPorts: make(map[uint16]string),
|
allowedInputPorts: make(map[uint16]string),
|
||||||
ip6Tables: ip6tablesSupported(context.Background(), runner),
|
ipTables: iptables,
|
||||||
|
ip6Tables: findIP6tablesSupported(ctx, runner),
|
||||||
customRulesPath: "/iptables/post-rules.txt",
|
customRulesPath: "/iptables/post-rules.txt",
|
||||||
// Obtained from routing
|
// Obtained from routing
|
||||||
defaultInterface: defaultInterface,
|
defaultInterface: defaultInterface,
|
||||||
defaultGateway: defaultGateway,
|
defaultGateway: defaultGateway,
|
||||||
localNetworks: localNetworks,
|
localNetworks: localNetworks,
|
||||||
localIP: localIP,
|
localIP: localIP,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,27 @@ import (
|
|||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// findIP6tablesSupported checks for multiple iptables implementations
|
||||||
ErrIP6NotSupported = errors.New("ip6tables not supported")
|
// and returns the iptables path that is supported. If none work, an
|
||||||
)
|
// empty string path is returned.
|
||||||
|
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
|
||||||
|
ip6tablesPath string) {
|
||||||
|
binsToTry := []string{"ip6tables", "ip6tables-nft"}
|
||||||
|
|
||||||
func ip6tablesSupported(ctx context.Context, runner command.Runner) (supported bool) {
|
var err error
|
||||||
cmd := exec.CommandContext(ctx, "ip6tables", "-L")
|
for _, ip6tablesPath = range binsToTry {
|
||||||
if _, err := runner.Run(cmd); err != nil {
|
cmd := exec.CommandContext(ctx, ip6tablesPath, "-L")
|
||||||
return false
|
_, err = runner.Run(cmd)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return true
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip6tablesPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
@@ -32,18 +43,19 @@ func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
|
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
|
||||||
if !c.ip6Tables {
|
if c.ip6Tables == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.ip6tablesMutex.Lock() // only one ip6tables command at once
|
c.ip6tablesMutex.Lock() // only one ip6tables command at once
|
||||||
defer c.ip6tablesMutex.Unlock()
|
defer c.ip6tablesMutex.Unlock()
|
||||||
|
|
||||||
c.logger.Debug("ip6tables " + instruction)
|
c.logger.Debug(c.ip6Tables + " " + instruction)
|
||||||
|
|
||||||
flags := strings.Fields(instruction)
|
flags := strings.Fields(instruction)
|
||||||
cmd := exec.CommandContext(ctx, "ip6tables", flags...)
|
cmd := exec.CommandContext(ctx, c.ip6Tables, flags...) // #nosec G204
|
||||||
if output, err := c.runner.Run(cmd); err != nil {
|
if output, err := c.runner.Run(cmd); err != nil {
|
||||||
return fmt.Errorf("command failed: \"ip6tables %s\": %s: %w", instruction, output, err)
|
return fmt.Errorf("command failed: \"%s %s\": %s: %w",
|
||||||
|
c.ip6Tables, instruction, output, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrIPTablesNotSupported = errors.New("no iptables supported found")
|
||||||
ErrIPTablesVersionTooShort = errors.New("iptables version string is too short")
|
ErrIPTablesVersionTooShort = errors.New("iptables version string is too short")
|
||||||
ErrPolicyUnknown = errors.New("unknown policy")
|
ErrPolicyUnknown = errors.New("unknown policy")
|
||||||
ErrNeedIP6Tables = errors.New("ip6tables is required, please upgrade your kernel to support it")
|
ErrNeedIP6Tables = errors.New("ip6tables is required, please upgrade your kernel to support it")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func findIptablesSupported(ctx context.Context, runner command.Runner) (iptablesPath string, err error) {
|
||||||
|
binsToTry := []string{"iptables", "iptables-nft"}
|
||||||
|
|
||||||
|
for _, iptablesPath = range binsToTry {
|
||||||
|
cmd := exec.CommandContext(ctx, iptablesPath, "-L")
|
||||||
|
_, err = runner.Run(cmd)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%w: from %s: last error is %s",
|
||||||
|
ErrIPTablesNotSupported, strings.Join(binsToTry, ", "), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return iptablesPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
func appendOrDelete(remove bool) string {
|
func appendOrDelete(remove bool) string {
|
||||||
if remove {
|
if remove {
|
||||||
return "--delete"
|
return "--delete"
|
||||||
@@ -71,12 +91,13 @@ func (c *Config) runIptablesInstruction(ctx context.Context, instruction string)
|
|||||||
c.iptablesMutex.Lock() // only one iptables command at once
|
c.iptablesMutex.Lock() // only one iptables command at once
|
||||||
defer c.iptablesMutex.Unlock()
|
defer c.iptablesMutex.Unlock()
|
||||||
|
|
||||||
c.logger.Debug("iptables " + instruction)
|
c.logger.Debug(c.ipTables + " " + instruction)
|
||||||
|
|
||||||
flags := strings.Fields(instruction)
|
flags := strings.Fields(instruction)
|
||||||
cmd := exec.CommandContext(ctx, "iptables", flags...)
|
cmd := exec.CommandContext(ctx, c.ipTables, flags...) // #nosec G204
|
||||||
if output, err := c.runner.Run(cmd); err != nil {
|
if output, err := c.runner.Run(cmd); err != nil {
|
||||||
return fmt.Errorf("command failed: \"iptables %s\": %s: %w", instruction, output, err)
|
return fmt.Errorf("command failed: \"%s %s\": %s: %w",
|
||||||
|
c.ipTables, instruction, output, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -124,7 +145,7 @@ func (c *Config) acceptInputToSubnet(ctx context.Context, intf string, destinati
|
|||||||
if isIP4Subnet {
|
if isIP4Subnet {
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
return c.runIptablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
if !c.ip6Tables {
|
if c.ip6Tables == "" {
|
||||||
return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables)
|
return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables)
|
||||||
}
|
}
|
||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
@@ -151,7 +172,7 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
|||||||
isIPv4 := connection.IP.To4() != nil
|
isIPv4 := connection.IP.To4() != nil
|
||||||
if isIPv4 {
|
if isIPv4 {
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
return c.runIptablesInstruction(ctx, instruction)
|
||||||
} else if !c.ip6Tables {
|
} else if c.ip6Tables == "" {
|
||||||
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
|
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
|
||||||
}
|
}
|
||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
@@ -172,7 +193,7 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
|
|||||||
|
|
||||||
if doIPv4 {
|
if doIPv4 {
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
return c.runIptablesInstruction(ctx, instruction)
|
||||||
} else if !c.ip6Tables {
|
} else if c.ip6Tables == "" {
|
||||||
return fmt.Errorf("accept output from %s to %s: %w", sourceIP, destinationSubnet, ErrNeedIP6Tables)
|
return fmt.Errorf("accept output from %s to %s: %w", sourceIP, destinationSubnet, ErrNeedIP6Tables)
|
||||||
}
|
}
|
||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
@@ -223,9 +244,15 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
|
|||||||
case strings.HasPrefix(line, "iptables "):
|
case strings.HasPrefix(line, "iptables "):
|
||||||
ipv4 = true
|
ipv4 = true
|
||||||
rule = strings.TrimPrefix(line, "iptables ")
|
rule = strings.TrimPrefix(line, "iptables ")
|
||||||
|
case strings.HasPrefix(line, "iptables-nft "):
|
||||||
|
ipv4 = true
|
||||||
|
rule = strings.TrimPrefix(line, "iptables-nft ")
|
||||||
case strings.HasPrefix(line, "ip6tables "):
|
case strings.HasPrefix(line, "ip6tables "):
|
||||||
ipv4 = false
|
ipv4 = false
|
||||||
rule = strings.TrimPrefix(line, "ip6tables ")
|
rule = strings.TrimPrefix(line, "ip6tables ")
|
||||||
|
case strings.HasPrefix(line, "ip6tables-nft "):
|
||||||
|
ipv4 = false
|
||||||
|
rule = strings.TrimPrefix(line, "ip6tables-nft ")
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -237,7 +264,7 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
|
|||||||
switch {
|
switch {
|
||||||
case ipv4:
|
case ipv4:
|
||||||
err = c.runIptablesInstruction(ctx, rule)
|
err = c.runIptablesInstruction(ctx, rule)
|
||||||
case !c.ip6Tables:
|
case c.ip6Tables == "":
|
||||||
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
|
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
|
||||||
default: // ipv6
|
default: // ipv6
|
||||||
err = c.runIP6tablesInstruction(ctx, rule)
|
err = c.runIP6tablesInstruction(ctx, rule)
|
||||||
|
|||||||
Reference in New Issue
Block a user