Skip to content

[client] Passthrough all non-NetBird chains to prevent them from dropping NetBird traffic#4899

Merged
lixmal merged 3 commits intomainfrom
nftables-all-chains
Dec 4, 2025
Merged

[client] Passthrough all non-NetBird chains to prevent them from dropping NetBird traffic#4899
lixmal merged 3 commits intomainfrom
nftables-all-chains

Conversation

@lixmal
Copy link
Copy Markdown
Collaborator

@lixmal lixmal commented Dec 3, 2025

Describe your changes

Currently, some tools (firewalld, miniupnpd, docker, ...) might create extra ntables tables with chains that have a drop policy. This would prevent NetBird's accept rules from working; hence we need to insert a passthrough rule in all input and forward chains that we find, not just the ip filter table from iptables-nft.

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Dynamic detection and handling of external firewall chains with targeted accept rules for forward and input traffic.
    • Per-chain and per-interface rule insertion with expanded table support for broader firewall coverage.
    • Enhanced operational logging for clearer visibility.
  • Bug Fixes

    • More reliable, deterministic cleanup of rules across filter and external chains.
    • Improved error aggregation and handling during rule application and removal.

✏️ Tip: You can customize this high-level summary in your review settings.

@lixmal lixmal changed the title Passthrough all non-NetBird chains to prevent them from dropping NetBird traffic [client] Passthrough all non-NetBird chains to prevent them from dropping NetBird traffic Dec 3, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 3, 2025

Walkthrough

Adds runtime discovery and deterministic handling of external nftables chains, makes nftables accept/remove flows table-aware, introduces helpers for hook/family naming and external-chain detection, and refactors insertion/removal into per-table and per-chain operations with aggregated error handling and expanded table constants. (34 words)

Changes

Cohort / File(s) Summary
Nftables router changes
client/firewall/nftables/router_linux.go
Added external-chain support and table-aware accept/remove flows: new helpers hookName, familyName, findExternalChains, isExternalChain, isIptablesTable; new methods acceptExternalChainsRules, insertForwardAcceptRules, insertInputAcceptRule, removeAcceptFilterRules, removeFilterTableRules, removeExternalChainsRules, findExternalChains; changed signature to acceptFilterRulesNftables(table *nftables.Table); added removeAcceptRulesFromTable/removeAcceptRulesFromChain; expanded nftables table constants; improved logging and aggregated error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect external-chain detection/filtering logic (findExternalChains, isExternalChain, isIptablesTable).
  • Verify callers/tests updated for acceptFilterRulesNftables(table *nftables.Table) signature.
  • Review deterministic cleanup in removeExternalChainsRules and removeAcceptRulesFromTable/removeAcceptRulesFromChain.
  • Validate aggregated error propagation and added logging contexts.

Possibly related PRs

Suggested reviewers

  • pappz

Poem

"I hopped through chains both wide and neat,
I named each hook with tiny feet,
I sniffed out externals, left no mess,
I placed accept rules, then cleaned the rest,
A rabbit's hop—secure and fleet! 🥕"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding passthrough rules to all non-NetBird chains to prevent traffic dropping, which matches the core functionality of the changeset.
Description check ✅ Passed The description includes key sections (changes description, bug fix checkbox) and explains the motivation. However, it lacks an issue ticket link and did not document why tests weren't created despite being a bug fix.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nftables-all-chains

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
client/firewall/nftables/router_linux.go (2)

1248-1264: Deterministic cleanup is well-designed.

The runtime scanning in removeExternalChainsRules ensures proper cleanup even if chains changed after startup or NetBird crashed. Good approach for resilience.

Note: This function logs errors as warnings without aggregating them (line 1259), unlike other removal functions that use multierror. This makes cleanup more resilient but loses error visibility. Consider whether you want to aggregate these errors while still continuing the cleanup loop.


1266-1306: Consider extracting chain filter logic to reduce complexity.

