fatedier/frp · error

unexpected frame type %d, want %d

Error message

unexpected frame type %d, want %d

What it means

decodeV2BinaryUDPPacketFrame received a wire frame whose type is not FrameTypeMessage while decoding a binary UDP packet. Under the v2 wire protocol each UDP packet must ride in a message frame; other frame types (e.g. stream/control frames) indicate the stream is being misinterpreted. This is an internal consistency check after the frame was already routed here by type ID.

Source

Thrown at pkg/msg/udp_binary.go:288

		frame, err := EncodeV2MessageFrame(message)
		if err != nil {
			return err
		}
		return rw.conn.WriteFrame(frame)
	}
	body, err := EncodeUDPPacketBinary(packet)
	if err != nil {
		return err
	}
	payload := make([]byte, 2+len(body))
	binary.BigEndian.PutUint16(payload[:2], V2TypeUDPPacketBinary)
	copy(payload[2:], body)
	return rw.conn.WriteFrame(&wire.Frame{Type: wire.FrameTypeMessage, Payload: payload})
}

func decodeV2BinaryUDPPacketFrame(frame *wire.Frame) (*UDPPacket, error) {
	if frame.Type != wire.FrameTypeMessage {
		return nil, fmt.Errorf("unexpected frame type %d, want %d", frame.Type, wire.FrameTypeMessage)
	}
	if len(frame.Payload) < 2 {
		return nil, fmt.Errorf("message frame payload too short")
	}
	if binary.BigEndian.Uint16(frame.Payload[:2]) != V2TypeUDPPacketBinary {
		return nil, unexpectedV2UDPPacketType(frame)
	}
	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)
	}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Verify the io.ReadWriter passed to NewV2BinaryUDPPacketReadWriter is a connection that actually carries v2 wire frames (use wire.NewConn framing).
  2. Use NewUDPPacketReadWriter to construct the read writer — it selects the correct codec from the negotiated protocol strings.
  3. Treat the error as fatal for the connection and reconnect.

Example fix

// before
rw := msg.NewV2BinaryUDPPacketReadWriter(rawConn) // rawConn lacks v2 wire framing

// after
rw, err := msg.NewUDPPacketReadWriter(rawConn, wire.ProtocolV2, wire.UDPPacketCodecBinary)
Defensive patterns

Strategy: validation

Validate before calling

// construct via the factory, never by hand
rw, err := msg.NewUDPPacketReadWriter(conn, wireProtocol, udpPacketCodec)
if err != nil {
	return err
}

Try / catch

if _, err := rw.ReadMsg(); err != nil {
	if strings.Contains(err.Error(), "unexpected frame type") {
		conn.Close() // wrong framing layer; reconnect
	}
}

Prevention

When it happens

Trigger: ReadMsg()/decodeV2BinaryUDPPacketFrame processes a frame with frame.Type != FrameTypeMessage — typically after stream desynchronization, or when a raw connection is fed into V2BinaryUDPPacketReadWriter without the v2 wire framing.

Common situations: Wrapping the wrong io.ReadWriter (a plain TCP conn expected to speak v2 wire frames); debugging harnesses replaying captured bytes; stream corruption from an intermediary.

Related errors


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