Add a handshake performance test
authorMatt Caswell <matt@openssl.org>
Wed, 17 May 2023 15:27:46 +0000 (16:27 +0100)
committerMatt Caswell <matt@openssl.org>
Mon, 29 May 2023 10:43:59 +0000 (11:43 +0100)
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/tools/pull/146)

12 files changed:
perf/Makefile
perf/handshake.c [new file with mode: 0644]
perf/libperf.a [deleted file]
perf/perfhelper.o [deleted file]
perf/perflib/perfhelper.c
perf/perflib/perflib.h
perf/perflib/perfsslhelper.c [new file with mode: 0644]
perf/perflib/threads.c
perf/randbytes [deleted file]
perf/randbytes.c
perf/threads.o [deleted file]
perf/time.o [deleted file]

index 27882b4b8df1672ccc24ad3431b26628bdf2c41b..b70f5a434e5b1cc969370d5138a2d757b583cd1e 100644 (file)
@@ -1,7 +1,7 @@
-all: randbytes
+all: randbytes handshake
 
 clean:
-       rm libperf.a *.o randbytes
+       rm libperf.a *.o randbytes handshake
 
 libperf.a: perflib/*.c perflib/*.h
        gcc -I$(TARGET_OSSL_INCLUDE_PATH) -I. -c perflib/*.c
@@ -9,3 +9,6 @@ libperf.a: perflib/*.c perflib/*.h
 
 randbytes:     randbytes.c libperf.a
        gcc -L$(TARGET_OSSL_LIBRARY_PATH) -L. -I$(TARGET_OSSL_INCLUDE_PATH) -I. -o randbytes randbytes.c -lperf -lcrypto
+
+handshake: handshake.c libperf.a
+       gcc -L$(TARGET_OSSL_LIBRARY_PATH) -L. -I$(TARGET_OSSL_INCLUDE_PATH) -I. -o handshake handshake.c -lperf -lcrypto -lssl
diff --git a/perf/handshake.c b/perf/handshake.c
new file mode 100644 (file)
index 0000000..358d95e
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <openssl/ssl.h>
+#include "perflib/perflib.h"
+
+#define NUM_HANDSHAKES_PER_THREAD         1000
+
+int err = 0;
+
+static SSL_CTX *sctx = NULL, *cctx = NULL;
+
+OSSL_TIME *times;
+
+static void do_handshake(size_t num)
+{
+    SSL *clientssl = NULL, *serverssl = NULL;
+    int ret = 1;
+    int i;
+    OSSL_TIME start, end;
+
+    start = ossl_time_now();
+
+    for (i = 0; i < NUM_HANDSHAKES_PER_THREAD; i++) {
+        ret = perflib_create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+                                         NULL, NULL);
+        ret &= perflib_create_ssl_connection(serverssl, clientssl,
+                                             SSL_ERROR_NONE);
+        perflib_shutdown_ssl_connection(serverssl, clientssl);
+        serverssl = clientssl = NULL;
+    }
+
+    end = ossl_time_now();
+    times[num] = ossl_time_subtract(end, start);
+
+    if (!ret)
+        err = 1;
+}
+
+int main(int argc, char *argv[])
+{
+    int threadcount;
+    double persec;
+    OSSL_TIME duration, av;
+    uint64_t us;
+    double avcalltime;
+    char *cert;
+    char *privkey;
+    int ret = EXIT_FAILURE;
+    int i;
+
+    if (argc != 3) {
+        printf("Usage: handshake certsdir threadcount\n");
+        return EXIT_FAILURE;
+    }
+
+    threadcount = atoi(argv[2]);
+    if (threadcount < 1) {
+        printf("threadcount must be > 0\n");
+        return EXIT_FAILURE;
+    }
+
+    cert = perflib_mk_file_path(argv[1], "servercert.pem");
+    privkey = perflib_mk_file_path(argv[1], "serverkey.pem");
+    if (cert == NULL || privkey == NULL) {
+        printf("Failed to allocate cert/privkey\n");
+        goto err;
+    }
+
+    times = OPENSSL_malloc(sizeof(OSSL_TIME) * threadcount);
+    if (times == NULL) {
+        printf("Failed to create times array\n");
+        goto err;
+    }
+
+    if (!perflib_create_ssl_ctx_pair(TLS_server_method(), TLS_client_method(),
+                                     0, 0, &sctx, &cctx, cert, privkey)) {
+        printf("Failed to create SSL_CTX pair\n");
+        goto err;
+    }
+
+    if (!perflib_run_multi_thread_test(do_handshake, threadcount, &duration)) {
+        printf("Failed to run the test\n");
+        goto err;
+    }
+
+    if (err) {
+        printf("Error during test\n");
+        goto err;
+    }
+
+    av = times[0];
+    for (i = 1; i < threadcount; i++)
+        av = ossl_time_add(av, times[i]);
+    av = ossl_time_divide(av, NUM_HANDSHAKES_PER_THREAD * threadcount);
+
+    persec = ((NUM_HANDSHAKES_PER_THREAD * threadcount * OSSL_TIME_SECOND)
+             / (double)ossl_time2ticks(duration));
+
+    printf("Average time per handshake: %ldus\n", ossl_time2us(av));
+    printf("Handshakes per second: %lf\n", persec);
+
+    ret = EXIT_SUCCESS;
+ err:
+    OPENSSL_free(cert);
+    OPENSSL_free(privkey);
+    OPENSSL_free(times);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return ret;
+}
diff --git a/perf/libperf.a b/perf/libperf.a
deleted file mode 100644 (file)
index f4d4fb0..0000000
Binary files a/perf/libperf.a and /dev/null differ
diff --git a/perf/perfhelper.o b/perf/perfhelper.o
deleted file mode 100644 (file)
index 2913696..0000000
Binary files a/perf/perfhelper.o and /dev/null differ
index 2a71cbcd65a53c0fe36ff9959a86a18e9c0d74f7..e3e3284b525c1970e3d617f89b59b960768ba8b2 100644 (file)
@@ -7,32 +7,27 @@
  * https://www.openssl.org/source/license.html
  */
 
