diff options
author | Hugo Landau <hlandau@openssl.org> | 2023-04-28 17:56:33 +0200 |
---|---|---|
committer | Hugo Landau <hlandau@openssl.org> | 2023-05-24 11:34:47 +0200 |
commit | a350db7318cba3566014ac02a5caa5f4884f00ba (patch) | |
tree | cff8759f813607c58782c120c67f17e88f4afb21 | |
parent | QUIC TSERVER: Allow detection of new incoming streams (diff) | |
download | openssl-a350db7318cba3566014ac02a5caa5f4884f00ba.tar.xz openssl-a350db7318cba3566014ac02a5caa5f4884f00ba.zip |
QUIC MSMT: Revise tests to support multithreading
Alsoo rename OPK_C_ACCEPT_STREAM to reflect its current behaviour.
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20856)
-rw-r--r-- | test/quic_multistream_test.c | 361 |
1 files changed, 303 insertions, 58 deletions
diff --git a/test/quic_multistream_test.c b/test/quic_multistream_test.c index 2fd8a2235f..1da6ef31fe 100644 --- a/test/quic_multistream_test.c +++ b/test/quic_multistream_test.c @@ -13,9 +13,25 @@ #include "internal/quic_tserver.h" #include "internal/quic_ssl.h" #include "testutil.h" +#if defined(OPENSSL_THREADS) +# include "internal/thread_arch.h" +#endif static const char *certfile, *keyfile; +#if defined(OPENSSL_THREADS) +struct child_thread_args { + struct helper *h; + const struct script_op *script; + int thread_idx; + + CRYPTO_THREAD *t; + CRYPTO_MUTEX *m; + int testresult; + int done; +}; +#endif + typedef struct stream_info { const char *name; SSL *c_stream; @@ -37,11 +53,22 @@ struct helper { SSL *c_conn; LHASH_OF(STREAM_INFO) *c_streams; +#if defined(OPENSSL_THREADS) + struct child_thread_args *threads; + size_t num_threads; +#endif + OSSL_TIME start_time; int init, blocking, check_spin_again; int free_order; }; +struct helper_local { + struct helper *h; + LHASH_OF(STREAM_INFO) *c_streams; + int thread_idx; +}; + struct script_op { uint32_t op; const void *arg0; @@ -67,7 +94,7 @@ struct script_op { #define OPK_C_ATTACH 13 #define OPK_C_NEW_STREAM 14 #define OPK_S_NEW_STREAM 15 -#define OPK_C_ACCEPT_STREAM 16 +#define OPK_C_ACCEPT_STREAM_WAIT 16 #define OPK_C_ACCEPT_STREAM_NONE 17 #define OPK_C_FREE_STREAM 18 #define OPK_C_SET_DEFAULT_STREAM_MODE 19 @@ -81,6 +108,8 @@ struct script_op { #define OPK_S_WRITE_FAIL 27 #define OPK_C_READ_FAIL 28 #define OPK_C_STREAM_RESET 29 +#define OPK_S_ACCEPT_STREAM_WAIT 30 +#define OPK_NEW_THREAD 31 #define EXPECT_CONN_CLOSE_APP (1U << 0) #define EXPECT_CONN_CLOSE_REMOTE (1U << 1) @@ -94,6 +123,8 @@ struct script_op { #define S_UNI_ID(ordinal) \ (((ordinal) << 2) | QUIC_STREAM_INITIATOR_SERVER | QUIC_STREAM_DIR_UNI) +#define ANY_ID UINT64_MAX + #define OP_END \ {OPK_END} #define OP_CHECK(func, arg2) \ @@ -130,8 +161,8 @@ struct script_op { {OPK_S_NEW_STREAM, NULL, 0, NULL, #stream_name, (expect_id)}, #define OP_S_NEW_STREAM_UNI(stream_name, expect_id) \ {OPK_S_NEW_STREAM, NULL, 1, NULL, #stream_name, (expect_id)}, -#define OP_C_ACCEPT_STREAM(stream_name) \ - {OPK_C_ACCEPT_STREAM, NULL, 0, NULL, #stream_name}, +#define OP_C_ACCEPT_STREAM_WAIT(stream_name) \ + {OPK_C_ACCEPT_STREAM_WAIT, NULL, 0, NULL, #stream_name}, #define OP_C_ACCEPT_STREAM_NONE() \ {OPK_C_ACCEPT_STREAM_NONE, NULL, 0, NULL, NULL}, #define OP_C_FREE_STREAM(stream_name) \ @@ -164,6 +195,10 @@ struct script_op { {OPK_C_READ_FAIL, NULL, 0, NULL, #stream_name}, #define OP_C_STREAM_RESET(stream_name, aec) \ {OPK_C_STREAM_RESET, NULL, 0, NULL, #stream_name, (aec)}, +#define OP_S_ACCEPT_STREAM_WAIT(stream_name) \ + {OPK_S_ACCEPT_STREAM_WAIT, NULL, 0, NULL, #stream_name}, +#define OP_NEW_THREAD(num_threads, script) \ + { OPK_NEW_THREAD, (script), (num_threads), NULL, NULL, 0 }, static int check_rejected(struct helper *h, const struct script_op *op) { @@ -228,8 +263,43 @@ static void helper_cleanup_streams(LHASH_OF(STREAM_INFO) **lh) *lh = NULL; } +#if defined(OPENSSL_THREADS) +static CRYPTO_THREAD_RETVAL run_script_child_thread(void *arg); + +static int join_threads(struct child_thread_args *threads, size_t num_threads) +{ + int ok = 1; + size_t i; + CRYPTO_THREAD_RETVAL rv; + + for (i = 0; i < num_threads; ++i) { + if (threads[i].t != NULL) { + ossl_crypto_thread_native_join(threads[i].t, &rv); + + if (!threads[i].testresult) + /* Do not log failure here, worker will do it. */ + ok = 0; + + ossl_crypto_thread_native_clean(threads[i].t); + threads[i].t = NULL; + } + + ossl_crypto_mutex_free(&threads[i].m); + } + + return ok; +} +#endif + static void helper_cleanup(struct helper *h) { +#if defined(OPENSSL_THREADS) + join_threads(h->threads, h->num_threads); + OPENSSL_free(h->threads); + h->threads = NULL; + h->num_threads = 0; +#endif + if (h->free_order == 0) { /* order 0: streams, then conn */ helper_cleanup_streams(&h->c_streams); @@ -367,6 +437,38 @@ err: return 0; } +static int helper_local_init(struct helper_local *hl, struct helper *h, + int thread_idx) +{ + hl->h = h; + hl->c_streams = NULL; + hl->thread_idx = thread_idx; + + if (!TEST_ptr(h)) + return 0; + + if (thread_idx < 0) { + hl->c_streams = h->c_streams; + } else { + if (!TEST_ptr(hl->c_streams = lh_STREAM_INFO_new(stream_info_hash, + stream_info_cmp))) + return 0; + } + + return 1; +} + +static void helper_local_cleanup(struct helper_local *hl) +{ + if (hl->h == NULL) + return; + + if (hl->thread_idx >= 0) + helper_cleanup_streams(&hl->c_streams); + + hl->h = NULL; +} + static STREAM_INFO *get_stream_info(LHASH_OF(STREAM_INFO) *lh, const char *stream_name) { @@ -393,10 +495,11 @@ static STREAM_INFO *get_stream_info(LHASH_OF(STREAM_INFO) *lh, return info; } -static int helper_set_c_stream(struct helper *h, const char *stream_name, - SSL *c_stream) +static int helper_local_set_c_stream(struct helper_local *hl, + const char *stream_name, + SSL *c_stream) { - STREAM_INFO *info = get_stream_info(h->c_streams, stream_name); + STREAM_INFO *info = get_stream_info(hl->c_streams, stream_name); if (info == NULL) return 0; @@ -406,14 +509,15 @@ static int helper_set_c_stream(struct helper *h, const char *stream_name, return 1; } -static SSL *helper_get_c_stream(struct helper *h, const char *stream_name) +static SSL *helper_local_get_c_stream(struct helper_local *hl, + const char *stream_name) { STREAM_INFO *info; if (!strcmp(stream_name, "DEFAULT")) - return h->c_conn; + return hl->h->c_conn; - info = get_stream_info(h->c_streams, stream_name); + info = get_stream_info(hl->c_streams, stream_name); if (info == NULL) return NULL; @@ -459,25 +563,26 @@ static int is_want(SSL *s, int ret) return ec == SSL_ERROR_WANT_READ || ec == SSL_ERROR_WANT_WRITE; } -static int run_script(const struct script_op *script, int free_order) +static int run_script_worker(struct helper *h, const struct script_op *script, + int thread_idx) { - size_t op_idx = 0; int testresult = 0; - int no_advance = 0, first = 1; - const struct script_op *op = NULL; - struct helper h; unsigned char *tmp_buf = NULL; int connect_started = 0; - OSSL_TIME op_start_time = ossl_time_zero(), op_deadline = ossl_time_zero(); size_t offset = 0; + size_t op_idx = 0; + const struct script_op *op = NULL; + int no_advance = 0, first = 1, end_wait_warning = 0; + OSSL_TIME op_start_time = ossl_time_zero(), op_deadline = ossl_time_zero(); + struct helper_local hl; - if (!TEST_true(helper_init(&h, free_order))) + if (!TEST_true(helper_local_init(&hl, h, thread_idx))) goto out; #define SPIN_AGAIN() { no_advance = 1; continue; } for (;;) { - SSL *c_tgt = h.c_conn; + SSL *c_tgt = h->c_conn; uint64_t s_stream_id = UINT64_MAX; if (no_advance) { @@ -493,31 +598,79 @@ static int run_script(const struct script_op *script, int free_order) } if (!TEST_int_le(ossl_time_compare(ossl_time_now(), op_deadline), 0)) { - TEST_error("op %zu timed out", op_idx + 1); + TEST_error("op %zu timed out on thread %d", op_idx + 1, thread_idx); goto out; } op = &script[op_idx]; if (op->stream_name != NULL) { - c_tgt = helper_get_c_stream(&h, op->stream_name); - s_stream_id = helper_get_s_stream(&h, op->stream_name); + c_tgt = helper_local_get_c_stream(&hl, op->stream_name); + if (thread_idx < 0) + s_stream_id = helper_get_s_stream(h, op->stream_name); + else + s_stream_id = UINT64_MAX; + } + + if (thread_idx < 0) { + ossl_quic_tserver_tick(h->s); + if (connect_started) + SSL_tick(h->c_conn); } - ossl_quic_tserver_tick(h.s); - if (connect_started) - SSL_tick(h.c_conn); + if (thread_idx >= 0) { + /* Only allow certain opcodes on child threads. */ + switch (op->op) { + case OPK_END: + case OPK_C_ACCEPT_STREAM_WAIT: + case OPK_C_NEW_STREAM: + case OPK_C_READ_EXPECT: + case OPK_C_EXPECT_FIN: + case OPK_C_WRITE: + case OPK_C_CONCLUDE: + case OPK_C_FREE_STREAM: + break; + + default: + TEST_error("opcode %d not allowed on child thread", op->op); + goto out; + } + } switch (op->op) { case OPK_END: + if (thread_idx < 0) { + int done; + size_t i; + + for (i = 0; i < h->num_threads; ++i) { + if (h->threads[i].m == NULL) + continue; + + ossl_crypto_mutex_lock(h->threads[i].m); + done = h->threads[i].done; + ossl_crypto_mutex_unlock(h->threads[i].m); + + if (!done) { + if (!end_wait_warning) { + TEST_info("still waiting for other threads to finish (%zu)", i); + end_wait_warning = 1; + } + + SPIN_AGAIN(); + } + } + } + + TEST_info("script finished on thread %d", thread_idx); testresult = 1; goto out; case OPK_CHECK: { - int ok = op->check_func(&h, op); - if (h.check_spin_again) { - h.check_spin_again = 0; + int ok = op->check_func(h, op); + if (h->check_spin_again) { + h->check_spin_again = 0; SPIN_AGAIN(); } @@ -539,7 +692,7 @@ static int run_script(const struct script_op *script, int free_order) tmp_buf[0] = (unsigned char)alpn_len; /* 0 is the success case for SSL_set_alpn_protos(). */ - if (!TEST_false(SSL_set_alpn_protos(h.c_conn, tmp_buf, + if (!TEST_false(SSL_set_alpn_protos(h->c_conn, tmp_buf, alpn_len + 1))) goto out; @@ -554,12 +707,12 @@ static int run_script(const struct script_op *script, int free_order) connect_started = 1; - ret = SSL_connect(h.c_conn); + ret = SSL_connect(h->c_conn); if (!TEST_true(ret == 1 - || (!h.blocking && is_want(h.c_conn, ret)))) + || (!h->blocking && is_want(h->c_conn, ret)))) goto out; - if (!h.blocking && ret != 1) + if (!h->blocking && ret != 1) SPIN_AGAIN(); } break; @@ -585,7 +738,7 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX)) goto out; - if (!TEST_true(ossl_quic_tserver_write(h.s, s_stream_id, + if (!TEST_true(ossl_quic_tserver_write(h->s, s_stream_id, op->arg0, op->arg1, &bytes_written)) || !TEST_size_t_eq(bytes_written, op->arg1)) @@ -605,7 +758,7 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX)) goto out; - ossl_quic_tserver_conclude(h.s, s_stream_id); + ossl_quic_tserver_conclude(h->s, s_stream_id); } break; @@ -660,7 +813,7 @@ static int run_script(const struct script_op *script, int free_order) && !TEST_ptr(tmp_buf = OPENSSL_malloc(op->arg1))) goto out; - if (!TEST_true(ossl_quic_tserver_read(h.s, s_stream_id, + if (!TEST_true(ossl_quic_tserver_read(h->s, s_stream_id, tmp_buf + offset, op->arg1 - offset, &bytes_read))) @@ -704,7 +857,7 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX)) goto out; - if (!ossl_quic_tserver_has_read_ended(h.s, s_stream_id)) + if (!ossl_quic_tserver_has_read_ended(h->s, s_stream_id)) SPIN_AGAIN(); } break; @@ -716,10 +869,10 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_ptr_null(c_tgt)) goto out; /* don't overwrite existing stream with same name */ - if (!TEST_ptr(c_stream = ossl_quic_detach_stream(h.c_conn))) + if (!TEST_ptr(c_stream = ossl_quic_detach_stream(h->c_conn))) goto out; - if (!TEST_true(helper_set_c_stream(&h, op->stream_name, c_stream))) + if (!TEST_true(helper_local_set_c_stream(&hl, op->stream_name, c_stream))) goto out; } break; @@ -729,10 +882,10 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_ptr(c_tgt)) goto out; - if (!TEST_true(ossl_quic_attach_stream(h.c_conn, c_tgt))) + if (!TEST_true(ossl_quic_attach_stream(h->c_conn, c_tgt))) goto out; - if (!TEST_true(helper_set_c_stream(&h, op->stream_name, NULL))) + if (!TEST_true(helper_local_set_c_stream(&hl, op->stream_name, NULL))) goto out; } break; @@ -748,7 +901,7 @@ static int run_script(const struct script_op *script, int free_order) if (op->arg1 != 0) flags |= SSL_STREAM_FLAG_UNI; - if (!TEST_ptr(c_stream = SSL_new_stream(h.c_conn, flags))) + if (!TEST_ptr(c_stream = SSL_new_stream(h->c_conn, flags))) goto out; if (op->arg2 != UINT64_MAX @@ -756,7 +909,7 @@ static int run_script(const struct script_op *script, int free_order) op->arg2)) goto out; - if (!TEST_true(helper_set_c_stream(&h, op->stream_name, c_stream))) + if (!TEST_true(helper_local_set_c_stream(&hl, op->stream_name, c_stream))) goto out; } break; @@ -768,7 +921,7 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_uint64_t_eq(s_stream_id, UINT64_MAX)) goto out; /* don't overwrite existing stream with same name */ - if (!TEST_true(ossl_quic_tserver_stream_new(h.s, + if (!TEST_true(ossl_quic_tserver_stream_new(h->s, op->arg1 > 0, &stream_id))) goto out; @@ -777,24 +930,40 @@ static int run_script(const struct script_op *script, int free_order) && !TEST_uint64_t_eq(stream_id, op->arg2)) goto out; - if (!TEST_true(helper_set_s_stream(&h, op->stream_name, + if (!TEST_true(helper_set_s_stream(h, op->stream_name, stream_id))) goto out; } break; - case OPK_C_ACCEPT_STREAM: + case OPK_C_ACCEPT_STREAM_WAIT: { SSL *c_stream; if (!TEST_ptr_null(c_tgt)) goto out; /* don't overwrite existing stream with same name */ - if ((c_stream = SSL_accept_stream(h.c_conn, 0)) == NULL) + if ((c_stream = SSL_accept_stream(h->c_conn, 0)) == NULL) SPIN_AGAIN(); - if (!TEST_true(helper_set_c_stream(&h, op->stream_name, - c_stream))) + if (!TEST_true(helper_local_set_c_stream(&hl, op->stream_name, + c_stream))) + goto out; + } + break; + + case OPK_S_ACCEPT_STREAM_WAIT: + { + uint64_t new_stream_id; + + if (!TEST_uint64_t_eq(s_stream_id, UINT64_MAX)) + goto out; + + new_stream_id = ossl_quic_tserver_pop_incoming_stream(h->s); + if (new_stream_id == UINT64_MAX) + SPIN_AGAIN(); + + if (!TEST_true(helper_set_s_stream(h, op->stream_name, new_stream_id))) goto out; } break; @@ -803,7 +972,7 @@ static int run_script(const struct script_op *script, int free_order) { SSL *c_stream; - if (!TEST_ptr_null(c_stream = SSL_accept_stream(h.c_conn, 0))) { + if (!TEST_ptr_null(c_stream = SSL_accept_stream(h->c_conn, 0))) { SSL_free(c_stream); goto out; } @@ -816,7 +985,7 @@ static int run_script(const struct script_op *script, int free_order) || !TEST_true(!SSL_is_connection(c_tgt))) goto out; - if (!TEST_true(helper_set_c_stream(&h, op->stream_name, NULL))) + if (!TEST_true(helper_local_set_c_stream(&hl, op->stream_name, NULL))) goto out; SSL_free(c_tgt); @@ -886,10 +1055,10 @@ static int run_script(const struct script_op *script, int free_order) int expect_remote = (op->arg1 & EXPECT_CONN_CLOSE_REMOTE) != 0; uint64_t error_code = op->arg2; - if (!ossl_quic_tserver_is_term_any(h.s)) + if (!ossl_quic_tserver_is_term_any(h->s)) SPIN_AGAIN(); - if (!TEST_ptr(tc = ossl_quic_tserver_get_terminate_cause(h.s))) + if (!TEST_ptr(tc = ossl_quic_tserver_get_terminate_cause(h->s))) goto out; if (!TEST_uint64_t_eq(error_code, tc->error_code) @@ -904,7 +1073,7 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_uint64_t_eq(s_stream_id, UINT64_MAX)) goto out; - if (!TEST_true(helper_set_s_stream(&h, op->stream_name, op->arg2))) + if (!TEST_true(helper_set_s_stream(h, op->stream_name, op->arg2))) goto out; } break; @@ -928,7 +1097,7 @@ static int run_script(const struct script_op *script, int free_order) if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX)) goto out; - if (!TEST_false(ossl_quic_tserver_write(h.s, s_stream_id, + if (!TEST_false(ossl_quic_tserver_write(h->s, s_stream_id, (const unsigned char *)"apple", 5, &bytes_written))) goto out; @@ -962,6 +1131,44 @@ static int run_script(const struct script_op *script, int free_order) } break; + case OPK_NEW_THREAD: + { +#if !defined(OPENSSL_THREADS) + TEST_error("threading not supported"); + goto out; +#else + size_t i; + + if (!TEST_ptr_null(h->threads)) { + TEST_error("max one NEW_THREAD operation per script"); + goto out; + } + + h->threads = OPENSSL_zalloc(op->arg1 * sizeof(struct child_thread_args)); + if (!TEST_ptr(h->threads)) + goto out; + + h->num_threads = op->arg1; + + for (i = 0; i < op->arg1; ++i) { + h->threads[i].h = h; + h->threads[i].script = op->arg0; + h->threads[i].thread_idx = i; + + h->threads[i].m = ossl_crypto_mutex_new(); + if (!TEST_ptr(h->threads[i].m)) + goto out; + + h->threads[i].t + = ossl_crypto_thread_native_start(run_script_child_thread, + &h->threads[i], 1); + if (!TEST_ptr(h->threads[i].t)) + goto out; + } +#endif + } + break; + default: TEST_error("unknown op"); goto out; @@ -970,13 +1177,51 @@ static int run_script(const struct script_op *script, int free_order) out: if (!testresult) - TEST_error("failed at script op %zu\n", op_idx + 1); + TEST_error("failed at script op %zu, thread %d\n", + op_idx + 1, thread_idx); OPENSSL_free(tmp_buf); + helper_local_cleanup(&hl); + return testresult; +} + +static int run_script(const struct script_op *script, int free_order) +{ + int testresult = 0; + struct helper h; + + if (!TEST_true(helper_init(&h, free_order))) + goto out; + + if (!TEST_true(run_script_worker(&h, script, -1))) + goto out; + + if (!TEST_true(join_threads(h.threads, h.num_threads))) + goto out; + + testresult = 1; +out: helper_cleanup(&h); return testresult; } +#if defined(OPENSSL_THREADS) +static CRYPTO_THREAD_RETVAL run_script_child_thread(void *arg) +{ + int testresult; + struct child_thread_args *args = arg; + + testresult = run_script_worker(args->h, args->script, + args->thread_idx); + + ossl_crypto_mutex_lock(args->m); + args->testresult = testresult; + args->done = 1; + ossl_crypto_mutex_unlock(args->m); + return 1; +} +#endif + /* 1. Simple single-stream test */ static const struct script_op script_1[] = { OP_C_SET_ALPN ("ossltest") @@ -1029,7 +1274,7 @@ static const struct script_op script_2[] = { OP_S_WRITE (d, "frog", 4) OP_S_CONCLUDE (d) - OP_C_ACCEPT_STREAM (d) + OP_C_ACCEPT_STREAM_WAIT (d) OP_C_ACCEPT_STREAM_NONE () OP_C_READ_EXPECT (d, "frog", 4) OP_C_EXPECT_FIN (d) @@ -1038,7 +1283,7 @@ static const struct script_op script_2[] = { OP_S_WRITE (e, "mixture", 7) OP_S_CONCLUDE (e) - OP_C_ACCEPT_STREAM (e) + OP_C_ACCEPT_STREAM_WAIT (e) OP_C_READ_EXPECT (e, "mixture", 7) OP_C_EXPECT_FIN (e) OP_C_WRITE (e, "ramble", 6) @@ -1050,7 +1295,7 @@ static const struct script_op script_2[] = { OP_S_WRITE (f, "yonder", 6) OP_S_CONCLUDE (f) - OP_C_ACCEPT_STREAM (f) + OP_C_ACCEPT_STREAM_WAIT (f) OP_C_ACCEPT_STREAM_NONE () OP_C_READ_EXPECT (f, "yonder", 6) OP_C_EXPECT_FIN (f) @@ -1122,7 +1367,7 @@ static const struct script_op script_4[] = { OP_C_READ_FAIL (DEFAULT) - OP_C_ACCEPT_STREAM (a) + OP_C_ACCEPT_STREAM_WAIT (a) OP_C_READ_EXPECT (a, "apple", 5) OP_C_ATTACH (a) @@ -1159,7 +1404,7 @@ static const struct script_op script_6[] = { OP_S_NEW_STREAM_BIDI (a, S_BIDI_ID(0)) OP_S_WRITE (a, "apple", 5) - OP_C_ACCEPT_STREAM (a) + OP_C_ACCEPT_STREAM_WAIT (a) OP_C_FREE_STREAM (a) OP_C_ACCEPT_STREAM_NONE () |