From d21953f62ed1b1ef223d55017df6e61c1814a009 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Wed, 25 Feb 2026 03:45:17 +0000 Subject: [PATCH] chore(firewall): split apart iptables specific code in internal/firewall/iptables --- cmd/gluetun/main.go | 2 +- internal/firewall/enable.go | 46 +++++++++--------- internal/firewall/firewall.go | 35 ++++++-------- internal/firewall/interfaces.go | 30 +++++++++++- .../{ => iptables}/cmd_matcher_test.go | 2 +- internal/firewall/{ => iptables}/delete.go | 2 +- .../firewall/{ => iptables}/delete_test.go | 2 +- internal/firewall/iptables/firewall.go | 36 ++++++++++++++ internal/firewall/iptables/interfaces.go | 14 ++++++ internal/firewall/{ => iptables}/ip6tables.go | 8 ++-- internal/firewall/{ => iptables}/iptables.go | 47 ++++++++++++------- .../firewall/{ => iptables}/iptablesmix.go | 2 +- internal/firewall/{ => iptables}/list.go | 2 +- internal/firewall/{ => iptables}/list_test.go | 2 +- .../{ => iptables}/mocks_generate_test.go | 2 +- .../firewall/{ => iptables}/mocks_test.go | 6 +-- internal/firewall/{ => iptables}/parse.go | 2 +- .../firewall/{ => iptables}/parse_test.go | 2 +- internal/firewall/{ => iptables}/support.go | 12 ++--- .../firewall/{ => iptables}/support_test.go | 4 +- internal/firewall/{ => iptables}/tcp.go | 2 +- internal/firewall/outboundsubnets.go | 4 +- internal/firewall/ports.go | 4 +- internal/firewall/redirect.go | 4 +- internal/firewall/vpn.go | 8 ++-- internal/firewall/wrappers.go | 21 +++++++++ internal/pmtud/pmtud.go | 4 +- internal/pmtud/tcp/helpers_test.go | 3 +- internal/pmtud/tcp/mss.go | 4 +- 29 files changed, 209 insertions(+), 103 deletions(-) rename internal/firewall/{ => iptables}/cmd_matcher_test.go (98%) rename internal/firewall/{ => iptables}/delete.go (99%) rename internal/firewall/{ => iptables}/delete_test.go (99%) create mode 100644 internal/firewall/iptables/firewall.go create mode 100644 internal/firewall/iptables/interfaces.go rename internal/firewall/{ => iptables}/ip6tables.go (91%) rename internal/firewall/{ => iptables}/iptables.go (81%) rename internal/firewall/{ => iptables}/iptablesmix.go (96%) rename internal/firewall/{ => iptables}/list.go (99%) rename internal/firewall/{ => iptables}/list_test.go (99%) rename internal/firewall/{ => iptables}/mocks_generate_test.go (83%) rename internal/firewall/{ => iptables}/mocks_test.go (95%) rename internal/firewall/{ => iptables}/parse.go (99%) rename internal/firewall/{ => iptables}/parse_test.go (99%) rename internal/firewall/{ => iptables}/support.go (92%) rename internal/firewall/{ => iptables}/support_test.go (99%) rename internal/firewall/{ => iptables}/tcp.go (99%) create mode 100644 internal/firewall/wrappers.go diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index c736bb48..c222a632 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -283,7 +283,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, err = printVersions(ctx, logger, []printVersionElement{ {name: "Alpine", getVersion: alpineConf.Version}, {name: "OpenVPN", getVersion: ovpnVersion}, - {name: "IPtables", getVersion: firewallConf.Version}, + {name: "Firewall", getVersion: firewallConf.Version}, }) if err != nil { return err diff --git a/internal/firewall/enable.go b/internal/firewall/enable.go index 30a6ebcd..f1909b17 100644 --- a/internal/firewall/enable.go +++ b/internal/firewall/enable.go @@ -42,13 +42,13 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) { } func (c *Config) disable(ctx context.Context) (err error) { - if err = c.clearAllRules(ctx); err != nil { + if err = c.impl.ClearAllRules(ctx); err != nil { return fmt.Errorf("clearing all rules: %w", err) } - if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil { + if err = c.impl.SetIPv4AllPolicies(ctx, "ACCEPT"); err != nil { return fmt.Errorf("setting ipv4 policies: %w", err) } - if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil { + if err = c.impl.SetIPv6AllPolicies(ctx, "ACCEPT"); err != nil { return fmt.Errorf("setting ipv6 policies: %w", err) } @@ -72,33 +72,31 @@ func (c *Config) fallbackToDisabled(ctx context.Context) { } func (c *Config) enable(ctx context.Context) (err error) { - touched := false - if err = c.setIPv4AllPolicies(ctx, "DROP"); err != nil { - return err - } - touched = true - - if err = c.setIPv6AllPolicies(ctx, "DROP"); err != nil { + if err = c.impl.SetIPv4AllPolicies(ctx, "DROP"); err != nil { return err } - const remove = false + if err = c.impl.SetIPv6AllPolicies(ctx, "DROP"); err != nil { + return err + } defer func() { - if touched && err != nil { + if err != nil { c.fallbackToDisabled(ctx) } }() + const remove = false + // Loopback traffic - if err = c.acceptInputThroughInterface(ctx, "lo", remove); err != nil { + if err = c.impl.AcceptInputThroughInterface(ctx, "lo", remove); err != nil { return err } - if err = c.acceptOutputThroughInterface(ctx, "lo", remove); err != nil { + if err = c.impl.AcceptOutputThroughInterface(ctx, "lo", remove); err != nil { return err } - if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil { + if err = c.impl.AcceptEstablishedRelatedTraffic(ctx, remove); err != nil { return err } @@ -108,7 +106,9 @@ func (c *Config) enable(ctx context.Context) (err error) { localInterfaces := make(map[string]struct{}, len(c.localNetworks)) for _, network := range c.localNetworks { - if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, network.IPNet, remove); err != nil { + err = c.impl.AcceptOutputFromIPToSubnet(ctx, + network.InterfaceName, network.IP, network.IPNet, remove) + if err != nil { return err } @@ -117,7 +117,7 @@ func (c *Config) enable(ctx context.Context) (err error) { continue } localInterfaces[network.InterfaceName] = struct{}{} - err = c.acceptIpv6MulticastOutput(ctx, network.InterfaceName, remove) + err = c.impl.AcceptIpv6MulticastOutput(ctx, network.InterfaceName, remove) if err != nil { return fmt.Errorf("accepting IPv6 multicast output: %w", err) } @@ -130,7 +130,7 @@ func (c *Config) enable(ctx context.Context) (err error) { // Allows packets from any IP address to go through eth0 / local network // to reach Gluetun. for _, network := range c.localNetworks { - if err := c.acceptInputToSubnet(ctx, network.InterfaceName, network.IPNet, remove); err != nil { + if err := c.impl.AcceptInputToSubnet(ctx, network.InterfaceName, network.IPNet, remove); err != nil { return err } } @@ -144,7 +144,7 @@ func (c *Config) enable(ctx context.Context) (err error) { return fmt.Errorf("redirecting ports: %w", err) } - if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil { + if err := c.impl.RunUserPostRules(ctx, c.customRulesPath, remove); err != nil { return fmt.Errorf("running user defined post firewall rules: %w", err) } @@ -164,7 +164,7 @@ func (c *Config) allowVPNIP(ctx context.Context) (err error) { continue } interfacesSeen[defaultRoute.NetInterface] = struct{}{} - err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove) + err = c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove) if err != nil { return fmt.Errorf("accepting output traffic through VPN: %w", err) } @@ -186,7 +186,7 @@ func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) { firewallUpdated = true const remove = false - err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface, + err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface, defaultRoute.AssignedIP, subnet, remove) if err != nil { return err @@ -204,7 +204,7 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) { for port, netInterfaces := range c.allowedInputPorts { for netInterface := range netInterfaces { const remove = false - err = c.acceptInputToPort(ctx, netInterface, port, remove) + err = c.impl.AcceptInputToPort(ctx, netInterface, port, remove) if err != nil { return fmt.Errorf("accepting input port %d on interface %s: %w", port, netInterface, err) @@ -216,7 +216,7 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) { func (c *Config) redirectPorts(ctx context.Context, remove bool) (err error) { for _, portRedirection := range c.portRedirections { - err = c.redirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort, + err = c.impl.RedirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort, portRedirection.destinationPort, remove) if err != nil { return err diff --git a/internal/firewall/firewall.go b/internal/firewall/firewall.go index f1618443..73bfcc46 100644 --- a/internal/firewall/firewall.go +++ b/internal/firewall/firewall.go @@ -2,24 +2,23 @@ package firewall import ( "context" + "fmt" "net/netip" "sync" + "github.com/qdm12/gluetun/internal/firewall/iptables" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/routing" ) type Config struct { - runner CmdRunner - logger Logger - iptablesMutex sync.Mutex - ip6tablesMutex sync.Mutex - defaultRoutes []routing.DefaultRoute - localNetworks []routing.LocalNetwork + runner CmdRunner + logger Logger + defaultRoutes []routing.DefaultRoute + localNetworks []routing.LocalNetwork - // Fixed state - ipTables string - ip6Tables string + // Fixed + impl firewallImpl customRulesPath string // State @@ -38,25 +37,19 @@ func NewConfig(ctx context.Context, logger Logger, runner CmdRunner, defaultRoutes []routing.DefaultRoute, localNetworks []routing.LocalNetwork, ) (config *Config, err error) { - iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy") + impl, err := iptables.New(ctx, runner, logger) if err != nil { - return nil, err - } - - ip6tables, err := findIP6tablesSupported(ctx, runner) - if err != nil { - return nil, err + return nil, fmt.Errorf("creating iptables firewall: %w", err) } return &Config{ runner: runner, logger: logger, allowedInputPorts: make(map[uint16]map[string]struct{}), - ipTables: iptables, - ip6Tables: ip6tables, - customRulesPath: "/iptables/post-rules.txt", // Obtained from routing - defaultRoutes: defaultRoutes, - localNetworks: localNetworks, + defaultRoutes: defaultRoutes, + localNetworks: localNetworks, + impl: impl, + customRulesPath: "/iptables/post-rules.txt", }, nil } diff --git a/internal/firewall/interfaces.go b/internal/firewall/interfaces.go index 5f91760a..80a1bab5 100644 --- a/internal/firewall/interfaces.go +++ b/internal/firewall/interfaces.go @@ -1,6 +1,12 @@ package firewall -import "os/exec" +import ( + "context" + "net/netip" + "os/exec" + + "github.com/qdm12/gluetun/internal/models" +) type CmdRunner interface { Run(cmd *exec.Cmd) (output string, err error) @@ -12,3 +18,25 @@ type Logger interface { Warn(s string) Error(s string) } + +type firewallImpl interface { //nolint:interfacebloat + AcceptEstablishedRelatedTraffic(ctx context.Context, remove bool) error + AcceptInputThroughInterface(ctx context.Context, intf string, remove bool) error + AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error + AcceptInputToSubnet(ctx context.Context, intf string, subnet netip.Prefix, remove bool) error + AcceptIpv6MulticastOutput(ctx context.Context, intf string, remove bool) error + AcceptOutputFromIPToSubnet(ctx context.Context, intf string, assignedIP netip.Addr, + subnet netip.Prefix, remove bool) error + AcceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error + AcceptOutputTrafficToVPN(ctx context.Context, intf string, + connection models.Connection, remove bool) error + ClearAllRules(ctx context.Context) error + RedirectPort(ctx context.Context, intf string, sourcePort, + destinationPort uint16, remove bool) error + RunUserPostRules(ctx context.Context, customRulesPath string, remove bool) error + SetIPv4AllPolicies(ctx context.Context, policy string) error + SetIPv6AllPolicies(ctx context.Context, policy string) error + TempDropOutputTCPRST(ctx context.Context, src, dst netip.AddrPort, excludeMark int) ( + revert func(ctx context.Context) error, err error) + Version(ctx context.Context) (version string, err error) +} diff --git a/internal/firewall/cmd_matcher_test.go b/internal/firewall/iptables/cmd_matcher_test.go similarity index 98% rename from internal/firewall/cmd_matcher_test.go rename to internal/firewall/iptables/cmd_matcher_test.go index c0e57e8c..3e4ec317 100644 --- a/internal/firewall/cmd_matcher_test.go +++ b/internal/firewall/iptables/cmd_matcher_test.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "fmt" diff --git a/internal/firewall/delete.go b/internal/firewall/iptables/delete.go similarity index 99% rename from internal/firewall/delete.go rename to internal/firewall/iptables/delete.go index 616cf7f2..e25c2a57 100644 --- a/internal/firewall/delete.go +++ b/internal/firewall/iptables/delete.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" diff --git a/internal/firewall/delete_test.go b/internal/firewall/iptables/delete_test.go similarity index 99% rename from internal/firewall/delete_test.go rename to internal/firewall/iptables/delete_test.go index a0f5a6fa..de7176f6 100644 --- a/internal/firewall/delete_test.go +++ b/internal/firewall/iptables/delete_test.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" diff --git a/internal/firewall/iptables/firewall.go b/internal/firewall/iptables/firewall.go new file mode 100644 index 00000000..aeedae63 --- /dev/null +++ b/internal/firewall/iptables/firewall.go @@ -0,0 +1,36 @@ +package iptables + +import ( + "context" + "sync" +) + +type Config struct { + runner CmdRunner + logger Logger + iptablesMutex sync.Mutex + ip6tablesMutex sync.Mutex + + // Fixed state + ipTables string + ip6Tables string +} + +func New(ctx context.Context, runner CmdRunner, logger Logger) (*Config, error) { + iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy") + if err != nil { + return nil, err + } + + ip6tables, err := findIP6tablesSupported(ctx, runner) + if err != nil { + return nil, err + } + + return &Config{ + runner: runner, + logger: logger, + ipTables: iptables, + ip6Tables: ip6tables, + }, nil +} diff --git a/internal/firewall/iptables/interfaces.go b/internal/firewall/iptables/interfaces.go new file mode 100644 index 00000000..ebfe8842 --- /dev/null +++ b/internal/firewall/iptables/interfaces.go @@ -0,0 +1,14 @@ +package iptables + +import "os/exec" + +type CmdRunner interface { + Run(cmd *exec.Cmd) (output string, err error) +} + +type Logger interface { + Debug(s string) + Info(s string) + Warn(s string) + Error(s string) +} diff --git a/internal/firewall/ip6tables.go b/internal/firewall/iptables/ip6tables.go similarity index 91% rename from internal/firewall/ip6tables.go rename to internal/firewall/iptables/ip6tables.go index 0bcc3f05..5dcf14e1 100644 --- a/internal/firewall/ip6tables.go +++ b/internal/firewall/iptables/ip6tables.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" @@ -14,8 +14,8 @@ import ( func findIP6tablesSupported(ctx context.Context, runner CmdRunner) ( ip6tablesPath string, err error, ) { - ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy") - if errors.Is(err, ErrIPTablesNotSupported) { + ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-legacy") + if errors.Is(err, ErrNotSupported) { return "", nil } else if err != nil { return "", err @@ -56,7 +56,7 @@ func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string var ErrPolicyNotValid = errors.New("policy is not valid") -func (c *Config) setIPv6AllPolicies(ctx context.Context, policy string) error { +func (c *Config) SetIPv6AllPolicies(ctx context.Context, policy string) error { switch policy { case "ACCEPT", "DROP": default: diff --git a/internal/firewall/iptables.go b/internal/firewall/iptables/iptables.go similarity index 81% rename from internal/firewall/iptables.go rename to internal/firewall/iptables/iptables.go index 72b44a02..64546742 100644 --- a/internal/firewall/iptables.go +++ b/internal/firewall/iptables/iptables.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" @@ -53,7 +53,7 @@ func (c *Config) Version(ctx context.Context) (string, error) { if len(words) < minWords { return "", fmt.Errorf("%w: %s", ErrIPTablesVersionTooShort, output) } - return words[1], nil + return "iptables " + words[1], nil } func (c *Config) runIptablesInstructions(ctx context.Context, instructions []string) error { @@ -84,7 +84,7 @@ func (c *Config) runIptablesInstruction(ctx context.Context, instruction string) return nil } -func (c *Config) clearAllRules(ctx context.Context) error { +func (c *Config) ClearAllRules(ctx context.Context) error { tables := []string{"filter"} for _, table := range tables { return c.runMixedIptablesInstructions(ctx, []string{ @@ -95,7 +95,7 @@ func (c *Config) clearAllRules(ctx context.Context) error { return nil } -func (c *Config) setIPv4AllPolicies(ctx context.Context, policy string) error { +func (c *Config) SetIPv4AllPolicies(ctx context.Context, policy string) error { switch policy { case "ACCEPT", "DROP": default: @@ -108,13 +108,13 @@ func (c *Config) setIPv4AllPolicies(ctx context.Context, policy string) error { }) } -func (c *Config) acceptInputThroughInterface(ctx context.Context, intf string, remove bool) error { +func (c *Config) AcceptInputThroughInterface(ctx context.Context, intf string, remove bool) error { return c.runMixedIptablesInstruction(ctx, fmt.Sprintf( "%s INPUT -i %s -j ACCEPT", appendOrDelete(remove), intf, )) } -func (c *Config) acceptInputToSubnet(ctx context.Context, intf string, +func (c *Config) AcceptInputToSubnet(ctx context.Context, intf string, destination netip.Prefix, remove bool, ) error { interfaceFlag := "-i " + intf @@ -134,20 +134,20 @@ func (c *Config) acceptInputToSubnet(ctx context.Context, intf string, return c.runIP6tablesInstruction(ctx, instruction) } -func (c *Config) acceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error { +func (c *Config) AcceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error { return c.runMixedIptablesInstruction(ctx, fmt.Sprintf( "%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf, )) } -func (c *Config) acceptEstablishedRelatedTraffic(ctx context.Context, remove bool) error { +func (c *Config) AcceptEstablishedRelatedTraffic(ctx context.Context, remove bool) error { return c.runMixedIptablesInstructions(ctx, []string{ fmt.Sprintf("%s OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)), fmt.Sprintf("%s INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)), }) } -func (c *Config) acceptOutputTrafficToVPN(ctx context.Context, +func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context, defaultInterface string, connection models.Connection, remove bool, ) error { protocol := connection.Protocol @@ -165,8 +165,11 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context, return c.runIP6tablesInstruction(ctx, instruction) } +// AcceptOutputFromIPToSubnet accepts outgoing traffic from sourceIP to destinationSubnet +// on the interface intf. If intf is empty, it is set to "*" which means all interfaces. +// If remove is true, the rule is removed instead of added. // Thanks to @npawelek. -func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context, +func (c *Config) AcceptOutputFromIPToSubnet(ctx context.Context, intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool, ) error { doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4() @@ -187,8 +190,11 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context, return c.runIP6tablesInstruction(ctx, instruction) } -// NDP uses multicast address (theres no broadcast in IPv6 like ARP uses in IPv4). -func (c *Config) acceptIpv6MulticastOutput(ctx context.Context, +// AcceptIpv6MulticastOutput accepts outgoing traffic to the IPv6 multicast address +// ff02::1:ff00:0/104, which is used for NDP (Neighbor Discovery Protocol) to resolve +// IPv6 addresses to MAC addresses. If intf is empty, it is set to "*" which means +// all interfaces. If remove is true, the rule is removed instead of added. +func (c *Config) AcceptIpv6MulticastOutput(ctx context.Context, intf string, remove bool, ) error { interfaceFlag := "-o " + intf @@ -200,8 +206,11 @@ func (c *Config) acceptIpv6MulticastOutput(ctx context.Context, return c.runIP6tablesInstruction(ctx, instruction) } -// Used for port forwarding, with intf set to tun. -func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error { +// AcceptInputToPort accepts incoming traffic on the specified port, for both TCP and UDP +// protocols, on the interface intf. If intf is empty, it is set to "*" which means all interfaces. +// If remove is true, the rule is removed instead of added. This is used for port forwarding, with +// intf set to the VPN tunnel interface. +func (c *Config) AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error { interfaceFlag := "-i " + intf if intf == "*" { // all interfaces interfaceFlag = "" @@ -212,8 +221,12 @@ func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16 }) } -// Used for VPN server side port forwarding, with intf set to the VPN tunnel interface. -func (c *Config) redirectPort(ctx context.Context, intf string, +// RedirectPort redirects incoming traffic on the specified source port to the +// specified destination port, for both TCP and UDP protocols, on the interface intf. +// If intf is empty, it is set to "*" which means all interfaces. If remove is true, +// the redirection is removed instead of added. This is used for VPN server side +// port forwarding, with intf set to the VPN tunnel interface. +func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort, destinationPort uint16, remove bool, ) (err error) { interfaceFlag := "-i " + intf @@ -260,7 +273,7 @@ func (c *Config) redirectPort(ctx context.Context, intf string, return nil } -func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove bool) error { +func (c *Config) RunUserPostRules(ctx context.Context, filepath string, remove bool) error { file, err := os.OpenFile(filepath, os.O_RDONLY, 0) if os.IsNotExist(err) { return nil diff --git a/internal/firewall/iptablesmix.go b/internal/firewall/iptables/iptablesmix.go similarity index 96% rename from internal/firewall/iptablesmix.go rename to internal/firewall/iptables/iptablesmix.go index 8d45c737..9c3d2dcb 100644 --- a/internal/firewall/iptablesmix.go +++ b/internal/firewall/iptables/iptablesmix.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" diff --git a/internal/firewall/list.go b/internal/firewall/iptables/list.go similarity index 99% rename from internal/firewall/list.go rename to internal/firewall/iptables/list.go index a81510a8..49f855fe 100644 --- a/internal/firewall/list.go +++ b/internal/firewall/iptables/list.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "errors" diff --git a/internal/firewall/list_test.go b/internal/firewall/iptables/list_test.go similarity index 99% rename from internal/firewall/list_test.go rename to internal/firewall/iptables/list_test.go index 0d3fa05c..38828784 100644 --- a/internal/firewall/list_test.go +++ b/internal/firewall/iptables/list_test.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "net/netip" diff --git a/internal/firewall/mocks_generate_test.go b/internal/firewall/iptables/mocks_generate_test.go similarity index 83% rename from internal/firewall/mocks_generate_test.go rename to internal/firewall/iptables/mocks_generate_test.go index ae563ca9..571d90a1 100644 --- a/internal/firewall/mocks_generate_test.go +++ b/internal/firewall/iptables/mocks_generate_test.go @@ -1,3 +1,3 @@ -package firewall +package iptables //go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger diff --git a/internal/firewall/mocks_test.go b/internal/firewall/iptables/mocks_test.go similarity index 95% rename from internal/firewall/mocks_test.go rename to internal/firewall/iptables/mocks_test.go index a46ba5b1..c4c0b828 100644 --- a/internal/firewall/mocks_test.go +++ b/internal/firewall/iptables/mocks_test.go @@ -1,8 +1,8 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/qdm12/gluetun/internal/firewall (interfaces: CmdRunner,Logger) +// Source: github.com/qdm12/gluetun/internal/firewall/iptables (interfaces: CmdRunner,Logger) -// Package firewall is a generated GoMock package. -package firewall +// Package iptables is a generated GoMock package. +package iptables import ( exec "os/exec" diff --git a/internal/firewall/parse.go b/internal/firewall/iptables/parse.go similarity index 99% rename from internal/firewall/parse.go rename to internal/firewall/iptables/parse.go index 948290d8..a18d7714 100644 --- a/internal/firewall/parse.go +++ b/internal/firewall/iptables/parse.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "errors" diff --git a/internal/firewall/parse_test.go b/internal/firewall/iptables/parse_test.go similarity index 99% rename from internal/firewall/parse_test.go rename to internal/firewall/iptables/parse_test.go index fa45b098..51c5ab7c 100644 --- a/internal/firewall/parse_test.go +++ b/internal/firewall/iptables/parse_test.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "net/netip" diff --git a/internal/firewall/support.go b/internal/firewall/iptables/support.go similarity index 92% rename from internal/firewall/support.go rename to internal/firewall/iptables/support.go index 0295c802..33245228 100644 --- a/internal/firewall/support.go +++ b/internal/firewall/iptables/support.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" @@ -11,10 +11,10 @@ import ( ) var ( - ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing") - ErrTestRuleCleanup = errors.New("failed cleaning up test rule") - ErrInputPolicyNotFound = errors.New("input policy not found") - ErrIPTablesNotSupported = errors.New("no iptables supported found") + ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing") + ErrTestRuleCleanup = errors.New("failed cleaning up test rule") + ErrInputPolicyNotFound = errors.New("input policy not found") + ErrNotSupported = errors.New("no iptables supported found") ) func checkIptablesSupport(ctx context.Context, runner CmdRunner, @@ -57,7 +57,7 @@ func checkIptablesSupport(ctx context.Context, runner CmdRunner, } return "", fmt.Errorf("%w: errors encountered are: %s", - ErrIPTablesNotSupported, strings.Join(allUnsupportedMessages, "; ")) + ErrNotSupported, strings.Join(allUnsupportedMessages, "; ")) } func testIptablesPath(ctx context.Context, path string, diff --git a/internal/firewall/support_test.go b/internal/firewall/iptables/support_test.go similarity index 99% rename from internal/firewall/support_test.go rename to internal/firewall/iptables/support_test.go index d0d413d5..9df4ed21 100644 --- a/internal/firewall/support_test.go +++ b/internal/firewall/iptables/support_test.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" @@ -101,7 +101,7 @@ func Test_checkIptablesSupport(t *testing.T) { return runner }, iptablesPathsToTry: []string{"path1", "path2"}, - errSentinel: ErrIPTablesNotSupported, + errSentinel: ErrNotSupported, errMessage: "no iptables supported found: " + "errors encountered are: " + "path1: output 1 (exit code 4); " + diff --git a/internal/firewall/tcp.go b/internal/firewall/iptables/tcp.go similarity index 99% rename from internal/firewall/tcp.go rename to internal/firewall/iptables/tcp.go index 6d21e360..77e5c5f2 100644 --- a/internal/firewall/tcp.go +++ b/internal/firewall/iptables/tcp.go @@ -1,4 +1,4 @@ -package firewall +package iptables import ( "context" diff --git a/internal/firewall/outboundsubnets.go b/internal/firewall/outboundsubnets.go index 1d2406d7..8862f266 100644 --- a/internal/firewall/outboundsubnets.go +++ b/internal/firewall/outboundsubnets.go @@ -48,7 +48,7 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Pref } firewallUpdated = true - err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface, + err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface, defaultRoute.AssignedIP, subNet, remove) if err != nil { c.logger.Error("cannot remove outdated outbound subnet: " + err.Error()) @@ -77,7 +77,7 @@ func (c *Config) addOutboundSubnets(ctx context.Context, subnets []netip.Prefix) } firewallUpdated = true - err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface, + err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface, defaultRoute.AssignedIP, subnet, remove) if err != nil { return err diff --git a/internal/firewall/ports.go b/internal/firewall/ports.go index 6f5867ee..aee52ce0 100644 --- a/internal/firewall/ports.go +++ b/internal/firewall/ports.go @@ -35,7 +35,7 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) ( c.logger.Info("setting allowed input port " + fmt.Sprint(port) + " through interface " + intf + "...") const remove = false - if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil { + if err := c.impl.AcceptInputToPort(ctx, intf, port, remove); err != nil { return fmt.Errorf("allowing input to port %d through interface %s: %w", port, intf, err) } @@ -68,7 +68,7 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error) const remove = true for netInterface := range interfacesSet { - err := c.acceptInputToPort(ctx, netInterface, port, remove) + err := c.impl.AcceptInputToPort(ctx, netInterface, port, remove) if err != nil { return fmt.Errorf("removing allowed port %d on interface %s: %w", port, netInterface, err) diff --git a/internal/firewall/redirect.go b/internal/firewall/redirect.go index 96e41804..cffab326 100644 --- a/internal/firewall/redirect.go +++ b/internal/firewall/redirect.go @@ -50,7 +50,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort, return nil case conflict != nil: const remove = true - err = c.redirectPort(ctx, conflict.interfaceName, conflict.sourcePort, + err = c.impl.RedirectPort(ctx, conflict.interfaceName, conflict.sourcePort, conflict.destinationPort, remove) if err != nil { return fmt.Errorf("removing conflicting redirection: %w", err) @@ -60,7 +60,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort, } const remove = false - err = c.redirectPort(ctx, intf, sourcePort, destinationPort, remove) + err = c.impl.RedirectPort(ctx, intf, sourcePort, destinationPort, remove) if err != nil { return fmt.Errorf("redirecting port: %w", err) } diff --git a/internal/firewall/vpn.go b/internal/firewall/vpn.go index 4e906854..7f3f9498 100644 --- a/internal/firewall/vpn.go +++ b/internal/firewall/vpn.go @@ -28,7 +28,7 @@ func (c *Config) SetVPNConnection(ctx context.Context, remove := true if c.vpnConnection.IP.IsValid() { for _, defaultRoute := range c.defaultRoutes { - if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil { + if err := c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil { c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error()) } } @@ -36,7 +36,7 @@ func (c *Config) SetVPNConnection(ctx context.Context, c.vpnConnection = models.Connection{} if c.vpnIntf != "" { - if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil { + if err = c.impl.AcceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil { c.logger.Error("cannot remove outdated VPN interface rule: " + err.Error()) } } @@ -45,13 +45,13 @@ func (c *Config) SetVPNConnection(ctx context.Context, remove = false for _, defaultRoute := range c.defaultRoutes { - if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil { + if err := c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil { return fmt.Errorf("allowing output traffic through VPN connection: %w", err) } } c.vpnConnection = connection - if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil { + if err = c.impl.AcceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil { return fmt.Errorf("accepting output traffic through interface %s: %w", vpnIntf, err) } c.vpnIntf = vpnIntf diff --git a/internal/firewall/wrappers.go b/internal/firewall/wrappers.go new file mode 100644 index 00000000..1e041497 --- /dev/null +++ b/internal/firewall/wrappers.go @@ -0,0 +1,21 @@ +package firewall + +import ( + "context" + "net/netip" +) + +func (c *Config) Version(ctx context.Context) (version string, err error) { + return c.impl.Version(ctx) +} + +// TempDropOutputTCPRST temporarily drops outgoing TCP RST packets to the specified address and port, +// for any TCP packets not marked with the excludeMark given. +// This is necessary for TCP path MTU discovery to work, as the kernel will try to terminate the connection +// by sending a TCP RST packet, although we want to handle the connection manually. +func (c *Config) TempDropOutputTCPRST(ctx context.Context, + src, dst netip.AddrPort, excludeMark int) ( + revert func(ctx context.Context) error, err error, +) { + return c.impl.TempDropOutputTCPRST(ctx, src, dst, excludeMark) +} diff --git a/internal/pmtud/pmtud.go b/internal/pmtud/pmtud.go index f06d882b..836d347e 100644 --- a/internal/pmtud/pmtud.go +++ b/internal/pmtud/pmtud.go @@ -7,7 +7,7 @@ import ( "net/netip" "time" - "github.com/qdm12/gluetun/internal/firewall" + "github.com/qdm12/gluetun/internal/firewall/iptables" "github.com/qdm12/gluetun/internal/pmtud/constants" "github.com/qdm12/gluetun/internal/pmtud/icmp" "github.com/qdm12/gluetun/internal/pmtud/tcp" @@ -71,7 +71,7 @@ func PathMTUDiscover(ctx context.Context, icmpAddrs []netip.Addr, tcpAddrs []net } mtu, err = tcp.PathMTUDiscover(ctx, tcpAddrs, minMTU, maxPossibleMTU, tryTimeout, fw, logger) if err != nil { - if errors.Is(err, firewall.ErrMarkMatchModuleMissing) { + if errors.Is(err, iptables.ErrMarkMatchModuleMissing) { logger.Debugf("aborting TCP path MTU discovery: %s", err) if icmpSuccess { return maxPossibleMTU, nil // only rely on ICMP PMTUD results diff --git a/internal/pmtud/tcp/helpers_test.go b/internal/pmtud/tcp/helpers_test.go index 7eeb0c8b..e5a21e05 100644 --- a/internal/pmtud/tcp/helpers_test.go +++ b/internal/pmtud/tcp/helpers_test.go @@ -8,6 +8,7 @@ import ( "github.com/qdm12/gluetun/internal/command" "github.com/qdm12/gluetun/internal/firewall" + "github.com/qdm12/gluetun/internal/firewall/iptables" "github.com/qdm12/gluetun/internal/netlink" "github.com/qdm12/gluetun/internal/pmtud/constants" "github.com/qdm12/gluetun/internal/routing" @@ -35,7 +36,7 @@ func getFirewall(t *testing.T) *firewall.Config { cmder := command.New() var err error testFirewall, err = firewall.NewConfig(t.Context(), noopLogger, cmder, nil, nil) - if errors.Is(err, firewall.ErrIPTablesNotSupported) { + if errors.Is(err, iptables.ErrNotSupported) { t.Skip("iptables not installed, skipping TCP PMTUD tests") } require.NoError(t, err, "creating firewall config") diff --git a/internal/pmtud/tcp/mss.go b/internal/pmtud/tcp/mss.go index ea9b751b..bedc2a5d 100644 --- a/internal/pmtud/tcp/mss.go +++ b/internal/pmtud/tcp/mss.go @@ -7,7 +7,7 @@ import ( "net/netip" "time" - "github.com/qdm12/gluetun/internal/firewall" + "github.com/qdm12/gluetun/internal/firewall/iptables" "github.com/qdm12/gluetun/internal/pmtud/constants" "github.com/qdm12/gluetun/internal/pmtud/ip" ) @@ -43,7 +43,7 @@ func findHighestMSSDestination(ctx context.Context, familyToFD map[int]fileDescr if result.err != nil { switch { case err != nil: // error already occurred for another findMSS goroutine - case errors.Is(result.err, firewall.ErrMarkMatchModuleMissing): + case errors.Is(result.err, iptables.ErrMarkMatchModuleMissing): err = fmt.Errorf("finding MSS for %s: %w", result.dst, result.err) case dst.Addr().Is6() && errors.Is(result.err, ip.ErrNetworkUnreachable): // silently discard IPv6 network unreachable errors since they are common