[PR #5682] [client] Fix exit node masquerade overrides per-route masquerade=false setting #28345

Open
opened 2026-08-05 08:06:12 -04:00 by saavagebueno · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/netbirdio/netbird/pull/5682
Author: @tobsec
Created: 3/24/2026
Status: 🔄 Open

Base: mainHead: fix/exit-node-overrides-masquerade-off


📝 Commits (6)

  • 19b8eb8 firewall: fix exit node masquerade overriding per-route masquerade=false
  • fba5220 firewall: clean up no-masq postrouting rule implementation
  • d007a62 firewall/nftables: address rollback and stale handle edge cases
  • 09b1d75 firewall/nftables: extract removeNoMasqPostRoutingRule to reduce complexity
  • 4754f31 firewall: fix mark scoping and protect inverse direction from masquerade
  • 9fb4812 firewall/iptables: decrement set counter on no-masq rule reinstall

📊 Changes

3 files changed (+195 additions, -12 deletions)

View changed files

📝 client/firewall/iptables/router_linux.go (+88 -8)
📝 client/firewall/manager/firewall.go (+5 -4)
📝 client/firewall/nftables/router_linux.go (+102 -0)

📄 Description

Problem

When a peer acts as both an exit node (0.0.0.0/0) and a routing peer for specific subnets with masquerade disabled, the exit node's postrouting masquerade rule fires for all traffic — silently ignoring the per-route masquerade=false setting.

Root cause: addNatRule() sets fwmark PreroutingFwmarkMasquerade on all wt0 traffic for the exit node. addPostroutingRules() then masquerades everything carrying that mark, regardless of per-route settings. There is no mechanism for a per-route masquerade=false to opt out of this blanket rule.

Practical effect: A client routing through an exit node cannot reach services that rely on the original source IP being preserved (e.g. firewall rules matching the client's NetBird IP, IPsec policies, per-device ACLs). All traffic appears to arrive from the routing peer's WireGuard interface IP instead.

This was reported in #2751 and is related to #4542.

Fix

When masquerade=false for a route pair, insert a RETURN verdict at the head of the postrouting NAT chain (netbird-rt-postrouting) matching the route's destination prefix. This short-circuits evaluation before the exit node's catch-all masquerade rule fires.

Both the nftables and iptables backends are updated:

  • New constant NoMasqPostRoutingFormat in firewall/manager/firewall.go
  • New addNoMasqPostRoutingRule() in both nftables/router_linux.go and iptables/router_linux.go
  • AddNatRule() / RemoveNatRule() updated to manage the return rule when masquerade=false

The rule is keyed and tracked in r.rules the same way as existing NAT rules, so it is correctly removed on route deletion or peer disconnect.

Test scenario

  1. Configure peer A as exit node (0.0.0.0/0, masquerade cannot be disabled)
  2. Configure peer B as routing peer for 192.168.0.0/24 with masquerade=OFF
  3. From a client behind the exit node, send traffic to 192.168.0.1
  4. Before fix: traffic arrives at 192.168.0.1 with src = peer B's WireGuard IP (masqueraded)
  5. After fix: traffic arrives with the client's original NetBird IP preserved

Summary by CodeRabbit

  • New Features

    • Added destination-scoped "no-masquerade" postrouting rules for both directions to provide finer NAT control across Linux routing backends.
  • Bug Fixes

    • Improved insertion ordering, deduplication, rollback, and cleanup of no-masquerade postrouting rules.
    • Graceful handling and removal of missing or stale rule entries to prevent leftover state.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/netbirdio/netbird/pull/5682 **Author:** [@tobsec](https://github.com/tobsec) **Created:** 3/24/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/exit-node-overrides-masquerade-off` --- ### 📝 Commits (6) - [`19b8eb8`](https://github.com/netbirdio/netbird/commit/19b8eb84112efb86a8dab14dc0e746492af6d205) firewall: fix exit node masquerade overriding per-route masquerade=false - [`fba5220`](https://github.com/netbirdio/netbird/commit/fba52204fa8a15bcdcb4ce189c0814a9f466ff18) firewall: clean up no-masq postrouting rule implementation - [`d007a62`](https://github.com/netbirdio/netbird/commit/d007a62699fd5ddc52c5476cd2881b1713c6d5f4) firewall/nftables: address rollback and stale handle edge cases - [`09b1d75`](https://github.com/netbirdio/netbird/commit/09b1d757d8cfafe3936972510b472432c8738137) firewall/nftables: extract removeNoMasqPostRoutingRule to reduce complexity - [`4754f31`](https://github.com/netbirdio/netbird/commit/4754f31b06b6fe14747c5351d0b42a07e8c4e105) firewall: fix mark scoping and protect inverse direction from masquerade - [`9fb4812`](https://github.com/netbirdio/netbird/commit/9fb48127b66d5aa69acd92a75a01288a823cdc4a) firewall/iptables: decrement set counter on no-masq rule reinstall ### 📊 Changes **3 files changed** (+195 additions, -12 deletions) <details> <summary>View changed files</summary> 📝 `client/firewall/iptables/router_linux.go` (+88 -8) 📝 `client/firewall/manager/firewall.go` (+5 -4) 📝 `client/firewall/nftables/router_linux.go` (+102 -0) </details> ### 📄 Description ## Problem When a peer acts as both an **exit node** (`0.0.0.0/0`) and a **routing peer** for specific subnets with masquerade disabled, the exit node's postrouting masquerade rule fires for all traffic — silently ignoring the per-route `masquerade=false` setting. **Root cause:** `addNatRule()` sets fwmark `PreroutingFwmarkMasquerade` on all `wt0` traffic for the exit node. `addPostroutingRules()` then masquerades everything carrying that mark, regardless of per-route settings. There is no mechanism for a per-route `masquerade=false` to opt out of this blanket rule. **Practical effect:** A client routing through an exit node cannot reach services that rely on the original source IP being preserved (e.g. firewall rules matching the client's NetBird IP, IPsec policies, per-device ACLs). All traffic appears to arrive from the routing peer's WireGuard interface IP instead. This was reported in #2751 and is related to #4542. ## Fix When `masquerade=false` for a route pair, insert a `RETURN` verdict at the **head** of the postrouting NAT chain (`netbird-rt-postrouting`) matching the route's destination prefix. This short-circuits evaluation before the exit node's catch-all masquerade rule fires. Both the **nftables** and **iptables** backends are updated: - New constant `NoMasqPostRoutingFormat` in `firewall/manager/firewall.go` - New `addNoMasqPostRoutingRule()` in both `nftables/router_linux.go` and `iptables/router_linux.go` - `AddNatRule()` / `RemoveNatRule()` updated to manage the return rule when `masquerade=false` The rule is keyed and tracked in `r.rules` the same way as existing NAT rules, so it is correctly removed on route deletion or peer disconnect. ## Test scenario 1. Configure peer A as exit node (`0.0.0.0/0`, masquerade cannot be disabled) 2. Configure peer B as routing peer for `192.168.0.0/24` with masquerade=OFF 3. From a client behind the exit node, send traffic to `192.168.0.1` 4. **Before fix:** traffic arrives at `192.168.0.1` with src = peer B's WireGuard IP (masqueraded) 5. **After fix:** traffic arrives with the client's original NetBird IP preserved ## Related issues - Closes #5683 - Closes #2751 - Related: #4542 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added destination-scoped "no-masquerade" postrouting rules for both directions to provide finer NAT control across Linux routing backends. * **Bug Fixes** * Improved insertion ordering, deduplication, rollback, and cleanup of no-masquerade postrouting rules. * Graceful handling and removal of missing or stale rule entries to prevent leftover state. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
saavagebueno added the pull-request label 2026-08-05 08:06:12 -04:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#28345