diff --git a/lib/Makefile.am b/lib/Makefile.am index 0b95c624..3ff70029 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -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 \ diff --git a/lib/internal.h b/lib/internal.h index 1b16aa3d..f621ca06 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -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) diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index a25e08c7..443ba9f8 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -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 * diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index ec9cec04..78bc2819 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -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; diff --git a/lib/setup.c b/lib/setup.c index ee9e9ad4..e47ae494 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -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; diff --git a/lib/utils_benchmark.c b/lib/utils_benchmark.c new file mode 100644 index 00000000..850ac7cb --- /dev/null +++ b/lib/utils_benchmark.c @@ -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 +#include +#include +#include + +#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; +} diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 84fdffc7..a1dd37fa 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -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 +.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\fR can be [\-\-cipher, \-\-key-size]. .SH OPTIONS .TP .B "\-\-verbose, \-v" diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 59278442..7a0289fe 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -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_(""), N_("remove device") }, { "resize", action_resize, 0, 1, 1, N_(""), N_("resize active device") }, { "status", action_status, 0, 1, 0, N_(""), N_("show device status") }, + { "benchmark", action_benchmark, 0, 0, 0, N_(""), N_("benchmark cipher") }, { "repair", action_luksRepair, 0, 1, 1, N_(""), N_("try to repair on-disk metadata") }, { "luksFormat", action_luksFormat, 0, 1, 1, N_(" []"), N_("formats a LUKS device") }, { "luksOpen", action_luksOpen, 0, 2, 1, N_(" "), N_("open LUKS device as mapping ") }, @@ -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));