juanfont/headscale · warning · errGeneratedIPNotInPrefix
%w: ip(%s) not in prefix(%s)
Error message
%w: ip(%s) not in prefix(%s)
What it means
Defensive check in randomNext: after computing a random address arithmetically inside the prefix's numeric range, it verifies pfx.Contains(ip) and otherwise returns this error (wrapping errGeneratedIPNotInPrefix). With correct arithmetic this is unreachable; seeing it means the range math produced an address outside the prefix — a code-level invariant break, not an environmental issue.
Source
Thrown at hscontrol/db/ip.go:280
}
out, err := rand.Int(rand.Reader, tempMax)
if err != nil {
return netip.Addr{}, fmt.Errorf("generating random IP: %w", err)
}
valInRange := big.NewInt(0).Add(&from, out)
// big.Int.Bytes() strips leading zero bytes, so a value with a zero high
// byte yields a too-short slice that AddrFromSlice rejects. Pad to the
// prefix's address width.
ip, ok := netip.AddrFromSlice(valInRange.FillBytes(make([]byte, len(fromIP.AsSlice()))))
if !ok {
return netip.Addr{}, errGeneratedIPBytesInvalid
}
if !pfx.Contains(ip) {
return netip.Addr{}, fmt.Errorf(
"%w: ip(%s) not in prefix(%s)",
errGeneratedIPNotInPrefix,
ip.String(),
pfx.String(),
)
}
return ip, nil
}
func isTailscaleReservedIP(ip netip.Addr) bool {
return tsaddr.ChromeOSVMRange().Contains(ip) ||
tsaddr.TailscaleServiceIP() == ip ||
tsaddr.TailscaleServiceIPv6() == ip
}
// BackfillNodeIPs will take a database transaction, and
// iterate through all of the current nodes ([types.Node]) in headscaleView on GitHub (pinned to 565fd254d0)
Solutions
- Normalize prefixes so network bits only: use 10.0.0.0/24, not 10.0.0.1/24.
- If using a dev/custom build, review randomNext's from/to computation against netip.Prefix.Contains semantics; report upstream with the prefix value.
- Switch to sequential strategy as a workaround while investigating.
Example fix
# before (config.yaml) ip_prefixes: - 10.0.0.1/24 # host bits set, contains-check can diverge from range math # after ip_prefixes: - 10.0.0.0/24 # properly masked network prefix
Defensive patterns
Strategy: validation
Validate before calling
// Ensure every configured prefix is masked before use:
p, err := netip.ParsePrefix(s)
if err != nil { return err }
p = p.Masked() // reject or normalize instead of passing host-bit prefixes Try / catch
// Treat as an internal invariant failure: log the prefix that triggered // it, normalize masked prefixes in config, and report upstream if it // reproduces with a properly masked prefix.
Prevention
- Always configure masked network prefixes (10.0.0.0/24, not 10.0.0.1/24).
- Run the prefix linter from error 414's defense in CI.
When it happens
Trigger: Only reachable if the from/to big.Int bounds and the padding logic in randomNext ever disagree with pfx — e.g. after a refactor of the range computation, or an unusual prefix (host-bits-set prefix like 10.0.0.1/24 where Contains semantics differ from raw range math).
Common situations: Custom ip_prefixes entries that are not properly masked (host bits set); running a modified or in-development build of headscale where randomNext was changed.
Related errors
- building initial IP Set: %w
- failed to allocate IP
- tag owner is not an Alias
- invalid prefix
- auto approver is not an alias
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/813d8915738c97a2.
Report an issue: GitHub.