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