From e33af8000f2e2f4700d9845dce5bbcad9bf77c92 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Mon, 16 Oct 2023 13:33:11 +0100 Subject: QUIC: Add HTTP/3 demo using nghttp3 Reviewed-by: Matt Caswell Reviewed-by: Tim Hudson (Merged from https://github.com/openssl/openssl/pull/22369) --- demos/http3/Makefile | 14 + demos/http3/ossl-nghttp3-demo.c | 142 ++++++++++ demos/http3/ossl-nghttp3.c | 603 ++++++++++++++++++++++++++++++++++++++++ demos/http3/ossl-nghttp3.h | 96 +++++++ 4 files changed, 855 insertions(+) create mode 100644 demos/http3/Makefile create mode 100644 demos/http3/ossl-nghttp3-demo.c create mode 100644 demos/http3/ossl-nghttp3.c create mode 100644 demos/http3/ossl-nghttp3.h (limited to 'demos') diff --git a/demos/http3/Makefile b/demos/http3/Makefile new file mode 100644 index 0000000000..89bac3743e --- /dev/null +++ b/demos/http3/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -I../../include -g -Wall +LDFLAGS = -L../.. +LDLIBS = -lcrypto -lssl -lnghttp3 + +all: ossl-nghttp3-demo + +clean: + $(RM) -f ossl-nghttp3-demo *.o + +ossl-nghttp3-demo: ossl-nghttp3-demo.o ossl-nghttp3.o + $(CC) $(CFLAGS) -o "$@" $^ $(LDFLAGS) $(LDLIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o "$@" "$<" diff --git a/demos/http3/ossl-nghttp3-demo.c b/demos/http3/ossl-nghttp3-demo.c new file mode 100644 index 0000000000..53acf31e40 --- /dev/null +++ b/demos/http3/ossl-nghttp3-demo.c @@ -0,0 +1,142 @@ +#include "ossl-nghttp3.h" +#include + +static int done; + +static void make_nv(nghttp3_nv *nv, const char *name, const char *value) +{ + nv->name = (uint8_t *)name; + nv->value = (uint8_t *)value; + nv->namelen = strlen(name); + nv->valuelen = strlen(value); + nv->flags = NGHTTP3_NV_FLAG_NONE; +} + +static int on_recv_header(nghttp3_conn *h3conn, int64_t stream_id, + int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, + uint8_t flags, + void *conn_user_data, + void *stream_user_data) +{ + nghttp3_vec vname, vvalue; + + /* Received a single HTTP header. */ + vname = nghttp3_rcbuf_get_buf(name); + vvalue = nghttp3_rcbuf_get_buf(value); + + fwrite(vname.base, vname.len, 1, stderr); + fprintf(stderr, ": "); + fwrite(vvalue.base, vvalue.len, 1, stderr); + fprintf(stderr, "\n"); + + return 0; +} + +static int on_end_headers(nghttp3_conn *h3conn, int64_t stream_id, + int fin, + void *conn_user_data, void *stream_user_data) +{ + fprintf(stderr, "\n"); + return 0; +} + +static int on_recv_data(nghttp3_conn *h3conn, int64_t stream_id, + const uint8_t *data, size_t datalen, + void *conn_user_data, void *stream_user_data) +{ + ssize_t wr; + + /* HTTP response body data - write it to stdout. */ + while (datalen > 0) { + wr = fwrite(data, 1, datalen, stdout); + if (wr < 0) + return 1; + + data += wr; + datalen -= wr; + } + + return 0; +} + +static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id, + void *conn_user_data, void *stream_user_data) +{ + /* HTTP transaction is done - set done flag so that we stop looping. */ + done = 1; + return 0; +} + +int main(int argc, char **argv) +{ + int ret = 1; + SSL_CTX *ctx = NULL; + H3_CONN *conn = NULL; + nghttp3_nv nva[16]; + nghttp3_callbacks callbacks = {0}; + size_t num_nv = 0; + const char *addr; + + /* Check arguments. */ + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + goto err; + } + + addr = argv[1]; + + /* Setup SSL_CTX. */ + if ((ctx = SSL_CTX_new(OSSL_QUIC_client_method())) == NULL) + goto err; + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + if (SSL_CTX_set_default_verify_paths(ctx) == 0) + goto err; + + /* Setup callbacks. */ + callbacks.recv_header = on_recv_header; + callbacks.end_headers = on_end_headers; + callbacks.recv_data = on_recv_data; + callbacks.end_stream = on_end_stream; + + /* Create connection. */ + if ((conn = H3_CONN_new_for_addr(ctx, addr, &callbacks, + NULL, NULL)) == NULL) { + ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, + "cannot create HTTP/3 connection"); + goto err; + } + + /* Build HTTP headers. */ + make_nv(&nva[num_nv++], ":method", "GET"); + make_nv(&nva[num_nv++], ":scheme", "https"); + make_nv(&nva[num_nv++], ":authority", addr); + make_nv(&nva[num_nv++], ":path", "/"); + make_nv(&nva[num_nv++], "user-agent", "OpenSSL-Demo/nghttp3"); + + /* Submit request. */ + if (!H3_CONN_submit_request(conn, nva, num_nv, NULL, NULL)) { + ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, + "cannot submit HTTP/3 request"); + goto err; + } + + /* Wait for request to complete. */ + while (!done) + if (!H3_CONN_handle_events(conn)) { + ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, + "cannot handle events"); + goto err; + } + + ret = 0; +err: + if (ret != 0) + ERR_print_errors_fp(stderr); + + H3_CONN_free(conn); + SSL_CTX_free(ctx); + return ret; +} diff --git a/demos/http3/ossl-nghttp3.c b/demos/http3/ossl-nghttp3.c new file mode 100644 index 0000000000..97bf8baec3 --- /dev/null +++ b/demos/http3/ossl-nghttp3.c @@ -0,0 +1,603 @@ +#include "ossl-nghttp3.h" +#include +#include + +#define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0])) + +enum { + H3_STREAM_TYPE_CTRL_SEND, + H3_STREAM_TYPE_QPACK_ENC_SEND, + H3_STREAM_TYPE_QPACK_DEC_SEND, + H3_STREAM_TYPE_REQ, +}; + +#define BUF_SIZE 4096 + +struct h3_stream_st { + uint64_t id; /* QUIC stream ID */ + SSL *s; /* QUIC stream SSL object */ + int done_recv_fin; /* Received FIN */ + void *user_data; + + uint8_t buf[BUF_SIZE]; + size_t buf_cur, buf_total; +}; + +DEFINE_LHASH_OF_EX(H3_STREAM); + +static void h3_stream_free(H3_STREAM *s) +{ + if (s == NULL) + return; + + SSL_free(s->s); + OPENSSL_free(s); +} + +static unsigned long h3_stream_hash(const H3_STREAM *s) +{ + return (unsigned long)s->id; +} + +static int h3_stream_eq(const H3_STREAM *a, const H3_STREAM *b) +{ + if (a->id < b->id) return -1; + if (a->id > b->id) return 1; + return 0; +} + +void *H3_STREAM_get_user_data(const H3_STREAM *s) +{ + return s->user_data; +} + +struct h3_conn_st { + SSL *qconn; /* QUIC connection SSL object */ + BIO *qconn_bio; /* BIO wrapping QCSO */ + nghttp3_conn *h3conn; /* HTTP/3 connection object */ + LHASH_OF(H3_STREAM) *streams; /* map of stream IDs to H3_STREAMs */ + void *user_data; /* opaque user data pointer */ + + int pump_res; + size_t consumed_app_data; + + /* Forwarding callbacks */ + nghttp3_recv_data recv_data_cb; + nghttp3_stream_close stream_close_cb; + nghttp3_stop_sending stop_sending_cb; + nghttp3_reset_stream reset_stream_cb; + nghttp3_deferred_consume deferred_consume_cb; +}; + +void H3_CONN_free(H3_CONN *conn) +{ + if (conn == NULL) + return; + + lh_H3_STREAM_doall(conn->streams, h3_stream_free); + + nghttp3_conn_del(conn->h3conn); + BIO_free_all(conn->qconn_bio); + lh_H3_STREAM_free(conn->streams); + OPENSSL_free(conn); +} + +static H3_STREAM *h3_conn_create_stream(H3_CONN *conn, int type) +{ + H3_STREAM *s; + uint64_t flags = SSL_STREAM_FLAG_ADVANCE; + + if ((s = OPENSSL_zalloc(sizeof(H3_STREAM))) == NULL) + return NULL; + + if (type != H3_STREAM_TYPE_REQ) + flags |= SSL_STREAM_FLAG_UNI; + + if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "could not create QUIC stream object"); + goto err; + } + + s->id = SSL_get_stream_id(s->s); + lh_H3_STREAM_insert(conn->streams, s); + return s; + +err: + OPENSSL_free(s); + return NULL; +} + +static H3_STREAM *h3_conn_accept_stream(H3_CONN *conn, SSL *qstream) +{ + H3_STREAM *s; + + if ((s = OPENSSL_zalloc(sizeof(H3_STREAM))) == NULL) + return NULL; + + s->id = SSL_get_stream_id(qstream); + s->s = qstream; + lh_H3_STREAM_insert(conn->streams, s); + return s; +} + +static void h3_conn_remove_stream(H3_CONN *conn, H3_STREAM *s) +{ + if (s == NULL) + return; + + lh_H3_STREAM_delete(conn->streams, s); + h3_stream_free(s); +} + +static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id, + const uint8_t *data, size_t datalen, + void *conn_user_data, void *stream_user_data) +{ + H3_CONN *conn = conn_user_data; + + conn->consumed_app_data += datalen; + if (conn->recv_data_cb == NULL) + return 0; + + return conn->recv_data_cb(h3conn, stream_id, data, datalen, + conn_user_data, stream_user_data); +} + +static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id, + uint64_t app_error_code, + void *conn_user_data, void *stream_user_data) +{ + int ret = 0; + H3_CONN *conn = conn_user_data; + H3_STREAM *stream = stream_user_data; + + if (conn->stream_close_cb != NULL) + ret = conn->stream_close_cb(h3conn, stream_id, app_error_code, + conn_user_data, stream_user_data); + + h3_conn_remove_stream(conn, stream); + return ret; +} + +static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id, + uint64_t app_error_code, + void *conn_user_data, void *stream_user_data) +{ + int ret = 0; + H3_CONN *conn = conn_user_data; + H3_STREAM *stream = stream_user_data; + + if (conn->stop_sending_cb != NULL) + ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code, + conn_user_data, stream_user_data); + + SSL_free(stream->s); + stream->s = NULL; + return ret; +} + +static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id, + uint64_t app_error_code, + void *conn_user_data, void *stream_user_data) +{ + int ret = 0; + H3_CONN *conn = conn_user_data; + H3_STREAM *stream = stream_user_data; + SSL_STREAM_RESET_ARGS args = {0}; + + if (conn->reset_stream_cb != NULL) + ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code, + conn_user_data, stream_user_data); + + if (stream->s != NULL) { + args.quic_error_code = app_error_code; + + if (!SSL_stream_reset(stream->s, &args, sizeof(args))) + return 1; + } + + return ret; +} + +static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id, + size_t consumed, + void *conn_user_data, void *stream_user_data) +{ + int ret = 0; + H3_CONN *conn = conn_user_data; + + if (conn->deferred_consume_cb != NULL) + ret = conn->deferred_consume_cb(h3conn, stream_id, consumed, + conn_user_data, stream_user_data); + + conn->consumed_app_data += consumed; + return ret; +} + +H3_CONN *H3_CONN_new_for_conn(BIO *qconn_bio, + const nghttp3_callbacks *callbacks, + const nghttp3_settings *settings, + void *user_data) +{ + int ec; + H3_CONN *conn; + H3_STREAM *s_ctl_send = NULL, *s_qpenc_send = NULL, *s_qpdec_send = NULL; + nghttp3_settings dsettings = {0}; + nghttp3_callbacks intl_callbacks = {0}; + static const unsigned char alpn[] = {2, 'h', '3'}; + + if (qconn_bio == NULL) { + ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, + "QUIC connection BIO must be provided"); + return NULL; + } + + if ((conn = OPENSSL_zalloc(sizeof(H3_CONN))) == NULL) + return NULL; + + conn->qconn_bio = qconn_bio; + conn->user_data = user_data; + + if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT, + "BIO must be an SSL BIO"); + goto err; + } + + if ((conn->streams = lh_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL) + goto err; + + if (SSL_in_before(conn->qconn)) + if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) { + /* SSL_set_alpn_protos returns 1 on failure */ + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "failed to configure ALPN"); + goto err; + } + + BIO_set_nbio(conn->qconn_bio, 1); + + if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "failed to configure default stream mode"); + goto err; + } + + if ((s_ctl_send = h3_conn_create_stream(conn, H3_STREAM_TYPE_CTRL_SEND)) == NULL) + goto err; + + if ((s_qpenc_send = h3_conn_create_stream(conn, H3_STREAM_TYPE_QPACK_ENC_SEND)) == NULL) + goto err; + + if ((s_qpdec_send = h3_conn_create_stream(conn, H3_STREAM_TYPE_QPACK_DEC_SEND)) == NULL) + goto err; + + if (settings == NULL) { + nghttp3_settings_default(&dsettings); + settings = &dsettings; + } + + if (callbacks != NULL) + intl_callbacks = *callbacks; + + conn->recv_data_cb = intl_callbacks.recv_data; + conn->stream_close_cb = intl_callbacks.stream_close; + conn->stop_sending_cb = intl_callbacks.stop_sending; + conn->reset_stream_cb = intl_callbacks.reset_stream; + conn->deferred_consume_cb = intl_callbacks.deferred_consume; + + intl_callbacks.recv_data = h3_conn_recv_data; + intl_callbacks.stream_close = h3_conn_stream_close; + intl_callbacks.stop_sending = h3_conn_stop_sending; + intl_callbacks.reset_stream = h3_conn_reset_stream; + intl_callbacks.deferred_consume = h3_conn_deferred_consume; + + ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings, + NULL, conn); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "cannot create nghttp3 connection: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "cannot bind nghttp3 control stream: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + ec = nghttp3_conn_bind_qpack_streams(conn->h3conn, + s_qpenc_send->id, + s_qpdec_send->id); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "cannot bind nghttp3 QPACK streams: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + return conn; + +err: + nghttp3_conn_del(conn->h3conn); + h3_stream_free(s_ctl_send); + h3_stream_free(s_qpenc_send); + h3_stream_free(s_qpdec_send); + lh_H3_STREAM_free(conn->streams); + OPENSSL_free(conn); + return NULL; +} + +H3_CONN *H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr, + const nghttp3_callbacks *callbacks, + const nghttp3_settings *settings, + void *user_data) +{ + BIO *qconn_bio = NULL; + SSL *qconn = NULL; + H3_CONN *conn = NULL; + const char *bare_hostname; + + /* QUIC connection setup */ + if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL) + goto err; + + if (BIO_set_conn_hostname(qconn_bio, addr) == 0) + goto err; + + bare_hostname = BIO_get_conn_hostname(qconn_bio); + if (bare_hostname == NULL) + goto err; + + if (BIO_get_ssl(qconn_bio, &qconn) == 0) + goto err; + + if (SSL_set1_host(qconn, bare_hostname) <= 0) + goto err; + + conn = H3_CONN_new_for_conn(qconn_bio, callbacks, settings, user_data); + if (conn == NULL) + goto err; + + return conn; + +err: + BIO_free_all(qconn_bio); + return NULL; +} + +int H3_CONN_connect(H3_CONN *conn) +{ + return SSL_connect(H3_CONN_get0_connection(conn)); +} + +void *H3_CONN_get_user_data(const H3_CONN *conn) +{ + return conn->user_data; +} + +SSL *H3_CONN_get0_connection(const H3_CONN *conn) +{ + return conn->qconn; +} + +/* Pumps received data to the HTTP/3 stack for a single stream. */ +static void h3_conn_pump_stream(H3_STREAM *s, void *conn_) +{ + int ec; + H3_CONN *conn = conn_; + size_t num_bytes, consumed; + uint64_t aec; + + if (!conn->pump_res) + return; + + for (;;) { + if (s->s == NULL + || SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR + || s->done_recv_fin) + break; + + /* + * Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek + * to get received data and passing it to nghttp3 using + * nghttp3_conn_read_stream. Note that this function is confusingly + * named and inputs data to the HTTP/3 stack. + */ + if (s->buf_cur == s->buf_total) { + /* Need more data. */ + ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes); + if (ec <= 0) { + num_bytes = 0; + if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) { + /* Stream concluded normally. Pass FIN to HTTP/3 stack. */ + ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0, + /*fin=*/1); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "cannot pass FIN to nghttp3: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + s->done_recv_fin = 1; + } else if (SSL_get_stream_read_state(s->s) + == SSL_STREAM_STATE_RESET_REMOTE) { + /* Stream was reset by peer. */ + if (!SSL_get_stream_read_error_code(s->s, &aec)) + goto err; + + ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "cannot mark stream as reset: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + s->done_recv_fin = 1; + } else { + /* Other error. */ + goto err; + } + } + + s->buf_cur = 0; + s->buf_total = num_bytes; + } + + if (s->buf_cur == s->buf_total) + break; + + assert(conn->consumed_app_data == 0); + ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur, + s->buf_total - s->buf_cur, /*fin=*/0); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "nghttp3 failed to process incoming data: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + consumed = ec + conn->consumed_app_data; + assert(consumed <= s->buf_total - s->buf_cur); + s->buf_cur += consumed; + conn->consumed_app_data = 0; + } + + return; +err: + conn->pump_res = 0; +} + +int H3_CONN_handle_events(H3_CONN *conn) +{ + int ec, fin; + size_t i, num_vecs, written, total_written, total_len; + int64_t stream_id; + nghttp3_vec vecs[8] = {0}; + H3_STREAM key, *s; + SSL *snew; + + if (conn == NULL) + return 0; + + /* Check for new incoming streams */ + for (;;) { + if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL) + break; + + if (h3_conn_accept_stream(conn, snew) == NULL) { + SSL_free(snew); + return 0; + } + } + + /* Pump outgoing data from HTTP/3 engine to QUIC. */ + for (;;) { + /* Get a number of send vectors from the HTTP/3 engine. */ + ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin, + vecs, ARRAY_LEN(vecs)); + if (ec < 0) + return 0; + if (ec == 0) + break; + + /* For each of the vectors returned, pass it to OpenSSL QUIC. */ + key.id = stream_id; + if ((s = lh_H3_STREAM_retrieve(conn->streams, &key)) == NULL) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "no stream for ID %zd", stream_id); + return 0; + } + + num_vecs = ec; + total_len = nghttp3_vec_len(vecs, num_vecs); + total_written = 0; + for (i = 0; i < num_vecs; ++i) { + if (vecs[i].len == 0) + continue; + + if (s->s == NULL) { + written = vecs[i].len; + } else if (!SSL_write_ex(s->s, vecs[i].base, vecs[i].len, &written)) { + if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) { + written = 0; + nghttp3_conn_block_stream(conn->h3conn, stream_id); + } else { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "writing HTTP/3 data to network failed"); + return 0; + } + } else { + nghttp3_conn_unblock_stream(conn->h3conn, stream_id); + } + + total_written += written; + if (written > 0) { + ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written); + if (ec < 0) + return 0; + + ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written); + if (ec < 0) + return 0; + } + } + + if (fin && total_written == total_len) { + SSL_stream_conclude(s->s, 0); + if (total_len == 0) { + ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0); + if (ec < 0) + return 0; + } + } + } + + /* Pump incoming data from QUIC to HTTP/3 engine. */ + conn->pump_res = 1; + lh_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn); + if (!conn->pump_res) + return 0; + + return 1; +} + +int H3_CONN_submit_request(H3_CONN *conn, const nghttp3_nv *nva, size_t nvlen, + const nghttp3_data_reader *dr, + void *user_data) +{ + int ec; + H3_STREAM *s_req = NULL; + + if (conn == NULL) { + ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, + "connection must be specified"); + return 0; + } + + if ((s_req = h3_conn_create_stream(conn, H3_STREAM_TYPE_REQ)) == NULL) + goto err; + + s_req->user_data = user_data; + + ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen, + dr, s_req); + if (ec < 0) { + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "cannot submit HTTP/3 request: %s (%d)", + nghttp3_strerror(ec), ec); + goto err; + } + + return 1; + +err: + h3_conn_remove_stream(conn, s_req); + return 0; +} diff --git a/demos/http3/ossl-nghttp3.h b/demos/http3/ossl-nghttp3.h new file mode 100644 index 0000000000..5013354947 --- /dev/null +++ b/demos/http3/ossl-nghttp3.h @@ -0,0 +1,96 @@ +#ifndef OSSL_NGHTTP3_H +# define OSSL_NGHTTP3_H + +# include +# include +# include + +/* + * ossl-nghttp3: Demo binding of nghttp3 to OpenSSL QUIC + * ===================================================== + * + * This is a simple library which provides an example binding of the nghttp3 + * HTTP/3 library to OpenSSL's QUIC API. + */ + +/* Represents an HTTP/3 connection to a server. */ +typedef struct h3_conn_st H3_CONN; + +/* Represents an HTTP/3 request, control or QPACK stream. */ +typedef struct h3_stream_st H3_STREAM; + +/* + * Creates a HTTP/3 connection using the given QUIC client connection BIO. The + * BIO must be able to provide an SSL object pointer using BIO_get_ssl. Takes + * ownership of the reference. If the QUIC connection SSL object has not already + * been connected, HTTP/3 ALPN is set automatically. If it has already been + * connected, HTTP/3 ALPN ("h3") must have been configured and no streams must + * have been created yet. + * + * If settings is NULL, use default settings only. Settings unsupported by + * this QUIC binding are ignored. + * + * user_data is an application-provided opaque value which can be retrieved + * using H3_CONN_get_user_data. Note that the user data value passed to the + * callback functions specified in callbacks is a pointer to the H3_CONN, not + * user_data. + * + * Returns NULL on failure. + */ +H3_CONN *H3_CONN_new_for_conn(BIO *qconn_bio, + const nghttp3_callbacks *callbacks, + const nghttp3_settings *settings, + void *user_data); + +/* + * Works identically to H3_CONN_new_for_conn except that it manages the creation + * of a QUIC connection SSL object automatically using an address string. addr + * should be a string such as "www.example.com:443". The created underlying QUIC + * connection SSL object is owned by the H3_CONN and can be subsequently + * retrieved using H3_CONN_get0_connection. + * + * Returns NULL on failure. ctx must be a SSL_CTX using a QUIC client + * SSL_METHOD. + */ +H3_CONN *H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr, + const nghttp3_callbacks *callbacks, + const nghttp3_settings *settings, + void *user_data); + +/* Equivalent to SSL_connect(H3_CONN_get0_connection(conn)). */ +int H3_CONN_connect(H3_CONN *conn); + +/* + * Free the H3_CONN and any underlying QUIC connection SSL object and associated + * streams. + */ +void H3_CONN_free(H3_CONN *conn); + +/* Returns the user data value which was specified in H3_CONN_new_for_conn. */ +void *H3_CONN_get_user_data(const H3_CONN *conn); + +/* Returns the underlying QUIC connection SSL object. */ +SSL *H3_CONN_get0_connection(const H3_CONN *conn); + +/* + * Handle any pending events on a given HTTP/3 connection. Returns 0 on error. + */ +int H3_CONN_handle_events(H3_CONN *conn); + +/* + * Submits a new HTTP/3 request on the given connection. Returns 0 on error. + * + * This works analogously to nghttp3_conn_submit_request(). The stream user data + * pointer passed to the callbacks is a H3_STREAM object pointer; to retrieve + * the stream user data pointer passed to this function, use + * H3_STREAM_get_user_data. + */ +int H3_CONN_submit_request(H3_CONN *conn, + const nghttp3_nv *hdr, size_t hdrlen, + const nghttp3_data_reader *dr, + void *stream_user_data); + +/* Returns the user data value which was specified in H3_CONN_submit_request. */ +void *H3_STREAM_get_user_data(const H3_STREAM *stream); + +#endif -- cgit v1.2.3