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

  1. Assign the peer a v6 address and restart the agent
  2. Filter v6 local addresses out of the inbound-DNAT setup when the interface lacks v6
  3. 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

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


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