Add simple cipher benchmarking.
authorMilan Broz <gmazyland@gmail.com>
Thu, 8 Nov 2012 15:36:00 +0000 (16:36 +0100)
committerMilan Broz <gmazyland@gmail.com>
Mon, 19 Nov 2012 20:22:43 +0000 (21:22 +0100)
lib/Makefile.am
lib/internal.h
lib/libcryptsetup.h
lib/libcryptsetup.sym
lib/setup.c
lib/utils_benchmark.c [new file with mode: 0644]
man/cryptsetup.8
src/cryptsetup.c

index 0b95c62..3ff7002 100644 (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                            \
index 1b16aa3..f621ca0 100644 (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)
index a25e08c..443ba9f 100644 (file)
@@ -850,6 +850,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
  *
  * @param cd crypt device handle
index ec9cec0..78bc281 100644 (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;
index ee9e9ad..e47ae49 100644 (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;
 
diff --git a/lib/utils_benchmark.c b/lib/utils_benchmark.c
new file mode 100644 (file)
index 0000000..850ac7c
--- /dev/null
@@ -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;
+}
index 84fdffc..a1dd37f 100644 (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"
index 5927844..7a0289f 100644 (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));