diff --git a/Dockerfile b/Dockerfile index 72d24eee..9a94913f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -165,6 +165,7 @@ ENV VPN_SERVICE_PROVIDER=pia \ FIREWALL_VPN_INPUT_PORTS= \ FIREWALL_INPUT_PORTS= \ FIREWALL_OUTBOUND_SUBNETS= \ + FIREWALL_IMPLEMENTATION=iptables \ FIREWALL_DEBUG=off \ # Logging LOG_LEVEL=info \ diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index c222a632..4eb1b60b 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -226,8 +226,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, if *allSettings.Firewall.Debug { // To remove in v4 firewallLogger.Patch(log.SetLevel(log.LevelDebug)) } - firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder, - defaultRoutes, localNetworks) + firewallConf, err := firewall.NewConfig(ctx, allSettings.Firewall.Implementation, + firewallLogger, cmder, defaultRoutes, localNetworks) if err != nil { return err } diff --git a/internal/configuration/settings/firewall.go b/internal/configuration/settings/firewall.go index 19712d66..c609a694 100644 --- a/internal/configuration/settings/firewall.go +++ b/internal/configuration/settings/firewall.go @@ -4,6 +4,7 @@ import ( "fmt" "net/netip" + "github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings" "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" @@ -16,6 +17,7 @@ type Firewall struct { OutboundSubnets []netip.Prefix Enabled *bool Debug *bool + Implementation string } func (f Firewall) validate() (err error) { @@ -33,6 +35,10 @@ func (f Firewall) validate() (err error) { } } + if !helpers.IsOneOf(f.Implementation, "iptables", "nftables") { + return fmt.Errorf("firewall implementation %q must be either 'iptables' or 'nftables'", f.Implementation) + } + return nil } @@ -52,6 +58,7 @@ func (f *Firewall) copy() (copied Firewall) { OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets), Enabled: gosettings.CopyPointer(f.Enabled), Debug: gosettings.CopyPointer(f.Debug), + Implementation: f.Implementation, } } @@ -64,11 +71,13 @@ func (f *Firewall) overrideWith(other Firewall) { f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets) f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled) f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug) + f.Implementation = gosettings.OverrideWithComparable(f.Implementation, other.Implementation) } func (f *Firewall) setDefaults() { f.Enabled = gosettings.DefaultPointer(f.Enabled, true) f.Debug = gosettings.DefaultPointer(f.Debug, false) + f.Implementation = gosettings.DefaultComparable(f.Implementation, "iptables") } func (f Firewall) String() string { @@ -83,6 +92,8 @@ func (f Firewall) toLinesNode() (node *gotree.Node) { return node } + node.Appendf("Implementation: %s", f.Implementation) + if *f.Debug { node.Appendf("Debug mode: on") } @@ -138,5 +149,7 @@ func (f *Firewall) read(r *reader.Reader) (err error) { return err } + f.Implementation = r.String("FIREWALL_IMPLEMENTATION") + return nil } diff --git a/internal/configuration/settings/settings_test.go b/internal/configuration/settings/settings_test.go index 73c3cb28..cee6f26c 100644 --- a/internal/configuration/settings/settings_test.go +++ b/internal/configuration/settings/settings_test.go @@ -65,7 +65,8 @@ func Test_Settings_String(t *testing.T) { | ├── Block ads: no | └── Block surveillance: yes ├── Firewall settings: -| └── Enabled: yes +| ├── Enabled: yes +| └── Implementation: iptables ├── Log settings: | └── Log level: INFO ├── Health settings: diff --git a/internal/firewall/firewall.go b/internal/firewall/firewall.go index 3b9b902a..50348f8d 100644 --- a/internal/firewall/firewall.go +++ b/internal/firewall/firewall.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/qdm12/gluetun/internal/firewall/iptables" + "github.com/qdm12/gluetun/internal/firewall/nftables" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/routing" ) @@ -33,14 +34,25 @@ type Config struct { } // NewConfig creates a new Config instance and returns an error -// if no iptables implementation is available. -func NewConfig(ctx context.Context, logger Logger, +// if no firewall implementation is available. +func NewConfig(ctx context.Context, implementation string, logger Logger, runner CmdRunner, defaultRoutes []routing.DefaultRoute, localNetworks []routing.LocalNetwork, ) (config *Config, err error) { - impl, err := iptables.New(ctx, runner, logger) - if err != nil { - return nil, fmt.Errorf("creating iptables firewall: %w", err) + var impl firewallImpl + var customRulesPath string + switch implementation { + case "iptables": + impl, err = iptables.New(ctx, runner, logger) + if err != nil { + return nil, fmt.Errorf("creating iptables firewall: %w", err) + } + customRulesPath = "/iptables/post-rules.txt" + case "nftables": + impl = nftables.New(logger) + customRulesPath = "/gluetun/firewall/nftables/post-rules.txt" + default: + return nil, fmt.Errorf("unknown firewall implementation: %s", implementation) } return &Config{ @@ -51,6 +63,6 @@ func NewConfig(ctx context.Context, logger Logger, defaultRoutes: defaultRoutes, localNetworks: localNetworks, impl: impl, - customRulesPath: "/iptables/post-rules.txt", + customRulesPath: customRulesPath, }, nil } diff --git a/internal/firewall/interfaces.go b/internal/firewall/interfaces.go index ca39f01a..dfc815ec 100644 --- a/internal/firewall/interfaces.go +++ b/internal/firewall/interfaces.go @@ -16,6 +16,7 @@ type Logger interface { Debug(s string) Info(s string) Warn(s string) + Warnf(format string, args ...any) Error(s string) }