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:
Viktor Liu
2026-04-10 13:06:14 +02:00
parent 567f36b07e
commit 4b298fb53c
4 changed files with 20 additions and 23 deletions

View File

@@ -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.