+#include <string.h>
 #include <openssl/crypto.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
 #include "perflib/perflib.h"
 
-int perflib_run_multi_thread_test(void (*f)(void), size_t threadcount,
-                                  OSSL_TIME *duration)
+char *perflib_mk_file_path(const char *dir, const char *file)
 {
-    OSSL_TIME start, end;
-    thread_t *threads;
-    size_t i;
-
-    threads = OPENSSL_malloc(sizeof(*threads) * threadcount);
-    if (threads == NULL)
-        return 0;
-
-    start = ossl_time_now();
-
-    for (i = 0; i < threadcount; i++)
-        perflib_run_thread(&threads[i], f);
-
-    for (i = 0; i < threadcount; i++)
-        perflib_wait_for_thread(threads[i]);
-
-    end = ossl_time_now();
-    OPENSSL_free(threads);
-
-    *duration = ossl_time_subtract(end, start);
-
-    return 1;
+    const char *sep = "/";
+    size_t dirlen = dir != NULL ? strlen(dir) : 0;
+    size_t len = dirlen + strlen(sep) + strlen(file) + 1;
+    char *full_file = OPENSSL_zalloc(len);
+
+    if (full_file != NULL) {
+        if (dir != NULL && dirlen > 0) {
+            OPENSSL_strlcpy(full_file, dir, len);
+            OPENSSL_strlcat(full_file, sep, len);
+        }
+        OPENSSL_strlcat(full_file, file, len);
+    }
+
+    return full_file;
 }
index 0a92c2ff444a4499f43099f0ca8bc37c76d93b17..d0570ab73d7d2ee7ad831d922999e8824f6e1ed8 100644 (file)
@@ -12,6 +12,8 @@
 # pragma once
 
 #include <stdlib.h>
+#include <openssl/ssl.h>
+#include <openssl/bio.h>
 #include "perflib/time.h"
 
 # if defined(_WIN32)
@@ -28,9 +30,19 @@ typedef pthread_t thread_t;
 
 # endif
 
