juanfont/headscale · warning

invalid destination %q: %w

Error message

invalid destination %q: %w

What it means

In the policy-test evaluator, the destination string of an accept/deny check (host:port form) failed parseDestinationAlias. The destination is syntactically invalid: malformed port range, missing ':', or an alias component that does not parse.

Source

Thrown at hscontrol/policy/v2/test.go:349

	}

	if addrs == nil || addrs.Empty() {
		return nil, nil
	}

	return addrs.Prefixes(), nil
}

// evalReachability reports whether traffic from any srcPrefix to dst (in
// `host:port` form) is allowed by filter for the requested protocol.
//
// Empty proto means the default set the client applies when proto is
// omitted (TCP/UDP/ICMP) — we accept a rule whose IPProto list contains
// any of those, or rules with no IPProto restriction at all.
func evalReachability(srcPrefixes []netip.Prefix, dst string, proto Protocol, pol *Policy, filter []tailcfg.FilterRule, users []types.User, nodes views.Slice[types.NodeView]) (bool, error) {
	awp, err := parseDestinationAlias(dst)
	if err != nil {
		return false, fmt.Errorf("invalid destination %q: %w", dst, err)
	}

	dstAddrs, err := awp.Resolve(pol, users, nodes)
	if err != nil {
		return false, fmt.Errorf("resolving destination: %w", err)
	}

	if dstAddrs == nil || dstAddrs.Empty() {
		return false, fmt.Errorf("%w: %q", errTestDestinationNoIP, dst)
	}

	dstPrefixes := dstAddrs.Prefixes()

	// Tailscale's tests semantics: ALL src prefixes must reach the dst for
	// the test to consider it allowed. A partial allow is a fail.
	for _, src := range srcPrefixes {
		if !srcReachesDst(src, dstPrefixes, awp.Ports, proto, filter) {
			return false, nil

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Rewrite the destination as 'alias:port' or 'alias:port-port', e.g. 'web:80' or 'web:8080-8090'.
  2. Ensure every port is in 0-65535 and ranges are 'low-high' with low <= high.
  3. For multiple ports use a comma-separated list of destinations ('web:80, web:443'), not multiple colons.
  4. For IPv6 destinations, use the canonical form accepted by the alias parser (a hosts-map name or a documented prefix form), never a bare '::'-style literal with a port.

Example fix

// before
"accept": ["web:80,443"]

// after
"accept": ["web:80", "web:443"]
Defensive patterns

Strategy: validation

Validate before calling

// Validate test destinations before running the suite.
var dstRe = regexp.MustCompile(`^[^:]+:[0-9]+(-[0-9]+)?$`)
if !dstRe.MatchString(dst) {
    return fmt.Errorf("bad test destination %q, want alias:port[-port]", dst)
}

Try / catch

if err := runPolicyTests(...); err != nil {
    if strings.Contains(err.Error(), "invalid destination") {
        // rewrite the dst entry as alias:port and re-run once
    }
    return err
}

Prevention

When it happens

Trigger: A test entry like "web:80-" , "web:" , "10.0.0.1:99999", or "web:80:443" (multiple colons). parseDestinationAlias(dst) returns an error inside evalReachability.

Common situations: Hand-writing test destinations and using a comma where a colon is expected ("web,80"), open-ended port ranges, ports outside 0-65535, or extra colons from copy-pasting IPv6 addresses without brackets.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/76cd3e6d58a6572d. Report an issue: GitHub.