cilium/cilium · error

invalid reserved range start %q in pool CIDR %s: %w

Error message

invalid reserved range start %q in pool CIDR %s: %w

What it means

parseReservedRanges could not parse the Start field of a ReservedRange as an IP address with netip.ParseAddr, while validating the reserved ranges of a pool CIDR. The offending value and the pool CIDR are included in the message, and the netip error is wrapped.

Source

Thrown at operator/pkg/ipam/allocator/multipool/pool_handler.go:191

		if err != nil {
			return nil, err
		}

		configs = append(configs, poolCIDRConfig{
			cidr:           prefix,
			reservedRanges: reservedRanges,
		})
	}

	return configs, nil
}

func parseReservedRanges(poolCIDR netip.Prefix, ranges []v2.ReservedRange) ([]netipx.IPRange, error) {
	reserved := make([]netipx.IPRange, 0, len(ranges))
	for _, rr := range ranges {
		start, err := netip.ParseAddr(rr.Start)
		if err != nil {
			return nil, fmt.Errorf("invalid reserved range start %q in pool CIDR %s: %w", rr.Start, poolCIDR, err)
		}

		end, err := netip.ParseAddr(rr.End)
		if err != nil {
			return nil, fmt.Errorf("invalid reserved range end %q in pool CIDR %s: %w", rr.End, poolCIDR, err)
		}

		r := netipx.IPRangeFrom(start, end)
		if !r.IsValid() {
			return nil, fmt.Errorf("invalid reserved range %s-%s in pool CIDR %s", start, end, poolCIDR)
		}

		if !poolCIDR.Contains(start) || !poolCIDR.Contains(end) {
			return nil, fmt.Errorf("reserved range %s-%s is outside pool CIDR %s", start, end, poolCIDR)
		}

		reserved = append(reserved, r)
	}

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Replace rr.Start with a valid bare IP literal (no /prefix, no hostname), matching the pool CIDR family.
  2. If a whole subnet should be excluded, express it as start/end pair of first and last addresses instead of CIDR notation.
  3. Check for unexpanded template variables or empty strings in the manifest.
  4. Validate with netip.ParseAddr (or a YAML schema check) before applying.

Example fix

// before
reservedRanges: [{start: "10.0.0.0/29", end: "10.0.0.7"}]
// after
reservedRanges: [{start: "10.0.0.0", end: "10.0.0.7"}]
Defensive patterns

Strategy: validation

Validate before calling

for _, rr := range ranges {
    if _, err := netip.ParseAddr(rr.Start); err != nil {
        return fmt.Errorf("ReservedRange.Start %q is not a valid IP: %w", rr.Start, err)
    }
}

Type guard

func isBareIP(s string) bool {
    a, err := netip.ParseAddr(s)
    return err == nil && !a.Zone().IsZero() == false && !strings.Contains(s, "/")
}

Try / catch

if _, err := netip.ParseAddr(rr.Start); err != nil {
    return fmt.Errorf("invalid reserved range start %q: use a bare IP literal", rr.Start)
}

Prevention

When it happens

Trigger: A v2.ReservedRange in a PoolCIDRConfig (reached via buildPoolCIDRConfigs -> UpsertPool or ParsePoolSpec callers) has rr.Start set to a non-IP string: hostname, CIDR ('10.0.0.1/32'), empty string, port-suffixed value, or malformed IPv6.

Common situations: Typos in reserved range YAML; users putting CIDR notation instead of a bare IP in the start field; template variables left unexpanded; empty fields from partial config merge.

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/bc2b0c445ea3c832. Report an issue: GitHub.