1 | =pod
|
---|
2 |
|
---|
3 | =begin comment
|
---|
4 |
|
---|
5 | NB: Changes to the source code samples in this file should also be reflected in
|
---|
6 | demos/guide/quic-multi-stream.c
|
---|
7 |
|
---|
8 | =end comment
|
---|
9 |
|
---|
10 | =head1 NAME
|
---|
11 |
|
---|
12 | ossl-guide-quic-multi-stream
|
---|
13 | - OpenSSL Guide: Writing a simple multi-stream QUIC client
|
---|
14 |
|
---|
15 | =head1 INTRODUCTION
|
---|
16 |
|
---|
17 | This page will introduce some important concepts required to write a simple
|
---|
18 | QUIC multi-stream application. It assumes a basic understanding of QUIC and how
|
---|
19 | it is used in OpenSSL. See L<ossl-guide-quic-introduction(7)> and
|
---|
20 | L<ossl-guide-quic-client-block(7)>.
|
---|
21 |
|
---|
22 | =head1 QUIC STREAMS
|
---|
23 |
|
---|
24 | In a QUIC multi-stream application we separate out the concepts of a QUIC
|
---|
25 | "connection" and a QUIC "stream". A connection object represents the overarching
|
---|
26 | details of the connection between a client and a server including all its
|
---|
27 | negotiated and configured parameters. We use the B<SSL> object for that in an
|
---|
28 | OpenSSL application (known as the connection B<SSL> object). It is created by an
|
---|
29 | application calling L<SSL_new(3)>.
|
---|
30 |
|
---|
31 | Separately a connection can have zero or more streams associated with it
|
---|
32 | (although a connection with zero streams is probably not very useful, so
|
---|
33 | normally you would have at least one). A stream is used to send and receive
|
---|
34 | data between the two peers. Each stream is also represented by an B<SSL>
|
---|
35 | object. A stream is logically independent of all the other streams associated
|
---|
36 | with the same connection. Data sent on a stream is guaranteed to be delivered
|
---|
37 | in the order that it was sent within that stream. The same is not true across
|
---|
38 | streams, e.g. if an application sends data on stream 1 first and then sends some
|
---|
39 | more data on stream 2 second, then the remote peer may receive the data sent on
|
---|
40 | stream 2 before it receives the data sent on stream 1.
|
---|
41 |
|
---|
42 | Once the connection B<SSL> object has completed its handshake (i.e.
|
---|
43 | L<SSL_connect(3)> has returned 1), stream B<SSL> objects are created by the
|
---|
44 | application calling L<SSL_new_stream(3)> or L<SSL_accept_stream(3)> (see
|
---|
45 | L</CREATING NEW STREAMS> below).
|
---|
46 |
|
---|
47 | The same threading rules apply to B<SSL> objects as for most OpenSSL objects
|
---|
48 | (see L<ossl-guide-libraries-introduction(7)>). In particular most OpenSSL
|
---|
49 | functions are thread safe, but the B<SSL> object is not. This means that you can
|
---|
50 | use an B<SSL> object representing one stream at the same time as another thread
|
---|
51 | is using a different B<SSL> object for a different stream on the same
|
---|
52 | connection. But you cannot use the same B<SSL> object on two different threads
|
---|
53 | at the same time (without additional application level locking).
|
---|
54 |
|
---|
55 | =head1 THE DEFAULT STREAM
|
---|
56 |
|
---|
57 | A connection B<SSL> object may also (optionally) be associated with a stream.
|
---|
58 | This stream is known as the default stream. The default stream is automatically
|
---|
59 | created and associated with the B<SSL> object when the application calls
|
---|
60 | L<SSL_read_ex(3)>, L<SSL_read(3)>, L<SSL_write_ex(3)> or L<SSL_write(3)> and
|
---|
61 | passes the connection B<SSL> object as a parameter.
|
---|
62 |
|
---|
63 | If a client application calls L<SSL_write_ex(3)> or L<SSL_write(3)> first then
|
---|
64 | (by default) the default stream will be a client-initiated bi-directional
|
---|
65 | stream. If a client application calls L<SSL_read_ex(3)> or L<SSL_read(3)>
|
---|
66 | first then the first stream initiated by the server will be used as the default
|
---|
67 | stream (whether it is bi-directional or uni-directional).
|
---|
68 |
|
---|
69 | This behaviour can be controlled via the default stream mode. See
|
---|
70 | L<SSL_set_default_stream_mode(3)> for further details.
|
---|
71 |
|
---|
72 | It is recommended that new multi-stream applications should not use a default
|
---|
73 | stream at all and instead should use a separate stream B<SSL> object for each
|
---|
74 | stream that is used. This requires calling L<SSL_set_default_stream_mode(3)>
|
---|
75 | and setting the mode to B<SSL_DEFAULT_STREAM_MODE_NONE>.
|
---|
76 |
|
---|
77 | =head1 CREATING NEW STREAMS
|
---|
78 |
|
---|
79 | An endpoint can create a new stream by calling L<SSL_new_stream(3)>. This
|
---|
80 | creates a locally initiated stream. In order to do so you must pass the QUIC
|
---|
81 | connection B<SSL> object as a parameter. You can also specify whether you want a
|
---|
82 | bi-directional or a uni-directional stream.
|
---|
83 |
|
---|
84 | The function returns a new QUIC stream B<SSL> object for sending and receiving
|
---|
85 | data on that stream.
|
---|
86 |
|
---|
87 | The peer may also initiate streams. An application can use the function
|
---|
88 | L<SSL_get_accept_stream_queue_len(3)> to determine the number of streams that
|
---|
89 | the peer has initiated that are waiting for the application to handle. An
|
---|
90 | application can call L<SSL_accept_stream(3)> to create a new B<SSL> object for
|
---|
91 | a remotely initiated stream. If the peer has not initiated any then this call
|
---|
92 | will block until one is available if the connection object is in blocking mode
|
---|
93 | (see L<SSL_set_blocking_mode(3)>).
|
---|
94 |
|
---|
95 | When using a default stream OpenSSL will prevent new streams from being
|
---|
96 | accepted. To override this behaviour you must call
|
---|
97 | L<SSL_set_incoming_stream_policy(3)> to set the policy to
|
---|
98 | B<SSL_INCOMING_STREAM_POLICY_ACCEPT>. See the man page for further details. This
|
---|
99 | is not relevant if the default stream has been disabled as described in
|
---|
100 | L</THE DEFAULT STREAM> above.
|
---|
101 |
|
---|
102 | Any stream may be bi-directional or uni-directional. If it is uni-directional
|
---|
103 | then the initiator can write to it but not read from it, and vice-versa for the
|
---|
104 | peer. You can determine what type of stream an B<SSL> object represents by
|
---|
105 | calling L<SSL_get_stream_type(3)>. See the man page for further details.
|
---|
106 |
|
---|
107 | =head1 USING A STREAM TO SEND AND RECEIVE DATA
|
---|
108 |
|
---|
109 | Once you have a stream B<SSL> object (which includes the connection B<SSL>
|
---|
110 | object if a default stream is in use) then you can send and receive data over it
|
---|
111 | using the L<SSL_write_ex(3)>, L<SSL_write(3)>, L<SSL_read_ex(3)> or
|
---|
112 | L<SSL_read(3)> functions. See the man pages for further details.
|
---|
113 |
|
---|
114 | In the event of one of these functions not returning a success code then
|
---|
115 | you should call L<SSL_get_error(3)> to find out further details about the error.
|
---|
116 | In blocking mode this will either be a fatal error (e.g. B<SSL_ERROR_SYSCALL>
|
---|
117 | or B<SSL_ERROR_SSL>), or it will be B<SSL_ERROR_ZERO_RETURN> which can occur
|
---|
118 | when attempting to read data from a stream and the peer has indicated that the
|
---|
119 | stream is concluded (i.e. "FIN" has been signalled on the stream). This means
|
---|
120 | that the peer will send no more data on that stream. Note that the
|
---|
121 | interpretation of B<SSL_ERROR_ZERO_RETURN> is slightly different for a QUIC
|
---|
122 | application compared to a TLS application. In TLS it occurs when the connection
|
---|
123 | has been shutdown by the peer. In QUIC this only tells you that the current
|
---|
124 | stream has been concluded by the peer. It tells you nothing about the underlying
|
---|
125 | connection. If the peer has concluded the stream then no more data will be
|
---|
126 | received on it, however an application can still send data to the peer until
|
---|
127 | the send side of the stream has also been concluded. This can happen by the
|
---|
128 | application calling L<SSL_stream_conclude(3)>. It is an error to attempt to
|
---|
129 | send more data on a stream after L<SSL_stream_conclude(3)> has been called.
|
---|
130 |
|
---|
131 | It is also possible to abandon a stream abnormally by calling
|
---|
132 | L<SSL_stream_reset(3)>.
|
---|
133 |
|
---|
134 | Once a stream object is no longer needed it should be freed via a call to
|
---|
135 | L<SSL_free(3)>. An application should not call L<SSL_shutdown(3)> on it since
|
---|
136 | this is only meaningful for connection level B<SSL> objects. Freeing the stream
|
---|
137 | will automatically signal STOP_SENDING to the peer.
|
---|
138 |
|
---|
139 | =head1 STREAMS AND CONNECTIONS
|
---|
140 |
|
---|
141 | Given a stream object it is possible to get the B<SSL> object corresponding to
|
---|
142 | the connection via a call to L<SSL_get0_connection(3)>. Multi-threaded
|
---|
143 | restrictions apply so care should be taken when using the returned connection
|
---|
144 | object. Specifically, if you are handling each of your stream objects in a
|
---|
145 | different thread and call L<SSL_get0_connection(3)> from within that thread then
|
---|
146 | you must be careful to not to call any function that uses the connection object
|
---|
147 | at the same time as one of the other threads is also using that connection
|
---|
148 | object (with the exception of L<SSL_accept_stream(3)> and
|
---|
149 | L<SSL_get_accept_stream_queue_len(3)> which are thread-safe).
|
---|
150 |
|
---|
151 | A stream object does not inherit all its settings and values from its parent
|
---|
152 | B<SSL> connection object. Therefore certain function calls that are relevant to
|
---|
153 | the connection as a whole will not work on a stream. For example the function
|
---|
154 | L<SSL_get_certificate(3)> can be used to obtain a handle on the peer certificate
|
---|
155 | when called with a connection B<SSL> object. When called with a stream B<SSL>
|
---|
156 | object it will return NULL.
|
---|
157 |
|
---|
158 | =head1 SIMPLE MULTI-STREAM QUIC CLIENT EXAMPLE
|
---|
159 |
|
---|
160 | This section will present various source code samples demonstrating how to write
|
---|
161 | a simple multi-stream QUIC client application which connects to a server, send
|
---|
162 | some HTTP/1.0 requests to it, and read back the responses. Note that HTTP/1.0
|
---|
163 | over QUIC is non-standard and will not be supported by real world servers. This
|
---|
164 | is for demonstration purposes only.
|
---|
165 |
|
---|
166 | We will build on the example code for the simple blocking QUIC client that is
|
---|
167 | covered on the L<ossl-guide-quic-client-block(7)> page and we assume that you
|
---|
168 | are familiar with it. We will only describe the differences between the simple
|
---|
169 | blocking QUIC client and the multi-stream QUIC client. Although the example code
|
---|
170 | uses blocking B<SSL> objects, you can equally use nonblocking B<SSL> objects.
|
---|
171 | See L<ossl-guide-quic-client-non-block(7)> for more information about writing a
|
---|
172 | nonblocking QUIC client.
|
---|
173 |
|
---|
174 | The complete source code for this example multi-stream QUIC client is available
|
---|
175 | in the C<demos/guide> directory of the OpenSSL source distribution in the file
|
---|
176 | C<quic-multi-stream.c>. It is also available online at
|
---|
177 | L<https://github.com/openssl/openssl/blob/master/demos/guide/quic-multi-stream.c>.
|
---|
178 |
|
---|
179 | =head2 Disabling the default stream
|
---|
180 |
|
---|
181 | As discussed above in L</THE DEFAULT STREAM> we will follow the recommendation
|
---|
182 | to disable the default stream for our multi-stream client. To do this we call
|
---|
183 | the L<SSL_set_default_stream_mode(3)> function and pass in our connection B<SSL>
|
---|
184 | object and the value B<SSL_DEFAULT_STREAM_MODE_NONE>.
|
---|
185 |
|
---|
186 | /*
|
---|
187 | * We will use multiple streams so we will disable the default stream mode.
|
---|
188 | * This is not a requirement for using multiple streams but is recommended.
|
---|
189 | */
|
---|
190 | if (!SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE)) {
|
---|
191 | printf("Failed to disable the default stream mode\n");
|
---|
192 | goto end;
|
---|
193 | }
|
---|
194 |
|
---|
195 | =head2 Creating the request streams
|
---|
196 |
|
---|
197 | For the purposes of this example we will create two different streams to send
|
---|
198 | two different HTTP requests to the server. For the purposes of demonstration the
|
---|
199 | first of these will be a bi-directional stream and the second one will be a
|
---|
200 | uni-directional one:
|
---|
201 |
|
---|
202 | /*
|
---|
203 | * We create two new client initiated streams. The first will be
|
---|
204 | * bi-directional, and the second will be uni-directional.
|
---|
205 | */
|
---|
206 | stream1 = SSL_new_stream(ssl, 0);
|
---|
207 | stream2 = SSL_new_stream(ssl, SSL_STREAM_FLAG_UNI);
|
---|
208 | if (stream1 == NULL || stream2 == NULL) {
|
---|
209 | printf("Failed to create streams\n");
|
---|
210 | goto end;
|
---|
211 | }
|
---|
212 |
|
---|
213 | =head2 Writing data to the streams
|
---|
214 |
|
---|
215 | Once the streams are successfully created we can start writing data to them. In
|
---|
216 | this example we will be sending a different HTTP request on each stream. To
|
---|
217 | avoid repeating too much code we write a simple helper function to send an HTTP
|
---|
218 | request to a stream:
|
---|
219 |
|
---|
220 | int write_a_request(SSL *stream, const char *request_start,
|
---|
221 | const char *hostname)
|
---|
222 | {
|
---|
223 | const char *request_end = "\r\n\r\n";
|
---|
224 | size_t written;
|
---|
225 |
|
---|
226 | if (!SSL_write_ex(stream, request_start, strlen(request_start), &written))
|
---|
227 | return 0;
|
---|
228 | if (!SSL_write_ex(stream, hostname, strlen(hostname), &written))
|
---|
229 | return 0;
|
---|
230 | if (!SSL_write_ex(stream, request_end, strlen(request_end), &written))
|
---|
231 | return 0;
|
---|
232 |
|
---|
233 | return 1;
|
---|
234 | }
|
---|
235 |
|
---|
236 | We assume the strings B<request1_start> and B<request2_start> hold the
|
---|
237 | appropriate HTTP requests. We can then call our helper function above to send
|
---|
238 | the requests on the two streams. For the sake of simplicity this example does
|
---|
239 | this sequentially, writing to B<stream1> first and, when this is successful,
|
---|
240 | writing to B<stream2> second. Remember that our client is blocking so these
|
---|
241 | calls will only return once they have been successfully completed. A real
|
---|
242 | application would not need to do these writes sequentially or in any particular
|
---|
243 | order. For example we could start two threads (one for each stream) and write
|
---|
244 | the requests to each stream simultaneously.
|
---|
245 |
|
---|
246 | /* Write an HTTP GET request on each of our streams to the peer */
|
---|
247 | if (!write_a_request(stream1, request1_start, hostname)) {
|
---|
248 | printf("Failed to write HTTP request on stream 1\n");
|
---|
249 | goto end;
|
---|
250 | }
|
---|
251 |
|
---|
252 | if (!write_a_request(stream2, request2_start, hostname)) {
|
---|
253 | printf("Failed to write HTTP request on stream 2\n");
|
---|
254 | goto end;
|
---|
255 | }
|
---|
256 |
|
---|
257 | =head2 Reading data from a stream
|
---|
258 |
|
---|
259 | In this example B<stream1> is a bi-directional stream so, once we have sent the
|
---|
260 | request on it, we can attempt to read the response from the server back. Here
|
---|
261 | we just repeatedly call L<SSL_read_ex(3)> until that function fails (indicating
|
---|
262 | either that there has been a problem, or that the peer has signalled the stream
|
---|
263 | as concluded).
|
---|
264 |
|
---|
265 | printf("Stream 1 data:\n");
|
---|
266 | /*
|
---|
267 | * Get up to sizeof(buf) bytes of the response from stream 1 (which is a
|
---|
268 | * bidirectional stream). We keep reading until the server closes the
|
---|
269 | * connection.
|
---|
270 | */
|
---|
271 | while (SSL_read_ex(stream1, buf, sizeof(buf), &readbytes)) {
|
---|
272 | /*
|
---|
273 | * OpenSSL does not guarantee that the returned data is a string or
|
---|
274 | * that it is NUL terminated so we use fwrite() to write the exact
|
---|
275 | * number of bytes that we read. The data could be non-printable or
|
---|
276 | * have NUL characters in the middle of it. For this simple example
|
---|
277 | * we're going to print it to stdout anyway.
|
---|
278 | */
|
---|
279 | fwrite(buf, 1, readbytes, stdout);
|
---|
280 | }
|
---|
281 | /* In case the response didn't finish with a newline we add one now */
|
---|
282 | printf("\n");
|
---|
283 |
|
---|
284 | In a blocking application like this one calls to L<SSL_read_ex(3)> will either
|
---|
285 | succeed immediately returning data that is already available, or they will block
|
---|
286 | waiting for more data to become available and return it when it is, or they will
|
---|
287 | fail with a 0 response code.
|
---|
288 |
|
---|
289 | Once we exit the while loop above we know that the last call to
|
---|
290 | L<SSL_read_ex(3)> gave a 0 response code so we call the L<SSL_get_error(3)>
|
---|
291 | function to find out more details. Since this is a blocking application this
|
---|
292 | will either return B<SSL_ERROR_SYSCALL> or B<SSL_ERROR_SSL> indicating a
|
---|
293 | fundamental problem, or it will return B<SSL_ERROR_ZERO_RETURN> indicating that
|
---|
294 | the stream is concluded and there will be no more data available to read from
|
---|
295 | it. Care must be taken to distinguish between an error at the stream level (i.e.
|
---|
296 | a stream reset) and an error at the connection level (i.e. a connection closed).
|
---|
297 | The L<SSL_get_stream_read_state(3)> function can be used to distinguish between
|
---|
298 | these different cases.
|
---|
299 |
|
---|
300 | /*
|
---|
301 | * Check whether we finished the while loop above normally or as the
|
---|
302 | * result of an error. The 0 argument to SSL_get_error() is the return
|
---|
303 | * code we received from the SSL_read_ex() call. It must be 0 in order
|
---|
304 | * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. In
|
---|
305 | * QUIC terms this means that the peer has sent FIN on the stream to
|
---|
306 | * indicate that no further data will be sent.
|
---|
307 | */
|
---|
308 | switch (SSL_get_error(stream1, 0)) {
|
---|
309 | case SSL_ERROR_ZERO_RETURN:
|
---|
310 | /* Normal completion of the stream */
|
---|
311 | break;
|
---|
312 |
|
---|
313 | case SSL_ERROR_SSL:
|
---|
314 | /*
|
---|
315 | * Some stream fatal error occurred. This could be because of a stream
|
---|
316 | * reset - or some failure occurred on the underlying connection.
|
---|
317 | */
|
---|
318 | switch (SSL_get_stream_read_state(stream1)) {
|
---|
319 | case SSL_STREAM_STATE_RESET_REMOTE:
|
---|
320 | printf("Stream reset occurred\n");
|
---|
321 | /* The stream has been reset but the connection is still healthy. */
|
---|
322 | break;
|
---|
323 |
|
---|
324 | case SSL_STREAM_STATE_CONN_CLOSED:
|
---|
325 | printf("Connection closed\n");
|
---|
326 | /* Connection is already closed. Skip SSL_shutdown() */
|
---|
327 | goto end;
|
---|
328 |
|
---|
329 | default:
|
---|
330 | printf("Unknown stream failure\n");
|
---|
331 | break;
|
---|
332 | }
|
---|
333 | break;
|
---|
334 |
|
---|
335 | default:
|
---|
336 | /* Some other unexpected error occurred */
|
---|
337 | printf ("Failed reading remaining data\n");
|
---|
338 | break;
|
---|
339 | }
|
---|
340 |
|
---|
341 | =head2 Accepting an incoming stream
|
---|
342 |
|
---|
343 | Our B<stream2> object that we created above was a uni-directional stream so it
|
---|
344 | cannot be used to receive data from the server. In this hypothetical example
|
---|
345 | we assume that the server initiates a new stream to send us back the data that
|
---|
346 | we requested. To do that we call L<SSL_accept_stream(3)>. Since this is a
|
---|
347 | blocking application this will wait indefinitely until the new stream has
|
---|
348 | arrived and is available for us to accept. In the event of an error it will
|
---|
349 | return B<NULL>.
|
---|
350 |
|
---|
351 | /*
|
---|
352 | * In our hypothetical HTTP/1.0 over QUIC protocol that we are using we
|
---|
353 | * assume that the server will respond with a server initiated stream
|
---|
354 | * containing the data requested in our uni-directional stream. This doesn't
|
---|
355 | * really make sense to do in a real protocol, but its just for
|
---|
356 | * demonstration purposes.
|
---|
357 | *
|
---|
358 | * We're using blocking mode so this will block until a stream becomes
|
---|
359 | * available. We could override this behaviour if we wanted to by setting
|
---|
360 | * the SSL_ACCEPT_STREAM_NO_BLOCK flag in the second argument below.
|
---|
361 | */
|
---|
362 | stream3 = SSL_accept_stream(ssl, 0);
|
---|
363 | if (stream3 == NULL) {
|
---|
364 | printf("Failed to accept a new stream\n");
|
---|
365 | goto end;
|
---|
366 | }
|
---|
367 |
|
---|
368 | We can now read data from the stream in the same way that we did for B<stream1>
|
---|
369 | above. We won't repeat that here.
|
---|
370 |
|
---|
371 | =head2 Cleaning up the streams
|
---|
372 |
|
---|
373 | Once we have finished using our streams we can simply free them by calling
|
---|
374 | L<SSL_free(3)>. Optionally we could call L<SSL_stream_conclude(3)> on them if
|
---|
375 | we want to indicate to the peer that we won't be sending them any more data, but
|
---|
376 | we don't do that in this example because we assume that the HTTP application
|
---|
377 | protocol supplies sufficient information for the peer to know when we have
|
---|
378 | finished sending request data.
|
---|
379 |
|
---|
380 | We should not call L<SSL_shutdown(3)> or L<SSL_shutdown_ex(3)> on the stream
|
---|
381 | objects since those calls should not be used for streams.
|
---|
382 |
|
---|
383 | SSL_free(stream1);
|
---|
384 | SSL_free(stream2);
|
---|
385 | SSL_free(stream3);
|
---|
386 |
|
---|
387 | =head1 SEE ALSO
|
---|
388 |
|
---|
389 | L<ossl-guide-introduction(7)>, L<ossl-guide-libraries-introduction(7)>,
|
---|
390 | L<ossl-guide-libssl-introduction(7)> L<ossl-guide-quic-introduction(7)>,
|
---|
391 | L<ossl-guide-quic-client-block(7)>
|
---|
392 |
|
---|
393 | =head1 COPYRIGHT
|
---|
394 |
|
---|
395 | Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
|
---|
396 |
|
---|
397 | Licensed under the Apache License 2.0 (the "License"). You may not use
|
---|
398 | this file except in compliance with the License. You can obtain a copy
|
---|
399 | in the file LICENSE in the source distribution or at
|
---|
400 | L<https://www.openssl.org/source/license.html>.
|
---|
401 |
|
---|
402 | =cut
|
---|