From 8b7be3aa7e90d85441f5012624cece4dca33291e Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Tue, 18 Apr 2023 19:30:55 +0100 Subject: QUIC DISPATCH/APL: Implement SSL_set_default_stream_mode, default XSO refactor Reviewed-by: Matt Caswell Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/20765) --- crypto/err/openssl.txt | 1 + include/internal/quic_channel.h | 13 ++ include/internal/quic_ssl.h | 3 + include/openssl/ssl.h.in | 8 + include/openssl/sslerr.h | 2 + ssl/quic/quic_impl.c | 324 +++++++++++++++++++++++++++++++++------- ssl/quic/quic_local.h | 3 + ssl/ssl_err.c | 1 + ssl/ssl_lib.c | 36 +++++ util/libssl.num | 4 + 10 files changed, 339 insertions(+), 56 deletions(-) diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index 8545eaab96..036a0e738d 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -1465,6 +1465,7 @@ SSL_R_NO_SHARED_CIPHER:193:no shared cipher SSL_R_NO_SHARED_GROUPS:410:no shared groups SSL_R_NO_SHARED_SIGNATURE_ALGORITHMS:376:no shared signature algorithms SSL_R_NO_SRTP_PROFILES:359:no srtp profiles +SSL_R_NO_STREAM:355:no stream SSL_R_NO_SUITABLE_DIGEST_ALGORITHM:297:no suitable digest algorithm SSL_R_NO_SUITABLE_GROUPS:295:no suitable groups SSL_R_NO_SUITABLE_KEY_SHARE:101:no suitable key share diff --git a/include/internal/quic_channel.h b/include/internal/quic_channel.h index 084c465c3a..74b3473782 100644 --- a/include/internal/quic_channel.h +++ b/include/internal/quic_channel.h @@ -84,6 +84,19 @@ */ # define QUIC_TAKES_LOCK +/* + * The function acquires the channel mutex and leaves it acquired + * when returning success. + * + * Any function tagged with this has the following precondition and + * postcondition: + * + * Precondition: must not hold channel mutex (unchecked) + * Postcondition: channel mutex is held by calling thread + * or function returned failure + */ +# define QUIC_ACQUIRES_LOCK + # define QUIC_TODO_LOCK # define QUIC_CHANNEL_STATE_IDLE 0 diff --git a/include/internal/quic_ssl.h b/include/internal/quic_ssl.h index 0ccb1c5526..986cd0e0d0 100644 --- a/include/internal/quic_ssl.h +++ b/include/internal/quic_ssl.h @@ -69,6 +69,9 @@ __owur SSL *ossl_quic_conn_stream_new(SSL *s, uint64_t flags); __owur SSL *ossl_quic_get0_connection(SSL *s); __owur int ossl_quic_get_stream_type(SSL *s); __owur uint64_t ossl_quic_get_stream_id(SSL *s); +__owur int ossl_quic_set_default_stream_mode(SSL *s, uint32_t mode); +__owur SSL *ossl_quic_detach_stream(SSL *s); +__owur int ossl_quic_attach_stream(SSL *conn, SSL *stream); /* * Used to override ossl_time_now() for debug purposes. Must be called before diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index 8d82bf6b5a..c5ab105816 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -2277,6 +2277,14 @@ __owur int SSL_get_stream_type(SSL *s); __owur uint64_t SSL_get_stream_id(SSL *s); +#define SSL_DEFAULT_STREAM_MODE_NONE 0 +#define SSL_DEFAULT_STREAM_MODE_AUTO_BIDI 1 +#define SSL_DEFAULT_STREAM_MODE_AUTO_UNI 2 +__owur int SSL_set_default_stream_mode(SSL *s, uint32_t mode); + +__owur SSL *SSL_detach_stream(SSL *s); +__owur int SSL_attach_stream(SSL *conn, SSL *stream); + #define SSL_STREAM_FLAG_UNI (1U << 0) __owur SSL *SSL_new_stream(SSL *s, uint64_t flags); diff --git a/include/openssl/sslerr.h b/include/openssl/sslerr.h index 94cb60f8be..d8c97c7666 100644 --- a/include/openssl/sslerr.h +++ b/include/openssl/sslerr.h @@ -202,6 +202,7 @@ # define SSL_R_NO_SHARED_GROUPS 410 # define SSL_R_NO_SHARED_SIGNATURE_ALGORITHMS 376 # define SSL_R_NO_SRTP_PROFILES 359 +# define SSL_R_NO_STREAM 355 # define SSL_R_NO_SUITABLE_DIGEST_ALGORITHM 297 # define SSL_R_NO_SUITABLE_GROUPS 295 # define SSL_R_NO_SUITABLE_KEY_SHARE 101 @@ -353,5 +354,6 @@ # define SSL_R_WRONG_VERSION_NUMBER 267 # define SSL_R_X509_LIB 268 # define SSL_R_X509_VERIFICATION_SETUP_PROBLEMS 269 +# define SSL_R_CONN_USE_ONLY 411 #endif diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index 6a28c00c75..4550ee3be0 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -20,7 +20,11 @@ static void aon_write_finish(QUIC_XSO *xso); static int create_channel(QUIC_CONNECTION *qc); static QUIC_XSO *create_xso_from_stream(QUIC_CONNECTION *qc, QUIC_STREAM *qs); -static int qc_try_create_default_xso(QUIC_CONNECTION *qc, int remote_init); +static int qc_try_create_default_xso_for_write(QUIC_CONNECTION *qc); +static int qc_wait_for_default_xso_for_read(QUIC_CONNECTION *qc); +static void quic_lock(QUIC_CONNECTION *qc); +static void quic_unlock(QUIC_CONNECTION *qc); +static int quic_do_handshake(QUIC_CONNECTION *qc); /* * QUIC Front-End I/O API: Common Utilities @@ -136,7 +140,7 @@ static int expect_quic(const SSL *s, QCTX *ctx) ctx->is_stream = 0; if (s == NULL) - return QUIC_RAISE_NON_NORMAL_ERROR(NULL, ERR_R_INTERNAL_ERROR, NULL); + return QUIC_RAISE_NON_NORMAL_ERROR(NULL, ERR_R_PASSED_NULL_PARAMETER, NULL); switch (s->type) { case SSL_TYPE_QUIC_CONNECTION: @@ -165,22 +169,50 @@ static int expect_quic(const SSL *s, QCTX *ctx) * * remote_init determines if we expect the default XSO to be remotely created or * not. If it is -1, do not instantiate a default XSO if one does not yet exist. + * + * Channel mutex is acquired and retained on success. */ -static int ossl_unused expect_quic_with_stream(const SSL *s, int remote_init, - QCTX *ctx) +QUIC_ACQUIRES_LOCK +static int ossl_unused expect_quic_with_stream_lock(const SSL *s, int remote_init, + QCTX *ctx) { if (!expect_quic(s, ctx)) return 0; + quic_lock(ctx->qc); + if (ctx->xso == NULL && remote_init >= 0) { - qc_try_create_default_xso(ctx->qc, remote_init); + if (ossl_quic_channel_is_term_any(ctx->qc->ch)) { + QUIC_RAISE_NON_NORMAL_ERROR(ctx->qc, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL); + goto err; + } + + /* If we haven't finished the handshake, try to advance it. */ + if (quic_do_handshake(ctx->qc) < 1) + /* ossl_quic_do_handshake raised error here */ + goto err; + + if (remote_init == 0) { + if (!qc_try_create_default_xso_for_write(ctx->qc)) + goto err; + } else { + if (!qc_wait_for_default_xso_for_read(ctx->qc)) + goto err; + } + ctx->xso = ctx->qc->default_xso; } - if (ctx->xso == NULL) - return QUIC_RAISE_NON_NORMAL_ERROR(ctx->qc, ERR_R_INTERNAL_ERROR, NULL); + if (ctx->xso == NULL) { + QUIC_RAISE_NON_NORMAL_ERROR(ctx->qc, SSL_R_NO_STREAM, NULL); + goto err; + } + + return 1; /* lock held */ - return 1; +err: + quic_unlock(ctx->qc); + return 0; } /* @@ -193,7 +225,7 @@ static int ossl_unused expect_quic_conn_only(const SSL *s, QCTX *ctx) return 0; if (ctx->is_stream) - return QUIC_RAISE_NON_NORMAL_ERROR(ctx->qc, ERR_R_INTERNAL_ERROR, NULL); + return QUIC_RAISE_NON_NORMAL_ERROR(ctx->qc, SSL_R_CONN_USE_ONLY, NULL); return 1; } @@ -261,6 +293,7 @@ SSL *ossl_quic_new(SSL_CTX *ctx) qc->as_server = 0; /* TODO(QUIC): server support */ qc->as_server_state = qc->as_server; + qc->default_stream_mode = SSL_DEFAULT_STREAM_MODE_AUTO_BIDI; qc->default_ssl_mode = qc->ssl.ctx->mode; qc->default_blocking = 1; qc->last_error = SSL_ERROR_NONE; @@ -1119,50 +1152,123 @@ int ossl_quic_accept(SSL *s) * exists). Note that this is NOT an error condition. */ QUIC_NEEDS_LOCK -static int qc_try_create_default_xso(QUIC_CONNECTION *qc, int remote_init) +static int qc_try_create_default_xso_for_write(QUIC_CONNECTION *qc) { - QUIC_STREAM *qs; + uint64_t flags = 0; - if (qc->default_xso != NULL) - qc->default_xso_created = 1; - - if (qc->default_xso_created) + if (qc->default_xso_created + || qc->default_stream_mode == SSL_DEFAULT_STREAM_MODE_NONE) /* * We only do this once. If the user detaches a previously created * default XSO we don't auto-create another one. */ - return 0; + return QUIC_RAISE_NON_NORMAL_ERROR(qc, SSL_R_NO_STREAM, NULL); - if (!remote_init) { - /* - * We are writing to the stream first, so this is a locally-initiated - * stream. - */ - qc->default_xso = (QUIC_XSO *)ossl_quic_conn_stream_new(&qc->ssl, 0); - if (qc->default_xso == NULL) - return 0; - } else { - /* - * Client is reading first. This means it is expecting to get data on a - * stream the peer writes to first, meaning this is a remotely-initiated - * stream. Ordinarily, we wait for the RXDP to handle a STREAM frame to - * create such a stream. But in this case, special case it and create - * the bookkeeping structures for the first peer-initiated bidirectional - * stream so the client can start to wait on it. - */ - qs = ossl_quic_channel_new_stream_remote(qc->ch, - QUIC_STREAM_INITIATOR_SERVER - | QUIC_STREAM_DIR_BIDI); - if (qs == NULL) - return 0; - - qc->default_xso = create_xso_from_stream(qc, qs); - if (qc->default_xso == NULL) { - ossl_quic_stream_map_release(ossl_quic_channel_get_qsm(qc->ch), qs); + /* Create a locally-initiated stream. */ + if (qc->default_stream_mode == SSL_DEFAULT_STREAM_MODE_AUTO_UNI) + flags |= SSL_STREAM_FLAG_UNI; + + qc->default_xso = (QUIC_XSO *)ossl_quic_conn_stream_new(&qc->ssl, flags); + if (qc->default_xso == NULL) + return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL); + + qc->default_xso_created = 1; + return 1; +} + +struct quic_wait_for_stream_args { + QUIC_CONNECTION *qc; + QUIC_STREAM *qs; + uint64_t expect_id; +}; + +QUIC_NEEDS_LOCK +static int quic_wait_for_stream(void *arg) +{ + struct quic_wait_for_stream_args *args = arg; + + if (!ossl_quic_channel_is_active(args->qc->ch)) { + /* If connection is torn down due to an error while blocking, stop. */ + QUIC_RAISE_NON_NORMAL_ERROR(args->qc, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL); + return -1; + } + + args->qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(args->qc->ch), + args->expect_id); + if (args->qs != NULL) + return 1; /* stream now exists */ + + return 0; /* did not get a stream, keep trying */ +} + +QUIC_NEEDS_LOCK +static int qc_wait_for_default_xso_for_read(QUIC_CONNECTION *qc) +{ + /* Called on a QCSO and we don't currently have a default stream. */ + uint64_t expect_id; + QUIC_STREAM *qs; + int res; + struct quic_wait_for_stream_args wargs; + + /* + * If default stream functionality is disabled or we already detached + * one, don't make another default stream and just fail. + */ + if (qc->default_xso_created + || qc->default_stream_mode == SSL_DEFAULT_STREAM_MODE_NONE) + return QUIC_RAISE_NON_NORMAL_ERROR(qc, SSL_R_NO_STREAM, NULL); + + /* + * The peer may have opened a stream since we last ticked. So tick and + * see if the stream with ordinal 0 (remote, bidi/uni based on stream + * mode) exists yet. QUIC stream IDs must be allocated in order, so the + * first stream created by a peer must have an ordinal of 0. + */ + expect_id = qc->as_server + ? QUIC_STREAM_INITIATOR_CLIENT + : QUIC_STREAM_INITIATOR_SERVER; + + expect_id |= (qc->default_stream_mode == SSL_DEFAULT_STREAM_MODE_AUTO_UNI) + ? QUIC_STREAM_DIR_UNI + : QUIC_STREAM_DIR_BIDI; + + qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(qc->ch), + expect_id); + if (qs == NULL) { + ossl_quic_reactor_tick(ossl_quic_channel_get_reactor(qc->ch), 0); + + qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(qc->ch), + expect_id); + } + + if (qs == NULL) { + if (!qc_blocking_mode(qc)) + /* Non-blocking mode, so just bail immediately. */ + return QUIC_RAISE_NORMAL_ERROR(qc, SSL_ERROR_WANT_READ); + + /* Block until we have a stream. */ + wargs.qc = qc; + wargs.qs = NULL; + wargs.expect_id = expect_id; + + res = block_until_pred(qc, quic_wait_for_stream, &wargs, 0); + if (res == 0) + return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL); + else if (res < 0 || wargs.qs == NULL) + /* quic_wait_for_stream raised error here */ return 0; - } + + qs = wargs.qs; } + /* + * We now have qs != NULL. Make it the default stream, creating the + * necessary XSO. + */ + qc->default_xso = create_xso_from_stream(qc, qs); + if (qc->default_xso == NULL) + return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL); + qc->default_xso_created = 1; return 1; } @@ -1218,6 +1324,7 @@ SSL *ossl_quic_conn_stream_new(SSL *s, uint64_t flags) if (xso == NULL) goto err; + ctx.qc->default_xso_created = 1; /* inhibits default XSO */ quic_unlock(ctx.qc); return &xso->ssl; @@ -1508,11 +1615,11 @@ int ossl_quic_write(SSL *s, const void *buf, size_t len, size_t *written) *written = 0; - if (!expect_quic_with_stream(s, /*remote_init=*/0, &ctx)) { - return 0; - } + if (len == 0) + return 1; - quic_lock(ctx.qc); + if (!expect_quic_with_stream_lock(s, /*remote_init=*/0, &ctx)) + return 0; partial_write = ((ctx.xso->ssl_mode & SSL_MODE_ENABLE_PARTIAL_WRITE) != 0); @@ -1647,7 +1754,7 @@ static int quic_read(SSL *s, void *buf, size_t len, size_t *bytes_read, int peek *bytes_read = 0; - if (!expect_quic_with_stream(s, /*remote_init=*/0, &ctx)) + if (!expect_quic(s, &ctx)) return 0; quic_lock(ctx.qc); @@ -1663,6 +1770,21 @@ static int quic_read(SSL *s, void *buf, size_t len, size_t *bytes_read, int peek goto out; } + if (ctx.xso == NULL) { + /* + * Called on a QCSO and we don't currently have a default stream. + * + * Wait until we get a stream initiated by the peer (blocking mode) or + * fail if we don't have one yet (non-blocking mode). + */ + if (!qc_wait_for_default_xso_for_read(ctx.qc)) { + ret = 0; /* error already raised here */ + goto out; + } + + ctx.xso = ctx.qc->default_xso; + } + if (ctx.xso->stream == NULL) { ret = QUIC_RAISE_NON_NORMAL_ERROR(ctx.qc, ERR_R_INTERNAL_ERROR, NULL); goto out; @@ -1734,11 +1856,9 @@ static size_t ossl_quic_pending_int(const SSL *s) size_t avail = 0; int fin = 0; - if (!expect_quic_with_stream(s, /*remote_init=*/-1, &ctx)) + if (!expect_quic_with_stream_lock(s, /*remote_init=*/-1, &ctx)) return 0; - quic_lock(ctx.qc); - if (ctx.xso->stream == NULL || ctx.xso->stream->rstream == NULL) /* Cannot raise errors here because we are const, just fail. */ goto out; @@ -1771,11 +1891,9 @@ int ossl_quic_conn_stream_conclude(SSL *s) QCTX ctx; QUIC_STREAM *qs; - if (!expect_quic_with_stream(s, /*remote_init=*/0, &ctx)) + if (!expect_quic_with_stream_lock(s, /*remote_init=*/0, &ctx)) return 0; - quic_lock(ctx.qc); - qs = ctx.xso->stream; if (qs == NULL || qs->sstream == NULL) { @@ -1874,11 +1992,105 @@ int ossl_quic_get_stream_type(SSL *s) uint64_t ossl_quic_get_stream_id(SSL *s) { QCTX ctx; + uint64_t id; - if (!expect_quic_with_stream(s, /*remote_init=*/-1, &ctx)) + if (!expect_quic_with_stream_lock(s, /*remote_init=*/-1, &ctx)) return UINT64_MAX; - return ctx.xso->stream->id; + id = ctx.xso->stream->id; + quic_unlock(ctx.qc); + + return id; +} + +/* + * SSL_set_default_stream_mode + * --------------------------- + */ +int ossl_quic_set_default_stream_mode(SSL *s, uint32_t mode) +{ + QCTX ctx; + + if (!expect_quic_conn_only(s, &ctx)) + return 0; + + quic_lock(ctx.qc); + + if (ctx.qc->default_xso_created) + return QUIC_RAISE_NON_NORMAL_ERROR(ctx.qc, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED, + "too late to change default stream mode"); + + switch (mode) { + case SSL_DEFAULT_STREAM_MODE_NONE: + case SSL_DEFAULT_STREAM_MODE_AUTO_BIDI: + case SSL_DEFAULT_STREAM_MODE_AUTO_UNI: + ctx.qc->default_stream_mode = mode; + break; + default: + quic_unlock(ctx.qc); + return QUIC_RAISE_NON_NORMAL_ERROR(ctx.qc, ERR_R_PASSED_INVALID_ARGUMENT, + "bad default stream type"); + } + + quic_unlock(ctx.qc); + return 1; +} + +/* + * SSL_detach_stream + * ----------------- + */ +SSL *ossl_quic_detach_stream(SSL *s) +{ + QCTX ctx; + QUIC_XSO *xso; + + if (!expect_quic_conn_only(s, &ctx)) + return NULL; + + quic_lock(ctx.qc); + + xso = ctx.qc->default_xso; + ctx.qc->default_xso = NULL; + + /* Calling this function inhibits default XSO autocreation. */ + ctx.qc->default_xso_created = 1; + + quic_unlock(ctx.qc); + + return &xso->ssl; +} + +/* + * SSL_attach_stream + * ----------------- + */ +int ossl_quic_attach_stream(SSL *conn, SSL *stream) +{ + QCTX ctx; + + if (!expect_quic_conn_only(conn, &ctx)) + return 0; + + if (stream == NULL || stream->type != SSL_TYPE_QUIC_XSO) + return QUIC_RAISE_NON_NORMAL_ERROR(ctx.qc, ERR_R_PASSED_NULL_PARAMETER, + "stream to attach must be a valid QUIC stream"); + + quic_lock(ctx.qc); + + if (ctx.qc->default_xso != NULL) { + quic_unlock(ctx.qc); + return QUIC_RAISE_NON_NORMAL_ERROR(ctx.qc, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED, + "connection already has a default stream"); + } + + ctx.qc->default_xso = (QUIC_XSO *)stream; + + /* Calling this function inhibits default XSO autocreation. */ + ctx.qc->default_xso_created = 1; + + quic_unlock(ctx.qc); + return 1; } /* diff --git a/ssl/quic/quic_local.h b/ssl/quic/quic_local.h index 1358a55966..edc82a415e 100644 --- a/ssl/quic/quic_local.h +++ b/ssl/quic/quic_local.h @@ -172,6 +172,9 @@ struct quic_conn_st { /* Have we created a default XSO yet? */ unsigned int default_xso_created : 1; + /* Default stream type. Defaults to SSL_DEFAULT_STREAM_MODE_AUTO_BIDI. */ + uint32_t default_stream_mode; + /* SSL_set_mode. This is not used directly but inherited by new XSOs. */ uint32_t default_ssl_mode; diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index f1464f7d64..520a297905 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -311,6 +311,7 @@ static const ERR_STRING_DATA SSL_str_reasons[] = { {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_SHARED_SIGNATURE_ALGORITHMS), "no shared signature algorithms"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_SRTP_PROFILES), "no srtp profiles"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_STREAM), "no stream"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_SUITABLE_DIGEST_ALGORITHM), "no suitable digest algorithm"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_SUITABLE_GROUPS), "no suitable groups"}, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index a7e3682291..c6cd2dabda 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -7352,6 +7352,42 @@ uint64_t SSL_get_stream_id(SSL *s) #endif } +int SSL_set_default_stream_mode(SSL *s, uint32_t mode) +{ +#ifndef OPENSSL_NO_QUIC + if (!IS_QUIC(s)) + return 0; + + return ossl_quic_set_default_stream_mode(s, mode); +#else + return 0; +#endif +} + +SSL *SSL_detach_stream(SSL *s) +{ +#ifndef OPENSSL_NO_QUIC + if (!IS_QUIC(s)) + return NULL; + + return ossl_quic_detach_stream(s); +#else + return NULL; +#endif +} + +int SSL_attach_stream(SSL *conn, SSL *stream) +{ +#ifndef OPENSSL_NO_QUIC + if (!IS_QUIC(conn)) + return 0; + + return ossl_quic_attach_stream(conn, stream); +#else + return NULL; +#endif +} + int SSL_add_expected_rpk(SSL *s, EVP_PKEY *rpk) { unsigned char *data = NULL; diff --git a/util/libssl.num b/util/libssl.num index a52a034e20..8427cd1273 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -564,3 +564,7 @@ SSL_new_stream ? 3_2_0 EXIST::FUNCTION: SSL_get0_connection ? 3_2_0 EXIST::FUNCTION: SSL_is_connection ? 3_2_0 EXIST::FUNCTION: SSL_get_stream_type ? 3_2_0 EXIST::FUNCTION: +SSL_get_stream_id ? 3_2_0 EXIST::FUNCTION: +SSL_set_default_stream_mode ? 3_2_0 EXIST::FUNCTION: +SSL_detach_stream ? 3_2_0 EXIST::FUNCTION: +SSL_attach_stream ? 3_2_0 EXIST::FUNCTION: -- cgit v1.2.1