1 #include "ossl-nghttp3.h"
2 #include <openssl/err.h>
5 #define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0]))
8 H3_STREAM_TYPE_CTRL_SEND
,
9 H3_STREAM_TYPE_QPACK_ENC_SEND
,
10 H3_STREAM_TYPE_QPACK_DEC_SEND
,
17 uint64_t id
; /* QUIC stream ID */
18 SSL
*s
; /* QUIC stream SSL object */
19 int done_recv_fin
; /* Received FIN */
22 uint8_t buf
[BUF_SIZE
];
23 size_t buf_cur
, buf_total
;
26 DEFINE_LHASH_OF_EX(H3_STREAM
);
28 static void h3_stream_free(H3_STREAM
*s
)
37 static unsigned long h3_stream_hash(const H3_STREAM
*s
)
39 return (unsigned long)s
->id
;
42 static int h3_stream_eq(const H3_STREAM
*a
, const H3_STREAM
*b
)
44 if (a
->id
< b
->id
) return -1;
45 if (a
->id
> b
->id
) return 1;
49 void *H3_STREAM_get_user_data(const H3_STREAM
*s
)
55 SSL
*qconn
; /* QUIC connection SSL object */
56 BIO
*qconn_bio
; /* BIO wrapping QCSO */
57 nghttp3_conn
*h3conn
; /* HTTP/3 connection object */
58 LHASH_OF(H3_STREAM
) *streams
; /* map of stream IDs to H3_STREAMs */
59 void *user_data
; /* opaque user data pointer */
62 size_t consumed_app_data
;
64 /* Forwarding callbacks */
65 nghttp3_recv_data recv_data_cb
;
66 nghttp3_stream_close stream_close_cb
;
67 nghttp3_stop_sending stop_sending_cb
;
68 nghttp3_reset_stream reset_stream_cb
;
69 nghttp3_deferred_consume deferred_consume_cb
;
72 void H3_CONN_free(H3_CONN
*conn
)
77 lh_H3_STREAM_doall(conn
->streams
, h3_stream_free
);
79 nghttp3_conn_del(conn
->h3conn
);
80 BIO_free_all(conn
->qconn_bio
);
81 lh_H3_STREAM_free(conn
->streams
);
85 static H3_STREAM
*h3_conn_create_stream(H3_CONN
*conn
, int type
)
88 uint64_t flags
= SSL_STREAM_FLAG_ADVANCE
;
90 if ((s
= OPENSSL_zalloc(sizeof(H3_STREAM
))) == NULL
)
93 if (type
!= H3_STREAM_TYPE_REQ
)
94 flags
|= SSL_STREAM_FLAG_UNI
;
96 if ((s
->s
= SSL_new_stream(conn
->qconn
, flags
)) == NULL
) {
97 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
98 "could not create QUIC stream object");
102 s
->id
= SSL_get_stream_id(s
->s
);
103 lh_H3_STREAM_insert(conn
->streams
, s
);
111 static H3_STREAM
*h3_conn_accept_stream(H3_CONN
*conn
, SSL
*qstream
)
115 if ((s
= OPENSSL_zalloc(sizeof(H3_STREAM
))) == NULL
)
118 s
->id
= SSL_get_stream_id(qstream
);
120 lh_H3_STREAM_insert(conn
->streams
, s
);
124 static void h3_conn_remove_stream(H3_CONN
*conn
, H3_STREAM
*s
)
129 lh_H3_STREAM_delete(conn
->streams
, s
);
133 static int h3_conn_recv_data(nghttp3_conn
*h3conn
, int64_t stream_id
,
134 const uint8_t *data
, size_t datalen
,
135 void *conn_user_data
, void *stream_user_data
)
137 H3_CONN
*conn
= conn_user_data
;
139 conn
->consumed_app_data
+= datalen
;
140 if (conn
->recv_data_cb
== NULL
)
143 return conn
->recv_data_cb(h3conn
, stream_id
, data
, datalen
,
144 conn_user_data
, stream_user_data
);
147 static int h3_conn_stream_close(nghttp3_conn
*h3conn
, int64_t stream_id
,
148 uint64_t app_error_code
,
149 void *conn_user_data
, void *stream_user_data
)
152 H3_CONN
*conn
= conn_user_data
;
153 H3_STREAM
*stream
= stream_user_data
;
155 if (conn
->stream_close_cb
!= NULL
)
156 ret
= conn
->stream_close_cb(h3conn
, stream_id
, app_error_code
,
157 conn_user_data
, stream_user_data
);
159 h3_conn_remove_stream(conn
, stream
);
163 static int h3_conn_stop_sending(nghttp3_conn
*h3conn
, int64_t stream_id
,
164 uint64_t app_error_code
,
165 void *conn_user_data
, void *stream_user_data
)
168 H3_CONN
*conn
= conn_user_data
;
169 H3_STREAM
*stream
= stream_user_data
;
171 if (conn
->stop_sending_cb
!= NULL
)
172 ret
= conn
->stop_sending_cb(h3conn
, stream_id
, app_error_code
,
173 conn_user_data
, stream_user_data
);
180 static int h3_conn_reset_stream(nghttp3_conn
*h3conn
, int64_t stream_id
,
181 uint64_t app_error_code
,
182 void *conn_user_data
, void *stream_user_data
)
185 H3_CONN
*conn
= conn_user_data
;
186 H3_STREAM
*stream
= stream_user_data
;
187 SSL_STREAM_RESET_ARGS args
= {0};
189 if (conn
->reset_stream_cb
!= NULL
)
190 ret
= conn
->reset_stream_cb(h3conn
, stream_id
, app_error_code
,
191 conn_user_data
, stream_user_data
);
193 if (stream
->s
!= NULL
) {
194 args
.quic_error_code
= app_error_code
;
196 if (!SSL_stream_reset(stream
->s
, &args
, sizeof(args
)))
203 static int h3_conn_deferred_consume(nghttp3_conn
*h3conn
, int64_t stream_id
,
205 void *conn_user_data
, void *stream_user_data
)
208 H3_CONN
*conn
= conn_user_data
;
210 if (conn
->deferred_consume_cb
!= NULL
)
211 ret
= conn
->deferred_consume_cb(h3conn
, stream_id
, consumed
,
212 conn_user_data
, stream_user_data
);
214 conn
->consumed_app_data
+= consumed
;
218 H3_CONN
*H3_CONN_new_for_conn(BIO
*qconn_bio
,
219 const nghttp3_callbacks
*callbacks
,
220 const nghttp3_settings
*settings
,
225 H3_STREAM
*s_ctl_send
= NULL
, *s_qpenc_send
= NULL
, *s_qpdec_send
= NULL
;
226 nghttp3_settings dsettings
= {0};
227 nghttp3_callbacks intl_callbacks
= {0};
228 static const unsigned char alpn
[] = {2, 'h', '3'};
230 if (qconn_bio
== NULL
) {
231 ERR_raise_data(ERR_LIB_USER
, ERR_R_PASSED_NULL_PARAMETER
,
232 "QUIC connection BIO must be provided");
236 if ((conn
= OPENSSL_zalloc(sizeof(H3_CONN
))) == NULL
)
239 conn
->qconn_bio
= qconn_bio
;
240 conn
->user_data
= user_data
;
242 if (BIO_get_ssl(qconn_bio
, &conn
->qconn
) == 0) {
243 ERR_raise_data(ERR_LIB_USER
, ERR_R_PASSED_INVALID_ARGUMENT
,
244 "BIO must be an SSL BIO");
248 if ((conn
->streams
= lh_H3_STREAM_new(h3_stream_hash
, h3_stream_eq
)) == NULL
)
251 if (SSL_in_before(conn
->qconn
))
252 if (SSL_set_alpn_protos(conn
->qconn
, alpn
, sizeof(alpn
))) {
253 /* SSL_set_alpn_protos returns 1 on failure */
254 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
255 "failed to configure ALPN");
259 BIO_set_nbio(conn
->qconn_bio
, 1);
261 if (!SSL_set_default_stream_mode(conn
->qconn
, SSL_DEFAULT_STREAM_MODE_NONE
)) {
262 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
263 "failed to configure default stream mode");
267 if ((s_ctl_send
= h3_conn_create_stream(conn
, H3_STREAM_TYPE_CTRL_SEND
)) == NULL
)
270 if ((s_qpenc_send
= h3_conn_create_stream(conn
, H3_STREAM_TYPE_QPACK_ENC_SEND
)) == NULL
)
273 if ((s_qpdec_send
= h3_conn_create_stream(conn
, H3_STREAM_TYPE_QPACK_DEC_SEND
)) == NULL
)
276 if (settings
== NULL
) {
277 nghttp3_settings_default(&dsettings
);
278 settings
= &dsettings
;
281 if (callbacks
!= NULL
)
282 intl_callbacks
= *callbacks
;
284 conn
->recv_data_cb
= intl_callbacks
.recv_data
;
285 conn
->stream_close_cb
= intl_callbacks
.stream_close
;
286 conn
->stop_sending_cb
= intl_callbacks
.stop_sending
;
287 conn
->reset_stream_cb
= intl_callbacks
.reset_stream
;
288 conn
->deferred_consume_cb
= intl_callbacks
.deferred_consume
;
290 intl_callbacks
.recv_data
= h3_conn_recv_data
;
291 intl_callbacks
.stream_close
= h3_conn_stream_close
;
292 intl_callbacks
.stop_sending
= h3_conn_stop_sending
;
293 intl_callbacks
.reset_stream
= h3_conn_reset_stream
;
294 intl_callbacks
.deferred_consume
= h3_conn_deferred_consume
;
296 ec
= nghttp3_conn_client_new(&conn
->h3conn
, &intl_callbacks
, settings
,
299 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
300 "cannot create nghttp3 connection: %s (%d)",
301 nghttp3_strerror(ec
), ec
);
305 ec
= nghttp3_conn_bind_control_stream(conn
->h3conn
, s_ctl_send
->id
);
307 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
308 "cannot bind nghttp3 control stream: %s (%d)",
309 nghttp3_strerror(ec
), ec
);
313 ec
= nghttp3_conn_bind_qpack_streams(conn
->h3conn
,
317 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
318 "cannot bind nghttp3 QPACK streams: %s (%d)",
319 nghttp3_strerror(ec
), ec
);
326 nghttp3_conn_del(conn
->h3conn
);
327 h3_stream_free(s_ctl_send
);
328 h3_stream_free(s_qpenc_send
);
329 h3_stream_free(s_qpdec_send
);
330 lh_H3_STREAM_free(conn
->streams
);
335 H3_CONN
*H3_CONN_new_for_addr(SSL_CTX
*ctx
, const char *addr
,
336 const nghttp3_callbacks
*callbacks
,
337 const nghttp3_settings
*settings
,
340 BIO
*qconn_bio
= NULL
;
342 H3_CONN
*conn
= NULL
;
343 const char *bare_hostname
;
345 /* QUIC connection setup */
346 if ((qconn_bio
= BIO_new_ssl_connect(ctx
)) == NULL
)
349 if (BIO_set_conn_hostname(qconn_bio
, addr
) == 0)
352 bare_hostname
= BIO_get_conn_hostname(qconn_bio
);
353 if (bare_hostname
== NULL
)
356 if (BIO_get_ssl(qconn_bio
, &qconn
) == 0)
359 if (SSL_set1_host(qconn
, bare_hostname
) <= 0)
362 conn
= H3_CONN_new_for_conn(qconn_bio
, callbacks
, settings
, user_data
);
369 BIO_free_all(qconn_bio
);
373 int H3_CONN_connect(H3_CONN
*conn
)
375 return SSL_connect(H3_CONN_get0_connection(conn
));
378 void *H3_CONN_get_user_data(const H3_CONN
*conn
)
380 return conn
->user_data
;
383 SSL
*H3_CONN_get0_connection(const H3_CONN
*conn
)
388 /* Pumps received data to the HTTP/3 stack for a single stream. */
389 static void h3_conn_pump_stream(H3_STREAM
*s
, void *conn_
)
392 H3_CONN
*conn
= conn_
;
393 size_t num_bytes
, consumed
;
401 || SSL_get_stream_read_state(s
->s
) == SSL_STREAM_STATE_WRONG_DIR
406 * Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek
407 * to get received data and passing it to nghttp3 using
408 * nghttp3_conn_read_stream. Note that this function is confusingly
409 * named and inputs data to the HTTP/3 stack.
411 if (s
->buf_cur
== s
->buf_total
) {
412 /* Need more data. */
413 ec
= SSL_read_ex(s
->s
, s
->buf
, sizeof(s
->buf
), &num_bytes
);
416 if (SSL_get_error(s
->s
, ec
) == SSL_ERROR_ZERO_RETURN
) {
417 /* Stream concluded normally. Pass FIN to HTTP/3 stack. */
418 ec
= nghttp3_conn_read_stream(conn
->h3conn
, s
->id
, NULL
, 0,
421 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
422 "cannot pass FIN to nghttp3: %s (%d)",
423 nghttp3_strerror(ec
), ec
);
427 s
->done_recv_fin
= 1;
428 } else if (SSL_get_stream_read_state(s
->s
)
429 == SSL_STREAM_STATE_RESET_REMOTE
) {
430 /* Stream was reset by peer. */
431 if (!SSL_get_stream_read_error_code(s
->s
, &aec
))
434 ec
= nghttp3_conn_close_stream(conn
->h3conn
, s
->id
, aec
);
436 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
437 "cannot mark stream as reset: %s (%d)",
438 nghttp3_strerror(ec
), ec
);
442 s
->done_recv_fin
= 1;
450 s
->buf_total
= num_bytes
;
453 if (s
->buf_cur
== s
->buf_total
)
456 assert(conn
->consumed_app_data
== 0);
457 ec
= nghttp3_conn_read_stream(conn
->h3conn
, s
->id
, s
->buf
+ s
->buf_cur
,
458 s
->buf_total
- s
->buf_cur
, /*fin=*/0);
460 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
461 "nghttp3 failed to process incoming data: %s (%d)",
462 nghttp3_strerror(ec
), ec
);
466 consumed
= ec
+ conn
->consumed_app_data
;
467 assert(consumed
<= s
->buf_total
- s
->buf_cur
);
468 s
->buf_cur
+= consumed
;
469 conn
->consumed_app_data
= 0;
477 int H3_CONN_handle_events(H3_CONN
*conn
)
480 size_t i
, num_vecs
, written
, total_written
, total_len
;
482 nghttp3_vec vecs
[8] = {0};
489 /* Check for new incoming streams */
491 if ((snew
= SSL_accept_stream(conn
->qconn
, SSL_ACCEPT_STREAM_NO_BLOCK
)) == NULL
)
494 if (h3_conn_accept_stream(conn
, snew
) == NULL
) {
500 /* Pump outgoing data from HTTP/3 engine to QUIC. */
502 /* Get a number of send vectors from the HTTP/3 engine. */
503 ec
= nghttp3_conn_writev_stream(conn
->h3conn
, &stream_id
, &fin
,
504 vecs
, ARRAY_LEN(vecs
));
510 /* For each of the vectors returned, pass it to OpenSSL QUIC. */
512 if ((s
= lh_H3_STREAM_retrieve(conn
->streams
, &key
)) == NULL
) {
513 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
514 "no stream for ID %zd", stream_id
);
519 total_len
= nghttp3_vec_len(vecs
, num_vecs
);
521 for (i
= 0; i
< num_vecs
; ++i
) {
522 if (vecs
[i
].len
== 0)
526 written
= vecs
[i
].len
;
527 } else if (!SSL_write_ex(s
->s
, vecs
[i
].base
, vecs
[i
].len
, &written
)) {
528 if (SSL_get_error(s
->s
, 0) == SSL_ERROR_WANT_WRITE
) {
530 nghttp3_conn_block_stream(conn
->h3conn
, stream_id
);
532 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
533 "writing HTTP/3 data to network failed");
537 nghttp3_conn_unblock_stream(conn
->h3conn
, stream_id
);
540 total_written
+= written
;
542 ec
= nghttp3_conn_add_write_offset(conn
->h3conn
, stream_id
, written
);
546 ec
= nghttp3_conn_add_ack_offset(conn
->h3conn
, stream_id
, written
);
552 if (fin
&& total_written
== total_len
) {
553 SSL_stream_conclude(s
->s
, 0);
554 if (total_len
== 0) {
555 ec
= nghttp3_conn_add_write_offset(conn
->h3conn
, stream_id
, 0);
562 /* Pump incoming data from QUIC to HTTP/3 engine. */
564 lh_H3_STREAM_doall_arg(conn
->streams
, h3_conn_pump_stream
, conn
);
571 int H3_CONN_submit_request(H3_CONN
*conn
, const nghttp3_nv
*nva
, size_t nvlen
,
572 const nghttp3_data_reader
*dr
,
576 H3_STREAM
*s_req
= NULL
;
579 ERR_raise_data(ERR_LIB_USER
, ERR_R_PASSED_NULL_PARAMETER
,
580 "connection must be specified");
584 if ((s_req
= h3_conn_create_stream(conn
, H3_STREAM_TYPE_REQ
)) == NULL
)
587 s_req
->user_data
= user_data
;
589 ec
= nghttp3_conn_submit_request(conn
->h3conn
, s_req
->id
, nva
, nvlen
,
592 ERR_raise_data(ERR_LIB_USER
, ERR_R_INTERNAL_ERROR
,
593 "cannot submit HTTP/3 request: %s (%d)",
594 nghttp3_strerror(ec
), ec
);
601 h3_conn_remove_stream(conn
, s_req
);