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
- Give work-conn UDP traffic its own ReadWriter; use NewUDPPacketReadWriter rather than wiring the binary reader by hand.
- Do not share a single wire.Conn between the control loop and UDP forwarding.
- 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
- Dedicate one ReadWriter per traffic class (control vs UDP).
- Construct readers via NewUDPPacketReadWriter.
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
- truncated zone
- received JSON UDP packet after binary codec negotiation
- unexpected frame type %d, want %d
- message frame payload too short
- UDP packet codec %q requires wire protocol v2
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/00d6dc1c388733f8.
Report an issue: GitHub.