{"record":{"id":"86d1d01fbaf9cc6e","repo":"juanfont/headscale","slug":"resolving-destination-w","errorCode":null,"errorMessage":"resolving destination: %w","messagePattern":"resolving destination: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"hscontrol/policy/v2/test.go","lineNumber":354,"sourceCode":"\n\treturn addrs.Prefixes(), nil\n}\n\n// evalReachability reports whether traffic from any srcPrefix to dst (in\n// `host:port` form) is allowed by filter for the requested protocol.\n//\n// Empty proto means the default set the client applies when proto is\n// omitted (TCP/UDP/ICMP) — we accept a rule whose IPProto list contains\n// any of those, or rules with no IPProto restriction at all.\nfunc evalReachability(srcPrefixes []netip.Prefix, dst string, proto Protocol, pol *Policy, filter []tailcfg.FilterRule, users []types.User, nodes views.Slice[types.NodeView]) (bool, error) {\n\tawp, err := parseDestinationAlias(dst)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"invalid destination %q: %w\", dst, err)\n\t}\n\n\tdstAddrs, err := awp.Resolve(pol, users, nodes)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"resolving destination: %w\", err)\n\t}\n\n\tif dstAddrs == nil || dstAddrs.Empty() {\n\t\treturn false, fmt.Errorf(\"%w: %q\", errTestDestinationNoIP, dst)\n\t}\n\n\tdstPrefixes := dstAddrs.Prefixes()\n\n\t// Tailscale's tests semantics: ALL src prefixes must reach the dst for\n\t// the test to consider it allowed. A partial allow is a fail.\n\tfor _, src := range srcPrefixes {\n\t\tif !srcReachesDst(src, dstPrefixes, awp.Ports, proto, filter) {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn true, nil\n}","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/test.go#L336-L372","documentation":"The destination alias in a policy test parsed but failed to resolve against the policy, users, and nodes. The wrapped error names the real cause — typically ErrHostResolve (unknown host) or a user/autogroup problem — occurring on the destination side of an accept/deny check.","triggerScenarios":"A test destination like 'unknownhost:80' where 'unknownhost' is not defined in the policy's hosts map, or an autogroup/tag destination the engine cannot resolve. awp.Resolve(pol, users, nodes) errors inside evalReachability.","commonSituations":"Destination hostnames drift out of sync with the hosts section after renames; tests written against Tailscale semantics using destination aliases headscale cannot resolve (e.g. autogroup:self as a destination).","solutions":["Read the wrapped error after 'resolving destination:' to get the exact cause.","If a hostname, add it to the 'hosts' section of the policy (e.g. \"web\": \"100.64.0.10/32\") or use the IP/CIDR directly.","If a user/tag destination, confirm the user exists or the tag is used somewhere in the policy.","Avoid autogroups as destinations; they are source-oriented."],"exampleFix":"// before\n\"hosts\": {},\n\"tests\": [{\"src\": \"a@example.com\", \"accept\": [\"web:80\"]}]\n\n// after\n\"hosts\": {\"web\": \"100.64.0.10/32\"},\n\"tests\": [{\"src\": \"a@example.com\", \"accept\": [\"web:80\"]}]","handlingStrategy":"validation","validationCode":"// Ensure every hostname used in tests exists in the hosts map.\nfunc hostDeclared(h string, hosts map[v2.Host]netip.Prefix) bool {\n    _, ok := hosts[v2.Host(h)]\n    return ok\n}","typeGuard":null,"tryCatchPattern":"if err := runPolicyTests(...); err != nil {\n    if errors.Is(err, v2.ErrHostResolve) {\n        // destination host missing from hosts map: add it or switch to CIDR\n    }\n    return err\n}","preventionTips":["Define every referenced hostname in the hosts section in the same commit.","Prefer CIDR destinations in tests to decouple from hosts-map churn.","Grep policy files for hostnames absent from the hosts block before merging."],"tags":["policy","acl","testing","resolution","hosts","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}