Fourth fallback, use DROP temporarily instead of REJECT

This commit is contained in:
Quentin McGaw
2026-02-27 12:17:12 +00:00
parent 1fd4cc511a
commit bfc8136bc9
3 changed files with 59 additions and 27 deletions
+41 -22
View File
@@ -10,33 +10,52 @@ import (
"github.com/qdm12/gluetun/internal/netlink" "github.com/qdm12/gluetun/internal/netlink"
) )
// Note remove is a no-op if conntrack netlink is supported by the kernel.
func (c *Config) flushExistingConnections(ctx context.Context) error { func (c *Config) flushExistingConnections(ctx context.Context) error {
err := c.netlinker.FlushConntrack() tries := []struct {
switch { name string
case err == nil: f func(ctx context.Context) error
return nil }{
case errors.Is(err, netlink.ErrConntrackNetlinkNotSupported): {name: "flushing conntrack", f: func(_ context.Context) error {
c.logger.Debugf("falling back to marking and filtering unmarked packets because flush conntrack failed: %s", err) return c.netlinker.FlushConntrack()
err = c.impl.AcceptOutputPublicOnlyNewTraffic(ctx) }},
if err != nil { {name: "marking and filtering unmarked packets", f: c.impl.AcceptOutputPublicOnlyNewTraffic},
if errors.Is(err, iptables.ErrKernelModuleMissing) { {name: "rejecting connections for one second", f: c.rejectOutputTrafficTemporarily},
c.logger.Debugf("falling back to killing connections for one second because marking packets failed: %s", err) {name: "dropping connections for one second", f: c.dropOutputTrafficTemporarily},
return c.rejectOutputTrafficTemporarily(ctx)
}
return fmt.Errorf("accepting only new output public traffic: %w", err)
}
return nil
default:
return fmt.Errorf("flushing conntrack: %w", err)
} }
errs := make([]error, 0, len(tries))
for i, try := range tries {
if i > 0 {
c.logger.Debugf("falling back to %s because %s failed: %s", try.name, tries[i-1].name, errs[i-1])
}
err := try.f(ctx)
if err == nil {
return nil
}
err = fmt.Errorf("%s: %w", try.name, err)
if !errors.Is(err, iptables.ErrKernelModuleMissing) && !errors.Is(err, netlink.ErrConntrackNetlinkNotSupported) {
return err
}
errs = append(errs, err)
}
return fmt.Errorf("all tries failed: %v", errs) //nolint:err113
} }
func (c *Config) rejectOutputTrafficTemporarily(ctx context.Context) error { func (c *Config) rejectOutputTrafficTemporarily(ctx context.Context) error {
return setupThenRevert(ctx, c.impl.RejectOutputPublicTraffic)
}
func (c *Config) dropOutputTrafficTemporarily(ctx context.Context) error {
return setupThenRevert(ctx, c.impl.DropOutputPublicTraffic)
}
// setupThenRevert is a helper function to run a setup function that takes a remove boolean argument,
// and then run the same function with remove set to true after one second or when the context is canceled,
// whichever comes first.
func setupThenRevert(ctx context.Context, f func(ctx context.Context, remove bool) error) error {
remove := false remove := false
err := c.impl.RejectOutputPublicTraffic(ctx, remove) err := f(ctx, remove)
if err != nil { if err != nil {
return fmt.Errorf("rejecting only new output public traffic: %w", err) return fmt.Errorf("setting up: %w", err)
} }
timer := time.NewTimer(time.Second) timer := time.NewTimer(time.Second)
select { select {
@@ -47,9 +66,9 @@ func (c *Config) rejectOutputTrafficTemporarily(ctx context.Context) error {
remove = true remove = true
// Use [context.Background] to make sure this is removed, even if the context // Use [context.Background] to make sure this is removed, even if the context
// passed to this function is canceled. // passed to this function is canceled.
err = c.impl.RejectOutputPublicTraffic(context.Background(), remove) err = f(context.Background(), remove)
if err != nil { if err != nil {
return fmt.Errorf("reverting rejecting only new output public traffic: %w", err) return fmt.Errorf("reverting: %w", err)
} }
return nil return nil
} }
+1
View File
@@ -28,6 +28,7 @@ type firewallImpl interface { //nolint:interfacebloat
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error) SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error
RejectOutputPublicTraffic(ctx context.Context, remove bool) error RejectOutputPublicTraffic(ctx context.Context, remove bool) error
DropOutputPublicTraffic(ctx context.Context, remove bool) error
AcceptInputThroughInterface(ctx context.Context, intf string) error AcceptInputThroughInterface(ctx context.Context, intf string) error
AcceptEstablishedRelatedTraffic(ctx context.Context) error AcceptEstablishedRelatedTraffic(ctx context.Context) error
AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error
+17 -5
View File
@@ -208,6 +208,14 @@ func (c *Config) AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error {
} }
func (c *Config) RejectOutputPublicTraffic(ctx context.Context, remove bool) error { func (c *Config) RejectOutputPublicTraffic(ctx context.Context, remove bool) error {
return c.targetOutputPublicTraffic(ctx, "REJECT", remove)
}
func (c *Config) DropOutputPublicTraffic(ctx context.Context, remove bool) error {
return c.targetOutputPublicTraffic(ctx, "DROP", remove)
}
func (c *Config) targetOutputPublicTraffic(ctx context.Context, target string, remove bool) error {
removeInstructions := []string{ removeInstructions := []string{
"-D OUTPUT -j PUBLIC_ONLY", "-D OUTPUT -j PUBLIC_ONLY",
"-F PUBLIC_ONLY", "-F PUBLIC_ONLY",
@@ -223,11 +231,15 @@ func (c *Config) RejectOutputPublicTraffic(ctx context.Context, remove bool) err
ipv6Instructions = append(ipv6Instructions, instruction) ipv6Instructions = append(ipv6Instructions, instruction)
} }
// Block UDP and ICMP, sending back ICMP port unreachable. if target == "REJECT" {
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate RELATED,ESTABLISHED -j REJECT") // Block TCP by sending back TCP RST packets.
// Block TCP by sending back TCP RST packets. appendToBoth("-A PUBLIC_ONLY -p tcp -m conntrack --ctstate RELATED,ESTABLISHED " +
appendToBoth("-A PUBLIC_ONLY -p tcp -m conntrack --ctstate RELATED,ESTABLISHED " + "-j REJECT --reject-with tcp-reset")
"-j REJECT --reject-with tcp-reset") // Block UDP and ICMP, sending back ICMP port unreachable.
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate RELATED,ESTABLISHED -j REJECT")
} else {
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate RELATED,ESTABLISHED -j " + target)
}
appendToBoth("-I OUTPUT -j PUBLIC_ONLY") appendToBoth("-I OUTPUT -j PUBLIC_ONLY")
kernelErr := checkKernelModulesAreOK(c.modules.nfConntrack, kernelErr := checkKernelModulesAreOK(c.modules.nfConntrack,