{"record":{"id":"84c21075eb5790f6","repo":"juanfont/headscale","slug":"proto-name-not-known-use-protocol-number-0-25","errorCode":null,"errorMessage":"proto name \"*\" not known; use protocol number 0-255 or protocol name (icmp, tcp, udp, etc.)","messagePattern":"proto name \"\\*\" not known; use protocol number 0-255 or protocol name \\(icmp, tcp, udp, etc\\.\\)","errorType":"validation","errorClass":"errUnknownProtocolWildcard","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":2005,"sourceCode":"\tSSHs                []SSH              `json:\"ssh,omitempty\"`\n\tTests               []PolicyTest       `json:\"tests,omitempty\"`\n\tSSHTests            []SSHPolicyTest    `json:\"sshTests,omitempty\"`\n\tRandomizeClientPort bool               `json:\"randomizeClientPort,omitempty\"`\n}\n\n// MarshalJSON is deliberately not implemented for [Policy].\n// We use the default JSON marshalling behavior provided by the Go runtime.\n\nvar (\n\t// TODO(kradalby): Add these checks for tagOwners and autoApprovers.\n\tautogroupForSrc       = []AutoGroup{AutoGroupMember, AutoGroupTagged, AutoGroupDangerAll}\n\tautogroupForDst       = []AutoGroup{AutoGroupInternet, AutoGroupMember, AutoGroupTagged, AutoGroupSelf}\n\tautogroupForSSHSrc    = []AutoGroup{AutoGroupMember, AutoGroupTagged}\n\tautogroupForSSHDst    = []AutoGroup{AutoGroupMember, AutoGroupTagged, AutoGroupSelf}\n\tautogroupForNodeAttrs = []AutoGroup{AutoGroupMember, AutoGroupTagged}\n\tautogroupNotSupported = []AutoGroup{}\n\n\terrUnknownProtocolWildcard = errors.New(\"proto name \\\"*\\\" not known; use protocol number 0-255 or protocol name (icmp, tcp, udp, etc.)\")\n)\n\n// reservedTSRanges are CGNAT subranges that Tailscale uses internally and that\n// nodeAttrs ipPool entries must not overlap.\n//\n//   - 100.100.100.0/24 is MagicDNS / TSMP\n//   - 100.115.92.0/23 is the Quad100 / IPN service range\n//\n// (See https://tailscale.com/kb/1304/ip-pool for the operator-facing list.)\nvar reservedTSRanges = []netip.Prefix{\n\tnetip.MustParsePrefix(\"100.100.100.0/24\"),\n\tnetip.MustParsePrefix(\"100.115.92.0/23\"),\n}\n\nfunc validateAutogroupSupported(ag *AutoGroup) error {\n\tif ag == nil {\n\t\treturn nil\n\t}","sourceCodeStart":1987,"sourceCodeEnd":2023,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L1987-L2023","documentation":"errUnknownProtocolWildcard (lowercase, unexported; hscontrol/policy/v2/types.go:2005) is returned by Protocol.validate (types.go:1763) when a rule's proto is the string \"*\". Although \"*\" reads as 'any protocol', headscale (matching Tailscale SaaS) rejects it explicitly: to match all protocols you omit the proto field instead, and to match all ports you use \"*\" on the destination port side.","triggerScenarios":"An ACL/grant rule with \"proto\": \"*\". Protocol.UnmarshalJSON lowercases the input, canonicalises numbers to names, then validate() hits case ProtocolNameWildcard and returns this error at parse time.","commonSituations":"Writing a deny-all-protocols rule using proto:\"*\" out of instinct; converting firewall rules from other systems (iptables ANY) that use a wildcard protocol token; docs/examples from other tools suggesting \"*\" as any-proto.","solutions":["Delete the proto field entirely — an omitted proto means all protocols","Keep \"*\" only on the port side (dst \"host:*\") where it is valid","If you meant 'all protocols to all ports', use {\"src\": [...], \"dst\": [\"x:*\"]} with no proto"],"exampleFix":"// before\n{\"proto\": \"*\", \"src\": [\"group:admin\"], \"dst\": [\"tag:server:*\"]}\n// after\n{\"src\": [\"group:admin\"], \"dst\": [\"tag:server:*\"]}","handlingStrategy":"validation","validationCode":"// Reject wildcard protocol before serialising a policy\nfunc protoValid(p string) bool { return p != \"*\" }","typeGuard":null,"tryCatchPattern":"if err := policyv2.LoadPolicy(buf); err != nil {\n    if strings.Contains(err.Error(), \"proto name \\\"*\\\" not known\") {\n        // sentinel is unexported; match by message or pre-validate proto != \"*\"\n    }\n}","preventionTips":["Never write proto:\"*\"; omit proto for all protocols","Reserve \"*\" for the port position only","Note the sentinel is unexported, so pre-validate rather than errors.Is"],"tags":["policy","acl","protocol","wildcard","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}