/* * 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 HTTP/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 monitor 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 = OPENSSL_strdup(fill_pattern)) == NULL) { OPENSSL_free(rts); return NULL; } rts->rts_pattern_len = (unsigned int)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 = OPENSSL_strdup(fill_pattern)) == NULL) { OPENSSL_free(rtf); return NULL; } rtf->rtf_pattern_len = (unsigned int)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_array(POLL_GROW, sizeof(struct poll_event)); 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 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_poll_set = OPENSSL_realloc_array(pm->pm_poll_set, pm->pm_poll_set_sz + POLL_GROW, sizeof(struct poll_event)); 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_poll_set = OPENSSL_realloc_array(pm->pm_poll_set, pm->pm_poll_set_sz - POLL_DOWNSIZ, sizeof(struct poll_event)); 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 = (unsigned int)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 -= (unsigned int)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 = (int)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; }