SonarCloud flagged this function with cognitive complexity 23 (threshold 20). While the logic is correct, extracting the chain filtering conditions (lines 1280-1299) into a helper method like isExternalChainRelevant(chain *nftables.Chain) bool would improve readability and testability.

Example refactor:

+func (r *router) isExternalChainRelevant(chain *nftables.Chain) bool {
+	if r.workTable != nil && chain.Table.Name == r.workTable.Name {
+		return false
+	}
+	if r.filterTable != nil && chain.Table.Name == r.filterTable.Name && chain.Table.Family == r.filterTable.Family {
+		return false
+	}
+	if chain.Type != nftables.ChainTypeFilter {
+		return false
+	}
+	if chain.Hooknum == nil {
+		return false
+	}
+	if *chain.Hooknum != *nftables.ChainHookForward && *chain.Hooknum != *nftables.ChainHookInput {
+		return false
+	}
+	return true
+}
+
 func (r *router) findExternalChains() []*nftables.Chain {
 	var chains []*nftables.Chain
 	families := []nftables.TableFamily{nftables.TableFamilyIPv4, nftables.TableFamilyINet}
 
 	for _, family := range families {
 		allChains, err := r.conn.ListChainsOfTableFamily(family)
 		if err != nil {
 			log.Debugf("list chains for family %d: %v", family, err)
 			continue
 		}
 
 		for _, chain := range allChains {
-			if r.workTable != nil && chain.Table.Name == r.workTable.Name {
-				continue
-			}
-			if r.filterTable != nil && chain.Table.Name == r.filterTable.Name && chain.Table.Family == r.filterTable.Family {
-				continue
-			}
-			if chain.Type != nftables.ChainTypeFilter {
-				continue
-			}
-			if chain.Hooknum == nil {
-				continue
-			}
-			if *chain.Hooknum != *nftables.ChainHookForward && *chain.Hooknum != *nftables.ChainHookInput {
-				continue
-			}
-			chains = append(chains, chain)
+			if r.isExternalChainRelevant(chain) {
+				chains = append(chains, chain)
+			}
 		}
 	}
 
 	return chains
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a232cf6 and 9f7c976.

📒 Files selected for processing (1)
  • client/firewall/nftables/router_linux.go (8 hunks)
🧰 Additional context used
🪛 GitHub Check: SonarCloud Code Analysis
client/firewall/nftables/router_linux.go

[failure] 1268-1268: Refactor this method to reduce its Cognitive Complexity from 23 to the 20 allowed.

See more on https://sonarcloud.io/project/issues?id=netbirdio_netbird&issues=AZrieIOu2chXJrHgifJl&open=AZrieIOu2chXJrHgifJl&pullRequest=4899

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Relay / Unit (386)
  • GitHub Check: Management / Unit (amd64, sqlite)
  • GitHub Check: Management / Unit (amd64, mysql)
  • GitHub Check: Management / Unit (amd64, postgres)
  • GitHub Check: Relay / Unit (amd64, -race)
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: release_ui_darwin
  • GitHub Check: release_ui
  • GitHub Check: release
  • GitHub Check: Client / Unit
  • GitHub Check: JS / Lint
  • GitHub Check: Windows
  • GitHub Check: Darwin
  • GitHub Check: Linux
  • GitHub Check: iOS / Build
  • GitHub Check: Android / Build
  • GitHub Check: Client / Unit
  • GitHub Check: Client / Unit
🔇 Additional comments (6)
client/firewall/nftables/router_linux.go (6)

94-94: LGTM! Appropriate log level change.

Downgrading from warning to debug is appropriate here since the absence of a filter table is handled gracefully and doesn't indicate a problem.


186-198: LGTM! Clean helper for readable logging.

The nil check prevents panics, and the hook mapping improves log readability. Well done.


946-958: LGTM! Clean refactoring with proper error aggregation.

The split into filter table and external chains paths improves code organization, and error handling using multierror is appropriate.


1015-1090: LGTM! Good refactoring for flexibility.

Parameterizing the table makes this function more flexible and reusable. The implementation correctly uses the provided table parameter throughout.


1092-1179: Accept rules are only added at startup.

The dynamic discovery in acceptExternalChainsRules runs during initialization, so external chains created after NetBird starts won't receive accept rules until NetBird restarts. This is likely acceptable since most firewall tools (firewalld, docker) create their chains during system boot, but consider whether periodic re-scanning would be beneficial for your use case.

The implementation itself looks correct—rules are properly inserted with UserData tags for later cleanup.

Do you want to add periodic re-scanning of chains, or is startup-only insertion sufficient for your deployment scenarios?


103-105: Cleanup at init ensures idempotent startup.

Calling removeAcceptFilterRules() during initialization (before adding new rules) ensures clean state even after crashes. Good defensive programming.

@lixmal lixmal force-pushed the nftables-all-chains branch 2 times, most recently from 203753e to daec924 Compare December 3, 2025 04:37
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/firewall/nftables/router_linux.go (1)

983-1001: Fix multierror.Append usage to properly accumulate errors

In both acceptFilterRulesIptables() and removeAcceptFilterRulesIptables(), the first argument to multierror.Append is the current iteration's err instead of the accumulator merr, causing earlier errors to be discarded:

merr = multierror.Append(err, fmt.Errorf("add iptables forward rule: %v", err))

Should be:

merr = multierror.Append(merr, fmt.Errorf("add iptables forward rule: %v", err))

This applies to all error handling in both functions. The correct pattern is used elsewhere in the file, so this appears to be an inconsistency. Each iteration overwrites merr with a new multierror containing only the current failure, losing previous errors.

🧹 Nitpick comments (3)
client/firewall/nftables/router_linux.go (3)

91-96: Distinguish “filter table not found” from other nftables errors in newRouter

Right now all failures from loadFilterTable() are treated as “filter table not found” and silently ignored. It would be safer to only downgrade errFilterTableNotFound while surfacing or at least warning on other errors (e.g. netlink permission issues):

-	r.filterTable, err = r.loadFilterTable()
-	if err != nil {
-		log.Debugf("ip filter table not found: %v", err)
-	}
+	r.filterTable, err = r.loadFilterTable()
+	if err != nil {
+		if errors.Is(err, errFilterTableNotFound) {
+			log.Debugf("ip filter table not found: %v", err)
+		} else {
+			return nil, fmt.Errorf("load filter table: %w", err)
+		}
+	}

This keeps the “no filter table” case non-fatal without masking genuine nftables failures.


1181-1215: removeAcceptRulesFromChain/removeExternalChainsRules behave as best‑effort cleanup

removeAcceptRulesFromChain() uses UserData equality on the three NetBird markers and avoids touching other rules, which is exactly what you want. removeExternalChainsRules() then iterates all external chains, logs per‑chain failures as warnings, and only returns a flush error, making this path intentionally best‑effort.

If you later need stronger guarantees (e.g. for Reset()), consider aggregating per‑chain errors via multierror instead of logging them only.


1217-1256: findExternalChains implements the right selection logic; Sonar complexity warning is low‑priority

The function correctly:

  • Scans IPv4 and INet families,
  • Excludes the NetBird work table and the configured filter table,
  • Restricts to ChainTypeFilter, and
  • Keeps only FORWARD/INPUT chains with non‑nil hooks.

That matches the external‑chain contract well. Sonar’s Cognitive Complexity of 23 vs the 20 threshold is mostly from the series of if filters inside nested loops. If you want to appease it without changing behavior, you could extract the chain‑filtering logic into a small helper:

+func (r *router) isExternalFilterChain(chain *nftables.Chain) bool {
+	if r.workTable != nil && chain.Table.Name == r.workTable.Name {
+		return false
+	}
+	if r.filterTable != nil &&
+		chain.Table.Name == r.filterTable.Name &&
+		chain.Table.Family == r.filterTable.Family {
+		return false
+	}
+	if chain.Type != nftables.ChainTypeFilter || chain.Hooknum == nil {
+		return false
+	}
+	return *chain.Hooknum == *nftables.ChainHookForward ||
+		*chain.Hooknum == *nftables.ChainHookInput
+}
+
 func (r *router) findExternalChains() []*nftables.Chain {
 	var chains []*nftables.Chain
@@
-		for _, chain := range allChains {
-			if r.workTable != nil && chain.Table.Name == r.workTable.Name {
-				continue
-			}
-			if r.filterTable != nil && chain.Table.Name == r.filterTable.Name && chain.Table.Family == r.filterTable.Family {
-				continue
-			}
-			if chain.Type != nftables.ChainTypeFilter {
-				continue
-			}
-			if chain.Hooknum == nil {
-				continue
-			}
-			if *chain.Hooknum != *nftables.ChainHookForward && *chain.Hooknum != *nftables.ChainHookInput {
-				continue
-			}
-			chains = append(chains, chain)
-		}
+		for _, chain := range allChains {
+			if r.isExternalFilterChain(chain) {
+				chains = append(chains, chain)
+			}
+		}

Functionally it’s equivalent and should satisfy the analyzer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f7c976 and 203753e.

📒 Files selected for processing (1)
  • client/firewall/nftables/router_linux.go (8 hunks)
🧰 Additional context used
🪛 GitHub Check: SonarCloud Code Analysis
client/firewall/nftables/router_linux.go

[failure] 1219-1219: Refactor this method to reduce its Cognitive Complexity from 23 to the 20 allowed.

See more on https://sonarcloud.io/project/issues?id=netbirdio_netbird&issues=AZrieIOu2chXJrHgifJl&open=AZrieIOu2chXJrHgifJl&pullRequest=4899

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Management / Unit (amd64, postgres)
  • GitHub Check: Management / Unit (amd64, sqlite)
  • GitHub Check: Management / Unit (amd64, mysql)
  • GitHub Check: Signal / Unit (386)
  • GitHub Check: Relay / Unit (386)
  • GitHub Check: Relay / Unit (amd64, -race)
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: iOS / Build
  • GitHub Check: Android / Build
  • GitHub Check: Client / Unit
  • GitHub Check: Client / Unit
  • GitHub Check: Windows
  • GitHub Check: Linux
  • GitHub Check: Darwin
  • GitHub Check: Client / Unit
  • GitHub Check: JS / Lint
  • GitHub Check: release_ui_darwin
  • GitHub Check: release
🔇 Additional comments (7)
client/firewall/nftables/router_linux.go (7)

186-198: hookName helper is nil‑safe and improves logging clarity

The hookName helper cleanly handles nil hooks and maps known hooks to readable names, with a sane fallback for others. This is a nice improvement for debug logs with no obvious behavioral risk.


947-980: Aggregating errors from filter‑table and external‑chain accept paths looks good

acceptForwardRules() now attempts both acceptFilterTableRules() and acceptExternalChainsRules() and aggregates failures via multierror, so one path failing doesn’t prevent the other from being tried. That matches the PR goal of broadening coverage while still surfacing combined errors through nberrors.FormatErrorOrNil.


960-980: iptables→nftables fallback for filter table is reasonable, but depends on environment

Using iptables when available and falling back to nftables (acceptFilterRulesNftables(r.filterTable)) when iptables.New() fails provides a pragmatic compatibility story. The deferred log that reports which backend was used is also helpful for debugging. Just be aware cleanup must mirror whichever backend actually succeeded, which you handle via the corresponding removal paths below.


1015-1039: acceptFilterRulesNftables correctly parameterizes the table and reuses insert helpers

Passing the table into acceptFilterRulesNftables() and constructing FORWARD/INPUT chains on that table, then delegating to insertForwardAcceptRules / insertInputAcceptRule, is a clean abstraction. The final Flush() centralizes error handling while the individual InsertRule calls remain simple.


1041-1068: acceptExternalChainsRules aligns with the PR objective; behavior seems sound

Dynamically discovering external FORWARD/INPUT filter chains and inserting the same iif/oif/established rules into each (then flushing once) matches the intent to avoid NetBird traffic being dropped by third‑party tables. The hookName usage keeps logs readable, and findExternalChains() already guarantees Hooknum is non‑nil before the switch.


1070-1128: insertForwardAcceptRules/insertInputAcceptRule give deterministic, removable rules

These helpers correctly:

  • Match by IIFNAME/OIFNAME against the WireGuard interface,
  • Tag rules with UserData (frwacceptiif / frwacceptoif / inputaccept) for deterministic cleanup, and
  • Use InsertRule so NetBird’s accept rules precede existing chain rules.

The shared helpers avoid duplication and line up with the removal logic later that looks for these UserData markers.


1130-1179: Filter‑table + external‑chain cleanup flow is consistent; minor removal behavior note

removeAcceptFilterRules() now orchestrates:

  • removeFilterTableRules() (iptables‑first, with nftables fallback via removeAcceptRulesFromTable), and
  • removeExternalChainsRules() for non‑NetBird tables.

removeAcceptRulesFromTable() then scopes deletions to the FORWARD/INPUT chains of the given table and only removes rules carrying your UserData tags, finishing with a single Flush(). This gives a coherent add/remove story for both internal and external chains with low risk of collateral deletion.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
client/firewall/nftables/router_linux.go (3)

946-1041: Confirm behavior when filterTable is nil so iptables-only setups still get accept rules

acceptFilterTableRules now returns immediately when r.filterTable == nil (Line 961), so you never attempt the iptables path on systems where the nftables "filter" table cannot be discovered. On an iptables‑only / iptables‑legacy system that doesn’t expose a nftables filter table, this would mean no forward/input accept rules are installed at all, even though iptables.New() might succeed.

If iptables‑only environments are still supported, consider decoupling the iptables flow from nftables detection, e.g.:

  • Always try iptables.New() first.
  • Only fall back to acceptFilterRulesNftables when r.filterTable != nil and iptables.New() fails.

That keeps nftables fallback but avoids skipping iptables in cases where nftables state isn’t available.


1132-1217: Make nftables removal best-effort across all chains (mirror external-chain behavior)

removeAcceptFilterRules aggregates errors, but removeAcceptRulesFromTable currently:

  • Iterates chains and returns immediately on the first error from removeAcceptRulesFromChain (Lines 1175–1177).
  • Only flushes if no per-chain error occurred (Lines 1180–1181).

This contrasts with removeExternalChainsRules, which logs per-chain failures, continues, and then flushes once. For resilience, especially after partial failures, you may want filter‑table cleanup to be similarly best‑effort:

  • Iterate all relevant chains, collecting errors into a *multierror.Error.
  • Always attempt the final Flush, appending any flush error.
  • Return FormatErrorOrNil at the end.

That avoids leaving stale rules in later chains when a single chain deletion fails.

-func (r *router) removeAcceptRulesFromTable(table *nftables.Table) error {
-	chains, err := r.conn.ListChainsOfTableFamily(table.Family)
+func (r *router) removeAcceptRulesFromTable(table *nftables.Table) error {
+	chains, err := r.conn.ListChainsOfTableFamily(table.Family)
 	if err != nil {
 		return fmt.Errorf("list chains: %v", err)
 	}
 
-	for _, chain := range chains {
+	var merr *multierror.Error
+	for _, chain := range chains {
 		if chain.Table.Name != table.Name {
 			continue
 		}
 
 		if chain.Name != chainNameForward && chain.Name != chainNameInput {
 			continue
 		}
 
-		if err := r.removeAcceptRulesFromChain(table, chain); err != nil {
-			return err
-		}
+		if err := r.removeAcceptRulesFromChain(table, chain); err != nil {
+			merr = multierror.Append(merr, err)
+		}
 	}
 
-	return r.conn.Flush()
+	if err := r.conn.Flush(); err != nil {
+		merr = multierror.Append(merr, fmt.Errorf("flush: %w", err))
+	}
+	return nberrors.FormatErrorOrNil(merr)
 }

1221-1258: Consider extracting a small predicate helper to reduce findExternalChains complexity (Sonar warning)

findExternalChains has a series of early continue filters (work table, filter table, type, hook, families loop), which Sonar flags as cognitive complexity 23 (>20). The logic is fine, but you can satisfy the check and improve readability by pulling the chain filter into a helper, e.g. isExternalFilterHookChain(chain *nftables.Chain) bool, and then doing a single if !r.isExternalFilterHookChain(chain) { continue } inside the inner loop.

That keeps behavior unchanged while addressing the static-analysis concern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 203753e and daec924.

📒 Files selected for processing (1)
  • client/firewall/nftables/router_linux.go (8 hunks)
🧰 Additional context used
🪛 GitHub Check: SonarCloud Code Analysis
client/firewall/nftables/router_linux.go

[failure] 1221-1221: Refactor this method to reduce its Cognitive Complexity from 23 to the 20 allowed.

See more on https://sonarcloud.io/project/issues?id=netbirdio_netbird&issues=AZrieIOu2chXJrHgifJl&open=AZrieIOu2chXJrHgifJl&pullRequest=4899

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: Android / Build
  • GitHub Check: iOS / Build
  • GitHub Check: Windows
  • GitHub Check: Client / Unit
  • GitHub Check: release
  • GitHub Check: Client / Unit
  • GitHub Check: JS / Lint
  • GitHub Check: Client / Unit
🔇 Additional comments (2)
client/firewall/nftables/router_linux.go (2)

186-198: hookName helper looks robust for logging and nil-safety

Mapping known hooks to human-readable names and guarding against nil avoids panics when logging external chains; no issues spotted here.


1043-1130: External chain handling and rule helpers are consistent; good reuse of UserData markers

The new acceptExternalChainsRules + insertForwardAcceptRules/insertInputAcceptRule flow cleanly:

  • findExternalChains scopes to non‑NetBird, filter‑type chains with FORWARD/INPUT hooks, so you don’t touch your own work/filter tables.
  • Forward/input rules use distinct UserData markers (frwacceptiif, frwacceptoif, inputaccept), which align with the removal helpers and keep cleanup deterministic.
  • Using InsertRule instead of AddRule is appropriate to get rules near the top of the chain so they can bypass DROP policies.

Overall this looks correct and matches the PR goal of protecting NetBird traffic across third‑party chains.

@lixmal lixmal force-pushed the nftables-all-chains branch from daec924 to 11fe967 Compare December 3, 2025 04:49
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
client/firewall/nftables/router_linux.go (2)

1171-1192: Consider aggregating errors instead of early return.

Currently, if removeAcceptRulesFromChain fails for one chain, subsequent chains won't be processed. This could leave the system in an inconsistent state. Consider aggregating errors with multierror.Append to ensure all chains are processed.

 func (r *router) removeAcceptRulesFromTable(table *nftables.Table) error {
 	chains, err := r.conn.ListChainsOfTableFamily(table.Family)
 	if err != nil {
 		return fmt.Errorf("list chains: %v", err)
 	}
 
+	var merr *multierror.Error
+
 	for _, chain := range chains {
 		if chain.Table.Name != table.Name {
 			continue
 		}
 
 		if chain.Name != chainNameForward && chain.Name != chainNameInput {
 			continue
 		}
 
 		if err := r.removeAcceptRulesFromChain(table, chain); err != nil {
-			return err
+			merr = multierror.Append(merr, err)
 		}
 	}
 
-	return r.conn.Flush()
+	if err := r.conn.Flush(); err != nil {
+		merr = multierror.Append(merr, fmt.Errorf("flush: %w", err))
+	}
+
+	return nberrors.FormatErrorOrNil(merr)
 }

1212-1228: Consider returning aggregated errors from external chain cleanup.

The function logs warnings for errors but doesn't return them. This could hide cleanup failures from the caller. Consider aggregating errors with multierror.Append and returning them.

 func (r *router) removeExternalChainsRules() error {
 	chains := r.findExternalChains()
 	if len(chains) == 0 {
 		return nil
 	}
 
+	var merr *multierror.Error
+
 	for _, chain := range chains {
 		if err := r.removeAcceptRulesFromChain(chain.Table, chain); err != nil {
 			log.Warnf("remove rules from external chain %s/%s: %v", chain.Table.Name, chain.Name, err)
+			merr = multierror.Append(merr, fmt.Errorf("remove rules from %s/%s: %w", chain.Table.Name, chain.Name, err))
 		}
 	}
 
-	return r.conn.Flush()
+	if err := r.conn.Flush(); err != nil {
+		merr = multierror.Append(merr, fmt.Errorf("flush: %w", err))
+	}
+
+	return nberrors.FormatErrorOrNil(merr)
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daec924 and e3d9ecb.

📒 Files selected for processing (1)
  • client/firewall/nftables/router_linux.go (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
client/firewall/nftables/router_linux.go (2)
client/errors/errors.go (1)
  • FormatErrorOrNil (25-30)
client/firewall/nftables/rule_linux.go (1)
  • Rule (10-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Management / Unit (amd64, mysql)
  • GitHub Check: Management / Unit (amd64, sqlite)
  • GitHub Check: Management / Unit (amd64, postgres)
  • GitHub Check: Relay / Unit (386)
  • GitHub Check: Relay / Unit (amd64, -race)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Client / Unit
  • GitHub Check: Darwin
  • GitHub Check: Windows
  • GitHub Check: Linux
  • GitHub Check: Android / Build
  • GitHub Check: Client / Unit
  • GitHub Check: JS / Lint
  • GitHub Check: Client / Unit
  • GitHub Check: release
  • GitHub Check: release_ui_darwin
🔇 Additional comments (15)
client/firewall/nftables/router_linux.go (15)

29-34: LGTM!

The additional table name constants are correctly defined and align with standard nftables table names.


204-215: LGTM!

The familyName helper correctly maps nftables table families to readable strings with appropriate fallback handling.


963-975: LGTM!

The refactoring properly separates filter table handling from external chain handling, with appropriate error aggregation.


977-999: LGTM!

The function correctly tries iptables first and falls back to nftables when iptables is unavailable, with appropriate logging.


1001-1020: LGTM!

Error messages are appropriately updated and error handling is consistent.


1034-1058: LGTM!

The refactoring to accept a table parameter improves reusability, and the chain structures are correctly constructed.


1089-1122: LGTM!

The function is correctly parameterized to work with any chain, promoting code reuse for both filter table and external chains.


1124-1141: LGTM!

The function is correctly parameterized and follows the same pattern as insertForwardAcceptRules.


1143-1155: LGTM!

The removal logic correctly mirrors the acceptance logic structure, maintaining good symmetry.


1157-1169: LGTM!

The removal logic correctly follows the same fallback pattern as the acceptance path.


1194-1210: LGTM!

The function correctly identifies and removes rules by UserData, with appropriate error handling.


1230-1252: LGTM!

The function correctly discovers external chains from relevant table families, with appropriate error handling for listing failures.


1254-1273: LGTM!

The filtering logic correctly identifies external chains by excluding NetBird's own table, iptables-managed tables, and chains that don't match the required criteria.


1283-1298: LGTM!

Error messages are appropriately updated and consistent with the refactoring.


1275-1281: No action required — tableNameFilter is properly defined.

The constant is declared in client/firewall/nftables/manager_linux.go (line 27) as a package-level constant and is correctly accessible in router_linux.go since both files are in the same package.

Comment thread client/firewall/nftables/router_linux.go
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
client/firewall/nftables/router_linux.go (1)

1060-1092: Consider adding tests for external chain discovery logic.

The external chain discovery and filtering logic (findExternalChains, isExternalChain) is complex and critical for preventing traffic drops. This logic would benefit from unit tests covering:

  • Discovery of chains in different table families (IPv4, INet)
  • Filtering of NetBird's own workTable
  • Filtering of iptables-managed tables
  • Edge cases like nil Hooknum, non-filter chain types

Per the PR checklist, no tests were added. While the code logic appears sound, tests would provide confidence for future refactoring and help document expected behavior.

Also applies to: 1235-1286

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3d9ecb and 7d1b3bc.

📒 Files selected for processing (1)
  • client/firewall/nftables/router_linux.go (9 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: Management / Unit (amd64, mysql)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Management / Unit (amd64, sqlite)
  • GitHub Check: Relay / Unit (386)
  • GitHub Check: Management / Unit (amd64, postgres)
  • GitHub Check: Relay / Unit (amd64, -race)
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: JS / Lint
  • GitHub Check: Android / Build
  • GitHub Check: iOS / Build
  • GitHub Check: Windows
  • GitHub Check: Client / Unit
  • GitHub Check: Linux
  • GitHub Check: Darwin
  • GitHub Check: release_ui_darwin
  • GitHub Check: release
  • GitHub Check: Client / Unit
  • GitHub Check: Client / Unit
🔇 Additional comments (10)
client/firewall/nftables/router_linux.go (10)

30-34: LGTM! Table constants added for standard nftables tables.

The additional table constants (mangle, raw, security) are correctly named and align with standard nftables table names used by iptables-nft and other tools.


963-975: LGTM! Well-structured error aggregation.

The refactored method properly separates filter table rules from external chain rules and aggregates errors using multierror, ensuring both operations are attempted.


1034-1058: LGTM! Excellent refactoring for table-agnostic rule insertion.

The signature change to accept a table parameter, combined with delegation to the extracted helper methods, enables rule insertion into any table while maintaining clean separation of concerns.


1060-1092: LGTM! Nil check properly addresses previous review concern.

The nil check for chain.Hooknum at lines 1071-1074 prevents the potential panic identified in the previous review. The function correctly handles Forward and Input hooks per the PR objectives.


1094-1127: LGTM! Clean extraction enables table-agnostic rule insertion.

The extracted method properly parameterizes the chain and interface, allowing rules to be inserted into any chain while maintaining the correct logic for both input and output interface rules.


1129-1146: LGTM! Consistent extraction pattern.

The extracted method follows the same pattern as insertForwardAcceptRules, providing table-agnostic input rule insertion.


1148-1160: LGTM! Symmetric removal structure.

The refactored removal method mirrors the insertion path, ensuring both filter table and external chains are cleaned up with proper error aggregation.


1176-1233: LGTM! Deterministic cleanup with resilient error handling.

The removal methods implement deterministic cleanup by discovering chains at removal time, ensuring cleanup works even after restarts or crashes. The pattern of logging warnings for individual failures while continuing cleanup is appropriate for resilience.


1235-1286: Code review approved—all constants properly defined.

The chain discovery and filtering logic is sound. The tableNameFilter constant referenced at line 1282 is correctly defined in manager_linux.go (line 27) and accessible within the package. The family-specific filtering appropriately skips iptables-managed tables in IPv4 while processing native nftables in the INet family to avoid conflicts.


190-215: No action required.

The chainNameInput constant is properly defined as a package-level constant in client/firewall/nftables/manager_linux.go (line 28), making it accessible to the helper functions in router_linux.go. Both hookName() and familyName() functions are well-implemented with appropriate error handling and readable output formatting.

@lixmal lixmal merged commit 9bdc490 into main Dec 4, 2025
37 checks passed
@lixmal lixmal deleted the nftables-all-chains branch December 4, 2025 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants