From 51a168b804be963e320c5515656301d25ea48322 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Tue, 22 Nov 2022 13:45:18 +0000 Subject: [PATCH] QUIC Test Server Implementation Reviewed-by: Matt Caswell Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/19734) --- include/internal/quic_tserver.h | 79 ++++++++++++++++ ssl/quic/build.info | 1 + ssl/quic/quic_tserver.c | 163 ++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 include/internal/quic_tserver.h create mode 100644 ssl/quic/quic_tserver.c diff --git a/include/internal/quic_tserver.h b/include/internal/quic_tserver.h new file mode 100644 index 0000000000..055d2f8e1c --- /dev/null +++ b/include/internal/quic_tserver.h @@ -0,0 +1,79 @@ +/* + * Copyright 2022 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 + */ + +#ifndef OSSL_QUIC_TSERVER_H +# define OSSL_QUIC_TSERVER_H + +# include +# include "internal/quic_stream.h" + +# ifndef OPENSSL_NO_QUIC + +/* + * QUIC Test Server Module + * ======================= + * + * This implements a QUIC test server. Since full QUIC server support is not yet + * implemented this server is limited in features and scope. It exists to + * provide a target for our QUIC client to talk to for testing purposes. + * + * A given QUIC test server instance supports only one client at a time. + * + * Note that this test server is not suitable for production use because it does + * not implement address verification, anti-amplification or retry logic. + */ +typedef struct quic_tserver_st QUIC_TSERVER; + +typedef struct quic_tserver_args_st { + OSSL_LIB_CTX *libctx; + const char *propq; + BIO *net_rbio, *net_wbio; +} QUIC_TSERVER_ARGS; + +QUIC_TSERVER *ossl_quic_tserver_new(const QUIC_TSERVER_ARGS *args); + +void ossl_quic_tserver_free(QUIC_TSERVER *srv); + +/* Advances the state machine. */ +int ossl_quic_tserver_tick(QUIC_TSERVER *srv); + +/* Returns 1 if we have a (non-terminated) client. */ +int ossl_quic_tserver_is_connected(QUIC_TSERVER *srv); + +/* + * Attempts to read from stream 0. Writes the number of bytes read to + * *bytes_read and returns 1 on success. If no bytes are available, 0 is written + * to *bytes_read and 1 is returned (this is considered a success case). + * + * Returns 0 if connection is not currently active. + */ +int ossl_quic_tserver_read(QUIC_TSERVER *srv, + unsigned char *buf, + size_t buf_len, + size_t *bytes_read); + +/* + * Attempts to write to stream 0. Writes the number of bytes consumed to + * *consumed and returns 1 on success. If there is no space currently available + * to write any bytes, 0 is written to *consumed and 1 is returned (this is + * considered a success case). + * + * Note that unlike libssl public APIs, this API always works in a 'partial + * write' mode. + * + * Returns 0 if connection is not currently active. + */ +int ossl_quic_tserver_write(QUIC_TSERVER *srv, + const unsigned char *buf, + size_t buf_len, + size_t *bytes_written); + +# endif + +#endif diff --git a/ssl/quic/build.info b/ssl/quic/build.info index 5f4d761b26..c3718017ce 100644 --- a/ssl/quic/build.info +++ b/ssl/quic/build.info @@ -11,3 +11,4 @@ SOURCE[$LIBSSL]=quic_sf_list.c quic_rstream.c quic_sstream.c SOURCE[$LIBSSL]=quic_dummy_handshake.c SOURCE[$LIBSSL]=quic_reactor.c SOURCE[$LIBSSL]=quic_channel.c +SOURCE[$LIBSSL]=quic_tserver.c diff --git a/ssl/quic/quic_tserver.c b/ssl/quic/quic_tserver.c new file mode 100644 index 0000000000..144fa072b7 --- /dev/null +++ b/ssl/quic/quic_tserver.c @@ -0,0 +1,163 @@ +/* + * Copyright 2022 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 "internal/quic_tserver.h" +#include "internal/quic_channel.h" +#include "internal/quic_statm.h" +#include "internal/common.h" + +/* + * QUIC Test Server Module + * ======================= + */ +struct quic_tserver_st { + QUIC_TSERVER_ARGS args; + + /* + * The QUIC channel providing the core QUIC connection implementation. + */ + QUIC_CHANNEL *ch; + + /* Our single bidirectional application data stream. */ + QUIC_STREAM *stream0; + + /* The current peer L4 address. AF_UNSPEC if we do not have a peer yet. */ + BIO_ADDR cur_peer_addr; + + /* Are we connected to a peer? */ + unsigned int connected : 1; +}; + +QUIC_TSERVER *ossl_quic_tserver_new(const QUIC_TSERVER_ARGS *args) +{ + QUIC_TSERVER *srv = NULL; + QUIC_CHANNEL_ARGS ch_args = {0}; + + if (args->net_rbio == NULL || args->net_wbio == NULL) + goto err; + + if ((srv = OPENSSL_zalloc(sizeof(*srv))) == NULL) + goto err; + + srv->args = *args; + + ch_args.libctx = srv->args.libctx; + ch_args.propq = srv->args.propq; + ch_args.is_server = 1; + + if ((srv->ch = ossl_quic_channel_new(&ch_args)) == NULL) + goto err; + + if (!ossl_quic_channel_set_net_rbio(srv->ch, srv->args.net_rbio) + || !ossl_quic_channel_set_net_wbio(srv->ch, srv->args.net_wbio)) + goto err; + + srv->stream0 = ossl_quic_channel_get_stream_by_id(srv->ch, 0); + if (srv->stream0 == NULL) + goto err; + + return srv; + +err: + if (srv != NULL) + ossl_quic_channel_free(srv->ch); + + OPENSSL_free(srv); + return NULL; +} + +void ossl_quic_tserver_free(QUIC_TSERVER *srv) +{ + if (srv == NULL) + return; + + ossl_quic_channel_free(srv->ch); + BIO_free(srv->args.net_rbio); + BIO_free(srv->args.net_wbio); + OPENSSL_free(srv); +} + +int ossl_quic_tserver_tick(QUIC_TSERVER *srv) +{ + ossl_quic_reactor_tick(ossl_quic_channel_get_reactor(srv->ch)); + + if (ossl_quic_channel_is_active(srv->ch)) + srv->connected = 1; + + return 1; +} + +int ossl_quic_tserver_is_connected(QUIC_TSERVER *srv) +{ + return ossl_quic_channel_is_active(srv->ch); +} + +int ossl_quic_tserver_read(QUIC_TSERVER *srv, + unsigned char *buf, + size_t buf_len, + size_t *bytes_read) +{ + int is_fin = 0; /* TODO(QUIC): Handle FIN in API */ + + if (!ossl_quic_channel_is_active(srv->ch)) + return 0; + + if (!ossl_quic_rstream_read(srv->stream0->rstream, buf, buf_len, + bytes_read, &is_fin)) + return 0; + + if (*bytes_read > 0) { + /* + * We have read at least one byte from the stream. Inform stream-level + * RXFC of the retirement of controlled bytes. Update the active stream + * status (the RXFC may now want to emit a frame granting more credit to + * the peer). + */ + OSSL_RTT_INFO rtt_info; + ossl_statm_get_rtt_info(ossl_quic_channel_get_statm(srv->ch), &rtt_info); + + if (!ossl_quic_rxfc_on_retire(&srv->stream0->rxfc, *bytes_read, + rtt_info.smoothed_rtt)) + return 0; + } + + if (is_fin) + srv->stream0->recv_fin_retired = 1; + + if (*bytes_read > 0) + ossl_quic_stream_map_update_state(ossl_quic_channel_get_qsm(srv->ch), + srv->stream0); + + return 1; +} + +int ossl_quic_tserver_write(QUIC_TSERVER *srv, + const unsigned char *buf, + size_t buf_len, + size_t *bytes_written) +{ + if (!ossl_quic_channel_is_active(srv->ch)) + return 0; + + if (!ossl_quic_sstream_append(srv->stream0->sstream, + buf, buf_len, bytes_written)) + return 0; + + if (*bytes_written > 0) + /* + * We have appended at least one byte to the stream. Potentially mark + * the stream as active, depending on FC. + */ + ossl_quic_stream_map_update_state(ossl_quic_channel_get_qsm(srv->ch), + srv->stream0); + + /* Try and send. */ + ossl_quic_tserver_tick(srv); + return 1; +}