diff options
author | Hugo Landau <hlandau@openssl.org> | 2024-02-05 17:48:49 +0000 |
---|---|---|
committer | Hugo Landau <hlandau@openssl.org> | 2024-04-19 09:31:06 +0100 |
commit | a72832cd604907266f8c036c8685b893d6ec248e (patch) | |
tree | 128fd8002c2664fe8f798b08cef2553e05d44012 | |
parent | acbae997b5ba9bd65a06cdaa89cdb5dbd9208bac (diff) |
QUIC RADIX: Add RADIX test framework implementation
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/23487)
-rw-r--r-- | test/radix/main.c | 61 | ||||
-rw-r--r-- | test/radix/quic_bindings.c | 768 | ||||
-rw-r--r-- | test/radix/quic_ops.c | 1044 | ||||
-rw-r--r-- | test/radix/quic_radix.c | 13 | ||||
-rw-r--r-- | test/radix/quic_tests.c | 33 | ||||
-rw-r--r-- | test/radix/terp.c | 882 |
6 files changed, 2801 insertions, 0 deletions
diff --git a/test/radix/main.c b/test/radix/main.c new file mode 100644 index 0000000000..55f2dc000b --- /dev/null +++ b/test/radix/main.c @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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 + */ + +OPT_TEST_DECLARE_USAGE("cert_file key_file\n") + +/* + * A RADIX test suite binding must define: + * + * static SCRIPT_INFO *const scripts[]; + * + * int bindings_process_init(size_t node_idx, size_t process_idx); + * void bindings_process_finish(int testresult); + * int bindings_adjust_terp_config(TERP_CONFIG *cfg); + * + */ +static int test_script(int idx) +{ + SCRIPT_INFO *script_info = scripts[idx]; + int testresult; + TERP_CONFIG cfg = {0}; + + if (!TEST_true(bindings_process_init(0, 0))) + return 0; + + cfg.debug_bio = bio_err; + + if (!TEST_true(bindings_adjust_terp_config(&cfg))) + return 0; + + testresult = TERP_run(script_info, &cfg); + + if (!bindings_process_finish(testresult)) + testresult = 0; + + return testresult; +} + +int setup_tests(void) +{ + if (!test_skip_common_options()) { + TEST_error("Error parsing test options\n"); + return 0; + } + + cert_file = test_get_argument(0); + if (cert_file == NULL) + cert_file = "test/certs/servercert.pem"; + + key_file = test_get_argument(1); + if (key_file == NULL) + key_file = "test/certs/serverkey.pem"; + + ADD_ALL_TESTS(test_script, OSSL_NELEM(scripts)); + return 1; +} diff --git a/test/radix/quic_bindings.c b/test/radix/quic_bindings.c new file mode 100644 index 0000000000..d738d8ceaf --- /dev/null +++ b/test/radix/quic_bindings.c @@ -0,0 +1,768 @@ +/* + * Copyright 2024 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 <openssl/lhash.h> +#include <assert.h> + +#include "internal/quic_engine.h" +#include "internal/quic_channel.h" +#include "internal/quic_ssl.h" +#include "internal/quic_error.h" + +/* + * RADIX 6D QUIC Test Framework + * ============================================================================= + * + * The radix test framework is a six-dimension script-driven facility to support + * execution of + * + * multi-stream + * multi-client + * multi-server + * multi-thread + * multi-process + * multi-node + * + * test vignettes for QUIC. Unlike the older multistream test framework, it does + * not assume a single client and a single server. Examples of vignettes + * designed to be supported by the radix test framework in future include: + * + * single client <-> single server + * multiple clients <-> single server + * single client <-> multiple servers + * multiple clients <-> multiple servers + * + * 'Multi-process' and 'multi-node' means there has been some consideration + * given to support of multi-process and multi-node testing in the future, + * though this is not currently supported. + */ + +/* + * An object is something associated with a name in the process-level state. The + * process-level state primarily revolves around a global dictionary of SSL + * objects. + */ +typedef struct radix_obj_st { + char *name; /* owned, zero-terminated */ + SSL *ssl; /* owns one reference */ + unsigned int registered : 1; /* in LHASH? */ + unsigned int active : 1; /* tick? */ +} RADIX_OBJ; + +DEFINE_LHASH_OF_EX(RADIX_OBJ); + +/* Process-level state (i.e. "globals" in the normal sense of the word) */ +typedef struct radix_process_st { + size_t node_idx; + size_t process_idx; + size_t next_thread_idx; + STACK_OF(RADIX_THREAD) *threads; + + /* Process-global state. */ + CRYPTO_MUTEX *gm; /* global mutex */ + LHASH_OF(RADIX_OBJ) *objs; /* protected by gm */ + OSSL_TIME time_slip; /* protected by gm */ + + int done_join_all_threads; + + /* + * Valid if done_join_all threads. Logical AND of all child worker results. + */ + int thread_composite_testresult; +} RADIX_PROCESS; + +#define NUM_SLOTS 4 + +/* Thread-level state within a process */ +typedef struct radix_thread_st { + RADIX_PROCESS *rp; + CRYPTO_THREAD *t; + unsigned char *tmp_buf; + size_t tmp_buf_offset; + size_t thread_idx; /* 0=main thread */ + RADIX_OBJ *slot[NUM_SLOTS]; + SSL *ssl[NUM_SLOTS]; + + /* child thread spawn arguments */ + SCRIPT_INFO *child_script_info; + BIO *debug_bio; + + /* m protects all of the below values */ + CRYPTO_MUTEX *m; + int done; + int testresult; /* valid if done */ + + uint64_t scratch0; +} RADIX_THREAD; + +DEFINE_STACK_OF(RADIX_THREAD) + +/* ssl reference is transferred. name is copied and is required. */ +static RADIX_OBJ *RADIX_OBJ_new(const char *name, SSL *ssl) +{ + RADIX_OBJ *obj; + + if (!TEST_ptr(name) || !TEST_ptr(ssl)) + return NULL; + + if (!TEST_ptr(obj = OPENSSL_zalloc(sizeof(*obj)))) + return NULL; + + obj->name = OPENSSL_strdup(name); + obj->ssl = ssl; + return obj; +} + +static void RADIX_OBJ_free(RADIX_OBJ *obj) +{ + if (obj == NULL) + return; + + assert(!obj->registered); + + SSL_free(obj->ssl); + OPENSSL_free(obj->name); + OPENSSL_free(obj); +} + +static unsigned long RADIX_OBJ_hash(const RADIX_OBJ *obj) +{ + return OPENSSL_LH_strhash(obj->name); +} + +static int RADIX_OBJ_cmp(const RADIX_OBJ *a, const RADIX_OBJ *b) +{ + return strcmp(a->name, b->name); +} + +static int RADIX_PROCESS_init(RADIX_PROCESS *rp, size_t node_idx, size_t process_idx) +{ + if (!TEST_ptr(rp->gm = ossl_crypto_mutex_new())) + goto err; + + if (!TEST_ptr(rp->objs = lh_RADIX_OBJ_new(RADIX_OBJ_hash, RADIX_OBJ_cmp))) + goto err; + + if (!TEST_ptr(rp->threads = sk_RADIX_THREAD_new(NULL))) + goto err; + + rp->node_idx = node_idx; + rp->process_idx = process_idx; + rp->done_join_all_threads = 0; + rp->next_thread_idx = 0; + return 1; + +err: + lh_RADIX_OBJ_free(rp->objs); + rp->objs = NULL; + ossl_crypto_mutex_free(&rp->gm); + return 0; +} + +static const char *stream_state_to_str(int state) +{ + switch (state) { + case SSL_STREAM_STATE_NONE: + return "none"; + case SSL_STREAM_STATE_OK: + return "OK"; + case SSL_STREAM_STATE_WRONG_DIR: + return "wrong dir"; + case SSL_STREAM_STATE_FINISHED: + return "finished"; + case SSL_STREAM_STATE_RESET_LOCAL: + return "reset-local"; + case SSL_STREAM_STATE_RESET_REMOTE: + return "reset-remote"; + case SSL_STREAM_STATE_CONN_CLOSED: + return "conn-closed"; + default: + return "?"; + } +} + +static void report_ssl_state(BIO *bio, const char *pfx, int is_write, + int state, uint64_t ec) +{ + const char *state_s = stream_state_to_str(state); + + BIO_printf(bio, "%s%-15s%s(%d)", pfx, is_write ? "Write state: " : "Read state: ", + state_s, state); + if (ec != UINT64_MAX) + BIO_printf(bio, ", %llu", (unsigned long long)ec); + BIO_printf(bio, "\n"); +} + +static void report_ssl(SSL *ssl, BIO *bio, const char *pfx) +{ + const char *type = "SSL"; + int is_quic = SSL_is_quic(ssl), is_conn = 0, is_listener = 0; + SSL_CONN_CLOSE_INFO cc_info = {0}; + const char *e_str, *f_str; + + if (is_quic) { + is_conn = SSL_is_connection(ssl); + is_listener = SSL_is_listener(ssl); + + if (is_listener) + type = "QLSO"; + else if (is_conn) + type = "QCSO"; + else + type = "QSSO"; + } + + BIO_printf(bio, "%sType: %s\n", pfx, type); + + if (is_quic && is_conn + && SSL_get_conn_close_info(ssl, &cc_info, sizeof(cc_info))) { + + e_str = ossl_quic_err_to_string(cc_info.error_code); + f_str = ossl_quic_frame_type_to_string(cc_info.frame_type); + + if (e_str == NULL) + e_str = "?"; + if (f_str == NULL) + f_str = "?"; + + BIO_printf(bio, "%sConnection is closed: %s(%llu)/%s(%llu), " + "%s, %s, reason: \"%s\"\n", + pfx, + e_str, + (unsigned long long)cc_info.error_code, + f_str, + (unsigned long long)cc_info.frame_type, + (cc_info.flags & SSL_CONN_CLOSE_FLAG_LOCAL) != 0 + ? "local" : "remote", + (cc_info.flags & SSL_CONN_CLOSE_FLAG_TRANSPORT) != 0 + ? "transport" : "app", + cc_info.reason != NULL ? cc_info.reason : "-"); + } + + if (is_quic && !is_listener) { + uint64_t stream_id = SSL_get_stream_id(ssl), rec, wec; + int rstate, wstate; + + if (stream_id != UINT64_MAX) + BIO_printf(bio, "%sStream ID: %llu\n", pfx, + (unsigned long long)stream_id); + + rstate = SSL_get_stream_read_state(ssl); + wstate = SSL_get_stream_write_state(ssl); + + if (SSL_get_stream_read_error_code(ssl, &rec) != 1) + rec = UINT64_MAX; + + if (SSL_get_stream_write_error_code(ssl, &wec) != 1) + wec = UINT64_MAX; + + report_ssl_state(bio, pfx, 0, rstate, rec); + report_ssl_state(bio, pfx, 1, wstate, wec); + } +} + +static void report_obj(RADIX_OBJ *obj, void *arg) +{ + BIO *bio = arg; + SSL *ssl = obj->ssl; + + BIO_printf(bio, " - %-16s @ %p\n", obj->name, (void *)obj->ssl); + ERR_set_mark(); + report_ssl(ssl, bio, " "); + ERR_pop_to_mark(); +} + +static void RADIX_THREAD_report_state(RADIX_THREAD *rt, BIO *bio) +{ + size_t i; + + BIO_printf(bio, " Slots:\n"); + for (i = 0; i < NUM_SLOTS; ++i) + if (rt->slot[i] == NULL) + BIO_printf(bio, " %3zu) <NULL>\n", i); + else + BIO_printf(bio, " %3zu) '%s' (SSL: %p)\n", i, + rt->slot[i]->name, + (void *)rt->ssl[i]); +} + +static void RADIX_PROCESS_report_state(RADIX_PROCESS *rp, BIO *bio, + int verbose) +{ + BIO_printf(bio, "Final process state for node %zu, process %zu:\n", + rp->node_idx, rp->process_idx); + + BIO_printf(bio, " Threads (incl. main): %zu\n", + rp->next_thread_idx); + BIO_printf(bio, " Time slip: %zu ms\n", + ossl_time2ms(rp->time_slip)); + + BIO_printf(bio, " Objects:\n"); + lh_RADIX_OBJ_doall_arg(rp->objs, report_obj, bio); + + if (verbose) + RADIX_THREAD_report_state(sk_RADIX_THREAD_value(rp->threads, 0), + bio_err); + + BIO_printf(bio, "\n===========================================" + "===========================\n"); +} + +static void RADIX_PROCESS_report_thread_results(RADIX_PROCESS *rp, BIO *bio) +{ + size_t i; + RADIX_THREAD *rt; + char *p; + long l; + char pfx_buf[64]; + int rt_testresult; + + for (i = 1; i < (size_t)sk_RADIX_THREAD_num(rp->threads); ++i) { + rt = sk_RADIX_THREAD_value(rp->threads, i); + + ossl_crypto_mutex_lock(rt->m); + assert(rt->done); + rt_testresult = rt->testresult; + ossl_crypto_mutex_unlock(rt->m); + + BIO_printf(bio, "\n====(n%zu/p%zu/t%zu)============================" + "===========================\n" + "Result for child thread with index %zu:\n", + rp->node_idx, rp->process_idx, rt->thread_idx, rt->thread_idx); + + BIO_snprintf(pfx_buf, sizeof(pfx_buf), "# -T-%2zu:\t# ", rt->thread_idx); + BIO_set_prefix(bio_err, pfx_buf); + + l = BIO_get_mem_data(rt->debug_bio, &p); + BIO_write(bio, p, l); + BIO_printf(bio, "\n"); + BIO_set_prefix(bio_err, "# "); + BIO_printf(bio, "==> Child thread with index %zu exited with %d\n", + rt->thread_idx, rt_testresult); + if (!rt_testresult) + RADIX_THREAD_report_state(rt, bio); + } + + BIO_printf(bio, "\n===========================================" + "===========================\n"); +} + +static int RADIX_THREAD_join(RADIX_THREAD *rt); + +static int RADIX_PROCESS_join_all_threads(RADIX_PROCESS *rp, int *testresult) +{ + int ok = 1; + size_t i; + RADIX_THREAD *rt; + int composite_testresult = 1; + + if (rp->done_join_all_threads) { + *testresult = rp->thread_composite_testresult; + return 1; + } + + for (i = 1; i < (size_t)sk_RADIX_THREAD_num(rp->threads); ++i) { + rt = sk_RADIX_THREAD_value(rp->threads, i); + + BIO_printf(bio_err, "==> Joining thread %zu\n", i); + + if (!TEST_true(RADIX_THREAD_join(rt))) + ok = 0; + + if (!rt->testresult) + composite_testresult = 0; + } + + rp->thread_composite_testresult = composite_testresult; + *testresult = composite_testresult; + rp->done_join_all_threads = 1; + + RADIX_PROCESS_report_thread_results(rp, bio_err); + return ok; +} + +static void cleanup_one(RADIX_OBJ *obj) +{ + obj->registered = 0; + RADIX_OBJ_free(obj); +} + +static void RADIX_THREAD_free(RADIX_THREAD *rt); + +static void RADIX_PROCESS_cleanup(RADIX_PROCESS *rp) +{ + size_t i; + + assert(rp->done_join_all_threads); + + for (i = 0; i < (size_t)sk_RADIX_THREAD_num(rp->threads); ++i) + RADIX_THREAD_free(sk_RADIX_THREAD_value(rp->threads, i)); + + sk_RADIX_THREAD_free(rp->threads); + rp->threads = NULL; + + lh_RADIX_OBJ_doall(rp->objs, cleanup_one); + lh_RADIX_OBJ_free(rp->objs); + rp->objs = NULL; + + ossl_crypto_mutex_free(&rp->gm); +} + +static RADIX_OBJ *RADIX_PROCESS_get_obj(RADIX_PROCESS *rp, const char *name) +{ + RADIX_OBJ key; + + key.name = (char *)name; + return lh_RADIX_OBJ_retrieve(rp->objs, &key); +} + +static int RADIX_PROCESS_set_obj(RADIX_PROCESS *rp, + const char *name, RADIX_OBJ *obj) +{ + RADIX_OBJ *existing; + + if (obj != NULL && !TEST_false(obj->registered)) + return 0; + + existing = RADIX_PROCESS_get_obj(rp, name); + if (existing != NULL && obj != existing) { + if (!TEST_true(existing->registered)) + return 0; + + lh_RADIX_OBJ_delete(rp->objs, existing); + existing->registered = 0; + RADIX_OBJ_free(existing); + } + + if (obj != NULL) { + lh_RADIX_OBJ_insert(rp->objs, obj); + obj->registered = 1; + } + + return 1; +} + +static int RADIX_PROCESS_set_ssl(RADIX_PROCESS *rp, const char *name, SSL *ssl) +{ + RADIX_OBJ *obj; + + if (!TEST_ptr(obj = RADIX_OBJ_new(name, ssl))) + return 0; + + if (!TEST_true(RADIX_PROCESS_set_obj(rp, name, obj))) { + RADIX_OBJ_free(obj); + return 0; + } + + return 1; +} + +static SSL *RADIX_PROCESS_get_ssl(RADIX_PROCESS *rp, const char *name) +{ + RADIX_OBJ *obj = RADIX_PROCESS_get_obj(rp, name); + + if (obj == NULL) + return NULL; + + return obj->ssl; +} + +static RADIX_THREAD *RADIX_THREAD_new(RADIX_PROCESS *rp) +{ + RADIX_THREAD *rt; + + if (!TEST_ptr(rp) + || !TEST_ptr(rt = OPENSSL_zalloc(sizeof(*rt)))) + return 0; + + rt->rp = rp; + + if (!TEST_ptr(rt->m = ossl_crypto_mutex_new())) { + OPENSSL_free(rt); + return 0; + } + + if (!TEST_true(sk_RADIX_THREAD_push(rp->threads, rt))) { + OPENSSL_free(rt); + return 0; + } + + rt->thread_idx = rp->next_thread_idx++; + assert(rt->thread_idx + 1 == (size_t)sk_RADIX_THREAD_num(rp->threads)); + return rt; +} + +static void RADIX_THREAD_free(RADIX_THREAD *rt) +{ + if (rt == NULL) + return; + + assert(rt->t == NULL); + BIO_free_all(rt->debug_bio); + OPENSSL_free(rt->tmp_buf); + ossl_crypto_mutex_free(&rt->m); + OPENSSL_free(rt); +} + +static int RADIX_THREAD_join(RADIX_THREAD *rt) +{ + CRYPTO_THREAD_RETVAL rv; + + if (rt->t != NULL) + ossl_crypto_thread_native_join(rt->t, &rv); + + ossl_crypto_thread_native_clean(rt->t); + rt->t = NULL; + + if (!TEST_true(rt->done)) + return 0; + + return 1; +} + +static RADIX_PROCESS radix_process; +static CRYPTO_THREAD_LOCAL radix_thread; + +static void radix_thread_cleanup_tl(void *p) +{ + /* Should already have been cleaned up. */ + if (!TEST_ptr_null(p)) + abort(); +} + +static RADIX_THREAD *radix_get_thread(void) +{ + return CRYPTO_THREAD_get_local(&radix_thread); +} + +static int radix_thread_init(RADIX_THREAD *rt) +{ + if (!TEST_ptr(rt) + || !TEST_ptr_null(CRYPTO_THREAD_get_local(&radix_thread))) + return 0; + + if (!TEST_true(CRYPTO_THREAD_set_local(&radix_thread, rt))) + return 0; + + set_override_bio_out(rt->debug_bio); + set_override_bio_err(rt->debug_bio); + return 1; +} + +static void radix_thread_cleanup(void) +{ + RADIX_THREAD *rt = radix_get_thread(); + + if (!TEST_ptr(rt)) + return; + + if (!TEST_true(CRYPTO_THREAD_set_local(&radix_thread, NULL))) + return; +} + +static int bindings_process_init(size_t node_idx, size_t process_idx) +{ + RADIX_THREAD *rt; + + if (!TEST_true(RADIX_PROCESS_init(&radix_process, node_idx, process_idx))) + return 0; + + if (!TEST_true(CRYPTO_THREAD_init_local(&radix_thread, + radix_thread_cleanup_tl))) + return 0; + + if (!TEST_ptr(rt = RADIX_THREAD_new(&radix_process))) + return 0; + + /* Allocate structures for main thread. */ + return radix_thread_init(rt); +} + +static int bindings_process_finish(int testresult_main) +{ + int testresult, testresult_child; + + if (!TEST_true(RADIX_PROCESS_join_all_threads(&radix_process, + &testresult_child))) + return 0; + + testresult = testresult_main && testresult_child; + RADIX_PROCESS_report_state(&radix_process, bio_err, + /*verbose=*/!testresult); + radix_thread_cleanup(); /* cleanup main thread */ + RADIX_PROCESS_cleanup(&radix_process); + + if (testresult) + BIO_printf(bio_err, "==> OK\n\n"); + else + BIO_printf(bio_err, "==> ERROR (main=%d, children=%d)\n\n", + testresult_main, testresult_child); + + return testresult; +} + +#define RP() (&radix_process) +#define RT() (radix_get_thread()) + +static OSSL_TIME get_time(void *arg) +{ + OSSL_TIME time_slip; + + ossl_crypto_mutex_lock(RP()->gm); + time_slip = RP()->time_slip; + ossl_crypto_mutex_unlock(RP()->gm); + + return ossl_time_add(ossl_time_now(), time_slip); +} + +ossl_unused static void radix_skip_time(OSSL_TIME t) +{ + ossl_crypto_mutex_lock(RP()->gm); + RP()->time_slip = ossl_time_add(RP()->time_slip, t); + ossl_crypto_mutex_unlock(RP()->gm); +} + +static void per_op_tick_obj(RADIX_OBJ *obj) +{ + if (obj->active) + SSL_handle_events(obj->ssl); +} + +static int do_per_op(TERP *terp, void *arg) +{ + lh_RADIX_OBJ_doall(RP()->objs, per_op_tick_obj); + return 1; +} + +static int bindings_adjust_terp_config(TERP_CONFIG *cfg) +{ + cfg->now_cb = get_time; + cfg->per_op_cb = do_per_op; + return 1; +} + +static int expect_slot_ssl(FUNC_CTX *fctx, size_t idx, SSL **p_ssl) +{ + if (!TEST_size_t_lt(idx, NUM_SLOTS) + || !TEST_ptr(*p_ssl = RT()->ssl[idx])) + return 0; + + return 1; +} + +#define REQUIRE_SSL_N(idx, ssl) \ + do { \ + if (!TEST_true(expect_slot_ssl(fctx, (idx), &(ssl)))) \ + goto err; \ + } while (0) +#define REQUIRE_SSL(ssl) REQUIRE_SSL_N(0, (ssl)) + +#define C_BIDI_ID(ordinal) \ + (((ordinal) << 2) | QUIC_STREAM_INITIATOR_CLIENT | QUIC_STREAM_DIR_BIDI) +#define S_BIDI_ID(ordinal) \ + (((ordinal) << 2) | QUIC_STREAM_INITIATOR_SERVER | QUIC_STREAM_DIR_BIDI) +#define C_UNI_ID(ordinal) \ + (((ordinal) << 2) | QUIC_STREAM_INITIATOR_CLIENT | QUIC_STREAM_DIR_UNI) +#define S_UNI_ID(ordinal) \ + (((ordinal) << 2) | QUIC_STREAM_INITIATOR_SERVER | QUIC_STREAM_DIR_UNI) + +static int RADIX_THREAD_worker_run(RADIX_THREAD *rt) +{ + int ok = 0; + TERP_CONFIG cfg = {0}; + + cfg.debug_bio = rt->debug_bio; + if (!TEST_true(bindings_adjust_terp_config(&cfg))) + goto err; + + if (!TERP_run(rt->child_script_info, &cfg)) + goto err; + + ok = 1; +err: + return ok; +} + +static unsigned int RADIX_THREAD_worker_main(void *p) +{ + int testresult = 0; + RADIX_THREAD *rt = p; + + if (!TEST_true(radix_thread_init(rt))) + return 0; + + /* Wait until thread-specific init is done (e.g. setting rt->t) */ + ossl_crypto_mutex_lock(rt->m); + ossl_crypto_mutex_unlock(rt->m); + + testresult = RADIX_THREAD_worker_run(rt); + + ossl_crypto_mutex_lock(rt->m); + rt->testresult = testresult; + rt->done = 1; + ossl_crypto_mutex_unlock(rt->m); + + radix_thread_cleanup(); + return 1; +} + +static void radix_activate_obj(RADIX_OBJ *obj) +{ + if (obj != NULL) + obj->active = 1; +} + +static void radix_activate_slot(size_t idx) +{ + if (idx >= NUM_SLOTS) + return; + + radix_activate_obj(RT()->slot[idx]); +} + +DEF_FUNC(hf_spawn_thread) +{ + int ok = 0; + RADIX_THREAD *child_rt = NULL; + SCRIPT_INFO *script_info = NULL; + + F_POP(script_info); + if (!TEST_ptr(script_info)) + goto err; + +#if !defined(OPENSSL_THREADS) + TEST_skip("threading not supported, skipping"); + F_SKIP_REST(); +#else + if (!TEST_ptr(child_rt = RADIX_THREAD_new(&radix_process))) + return 0; + + if (!TEST_ptr(child_rt->debug_bio = BIO_new(BIO_s_mem()))) + goto err; + + ossl_crypto_mutex_lock(child_rt->m); + + child_rt->child_script_info = script_info; + if (!TEST_ptr(child_rt->t = ossl_crypto_thread_native_start(RADIX_THREAD_worker_main, + child_rt, 1))) { + ossl_crypto_mutex_unlock(child_rt->m); + goto err; + } + + ossl_crypto_mutex_unlock(child_rt->m); + ok = 1; +#endif +err: + if (!ok) + RADIX_THREAD_free(child_rt); + + return ok; +} + +#define OP_SPAWN_THREAD(script_name) \ + (OP_PUSH_P(SCRIPT(script_name)), OP_FUNC(hf_spawn_thread)) diff --git a/test/radix/quic_ops.c b/test/radix/quic_ops.c new file mode 100644 index 0000000000..f3dedc13ac --- /dev/null +++ b/test/radix/quic_ops.c @@ -0,0 +1,1044 @@ +#include <netinet/in.h> + +static const unsigned char alpn_ossltest[] = { + /* "\x08ossltest" (hex for EBCDIC resilience) */ + 0x08, 0x6f, 0x73, 0x73, 0x6c, 0x74, 0x65, 0x73, 0x74 +}; + +DEF_FUNC(hf_unbind) +{ + int ok = 0; + const char *name; + + F_POP(name); + RADIX_PROCESS_set_obj(RP(), name, NULL); + + ok = 1; +err: + return ok; +} + +static int ssl_ctx_select_alpn(SSL *ssl, + const unsigned char **out, unsigned char *out_len, + const unsigned char *in, unsigned int in_len, + void *arg) +{ + if (SSL_select_next_proto((unsigned char **)out, out_len, + alpn_ossltest, sizeof(alpn_ossltest), in, in_len) + != OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + return SSL_TLSEXT_ERR_OK; +} + +static int ssl_ctx_configure(SSL_CTX *ctx, int is_server) +{ + if (!TEST_true(ossl_quic_set_diag_title(ctx, "quic_radix_test"))) + return 0; + + if (!is_server) + return 1; + + if (!TEST_int_eq(SSL_CTX_use_certificate_file(ctx, cert_file, + SSL_FILETYPE_PEM), 1) + || !TEST_int_eq(SSL_CTX_use_PrivateKey_file(ctx, key_file, + SSL_FILETYPE_PEM), 1)) + return 0; + + SSL_CTX_set_alpn_select_cb(ctx, ssl_ctx_select_alpn, NULL); + return 1; +} + +static int ssl_create_bound_socket(uint16_t listen_port, + int *p_fd, uint16_t *p_result_port) +{ + int ok = 0; + int fd = -1; + BIO_ADDR *addr = NULL; + union BIO_sock_info_u info; + struct in_addr ina = { htonl(INADDR_LOOPBACK) }; + + fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0); + if (!TEST_int_ge(fd, 0)) + goto err; + + if (!TEST_true(BIO_socket_nbio(fd, 1))) + goto err; + + if (!TEST_ptr(addr = BIO_ADDR_new())) + goto err; + + if (!TEST_true(BIO_ADDR_rawmake(addr, AF_INET, + &ina, sizeof(ina), 0))) + goto err; + + if (!TEST_true(BIO_bind(fd, addr, 0))) + goto err; + + info.addr = addr; + if (!TEST_true(BIO_sock_info(fd, BIO_SOCK_INFO_ADDRESS, &info))) + goto err; + + if (!TEST_int_gt(BIO_ADDR_rawport(addr), 0)) + goto err; + + ok = 1; +err: + if (!ok && fd >= 0) + BIO_closesocket(fd); + else if (ok) { + *p_fd = fd; + if (p_result_port != NULL) + *p_result_port = BIO_ADDR_rawport(addr); + } + BIO_ADDR_free(addr); + return ok; +} + +static int ssl_attach_bio_dgram(SSL *ssl, + uint16_t local_port, uint16_t *actual_port) +{ + int s_fd = -1; + BIO *bio; + + if (!TEST_true(ssl_create_bound_socket(local_port, &s_fd, actual_port))) + return 0; + + if (!TEST_ptr(bio = BIO_new_dgram(s_fd, BIO_CLOSE))) { + BIO_closesocket(s_fd); + return 0; + } + + SSL_set0_rbio(ssl, bio); + if (!TEST_true(BIO_up_ref(bio))) + return 0; + + SSL_set0_wbio(ssl, bio); + + return 1; +} + +DEF_FUNC(hf_new_ssl) +{ + int ok = 0; + const char *name; + SSL_CTX *ctx = NULL; + const SSL_METHOD *method; + SSL *ssl; + uint64_t flags; + int is_server; + + F_POP2(name, flags); + + is_server = (flags != 0); + method = is_server ? OSSL_QUIC_server_method() : OSSL_QUIC_client_method(); + if (!TEST_ptr(ctx = SSL_CTX_new(method))) + goto err; + + if (!TEST_true(ssl_ctx_configure(ctx, is_server))) + goto err; + + if (is_server) { + if (!TEST_ptr(ssl = SSL_new_listener(ctx, 0))) + goto err; + } else { + if (!TEST_ptr(ssl = SSL_new(ctx))) + goto err; + } + + if (!TEST_true(ssl_attach_bio_dgram(ssl, 0, NULL))) + goto err; + + if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), name, ssl))) { + SSL_free(ssl); + goto err; + } + + ok = 1; +err: + /* SSL object will hold ref, we don't need it */ + SSL_CTX_free(ctx); + return ok; +} + +DEF_FUNC(hf_listen) +{ + int ok = 0, r; + SSL *ssl; + + REQUIRE_SSL(ssl); + + r = SSL_listen(ssl); + if (!TEST_true(r)) + goto err; + + radix_activate_slot(0); + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_new_stream) +{ + int ok = 0; + const char *conn_name, *stream_name; + SSL *conn, *stream; + uint64_t flags, do_accept; + + F_POP2(flags, do_accept); + F_POP2(conn_name, stream_name); + + if (!TEST_ptr_null(RADIX_PROCESS_get_obj(RP(), stream_name))) + goto err; + + if (!TEST_ptr(conn = RADIX_PROCESS_get_ssl(RP(), conn_name))) + goto err; + + if (do_accept) { + stream = SSL_accept_stream(conn, flags); + + if (stream == NULL) + F_SPIN_AGAIN(); + } else { + stream = SSL_new_stream(conn, flags); + } + + if (!TEST_ptr(stream)) + goto err; + + /* TODO(QUIC RADIX): Implement wait behaviour */ + + if (stream != NULL + && !TEST_true(RADIX_PROCESS_set_ssl(RP(), stream_name, stream))) { + SSL_free(stream); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_accept_conn) +{ + int ok = 0; + const char *conn_name; + uint64_t flags; + SSL *listener, *conn; + + F_POP2(conn_name, flags); + REQUIRE_SSL(listener); + + if (!TEST_ptr_null(RADIX_PROCESS_get_obj(RP(), conn_name))) + goto err; + + conn = SSL_accept_connection(listener, flags); + if (conn == NULL) + F_SPIN_AGAIN(); + + if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), conn_name, conn))) { + SSL_free(conn); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_accept_conn_none) +{ + int ok = 0; + SSL *listener, *conn; + + REQUIRE_SSL(listener); + + conn = SSL_accept_connection(listener, 0); + if (!TEST_ptr_null(conn)) { + SSL_free(conn); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_accept_stream_none) +{ + int ok = 0; + const char *conn_name; + uint64_t flags; + SSL *conn, *stream; + + F_POP2(conn_name, flags); + + if (!TEST_ptr(conn = RADIX_PROCESS_get_ssl(RP(), conn_name))) + goto er |