]>
Commit | Line | Data |
---|---|---|
584140fa MC |
1 | /* |
2 | * Copyright 2023 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 | ||
10 | /* | |
11 | * NB: Changes to this file should also be reflected in | |
12 | * doc/man7/ossl-guide-quic-multi-stream.pod | |
13 | */ | |
14 | ||
15 | #include <string.h> | |
16 | ||
17 | /* Include the appropriate header file for SOCK_DGRAM */ | |
18 | #ifdef _WIN32 /* Windows */ | |
19 | # include <winsock2.h> | |
20 | #else /* Linux/Unix */ | |
21 | # include <sys/socket.h> | |
22 | #endif | |
23 | ||
24 | #include <openssl/bio.h> | |
25 | #include <openssl/ssl.h> | |
26 | #include <openssl/err.h> | |
27 | ||
28 | /* Helper function to create a BIO connected to the server */ | |
29 | static BIO *create_socket_bio(const char *hostname, const char *port, | |
5091aadc | 30 | int family, BIO_ADDR **peer_addr) |
584140fa MC |
31 | { |
32 | int sock = -1; | |
33 | BIO_ADDRINFO *res; | |
34 | const BIO_ADDRINFO *ai = NULL; | |
35 | BIO *bio; | |
36 | ||
37 | /* | |
38 | * Lookup IP address info for the server. | |
39 | */ | |
5091aadc | 40 | if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0, |
584140fa MC |
41 | &res)) |
42 | return NULL; | |
43 | ||
44 | /* | |
45 | * Loop through all the possible addresses for the server and find one | |
46 | * we can connect to. | |
47 | */ | |
48 | for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { | |
49 | /* | |
59d8a338 | 50 | * Create a UDP socket. We could equally use non-OpenSSL calls such |
584140fa MC |
51 | * as "socket" here for this and the subsequent connect and close |
52 | * functions. But for portability reasons and also so that we get | |
53 | * errors on the OpenSSL stack in the event of a failure we use | |
54 | * OpenSSL's versions of these functions. | |
55 | */ | |
56 | sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0); | |
57 | if (sock == -1) | |
58 | continue; | |
59 | ||
60 | /* Connect the socket to the server's address */ | |
61 | if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) { | |
62 | BIO_closesocket(sock); | |
63 | sock = -1; | |
64 | continue; | |
65 | } | |
66 | ||
67 | /* Set to nonblocking mode */ | |
68 | if (!BIO_socket_nbio(sock, 1)) { | |
cdedecd5 | 69 | BIO_closesocket(sock); |
584140fa MC |
70 | sock = -1; |
71 | continue; | |
72 | } | |
73 | ||
74 | break; | |
75 | } | |
76 | ||
77 | if (sock != -1) { | |
78 | *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); | |
79 | if (*peer_addr == NULL) { | |
80 | BIO_closesocket(sock); | |
81 | return NULL; | |
82 | } | |
83 | } | |
84 | ||
584140fa MC |
85 | /* Free the address information resources we allocated earlier */ |
86 | BIO_ADDRINFO_free(res); | |
87 | ||
88 | /* If sock is -1 then we've been unable to connect to the server */ | |
89 | if (sock == -1) | |
90 | return NULL; | |
91 | ||
11b7d46f | 92 | /* Create a BIO to wrap the socket */ |
584140fa | 93 | bio = BIO_new(BIO_s_datagram()); |
11b7d46f | 94 | if (bio == NULL) { |
584140fa | 95 | BIO_closesocket(sock); |
11b7d46f MC |
96 | return NULL; |
97 | } | |
59d8a338 | 98 | |
584140fa MC |
99 | /* |
100 | * Associate the newly created BIO with the underlying socket. By | |
101 | * passing BIO_CLOSE here the socket will be automatically closed when | |
102 | * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which | |
103 | * case you must close the socket explicitly when it is no longer | |
104 | * needed. | |
105 | */ | |
106 | BIO_set_fd(bio, sock, BIO_CLOSE); | |
107 | ||
108 | return bio; | |
109 | } | |
110 | ||
420037c8 MC |
111 | int write_a_request(SSL *stream, const char *request_start, |
112 | const char *hostname) | |
113 | { | |
114 | const char *request_end = "\r\n\r\n"; | |
115 | size_t written; | |
116 | ||
117 | if (!SSL_write_ex(stream, request_start, strlen(request_start), | |
118 | &written)) | |
119 | return 0; | |
120 | if (!SSL_write_ex(stream, hostname, strlen(hostname), &written)) | |
121 | return 0; | |
122 | if (!SSL_write_ex(stream, request_end, strlen(request_end), &written)) | |
123 | return 0; | |
124 | ||
125 | return 1; | |
126 | } | |
584140fa MC |
127 | |
128 | /* | |
129 | * Simple application to send basic HTTP/1.0 requests to a server and print the | |
130 | * response on the screen. Note that HTTP/1.0 over QUIC is not a real protocol | |
131 | * and will not be supported by real world servers. This is for demonstration | |
132 | * purposes only. | |
133 | */ | |
420037c8 | 134 | int main(int argc, char *argv[]) |
584140fa MC |
135 | { |
136 | SSL_CTX *ctx = NULL; | |
137 | SSL *ssl = NULL; | |
138 | SSL *stream1 = NULL, *stream2 = NULL, *stream3 = NULL; | |
139 | BIO *bio = NULL; | |
140 | int res = EXIT_FAILURE; | |
141 | int ret; | |
142 | unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' }; | |
420037c8 MC |
143 | const char *request1_start = |
144 | "GET /request1.html HTTP/1.0\r\nConnection: close\r\nHost: "; | |
145 | const char *request2_start = | |
146 | "GET /request2.html HTTP/1.0\r\nConnection: close\r\nHost: "; | |
147 | size_t readbytes; | |
584140fa MC |
148 | char buf[160]; |
149 | BIO_ADDR *peer_addr = NULL; | |
420037c8 | 150 | char *hostname, *port; |
5091aadc NH |
151 | int argnext = 1; |
152 | int ipv6 = 0; | |
420037c8 | 153 | |
5091aadc NH |
154 | if (argc < 3) { |
155 | printf("Usage: quic-client-non-block [-6] hostname port\n"); | |
420037c8 MC |
156 | goto end; |
157 | } | |
158 | ||
5091aadc NH |
159 | if (!strcmp(argv[argnext], "-6")) { |
160 | if (argc < 4) { | |
161 | printf("Usage: quic-client-non-block [-6] hostname port\n"); | |
162 | goto end; | |
163 | } | |
164 | ipv6 = 1; | |
165 | argnext++; | |
166 | } | |
167 | hostname = argv[argnext++]; | |
168 | port = argv[argnext]; | |
584140fa MC |
169 | |
170 | /* | |
171 | * Create an SSL_CTX which we can use to create SSL objects from. We | |
172 | * want an SSL_CTX for creating clients so we use | |
173 | * OSSL_QUIC_client_method() here. | |
174 | */ | |
175 | ctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
176 | if (ctx == NULL) { | |
177 | printf("Failed to create the SSL_CTX\n"); | |
178 | goto end; | |
179 | } | |
180 | ||
181 | /* | |
182 | * Configure the client to abort the handshake if certificate | |
183 | * verification fails. Virtually all clients should do this unless you | |
184 | * really know what you are doing. | |
185 | */ | |
186 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
187 | ||
188 | /* Use the default trusted certificate store */ | |
189 | if (!SSL_CTX_set_default_verify_paths(ctx)) { | |
190 | printf("Failed to set the default trusted certificate store\n"); | |
191 | goto end; | |
192 | } | |
193 | ||
194 | /* Create an SSL object to represent the TLS connection */ | |
195 | ssl = SSL_new(ctx); | |
196 | if (ssl == NULL) { | |
197 | printf("Failed to create the SSL object\n"); | |
198 | goto end; | |
199 | } | |
200 | ||
201 | /* | |
202 | * We will use multiple streams so we will disable the default stream mode. | |
203 | * This is not a requirement for using multiple streams but is recommended. | |
204 | */ | |
205 | if (!SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE)) { | |
206 | printf("Failed to disable the default stream mode\n"); | |
207 | goto end; | |
208 | } | |
209 | ||
210 | /* | |
211 | * Create the underlying transport socket/BIO and associate it with the | |
212 | * connection. | |
213 | */ | |
5091aadc | 214 | bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET, &peer_addr); |
584140fa MC |
215 | if (bio == NULL) { |
216 | printf("Failed to crete the BIO\n"); | |
217 | goto end; | |
218 | } | |
219 | SSL_set_bio(ssl, bio, bio); | |
220 | ||
221 | /* | |
222 | * Tell the server during the handshake which hostname we are attempting | |
223 | * to connect to in case the server supports multiple hosts. | |
224 | */ | |
420037c8 | 225 | if (!SSL_set_tlsext_host_name(ssl, hostname)) { |
584140fa MC |
226 | printf("Failed to set the SNI hostname\n"); |
227 | goto end; | |
228 | } | |
229 | ||
230 | /* | |
231 | * Ensure we check during certificate verification that the server has | |
232 | * supplied a certificate for the hostname that we were expecting. | |
233 | * Virtually all clients should do this unless you really know what you | |
234 | * are doing. | |
235 | */ | |
420037c8 | 236 | if (!SSL_set1_host(ssl, hostname)) { |
584140fa MC |
237 | printf("Failed to set the certificate verification hostname"); |
238 | goto end; | |
239 | } | |
240 | ||
241 | /* SSL_set_alpn_protos returns 0 for success! */ | |
242 | if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)) != 0) { | |
243 | printf("Failed to set the ALPN for the connection\n"); | |
244 | goto end; | |
245 | } | |
246 | ||
8d74a131 | 247 | /* Set the IP address of the remote peer */ |
dac42bdc | 248 | if (!SSL_set1_initial_peer_addr(ssl, peer_addr)) { |
584140fa MC |
249 | printf("Failed to set the initial peer address\n"); |
250 | goto end; | |
251 | } | |
252 | ||
59d8a338 JM |
253 | /* Do the handshake with the server */ |
254 | if (SSL_connect(ssl) < 1) { | |
255 | printf("Failed to connect to the server\n"); | |
584140fa MC |
256 | /* |
257 | * If the failure is due to a verification error we can get more | |
258 | * information about it from SSL_get_verify_result(). | |
259 | */ | |
260 | if (SSL_get_verify_result(ssl) != X509_V_OK) | |
261 | printf("Verify error: %s\n", | |
262 | X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
263 | goto end; | |
264 | } | |
265 | ||
266 | /* | |
267 | * We create two new client initiated streams. The first will be | |
268 | * bi-directional, and the second will be uni-directional. | |
269 | */ | |
270 | stream1 = SSL_new_stream(ssl, 0); | |
271 | stream2 = SSL_new_stream(ssl, SSL_STREAM_FLAG_UNI); | |
272 | if (stream1 == NULL || stream2 == NULL) { | |
273 | printf("Failed to create streams\n"); | |
274 | goto end; | |
275 | } | |
276 | ||
277 | /* Write an HTTP GET request on each of our streams to the peer */ | |
420037c8 | 278 | if (!write_a_request(stream1, request1_start, hostname)) { |
584140fa MC |
279 | printf("Failed to write HTTP request on stream 1\n"); |
280 | goto end; | |
281 | } | |
282 | ||
420037c8 | 283 | if (!write_a_request(stream2, request2_start, hostname)) { |
584140fa MC |
284 | printf("Failed to write HTTP request on stream 2\n"); |
285 | goto end; | |
286 | } | |
287 | ||
288 | /* | |
289 | * In this demo we read all the data from one stream before reading all the | |
290 | * data from the next stream for simplicity. In practice there is no need to | |
291 | * do this. We can interleave IO on the different streams if we wish, or | |
292 | * manage the streams entirely separately on different threads. | |
293 | */ | |
294 | ||
295 | printf("Stream 1 data:\n"); | |
296 | /* | |
297 | * Get up to sizeof(buf) bytes of the response from stream 1 (which is a | |
298 | * bidirectional stream). We keep reading until the server closes the | |
299 | * connection. | |
300 | */ | |
301 | while (SSL_read_ex(stream1, buf, sizeof(buf), &readbytes)) { | |
302 | /* | |
303 | * OpenSSL does not guarantee that the returned data is a string or | |
304 | * that it is NUL terminated so we use fwrite() to write the exact | |
305 | * number of bytes that we read. The data could be non-printable or | |
306 | * have NUL characters in the middle of it. For this simple example | |
307 | * we're going to print it to stdout anyway. | |
308 | */ | |
309 | fwrite(buf, 1, readbytes, stdout); | |
310 | } | |
311 | /* In case the response didn't finish with a newline we add one now */ | |
312 | printf("\n"); | |
313 | ||
314 | /* | |
315 | * Check whether we finished the while loop above normally or as the | |
316 | * result of an error. The 0 argument to SSL_get_error() is the return | |
317 | * code we received from the SSL_read_ex() call. It must be 0 in order | |
318 | * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. In | |
319 | * QUIC terms this means that the peer has sent FIN on the stream to | |
320 | * indicate that no further data will be sent. | |
321 | */ | |
02e36ed3 MC |
322 | switch (SSL_get_error(stream1, 0)) { |
323 | case SSL_ERROR_ZERO_RETURN: | |
324 | /* Normal completion of the stream */ | |
325 | break; | |
326 | ||
327 | case SSL_ERROR_SSL: | |
584140fa | 328 | /* |
02e36ed3 MC |
329 | * Some stream fatal error occurred. This could be because of a stream |
330 | * reset - or some failure occurred on the underlying connection. | |
584140fa | 331 | */ |
02e36ed3 MC |
332 | switch (SSL_get_stream_read_state(stream1)) { |
333 | case SSL_STREAM_STATE_RESET_REMOTE: | |
334 | printf("Stream reset occurred\n"); | |
335 | /* The stream has been reset but the connection is still healthy. */ | |
336 | break; | |
337 | ||
338 | case SSL_STREAM_STATE_CONN_CLOSED: | |
339 | printf("Connection closed\n"); | |
340 | /* Connection is already closed. Skip SSL_shutdown() */ | |
341 | goto end; | |
342 | ||
343 | default: | |
344 | printf("Unknown stream failure\n"); | |
345 | break; | |
346 | } | |
347 | break; | |
348 | ||
349 | default: | |
350 | /* Some other unexpected error occurred */ | |
351 | printf ("Failed reading remaining data\n"); | |
352 | break; | |
584140fa MC |
353 | } |
354 | ||
355 | /* | |
356 | * In our hypothetical HTTP/1.0 over QUIC protocol that we are using we | |
357 | * assume that the server will respond with a server initiated stream | |
358 | * containing the data requested in our uni-directional stream. This doesn't | |
359 | * really make sense to do in a real protocol, but its just for | |
360 | * demonstration purposes. | |
361 | * | |
362 | * We're using blocking mode so this will block until a stream becomes | |
363 | * available. We could override this behaviour if we wanted to by setting | |
364 | * the SSL_ACCEPT_STREAM_NO_BLOCK flag in the second argument below. | |
365 | */ | |
366 | stream3 = SSL_accept_stream(ssl, 0); | |
367 | if (stream3 == NULL) { | |
368 | printf("Failed to accept a new stream\n"); | |
369 | goto end; | |
370 | } | |
371 | ||
372 | printf("Stream 3 data:\n"); | |
373 | /* | |
374 | * Read the data from stream 3 like we did for stream 1 above. Note that | |
375 | * stream 2 was uni-directional so there is no data to be read from that | |
376 | * one. | |
377 | */ | |
378 | while (SSL_read_ex(stream3, buf, sizeof(buf), &readbytes)) | |
379 | fwrite(buf, 1, readbytes, stdout); | |
380 | printf("\n"); | |
381 | ||
382 | /* Check for errors on the stream */ | |
02e36ed3 MC |
383 | switch (SSL_get_error(stream3, 0)) { |
384 | case SSL_ERROR_ZERO_RETURN: | |
385 | /* Normal completion of the stream */ | |
386 | break; | |
387 | ||
388 | case SSL_ERROR_SSL: | |
389 | switch (SSL_get_stream_read_state(stream3)) { | |
390 | case SSL_STREAM_STATE_RESET_REMOTE: | |
391 | printf("Stream reset occurred\n"); | |
392 | break; | |
393 | ||
394 | case SSL_STREAM_STATE_CONN_CLOSED: | |
395 | printf("Connection closed\n"); | |
396 | goto end; | |
397 | ||
398 | default: | |
399 | printf("Unknown stream failure\n"); | |
400 | break; | |
401 | } | |
402 | break; | |
403 | ||
404 | default: | |
405 | printf ("Failed reading remaining data\n"); | |
406 | break; | |
584140fa MC |
407 | } |
408 | ||
409 | /* | |
410 | * Repeatedly call SSL_shutdown() until the connection is fully | |
411 | * closed. | |
412 | */ | |
413 | do { | |
414 | ret = SSL_shutdown(ssl); | |
415 | if (ret < 0) { | |
416 | printf("Error shutting down: %d\n", ret); | |
417 | goto end; | |
418 | } | |
419 | } while (ret != 1); | |
420 | ||
421 | /* Success! */ | |
422 | res = EXIT_SUCCESS; | |
423 | end: | |
424 | /* | |
425 | * If something bad happened then we will dump the contents of the | |
426 | * OpenSSL error stack to stderr. There might be some useful diagnostic | |
427 | * information there. | |
428 | */ | |
429 | if (res == EXIT_FAILURE) | |
430 | ERR_print_errors_fp(stderr); | |
431 | ||
432 | /* | |
433 | * Free the resources we allocated. We do not free the BIO object here | |
434 | * because ownership of it was immediately transferred to the SSL object | |
435 | * via SSL_set_bio(). The BIO will be freed when we free the SSL object. | |
436 | */ | |
437 | SSL_free(ssl); | |
438 | SSL_free(stream1); | |
439 | SSL_free(stream2); | |
440 | SSL_free(stream3); | |
441 | SSL_CTX_free(ctx); | |
442 | BIO_ADDR_free(peer_addr); | |
443 | return res; | |
444 | } |