netbirdio/netbird · error

determine destination: %w

Error message

determine destination: %w

What it means

Returned by applyRouteACL when determineDestination fails. Inside determineDestination the realistic failure is `parse destination: %w` from netip.ParsePrefix(rule.Destination) for non-dynamic rules; the dynamic branches (IsDynamic with domains, or the outdated-management fallback to the default prefix) always succeed. So the error means a static route ACL rule carried a Destination that is not a parseable CIDR.

Source

Thrown at client/internal/acl/manager.go:255

}

func (d *DefaultManager) applyRouteACL(rule *mgmProto.RouteFirewallRule, dynamicResolver bool) (id.RuleID, error) {
	if len(rule.SourceRanges) == 0 {
		return "", ErrSourceRangesEmpty
	}

	var sources []netip.Prefix
	for _, sourceRange := range rule.SourceRanges {
		source, err := netip.ParsePrefix(sourceRange)
		if err != nil {
			return "", fmt.Errorf("parse source range: %w", err)
		}
		sources = append(sources, source)
	}

	destination, err := determineDestination(rule, dynamicResolver, sources)
	if err != nil {
		return "", fmt.Errorf("determine destination: %w", err)
	}

	protocol, err := convertToFirewallProtocol(rule.Protocol)
	if err != nil {
		return "", fmt.Errorf("invalid protocol: %w", err)
	}

	action, err := convertFirewallAction(rule.Action)
	if err != nil {
		return "", fmt.Errorf("invalid action: %w", err)
	}

	dPorts := convertPortInfo(rule.PortInfo)

	addedRule, err := d.firewall.AddRouteFiltering(rule.PolicyID, sources, destination, protocol, nil, dPorts, action)
	if err != nil {
		return "", fmt.Errorf("add route rule: %w", err)
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set Destination to a valid CIDR (a.b.c.d/32 for single hosts), or move the target into the domains field and enable the dynamic flag
  2. Verify the management version validates route firewall destinations before dispatch; upgrade if not
  3. Reconnect the peer / update the policy afterwards so the agent re-applies the corrected rule
Defensive patterns

Strategy: validation

Validate before calling

// policy-authoring guard: destination must be a CIDR unless the rule is dynamic
func validRouteDestination(r *mgmProto.RouteFirewallRule) error {
    if r.IsDynamic {
        if len(r.Domains) == 0 {
            return errors.New("dynamic rule without domains")
        }
        return nil
    }
    if _, err := netip.ParsePrefix(r.Destination); err != nil {
        return fmt.Errorf("destination %q must be a CIDR: %w", r.Destination, err)
    }
    return nil
}

Type guard

func isStaticCidrDestination(r *mgmProto.RouteFirewallRule) bool {
    return !r.IsDynamic && isStrictCIDR(r.Destination)
}

Try / catch

destination, err := determineDestination(rule, dynamicResolver, sources)
if err != nil {
    if strings.Contains(err.Error(), "parse destination") {
        log.Warnf("rule %s has non-CIDR destination %q; fix policy or set IsDynamic", rule.PolicyID, rule.Destination)
        return "", err // drop rule rather than guess at an over-broad destination
    }
    return "", err
}

Prevention

When it happens

Trigger: Rule with IsDynamic=false but a Destination holding a bare IP, hostname, or empty string; management/API writing a domain into Destination without setting IsDynamic; version mismatch where the agent expects CIDR-only destinations.

Common situations: Policies authored via API mixing up the domain field semantics (domains belong in rule.Domains with IsDynamic=true); dashboard edge cases; management validation gaps on older builds.

Related errors


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