]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add a test quicserver utility
authorMatt Caswell <matt@openssl.org>
Thu, 8 Jun 2023 11:18:38 +0000 (12:18 +0100)
committerPauli <pauli@openssl.org>
Tue, 27 Jun 2023 23:53:22 +0000 (09:53 +1000)
This QUIC server utility is intended for test purposes only and is expected
to be replaced in a future version of OpenSSL by s_server. At that point
it will be removed.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21204)

.gitignore
include/internal/quic_tserver.h
ssl/quic/quic_tserver.c
test/helpers/quictestlib.c
test/quic_multistream_test.c
test/quic_tserver_test.c
util/build.info
util/quicserver.c [new file with mode: 0644]

index be9517cb59222694e25312270f25d8fa896ef041..a6b0ab4de9bb0e637e3411f26c39e9e7b68fd0bd 100644 (file)
@@ -132,6 +132,7 @@ providers/common/include/prov/der_sm2.h
 /tools/c_rehash.pl
 /util/shlib_wrap.sh
 /util/wrap.pl
+/util/quicserver
 /tags
 /TAGS
 *.map
index a040a761fc41af77339ddd490bdc62849fba1fd2..d97199f6e6704b3ec0a88d842abc0848c4f28a8b 100644 (file)
@@ -15,6 +15,7 @@
 # include "internal/quic_stream.h"
 # include "internal/quic_channel.h"
 # include "internal/statem.h"
+# include "internal/time.h"
 
 # ifndef OPENSSL_NO_QUIC
 
