{"record":{"id":"bcd8e5edc68bb21a","repo":"juanfont/headscale","slug":"w-q-bcd8e5","errorCode":null,"errorMessage":"%w: %q","messagePattern":"%w: %q","errorType":"validation","errorClass":"errTestDestinationNoIP","httpStatus":null,"severity":"warning","filePath":"hscontrol/policy/v2/test.go","lineNumber":358,"sourceCode":"// 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}\n\n// parseDestinationAlias is a thin wrapper over [AliasWithPorts.UnmarshalJSON]\n// so callers can hand it a bare `\"host:port\"` string without re-implementing\n// the parse logic.","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/test.go#L340-L376","documentation":"A policy-test destination resolved syntactically but produced an empty IP set, so there is nothing to test reachability against. errTestDestinationNoIP is the dedicated sentinel for 'the alias is valid but names no addresses' — commonly a tag or group with no matching nodes/users in the test fixture.","triggerScenarios":"Destination alias references a tag that no node carries, a group with no members, or a hosts entry that would resolve but the alias resolves empty; dstAddrs is nil or Empty() in evalReachability.","commonSituations":"Testing against 'tag:server' before any node has registered with that tag; group membership lists only users absent from the test user set; hosts entry pointing at an empty expansion.","solutions":["Give the alias at least one address in the test context: attach the tag to a node in the test fixture, add a member to the group, or use a concrete host/IP destination.","If the alias is intentionally empty, remove that accept/deny entry — an empty destination cannot produce a meaningful pass/fail.","Verify the destination is spelled the same as in the grants section (tag:server vs tag:servers)."],"exampleFix":"// before\n\"tests\": [{\"src\": \"a@example.com\", \"accept\": [\"tag:empty:80\"]}]\n\n// after\n\"tests\": [{\"src\": \"a@example.com\", \"accept\": [\"100.64.0.5:80\"]}]","handlingStrategy":"validation","validationCode":"// Skip destinations that resolve to zero addresses before asserting.\naddrs, err := alias.Resolve(pol, users, nodes)\nif err != nil { return err }\nif addrs == nil || addrs.Empty() {\n    return fmt.Errorf(\"destination %q has no IPs; attach a node or drop the entry\", dst)\n}","typeGuard":null,"tryCatchPattern":"if err := runPolicyTests(...); err != nil {\n    if errors.Is(err, v2.ErrTestDestinationNoIP) {\n        // empty alias: fix fixture (tag a node / add group member), not policy\n    }\n    return err\n}","preventionTips":["Attach tags to at least one fixture node before testing tag destinations.","Populate groups with at least one existing member.","Treat empty-resolution as a fixture bug, not an ACL result."],"tags":["policy","acl","testing","empty-result","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}