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 prefixesView on GitHub (pinned to 93e97f4bf1)
Solutions
- Enable the v6 overlay address for the peer and restart so router6 exists
- Restrict v6 DNAT rules to v6-capable peers in management
- 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
- Check TranslatedAddress family against HasIPv6() before submitting DNAT rules
- Distribute v6 forwarding rules only to v6-capable peers
- Use errors.Is on the sentinel instead of parsing the message
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
- add inbound DNAT: %w
- remove inbound DNAT: %w
- create v6 acl manager: %w
- add peer filtering for %s: %w
- add route filtering: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/46c75a0a836e010f.
Report an issue: GitHub.