@@ -39,6 +40,8 @@ typedef struct quic_tserver_args_st {
     BIO *net_rbio, *net_wbio;
     OSSL_TIME (*now_cb)(void *arg);
     void *now_cb_arg;
+    const unsigned char *alpn;
+    size_t alpnlen;
 } QUIC_TSERVER_ARGS;
 
 QUIC_TSERVER *ossl_quic_tserver_new(const QUIC_TSERVER_ARGS *args,
@@ -157,6 +160,27 @@ int ossl_quic_tserver_set_new_local_cid(QUIC_TSERVER *srv,
  */
 uint64_t ossl_quic_tserver_pop_incoming_stream(QUIC_TSERVER *srv);
 
+/*
+ * Returns 1 if all data sent on the given stream_id has been acked by the peer.
+ */
+int ossl_quic_tserver_is_stream_totally_acked(QUIC_TSERVER *srv,
+                                              uint64_t stream_id);
+
+/* Returns 1 if we are currently interested in reading data from the network */
+int ossl_quic_tserver_get_net_read_desired(QUIC_TSERVER *srv);
+
+/* Returns 1 if we are currently interested in writing data to the network */
+int ossl_quic_tserver_get_net_write_desired(QUIC_TSERVER *srv);
+
+/* Returns the next event deadline */
+OSSL_TIME ossl_quic_tserver_get_deadline(QUIC_TSERVER *srv);
+
+/*
+ * Shutdown the QUIC connection. Returns 1 if the connection is terminated and
+ * 0 otherwise.
+ */
+int ossl_quic_tserver_shutdown(QUIC_TSERVER *srv);
+
 # endif
 
 #endif
index 6fc341f3c4ecf2d58921454da308604435a1386e..0bfebbdb3f0fc35b326cc18ab6b9f10ceade5ef8 100644 (file)
@@ -11,6 +11,7 @@
 #include "internal/quic_channel.h"
 #include "internal/quic_statm.h"
 #include "internal/common.h"
+#include "internal/time.h"
 
 /*
  * QUIC Test Server Module
@@ -44,9 +45,22 @@ static int alpn_select_cb(SSL *ssl, const unsigned char **out,
                           unsigned char *outlen, const unsigned char *in,
                           unsigned int inlen, void *arg)
 {
-    static const unsigned char alpn[] = { 8, 'o', 's', 's', 'l', 't', 'e', 's', 't' };
+    QUIC_TSERVER *srv = arg;
+    static const unsigned char alpndeflt[] = {
+        8, 'o', 's', 's', 'l', 't', 'e', 's', 't'
+    };
+    static const unsigned char *alpn;
+    size_t alpnlen;
+
+    if (srv->args.alpn == NULL) {
+        alpn = alpndeflt;
+        alpnlen = sizeof(alpn);
+    } else {
+        alpn = srv->args.alpn;
+        alpnlen = srv->args.alpnlen;
+    }
 
-    if (SSL_select_next_proto((unsigned char **)out, outlen, alpn, sizeof(alpn),
+    if (SSL_select_next_proto((unsigned char **)out, outlen, alpn, alpnlen,
                               in, inlen) != OPENSSL_NPN_NEGOTIATED)
         return SSL_TLSEXT_ERR_ALERT_FATAL;
 
@@ -423,3 +437,48 @@ uint64_t ossl_quic_tserver_pop_incoming_stream(QUIC_TSERVER *srv)
 
     return qs->id;
 }
+
+int ossl_quic_tserver_is_stream_totally_acked(QUIC_TSERVER *srv,
+                                              uint64_t stream_id)
+{
+    QUIC_STREAM *qs;
+
+    qs = ossl_quic_stream_map_get_by_id(ossl_quic_channel_get_qsm(srv->ch),
+                                        stream_id);
+    if (qs == NULL)
+        return 1;
+
+    return ossl_quic_sstream_is_totally_acked(qs->sstream);
+}
+
+int ossl_quic_tserver_get_net_read_desired(QUIC_TSERVER *srv)
+{
+    return ossl_quic_reactor_net_read_desired(
+                ossl_quic_channel_get_reactor(srv->ch));
+}
+
+int ossl_quic_tserver_get_net_write_desired(QUIC_TSERVER *srv)
+{
+    return ossl_quic_reactor_net_write_desired(
+                ossl_quic_channel_get_reactor(srv->ch));
+}
+
+OSSL_TIME ossl_quic_tserver_get_deadline(QUIC_TSERVER *srv)
+{
+    return ossl_quic_reactor_get_tick_deadline(
+                ossl_quic_channel_get_reactor(srv->ch));
+}
+
+int ossl_quic_tserver_shutdown(QUIC_TSERVER *srv)
+{
+    ossl_quic_channel_local_close(srv->ch, 0);
+
+    /* TODO(QUIC): !SSL_SHUTDOWN_FLAG_NO_STREAM_FLUSH */
+
+    if (ossl_quic_channel_is_terminated(srv->ch))
+        return 1;
+
+    ossl_quic_reactor_tick(ossl_quic_channel_get_reactor(srv->ch), 0);
+
+    return ossl_quic_channel_is_terminated(srv->ch);
+}
index e289cfc056bdc603266f272ebf8a5816f51f95e3..76fc7674111b8dacecc1fcb415d02051ccb65914 100644 (file)
@@ -156,6 +156,7 @@ int qtest_create_quic_objects(OSSL_LIB_CTX *libctx, SSL_CTX *clientctx,
     tserver_args.libctx = libctx;
     tserver_args.net_rbio = sbio;
     tserver_args.net_wbio = fisbio;
+    tserver_args.alpn = NULL;
 
     if (!TEST_ptr(*qtserv = ossl_quic_tserver_new(&tserver_args, certfile,
                                                   keyfile)))
index 8c2f935ff4d3b6006759f20dda9e8b70c993d19b..dbd7b54e383d974855a3b0af70e8a5c4cca50138 100644 (file)
@@ -489,6 +489,7 @@ static int helper_init(struct helper *h, int free_order)
 
     s_args.net_rbio     = h->s_net_bio;
     s_args.net_wbio     = h->s_net_bio;
+    s_args.alpn         = NULL;
     s_args.now_cb       = get_time;
     s_args.now_cb_arg   = h;
 
index beed40d76c66a6d079388cecd735ef0a374dfa7f..74b73a919036394aa7eae720e6f0ad117ae5e0c0 100644 (file)
@@ -119,6 +119,7 @@ static int do_test(int use_thread_assist, int use_fake_time, int use_inject)
 
     tserver_args.net_rbio = s_net_bio;
     tserver_args.net_wbio = s_net_bio;
+    tserver_args.alpn = NULL;
     if (use_fake_time)
         tserver_args.now_cb = fake_now;
 
index c49d3e068debdaecd5c4443361df63d7d4e43b44..9ed12a8293f800a9eeea6b703d54e74cf6862690 100644 (file)
@@ -1,7 +1,14 @@
 IF[{- $target{build_scheme}->[1] eq "unix" -}]
- SCRIPTS{noinst}=shlib_wrap.sh
- SOURCE[shlib_wrap.sh]=shlib_wrap.sh.in
 SCRIPTS{noinst}=shlib_wrap.sh
 SOURCE[shlib_wrap.sh]=shlib_wrap.sh.in
 ENDIF
 SCRIPTS{noinst}=wrap.pl
 SOURCE[wrap.pl]=wrap.pl.in
 DEPEND[wrap.pl]=../configdata.pm
+
+IF[{- !$disabled{quic} -}]
+  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
new file mode 100644 (file)
index 0000000..42adee5
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * 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 <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/time.h"
+
+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);
+            sock = -1;
+            continue;
+        }
+
+        /* Set to non-blocking mode */
+        if (!BIO_socket_nbio(sock, 1)) {
+            BIO_closesocket(sock);
+            sock = -1;
+            continue;
+        }
+    }
+
+    /* Free the address information resources we allocated earlier */
+    BIO_ADDRINFO_free(res);
+
+    /* If sock is -1 then we've been unable to connect to the server */
+    if (sock == -1)
+        return NULL;
+
+    /* Create a BIO to wrap the socket*/
+    bio = BIO_new(BIO_s_datagram());
+    if (bio == NULL)
+        BIO_closesocket(sock);
+
+    /*
+     * 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)
+{
+    printf("quicserver [-6] hostname port certfile keyfile\n");
+}
+
+int main(int argc, char *argv[])
+{
+    QUIC_TSERVER_ARGS tserver_args = {0};
+    QUIC_TSERVER *qtserv = NULL;
+    int ipv6 = 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 *msg = "Hello world\n";
+    unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' };
+    int first = 1;
+
+    if (argc == 0)
+        return EXIT_FAILURE;
+
+    while (argnext < argc) {
+        if (argv[argnext][0] != '-')
+            break;
+        if (strcmp(argv[argnext], "-6") == 0) {
+            ipv6 = 1;
+        } else {
+            printf("Unrecognised argument %s\n", argv[argnext]);
+            usage();
+            return EXIT_FAILURE;
+        }
+        argnext++;
+    }
+
+    if (argc - argnext != 4) {
+        usage();
+        return EXIT_FAILURE;
+    }
+    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_free(bio);
+        printf("Unable to create server socket\n");
+        return EXIT_FAILURE;
+    }
+
+    tserver_args.libctx = NULL;
+    tserver_args.net_rbio = bio;
+    tserver_args.net_wbio = bio;
+    tserver_args.alpn = alpn;
+    tserver_args.alpnlen = sizeof(alpn);
+
+    qtserv = ossl_quic_tserver_new(&tserver_args, certfile, keyfile);
+    if (qtserv == NULL) {
+        printf("Failed to create the QUIC_TSERVER\n");
+        goto end;
+    }
+
+    printf("Starting quicserver\n");
+    printf("Note that this utility will be removed in a future OpenSSL version\n");
+    printf("For test purposes only. Not for use in a production environment\n");
+
+    /* Ownership of the BIO is passed to qtserv */
+    bio = NULL;
+
+    /* Read the request */
+    do {
+        if (first)
+            first = 0;
+        else
+            wait_for_activity(qtserv);
+
+        ossl_quic_tserver_tick(qtserv);
+
+        if (ossl_quic_tserver_read(qtserv, 0, reqbuf, sizeof(reqbuf),
+                                    &numbytes)) {
+            if (numbytes > 0) {
+                fwrite(reqbuf, 1, numbytes, stdout);
+            }
+            reqbytes += numbytes;
+        }
+    } while (reqbytes < sizeof(reqterm)
+             || memcmp(reqbuf + reqbytes - sizeof(reqterm), reqterm,
+                       sizeof(reqterm)) != 0);
+
+    /* Send the response */
+
+    ossl_quic_tserver_tick(qtserv);
+    if (!ossl_quic_tserver_write(qtserv, 0, (unsigned char *)msg, strlen(msg),
+                                 &numbytes))
+        goto end;
+
+    if (!ossl_quic_tserver_conclude(qtserv, 0))
+        goto end;
+
+    /* Wait until all data we have sent has been acked */
+    while (!ossl_quic_tserver_is_terminated(qtserv)
+           && !ossl_quic_tserver_is_stream_totally_acked(qtserv, 0)) {
+        ossl_quic_tserver_tick(qtserv);
+        wait_for_activity(qtserv);
+    }
+
+    while (!ossl_quic_tserver_shutdown(qtserv))
+        wait_for_activity(qtserv);
+
+    /* Close down here */
+
+    ret = EXIT_SUCCESS;
+ end:
+    /* Free twice because we did an up-ref */
+    BIO_free(bio);
+    BIO_free(bio);
+    ossl_quic_tserver_free(qtserv);
+    return ret;
+}