netbirdio/netbird · error · firewall.ErrIPv6NotInitialized

add route filtering: %w

Error message

add route filtering: %w

What it means

AddRouteFiltering classified the rule as IPv6 via isIPv6RouteRule - the destination is a v6 prefix, or destination is not a prefix and the first source prefix is v6 - but the manager has no v6 half. Wraps the sentinel firewall.ErrIPv6NotInitialized, meaning the v6 capability decision (HasIPv6 at Create time) does not match the traffic being programmed.

Source

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

		return nil, fmt.Errorf("add peer filtering for %s: %w", ip, firewall.ErrIPv6NotInitialized)
	}
	return m.aclMgr6.AddPeerFiltering(id, ip, proto, sPort, dPort, action, ipsetName)
}

func (m *Manager) AddRouteFiltering(
	id []byte,
	sources []netip.Prefix,
	destination firewall.Network,
	proto firewall.Protocol,
	sPort, dPort *firewall.Port,
	action firewall.Action,
) (firewall.Rule, error) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if isIPv6RouteRule(sources, destination) {
		if !m.hasIPv6() {
			return nil, fmt.Errorf("add route filtering: %w", firewall.ErrIPv6NotInitialized)
		}
		return m.router6.AddRouteFiltering(id, sources, destination, proto, sPort, dPort, action)
	}

	return m.router.AddRouteFiltering(id, sources, destination, proto, sPort, dPort, action)
}

func isIPv6RouteRule(sources []netip.Prefix, destination firewall.Network) bool {
	if destination.IsPrefix() {
		return destination.Prefix.Addr().Is6()
	}
	return len(sources) > 0 && sources[0].Addr().Is6()
}

// DeletePeerRule from the firewall by rule definition
func (m *Manager) DeletePeerRule(rule firewall.Rule) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Assign the peer a v6 address and restart the agent so the v6 router exists
  2. Scope the v6 route's distribution groups in management so v4-only peers do not receive it
  3. In custom route managers, drop v6 route rules when !wgIface.Address().HasIPv6() instead of forwarding them to the firewall

Example fix

// before
rule, err := mgr.AddRouteFiltering(id, sources, dest, proto, sPort, dPort, action)
if err != nil {
	return err // v6 route on v4-only manager fails the apply
}

// after
if isV6 := (dest.IsPrefix() && dest.Prefix.Addr().Is6()) || (!dest.IsPrefix() && len(sources) > 0 && sources[0].Addr().Is6()); isV6 && !wgIface.Address().HasIPv6() {
	log.Debugf("skipping v6 route rule: no v6 overlay")
	continue
}
rule, err := mgr.AddRouteFiltering(id, sources, dest, proto, sPort, dPort, action)
Defensive patterns

Strategy: validation

Validate before calling

func isV6RouteRule(sources []netip.Prefix, destination firewall.Network) bool {
    if destination.IsPrefix() {
        return destination.Prefix.Addr().Is6()
    }
    return len(sources) > 0 && sources[0].Addr().Is6()
}

// before applying route rules:
if isV6RouteRule(sources, destination) && !wgIface.Address().HasIPv6() {
    log.Debugf("skipping v6 route rule: no v6 firewall")
    continue
}

Try / catch

rule, err := mgr.AddRouteFiltering(id, sources, destination, proto, sPort, dPort, action)
if err != nil {
    if errors.Is(err, firewall.ErrIPv6NotInitialized) {
        continue // v6 route on a v4-only overlay: skip, keep applying the rest
    }
    return err
}

Prevention

When it happens

Trigger: Calling AddRouteFiltering with destination.Prefix.Addr().Is6() true, or with the first entry of sources being a v6 prefix, while the local overlay address lacks IPv6.

Common situations: Management distributes an IPv6 network route (v6 resource range, or a v6 exit node) to a peer whose WgAddr is v4-only; network routes regenerated after the agent connected without v6.

Related errors


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