diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go index 49070a1f0..4f8864d9a 100644 --- a/client/firewall/iptables/manager_linux.go +++ b/client/firewall/iptables/manager_linux.go @@ -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 { diff --git a/client/firewall/manager/routerpair.go b/client/firewall/manager/routerpair.go index b2e37415e..096f8b9bb 100644 --- a/client/firewall/manager/routerpair.go +++ b/client/firewall/manager/routerpair.go @@ -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 { diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index 787fd1a14..e53209ecc 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -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. diff --git a/client/internal/routemanager/server/server.go b/client/internal/routemanager/server/server.go index 17bf74a53..9fe7e1564 100644 --- a/client/internal/routemanager/server/server.go +++ b/client/internal/routemanager/server/server.go @@ -160,6 +160,7 @@ func routeToRouterPair(route *route.Route, useNewDNSRoute bool) firewall.RouterP Source: source, Destination: destination, Masquerade: route.Masquerade, + Dynamic: route.IsDynamic(), } }