netbirdio/netbird · warning

unhandled protocol type: %d

Error message

unhandled protocol type: %d

What it means

The default arm of the switch in toExposeProtocol: ParseProtocolType returned a ProtocolType value that has no mapping to a proto.ExposeProtocol enum. Today ParseProtocolType can only return the values 0-4 and every one has a case, so this branch is dead code — a guard ensuring that any protocol constant added to client/internal/expose/protocol.go without a matching mapping here fails loudly instead of silently mapping to zero (EXPOSE_HTTP).

Source

Thrown at client/cmd/expose.go:233

func toExposeProtocol(exposeProtocol string) (proto.ExposeProtocol, error) {
	p, err := expose.ParseProtocolType(exposeProtocol)
	if err != nil {
		return 0, fmt.Errorf("invalid protocol: %w", err)
	}

	switch p {
	case expose.ProtocolHTTP:
		return proto.ExposeProtocol_EXPOSE_HTTP, nil
	case expose.ProtocolHTTPS:
		return proto.ExposeProtocol_EXPOSE_HTTPS, nil
	case expose.ProtocolTCP:
		return proto.ExposeProtocol_EXPOSE_TCP, nil
	case expose.ProtocolUDP:
		return proto.ExposeProtocol_EXPOSE_UDP, nil
	case expose.ProtocolTLS:
		return proto.ExposeProtocol_EXPOSE_TLS, nil
	default:
		return 0, fmt.Errorf("unhandled protocol type: %d", p)
	}
}

func handleExposeReady(cmd *cobra.Command, stream proto.DaemonService_ExposeServiceClient, port uint64) error {
	event, err := stream.Recv()
	if err != nil {
		return fmt.Errorf("receive expose event: %v", status.Convert(err).Message())
	}

	ready, ok := event.Event.(*proto.ExposeServiceEvent_Ready)
	if !ok {
		return fmt.Errorf("unexpected expose event: %T", event.Event)
	}
	printExposeReady(cmd, ready.Ready, port)
	return nil
}

func printExposeReady(cmd *cobra.Command, r *proto.ExposeServiceReady, port uint64) {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. As a user: not reachable in stock builds — if a custom build shows it, update/rebuild so both enums match
  2. As a contributor: in the same change that adds the expose.ProtocolType constant, add its case mapping to the matching proto.ExposeProtocol_* value in toExposeProtocol

Example fix

// before: new constant parsed but not mapped -> hits default
case expose.ProtocolTLS:
	return proto.ExposeProtocol_EXPOSE_TLS, nil
default:
	return 0, fmt.Errorf("unhandled protocol type: %d", p)

// after: add the mapping when introducing the constant
case expose.ProtocolTLS:
	return proto.ExposeProtocol_EXPOSE_TLS, nil
case expose.ProtocolQUIC:
	return proto.ExposeProtocol_EXPOSE_QUIC, nil
default:
	return 0, fmt.Errorf("unhandled protocol type: %d", p)
Defensive patterns

Strategy: type-guard

Type guard

func protocolTypeSupported(p expose.ProtocolType) bool {
	switch p {
	case expose.ProtocolHTTP, expose.ProtocolHTTPS, expose.ProtocolTCP, expose.ProtocolUDP, expose.ProtocolTLS:
		return true
	default:
		return false
	}
}

Prevention

When it happens

Trigger: A contributor adds a new constant to ProtocolType (e.g. ProtocolQUIC = 5) and extends ParseProtocolType to parse it, but forgets the corresponding case in toExposeProtocol's switch.

Common situations: Two-file feature changes where one hunk is lost in rebase; forks adding protocols to the expose package only; copy of the mapping drifting during refactors.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/1de571afdae43d42. Report an issue: GitHub.