Add simple cipher benchmarking.

This commit is contained in:
Milan Broz
2012-11-08 16:36:00 +01:00
parent 7199662fbb
commit db97d3d8c8
8 changed files with 342 additions and 4 deletions

View File

@@ -51,6 +51,7 @@ libcryptsetup_la_SOURCES = \
nls.h \
libcryptsetup.h \
utils.c \
utils_benchmark.c \
utils_crypt.c \
utils_crypt.h \
utils_loop.c \

View File

@@ -100,7 +100,7 @@ ssize_t read_blockwise(int fd, int bsize, void *_buf, size_t count);
ssize_t write_lseek_blockwise(int fd, int bsize, char *buf, size_t count, off_t offset);
unsigned crypt_getpagesize(void);
int init_crypto(struct crypt_device *ctx);
void logger(struct crypt_device *cd, int class, const char *file, int line, const char *format, ...);
#define log_dbg(x...) logger(NULL, CRYPT_LOG_DEBUG, __FILE__, __LINE__, x)

View File

@@ -849,6 +849,29 @@ crypt_status_info crypt_status(struct crypt_device *cd, const char *name);
*/
int crypt_dump(struct crypt_device *cd);
/**
* Informational benchmark for ciphers
*
* @param cd crypt device handle
* @param cipher (e.g. "aes")
* @param cipher_mode (e.g. "xts"), IV generator is ignored
* @param volume_key_size size of volume key in bytes
* @param iv_size size of IV in bytes
* @param buffer_size size of encryption buffer in bytes used in test
* @param encryption_mbs measured encryption speed in MiB/s
* @param decryption_mbs measured decryption speed in MiB/s
*
* @return @e 0 on success or negative errno value otherwise.
*/
int crypt_benchmark(struct crypt_device *cd,
const char *cipher,
const char *cipher_mode,
size_t volume_key_size,
size_t iv_size,
size_t buffer_size,
double *encryption_mbs,
double *decryption_mbs);
/**
* Get cipher used in device
*

View File

@@ -39,6 +39,7 @@ CRYPTSETUP_1.0 {
crypt_volume_key_verify;
crypt_status;
crypt_dump;
crypt_benchmark;
crypt_get_cipher;
crypt_get_cipher_mode;
crypt_get_uuid;

View File

@@ -168,7 +168,7 @@ struct device *crypt_data_device(struct crypt_device *cd)
return cd->device;
}
static int init_crypto(struct crypt_device *ctx)
int init_crypto(struct crypt_device *ctx)
{
int r;

217
lib/utils_benchmark.c Normal file
View File

@@ -0,0 +1,217 @@
/*
* libcryptsetup - cryptsetup library, cipher bechmark
*
* Copyright (C) 2012, Red Hat, Inc. All rights reserved.
* Copyright (C) 2012, Milan Broz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "internal.h"
/*
* This is not simulating storage, so using disk block causes extreme overhead.
* Let's use some fixed block size where results are more reliable...
*/
#define CIPHER_BLOCK_BYTES 65536
/*
* The whole test depends on Linux kernel usermode crypto API for now.
* (The same implementations are used in dm-crypt though.)
*/
struct cipher_perf {
char name[32];
char mode[32];
char *key;
size_t key_length;
char *iv;
size_t iv_length;
size_t buffer_size;
};
static long time_ms(struct rusage *start, struct rusage *end)
{
long ms = 0;
/* For kernel backend, we need to measure only tim in kernel.
ms = (end->ru_utime.tv_sec - start->ru_utime.tv_sec) * 1000;
ms += (end->ru_utime.tv_usec - start->ru_utime.tv_usec) / 1000;
*/
ms += (end->ru_stime.tv_sec - start->ru_stime.tv_sec) * 1000;
ms += (end->ru_stime.tv_usec - start->ru_stime.tv_usec) / 1000;
return ms;
}
static int cipher_perf_one(struct cipher_perf *cp, char *buf,
size_t buf_size, int enc)
{
struct crypt_cipher *cipher = NULL;
size_t done = 0, block = CIPHER_BLOCK_BYTES;
int r;
if (buf_size < block)
block = buf_size;
r = crypt_cipher_init(&cipher, cp->name, cp->mode, cp->key, cp->key_length);
if (r < 0) {
log_dbg("Cannot initialise cipher %s, mode %s.", cp->name, cp->mode);
return r;
}
while (done < buf_size) {
if ((done + block) > buf_size)
block = buf_size - done;
if (enc)
r = crypt_cipher_encrypt(cipher, &buf[done], &buf[done],
block, cp->iv, cp->iv_length);
else
r = crypt_cipher_decrypt(cipher, &buf[done], &buf[done],
block, cp->iv, cp->iv_length);
if (r < 0)
break;
done += block;
}
crypt_cipher_destroy(cipher);
return r;
}
static long cipher_measure(struct cipher_perf *cp, char *buf,
size_t buf_size, int encrypt)
{
struct rusage rstart, rend;
int r;
if (getrusage(RUSAGE_SELF, &rstart) < 0)
return -EINVAL;
r = cipher_perf_one(cp, buf, buf_size, encrypt);
if (r < 0)
return r;
if (getrusage(RUSAGE_SELF, &rend) < 0)
return -EINVAL;
return time_ms(&rstart, &rend);
}
static double speed_mbs(unsigned long bytes, unsigned long ms)
{
double speed = bytes, s = ms / 1000.;
return speed / (1024 * 1024) / s;
}
static int cipher_perf(struct cipher_perf *cp,
double *encryption_mbs, double *decryption_mbs)
{
long ms_enc, ms_dec, ms;
int repeat_enc, repeat_dec;
void *buf = NULL;
if (posix_memalign(&buf, crypt_getpagesize(), cp->buffer_size))
return -ENOMEM;
ms_enc = 0;
repeat_enc = 1;
while (ms_enc < 1000) {
ms = cipher_measure(cp, buf, cp->buffer_size, 1);
if (ms < 0) {
free(buf);
return (int)ms;
}
ms_enc += ms;
repeat_enc++;
}
ms_dec = 0;
repeat_dec = 1;
while (ms_dec < 1000) {
ms = cipher_measure(cp, buf, cp->buffer_size, 0);
if (ms < 0) {
free(buf);
return (int)ms;
}
ms_dec += ms;
repeat_dec++;
}
free(buf);
*encryption_mbs = speed_mbs(cp->buffer_size * repeat_enc, ms_enc);
*decryption_mbs = speed_mbs(cp->buffer_size * repeat_dec, ms_dec);
return 0;
}
int crypt_benchmark(struct crypt_device *cd,
const char *cipher,
const char *cipher_mode,
size_t volume_key_size,
size_t iv_size,
size_t buffer_size,
double *encryption_mbs,
double *decryption_mbs)
{
struct cipher_perf cp = {
.key_length = volume_key_size,
.iv_length = iv_size,
.buffer_size = buffer_size,
};
char *c;
int r;
if (!cipher || !cipher_mode || !volume_key_size)
return -EINVAL;
r = init_crypto(cd);
if (r < 0)
return r;
r = -ENOMEM;
if (iv_size) {
cp.iv = malloc(iv_size);
if (!cp.iv)
goto out;
crypt_random_get(cd, cp.iv, iv_size, CRYPT_RND_NORMAL);
}
cp.key = malloc(volume_key_size);
if (!cp.key)
goto out;
crypt_random_get(cd, cp.key, volume_key_size, CRYPT_RND_NORMAL);
strncpy(cp.name, cipher, sizeof(cp.name)-1);
strncpy(cp.mode, cipher_mode, sizeof(cp.mode)-1);
/* Ignore IV generator */
if ((c = strchr(cp.mode, '-')))
*c = '\0';
r = cipher_perf(&cp, encryption_mbs, decryption_mbs);
out:
free(cp.key);
free(cp.iv);
return r;
}

