{"record":{"id":"dbaa4fd180d4ae47","repo":"juanfont/headscale","slug":"test-protocol-must-be-tcp-udp-sctp-or-empty","errorCode":null,"errorMessage":"test protocol must be tcp, udp, sctp, or empty","messagePattern":"test protocol must be tcp, udp, sctp, or empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":148,"sourceCode":"\tErrInvalidSSHAction            = errors.New(\"invalid SSH action\")\n\tErrInvalidProtocolNumber       = errors.New(\"invalid protocol number\")\n\tErrProtocolLeadingZero         = errors.New(\"leading 0 not permitted in protocol number\")\n\tErrProtocolOutOfRange          = errors.New(\"protocol number out of range (0-255)\")\n\tErrAutogroupNotSupported       = errors.New(\"autogroup not supported in headscale\")\n\tErrAutogroupInternetSrc        = errors.New(\"autogroup:internet can only be used in ACL destinations\")\n\tErrAutogroupSelfSrc            = errors.New(\"\\\"autogroup:self\\\" not valid on the src side of a rule\")\n\tErrAutogroupNotSupportedACLSrc = errors.New(\"autogroup not supported for ACL sources\")\n\tErrAutogroupNotSupportedACLDst = errors.New(\"autogroup not supported for ACL destinations\")\n\tErrAutogroupDangerAllDst       = errors.New(\"cannot use autogroup:danger-all as a dst\")\n\tErrAutogroupNotSupportedSSHSrc = errors.New(\"autogroup not supported for SSH sources\")\n\tErrAutogroupNotSupportedSSHDst = errors.New(\"autogroup not supported for SSH destinations\")\n\tErrHostNotDefined              = errors.New(\"host not defined in policy\")\n\tErrSSHSourceAliasNotSupported  = errors.New(\"alias not supported for SSH source\")\n\tErrSSHDestAliasNotSupported    = errors.New(\"alias not supported for SSH destination\")\n\tErrUnknownField                = errors.New(\"unknown field\")\n\tErrProtocolNoSpecificPorts     = errors.New(\"protocol does not support specific ports\")\n\tErrTestEmptyAssertions         = errors.New(\"test entry must have at least one of \\\"accept\\\" or \\\"deny\\\"\")\n\tErrTestProtocolNotAllowed      = errors.New(\"test protocol must be tcp, udp, sctp, or empty\")\n\tErrTestDestinationMultiPort    = errors.New(\"test destination port must be a single port\")\n\tErrTestDestinationCIDR         = errors.New(\"test destination must be a single host, not a CIDR range\")\n\tErrAutogroupInternetTestDst    = errors.New(\"autogroup:internet not valid as a test destination\")\n\tErrSSHTestEmptySrc             = errors.New(\"SSH tests entry must have a non-empty src\")\n\tErrSSHTestEmptyDst             = errors.New(\"SSH tests entry must have at least one dst\")\n\tErrSSHTestDstUnknownTag        = errors.New(\"SSH tests dst contains unknown tag\")\n\tErrSSHTestDstDisallowedElement = errors.New(\"SSH tests dst contains disallowed element\")\n)\n\ntype resolved struct {\n\tips netipx.IPSet\n}\n\nfunc newResolved(ipb *netipx.IPSetBuilder) (resolved, error) {\n\tips, err := ipb.IPSet()\n\tif err != nil {\n\t\treturn resolved{}, err\n\t}","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L130-L166","documentation":"ErrTestProtocolNotAllowed is returned by validateTests (hscontrol/policy/v2/types.go:3209) when a tests entry sets a \"proto\" that is not tcp, udp, or sctp (or empty). Policy tests simulate one connection attempt over a connection-oriented protocol; port-less protocols like icmp, and numeric protocol strings, cannot be tested this way. The offending proto value is included in the message.","triggerScenarios":"A tests entry with \"proto\": \"icmp\", \"proto\": \"gre\", or a numeric string like \"proto\": \"47\". Raised when t.Proto != \"\" and not one of ProtocolNameTCP/UDP/SCTP.","commonSituations":"Copy-pasting an ACL rule's proto into a test; assuming tests accept the same proto values as grants (they do not — grants allow any valid protocol, tests only port protocols); trying to test ping (icmp) via the tests block, which is unsupported.","solutions":["Remove the proto field (empty defaults to tcp-like port semantics and is allowed)","Or set it to tcp, udp, or sctp","If you need to assert icmp behavior, that must be verified outside the tests block (e.g. integration test with real pings)"],"exampleFix":"// before\n\"tests\": [{\"src\": \"user1\", \"proto\": \"icmp\", \"accept\": [\"tag:server:80\"]}]\n// after\n\"tests\": [{\"src\": \"user1\", \"proto\": \"tcp\", \"accept\": [\"tag:server:80\"]}]","handlingStrategy":"validation","validationCode":"var testableProtos = map[string]bool{\"\": true, \"tcp\": true, \"udp\": true, \"sctp\": true}\nfunc testProtoAllowed(p string) bool { return testableProtos[p] }","typeGuard":null,"tryCatchPattern":"if errors.Is(err, policyv2.ErrTestProtocolNotAllowed) {\n    // drop proto or use tcp/udp/sctp in the flagged test\n}","preventionTips":["Remember tests accept a strict proto subset of grants","Default to omitting proto in tests unless the test is port-protocol specific","Assert icmp behaviour in integration tests, not policy tests"],"tags":["policy","tests","protocol","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}