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
- 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)
- Validate before saving via API: `if _, err := netip.ParsePrefix(r); err != nil { ... }`
- 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
- Always write CIDRs with explicit prefix length: single hosts are /32 (v4) or /128 (v6)
- netip is stricter than net.ParseCIDR: no spaces, no bare IPs - normalize input at the API boundary
- Add the parseSourceRanges guard to any script or automation that writes network policies
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
- determine destination: %w
- connector type change not allowed
- service name is required
- service name exceeds maximum length of 255 characters
- at least one target is required
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/580bdbbe37d8b042.
Report an issue: GitHub.