netbirdio/netbird · error

add DNAT rule: %w

Error message

add DNAT rule: %w

What it means

AddDNATRule received a ForwardRule whose TranslatedAddress is IPv6 while the manager has no v6 half (m.ipv6Client == nil from Create-time HasIPv6 being false). Wraps the sentinel firewall.ErrIPv6NotInitialized; the DNAT is refused before any iptables call, keeping the v4 nat table untouched.

Source

Thrown at client/firewall/iptables/manager_linux.go:420

}

func (m *Manager) EnableRouting() error {
	// v6 only when the overlay actually has v6.
	return m.router.ipFwdState.RequestRouting(m.router6 != nil)
}

func (m *Manager) DisableRouting() error {
	return m.router.ipFwdState.ReleaseRouting()
}

// AddDNATRule adds a DNAT rule
func (m *Manager) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if rule.TranslatedAddress.Is6() {
		if !m.hasIPv6() {
			return nil, fmt.Errorf("add DNAT rule: %w", firewall.ErrIPv6NotInitialized)
		}
		return m.router6.AddDNATRule(rule)
	}
	return m.router.AddDNATRule(rule)
}

// DeleteDNATRule deletes a DNAT rule
func (m *Manager) DeleteDNATRule(rule firewall.Rule) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if m.hasIPv6() && !m.router.hasRule(rule.ID()+dnatSuffix) {
		return m.router6.DeleteDNATRule(rule)
	}
	return m.router.DeleteDNATRule(rule)
}

// UpdateSet updates the set with the given prefixes

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Enable the v6 overlay address for the peer and restart so router6 exists
  2. Restrict v6 DNAT rules to v6-capable peers in management
  3. Skip v6 translated addresses in the caller when !wgIface.Address().HasIPv6()

Example fix

// before
r, err := mgr.AddDNATRule(fwRule)
if err != nil {
	return err
}

// after
if fwRule.TranslatedAddress.Is6() && !wgIface.Address().HasIPv6() {
	log.Debugf("skipping v6 DNAT rule: no v6 overlay")
	return nil, nil
}
r, err := mgr.AddDNATRule(fwRule)
Defensive patterns

Strategy: validation

Validate before calling

if rule.TranslatedAddress.Is6() && !wgIface.Address().HasIPv6() {
    return nil // cannot program v6 DNAT; skip before touching iptables
}

Type guard

func isProgrammableDNAT(rule firewall.ForwardRule, ifaceHasV6 bool) bool {
    return !rule.TranslatedAddress.Is6() || ifaceHasV6
}

Try / catch

r, err := mgr.AddDNATRule(rule)
if err != nil {
    if errors.Is(err, firewall.ErrIPv6NotInitialized) {
        log.Debugf("skipping v6 DNAT: no v6 firewall")
        continue
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling AddDNATRule(rule) with rule.TranslatedAddress.Is6() true on a manager created from a v4-only interface address.

Common situations: Port forwarding/DNAT configuration targeting a v6 translated address pushed to a peer without v6 overlay; mixed-family forwarding rules applied uniformly across peers.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/46c75a0a836e010f. Report an issue: GitHub.