{"record":{"id":"46c75a0a836e010f","repo":"netbirdio/netbird","slug":"add-dnat-rule-w","errorCode":null,"errorMessage":"add DNAT rule: %w","messagePattern":"add DNAT rule: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/iptables/manager_linux.go","lineNumber":420,"sourceCode":"}\n\nfunc (m *Manager) EnableRouting() error {\n\t// v6 only when the overlay actually has v6.\n\treturn m.router.ipFwdState.RequestRouting(m.router6 != nil)\n}\n\nfunc (m *Manager) DisableRouting() error {\n\treturn m.router.ipFwdState.ReleaseRouting()\n}\n\n// AddDNATRule adds a DNAT rule\nfunc (m *Manager) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tif rule.TranslatedAddress.Is6() {\n\t\tif !m.hasIPv6() {\n\t\t\treturn nil, fmt.Errorf(\"add DNAT rule: %w\", firewall.ErrIPv6NotInitialized)\n\t\t}\n\t\treturn m.router6.AddDNATRule(rule)\n\t}\n\treturn m.router.AddDNATRule(rule)\n}\n\n// DeleteDNATRule deletes a DNAT rule\nfunc (m *Manager) DeleteDNATRule(rule firewall.Rule) error {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tif m.hasIPv6() && !m.router.hasRule(rule.ID()+dnatSuffix) {\n\t\treturn m.router6.DeleteDNATRule(rule)\n\t}\n\treturn m.router.DeleteDNATRule(rule)\n}\n\n// UpdateSet updates the set with the given prefixes","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/iptables/manager_linux.go#L402-L438","documentation":"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.","triggerScenarios":"Calling AddDNATRule(rule) with rule.TranslatedAddress.Is6() true on a manager created from a v4-only interface address.","commonSituations":"Port forwarding/DNAT configuration targeting a v6 translated address pushed to a peer without v6 overlay; mixed-family forwarding rules applied uniformly across peers.","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()"],"exampleFix":"// before\nr, err := mgr.AddDNATRule(fwRule)\nif err != nil {\n\treturn err\n}\n\n// after\nif fwRule.TranslatedAddress.Is6() && !wgIface.Address().HasIPv6() {\n\tlog.Debugf(\"skipping v6 DNAT rule: no v6 overlay\")\n\treturn nil, nil\n}\nr, err := mgr.AddDNATRule(fwRule)","handlingStrategy":"validation","validationCode":"if rule.TranslatedAddress.Is6() && !wgIface.Address().HasIPv6() {\n    return nil // cannot program v6 DNAT; skip before touching iptables\n}","typeGuard":"func isProgrammableDNAT(rule firewall.ForwardRule, ifaceHasV6 bool) bool {\n    return !rule.TranslatedAddress.Is6() || ifaceHasV6\n}","tryCatchPattern":"r, err := mgr.AddDNATRule(rule)\nif err != nil {\n    if errors.Is(err, firewall.ErrIPv6NotInitialized) {\n        log.Debugf(\"skipping v6 DNAT: no v6 firewall\")\n        continue\n    }\n    return nil, err\n}","preventionTips":["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"],"tags":["iptables","ipv6","firewall","dnat","netbird","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}