summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--include/internal/quic_tserver.h24
-rw-r--r--ssl/quic/quic_tserver.c63
-rw-r--r--test/helpers/quictestlib.c1
-rw-r--r--test/quic_multistream_test.c1
-rw-r--r--test/quic_tserver_test.c1
-rw-r--r--util/build.info11
-rw-r--r--util/quicserver.c253
8 files changed, 351 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore
index be9517cb59..a6b0ab4de9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/include/internal/quic_tserver.h b/include/internal/quic_tserver.h
index a040a761fc..d97199f6e6 100644
--- a/include/internal/quic_tserver.h
+++ b/include/internal/quic_tserver.h
@@ -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
diff --git a/ssl/quic/quic_tserver.c b/ssl/quic/quic_tserver.c
index 6fc341f3c4..0bfebbdb3f 100644
--- a/ssl/quic/quic_tserver.c
+++ b/ssl/quic/quic_tserver.c
@@ -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);
+}
diff --git a/test/helpers/quictestlib.c b/test/helpers/quictestlib.c
index e289cfc056..76fc767411 100644
--- a/test/helpers/quictestlib.c
+++ b/test/helpers/quictestlib.c
@@ -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)))
diff --git a/test/quic_multistream_test.c b/test/quic_multistream_test.c
index 8c2f935ff4..dbd7b54e38 100644
--- a/test/quic_multistream_test.c
+++ b/test/quic_multistream_test.c
@@ -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;
diff --git a/test/quic_tserver_test.c b/test/quic_tserver_test.c
index beed40d76c..74b73a9190 100644
--- a/test/quic_tserver_test.c
+++ b/test/quic_tserver_test.c
@@ -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;
diff --git a/util/build.info b/util/build.info
index c49d3e068d..9ed12a8293 100644
--- a/util/build.info
+++ b/util/build.info
@@ -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
index 0000000000..42adee531c
--- /dev/null
+++ b/util/quicserver.c
@@ -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;
+}