-int perflib_run_thread(thread_t *t, void (*f)(void));
-int perflib_wait_for_thread(thread_t thread);
-int perflib_run_multi_thread_test(void (*f)(void), size_t threadcount,
+int perflib_run_multi_thread_test(void (*f)(size_t), size_t threadcount,
                                   OSSL_TIME *duration);
+char *perflib_mk_file_path(const char *dir, const char *file);
+
+int perflib_create_ssl_ctx_pair(const SSL_METHOD *sm, const SSL_METHOD *cm,
+                                int min_proto_version, int max_proto_version,
+                                SSL_CTX **sctx, SSL_CTX **cctx, char *certfile,
+                                char *privkeyfile);
+int perflib_create_ssl_objects(SSL_CTX *serverctx, SSL_CTX *clientctx,
+                               SSL **sssl, SSL **cssl, BIO *s_to_c_fbio,
+                               BIO *c_to_s_fbio);
+int perflib_create_bare_ssl_connection(SSL *serverssl, SSL *clientssl, int want);
+int perflib_create_ssl_connection(SSL *serverssl, SSL *clientssl, int want);
+void perflib_shutdown_ssl_connection(SSL *serverssl, SSL *clientssl);
 
 #endif
diff --git a/perf/perflib/perfsslhelper.c b/perf/perflib/perfsslhelper.c
new file mode 100644 (file)
index 0000000..0422dca
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+#include <openssl/crypto.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include "perflib/perflib.h"
+
+int perflib_create_ssl_ctx_pair(const SSL_METHOD *sm,
+                                const SSL_METHOD *cm, int min_proto_version,
+                                int max_proto_version, SSL_CTX **sctx,
+                                SSL_CTX **cctx, char *certfile,
+                                char *privkeyfile)
+{
+    SSL_CTX *serverctx = NULL;
+    SSL_CTX *clientctx = NULL;
+
+    if (sctx != NULL) {
+        if (*sctx != NULL)
+            serverctx = *sctx;
+        else if ((serverctx = SSL_CTX_new(sm)) == NULL)
+            goto err;
+    }
+
+    if (cctx != NULL) {
+        if (*cctx != NULL)
+            clientctx = *cctx;
+        else if ((clientctx = SSL_CTX_new(cm)) == NULL)
+            goto err;
+    }
+
+    if (serverctx != NULL
+            && ((min_proto_version > 0
+                 && !SSL_CTX_set_min_proto_version(serverctx,
+                                                   min_proto_version))
+                || (max_proto_version > 0
+                    && !SSL_CTX_set_max_proto_version(serverctx,
+                                                      max_proto_version))))
+        goto err;
+
+    if (clientctx != NULL
+        && ((min_proto_version > 0
+             && !SSL_CTX_set_min_proto_version(clientctx,
+                                               min_proto_version))
+            || (max_proto_version > 0
+                && !SSL_CTX_set_max_proto_version(clientctx,
+                                                  max_proto_version))))
+        goto err;
+
+    if (serverctx != NULL && certfile != NULL && privkeyfile != NULL) {
+        if (SSL_CTX_use_certificate_file(serverctx, certfile,
+                                         SSL_FILETYPE_PEM) != 1
+                || SSL_CTX_use_PrivateKey_file(serverctx, privkeyfile,
+                                               SSL_FILETYPE_PEM) != 1
+                || SSL_CTX_check_private_key(serverctx) != 1)
+            goto err;
+    }
+
+    if (sctx != NULL)
+        *sctx = serverctx;
+    if (cctx != NULL)
+        *cctx = clientctx;
+    return 1;
+
+ err:
+    if (sctx != NULL && *sctx == NULL)
+        SSL_CTX_free(serverctx);
+    if (cctx != NULL && *cctx == NULL)
+        SSL_CTX_free(clientctx);
+    return 0;
+}
+
+/*
+ * NOTE: Transfers control of the BIOs - this function will free them on error.
+ * There is no DTLS support at this stage.
+ */
+int perflib_create_ssl_objects(SSL_CTX *serverctx, SSL_CTX *clientctx,
+                               SSL **sssl, SSL **cssl, BIO *s_to_c_fbio,
+                               BIO *c_to_s_fbio)
+{
+    SSL *serverssl = NULL, *clientssl = NULL;
+    BIO *s_to_c_bio = NULL, *c_to_s_bio = NULL;
+
+    if (*sssl != NULL)
+        serverssl = *sssl;
+    else if ((serverssl = SSL_new(serverctx)) == NULL)
+        goto error;
+    if (*cssl != NULL)
+        clientssl = *cssl;
+    else if ((clientssl = SSL_new(clientctx)) == NULL)
+        goto error;
+
+    if ((s_to_c_bio = BIO_new(BIO_s_mem())) == NULL
+            || (c_to_s_bio = BIO_new(BIO_s_mem())) == NULL)
+        goto error;
+
+    if (s_to_c_fbio != NULL
+            && (s_to_c_bio = BIO_push(s_to_c_fbio, s_to_c_bio)) == NULL)
+        goto error;
+    if (c_to_s_fbio != NULL
+            && (c_to_s_bio = BIO_push(c_to_s_fbio, c_to_s_bio)) == NULL)
+        goto error;
+
+    /* Set Non-blocking IO behaviour */
+    BIO_set_mem_eof_return(s_to_c_bio, -1);
+    BIO_set_mem_eof_return(c_to_s_bio, -1);
+
+    /* Up ref these as we are passing them to two SSL objects */
+    SSL_set_bio(serverssl, c_to_s_bio, s_to_c_bio);
+    BIO_up_ref(s_to_c_bio);
+    BIO_up_ref(c_to_s_bio);
+    SSL_set_bio(clientssl, s_to_c_bio, c_to_s_bio);
+    *sssl = serverssl;
+    *cssl = clientssl;
+    return 1;
+
+ error:
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    BIO_free(s_to_c_bio);
+    BIO_free(c_to_s_bio);
+    BIO_free(s_to_c_fbio);
+    BIO_free(c_to_s_fbio);
+
+    return 0;
+}
+
+#define MAXLOOPS    1000000
+
+/*
+ * Create an SSL connection, but does not read any post-handshake
+ * NewSessionTicket messages.
+ * We stop the connection attempt (and return a failure value) if either peer
+ * has SSL_get_error() return the value in the |want| parameter. The connection
+ * attempt could be restarted by a subsequent call to this function.
+ */
+int perflib_create_bare_ssl_connection(SSL *serverssl, SSL *clientssl, int want)
+{
+    int retc = -1, rets = -1, err, abortctr = 0, ret = 0;
+    int clienterr = 0, servererr = 0;
+
+    do {
+        err = SSL_ERROR_WANT_WRITE;
+        while (!clienterr && retc <= 0 && err == SSL_ERROR_WANT_WRITE) {
+            retc = SSL_connect(clientssl);
+            if (retc <= 0)
+                err = SSL_get_error(clientssl, retc);
+        }
+
+        if (!clienterr && retc <= 0 && err != SSL_ERROR_WANT_READ) {
+            printf("SSL_connect() failed %d, %d", retc, err);
+            if (want != SSL_ERROR_SSL)
+                ERR_print_errors_fp(stdout);
+            clienterr = 1;
+        }
+        if (want != SSL_ERROR_NONE && err == want)
+            goto err;
+
+        err = SSL_ERROR_WANT_WRITE;
+        while (!servererr && rets <= 0 && err == SSL_ERROR_WANT_WRITE) {
+            rets = SSL_accept(serverssl);
+            if (rets <= 0)
+                err = SSL_get_error(serverssl, rets);
+        }
+
+        if (!servererr && rets <= 0
+                && err != SSL_ERROR_WANT_READ
+                && err != SSL_ERROR_WANT_X509_LOOKUP) {
+            printf("SSL_accept() failed %d, %d", rets, err);
+            if (want != SSL_ERROR_SSL)
+                ERR_print_errors_fp(stdout);
+            servererr = 1;
+        }
+        if (want != SSL_ERROR_NONE && err == want)
+            goto err;
+        if (clienterr && servererr)
+            goto err;
+        if (++abortctr == MAXLOOPS) {
+            printf("No progress made");
+            goto err;
+        }
+    } while (retc <=0 || rets <= 0);
+
+    ret = 1;
+ err:
+    return ret;
+}
+
+/*
+ * Create an SSL connection including any post handshake NewSessionTicket
+ * messages.
+ */
+int perflib_create_ssl_connection(SSL *serverssl, SSL *clientssl, int want)
+{
+    int i;
+    unsigned char buf;
+    size_t readbytes;
+
+    if (!perflib_create_bare_ssl_connection(serverssl, clientssl, want))
+        return 0;
+
+    /*
+     * We attempt to read some data on the client side which we expect to fail.
+     * This will ensure we have received the NewSessionTicket in TLSv1.3 where
+     * appropriate. We do this twice because there are 2 NewSessionTickets.
+     */
+    for (i = 0; i < 2; i++) {
+        if (SSL_read_ex(clientssl, &buf, sizeof(buf), &readbytes) > 0) {
+            if (readbytes != 0) {
+                printf("Unexpected data reading ticket\n");
+                return 0;
+            }
+        } else if (SSL_get_error(clientssl, 0) != SSL_ERROR_WANT_READ) {
+            printf("Unexpected error reading ticket\n");
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+void perflib_shutdown_ssl_connection(SSL *serverssl, SSL *clientssl)
+{
+    SSL_shutdown(clientssl);
+    SSL_shutdown(serverssl);
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+}
index 9e46ce81a25e46b96f288d784d7e3e28b0e50738..e1148a83d13932574671f9fa0fc176e84a9967b7 100644 (file)
@@ -9,21 +9,25 @@
 
 #include "perflib/perflib.h"
 
+struct thread_arg_st {
+    void (*func)(size_t num);
+    size_t num;
+};
+
 #if defined(_WIN32)
 
-static DWORD WINAPI thread_run(LPVOID arg)
+static DWORD WINAPI thread_run(LPVOID varg)
 {
-    void (*f)(void);
+    struct thread_arg_st *arg = varg;
 
-    *(void **) (&f) = arg;
+    arg->func(arg->num);
 
-    f();
     return 0;
 }
 
-int perflib_run_thread(thread_t *t, void (*f)(void))
+int perflib_run_thread(thread_t *t, struct thread_arg_st *arg)
 {
-    *t = CreateThread(NULL, 0, thread_run, *(void **) &f, 0, NULL);
+    *t = CreateThread(NULL, 0, thread_run, arg, 0, NULL);
     return *t != NULL;
 }
 
@@ -34,19 +38,18 @@ int perflib_wait_for_thread(thread_t thread)
 
 #else
 
-static void *thread_run(void *arg)
+static void *thread_run(void *varg)
 {
-    void (*f)(void);
+    struct thread_arg_st *arg = varg;
 
-    *(void **) (&f) = arg;
+    arg->func(arg->num);
 
-    f();
     return NULL;
 }
 
-int perflib_run_thread(thread_t *t, void (*f)(void))
+int perflib_run_thread(thread_t *t, struct thread_arg_st *arg)
 {
-    return pthread_create(t, NULL, thread_run, *(void **) &f) == 0;
+    return pthread_create(t, NULL, thread_run, arg) == 0;
 }
 
 int perflib_wait_for_thread(thread_t thread)
@@ -55,3 +58,41 @@ int perflib_wait_for_thread(thread_t thread)
 }
 
 #endif
+
+int perflib_run_multi_thread_test(void (*f)(size_t), size_t threadcount,
+                                  OSSL_TIME *duration)
+{
+    OSSL_TIME start, end;
+    thread_t *threads;
+    size_t i;
+    struct thread_arg_st *args;
+
+    threads = OPENSSL_malloc(sizeof(*threads) * threadcount);
+    if (threads == NULL)
+        return 0;
+
+    args = OPENSSL_malloc(sizeof(*args) * threadcount);
+    if (args == NULL) {
+        OPENSSL_free(threads);
+        return 0;
+    }
+
+    start = ossl_time_now();
+
+    for (i = 0; i < threadcount; i++) {
+        args[i].func = f;
+        args[i].num = i;
+        perflib_run_thread(&threads[i], &args[i]);
+    }
+
+    for (i = 0; i < threadcount; i++)
+        perflib_wait_for_thread(threads[i]);
+
+    end = ossl_time_now();
+    OPENSSL_free(threads);
+    OPENSSL_free(args);
+
+    *duration = ossl_time_subtract(end, start);
+
+    return 1;
+}
diff --git a/perf/randbytes b/perf/randbytes
deleted file mode 100755 (executable)
index b7d068f..0000000
Binary files a/perf/randbytes and /dev/null differ
index 6578dd7557c7787cf76ffecbcaa8bfa8eeee6cf9..09b8f0a6b768ade4f6bc6af997e65b89e3ddae1c 100644 (file)
@@ -19,7 +19,7 @@
 
 int err = 0;
 
-void do_randbytes(void)
+void do_randbytes(size_t num)
 {
     int i;
     unsigned char buf[32];
diff --git a/perf/threads.o b/perf/threads.o
deleted file mode 100644 (file)
index 0154848..0000000
Binary files a/perf/threads.o and /dev/null differ
diff --git a/perf/time.o b/perf/time.o
deleted file mode 100644 (file)
index 8b5b787..0000000
Binary files a/perf/time.o and /dev/null differ