netbirdio/netbird · error
add inbound DNAT: %w
Error message
add inbound DNAT: %w
What it means
AddInboundDNAT was asked to redirect traffic for a v6 local address (localAddr.Is6()) into the overlay, but the manager has no v6 half. Wraps the sentinel firewall.ErrIPv6NotInitialized - the inbound PREROUTING DNAT is refused rather than half-installed.
Source
Thrown at client/firewall/iptables/manager_linux.go:472
}
if m.hasIPv6() && len(v6Prefixes) > 0 {
if err := m.router6.UpdateSet(set, v6Prefixes); err != nil {
return fmt.Errorf("update v6 set: %w", err)
}
}
return nil
}
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services.
func (m *Manager) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if localAddr.Is6() {
if !m.hasIPv6() {
return fmt.Errorf("add inbound DNAT: %w", firewall.ErrIPv6NotInitialized)
}
return m.router6.AddInboundDNAT(localAddr, protocol, originalPort, translatedPort)
}
return m.router.AddInboundDNAT(localAddr, protocol, originalPort, translatedPort)
}
// RemoveInboundDNAT removes an inbound DNAT rule.
func (m *Manager) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if localAddr.Is6() {
if !m.hasIPv6() {
return fmt.Errorf("remove inbound DNAT: %w", firewall.ErrIPv6NotInitialized)
}
return m.router6.RemoveInboundDNAT(localAddr, protocol, originalPort, translatedPort)
}
return m.router.RemoveInboundDNAT(localAddr, protocol, originalPort, translatedPort)View on GitHub (pinned to 93e97f4bf1)
Solutions
- Assign the peer a v6 address and restart the agent
- Filter v6 local addresses out of the inbound-DNAT setup when the interface lacks v6
- Verify the overlay actually has v6: check the interface address, not just the config
Example fix
// before
if err := mgr.AddInboundDNAT(localAddr, proto, origPort, xlatePort); err != nil {
return err
}
// after
if localAddr.Is6() && !wgIface.Address().HasIPv6() {
log.Debugf("skipping v6 inbound DNAT: no v6 overlay")
return nil
}
if err := mgr.AddInboundDNAT(localAddr, proto, origPort, xlatePort); err != nil {
return err
} Defensive patterns
Strategy: validation
Validate before calling
if localAddr.Is6() && !wgIface.Address().HasIPv6() {
return nil // v6 inbound DNAT cannot be programmed
} Try / catch
if err := mgr.AddInboundDNAT(localAddr, proto, origPort, xlatePort); err != nil {
if errors.Is(err, firewall.ErrIPv6NotInitialized) {
log.Debugf("skipping v6 inbound DNAT: no v6 firewall")
return nil
}
return err
} Prevention
- Gate v6 local addresses on the interface's HasIPv6() accessor before adding inbound DNAT
- Prefer netip.Addr (Is6()) over string parsing for family checks
- Re-validate after interface address changes rather than caching the capability
When it happens
Trigger: Calling AddInboundDNAT with a v6 netip.Addr on a manager whose Create ran with a v4-only WgAddr.
Common situations: Exposing a local service over v6 (peer-to-service forwarding) on a peer without a v6 overlay address; service config listing :: addresses alongside v4.
Related errors
- add DNAT rule: %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/71a62e41bd80ed91.
Report an issue: GitHub.