From: sashan Date: Sat, 26 Apr 2025 15:31:36 +0000 (+0200) Subject: initial implementation of http/1.0 server to benchmark OpenSSL QUIC X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=f426dd1311eecd12f24190c94f56eb85e62aaa27;p=thirdparty%2Fopenssl.git initial implementation of http/1.0 server to benchmark OpenSSL QUIC stack. The server currently replies with HTTP 200 OK only. It provides text/plain response body. It only accepts GET request with any URI. Any other requests will make server to drop stream/connection. Reviewed-by: Tomas Mraz Reviewed-by: Neil Horman (Merged from https://github.com/openssl/openssl/pull/27508) --- diff --git a/demos/quic/build.info b/demos/quic/build.info index d9ef4c7d4e3..476731129cd 100644 --- a/demos/quic/build.info +++ b/demos/quic/build.info @@ -1 +1 @@ -SUBDIRS=server +SUBDIRS=server poll-server diff --git a/demos/quic/poll-server/Makefile b/demos/quic/poll-server/Makefile new file mode 100644 index 00000000000..75609f1b83a --- /dev/null +++ b/demos/quic/poll-server/Makefile @@ -0,0 +1,26 @@ +# +# To run the demo when linked with a shared library (default) ensure that +# libcrypto and libssl are on the library path. For example: +# +# LD_LIBRARY_PATH=../../.. ./server 4444 \ +# ../../../test/certs/servercert.pem \ +# ../../../test/certs/serverkey.pem +# +CFLAGS += -I../../../include -g -Wall -Wsign-compare +LDFLAGS += -L../../.. +LDLIBS = -lcrypto -lssl + +.PHONY: all server clean run s_client + +all: quic-server-ssl-poll-http + +quic-server-ssl-poll-http: quic-server-ssl-poll-http.c + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + $(RM) server *.o + +run: quic-server-ssl-poll-http + LD_LIBRARY_PATH=../../.. ./quic-server-ssl-poll-http 4444 \ + ../../../test/certs/servercert.pem \ + ../../../test/certs/serverkey.pem diff --git a/demos/quic/poll-server/build.info b/demos/quic/poll-server/build.info new file mode 100644 index 00000000000..f6b3ffccc13 --- /dev/null +++ b/demos/quic/poll-server/build.info @@ -0,0 +1,14 @@ +# +# To run the demo when linked with a shared library (default) ensure that +# libcrypto and libssl are on the library path. For example: +# +# LD_LIBRARY_PATH=../../.. ./quic-server-ssl-poll-http 4444 \ +# ../../../test/certs/servercert.pem \ +# ../../../test/certs/serverkey.pem + + +PROGRAMS{noinst} = quic-server-ssl-poll-http + +INCLUDE[quic-server-ssl-poll-http]=../../../include +SOURCE[quic-server-ssl-poll-http]=quic-server-ssl-poll-http.c +DEPEND[quic-server-ssl-poll-http]=../../../libcrypto ../../../libssl diff --git a/demos/quic/poll-server/quic-server-ssl-poll-http.c b/demos/quic/poll-server/quic-server-ssl-poll-http.c new file mode 100644 index 00000000000..747d00754f7 --- /dev/null +++ b/demos/quic/poll-server/quic-server-ssl-poll-http.c @@ -0,0 +1,1955 @@ +/* + * Copyright 2024-2025 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 + */ + +/* + * Implementation of hq-interop and http/1.0 QUIC server built on top of + * SSL_poll(3ossl). The server is able to deal with simultaneous connections + * All I/O operations are non-blocking. + * + * The only supported request is get. No HTTP errors are sent back. If + * request is not understood the channel is reset. + * + * If client sends request for example GET /foo_1024.txt the server + * replies with HTTP/1.0 200 OK with plain-text body. The body is + * 1k of text foo_1024.txtfoo_1024,txtfoo... + * + * To run the server use command as follows: + * ./quic-server-ssl-poll-http 8080 chain.pem pkey.pem + * command above starts server which listens to port localhost:8080 + * Although the server is simple one can use it with tquic_client + * found at https://tquic.net/. Other 3rd party clients were not tested yet. + * + * The main() function creates instance of poll_manager which manages + * all events (poll_event). If all is good execution calls to + * run_quic_server() function where QUIC listener is created and passed + * to manager. Then the polling loop is entered to serve remote clients. + * + * Some naming conventions are followed in code: + * - pe is used for poll event, it can be associated with listener, + * connection or stream. + * - pec is used for poll event which handles connection + * - pes is used for poll event which handles stream + * + * there is also response buffer which helps server to dispatch reply: + * - rb is used for any response buffer type + * - rts is used for simple text buffer (just data with no HTTP/1.0 + * headers) + * - rtf is used for response text buffers which carry HTTP/1.0 headers + * (response text full) + */ +#include +#include +#include +#include +#include +#include + +/* Include the appropriate header file for SOCK_STREAM */ +#ifdef _WIN32 /* Windows */ +# include +# include +#else /* Linux/Unix */ +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include + +#include "internal/list.h" + +/* + * This is a simple non-blocking QUIC HTTP/1.0 server application. + * Server accepts QUIC connections. It then accepts bi-directional + * stream from client and reads request. By default it sends + * 12345 bytes back as HHTTP/1.0 response to any GET request. + * If GET request comes with URL for example as follows: + * /foo/bar/file_65535.txt + * then the server sends 64kB of data in HTTP/1.0 response. + */ + +#ifdef DEBUG +# define DPRINTF fprintf +#else +# define DPRINTF(...) (void)(0) +#endif + +/* + * format string so we can print SSL_poll() events in more informative + * way. To print events one does this: + * int events = SSL_POLL_EVENT_F | SSL_POLL_EVENT_R; + * printf("%s We got events: " POLL_FMT "\n", __func__, POLL_PRINTA(events)); + */ +#define POLL_FMT "%s%s%s%s%s%s%s%s%s%s%s%s%s" +#define POLL_PRINTA(_revents_) \ + (_revents_) & SSL_POLL_EVENT_F ? "SSL_POLL_EVENT_F " : "", \ + (_revents_) & SSL_POLL_EVENT_EL ? "SSL_POLL_EVENT_EL" : "", \ + (_revents_) & SSL_POLL_EVENT_EC ? "SSL_POLL_EVENT_EC " : "", \ + (_revents_) & SSL_POLL_EVENT_ECD ? "SSL_POLL_EVENT_ECD " : "", \ + (_revents_) & SSL_POLL_EVENT_ER ? "SSL_POLL_EVENT_ER " : "", \ + (_revents_) & SSL_POLL_EVENT_EW ? "SSL_POLL_EVENT_EW " : "", \ + (_revents_) & SSL_POLL_EVENT_R ? "SSL_POLL_EVENT_R " : "", \ + (_revents_) & SSL_POLL_EVENT_W ? "SSL_POLL_EVENT_W " : "", \ + (_revents_) & SSL_POLL_EVENT_IC ? "SSL_POLL_EVENT_IC " : "", \ + (_revents_) & SSL_POLL_EVENT_ISB ? "SSL_POLL_EVENT_ISB " : "", \ + (_revents_) & SSL_POLL_EVENT_ISU ? "SSL_POLL_EVENT_ISU " : "", \ + (_revents_) & SSL_POLL_EVENT_OSB ? "SSL_POLL_EVENT_OSB " : "", \ + (_revents_) & SSL_POLL_EVENT_OSU ? "SSL_POLL_EVENT_OSU " : "" + +/* + * every poll_event structure has members enumerated here in poll_event_base + * The poll events are kept in list which is managed by poll_manager instance. + * However SSL_poll(9ossl) expects an array of SSL_POLL_ITEM structures. Therefore + * the poll manager needs to construct array of poll_event structures from list. + * We get two copies of poll_event structures: + * - one copy is held in list (original) + * - one copy is held in array (copy for SSL_poll()) + * We use pe_self member to easily reach from SSL_poll() copy instance back to + * original managed by poll manager and used by our application. + * There are four callbacks: + * - pe_cb_in() - triggered when inbound data/stream/connection is coming + * - pe_cb_out() - triggered when outbound data/stream/connection is coming + * - pe_cb_error() - triggered when polled object enters an error state + * - pe_cb_destroy() - this a destructor, application destroy pe_appdata + * The remaining members are rather self explanatory. + */ +#define poll_event_base \ + SSL_POLL_ITEM pe_poll_item; \ + OSSL_LIST_MEMBER(pe, struct poll_event);\ + uint64_t pe_want_events; \ + uint64_t pe_want_mask; \ + struct poll_manager *pe_my_pm; \ + unsigned char pe_type; \ + struct poll_event *pe_self; \ + void *pe_appdata; \ + int(*pe_cb_in)(struct poll_event *); \ + int(*pe_cb_out)(struct poll_event *); \ + int(*pe_cb_error)(struct poll_event *); \ + void(*pe_cb_ondestroy)(struct poll_event *) + +struct poll_event { + poll_event_base; +}; + +struct poll_event_listener { + poll_event_base; +}; + +/* + * Each poll_event holds pe_appdata context. The poll_event is associated with + * SSL object which is typically QUIC stream. There are two types of streams + * in QUIC: + * - uni-directional (simplex) + * - bi-directional (duplex) + * bi-directional are easy to handle: we create them, then we read request + * from client and write reply back. Then stream gets destroyed. + * This request-reply handling is more tricky with uni-directional streams. + * We need a pair of streams: server reads a request from one stream and + * then must create a stream for reply. For echo-reply server we need to + * pass data we read from input stream to output stream. The poll_event_context + * here is to do it for us. The echo-reply server handling with simplex + * (unidirectional) streams goes as follows: + * - we read data from input stream and parse request + * - then we request poll manager to create an outbound stream, + * at this point we also create a response (response_buffer). + * the response buffer is added to connection. + * - connection keeps list of responses to be dispatched because + * client may establish more streams to send more requests + * - once outbound stream is created, poll manager moves response + * connection to outbound stream. + */ +struct poll_event_context { + OSSL_LIST_MEMBER(peccx, struct poll_event_context); + void *peccx; + void(*peccx_cb_ondestroy)(void *); +}; + +DEFINE_LIST_OF(pe, struct poll_event); + +DEFINE_LIST_OF(peccx, struct poll_event_context); + +/* + * It facilitates transfer of app data from one stream to the other. + * There are two lists: + * - pec_stream_cx for bi-directioanl streams (this list is unused + * currently). + * - pec_unistream_cx for uni-direcitonal (simplex) streams. + * + * Then there are two counters: + * - pec_want_stream bumbped up when application requests duplex stream, + * bumped down when stream is created + * - pec_want_unistream bumped up when application requests simplex stream. + * bumped down when stream is created + */ +struct poll_event_connection { + poll_event_base; + OSSL_LIST(peccx) pec_stream_cx; + OSSL_LIST(peccx) pec_unistream_cx; + uint64_t pec_want_stream; + uint64_t pec_want_unistream; +}; + +/* + * We always allocate more slots than we need. If POLL_GROW slots get + * deplepted then we allocate POLL_GROW more for the next one. + * Downsizing is similar. This is very naive and leads to oscillations + * (where we may end up freeing and reallocating poll event set) we need to + * figure out better strategy. + */ +#define POLL_GROW 20 +#define POLL_DOWNSIZ 20 + +/* + * Members in poll manager deserve some explanation: + * - pm_head, holds a list of poll_event structures (connections and + * streams) + * - pm_event_count number of events to montior in SSL_poll(3ossl) + * - pm_poll_set array of events to poll on + * - pm_poll_set_sz number of slots (space) available in pm_poll_set + * - pm_need_rebuild whenever list of events to monitor in a list changes + * poll mamnager must rebuild pm_poll_set + * - pm_continue a flag indicates whether event loop should continue to + * run or if it should be terminated (pm_continue == 0) + * - pm_wconn_in() callback fires when there is a new connection + * - pm qconn + */ +struct poll_manager { + OSSL_LIST(pe) pm_head; + unsigned int pm_event_count; + struct poll_event *pm_poll_set; + unsigned int pm_poll_set_sz; + int pm_need_rebuild; + int pm_continue; +}; + +#define SSL_POLL_ERROR (SSL_POLL_EVENT_F | SSL_POLL_EVENT_EL | \ + SSL_POLL_EVENT_EC | SSL_POLL_EVENT_ECD | \ + SSL_POLL_EVENT_ER | SSL_POLL_EVENT_EW) + +#define SSL_POLL_IN (SSL_POLL_EVENT_R | SSL_POLL_EVENT_IC | \ + SSL_POLL_EVENT_ISB | SSL_POLL_EVENT_ISU) + +#define SSL_POLL_OUT (SSL_POLL_EVENT_W | SSL_POLL_EVENT_OSB | \ + SSL_POLL_EVENT_OSU) + +struct poll_event_stream { + poll_event_base; + struct poll_event_connection *pes_conn; + char *pes_wpos; + unsigned int pes_wpos_sz; + int pes_got_request; + char pes_reqbuf[8192]; +}; + +/* + * Response buffer. + */ +enum { + RB_TYPE_NONE, + RB_TYPE_TEXT_SIMPLE, + RB_TYPE_TEXT_FULL +}; +#define response_buffer_base \ + unsigned char rb_type; \ + unsigned int rb_rpos; \ + void (*rb_advrpos_cb)(struct response_buffer *, unsigned int);\ + unsigned int (*rb_read_cb)(struct response_buffer *, char *, \ + unsigned int); \ + int (*rb_eof_cb)(struct response_buffer *); \ + void (*rb_ondestroy_cb)(struct response_buffer *) + +struct response_buffer { + response_buffer_base; +}; + +struct response_txt_simple { + response_buffer_base; + char *rts_pattern; + unsigned int rts_pattern_len; + unsigned int rts_len; +}; + +struct response_txt_full { + response_buffer_base; + char rtf_headers[1024]; + char *rtf_pattern; + unsigned int rtf_pattern_len; + unsigned int rtf_hdr_len; + unsigned int rtf_len; /* headers + data */ +}; + +static void destroy_pe(struct poll_event *); +static int pe_return_error(struct poll_event *); +static void pe_return_void(struct poll_event *); + +#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); +} + +/* + * we can get away with this mock-up on windows. + * we generate payload for any URL we obtain in + * GET request. mock-up is good enough for us. + */ +static char * +basename(char *path) +{ + return path; +} + +# define strncasecmp(_a_, _b_, _c_) _strnicmp((_a_), (_b_), (_c_)) + +# define ctime_r(_t_, _b_) ctime_s((_b_), sizeof ((_b_)), (_t_)) + +#endif + +enum pe_types { + PE_NONE, + PE_LISTENER, + PE_CONNECTION, + PE_STREAM, + PE_STREAM_UNI_IN, + PE_STREAM_UNI_OUT, + PE_INVALID +}; + +static struct response_txt_simple * +rb_to_txt_simple(struct response_buffer *rb) +{ + if (rb == NULL || rb->rb_type != RB_TYPE_TEXT_SIMPLE) + return NULL; + + return (struct response_txt_simple *)rb; +} + +static struct response_txt_full * +rb_to_txt_full(struct response_buffer *rb) +{ + if (rb == NULL || rb->rb_type != RB_TYPE_TEXT_FULL) + return NULL; + + return (struct response_txt_full *)rb; +} + +static void +rb_advrpos_cb(struct response_buffer *rb, unsigned int rpos) +{ + /* we assume base response_buffer is unlimited */ + rb->rb_rpos += rpos; +} + +static void +rb_ondestroy_cb(struct response_buffer *rb) +{ + OPENSSL_free(rb); +} + +static unsigned int +rb_null_read_cb(struct response_buffer *rb, char *buf, unsigned int buf_sz) +{ + return 0; +} + +static int +rb_eof_cb(struct response_buffer *rb) +{ + return 1; +} + +static void +rb_init(struct response_buffer *rb) +{ + rb->rb_type = RB_TYPE_NONE; + rb->rb_advrpos_cb = rb_advrpos_cb; + rb->rb_read_cb = rb_null_read_cb; + rb->rb_eof_cb = rb_eof_cb; + rb->rb_ondestroy_cb = rb_ondestroy_cb; + rb->rb_rpos = 0; +} + +static void +rb_advrpos(struct response_buffer *rb, unsigned int rpos) +{ + if (rb != NULL) + rb->rb_advrpos_cb(rb, rpos); +} + +static unsigned int +rb_read(struct response_buffer *rb, char *buf, unsigned int buf_sz) +{ + if (rb != NULL) + return rb->rb_read_cb(rb, buf, buf_sz); + else + return 0; +} + +static unsigned int +rb_eof(struct response_buffer *rb) +{ + if (rb != NULL) + return rb->rb_eof_cb(rb); + else + return 1; +} + +static void +rb_destroy(struct response_buffer *rb) +{ + if (rb != NULL) + rb->rb_ondestroy_cb(rb); +} + +static int +rb_txt_simple_eof_cb(struct response_buffer *rb) +{ + struct response_txt_simple *rts = rb_to_txt_simple(rb); + + if (rb_to_txt_full(rb) == NULL) + return 1; + + if (rb->rb_rpos >= rts->rts_len) + return 1; + else + return 0; +} + +static unsigned int +rb_txt_simple_read_cb(struct response_buffer *rb, char *buf, + unsigned int buf_sz) +{ + struct response_txt_simple *rts = rb_to_txt_simple(rb); + unsigned int i = rb->rb_rpos; + unsigned int rv = 0; + + if (rts == NULL || rb_eof(rb)) + return 0; + + while ((i < rts->rts_len) && (rv < buf_sz)) { + *buf++ = rts->rts_pattern[i % rts->rts_pattern_len]; + i++; + rv++; + } + + return rv; +} + +static void +rb_txt_simple_ondestroy_cb(struct response_buffer *rb) +{ + struct response_txt_simple *rts = rb_to_txt_simple(rb); + + if (rts != NULL) { + OPENSSL_free(rts->rts_pattern); + OPENSSL_free(rts); + } +} + +static void +rb_txt_simple_advrpos_cb(struct response_buffer *rb, unsigned int sz) +{ + struct response_txt_simple *rts = rb_to_txt_simple(rb); + + if (rts != NULL) { + rb->rb_rpos += sz; + if (rb->rb_rpos >= rts->rts_len) + rb->rb_rpos = rts->rts_len; + } +} + +static ossl_unused struct response_txt_simple * +new_txt_simple_respoonse(const char *fill_pattern, unsigned int fsize) +{ + struct response_txt_simple *rts; + struct response_buffer *rb; + + rts = OPENSSL_malloc(sizeof (struct response_txt_simple)); + if (rts == NULL) + return NULL; + + if ((rts->rts_pattern = strdup(fill_pattern)) == NULL) { + OPENSSL_free(rts); + return NULL; + } + rts->rts_pattern_len = strlen(fill_pattern); + rts->rts_len = fsize; + + rb = (struct response_buffer *)rts; + rb_init(rb); + rb->rb_type = RB_TYPE_TEXT_SIMPLE; + rb->rb_eof_cb = rb_txt_simple_eof_cb; + rb->rb_read_cb = rb_txt_simple_read_cb; + rb->rb_ondestroy_cb = rb_txt_simple_ondestroy_cb; + rb->rb_advrpos_cb = rb_txt_simple_advrpos_cb; + + return rts; +} + +static int +rb_txt_full_eof_cb(struct response_buffer *rb) +{ + struct response_txt_full *rtf = rb_to_txt_full(rb); + + if (rtf == NULL) + return 1; + + if (rb->rb_rpos >= rtf->rtf_len) + return 1; + else + return 0; +} + +static void +rb_txt_full_ondestroy_cb(struct response_buffer *rb) +{ + struct response_txt_full *rtf = rb_to_txt_full(rb); + + if (rtf != NULL) { + OPENSSL_free(rtf->rtf_pattern); + OPENSSL_free(rtf); + } +} + +static unsigned int +rb_txt_full_read_cb(struct response_buffer *rb, char *buf, unsigned int buf_sz) +{ + struct response_txt_full *rtf = rb_to_txt_full(rb); + unsigned int i = rb->rb_rpos; + unsigned int j; + unsigned int rv = 0; + + if (rtf == NULL || rb_eof(rb)) + return 0; + + while ((i < rtf->rtf_hdr_len) && (rv < buf_sz)) { + *buf++ = rtf->rtf_headers[i++]; + rv++; + } + + j = i - rtf->rtf_hdr_len; + while ((i < rtf->rtf_len) && (rv < buf_sz)) { + *buf++ = rtf->rtf_pattern[j % rtf->rtf_pattern_len]; + j++; + i++; + rv++; + } + + return rv; +} + +static void +rb_txt_full_advrpos_cb(struct response_buffer *rb, unsigned int sz) +{ + struct response_txt_full *rtf = rb_to_txt_full(rb); + + if (rtf != NULL) { + rb->rb_rpos += sz; + if (rb->rb_rpos >= rtf->rtf_len) + rb->rb_rpos = rtf->rtf_len; + } +} + +static struct response_txt_full * +new_txt_full_respoonse(const char *fill_pattern, unsigned int fsize) +{ + struct response_txt_full *rtf; + struct response_buffer *rb; + char date_str[80]; + int hlen; + time_t t; + + rtf = OPENSSL_malloc(sizeof (struct response_txt_full)); + if (rtf == NULL) + return NULL; + + if ((rtf->rtf_pattern = strdup(fill_pattern)) == NULL) { + OPENSSL_free(rtf); + return NULL; + } + rtf->rtf_pattern_len = strlen(fill_pattern); + + t = time(&t); + ctime_r(&t, date_str); + /* TODO check headers if they confirm to HTTP/1.0 */ + hlen = snprintf(rtf->rtf_headers, sizeof (rtf->rtf_headers), + "HTTP/1.0 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %u\r\n" + "Date: %s\r\n" + "\r\n", fsize, date_str); + if (hlen >= (int)sizeof (rtf->rtf_headers)) { + OPENSSL_free(rtf->rtf_pattern); + OPENSSL_free(rtf); + return NULL; + } + rtf->rtf_hdr_len = (unsigned int)hlen; + + rtf->rtf_len = rtf->rtf_hdr_len + fsize; + + rb = (struct response_buffer *)rtf; + rb_init(rb); + rb->rb_type = RB_TYPE_TEXT_FULL; + rb->rb_eof_cb = rb_txt_full_eof_cb; + rb->rb_read_cb = rb_txt_full_read_cb; + rb->rb_ondestroy_cb = rb_txt_full_ondestroy_cb; + rb->rb_advrpos_cb = rb_txt_full_advrpos_cb; + + return rtf; +} + +static ossl_unused const char * +pe_type_to_name(const struct poll_event *pe) +{ + static const char *names[] = { + "none", + "listener", + "connection", + "stream (bidi)", + "stream (in)", + "stream (out)", + "invalid" + }; + + if (pe->pe_type >= PE_INVALID) + return (names[PE_INVALID]); + + return names[pe->pe_type]; +} + +static struct poll_event_connection * +pe_to_connection(struct poll_event *pe) +{ + if ((pe == NULL) || (pe->pe_type != PE_CONNECTION)) + return NULL; + + return ((struct poll_event_connection *)pe); +} + +static void +init_pe(struct poll_event *pe, SSL *ssl) +{ + pe->pe_poll_item.desc = SSL_as_poll_descriptor(ssl); + pe->pe_cb_in = pe_return_error; + pe->pe_cb_out = pe_return_error; + pe->pe_cb_error = pe_return_error; + pe->pe_cb_ondestroy = pe_return_void; + pe->pe_self = pe; + pe->pe_type = PE_NONE; + pe->pe_want_mask = ~0; +} + +static struct poll_event * +new_pe(SSL *ssl) +{ + struct poll_event *pe; + + if (ssl == NULL) + return NULL; + + pe = OPENSSL_zalloc(sizeof (struct poll_event)); + if (pe != NULL) + init_pe(pe, ssl); + + return pe; +} + +static struct poll_event_listener * +new_listener_pe(SSL *ssl_listener) +{ + struct poll_event *listener_pe = new_pe(ssl_listener); + + if (listener_pe != NULL) { + listener_pe->pe_type = PE_LISTENER; + listener_pe->pe_want_events = SSL_POLL_EVENT_IC | SSL_POLL_EVENT_EL; + } + + return (struct poll_event_listener *)listener_pe; +} + +static struct poll_event * +new_qconn_pe(SSL *ssl_qconn) +{ + struct poll_event *qconn_pe; + struct poll_event_connection *pec; + + qconn_pe = OPENSSL_zalloc(sizeof (struct poll_event_connection)); + + if (qconn_pe != NULL) { + init_pe(qconn_pe, ssl_qconn); + qconn_pe->pe_type = PE_CONNECTION; + qconn_pe->pe_want_events = SSL_POLL_EVENT_ISB | SSL_POLL_EVENT_ISU; + qconn_pe->pe_want_events |= SSL_POLL_EVENT_EC | SSL_POLL_EVENT_ECD; + /* + * SSL_POLL_EVENT_OSB (or SSL_POLL_EVENT_OSU) must be monitored once + * there is a request for outbound stream created by app. + */ + pec = (struct poll_event_connection *)qconn_pe; + ossl_list_peccx_init(&pec->pec_unistream_cx); + ossl_list_peccx_init(&pec->pec_stream_cx); + } + + return qconn_pe; +} + +static struct poll_event_stream * +new_stream_pe(SSL *ssl_qs) +{ + struct poll_event_stream *pes; + + pes = OPENSSL_zalloc(sizeof (struct poll_event_stream)); + + if (pes != NULL) { + init_pe((struct poll_event *)pes, ssl_qs); + pes->pes_wpos = pes->pes_reqbuf; + pes->pes_wpos_sz = sizeof (pes->pes_reqbuf) - 1; + } + + return (pes); +} + +static SSL * +get_ssl_from_pe(struct poll_event *pe) +{ + SSL *ssl = NULL; + + if (pe != NULL) + ssl = pe->pe_poll_item.desc.value.ssl; + + return ssl; +} + +static void +pe_pause_read(struct poll_event *pe) +{ + pe->pe_want_events &= ~SSL_POLL_EVENT_R; + pe->pe_my_pm->pm_need_rebuild = 1; +} + +static void +pe_resume_read(struct poll_event *pe) +{ + pe->pe_want_events |= (SSL_POLL_EVENT_R & pe->pe_want_mask); + pe->pe_my_pm->pm_need_rebuild = 1; +} + +static void +pe_pause_write(struct poll_event *pe) +{ + pe->pe_want_events &= ~SSL_POLL_EVENT_W; + pe->pe_my_pm->pm_need_rebuild = 1; +} + +static void +pe_resume_write(struct poll_event *pe) +{ + pe->pe_want_events |= (SSL_POLL_EVENT_W & pe->pe_want_mask); + pe->pe_my_pm->pm_need_rebuild = 1; +} + +/* + * like pause, but is permanent, + */ +static void +pe_disable_read(struct poll_event *pe) +{ + pe_pause_read(pe); + pe->pe_want_mask &= ~SSL_POLL_EVENT_R; +} + +static void +pe_disable_write(struct poll_event *pe) +{ + pe_pause_write(pe); + pe->pe_want_mask &= ~SSL_POLL_EVENT_W; +} + +/* + * handle_ssl_error() diagnoses error from SSL/QUIC stack and + * decides if it is temporal error (in that case it returns zero) + * or error is permanent. In case of permanent error the + * poll event pe should be removed from poll manager and destroyed. + */ +static ossl_unused const char * +err_str_n(unsigned long e, char *buf, size_t buf_sz) +{ + ERR_error_string_n(e, buf, buf_sz); + return buf; +} + +static int +handle_ssl_error(struct poll_event *pe, int rc, const char *caller) +{ + SSL *ssl = get_ssl_from_pe(pe); + int ssl_error, rv; +#ifdef DEBUG + char err_str[120]; +#endif + + /* may be we should use SSL_shutdown_ex() to signal peer what's going on */ + ssl_error = SSL_get_error(ssl, rc); + if (rc <= 0) { + switch (ssl_error) { + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + DPRINTF(stderr, "%s permanent error on %p (%s) [ %s ]\n", + caller, pe, pe_type_to_name(pe), + err_str_n(ssl_error, err_str, sizeof (err_str))); + rv = -1; + break; + case SSL_ERROR_ZERO_RETURN: + default: + DPRINTF(stderr, "%s temporal error on %p (%s) [ %s ]\n", + caller, pe, pe_type_to_name(pe), + err_str_n(ssl_error, err_str, sizeof (err_str))); + rv = 0; /* maybe return -1 here too */ + } + } else if (rc == 0) { + DPRINTF(stderr, "%s temporal error on %p (%s) [ %s ]\n", + caller, pe, pe_type_to_name(pe), + err_str_n(ssl_error, err_str, sizeof (err_str))); + rv = 0; + } else if (rc == 1) { + DPRINTF(stderr, "%s no error on %p (%s) [ ??? ]\n", caller, pe, + pe_type_to_name(pe)); + rv = -1; /* complete, stop polling for event */ + } else { + DPRINTF(stderr, "%s ?unexpected? error on %p (%s) [ %s ]\n", + caller, pe, pe_type_to_name(pe), + err_str_n(ssl_error, err_str, sizeof (err_str))); + rv = -1; /* stop polling */ + } + + return rv; +} + +static ossl_unused const char * +stream_state_str(int stream_state) +{ + const char *rv; + + switch (stream_state) { + case SSL_STREAM_STATE_NONE: + rv = "SSL_STREAM_STATE_NONE"; + break; + case SSL_STREAM_STATE_OK: + rv = "SSL_STREAM_STATE_OK"; + break; + case SSL_STREAM_STATE_WRONG_DIR: + rv = "SSL_STREAM_STATE_WRONG_DIR"; + break; + case SSL_STREAM_STATE_FINISHED: + rv = "SSL_STREAM_STATE_FINISHED"; + break; + case SSL_STREAM_STATE_RESET_LOCAL: + rv = "SSL_STREAM_STATE_RESET_LOCAL"; + break; + case SSL_STREAM_STATE_RESET_REMOTE: + rv = "SSL_STREAM_STATE_RESET_REMOTE"; + break; + case SSL_STREAM_STATE_CONN_CLOSED: + rv = "SSL_STREAM_STATE_CONN_CLOSED"; + break; + default: + rv = "???"; + } + + return rv; +} + +static int +handle_read_stream_state(struct poll_event *pe) +{ + int stream_state = SSL_get_stream_read_state(get_ssl_from_pe(pe)); + int rv; + + switch (stream_state) { + case SSL_STREAM_STATE_FINISHED: + DPRINTF(stderr, "%s remote peer concluded the stream\n", __func__); + pe_disable_read(pe); + /* FALLTHRU */ + case SSL_STREAM_STATE_OK: + rv = 0; + break; + default: + DPRINTF(stderr, + "%s error %s on stream, the %p (%s) should be destroyed\n", + __func__, stream_state_str(stream_state), pe, + pe_type_to_name(pe)); + rv = -1; + } + + return rv; +} + +static int +handle_write_stream_state(struct poll_event *pe) +{ + int state = SSL_get_stream_write_state(get_ssl_from_pe(pe)); + int rv; + + switch (state) { + case SSL_STREAM_STATE_FINISHED: + DPRINTF(stderr, "%s remote peer concluded the stream\n", __func__); + /* FALLTHRU */ + case SSL_STREAM_STATE_OK: + rv = 0; + break; + default: + DPRINTF(stderr, + "%s error %s on stream, the %p (%s) should be destroyed\n", + __func__, stream_state_str(state), pe, pe_type_to_name(pe)); + rv = -1; + } + + return rv; +} + +static void +add_pe_to_pm(struct poll_manager *pm, struct poll_event *pe) +{ + if (pe->pe_my_pm == NULL) { + ossl_list_pe_insert_head(&pm->pm_head, pe); + pm->pm_need_rebuild = 1; + pe->pe_my_pm = pm; + } +} + +static void +remove_pe_from_pm(struct poll_manager *pm, struct poll_event *pe) +{ + if (pe->pe_my_pm == pm) { + ossl_list_pe_remove(&pm->pm_head, pe); + pm->pm_need_rebuild = 1; + pe->pe_my_pm = NULL; + } +} + +static struct poll_manager * +create_poll_manager(void) +{ + struct poll_manager *pm = NULL; + + pm = OPENSSL_zalloc(sizeof (struct poll_manager)); + if (pm == NULL) + return NULL; + + ossl_list_pe_init(&pm->pm_head); + pm->pm_poll_set = OPENSSL_malloc(sizeof (struct poll_event) * POLL_GROW); + if (pm->pm_poll_set != NULL) { + pm->pm_poll_set_sz = POLL_GROW; + pm->pm_event_count = 0; + } else { + OPENSSL_free(pm); + return NULL; + } + + return pm; +} + +static int +rebuild_poll_set(struct poll_manager *pm) +{ + struct poll_event *new_poll_set; + struct poll_event *pe; + size_t new_sz; + size_t pe_num; + size_t i; + + if (pm->pm_need_rebuild == 0) + return 0; + + pe_num = ossl_list_pe_num(&pm->pm_head); + if (pe_num > pm->pm_poll_set_sz) { + /* + * grow poll set by POLL_GROW + */ + new_sz = sizeof (struct poll_event) * (pm->pm_poll_set_sz + POLL_GROW); + new_poll_set = (struct poll_event *)OPENSSL_realloc(pm->pm_poll_set, + new_sz); + if (new_poll_set == NULL) + return -1; + pm->pm_poll_set = new_poll_set; + pm->pm_poll_set_sz += POLL_GROW; + + } else if ((pe_num + POLL_DOWNSIZ) < pm->pm_poll_set_sz) { + /* + * shrink poll set by POLL_DOWNSIZ + */ + new_sz = sizeof (struct poll_event) * + (pm->pm_poll_set_sz - POLL_DOWNSIZ); + new_poll_set = (struct poll_event *)OPENSSL_realloc(pm->pm_poll_set, + new_sz); + if (new_poll_set == NULL) + return -1; + pm->pm_poll_set = new_poll_set; + pm->pm_poll_set_sz -= POLL_GROW; + } + + i = 0; + DPRINTF(stderr, "%s there %zu events to poll\n", __func__, + ossl_list_pe_num(&pm->pm_head)); + OSSL_LIST_FOREACH(pe, pe, &pm->pm_head) { + pe->pe_poll_item.events = pe->pe_want_events; + pm->pm_poll_set[i++] = *pe; + DPRINTF(stderr, "\t%p (%s) " POLL_FMT " (disabled: " POLL_FMT ")\n", + pe, pe_type_to_name(pe), + POLL_PRINTA(pe->pe_poll_item.events), + POLL_PRINTA(~pe->pe_want_mask)); + } + pm->pm_event_count = i; + pm->pm_need_rebuild = 0; + + return 0; +} + +static void +destroy_poll_manager(struct poll_manager *pm) +{ + struct poll_event *pe, *pe_safe; + + if (pm == NULL) + return; + + OSSL_LIST_FOREACH_DELSAFE(pe, pe_safe, pe, &pm->pm_head) + destroy_pe(pe); + + OPENSSL_free(pm->pm_poll_set); + OPENSSL_free(pm); +} + +static void +destroy_pe(struct poll_event *pe) +{ + SSL *ssl; + + if (pe == NULL) + return; + + DPRINTF(stderr, "%s %p (%s)\n", __func__, pe, pe_type_to_name(pe)); + ssl = get_ssl_from_pe(pe); + if (pe->pe_my_pm) + remove_pe_from_pm(pe->pe_my_pm, pe); + + pe->pe_cb_ondestroy(pe); + + OPENSSL_free(pe); + + SSL_free(ssl); +} + +static int +pe_return_error(struct poll_event *pe) +{ + return -1; +} + +static void +pe_return_void(struct poll_event *ctx) +{ + return; +} + +static int +pe_handle_listener_error(struct poll_event *pe) +{ + pe->pe_my_pm->pm_continue = 0; + if (pe->pe_poll_item.revents & SSL_POLL_EVENT_EL) + return -1; + + DPRINTF(stderr, "%s unexpected error on %p (%s) " POLL_FMT "\n", __func__, + pe, pe_type_to_name(pe), POLL_PRINTA(pe->pe_poll_item.revents)); + + return -1; +} + +static struct poll_event_stream * +pe_to_stream(struct poll_event *pe) +{ + switch (pe->pe_type) { + case PE_STREAM: + case PE_STREAM_UNI_IN: + case PE_STREAM_UNI_OUT: + return ((struct poll_event_stream *)pe); + default: + return NULL; + } +} + +/* + * non-blocking variant for new_stream(). Creating outbound stream + * is two step process when using non-blocking I/O. + * application starts polling for SSL_POLL_EVENT_OS* to check + * if outbound streams are available. + * + * as soon as SSL_POLL_EVENT_OS comes back from SSL_poll() application + * should call SSL-new_stream() to create a stream object and + * add its poll descriptor to SSL_poll() events. The stream object + * should be monitored for SSL_POLL_EVENT_{W,R} + * + * new_stream() function below is supposed to be called by application + * which uses SSL_poll() to manage I/O. We expect there might be more + * than 1 stream request. + */ +static int +request_new_stream(struct poll_event_connection *pec, uint64_t qsflag, + void *peccx_arg) +{ + struct poll_event_context *peccx; + struct poll_event *qconn_pe = (struct poll_event *)pec; + + if (peccx_arg == NULL) + return -1; + + peccx = OPENSSL_malloc(sizeof (struct poll_event_context)); + if (peccx == NULL) + return -1; + peccx->peccx = peccx_arg; + + if (qsflag & SSL_STREAM_FLAG_UNI) { + pec->pec_want_unistream++; + qconn_pe->pe_want_events |= SSL_POLL_EVENT_OSU; + ossl_list_peccx_insert_tail(&pec->pec_unistream_cx, peccx); + } else { + pec->pec_want_stream++; + qconn_pe->pe_want_events |= SSL_POLL_EVENT_OSB; + ossl_list_peccx_insert_tail(&pec->pec_stream_cx, peccx); + } + + /* + * We are changing poll events, so SSL_poll() array needs be rebuilt. + */ + qconn_pe->pe_my_pm->pm_need_rebuild = 1; + + return 0; +} + +static void * +get_response_from_pec(struct poll_event_connection *pec, int stype) +{ + struct poll_event_context *peccx; + void *rv; + + switch (stype) { + case PE_STREAM_UNI_OUT: + peccx = ossl_list_peccx_head(&pec->pec_unistream_cx); + if (peccx != NULL) { + pec->pec_want_unistream--; + ossl_list_peccx_remove(&pec->pec_unistream_cx, peccx); + rv = peccx->peccx; + OPENSSL_free(peccx); + } else { + rv = NULL; + } + break; + case PE_STREAM: + peccx = ossl_list_peccx_head(&pec->pec_stream_cx); + if (peccx != NULL) { + pec->pec_want_stream--; + ossl_list_peccx_remove(&pec->pec_stream_cx, peccx); + rv = peccx->peccx; + OPENSSL_free(peccx); + } else { + rv = NULL; + } + break; + default: + rv = NULL; + } + + return rv; +} + +static int +app_handle_stream_error(struct poll_event *pe) +{ + int rv = 0; + + if (pe->pe_poll_item.revents & SSL_POLL_EVENT_ER) { + + if ((pe->pe_poll_item.events & SSL_POLL_EVENT_R) == 0) { + DPRINTF(stderr, "%s unexpected failure on reader %p (%s) " + POLL_FMT "\n", __func__, pe, pe_type_to_name(pe), + POLL_PRINTA(pe->pe_poll_item.revents)); + } + + (void) handle_read_stream_state(pe); + rv = -1; /* tell pm to stop polling and destroy stream/event */ + } else if (pe->pe_poll_item.revents & SSL_POLL_EVENT_EW) { + + if ((pe->pe_poll_item.events & SSL_POLL_EVENT_W) == 0) { + DPRINTF(stderr, "%s unexpected failure on writer %p (%s) " + POLL_FMT "\n", __func__, pe, pe_type_to_name(pe), + POLL_PRINTA(pe->pe_poll_item.revents)); + } + (void) handle_write_stream_state(pe); + + rv = -1; /* tell pm to stop polling and destroy stream/event */ + } else { + DPRINTF(stderr, "%s unexpected failure on writer/reader %p (%s) " + POLL_FMT "\n", __func__, pe, pe_type_to_name(pe), + POLL_PRINTA(pe->pe_poll_item.revents)); + rv = -1; /* tell pm to stop polling and destroy stream/event */ + } + + return rv; +} + +/* + * app_write_cb() callback notifies application the QUIC stack + * is ready to send data. The write callback attempts to process + * all buffers in write queue. + * if write queue becomes empty, stream is concluded. + */ +static int +app_write_cb(struct poll_event *pe) +{ + struct response_buffer *rb = (struct response_buffer *)pe->pe_appdata; + char buf[4096]; + size_t written; + unsigned int wlen; + int rv; + + if (rb == NULL) { + DPRINTF(stderr, "%s no response buffer\n", __func__); + return -1; + } + + wlen = rb_read(rb, buf, sizeof (buf)); + if (wlen == 0) { + DPRINTF(stderr, "%s no more data to write to %p (%s)\n", __func__, + pe, pe_type_to_name(pe)); + rv = SSL_stream_conclude(get_ssl_from_pe(pe), 0); + pe_disable_write(pe); + /* + * we deliberately override return value of SSL_stream_conclude() here + * to keep CI build happy. -1 means we are going to kill poll event + * anyway. + * + * another option would be to return 0 and let poll manager wait + * for confirmation of FIN packet sent on behalf of + * SSL_stream_conclude(). At the moment it does not seem necessary. + * More details can be found here: + * https://github.com/openssl/project/issues/1160 + */ + rv = -1; + } else { + rv = SSL_write_ex(get_ssl_from_pe(pe), buf, wlen, &written); + if (rv == 1) { + rb_advrpos(rb, (unsigned int)written); + rv = 0; + } else { + rv = handle_ssl_error(pe, rv, __func__); + } + } + + return rv; +} + +static int +app_setup_response(struct poll_event_stream *pes) +{ + struct poll_event *pe = (struct poll_event *)pes; + int rv; + + switch (pe->pe_type) { + case PE_STREAM_UNI_IN: + rv = request_new_stream(pes->pes_conn, SSL_STREAM_FLAG_UNI, + pe->pe_appdata); + break; + case PE_STREAM: + pe->pe_cb_out = app_write_cb; + rv = 0; + pe_resume_write(pe); + break; + default: + rv = -1; + } + + return rv; +} + +static unsigned int +get_fsize(const char *file_name) +{ + const char *digit = file_name; + unsigned int fsize; + + /* any number we find in filename is desired size */ + fsize = 0; + + while (*digit && !isdigit((int)*digit)) + digit++; + + while (*digit && isdigit((int)*digit)) { + fsize = fsize * 10; + fsize = fsize + (*digit - 0x30); + digit++; + } + + if (fsize == 0) + fsize = 12345; /* ? may be random ? */ + + return fsize; +} + +static int +parse_request(struct poll_event_stream *pes) +{ + const char *pos = pes->pes_reqbuf; + char file_name_buf[4096]; + char *dst = file_name_buf; + char *end = &file_name_buf[4096]; + char *file_name; + struct poll_event *pe = (struct poll_event *)pes; + int rv; + + /* got request already */ + if (pe->pe_appdata != NULL) + return -1; + + while (*pos && isspace((int)*pos)) + pos++; + + if (strncasecmp(pos, "GET", 3) != 0) + return -1; /* this will reset the stream */ + pos += 3; + + while (*pos && isspace((int)*pos)) + pos++; + + if (*pos != '/') + return -1; /* this will reset the stream */ + + /* strip leading slashes */ + while (*pos == '/') + pos++; + + while ((isalnum((int)*pos) || ispunct((int)*pos)) && (dst < end)) + *dst++ = *pos++; + if (dst == end) + dst--; + *dst = '\0'; + /* + * if request is something like 'GET / HTTP/1.0...' we assume /index.html + * otherwise take the last component + */ + if (file_name_buf[0] == '\0') { + file_name = "index.html"; + } else { + file_name = basename(file_name_buf); + /* + * I'm not sure what happens when file_name_buf looks for example + * like that: /foo/bar/nothing/ + * (the basename component is missing/is empty). + */ + if (file_name == NULL || *file_name == '\0') + file_name = "foo"; + } + + assert(pe->pe_appdata == NULL); + pe->pe_appdata = new_txt_full_respoonse(file_name, get_fsize(file_name)); + + if (pe->pe_appdata == NULL) + rv = -1; + else + rv = app_setup_response(pes); + + return rv; +} + +static int +wrap_around(struct poll_event_stream *pes) +{ + int rv = 0; + + /* we can wrap the buffer iff we got request */ + if (pes->pes_wpos_sz == 0) { + if (((struct poll_event *)pes)->pe_appdata != NULL) { + pes->pes_wpos = pes->pes_reqbuf; + pes->pes_wpos_sz = sizeof (pes->pes_reqbuf) - 1; + } else { + rv = -1; + } + } + + return rv; +} + +/* + * app_read_cb() callback notifies application there are data + * waiting to be read from stream. The callback allocates + * new linked buffer and reads data from stream to newly allocated + * buffer. It then uses request_write() to put the buffer to write + * queue so data can be echoed back to client. + */ +static int +app_read_cb(struct poll_event *pe) +{ + struct poll_event_stream *pes = pe_to_stream(pe); + size_t read_len; + int rv; + + if (pes == NULL) + return -1; + + /* + * if we could not parse the request in the first chunk (8k), then just + * fail the stream with reset. If we got request then finish reading + * data from client. + */ + if (wrap_around(pes) == -1) + return -1; + + rv = SSL_read_ex(get_ssl_from_pe(pe), pes->pes_wpos, pes->pes_wpos_sz, + &read_len); + if (rv == 0) { + pe_disable_read(pe); + /* + * May be it's over cautious, we should just examine stream state and + * decide if we can continue with poll (rv == 0) or we should stop + * polling (rv == -1). + */ + rv = handle_ssl_error(pe, rv, __func__); + if (rv == 0) + rv = handle_read_stream_state(pe); + return rv; + } + pes->pes_wpos += read_len; + pes->pes_wpos_sz -= read_len; + + rv = parse_request(pes); + + return rv; +} + +static void +app_ondestroy_cb(struct poll_event *pe) +{ + rb_destroy((struct response_buffer *)pe->pe_appdata); +} + +/* + * create new outbound stream + */ +static int +app_new_stream_cb(struct poll_event *qconn_pe) +{ + SSL *qconn; + SSL *qs; + struct poll_event_connection *pec; + struct poll_event *qs_pe; + struct poll_event_stream *pes; + int rv = 0; + + assert(qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_OS); + pec = pe_to_connection(qconn_pe); + assert(pec != NULL); + + qconn = get_ssl_from_pe(qconn_pe); + + if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_OSU) + qs = SSL_new_stream(qconn, SSL_STREAM_FLAG_UNI); + else + qs = SSL_new_stream(qconn, 0); + if (qs == NULL) + return -1; + + pes = new_stream_pe(qs); + qs_pe = (struct poll_event *)pes; + if (qconn_pe != NULL) { + qs_pe->pe_cb_error = app_handle_stream_error; + qs_pe->pe_cb_out = app_write_cb; /* unidirectional stream is outbound */ + qs_pe->pe_cb_ondestroy = app_ondestroy_cb; + qs_pe->pe_want_events = SSL_POLL_EVENT_EW; + + if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_OSU) { + qs_pe->pe_type = PE_STREAM_UNI_OUT; + } else if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_OSB) { + /* we will enable read side for bi-directional stream */ + qs_pe->pe_type = PE_STREAM; + qs_pe->pe_cb_out = app_read_cb; + qs_pe->pe_want_events = SSL_POLL_EVENT_ER; + } + + qs_pe->pe_appdata = get_response_from_pec(pec, qs_pe->pe_type); + if (qs_pe->pe_appdata == NULL) { + rv = -1; + destroy_pe(qs_pe); + } else { + add_pe_to_pm(qconn_pe->pe_my_pm, qs_pe); + pe_resume_write(qs_pe); + /* enable read side on bidirectional outbound streams */ + if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_OSB) + pe_resume_read(qs_pe); + } + } else { + SSL_free(qs); + rv = -1; + } + + return rv; +} + +static int +app_handle_qconn_error(struct poll_event *pe) +{ + int rv = -2; + + if (pe->pe_poll_item.revents & SSL_POLL_EVENT_EC) { + DPRINTF(stderr, + "%s connection shutdown started on %p (%s), keep polling\n", + __func__, pe, pe_type_to_name(pe)); + /* + * shutdown has started, Not sure what we should be doing here. + * So the plan is to call SSL_shutdown() here and stop monitoring + * _EVENT_EC here. We will keep _EVENT_ECD monitored. + * Shall we call shutdown too? + */ + SSL_shutdown(get_ssl_from_pe(pe)); + /* + * adjust _want_events, don't forget to ask poll manager to rebuild + * poll set so _want_events can take effect in next loop iteration + */ + pe->pe_want_events &= ~SSL_POLL_EVENT_EC; + pe->pe_my_pm->pm_need_rebuild = 1; + rv = 0; + } + + if (pe->pe_poll_item.revents & SSL_POLL_EVENT_ECD) { + DPRINTF(stderr, + "%s connection shutdown done on %p (%s), stop polling\n", + __func__, pe, pe_type_to_name(pe)); + rv = -1; /* shutdown is complete stop polling let pe to be destroyed */ + } + + if (rv == -2) { + DPRINTF(stderr, "%s unexpected event on %p (%s)" POLL_FMT "\n", + __func__, pe, pe_type_to_name(pe), + POLL_PRINTA(pe->pe_poll_item.revents)); + rv = -1; + } + + return rv; +} + +/* + * accept stream from remote peer + */ +static int +app_accept_stream_cb(struct poll_event *qconn_pe) +{ + SSL *qconn; + SSL *qs; + struct poll_event *qs_pe; + int rv = 0; +#ifdef DEBUG + struct poll_event_connection *pec; + + assert(qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_IS); + pec = pe_to_connection(qconn_pe); + assert(pec != NULL); +#endif + + qconn = get_ssl_from_pe(qconn_pe); + + if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_ISU) + qs = SSL_accept_stream(qconn, SSL_STREAM_FLAG_UNI); + else + qs = SSL_accept_stream(qconn, SSL_STREAM_FLAG_UNI); + if (qs == NULL) + return -1; + + qs_pe = (struct poll_event *)new_stream_pe(qs); + if (qs_pe != NULL) { + qs_pe->pe_cb_error = app_handle_stream_error; + qs_pe->pe_cb_in = app_read_cb; + qs_pe->pe_cb_ondestroy = app_ondestroy_cb; + qs_pe->pe_want_events = SSL_POLL_EVENT_ER; + add_pe_to_pm(qconn_pe->pe_my_pm, qs_pe); + + if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_ISU) { + qs_pe->pe_type = PE_STREAM_UNI_IN; + } else if (qconn_pe->pe_poll_item.revents & SSL_POLL_EVENT_ISB) { + qs_pe->pe_type = PE_STREAM; + /* + * disable write side on duplex (bi-directional) stream, + * because we need to read response from client first. + */ + pe_pause_write(qs_pe); + } + qs_pe->pe_appdata = NULL; + pe_resume_read(qs_pe); + } else { + SSL_free(qs); + rv = -1; + } + + return rv; +} + +static void +app_destroy_qconn(struct poll_event *pe) +{ + struct poll_event_connection *pec; + struct poll_event_context *peccx, *peccx_save; + + pec = pe_to_connection(pe); + if (pec == NULL) + return; + + OSSL_LIST_FOREACH_DELSAFE(peccx, peccx_save, peccx, &pec->pec_unistream_cx) { + peccx->peccx_cb_ondestroy(peccx->peccx); + OPENSSL_free(peccx); + } + + OSSL_LIST_FOREACH_DELSAFE(peccx, peccx_save, peccx, &pec->pec_stream_cx) { + peccx->peccx_cb_ondestroy(peccx->peccx); + OPENSSL_free(peccx); + } +} + +static int +app_accept_qconn(struct poll_event *listener_pe) +{ + SSL *listener; + SSL *qconn; + struct poll_event *qc_pe; + + listener = get_ssl_from_pe(listener_pe); + qconn = SSL_accept_connection(listener, 0); + if (qconn == NULL) + return -1; + + qc_pe = new_qconn_pe(qconn); + if (qc_pe != NULL) { + qc_pe->pe_cb_in = app_accept_stream_cb; + qc_pe->pe_cb_out = app_new_stream_cb; + qc_pe->pe_cb_error = app_handle_qconn_error; + qc_pe->pe_cb_ondestroy = app_destroy_qconn; + add_pe_to_pm(listener_pe->pe_my_pm, qc_pe); + } else { + SSL_free(qconn); + return -1; + } + + return 0; +} + +/* + * Main loop for server to accept QUIC connections. + * Echo every request back to the client. + */ +static int +run_quic_server(SSL_CTX *ctx, struct poll_manager *pm, int fd) +{ + int ok = -1; + int e = 0; + unsigned int i; + SSL *listener; + struct poll_event *pe; + struct poll_event_listener *listener_pe = NULL; + size_t poll_items; + + /* Create a new QUIC listener */ + if ((listener = SSL_new_listener(ctx, 0)) == NULL) + goto err; + + if (!SSL_set_fd(listener, fd)) + goto err; + + /* + * Set the listener mode to non-blocking, which is inherited by + * child objects. + */ + if (!SSL_set_blocking_mode(listener, 0)) + goto err; + + /* + * Begin listening. Note that is not usually needed as SSL_accept_connection + * will implicitly start listening. It is only needed if a server wishes to + * ensure it has started to accept incoming connections but does not wish to + * actually call SSL_accept_connection yet. + */ + if (!SSL_listen(listener)) + goto err; + + listener_pe = new_listener_pe(listener); + if (listener_pe == NULL) + goto err; + listener = NULL; /* listener_pe took ownership */ + + pe = (struct poll_event *)listener_pe; + pe->pe_cb_in = app_accept_qconn; + pe->pe_cb_error = pe_handle_listener_error; + + add_pe_to_pm(pm, pe); + listener_pe = NULL; /* listener is owned by pm now */ + + /* + * Begin an infinite loop of listening for connections. We will only + * exit this loop if we encounter an error. + */ + pm->pm_continue = 1; + while (pm->pm_continue) { + rebuild_poll_set(pm); + ok = SSL_poll((SSL_POLL_ITEM *)pm->pm_poll_set, pm->pm_event_count, + sizeof (struct poll_event), NULL, 0, &poll_items); + + if (ok == 0 && poll_items == 0) + break; + + for (i = 0; i < pm->pm_event_count; i++) { + pe = &pm->pm_poll_set[i]; + if (pe->pe_poll_item.revents == 0) + continue; + DPRINTF(stderr, "%s %s (%p) " POLL_FMT "\n", __func__, + pe_type_to_name(pe), pe, + POLL_PRINTA(pe->pe_poll_item.revents)); + pe->pe_self->pe_poll_item.revents = pe->pe_poll_item.revents; + if (pe->pe_poll_item.revents & SSL_POLL_ERROR) + e = pe->pe_cb_error(pe->pe_self); + else if (pe->pe_poll_item.revents & SSL_POLL_IN) + e = pe->pe_cb_in(pe->pe_self); + else if (pe->pe_poll_item.revents & SSL_POLL_OUT) + e = pe->pe_cb_out(pe->pe_self); + + if (e == -1) { + pe = pm->pm_poll_set[i].pe_self; + destroy_pe(pe); + } + } + } + + ok = EXIT_SUCCESS; +err: + SSL_free(listener); + destroy_pe((struct poll_event *)listener_pe); + destroy_poll_manager(pm); + return ok; +} + +/* + * 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) { + DPRINTF(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) { + DPRINTF(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) { + DPRINTF(stderr, "cannot create socket"); + return -1; + } + + 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) { + DPRINTF(stderr, "cannot bind to %u\n", port); + BIO_closesocket(fd); + return -1; + } + + /* Set port to nonblocking mode */ + if (BIO_socket_nbio(fd, 1) <= 0) { + DPRINTF(stderr, "Unable to set port to nonblocking mode"); + BIO_closesocket(fd); + return -1; + } + + return fd; +} + +/* 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; + struct poll_manager *pm; + +#ifdef _WIN32 + progname = argv[0]; +#endif + + if (argc != 4) + errx(res, "usage: %s ", 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"); + } + + pm = create_poll_manager(); + if (pm == NULL) { + 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, pm, fd) < 0) { + SSL_CTX_free(ctx); + BIO_closesocket(fd); + ERR_print_errors_fp(stderr); + errx(res, "Error in QUIC server loop"); + } + + destroy_poll_manager(pm); + /* Free resources. */ + SSL_CTX_free(ctx); + BIO_closesocket(fd); + res = EXIT_SUCCESS; + return res; +}