juanfont/headscale · error
test protocol must be tcp, udp, sctp, or empty
Error message
test protocol must be tcp, udp, sctp, or empty
What it means
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.
Source
Thrown at hscontrol/policy/v2/types.go:148
ErrInvalidSSHAction = errors.New("invalid SSH action")
ErrInvalidProtocolNumber = errors.New("invalid protocol number")
ErrProtocolLeadingZero = errors.New("leading 0 not permitted in protocol number")
ErrProtocolOutOfRange = errors.New("protocol number out of range (0-255)")
ErrAutogroupNotSupported = errors.New("autogroup not supported in headscale")
ErrAutogroupInternetSrc = errors.New("autogroup:internet can only be used in ACL destinations")
ErrAutogroupSelfSrc = errors.New("\"autogroup:self\" not valid on the src side of a rule")
ErrAutogroupNotSupportedACLSrc = errors.New("autogroup not supported for ACL sources")
ErrAutogroupNotSupportedACLDst = errors.New("autogroup not supported for ACL destinations")
ErrAutogroupDangerAllDst = errors.New("cannot use autogroup:danger-all as a dst")
ErrAutogroupNotSupportedSSHSrc = errors.New("autogroup not supported for SSH sources")
ErrAutogroupNotSupportedSSHDst = errors.New("autogroup not supported for SSH destinations")
ErrHostNotDefined = errors.New("host not defined in policy")
ErrSSHSourceAliasNotSupported = errors.New("alias not supported for SSH source")
ErrSSHDestAliasNotSupported = errors.New("alias not supported for SSH destination")
ErrUnknownField = errors.New("unknown field")
ErrProtocolNoSpecificPorts = errors.New("protocol does not support specific ports")
ErrTestEmptyAssertions = errors.New("test entry must have at least one of \"accept\" or \"deny\"")
ErrTestProtocolNotAllowed = errors.New("test protocol must be tcp, udp, sctp, or empty")
ErrTestDestinationMultiPort = errors.New("test destination port must be a single port")
ErrTestDestinationCIDR = errors.New("test destination must be a single host, not a CIDR range")
ErrAutogroupInternetTestDst = errors.New("autogroup:internet not valid as a test destination")
ErrSSHTestEmptySrc = errors.New("SSH tests entry must have a non-empty src")
ErrSSHTestEmptyDst = errors.New("SSH tests entry must have at least one dst")
ErrSSHTestDstUnknownTag = errors.New("SSH tests dst contains unknown tag")
ErrSSHTestDstDisallowedElement = errors.New("SSH tests dst contains disallowed element")
)
type resolved struct {
ips netipx.IPSet
}
func newResolved(ipb *netipx.IPSetBuilder) (resolved, error) {
ips, err := ipb.IPSet()
if err != nil {
return resolved{}, err
}View on GitHub (pinned to 565fd254d0)
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)
Example fix
// before
"tests": [{"src": "user1", "proto": "icmp", "accept": ["tag:server:80"]}]
// after
"tests": [{"src": "user1", "proto": "tcp", "accept": ["tag:server:80"]}] Defensive patterns
Strategy: validation
Validate before calling
var testableProtos = map[string]bool{"": true, "tcp": true, "udp": true, "sctp": true}
func testProtoAllowed(p string) bool { return testableProtos[p] } Try / catch
if errors.Is(err, policyv2.ErrTestProtocolNotAllowed) {
// drop proto or use tcp/udp/sctp in the flagged test
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid protocol number
- leading 0 not permitted in protocol number
- protocol number out of range (0-255)
- protocol does not support specific ports
- test entry must have at least one of "accept" or "deny"
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/dbaa4fd180d4ae47.
Report an issue: GitHub.