1 1.1 christos /* 2 1.1 christos * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved. 3 1.1 christos * 4 1.1 christos * Licensed under the Apache License 2.0 (the "License"). You may not use 5 1.1 christos * this file except in compliance with the License. You can obtain a copy 6 1.1 christos * in the file LICENSE in the source distribution or at 7 1.1 christos * https://www.openssl.org/source/license.html 8 1.1 christos */ 9 1.1 christos #include "ossl-nghttp3.h" 10 1.1 christos #include <openssl/err.h> 11 1.1 christos #include <assert.h> 12 1.1 christos 13 1.1.1.2 christos #define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) 14 1.1 christos 15 1.1 christos enum { 16 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND, 17 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND, 18 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND, 19 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_REQ, 20 1.1 christos }; 21 1.1 christos 22 1.1.1.2 christos #define BUF_SIZE 4096 23 1.1 christos 24 1.1 christos struct ossl_demo_h3_stream_st { 25 1.1.1.2 christos uint64_t id; /* QUIC stream ID */ 26 1.1.1.2 christos SSL *s; /* QUIC stream SSL object */ 27 1.1.1.2 christos int done_recv_fin; /* Received FIN */ 28 1.1.1.2 christos void *user_data; 29 1.1 christos 30 1.1.1.2 christos uint8_t buf[BUF_SIZE]; 31 1.1.1.2 christos size_t buf_cur, buf_total; 32 1.1 christos }; 33 1.1 christos 34 1.1 christos DEFINE_LHASH_OF_EX(OSSL_DEMO_H3_STREAM); 35 1.1 christos 36 1.1 christos static void h3_stream_free(OSSL_DEMO_H3_STREAM *s) 37 1.1 christos { 38 1.1 christos if (s == NULL) 39 1.1 christos return; 40 1.1 christos 41 1.1 christos SSL_free(s->s); 42 1.1 christos OPENSSL_free(s); 43 1.1 christos } 44 1.1 christos 45 1.1 christos static unsigned long h3_stream_hash(const OSSL_DEMO_H3_STREAM *s) 46 1.1 christos { 47 1.1 christos return (unsigned long)s->id; 48 1.1 christos } 49 1.1 christos 50 1.1 christos static int h3_stream_eq(const OSSL_DEMO_H3_STREAM *a, const OSSL_DEMO_H3_STREAM *b) 51 1.1 christos { 52 1.1.1.2 christos if (a->id < b->id) 53 1.1.1.2 christos return -1; 54 1.1.1.2 christos if (a->id > b->id) 55 1.1.1.2 christos return 1; 56 1.1 christos return 0; 57 1.1 christos } 58 1.1 christos 59 1.1 christos void *OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM *s) 60 1.1 christos { 61 1.1 christos return s->user_data; 62 1.1 christos } 63 1.1 christos 64 1.1 christos struct ossl_demo_h3_conn_st { 65 1.1 christos /* QUIC connection SSL object */ 66 1.1.1.2 christos SSL *qconn; 67 1.1 christos /* BIO wrapping QCSO */ 68 1.1.1.2 christos BIO *qconn_bio; 69 1.1 christos /* HTTP/3 connection object */ 70 1.1.1.2 christos nghttp3_conn *h3conn; 71 1.1 christos /* map of stream IDs to OSSL_DEMO_H3_STREAMs */ 72 1.1.1.2 christos LHASH_OF(OSSL_DEMO_H3_STREAM) *streams; 73 1.1 christos /* opaque user data pointer */ 74 1.1.1.2 christos void *user_data; 75 1.1 christos 76 1.1.1.2 christos int pump_res; 77 1.1.1.2 christos size_t consumed_app_data; 78 1.1 christos 79 1.1 christos /* Forwarding callbacks */ 80 1.1.1.2 christos nghttp3_recv_data recv_data_cb; 81 1.1.1.2 christos nghttp3_stream_close stream_close_cb; 82 1.1.1.2 christos nghttp3_stop_sending stop_sending_cb; 83 1.1.1.2 christos nghttp3_reset_stream reset_stream_cb; 84 1.1.1.2 christos nghttp3_deferred_consume deferred_consume_cb; 85 1.1 christos }; 86 1.1 christos 87 1.1 christos void OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN *conn) 88 1.1 christos { 89 1.1 christos if (conn == NULL) 90 1.1 christos return; 91 1.1 christos 92 1.1 christos lh_OSSL_DEMO_H3_STREAM_doall(conn->streams, h3_stream_free); 93 1.1 christos 94 1.1 christos nghttp3_conn_del(conn->h3conn); 95 1.1 christos BIO_free_all(conn->qconn_bio); 96 1.1 christos lh_OSSL_DEMO_H3_STREAM_free(conn->streams); 97 1.1 christos OPENSSL_free(conn); 98 1.1 christos } 99 1.1 christos 100 1.1 christos static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type) 101 1.1 christos { 102 1.1 christos OSSL_DEMO_H3_STREAM *s; 103 1.1 christos uint64_t flags = SSL_STREAM_FLAG_ADVANCE; 104 1.1 christos 105 1.1 christos if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL) 106 1.1 christos return NULL; 107 1.1 christos 108 1.1 christos if (type != OSSL_DEMO_H3_STREAM_TYPE_REQ) 109 1.1 christos flags |= SSL_STREAM_FLAG_UNI; 110 1.1 christos 111 1.1 christos if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) { 112 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 113 1.1.1.2 christos "could not create QUIC stream object"); 114 1.1 christos goto err; 115 1.1 christos } 116 1.1 christos 117 1.1.1.2 christos s->id = SSL_get_stream_id(s->s); 118 1.1 christos lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s); 119 1.1 christos return s; 120 1.1 christos 121 1.1 christos err: 122 1.1 christos OPENSSL_free(s); 123 1.1 christos return NULL; 124 1.1 christos } 125 1.1 christos 126 1.1 christos static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream) 127 1.1 christos { 128 1.1 christos OSSL_DEMO_H3_STREAM *s; 129 1.1 christos 130 1.1 christos if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL) 131 1.1 christos return NULL; 132 1.1 christos 133 1.1.1.2 christos s->id = SSL_get_stream_id(qstream); 134 1.1.1.2 christos s->s = qstream; 135 1.1 christos lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s); 136 1.1 christos return s; 137 1.1 christos } 138 1.1 christos 139 1.1 christos static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s) 140 1.1 christos { 141 1.1 christos if (s == NULL) 142 1.1 christos return; 143 1.1 christos 144 1.1 christos lh_OSSL_DEMO_H3_STREAM_delete(conn->streams, s); 145 1.1 christos h3_stream_free(s); 146 1.1 christos } 147 1.1 christos 148 1.1 christos static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id, 149 1.1.1.2 christos const uint8_t *data, size_t datalen, 150 1.1.1.2 christos void *conn_user_data, void *stream_user_data) 151 1.1 christos { 152 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data; 153 1.1 christos 154 1.1 christos conn->consumed_app_data += datalen; 155 1.1 christos if (conn->recv_data_cb == NULL) 156 1.1 christos return 0; 157 1.1 christos 158 1.1 christos return conn->recv_data_cb(h3conn, stream_id, data, datalen, 159 1.1.1.2 christos conn_user_data, stream_user_data); 160 1.1 christos } 161 1.1 christos 162 1.1 christos static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id, 163 1.1.1.2 christos uint64_t app_error_code, 164 1.1.1.2 christos void *conn_user_data, void *stream_user_data) 165 1.1 christos { 166 1.1 christos int ret = 0; 167 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data; 168 1.1 christos OSSL_DEMO_H3_STREAM *stream = stream_user_data; 169 1.1 christos 170 1.1 christos if (conn->stream_close_cb != NULL) 171 1.1 christos ret = conn->stream_close_cb(h3conn, stream_id, app_error_code, 172 1.1.1.2 christos conn_user_data, stream_user_data); 173 1.1 christos 174 1.1 christos h3_conn_remove_stream(conn, stream); 175 1.1 christos return ret; 176 1.1 christos } 177 1.1 christos 178 1.1 christos static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id, 179 1.1.1.2 christos uint64_t app_error_code, 180 1.1.1.2 christos void *conn_user_data, void *stream_user_data) 181 1.1 christos { 182 1.1 christos int ret = 0; 183 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data; 184 1.1 christos OSSL_DEMO_H3_STREAM *stream = stream_user_data; 185 1.1 christos 186 1.1 christos if (conn->stop_sending_cb != NULL) 187 1.1 christos ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code, 188 1.1.1.2 christos conn_user_data, stream_user_data); 189 1.1 christos 190 1.1 christos SSL_free(stream->s); 191 1.1 christos stream->s = NULL; 192 1.1 christos return ret; 193 1.1 christos } 194 1.1 christos 195 1.1 christos static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id, 196 1.1.1.2 christos uint64_t app_error_code, 197 1.1.1.2 christos void *conn_user_data, void *stream_user_data) 198 1.1 christos { 199 1.1 christos int ret = 0; 200 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data; 201 1.1 christos OSSL_DEMO_H3_STREAM *stream = stream_user_data; 202 1.1.1.2 christos SSL_STREAM_RESET_ARGS args = { 0 }; 203 1.1 christos 204 1.1 christos if (conn->reset_stream_cb != NULL) 205 1.1 christos ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code, 206 1.1.1.2 christos conn_user_data, stream_user_data); 207 1.1 christos 208 1.1 christos if (stream->s != NULL) { 209 1.1 christos args.quic_error_code = app_error_code; 210 1.1 christos 211 1.1 christos if (!SSL_stream_reset(stream->s, &args, sizeof(args))) 212 1.1 christos return 1; 213 1.1 christos } 214 1.1 christos 215 1.1 christos return ret; 216 1.1 christos } 217 1.1 christos 218 1.1 christos static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id, 219 1.1.1.2 christos size_t consumed, 220 1.1.1.2 christos void *conn_user_data, void *stream_user_data) 221 1.1 christos { 222 1.1 christos int ret = 0; 223 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data; 224 1.1 christos 225 1.1 christos if (conn->deferred_consume_cb != NULL) 226 1.1 christos ret = conn->deferred_consume_cb(h3conn, stream_id, consumed, 227 1.1.1.2 christos conn_user_data, stream_user_data); 228 1.1 christos 229 1.1 christos conn->consumed_app_data += consumed; 230 1.1 christos return ret; 231 1.1 christos } 232 1.1 christos 233 1.1 christos OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio, 234 1.1.1.2 christos const nghttp3_callbacks *callbacks, 235 1.1.1.2 christos const nghttp3_settings *settings, 236 1.1.1.2 christos void *user_data) 237 1.1 christos { 238 1.1 christos int ec; 239 1.1 christos OSSL_DEMO_H3_CONN *conn; 240 1.1 christos OSSL_DEMO_H3_STREAM *s_ctl_send = NULL; 241 1.1 christos OSSL_DEMO_H3_STREAM *s_qpenc_send = NULL; 242 1.1 christos OSSL_DEMO_H3_STREAM *s_qpdec_send = NULL; 243 1.1.1.2 christos nghttp3_settings dsettings = { 0 }; 244 1.1.1.2 christos nghttp3_callbacks intl_callbacks = { 0 }; 245 1.1.1.2 christos static const unsigned char alpn[] = { 2, 'h', '3' }; 246 1.1 christos 247 1.1 christos if (qconn_bio == NULL) { 248 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, 249 1.1.1.2 christos "QUIC connection BIO must be provided"); 250 1.1 christos return NULL; 251 1.1 christos } 252 1.1 christos 253 1.1 christos if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL) 254 1.1 christos return NULL; 255 1.1 christos 256 1.1 christos conn->qconn_bio = qconn_bio; 257 1.1 christos conn->user_data = user_data; 258 1.1 christos 259 1.1 christos if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) { 260 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT, 261 1.1.1.2 christos "BIO must be an SSL BIO"); 262 1.1 christos goto err; 263 1.1 christos } 264 1.1 christos 265 1.1 christos /* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */ 266 1.1 christos if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL) 267 1.1 christos goto err; 268 1.1 christos 269 1.1 christos /* 270 1.1 christos * If the application has not started connecting yet, helpfully 271 1.1 christos * auto-configure ALPN. If the application wants to initiate the connection 272 1.1 christos * itself, it must take care of this itself. 273 1.1 christos */ 274 1.1 christos if (SSL_in_before(conn->qconn)) 275 1.1 christos if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) { 276 1.1 christos /* SSL_set_alpn_protos returns 1 on failure */ 277 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 278 1.1.1.2 christos "failed to configure ALPN"); 279 1.1 christos goto err; 280 1.1 christos } 281 1.1 christos 282 1.1 christos /* 283 1.1 christos * We use the QUIC stack in non-blocking mode so that we can react to 284 1.1 christos * incoming data on different streams, and e.g. incoming streams initiated 285 1.1 christos * by a server, as and when events occur. 286 1.1 christos */ 287 1.1 christos BIO_set_nbio(conn->qconn_bio, 1); 288 1.1 christos 289 1.1 christos /* 290 1.1 christos * Disable default stream mode and create all streams explicitly. Each QUIC 291 1.1 christos * stream will be represented by its own QUIC stream SSL object (QSSO). This 292 1.1 christos * also automatically enables us to accept incoming streams (see 293 1.1 christos * SSL_set_incoming_stream_policy(3)). 294 1.1 christos */ 295 1.1 christos if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) { 296 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 297 1.1.1.2 christos "failed to configure default stream mode"); 298 1.1 christos goto err; 299 1.1 christos } 300 1.1 christos 301 1.1 christos /* 302 1.1 christos * HTTP/3 requires a couple of unidirectional management streams: a control 303 1.1 christos * stream and some QPACK state management streams for each side of a 304 1.1 christos * connection. These are the instances on our side (with us sending); the 305 1.1 christos * server will also create its own equivalent unidirectional streams on its 306 1.1 christos * side, which we handle subsequently as they come in (see SSL_accept_stream 307 1.1 christos * in the event handling code below). 308 1.1 christos */ 309 1.1 christos if ((s_ctl_send 310 1.1.1.2 christos = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND)) 311 1.1.1.2 christos == NULL) 312 1.1 christos goto err; 313 1.1 christos 314 1.1 christos if ((s_qpenc_send 315 1.1.1.2 christos = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND)) 316 1.1.1.2 christos == NULL) 317 1.1 christos goto err; 318 1.1 christos 319 1.1 christos if ((s_qpdec_send 320 1.1.1.2 christos = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND)) 321 1.1.1.2 christos == NULL) 322 1.1 christos goto err; 323 1.1 christos 324 1.1 christos if (settings == NULL) { 325 1.1 christos nghttp3_settings_default(&dsettings); 326 1.1 christos settings = &dsettings; 327 1.1 christos } 328 1.1 christos 329 1.1 christos if (callbacks != NULL) 330 1.1 christos intl_callbacks = *callbacks; 331 1.1 christos 332 1.1 christos /* 333 1.1 christos * We need to do some of our own processing when many of these events occur, 334 1.1 christos * so we note the original callback functions and forward appropriately. 335 1.1 christos */ 336 1.1.1.2 christos conn->recv_data_cb = intl_callbacks.recv_data; 337 1.1.1.2 christos conn->stream_close_cb = intl_callbacks.stream_close; 338 1.1.1.2 christos conn->stop_sending_cb = intl_callbacks.stop_sending; 339 1.1.1.2 christos conn->reset_stream_cb = intl_callbacks.reset_stream; 340 1.1.1.2 christos conn->deferred_consume_cb = intl_callbacks.deferred_consume; 341 1.1.1.2 christos 342 1.1.1.2 christos intl_callbacks.recv_data = h3_conn_recv_data; 343 1.1.1.2 christos intl_callbacks.stream_close = h3_conn_stream_close; 344 1.1.1.2 christos intl_callbacks.stop_sending = h3_conn_stop_sending; 345 1.1.1.2 christos intl_callbacks.reset_stream = h3_conn_reset_stream; 346 1.1 christos intl_callbacks.deferred_consume = h3_conn_deferred_consume; 347 1.1 christos 348 1.1 christos /* Create the HTTP/3 client state. */ 349 1.1 christos ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings, 350 1.1.1.2 christos NULL, conn); 351 1.1 christos if (ec < 0) { 352 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 353 1.1.1.2 christos "cannot create nghttp3 connection: %s (%d)", 354 1.1.1.2 christos nghttp3_strerror(ec), ec); 355 1.1 christos goto err; 356 1.1 christos } 357 1.1 christos 358 1.1 christos /* 359 1.1 christos * Tell the HTTP/3 stack which stream IDs are used for our outgoing control 360 1.1 christos * and QPACK streams. Note that we don't have to tell the HTTP/3 stack what 361 1.1 christos * IDs are used for incoming streams as this is inferred automatically from 362 1.1 christos * the stream type byte which starts every incoming unidirectional stream, 363 1.1 christos * so it will autodetect the correct stream IDs for the incoming control and 364 1.1 christos * QPACK streams initiated by the server. 365 1.1 christos */ 366 1.1 christos ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id); 367 1.1 christos if (ec < 0) { 368 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 369 1.1.1.2 christos "cannot bind nghttp3 control stream: %s (%d)", 370 1.1.1.2 christos nghttp3_strerror(ec), ec); 371 1.1 christos goto err; 372 1.1 christos } 373 1.1 christos 374 1.1 christos ec = nghttp3_conn_bind_qpack_streams(conn->h3conn, 375 1.1.1.2 christos s_qpenc_send->id, 376 1.1.1.2 christos s_qpdec_send->id); 377 1.1 christos if (ec < 0) { 378 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 379 1.1.1.2 christos "cannot bind nghttp3 QPACK streams: %s (%d)", 380 1.1.1.2 christos nghttp3_strerror(ec), ec); 381 1.1 christos goto err; 382 1.1 christos } 383 1.1 christos 384 1.1 christos return conn; 385 1.1 christos 386 1.1 christos err: 387 1.1 christos nghttp3_conn_del(conn->h3conn); 388 1.1 christos h3_stream_free(s_ctl_send); 389 1.1 christos h3_stream_free(s_qpenc_send); 390 1.1 christos h3_stream_free(s_qpdec_send); 391 1.1 christos lh_OSSL_DEMO_H3_STREAM_free(conn->streams); 392 1.1 christos OPENSSL_free(conn); 393 1.1 christos return NULL; 394 1.1 christos } 395 1.1 christos 396 1.1 christos OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr, 397 1.1.1.2 christos const nghttp3_callbacks *callbacks, 398 1.1.1.2 christos const nghttp3_settings *settings, 399 1.1.1.2 christos void *user_data) 400 1.1 christos { 401 1.1 christos BIO *qconn_bio = NULL; 402 1.1 christos SSL *qconn = NULL; 403 1.1 christos OSSL_DEMO_H3_CONN *conn = NULL; 404 1.1 christos const char *bare_hostname; 405 1.1 christos 406 1.1 christos /* QUIC connection setup */ 407 1.1 christos if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL) 408 1.1 christos goto err; 409 1.1 christos 410 1.1 christos /* Pass the 'hostname:port' string into the ssl_connect BIO. */ 411 1.1 christos if (BIO_set_conn_hostname(qconn_bio, addr) == 0) 412 1.1 christos goto err; 413 1.1 christos 414 1.1 christos /* 415 1.1 christos * Get the 'bare' hostname out of the ssl_connect BIO. This is the hostname 416 1.1 christos * without the port. 417 1.1 christos */ 418 1.1 christos bare_hostname = BIO_get_conn_hostname(qconn_bio); 419 1.1 christos if (bare_hostname == NULL) 420 1.1 christos goto err; 421 1.1 christos 422 1.1 christos if (BIO_get_ssl(qconn_bio, &qconn) == 0) 423 1.1 christos goto err; 424 1.1 christos 425 1.1 christos /* Set the hostname we will validate the X.509 certificate against. */ 426 1.1 christos if (SSL_set1_host(qconn, bare_hostname) <= 0) 427 1.1 christos goto err; 428 1.1 christos 429 1.1 christos /* Configure SNI */ 430 1.1 christos if (!SSL_set_tlsext_host_name(qconn, bare_hostname)) 431 1.1 christos goto err; 432 1.1 christos 433 1.1 christos conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn_bio, callbacks, 434 1.1.1.2 christos settings, user_data); 435 1.1 christos if (conn == NULL) 436 1.1 christos goto err; 437 1.1 christos 438 1.1 christos return conn; 439 1.1 christos 440 1.1 christos err: 441 1.1 christos BIO_free_all(qconn_bio); 442 1.1 christos return NULL; 443 1.1 christos } 444 1.1 christos 445 1.1 christos int OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN *conn) 446 1.1 christos { 447 1.1 christos return SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn)); 448 1.1 christos } 449 1.1 christos 450 1.1 christos void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn) 451 1.1 christos { 452 1.1 christos return conn->user_data; 453 1.1 christos } 454 1.1 christos 455 1.1 christos SSL *OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN *conn) 456 1.1 christos { 457 1.1 christos return conn->qconn; 458 1.1 christos } 459 1.1 christos 460 1.1 christos /* Pumps received data to the HTTP/3 stack for a single stream. */ 461 1.1 christos static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_) 462 1.1 christos { 463 1.1 christos int ec; 464 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_; 465 1.1 christos size_t num_bytes, consumed; 466 1.1 christos uint64_t aec; 467 1.1 christos 468 1.1 christos if (!conn->pump_res) 469 1.1 christos /* 470 1.1 christos * Handling of a previous stream in the iteration over all streams 471 1.1 christos * failed, so just do nothing. 472 1.1 christos */ 473 1.1 christos return; 474 1.1 christos 475 1.1 christos for (;;) { 476 1.1 christos if (s->s == NULL /* If we already did STOP_SENDING, ignore this stream. */ 477 1.1 christos /* If this is a write-only stream, there is no read data to check. */ 478 1.1 christos || SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR 479 1.1 christos /* 480 1.1 christos * If we already got a FIN for this stream, there is nothing more to 481 1.1 christos * do for it. 482 1.1 christos */ 483 1.1 christos || s->done_recv_fin) 484 1.1 christos break; 485 1.1 christos 486 1.1 christos /* 487 1.1 christos * Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek 488 1.1 christos * to get received data and passing it to nghttp3 using 489 1.1 christos * nghttp3_conn_read_stream. Note that this function is confusingly 490 1.1 christos * named and inputs data to the HTTP/3 stack. 491 1.1 christos */ 492 1.1 christos if (s->buf_cur == s->buf_total) { 493 1.1 christos /* Need more data. */ 494 1.1 christos ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes); 495 1.1 christos if (ec <= 0) { 496 1.1 christos num_bytes = 0; 497 1.1 christos if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) { 498 1.1 christos /* Stream concluded normally. Pass FIN to HTTP/3 stack. */ 499 1.1 christos ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0, 500 1.1.1.2 christos /*fin=*/1); 501 1.1 christos if (ec < 0) { 502 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 503 1.1.1.2 christos "cannot pass FIN to nghttp3: %s (%d)", 504 1.1.1.2 christos nghttp3_strerror(ec), ec); 505 1.1 christos goto err; 506 1.1 christos } 507 1.1 christos 508 1.1 christos s->done_recv_fin = 1; 509 1.1 christos } else if (SSL_get_stream_read_state(s->s) 510 1.1.1.2 christos == SSL_STREAM_STATE_RESET_REMOTE) { 511 1.1 christos /* Stream was reset by peer. */ 512 1.1 christos if (!SSL_get_stream_read_error_code(s->s, &aec)) 513 1.1 christos goto err; 514 1.1 christos 515 1.1 christos ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec); 516 1.1 christos if (ec < 0) { 517 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 518 1.1.1.2 christos "cannot mark stream as reset: %s (%d)", 519 1.1.1.2 christos nghttp3_strerror(ec), ec); 520 1.1 christos goto err; 521 1.1 christos } 522 1.1 christos 523 1.1 christos s->done_recv_fin = 1; 524 1.1 christos } else { 525 1.1 christos /* Other error. */ 526 1.1 christos goto err; 527 1.1 christos } 528 1.1 christos } 529 1.1 christos 530 1.1.1.2 christos s->buf_cur = 0; 531 1.1.1.2 christos s->buf_total = num_bytes; 532 1.1 christos } 533 1.1 christos 534 1.1 christos if (s->buf_cur == s->buf_total) 535 1.1 christos break; 536 1.1 christos 537 1.1 christos /* 538 1.1 christos * This function is confusingly named as it is is named from nghttp3's 539 1.1 christos * 'perspective'; it is used to pass data *into* the HTTP/3 stack which 540 1.1 christos * has been received from the network. 541 1.1 christos */ 542 1.1 christos assert(conn->consumed_app_data == 0); 543 1.1 christos ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur, 544 1.1.1.2 christos s->buf_total - s->buf_cur, /*fin=*/0); 545 1.1 christos if (ec < 0) { 546 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 547 1.1.1.2 christos "nghttp3 failed to process incoming data: %s (%d)", 548 1.1.1.2 christos nghttp3_strerror(ec), ec); 549 1.1 christos goto err; 550 1.1 christos } 551 1.1 christos 552 1.1 christos /* 553 1.1 christos * read_stream reports the data it consumes from us in two different 554 1.1 christos * ways; the non-application data is returned as a number of bytes 'ec' 555 1.1 christos * above, but the number of bytes of application data has to be recorded 556 1.1 christos * by our callback. We sum the two to determine the total number of 557 1.1 christos * bytes which nghttp3 consumed. 558 1.1 christos */ 559 1.1 christos consumed = ec + conn->consumed_app_data; 560 1.1 christos assert(consumed <= s->buf_total - s->buf_cur); 561 1.1 christos s->buf_cur += consumed; 562 1.1 christos conn->consumed_app_data = 0; 563 1.1 christos } 564 1.1 christos 565 1.1 christos return; 566 1.1 christos err: 567 1.1 christos conn->pump_res = 0; 568 1.1 christos } 569 1.1 christos 570 1.1 christos int OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN *conn) 571 1.1 christos { 572 1.1 christos int ec, fin; 573 1.1 christos size_t i, num_vecs, written, total_written, total_len; 574 1.1 christos int64_t stream_id; 575 1.1 christos uint64_t flags; 576 1.1.1.2 christos nghttp3_vec vecs[8] = { 0 }; 577 1.1 christos OSSL_DEMO_H3_STREAM key, *s; 578 1.1 christos SSL *snew; 579 1.1 christos 580 1.1 christos if (conn == NULL) 581 1.1 christos return 0; 582 1.1 christos 583 1.1 christos /* 584 1.1 christos * We handle events by doing three things: 585 1.1 christos * 586 1.1 christos * 1. Handle new incoming streams 587 1.1 christos * 2. Pump outgoing data from the HTTP/3 stack to the QUIC engine 588 1.1 christos * 3. Pump incoming data from the QUIC engine to the HTTP/3 stack 589 1.1 christos */ 590 1.1 christos 591 1.1 christos /* 1. Check for new incoming streams */ 592 1.1 christos for (;;) { 593 1.1 christos if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL) 594 1.1 christos break; 595 1.1 christos 596 1.1 christos /* 597 1.1 christos * Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and 598 1.1 christos * added into our stream ID map. 599 1.1 christos */ 600 1.1 christos if (h3_conn_accept_stream(conn, snew) == NULL) { 601 1.1 christos SSL_free(snew); 602 1.1 christos return 0; 603 1.1 christos } 604 1.1 christos } 605 1.1 christos 606 1.1 christos /* 2. Pump outgoing data from HTTP/3 engine to QUIC. */ 607 1.1 christos for (;;) { 608 1.1 christos /* 609 1.1 christos * Get a number of send vectors from the HTTP/3 engine. 610 1.1 christos * 611 1.1 christos * Note that this function is confusingly named as it is named from 612 1.1 christos * nghttp3's 'perspective': this outputs pointers to data which nghttp3 613 1.1 christos * wants to *write* to the network. 614 1.1 christos */ 615 1.1 christos ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin, 616 1.1.1.2 christos vecs, ARRAY_LEN(vecs)); 617 1.1 christos if (ec < 0) 618 1.1 christos return 0; 619 1.1 christos if (ec == 0) 620 1.1 christos break; 621 1.1 christos 622 1.1 christos /* 623 1.1.1.2 christos * we let SSL_write_ex2(3) to conclude the stream for us (send FIN) 624 1.1.1.2 christos * after all data are written. 625 1.1 christos */ 626 1.1 christos flags = (fin == 0) ? 0 : SSL_WRITE_FLAG_CONCLUDE; 627 1.1 christos 628 1.1 christos /* For each of the vectors returned, pass it to OpenSSL QUIC. */ 629 1.1 christos key.id = stream_id; 630 1.1 christos if ((s = lh_OSSL_DEMO_H3_STREAM_retrieve(conn->streams, &key)) == NULL) { 631 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 632 1.1.1.2 christos "no stream for ID %zd", stream_id); 633 1.1 christos return 0; 634 1.1 christos } 635 1.1 christos 636 1.1 christos num_vecs = ec; 637 1.1 christos total_len = nghttp3_vec_len(vecs, num_vecs); 638 1.1 christos total_written = 0; 639 1.1 christos for (i = 0; i < num_vecs; ++i) { 640 1.1 christos if (vecs[i].len == 0) 641 1.1 christos continue; 642 1.1 christos 643 1.1 christos if (s->s == NULL) { 644 1.1 christos /* Already did STOP_SENDING and threw away stream, ignore */ 645 1.1 christos written = vecs[i].len; 646 1.1 christos } else if (!SSL_write_ex2(s->s, vecs[i].base, vecs[i].len, flags, &written)) { 647 1.1 christos if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) { 648 1.1 christos /* 649 1.1 christos * We have filled our send buffer so tell nghttp3 to stop 650 1.1 christos * generating more data; we have to do this explicitly. 651 1.1 christos */ 652 1.1 christos written = 0; 653 1.1 christos nghttp3_conn_block_stream(conn->h3conn, stream_id); 654 1.1 christos } else { 655 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 656 1.1.1.2 christos "writing HTTP/3 data to network failed"); 657 1.1 christos return 0; 658 1.1 christos } 659 1.1 christos } else { 660 1.1 christos /* 661 1.1 christos * Tell nghttp3 it can resume generating more data in case we 662 1.1 christos * previously called block_stream. 663 1.1 christos */ 664 1.1 christos nghttp3_conn_unblock_stream(conn->h3conn, stream_id); 665 1.1 christos } 666 1.1 christos 667 1.1 christos total_written += written; 668 1.1 christos if (written > 0) { 669 1.1 christos /* 670 1.1 christos * Tell nghttp3 we have consumed the data it output when we 671 1.1 christos * called writev_stream, otherwise subsequent calls to 672 1.1 christos * writev_stream will output the same data. 673 1.1 christos */ 674 1.1 christos ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written); 675 1.1 christos if (ec < 0) 676 1.1 christos return 0; 677 1.1 christos 678 1.1 christos /* 679 1.1 christos * Tell nghttp3 it can free the buffered data because we will 680 1.1 christos * not need it again. In our case we can always do this right 681 1.1 christos * away because we copy the data into our QUIC send buffers 682 1.1 christos * rather than simply storing a reference to it. 683 1.1 christos */ 684 1.1 christos ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written); 685 1.1 christos if (ec < 0) 686 1.1 christos return 0; 687 1.1 christos } 688 1.1 christos } 689 1.1 christos 690 1.1 christos if (fin && total_written == total_len) { 691 1.1 christos 692 1.1 christos if (total_len == 0) { 693 1.1 christos /* 694 1.1 christos * As a special case, if nghttp3 requested to write a 695 1.1 christos * zero-length stream with a FIN, we have to tell it we did this 696 1.1 christos * by calling add_write_offset(0). 697 1.1 christos */ 698 1.1 christos ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0); 699 1.1 christos if (ec < 0) 700 1.1 christos return 0; 701 1.1 christos } 702 1.1 christos } 703 1.1 christos } 704 1.1 christos 705 1.1 christos /* 3. Pump incoming data from QUIC to HTTP/3 engine. */ 706 1.1 christos conn->pump_res = 1; /* cleared in below call if an error occurs */ 707 1.1 christos lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn); 708 1.1 christos if (!conn->pump_res) 709 1.1 christos return 0; 710 1.1 christos 711 1.1 christos return 1; 712 1.1 christos } 713 1.1 christos 714 1.1 christos int OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN *conn, 715 1.1.1.2 christos const nghttp3_nv *nva, size_t nvlen, 716 1.1.1.2 christos const nghttp3_data_reader *dr, 717 1.1.1.2 christos void *user_data) 718 1.1 christos { 719 1.1 christos int ec; 720 1.1 christos OSSL_DEMO_H3_STREAM *s_req = NULL; 721 1.1 christos 722 1.1 christos if (conn == NULL) { 723 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, 724 1.1.1.2 christos "connection must be specified"); 725 1.1 christos return 0; 726 1.1 christos } 727 1.1 christos 728 1.1 christos /* Each HTTP/3 request is represented by a stream. */ 729 1.1 christos if ((s_req = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_REQ)) == NULL) 730 1.1 christos goto err; 731 1.1 christos 732 1.1 christos s_req->user_data = user_data; 733 1.1 christos 734 1.1 christos ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen, 735 1.1.1.2 christos dr, s_req); 736 1.1 christos if (ec < 0) { 737 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, 738 1.1.1.2 christos "cannot submit HTTP/3 request: %s (%d)", 739 1.1.1.2 christos nghttp3_strerror(ec), ec); 740 1.1 christos goto err; 741 1.1 christos } 742 1.1 christos 743 1.1 christos return 1; 744 1.1 christos 745 1.1 christos err: 746 1.1 christos h3_conn_remove_stream(conn, s_req); 747 1.1 christos return 0; 748 1.1 christos } 749