View File

@@ -362,6 +362,23 @@ Identical to \fIremove\fR.
.PP
See also section 7 of the FAQ and \fBhttp://loop-aes.sourceforge.net\fR
for more information regarding loop-AES.
.SH MISCELLANEOUS
.PP
\fIbenchmark\fR <options>
.IP
Benchmarks ciphers. Without parameters it tries to measure few common
configurations.
To benchmark other ciphers or modes, you need to specify \fB\-\-cipher\fR
and \fB\-\-key-size\fR options.
\fBNOTE:\fR This benchmark is using memory only and is only informative.
You cannot directly predict real storage encryption speed from it.
This benchmark requires kernel userspace crypto API interface to be available
(kernel af_alg and af_skcipher modules, introduced in Linux kernel 2.6.38).
\fB<options>\fR can be [\-\-cipher, \-\-key-size].
.SH OPTIONS
.TP
.B "\-\-verbose, \-v"

View File

@@ -60,6 +60,7 @@ static int action_create(int arg);
static int action_remove(int arg);
static int action_resize(int arg);
static int action_status(int arg);
static int action_benchmark(int arg);
static int action_luksFormat(int arg);
static int action_luksOpen(int arg);
static int action_luksAddKey(int arg);
@@ -89,6 +90,7 @@ static struct action_type {
{ "remove", action_remove, 0, 1, 1, N_("<name>"), N_("remove device") },
{ "resize", action_resize, 0, 1, 1, N_("<name>"), N_("resize active device") },
{ "status", action_status, 0, 1, 0, N_("<name>"), N_("show device status") },
{ "benchmark", action_benchmark, 0, 0, 0, N_("<name>"), N_("benchmark cipher") },
{ "repair", action_luksRepair, 0, 1, 1, N_("<device>"), N_("try to repair on-disk metadata") },
{ "luksFormat", action_luksFormat, 0, 1, 1, N_("<device> [<new key file>]"), N_("formats a LUKS device") },
{ "luksOpen", action_luksOpen, 0, 2, 1, N_("<device> <name> "), N_("open LUKS device as mapping <name>") },
@@ -346,6 +348,82 @@ out:
return r;
}
static int action_benchmark(int arg __attribute__((unused)))
{
static struct {
char *cipher;
char *mode;
size_t key_size;
size_t iv_size;
} bciphers[] = {
{ "aes", "cbc", 16, 16 },
{ "serpent", "cbc", 16, 16 },
{ "twofish", "cbc", 16, 16 },
{ "aes", "cbc", 32, 16 },
{ "serpent", "cbc", 32, 16 },
{ "twofish", "cbc", 32, 16 },
{ "aes", "xts", 32, 16 },
{ "serpent", "xts", 32, 16 },
{ "twofish", "xts", 32, 16 },
{ "aes", "xts", 64, 16 },
{ "serpent", "xts", 64, 16 },
{ "twofish", "xts", 64, 16 },
{ NULL, NULL, 0, 0 }
};
char *header = "# Tests are approximate using memory only (no storage IO).\n"
"# Algorithm | Key | Encryption | Decryption\n";
char cipher[MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN];
double enc_mbr = 0, dec_mbr = 0;
int key_size = (opt_key_size ?: DEFAULT_PLAIN_KEYBITS);
int iv_size = 16;
int buffer_size = 1024 * 1024;
char *c;
int i, r;
if (opt_cipher) {
r = crypt_parse_name_and_mode(opt_cipher, cipher, NULL, cipher_mode);
if (r < 0) {
log_err(_("No known cipher specification pattern detected.\n"));
return r;
}
if ((c = strchr(cipher_mode, '-')))
*c = '\0';
/* FIXME: not really clever :) */
if (strstr(cipher, "des"))
iv_size = 8;
r = crypt_benchmark(NULL, cipher, cipher_mode,
key_size / 8, iv_size, buffer_size,
&enc_mbr, &dec_mbr);
if (!r) {
log_std("%s", header);
strncat(cipher, "-", MAX_CIPHER_LEN);
strncat(cipher, cipher_mode, MAX_CIPHER_LEN);
log_std("%11s %4db %5.1f MiB/s %5.1f MiB/s\n",
cipher, key_size, enc_mbr, dec_mbr);
} else
log_err(_("Cannot benchmark %s.\n"), cipher);
} else {
log_std("%s", header);
for (i = 0; bciphers[i].cipher; i++) {
r = crypt_benchmark(NULL, bciphers[i].cipher, bciphers[i].mode,
bciphers[i].key_size, bciphers[i].iv_size,
buffer_size, &enc_mbr, &dec_mbr);
snprintf(cipher, MAX_CIPHER_LEN, "%s-%s",
bciphers[i].cipher, bciphers[i].mode);
if (!r)
log_std("%11s %4db %5.1f MiB/s %5.1f MiB/s\n",
cipher, bciphers[i].key_size*8, enc_mbr, dec_mbr);
else
log_std("%11s %4db %12s %12s\n", cipher,
bciphers[i].key_size*8, _("N/A"), _("N/A"));
}
}
return r;
}
static int _read_mk(const char *file, char **key, int keysize)
{
int fd;
@@ -1221,9 +1299,10 @@ int main(int argc, const char **argv)
if (opt_key_size &&
strcmp(aname, "luksFormat") &&
strcmp(aname, "create") &&
strcmp(aname, "loopaesOpen"))
strcmp(aname, "loopaesOpen") &&
strcmp(aname, "benchmark"))
usage(popt_context, EXIT_FAILURE,
_("Option --key-size is allowed only for luksFormat, create and loopaesOpen.\n"
_("Option --key-size is allowed only for luksFormat, create, loopaesOpen and benchmark.\n"
"To limit read from keyfile use --keyfile-size=(bytes)."),
poptGetInvocationName(popt_context));