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"
)
// Note remove is a no-op if conntrack netlink is supported by the kernel.
func (c *Config) flushExistingConnections(ctx context.Context) error {
err := c.netlinker.FlushConntrack()
switch {
case err == nil:
return nil
case errors.Is(err, netlink.ErrConntrackNetlinkNotSupported):
c.logger.Debugf("falling back to marking and filtering unmarked packets because flush conntrack failed: %s", err)
err = c.impl.AcceptOutputPublicOnlyNewTraffic(ctx)
if err != nil {
if errors.Is(err, iptables.ErrKernelModuleMissing) {
c.logger.Debugf("falling back to killing connections for one second because marking packets failed: %s", err)
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)
tries := []struct {
name string
f func(ctx context.Context) error
}{
{name: "flushing conntrack", f: func(_ context.Context) error {
return c.netlinker.FlushConntrack()
}},
{name: "marking and filtering unmarked packets", f: c.impl.AcceptOutputPublicOnlyNewTraffic},
{name: "rejecting connections for one second", f: c.rejectOutputTrafficTemporarily},
{name: "dropping connections for one second", f: c.dropOutputTrafficTemporarily},
}
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 {
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
err := c.impl.RejectOutputPublicTraffic(ctx, remove)
err := f(ctx, remove)
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)
select {
@@ -47,9 +66,9 @@ func (c *Config) rejectOutputTrafficTemporarily(ctx context.Context) error {
remove = true
// Use [context.Background] to make sure this is removed, even if the context
// passed to this function is canceled.
err = c.impl.RejectOutputPublicTraffic(context.Background(), remove)
err = f(context.Background(), remove)
if err != nil {
return fmt.Errorf("reverting rejecting only new output public traffic: %w", err)
return fmt.Errorf("reverting: %w", err)
}
return nil
}
+1
View File
@@ -28,6 +28,7 @@ type firewallImpl interface { //nolint:interfacebloat
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error
RejectOutputPublicTraffic(ctx context.Context, remove bool) error
DropOutputPublicTraffic(ctx context.Context, remove bool) error
AcceptInputThroughInterface(ctx context.Context, intf string) error
AcceptEstablishedRelatedTraffic(ctx context.Context) 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 {
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{
"-D OUTPUT -j PUBLIC_ONLY",
"-F PUBLIC_ONLY",
@@ -223,11 +231,15 @@ func (c *Config) RejectOutputPublicTraffic(ctx context.Context, remove bool) err
ipv6Instructions = append(ipv6Instructions, instruction)
}
// Block UDP and ICMP, sending back ICMP port unreachable.
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate RELATED,ESTABLISHED -j REJECT")
// Block TCP by sending back TCP RST packets.
appendToBoth("-A PUBLIC_ONLY -p tcp -m conntrack --ctstate RELATED,ESTABLISHED " +
"-j REJECT --reject-with tcp-reset")
if target == "REJECT" {
// Block TCP by sending back TCP RST packets.
appendToBoth("-A PUBLIC_ONLY -p tcp -m conntrack --ctstate RELATED,ESTABLISHED " +
"-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")
kernelErr := checkKernelModulesAreOK(c.modules.nfConntrack,