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)
This commit is contained in:
Hugo Landau 2023-04-28 16:56:33 +01:00
parent 1df479a9f9
commit a350db7318

View File

@ -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;
}
ossl_quic_tserver_tick(h.s);
if (connect_started)
SSL_tick(h.c_conn);
if (thread_idx < 0) {
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 ()