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

  1. Remove the proto field (empty defaults to tcp-like port semantics and is allowed)
  2. Or set it to tcp, udp, or sctp
  3. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/dbaa4fd180d4ae47. Report an issue: GitHub.