fatedier/frp · error

unexpected message type %d, want %d

Error message

unexpected message type %d, want %d

What it means

unexpectedV2UDPPacketType's final branch: the frame is a well-formed message frame carrying a known-shape but unexpected message type ID — not 13 (JSON UDP) and not 19 (binary UDP). E.g. a Login or Ping message routed to the binary UDP packet decoder. This means control-plane traffic is arriving on a path dedicated to UDP packets, usually a misuse of the read writer or a desynchronized stream.

Source

Thrown at pkg/msg/udp_binary.go:314

	return DecodeUDPPacketBinary(frame.Payload[2:])
}

func isV2MessageType(frame *wire.Frame, typeID uint16) bool {
	return frame.Type == wire.FrameTypeMessage && len(frame.Payload) >= 2 && binary.BigEndian.Uint16(frame.Payload[:2]) == typeID
}

func unexpectedV2UDPPacketType(frame *wire.Frame) error {
	if frame.Type != wire.FrameTypeMessage {
		return fmt.Errorf("unexpected frame type %d, want %d", frame.Type, wire.FrameTypeMessage)
	}
	if len(frame.Payload) < 2 {
		return fmt.Errorf("message frame payload too short")
	}
	typeID := binary.BigEndian.Uint16(frame.Payload[:2])
	if typeID == V2TypeUDPPacket {
		return fmt.Errorf("received JSON UDP packet after binary codec negotiation")
	}
	return fmt.Errorf("unexpected message type %d, want %d", typeID, V2TypeUDPPacketBinary)
}

// NewUDPPacketReadWriter selects the negotiated packet codec without changing
// the framing or codecs used by non-UDP messages on the work connection.
func NewUDPPacketReadWriter(rw io.ReadWriter, wireProtocol, udpPacketCodec string) (ReadWriter, error) {
	switch wireProtocol {
	case "", wire.ProtocolV1:
		if udpPacketCodec != "" {
			return nil, fmt.Errorf("UDP packet codec %q requires wire protocol v2", udpPacketCodec)
		}
		return NewV1ReadWriter(rw), nil
	case wire.ProtocolV2:
		switch udpPacketCodec {
		case "":
			return NewV2ReadWriter(rw), nil
		case wire.UDPPacketCodecBinary:
			return NewV2BinaryUDPPacketReadWriter(rw), nil
		default:

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Give work-conn UDP traffic its own ReadWriter; use NewUDPPacketReadWriter rather than wiring the binary reader by hand.
  2. Do not share a single wire.Conn between the control loop and UDP forwarding.
  3. On this error, close the connection — the message stream ordering is broken.
Defensive patterns

Strategy: try-catch

Try / catch

if err := rw.ReadMsgInto(&msg.UDPPacket{}); err != nil {
	if strings.Contains(err.Error(), "unexpected message type") {
		// wrong message arrived on the UDP path: close and reconnect
		conn.Close()
	}
}

Prevention

When it happens

Trigger: ReadMsgInto(&msg.UDPPacket{}) receives any non-UDP v2 message (Login=1, Ping=11, NatHoleVisitor=14, etc.) — e.g. pointing V2BinaryUDPPacketReadWriter at a control connection, or a stream that slipped alignment.

Common situations: Custom code reuses one ReadWriter for both control messages and UDP packets; framing desync causes a control frame to be read where a UDP packet was expected.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/00d6dc1c388733f8. Report an issue: GitHub.