netbirdio/netbird · error

parse source range: %w

Error message

parse source range: %w

What it means

Returned by applyRouteACL when netip.ParsePrefix rejects one of the rule's SourceRanges strings from the management proto (RouteFirewallRule.SourceRanges). ParsePrefix requires a strict `addr/bits` CIDR (netip is stricter than older net utils: no spaces, no bare IPs, bits must be valid for the family, no zone qualifiers). The error aborts that single rule before destination/protocol are even examined.

Source

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

			}
			// implicitly deleted from the map
		}
	}

	d.routeRules = newRouteRules
	return nberrors.FormatErrorOrNil(merr)
}

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)
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Fix the source range in the policy to a valid CIDR - bare IPs must be written as a.b.c.d/32 (or /128 for v6)
  2. Validate before saving via API: `if _, err := netip.ParsePrefix(r); err != nil { ... }`
  3. After fixing, force a network map update (reconnect the peer or edit the policy) so the agent retries the rule

Example fix

// before (policy payload)
{"source_ranges": ["10.20.0.5"]}

// after
{"source_ranges": ["10.20.0.5/32"]}

// api-side guard
func validSourceRanges(rs []string) bool {
    for _, r := range rs {
        if _, err := netip.ParsePrefix(r); err != nil {
            return false
        }
    }
    return true
}
Defensive patterns

Strategy: validation

Validate before calling

// run on every source range before creating/updating a policy
func parseSourceRanges(ranges []string) ([]netip.Prefix, error) {
    out := make([]netip.Prefix, 0, len(ranges))
    for _, r := range ranges {
        p, err := netip.ParsePrefix(strings.TrimSpace(r))
        if err != nil {
            return nil, fmt.Errorf("source range %q: %w", r, err)
        }
        out = append(out, p)
    }
    return out, nil
}

Type guard

func isStrictCIDR(s string) bool {
    _, err := netip.ParsePrefix(s)
    return err == nil
}

Try / catch

if _, err := netip.ParsePrefix(sourceRange); err != nil {
    log.Warnf("skipping malformed source range %q, management data issue: %v", sourceRange, err)
    continue // skip one range, keep the rest of the rule working where possible
}

Prevention

When it happens

Trigger: Management sending a source range as a bare IP (`10.0.0.5` without `/32`), a malformed mask (`10.0.0.0/33`), a hostname, or a v4-in-v6 mix; typical when policies are authored through the REST API rather than the dashboard, or by a management version that skips validation.

Common situations: API/script-driven policy creation without CIDR validation; copy-paste of IP lists into source ranges; older self-hosted management builds.

Related errors


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