mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
Fix legacy dynamic route NAT missing v6 duplicate
The v6 NAT duplication only triggered for DomainSet destinations (modern DNS path). Legacy dynamic routes use a 0.0.0.0/0 prefix destination, so the v6 NAT rule was never created. Add a Dynamic field to RouterPair so the firewall manager can distinguish dynamic routes from exit nodes (both use /0 prefixes). Set it from route.IsDynamic() in routeToRouterPair and propagate through GetInversePair. Both nftables and iptables managers check pair.Dynamic instead of destination shape. Also accumulate errors in RemoveNatRule so v6 cleanup is attempted even if v4 removal fails.
This commit is contained in:
@@ -275,7 +275,7 @@ func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
|
||||
// Dynamic routes need NAT in both tables since resolved IPs can be
|
||||
// either v4 or v6. This covers both DomainSet (modern) and the legacy
|
||||
// wildcard 0.0.0.0/0 destination where the client resolves DNS.
|
||||
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||
if m.hasIPv6() && pair.Dynamic {
|
||||
v6Pair := firewall.ToV6NatPair(pair)
|
||||
if err := m.router6.AddNatRule(v6Pair); err != nil {
|
||||
return fmt.Errorf("add v6 NAT rule: %w", err)
|
||||
@@ -296,18 +296,20 @@ func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
|
||||
return m.router6.RemoveNatRule(pair)
|
||||
}
|
||||
|
||||
var merr *multierror.Error
|
||||
|
||||
if err := m.router.RemoveNatRule(pair); err != nil {
|
||||
return err
|
||||
merr = multierror.Append(merr, fmt.Errorf("remove v4 NAT rule: %w", err))
|
||||
}
|
||||
|
||||
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||
if m.hasIPv6() && pair.Dynamic {
|
||||
v6Pair := firewall.ToV6NatPair(pair)
|
||||
if err := m.router6.RemoveNatRule(v6Pair); err != nil {
|
||||
return fmt.Errorf("remove v6 NAT rule: %w", err)
|
||||
merr = multierror.Append(merr, fmt.Errorf("remove v6 NAT rule: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nberrors.FormatErrorOrNil(merr)
|
||||
}
|
||||
|
||||
func (m *Manager) SetLegacyManagement(isLegacy bool) error {
|
||||
|
||||
@@ -12,6 +12,10 @@ type RouterPair struct {
|
||||
Destination Network
|
||||
Masquerade bool
|
||||
Inverse bool
|
||||
// Dynamic indicates the route is domain-based. NAT rules for dynamic
|
||||
// routes are duplicated to the v6 table so that resolved AAAA records
|
||||
// are masqueraded correctly.
|
||||
Dynamic bool
|
||||
}
|
||||
|
||||
func GetInversePair(pair RouterPair) RouterPair {
|
||||
@@ -22,22 +26,10 @@ func GetInversePair(pair RouterPair) RouterPair {
|
||||
Destination: pair.Source,
|
||||
Masquerade: pair.Masquerade,
|
||||
Inverse: true,
|
||||
Dynamic: pair.Dynamic,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -330,7 +330,7 @@ func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
|
||||
// Dynamic routes need NAT in both tables since resolved IPs can be
|
||||
// either v4 or v6. This covers both DomainSet (modern) and the legacy
|
||||
// wildcard 0.0.0.0/0 destination where the client resolves DNS.
|
||||
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||
if m.hasIPv6() && pair.Dynamic {
|
||||
v6Pair := firewall.ToV6NatPair(pair)
|
||||
if err := m.router6.AddNatRule(v6Pair); err != nil {
|
||||
return fmt.Errorf("add v6 NAT rule: %w", err)
|
||||
@@ -351,18 +351,20 @@ func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
|
||||
return m.router6.RemoveNatRule(pair)
|
||||
}
|
||||
|
||||
var merr *multierror.Error
|
||||
|
||||
if err := m.router.RemoveNatRule(pair); err != nil {
|
||||
return err
|
||||
merr = multierror.Append(merr, fmt.Errorf("remove v4 NAT rule: %w", err))
|
||||
}
|
||||
|
||||
if m.hasIPv6() && firewall.NeedsV6NATDuplicate(pair) {
|
||||
if m.hasIPv6() && pair.Dynamic {
|
||||
v6Pair := firewall.ToV6NatPair(pair)
|
||||
if err := m.router6.RemoveNatRule(v6Pair); err != nil {
|
||||
return fmt.Errorf("remove v6 NAT rule: %w", err)
|
||||
merr = multierror.Append(merr, fmt.Errorf("remove v6 NAT rule: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nberrors.FormatErrorOrNil(merr)
|
||||
}
|
||||
|
||||
// AllowNetbird allows netbird interface traffic.
|
||||
|
||||
@@ -160,6 +160,7 @@ func routeToRouterPair(route *route.Route, useNewDNSRoute bool) firewall.RouterP
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
Masquerade: route.Masquerade,
|
||||
Dynamic: route.IsDynamic(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user