Skip to content

Fix: Re-apply custom firewall post-rules after VPN reconnection and rule updates#2821

Open
atasmohammadi wants to merge 6 commits intoqdm12:masterfrom
atasmohammadi:master
Open

Fix: Re-apply custom firewall post-rules after VPN reconnection and rule updates#2821
atasmohammadi wants to merge 6 commits intoqdm12:masterfrom
atasmohammadi:master

Conversation

@atasmohammadi
Copy link

Problem

When using custom firewall post-rules in /iptables/post-rules.txt, these rules are only applied during the initial firewall setup but are not re-applied when:

  • VPN connection changes or reconnects
  • Firewall ports are updated
  • Outbound subnets are modified

This causes custom rules to be lost during normal operation, requiring manual re-application of rules (e.g., via SSH or exec into the container).

Solution

This PR adds a helper method applyUserPostRules() and ensures it's called after any significant firewall rule changes in:

  • SetVPNConnection() in vpn.go
  • SetAllowedPort() in ports.go
  • SetOutboundSubnets() in outboundsubnets.go

The changes are minimal and follow the existing code patterns.

Testing

I've tested this fix with a custom Gluetun setup using WireGuard and the following scenario:

  1. Created custom post-rules in /iptables/post-rules.txt to allow specific traffic
  2. Observed that without this fix, rules were lost after VPN reconnection
  3. With this fix, rules persist through VPN reconnections and firewall updates
  4. Verified with iptables -L that rules remain in place after reconnection events

Use Case

This is particularly important for users who:

  • Need to allow specific traffic through the VPN tunnel (tun0)
  • Run other containers in network_mode: "container:gluetun"
  • Need persistent custom firewall rules for services like BitTorrent clients

Changes

  • Added applyUserPostRules() helper method in firewall.go
  • Modified SetVPNConnection() to re-apply post-rules after VPN changes
  • Modified SetAllowedPort() to re-apply post-rules after port changes
  • Modified SetOutboundSubnets() to re-apply post-rules after subnet changes
  • Added improved logging in runUserPostRules() (optional)

Copy link
Owner

@qdm12 qdm12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Can you please move things around as suggested in comments, in order to have an easier PR review? Thanks!

VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
tags: ${{ steps.meta.outputs.tags }}
push: true
push: ${{ github.event_name != 'pull_request' }}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert this file for the time being, this is out of scope.

release_name: Release ${{ github.ref }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false No newline at end of file
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, revert this. Happy to get another PR eventually though.

_ = c.runIptablesInstruction(ctx, flipRule(rule))
}
}()

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit unneeded newline change here

Suggested change

Comment on lines +130 to +164
func (c *Config) disable(ctx context.Context) (err error) {
// Clear applied post-rules when disabling
if err = c.clearAppliedPostRules(ctx); err != nil {
c.logger.Warn("failed to clear post-rules during disable: " + err.Error())
}

if err = c.clearAllRules(ctx); err != nil {
return fmt.Errorf("clearing all rules: %w", err)
}
if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil {
return fmt.Errorf("setting ipv4 policies: %w", err)
}
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
return fmt.Errorf("setting ipv6 policies: %w", err)
}

const remove = true
err = c.redirectPorts(ctx, remove)
if err != nil {
return fmt.Errorf("removing port redirections: %w", err)
}

return nil
}

// To use in defered call when enabling the firewall.
func (c *Config) fallbackToDisabled(ctx context.Context) {
if ctx.Err() != nil {
return
}
if err := c.disable(ctx); err != nil {
c.logger.Error("failed reversing firewall changes: " + err.Error())
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move these blocks where they previously were to see diffs more clearly.

@qdm12 qdm12 force-pushed the master branch 3 times, most recently from 27f74e4 to fe3d4a9 Compare January 24, 2026 17:56
@qdm12 qdm12 force-pushed the master branch 2 times, most recently from d0247a1 to 0eeee5c Compare February 25, 2026 04:24
Copy link
Owner

@qdm12 qdm12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point of this pull request, please elaborate on how this helps

Also

Observed that without this fix, rules were lost after VPN reconnection

That is not true, rules are persisted already as far as I'm aware.

Plus modifications to SetVPNConnection and SetOutboundSubnets are missing.

Comment on lines +317 to +318
// Check if this rule was already applied (avoid duplicates)
if !remove {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we track duplicates? That should be up to the user to ensure they don't have duplicate iptables commands

Comment on lines +45 to +49
// Apply post-rules only once after adding the port, and only if not already applied
if err := c.applyPostRulesOnce(ctx); err != nil {
c.logger.Warn("failed to apply post-rules after adding port: " + err.Error())
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this different from just running the post rules once at firewall enabling? Especially given applyPostRulesOnce is not even aware of the newly allowed port.

Comment on lines +63 to 67
// Clear any previously applied post-rules
if err = c.clearAppliedPostRules(ctx); err != nil {
c.logger.Warn("failed to clear previous post-rules: " + err.Error())
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not needed, given the firewall is only enabled once on a clear set of rules. Plus disable does remove all rules anyway, in case the firewall would get disabled then re-enabled.

Comment on lines +131 to +134
// Clear applied post-rules when disabling
if err = c.clearAppliedPostRules(ctx); err != nil {
c.logger.Warn("failed to clear post-rules during disable: " + err.Error())
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed because of existing clearAllRules

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants