]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Adds a new demo blocking QUIC server for use with the existing demo QUIC clients
authorAndrew Dinh <andrewd@openssl.org>
Sun, 15 Sep 2024 19:26:47 +0000 (03:26 +0800)
committerNeil Horman <nhorman@openssl.org>
Mon, 17 Feb 2025 16:27:33 +0000 (11:27 -0500)
Remove util/quicserver.c

Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/25465)

.gitignore
demos/guide/Makefile
demos/guide/README.md
demos/guide/build.info
demos/guide/quic-client-block.c
demos/guide/quic-client-non-block.c
demos/guide/quic-server-block.c [new file with mode: 0644]
doc/man7/ossl-guide-quic-client-block.pod
doc/man7/ossl-guide-quic-client-non-block.pod
util/build.info
util/quicserver.c [deleted file]

index 23dfd7c8ee6ba96e98125f82e4f9a7a4ef540ee2..231e1765c2b9abfea407f383e56ef5269fba29c4 100644 (file)
@@ -207,7 +207,6 @@ providers/common/include/prov/der_ml_dsa.h
 /tools/c_rehash.pl
 /util/shlib_wrap.sh
 /util/wrap.pl
-/util/quicserver
 /tags
 /TAGS
 *.map
index 943d2f3ee78d3dd569fcedd65a597e922253207a..12e2efd5678806e14b1b96c6f7dad6c7d13e42d1 100644 (file)
@@ -9,6 +9,7 @@ TESTS = tls-client-block \
         quic-client-block \
         quic-multi-stream \
         tls-client-non-block \
+        quic-server-block \
         quic-client-non-block
 
 CFLAGS  = -I../../include -g -Wall
@@ -22,13 +23,14 @@ tls-server-block: tls-server-block.o
 quic-client-block: quic-client-block.o
 quic-multi-stream: quic-multi-stream.o
 tls-client-non-block: tls-client-non-block.o
+quic-server-block: quic-server-block.o
 quic-client-non-block: quic-client-non-block.o
 
 chain: chain.pem
 pkey.pem:
        openssl genpkey -algorithm rsa -out pkey.pem -pkeyopt rsa_keygen_bits:2048
 chain.pem: pkey.pem
-       openssl req -x509 -new -key pkey.pem -days 36500 -subj / -out chain.pem
+       openssl req -x509 -new -key pkey.pem -days 36500 -subj '/CN=localhost' -out chain.pem
 
 $(TESTS):
        $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
index 2e1bf05d8e7a82a2b66ad349e17e596bad57254f..835c12498959d7a2086049e42bb9f053f2236897 100644 (file)
@@ -51,25 +51,16 @@ The tls-client-non-block demo can be run in exactly the same way. Just replace
 Running the QUIC Demos
 ----------------------
 
-The QUIC demos can be run in a very similar way to the TLS demos. However, a
-different server implementation will need to be used.
+The QUIC demos can be run in a very similar way to the TLS demos.
 
-The OpenSSL source distribution includes a test QUIC server implementation for
-use with the demos. Note that, although this server does get built when building
-OpenSSL from source, it does not get installed via "make install". After
-building OpenSSL from source you will find the "quicserver" utility in the
-"util" sub-directory of the top of the build tree. This server utility is not
-suitable for production use and exists for test purposes only. It will be
-removed from a future version of OpenSSL.
+While in the demos directory the QUIC server can be run like this:
 
-While in the demos directory the quic server can be run like this:
-
-./../util/quicserver localhost 4443 servercert.pem serverkey.pem
+LD_LIBRARY_PATH=../.. ./quic-server-block 4443 ./chain.pem ./pkey.pem
 
 The QUIC demos can then be run in the same was as the TLS demos. For example
 to run the quic-client-block demo:
 
-SSL_CERT_FILE=rootcert.pem LD_LIBRARY_PATH=../.. ./quic-client-block localhost 4443
+SSL_CERT_FILE=chain.pem LD_LIBRARY_PATH=../.. ./quic-client-block localhost 4443
 
 Notes on the quic-hq-interop demo
 ---------------------------------
index 21219a34b5a554fb2704832976cd7979e8db1424..f5c62dcd67bd514a7470abcb315f56cb57c222b7 100644 (file)
@@ -9,7 +9,9 @@ PROGRAMS{noinst} = tls-client-block \
                    quic-multi-stream \
                    tls-client-non-block \
                    quic-client-non-block \
-                   quic-hq-interop
+                   quic-hq-interop \
+                   quic-server-block \
+                   quic-client-non-block
 
 INCLUDE[tls-client-block]=../../include
 SOURCE[tls-client-block]=tls-client-block.c
@@ -27,6 +29,10 @@ INCLUDE[tls-client-non-block]=../../include
 SOURCE[tls-client-non-block]=tls-client-non-block.c
 DEPEND[tls-client-non-block]=../../libcrypto ../../libssl
 
+INCLUDE[quic-server-block]=../../include
+SOURCE[quic-server-block]=quic-server-block.c
+DEPEND[quic-server-block]=../../libcrypto ../../libssl
+
 INCLUDE[quic-client-non-block]=../../include
 SOURCE[quic-client-non-block]=quic-client-non-block.c
 DEPEND[quic-client-non-block]=../../libcrypto ../../libssl
index baf5292c477992bf81ff719aa9d8a8f95e2dac02..beea29113860474d8a57f56b6d77613cc3122fb1 100644 (file)
@@ -243,7 +243,8 @@ int main(int argc, char *argv[])
         printf("Failed to write hostname in HTTP request\n");
         goto end;
     }
-    if (!SSL_write_ex(ssl, request_end, strlen(request_end), &written)) {
+    if (!SSL_write_ex2(ssl, request_end, strlen(request_end),
+                       SSL_WRITE_FLAG_CONCLUDE, &written)) {
         printf("Failed to write end of HTTP request\n");
         goto end;
     }
index 3e3627c5ed2a1694ceeea84cc7588b2ee576bdf1..6058a4d4c1c8dacaccfd9a8b0a0783de8816f5a1 100644 (file)
@@ -361,7 +361,8 @@ int main(int argc, char *argv[])
         printf("Failed to write hostname in HTTP request\n");
         goto end; /* Cannot retry: error */
     }
-    while (!SSL_write_ex(ssl, request_end, strlen(request_end), &written)) {
+    while (!SSL_write_ex2(ssl, request_end, strlen(request_end),
+                          SSL_WRITE_FLAG_CONCLUDE, &written)) {
         if (handle_io_failure(ssl, 0) == 1)
             continue; /* Retry */
         printf("Failed to write end of HTTP request\n");
diff --git a/demos/guide/quic-server-block.c b/demos/guide/quic-server-block.c
new file mode 100644 (file)
index 0000000..f451ce7
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ *  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
+ */
+
+/*
+ * NB: Changes to this file should also be reflected in
+ * doc/man7/ossl-guide-quic-server-block.pod
+ */
+
+#include <string.h>
+
+/* Include the appropriate header file for SOCK_STREAM */
+#ifdef _WIN32 /* Windows */
+# include <stdarg.h>
+# include <winsock2.h>
+#else /* Linux/Unix */
+# include <err.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <netinet/in.h>
+# include <unistd.h>
+#endif
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/quic.h>
+
+#ifdef _WIN32
+static const char *progname;
+
+static void vwarnx(const char *fmt, va_list ap)
+{
+    if (progname != NULL)
+        fprintf(stderr, "%s: ", progname);
+    vfprintf(stderr, fmt, ap);
+    putc('\n', stderr);
+}
+
+static void errx(int status, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vwarnx(fmt, ap);
+    va_end(ap);
+    exit(status);
+}
+
+static void warnx(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vwarnx(fmt, ap);
+    va_end(ap);
+}
+#endif
+
+/*
+ * ALPN strings for TLS handshake. Only 'http/1.0' and 'hq-interop'
+ * are accepted.
+ */
+static const unsigned char alpn_ossltest[] = {
+    8,  'h', 't', 't', 'p', '/', '1', '.', '0',
+    10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p',
+};
+
+/*
+ * This callback validates and negotiates the desired ALPN on the server side.
+ */
+static int 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_OK;
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ctx(const char *cert_path, const char *key_path)
+{
+    SSL_CTX *ctx;
+
+    /*
+     * An SSL_CTX holds shared configuration information for multiple
+     * subsequent per-client connections. We specifically load a QUIC
+     * server method here.
+     */
+    ctx = SSL_CTX_new(OSSL_QUIC_server_method());
+    if (ctx == NULL)
+        goto err;
+
+    /*
+     * Load the server's certificate *chain* file (PEM format), which includes
+     * not only the leaf (end-entity) server certificate, but also any
+     * intermediate issuer-CA certificates.  The leaf certificate must be the
+     * first certificate in the file.
+     *
+     * In advanced use-cases this can be called multiple times, once per public
+     * key algorithm for which the server has a corresponding certificate.
+     * However, the corresponding private key (see below) must be loaded first,
+     * *before* moving on to the next chain file.
+     *
+     * The requisite files "chain.pem" and "pkey.pem" can be generated by running
+     * "make chain" in this directory.  If the server will be executed from some
+     * other directory, move or copy the files there.
+     */
+    if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) {
+        fprintf(stderr, "couldn't load certificate file: %s\n", cert_path);
+        goto err;
+    }
+
+    /*
+     * Load the corresponding private key, this also checks that the private
+     * key matches the just loaded end-entity certificate.  It does not check
+     * whether the certificate chain is valid, the certificates could be
+     * expired, or may otherwise fail to form a chain that a client can validate.
+     */
+    if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) {
+        fprintf(stderr, "couldn't load key file: %s\n", key_path);
+        goto err;
+    }
+
+    /*
+     * Clients rarely employ certificate-based authentication, and so we don't
+     * require "mutual" TLS authentication (indeed there's no way to know
+     * whether or how the client authenticated the server, so the term "mutual"
+     * is potentially misleading).
+     *
+     * Since we're not soliciting or processing client certificates, we don't
+     * need to configure a trusted-certificate store, so no call to
+     * SSL_CTX_set_default_verify_paths() is needed.  The server's own
+     * certificate chain is assumed valid.
+     */
+    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
+
+    /* Setup ALPN negotiation callback to decide which ALPN is accepted. */
+    SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL);
+
+    return ctx;
+
+err:
+    SSL_CTX_free(ctx);
+    return NULL;
+}
+
+/* Create UDP socket on the given port. */
+static int create_socket(uint16_t port)
+{
+    int fd;
+    struct sockaddr_in sa = {0};
+
+    /* Retrieve the file descriptor for a new UDP socket */
+    if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+        fprintf(stderr, "cannot create socket");
+        goto err;
+    }
+
+    sa.sin_family = AF_INET;
+    sa.sin_port = htons(port);
+
+    /* Bind to the new UDP socket on localhost */
+    if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) {
+        fprintf(stderr, "cannot bind to %u\n", port);
+        BIO_closesocket(fd);
+        goto err;
+    }
+
+    return fd;
+
+err:
+    BIO_closesocket(fd);
+    return -1;
+}
+
+/*
+ * Main loop for server to accept QUIC connections.
+ * Echo every request back to the client.
+ */
+static int run_quic_server(SSL_CTX *ctx, int fd)
+{
+    int ok = 0;
+    SSL *listener, *conn;
+    unsigned char buf[8192];
+    size_t nread;
+    size_t nwritten;
+
+    /*
+     * Create a new QUIC listener. Listeners, and other QUIC objects, default
+     * to operating in blocking mode. The configured behaviour is inherited by
+     * child objects.
+     */
+    if ((listener = SSL_new_listener(ctx, 0)) == NULL)
+        goto err;
+
+    /* Provide the listener with our UDP socket. */
+    if (!SSL_set_fd(listener, fd))
+        goto err;
+
+    /* Begin listening. */
+    if (!SSL_listen(listener))
+        goto err;
+
+    /*
+     * Begin an infinite loop of listening for connections. We will only
+     * exit this loop if we encounter an error.
+     */
+    for (;;) {
+        /* Pristine error stack for each new connection */
+        ERR_clear_error();
+
+        /* Block while waiting for a client connection */
+        printf("Waiting for connection\n");
+        conn = SSL_accept_connection(listener, 0);
+        if (conn == NULL) {
+            fprintf(stderr, "error while accepting connection\n");
+            goto err;
+        }
+        printf("Accepted new connection\n");
+
+        /* Echo client input */
+        while (SSL_read_ex(conn, buf, sizeof(buf), &nread) > 0) {
+            if (SSL_write_ex(conn, buf, nread, &nwritten) > 0
+                && nwritten == nread)
+                break;
+            fprintf(stderr, "Error echoing client input");
+            break;
+        }
+
+        /* Signal the end of the stream. */
+        if (SSL_stream_conclude(conn, 0) != 1) {
+            fprintf(stderr, "Unable to conclude stream\n");
+            SSL_free(conn);
+            goto err;
+        }
+
+        /*
+         * Shut down the connection. We may need to call this multiple times
+         * to ensure the connection is shutdown completely.
+         */
+        while (SSL_shutdown(conn) != 1)
+            continue;
+
+        SSL_free(conn);
+    }
+
+err:
+    SSL_free(listener);
+    return ok;
+}
+
+/* Minimal QUIC HTTP/1.0 server. */
+int main(int argc, char *argv[])
+{
+    int res = EXIT_FAILURE;
+    SSL_CTX *ctx = NULL;
+    int fd;
+    unsigned long port;
+#ifdef _WIN32
+    static const char *progname;
+
+    progname = argv[0];
+#endif
+
+    if (argc != 4)
+        errx(res, "usage: %s <port> <server.crt> <server.key>", argv[0]);
+
+    /* Create SSL_CTX that supports QUIC. */
+    if ((ctx = create_ctx(argv[2], argv[3])) == NULL) {
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to create context");
+    }
+
+    /* Parse port number from command line arguments. */
+    port = strtoul(argv[1], NULL, 0);
+    if (port == 0 || port > UINT16_MAX) {
+        SSL_CTX_free(ctx);
+        errx(res, "Failed to parse port number");
+    }
+
+    /* Create and bind a UDP socket. */
+    if ((fd = create_socket((uint16_t)port)) < 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to create socket");
+    }
+
+    /* QUIC server connection acceptance loop. */
+    if (!run_quic_server(ctx, fd)) {
+        SSL_CTX_free(ctx);
+        BIO_closesocket(fd);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error in QUIC server loop");
+    }
+
+    /* Free resources. */
+    SSL_CTX_free(ctx);
+    BIO_closesocket(fd);
+    res = EXIT_SUCCESS;
+    return res;
+}
index ab018e4a220b8500d72dc67adb78a3c912060570..5a91f76d044ad4ef969fcfa5edab382e3055e35a 100644 (file)
@@ -250,6 +250,29 @@ slightly differently. With QUIC the stream can be reset by the peer (which is
 fatal for that stream), but the underlying connection itself may still be
 healthy.
 
+First, we write the entire request to the stream. We also must make sure to
+signal to the server that we have finished writing. This can be done by passing
+the SSL_WRITE_FLAG_CONCLUDE flag to L<SSL_write_ex2(3)> or by calling
+L<SSL_stream_conclude(3)>. Since the first way is more efficient, we choose to
+do that.
+
+    /* Write an HTTP GET request to the peer */
+    if (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) {
+        printf("Failed to write start of HTTP request\n");
+        goto end;
+    }
+    if (!SSL_write_ex(ssl, hostname, strlen(hostname), &written)) {
+        printf("Failed to write hostname in HTTP request\n");
+        goto end;
+    }
+    if (!SSL_write_ex2(ssl, request_end, strlen(request_end),
+        SSL_WRITE_FLAG_CONCLUDE, &written)) {
+        printf("Failed to write end of HTTP request\n");
+        goto end;
+    }
+
+Then, we read the response from the server.
+
     /*
      * Get up to sizeof(buf) bytes of the response. We keep reading until the
      * server closes the connection.
index f07e9cc6692535a73ee9036f5cd7542f15fda5f6..d51719b06fdebd63e1bb51035dba6425ac690fd6 100644 (file)
@@ -344,6 +344,12 @@ contains that data may change location. See L<SSL_CTX_set_mode(3)> for further
 details. As in the TLS tutorials (L<ossl-guide-tls-client-block(7)>) we write
 the request in three chunks.
 
+First, we write the entire request to the stream. We also must make sure to
+signal to the server that we have finished writing. This can be done by passing
+the SSL_WRITE_FLAG_CONCLUDE flag to L<SSL_write_ex2(3)> or by calling
+L<SSL_stream_conclude(3)>. Since the first way is more efficient, we choose to
+do that.
+
     /* Write an HTTP GET request to the peer */
     while (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) {
         if (handle_io_failure(ssl, 0) == 1)
@@ -357,7 +363,8 @@ the request in three chunks.
         printf("Failed to write hostname in HTTP request\n");
         goto end; /* Cannot retry: error */
     }
-    while (!SSL_write_ex(ssl, request_end, strlen(request_end), &written)) {
+    while (!SSL_write_ex2(ssl, request_end, strlen(request_end),
+           SSL_WRITE_FLAG_CONCLUDE, &written)) {
         if (handle_io_failure(ssl, 0) == 1)
             continue; /* Retry */
         printf("Failed to write end of HTTP request\n");
index e4aab44b38038e1d53359476e6682b04d4e2269b..aad7c50fee3fd35ab0a3ea43c066aa8ee723e924 100644 (file)
@@ -5,10 +5,3 @@ ENDIF
 SCRIPTS{noinst}=wrap.pl
 SOURCE[wrap.pl]=wrap.pl.in
 DEPEND[wrap.pl]=../configdata.pm
-
-IF[{- !$disabled{quic} && !$disabled{stdio} && !$disabled{apps} -}]
-  PROGRAMS{noinst}=quicserver
-  SOURCE[quicserver]=quicserver.c
-  INCLUDE[quicserver]=../include ../apps/include
-  DEPEND[quicserver]=../libcrypto.a ../libssl.a
-ENDIF
diff --git a/util/quicserver.c b/util/quicserver.c
deleted file mode 100644 (file)
index d752340..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright 2023 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
- */
-
-/*
- * This is a temporary test server for QUIC. It will eventually be replaced
- * by s_server and removed once we have full QUIC server support.
- */
-
-#include <stdio.h>
-#include <openssl/bio.h>
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include "internal/e_os.h"
-#include "internal/sockets.h"
-#include "internal/quic_tserver.h"
-#include "internal/quic_stream_map.h"
-#include "internal/time.h"
-
-static BIO *bio_err = NULL;
-
-static void wait_for_activity(QUIC_TSERVER *qtserv)
-{
-    fd_set readfds, writefds;
-    fd_set *readfdsp = NULL, *writefdsp = NULL;
-    struct timeval timeout, *timeoutp = NULL;
-    int width;
-    int sock;
-    BIO *bio = ossl_quic_tserver_get0_rbio(qtserv);
-    OSSL_TIME deadline;
-
-    BIO_get_fd(bio, &sock);
-
-    if (ossl_quic_tserver_get_net_read_desired(qtserv)) {
-        readfdsp = &readfds;
-        FD_ZERO(readfdsp);
-        openssl_fdset(sock, readfdsp);
-    }
-
-    if (ossl_quic_tserver_get_net_write_desired(qtserv)) {
-        writefdsp = &writefds;
-        FD_ZERO(writefdsp);
-        openssl_fdset(sock, writefdsp);
-    }
-
-    deadline = ossl_quic_tserver_get_deadline(qtserv);
-
-    if (!ossl_time_is_infinite(deadline)) {
-        timeout = ossl_time_to_timeval(ossl_time_subtract(deadline,
-                                                          ossl_time_now()));
-        timeoutp = &timeout;
-    }
-
-    width = sock + 1;
-
-    if (readfdsp == NULL && writefdsp == NULL && timeoutp == NULL)
-        return;
-
-    select(width, readfdsp, writefdsp, NULL, timeoutp);
-}
-
-/* Helper function to create a BIO connected to the server */
-static BIO *create_dgram_bio(int family, const char *hostname, const char *port)
-{
-    int sock = -1;
-    BIO_ADDRINFO *res;
-    const BIO_ADDRINFO *ai = NULL;
-    BIO *bio;
-
-    if (BIO_sock_init() != 1)
-        return NULL;
-
-    /*
-     * Lookup IP address info for the server.
-     */
-    if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_SERVER, family, SOCK_DGRAM,
-                       0, &res))
-        return NULL;
-
-    /*
-     * Loop through all the possible addresses for the server and find one
-     * we can create and start listening on
-     */
-    for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) {
-        /* Create the UDP socket */
-        sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0);
-        if (sock == -1)
-            continue;
-
-        /* Start listening on the socket */
-        if (!BIO_listen(sock, BIO_ADDRINFO_address(ai), 0)) {
-            BIO_closesocket(sock);
-            continue;
-        }
-
-        /* Set to non-blocking mode */
-        if (!BIO_socket_nbio(sock, 1)) {
-            BIO_closesocket(sock);
-            continue;
-        }
-
-        break; /* stop searching if we found an addr */
-    }
-
-    /* Free the address information resources we allocated earlier */
-    BIO_ADDRINFO_free(res);
-
-    /* If we didn't bind any sockets, fail */
-    if (ai == NULL)
-        return NULL;
-
-    /* Create a BIO to wrap the socket */
-    bio = BIO_new(BIO_s_datagram());
-    if (bio == NULL) {
-        BIO_closesocket(sock);
-        return NULL;
-    }
-
-    /*
-     * Associate the newly created BIO with the underlying socket. By
-     * passing BIO_CLOSE here the socket will be automatically closed when
-     * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which
-     * case you must close the socket explicitly when it is no longer
-     * needed.
-     */
-    BIO_set_fd(bio, sock, BIO_CLOSE);
-
-    return bio;
-}
-
-static void usage(void)
-{
-    BIO_printf(bio_err, "quicserver [-6][-trace] hostname port certfile keyfile\n");
-}
-
-int main(int argc, char *argv[])
-{
-    QUIC_TSERVER_ARGS tserver_args = {0};
-    QUIC_TSERVER *qtserv = NULL;
-    int ipv6 = 0, trace = 0;
-    int argnext = 1;
-    BIO *bio = NULL;
-    char *hostname, *port, *certfile, *keyfile;
-    int ret = EXIT_FAILURE;
-    unsigned char reqbuf[1024];
-    size_t numbytes, reqbytes = 0;
-    const char reqterm[] = {
-        '\r', '\n', '\r', '\n'
-    };
-    const char *response[] = {
-        "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>Hello world</body>\n</html>\n",
-        "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>Hello again</body>\n</html>\n",
-        "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>Another response</body>\n</html>\n",
-        "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>A message</body>\n</html>\n",
-    };
-    unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' };
-    int first = 1;
-    uint64_t streamid;
-    size_t respnum = 0;
-
-    bio_err = BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT);
-    if (argc == 0 || bio_err == NULL)
-        goto end2;
-
-    while (argnext < argc) {
-        if (argv[argnext][0] != '-')
-            break;
-        if (strcmp(argv[argnext], "-6") == 0) {
-            ipv6 = 1;
-        } else if(strcmp(argv[argnext], "-trace") == 0) {
-            trace = 1;
-        } else {
-            BIO_printf(bio_err, "Unrecognised argument %s\n", argv[argnext]);
-            usage();
-            goto end2;
-        }
-        argnext++;
-    }
-
-    if (argc - argnext != 4) {
-        usage();
-        goto end2;
-    }
-    hostname = argv[argnext++];
-    port = argv[argnext++];
-    certfile = argv[argnext++];
-    keyfile = argv[argnext++];
-
-    bio = create_dgram_bio(ipv6 ? AF_INET6 : AF_INET, hostname, port);
-    if (bio == NULL || !BIO_up_ref(bio)) {
-        BIO_printf(bio_err, "Unable to create server socket\n");
-        goto end2;
-    }
-
-    tserver_args.libctx = NULL;
-    tserver_args.net_rbio = bio;
-    tserver_args.net_wbio = bio;
-    tserver_args.alpn = alpn;
-    tserver_args.alpnlen = sizeof(alpn);
-    tserver_args.ctx = NULL;
-
-    qtserv = ossl_quic_tserver_new(&tserver_args, certfile, keyfile);
-    if (qtserv == NULL) {
-        BIO_printf(bio_err, "Failed to create the QUIC_TSERVER\n");
-        goto end;
-    }
-
-    BIO_printf(bio_err, "Starting quicserver\n");
-    BIO_printf(bio_err,
-               "Note that this utility will be removed in a future OpenSSL version.\n");
-    BIO_printf(bio_err,
-               "For test purposes only. Not for use in a production environment.\n");
-
-    /* Ownership of the BIO is passed to qtserv */
-    bio = NULL;
-
-    if (trace)
-#ifndef OPENSSL_NO_SSL_TRACE
-        ossl_quic_tserver_set_msg_callback(qtserv, SSL_trace, bio_err);
-#else
-        BIO_printf(bio_err,
-                   "Warning: -trace specified but no SSL tracing support present\n");
-#endif
-
-    /* Wait for handshake to complete */
-    ossl_quic_tserver_tick(qtserv);
-    while(!ossl_quic_tserver_is_handshake_confirmed(qtserv)) {
-        wait_for_activity(qtserv);
-        ossl_quic_tserver_tick(qtserv);
-        if (ossl_quic_tserver_is_terminated(qtserv)) {
-            BIO_printf(bio_err, "Failed waiting for handshake completion\n");
-            ret = EXIT_FAILURE;
-            goto end;
-        }
-    }
-
-    for (;; respnum++) {
-        if (respnum >= OSSL_NELEM(response))
-            goto end;
-        /* Wait for an incoming stream */
-        do {
-            streamid = ossl_quic_tserver_pop_incoming_stream(qtserv);
-            if (streamid == UINT64_MAX)
-                wait_for_activity(qtserv);
-            ossl_quic_tserver_tick(qtserv);
-            if (ossl_quic_tserver_is_terminated(qtserv)) {
-                /* Assume we finished everything the clients wants from us */
-                ret = EXIT_SUCCESS;
-                goto end;
-            }
-        } while(streamid == UINT64_MAX);
-
-        /* Read the request */
-        do {
-            if (first)
-                first = 0;
-            else
-                wait_for_activity(qtserv);
-
-            ossl_quic_tserver_tick(qtserv);
-            if (ossl_quic_tserver_is_terminated(qtserv)) {
-                BIO_printf(bio_err, "Failed reading request\n");
-                ret = EXIT_FAILURE;
-                goto end;
-            }
-
-            if (ossl_quic_tserver_read(qtserv, streamid, reqbuf + reqbytes,
-                                    sizeof(reqbuf) - reqbytes,
-                                    &numbytes)) {
-                if (numbytes > 0)
-                    fwrite(reqbuf + reqbytes, 1, numbytes, stdout);
-                reqbytes += numbytes;
-            }
-        } while (reqbytes < sizeof(reqterm)
-                || memcmp(reqbuf + reqbytes - sizeof(reqterm), reqterm,
-                        sizeof(reqterm)) != 0);
-
-        if ((streamid & QUIC_STREAM_DIR_UNI) != 0) {
-            /*
-            * Incoming stream was uni-directional. Create a server initiated
-            * uni-directional stream for the response.
-            */
-            if (!ossl_quic_tserver_stream_new(qtserv, 1, &streamid)) {
-                BIO_printf(bio_err, "Failed creating response stream\n");
-                goto end;
-            }
-        }
-
-        /* Send the response */
-
-        ossl_quic_tserver_tick(qtserv);
-        if (!ossl_quic_tserver_write(qtserv, streamid,
-                                    (unsigned char *)response[respnum],
-                                    strlen(response[respnum]), &numbytes))
-            goto end;
-
-        if (!ossl_quic_tserver_conclude(qtserv, streamid))
-            goto end;
-    }
-
- end:
-    /* Free twice because we did an up-ref */
-    BIO_free(bio);
- end2:
-    BIO_free(bio);
-    ossl_quic_tserver_free(qtserv);
-    BIO_free(bio_err);
-    return ret;
-}