Add a test quicserver utility

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)
This commit is contained in:
Matt Caswell 2023-06-08 12:18:38 +01:00 committed by Pauli
parent 956b4c75dc
commit 37f27b91de
8 changed files with 351 additions and 4 deletions

1
.gitignore vendored
View 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

View 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

View 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 (SSL_select_next_proto((unsigned char **)out, outlen, alpn, sizeof(alpn),
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, 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);
}

View 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)))

View 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;

View 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;

View 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

253
util/quicserver.c Normal file
View File

@ -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;
}