mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 16:26:38 +00:00
Fix legacy dynamic route NAT missing v6 duplicate
The legacy DNS resolver path creates NAT pairs with destination 0.0.0.0/0 (a prefix, not a DomainSet). The v6 NAT duplication only triggered for DomainSets, so legacy dynamic routes never got a v6 NAT rule. Extract NeedsV6NATDuplicate and ToV6NatPair helpers that detect both DomainSets and the v4 default wildcard 0.0.0.0/0. Both nftables and iptables managers now use these for Add/RemoveNatRule, ensuring v6 NAT duplication works for both modern and legacy DNS resolver paths.
This commit is contained in:
@@ -272,10 +272,11 @@ func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic routes need NAT in both tables
|
// Dynamic routes need NAT in both tables since resolved IPs can be
|
||||||
if m.hasIPv6() && pair.Destination.IsSet() {
|
// either v4 or v6. This covers both DomainSet (modern) and the legacy
|
||||||
v6Pair := pair
|
// wildcard 0.0.0.0/0 destination where the client resolves DNS.
|
||||||
v6Pair.Source = firewall.Network{Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||||
|
v6Pair := firewall.ToV6NatPair(pair)
|
||||||
if err := m.router6.AddNatRule(v6Pair); err != nil {
|
if err := m.router6.AddNatRule(v6Pair); err != nil {
|
||||||
return fmt.Errorf("add v6 NAT rule: %w", err)
|
return fmt.Errorf("add v6 NAT rule: %w", err)
|
||||||
}
|
}
|
||||||
@@ -299,9 +300,8 @@ func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.hasIPv6() && pair.Destination.IsSet() {
|
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||||
v6Pair := pair
|
v6Pair := firewall.ToV6NatPair(pair)
|
||||||
v6Pair.Source = firewall.Network{Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
|
||||||
if err := m.router6.RemoveNatRule(v6Pair); err != nil {
|
if err := m.router6.RemoveNatRule(v6Pair); err != nil {
|
||||||
return fmt.Errorf("remove v6 NAT rule: %w", err)
|
return fmt.Errorf("remove v6 NAT rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package manager
|
package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,3 +24,27 @@ func GetInversePair(pair RouterPair) RouterPair {
|
|||||||
Inverse: true,
|
Inverse: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NeedsV6NATDuplicate reports whether a v4 NAT pair should be duplicated to
|
||||||
|
// the v6 table. This is true for DomainSets (resolved IPs can be either
|
||||||
|
// family) and for the v4 default wildcard 0.0.0.0/0 used by the legacy DNS
|
||||||
|
// resolver path for dynamic routes.
|
||||||
|
func NeedsV6NATDuplicate(pair RouterPair) bool {
|
||||||
|
if pair.Destination.IsSet() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return pair.Destination.IsPrefix() &&
|
||||||
|
pair.Destination.Prefix.Bits() == 0 &&
|
||||||
|
pair.Destination.Prefix.Addr().Is4()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToV6NatPair creates a v6 counterpart of a v4 NAT pair with `::/0` source
|
||||||
|
// and, for prefix destinations, `::/0` destination.
|
||||||
|
func ToV6NatPair(pair RouterPair) RouterPair {
|
||||||
|
v6 := pair
|
||||||
|
v6.Source = Network{Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
||||||
|
if v6.Destination.IsPrefix() {
|
||||||
|
v6.Destination = Network{Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
||||||
|
}
|
||||||
|
return v6
|
||||||
|
}
|
||||||
|
|||||||
@@ -327,11 +327,11 @@ func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic routes (DomainSet) need NAT in both tables since resolved IPs
|
// Dynamic routes need NAT in both tables since resolved IPs can be
|
||||||
// can be either v4 or v6.
|
// either v4 or v6. This covers both DomainSet (modern) and the legacy
|
||||||
if m.hasIPv6() && pair.Destination.IsSet() {
|
// wildcard 0.0.0.0/0 destination where the client resolves DNS.
|
||||||
v6Pair := pair
|
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||||
v6Pair.Source = firewall.Network{Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
v6Pair := firewall.ToV6NatPair(pair)
|
||||||
if err := m.router6.AddNatRule(v6Pair); err != nil {
|
if err := m.router6.AddNatRule(v6Pair); err != nil {
|
||||||
return fmt.Errorf("add v6 NAT rule: %w", err)
|
return fmt.Errorf("add v6 NAT rule: %w", err)
|
||||||
}
|
}
|
||||||
@@ -355,9 +355,8 @@ func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.hasIPv6() && pair.Destination.IsSet() {
|
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||||
v6Pair := pair
|
v6Pair := firewall.ToV6NatPair(pair)
|
||||||
v6Pair.Source = firewall.Network{Prefix: netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
|
||||||
if err := m.router6.RemoveNatRule(v6Pair); err != nil {
|
if err := m.router6.RemoveNatRule(v6Pair); err != nil {
|
||||||
return fmt.Errorf("remove v6 NAT rule: %w", err)
|
return fmt.Errorf("remove v6 NAT rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user