1 /*
2 * Copyright 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
10 /**
11 * @file quic-hq-interop.c
12 * @brief QUIC client interoperability demo using OpenSSL.
13 *
14 * This file contains the implementation of a QUIC client that demonstrates
15 * interoperability with hq-interop servers. It handles connection setup,
16 * session caching, key logging, and sending HTTP GET requests over QUIC.
17 *
18 * The file includes functions for setting up SSL/TLS contexts, managing session
19 * caches, and handling I/O failures. It supports both IPv4 and IPv6 connections
20 * and uses non-blocking mode for QUIC operations.
21 *
22 * The client sends HTTP/1.0 GET requests for specified files and saves the
23 * responses to disk. It demonstrates OpenSSL's QUIC API, including ALPN protocol
24 * settings, peer address management, and SSL handshake and shutdown processes.
25 *
26 * @note This client is intended for demonstration purposes and may require
27 * additional error handling and robustness improvements for production use.
28 *
29 * USAGE
30 * quic-hq-interop <host> <port> <reqfile>
31 * host - The hostname of the server to contact
32 * port - The port that the server is listening on
33 * reqfile - a text file containing a space separated list of paths to fetch
34 * via http 1.0 GET requests
35 *
36 * ENVIRONMENT VARIABLES
37 * SSLKEYLOGFILE - set to a file path to record keylog exchange with server
38 * SSL_SESSION_FILE - set to a file path to record ssl sessions and restore
39 * said sessions on next invocation
40 * SSL_CERT_FILE - The ca file to use when validating a server
41 */
42 #include <string.h>
43
44 /* Include the appropriate header file for SOCK_DGRAM */
45 #ifdef _WIN32 /* Windows */
46 # include <winsock2.h>
47 #else /* Linux/Unix */
48 # include <sys/socket.h>
49 # include <sys/select.h>
50 #endif
51
52 #include <openssl/bio.h>
53 #include <openssl/ssl.h>
54 #include <openssl/err.h>
55
56 static int handle_io_failure(SSL *ssl, int res);
57 static int set_keylog_file(SSL_CTX *ctx, const char *keylog_file);
58
59 #define REQ_STRING_SZ 1024
60
61 /**
62 * @brief A static pointer to a BIO object representing the session's BIO.
63 *
64 * This variable holds a reference to a BIO object used for network
65 * communication during the session. It is initialized to NULL and should
66 * be assigned a valid BIO object before use. The BIO object pointed to by
67 * this variable may be used throughout the session for reading and writing
68 * data.
69 *
70 * @note This variable is static, meaning it is only accessible within the
71 * file in which it is declared.
72 */
73 static BIO *session_bio = NULL;
74
75 /**
76 * @brief A static pointer to a BIO object used for logging key material.
77 *
78 * This variable holds a reference to a BIO object that is used to log
79 * cryptographic key material for debugging purposes. It is initialized to
80 * NULL and should be assigned a valid BIO object before use.
81 *
82 * @note This variable is static, meaning it is only accessible within the
83 * file in which it is declared.
84 */
85 static BIO *bio_keylog = NULL;
86
87 /**
88 * @brief Creates a BIO object for a UDP socket connection to a server.
89 *
90 * This function attempts to create a UDP socket and connect it to the server
91 * specified by the given hostname and port. The socket is wrapped in a BIO
92 * object, which allows for network communication via OpenSSL's BIO API.
93 * The function also returns the address of the connected peer.
94 *
95 * @param hostname The hostname of the server to connect to.
96 * @param port The port number of the server to connect to.
97 * @param family The desired address family (e.g., AF_INET for IPv4,
98 * AF_INET6 for IPv6).
99 * @param peer_addr A pointer to a BIO_ADDR pointer that will hold the address
100 * of the connected peer on success. The caller is responsible
101 * for freeing this memory using BIO_ADDR_free().
102 * @return A pointer to a BIO object on success, or NULL on failure.
103 * The returned BIO object will be associated with the connected socket.
104 * If the BIO object is successfully created, it will take ownership of
105 * the socket and automatically close it when the BIO is freed.
106 *
107 * @note The function uses OpenSSL's socket-related functions (e.g., BIO_socket,
108 * BIO_connect) or portability and to integrate with OpenSSL's error
109 * handling mechanisms.
110 * @note If no valid connection is established, the function returns NULL and
111 * ensures that any resources allocated during the process are properly
112 * freed.
113 */
create_socket_bio(const char * hostname,const char * port,int family,BIO_ADDR ** peer_addr)114 static BIO *create_socket_bio(const char *hostname, const char *port,
115 int family, BIO_ADDR **peer_addr)
116 {
117 int sock = -1;
118 BIO_ADDRINFO *res;
119 const BIO_ADDRINFO *ai = NULL;
120 BIO *bio;
121
122 /*
123 * Lookup IP address info for the server.
124 */
125 if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0,
126 &res))
127 return NULL;
128
129 /*
130 * Loop through all the possible addresses for the server and find one
131 * we can connect to.
132 */
133 for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) {
134 /*
135 * Create a UDP socket. We could equally use non-OpenSSL calls such
136 * as "socket" here for this and the subsequent connect and close
137 * functions. But for portability reasons and also so that we get
138 * errors on the OpenSSL stack in the event of a failure we use
139 * OpenSSL's versions of these functions.
140 */
141 sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0);
142 if (sock == -1)
143 continue;
144
145 /* Connect the socket to the server's address */
146 if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) {
147 BIO_closesocket(sock);
148 sock = -1;
149 continue;
150 }
151
152 /*
153 * Set to nonblocking mode
154 * Note: This function returns a range of errors
155 * <= 0 if something goes wrong, so catch them all here
156 */
157 if (BIO_socket_nbio(sock, 1) <= 0) {
158 BIO_closesocket(sock);
159 sock = -1;
160 continue;
161 }
162
163 break;
164 }
165
166 if (sock != -1) {
167 *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai));
168 if (*peer_addr == NULL) {
169 BIO_closesocket(sock);
170 return NULL;
171 }
172 }
173
174 /* Free the address information resources we allocated earlier */
175 BIO_ADDRINFO_free(res);
176
177 /* If sock is -1 then we've been unable to connect to the server */
178 if (sock == -1)
179 return NULL;
180
181 /* Create a BIO to wrap the socket */
182 bio = BIO_new(BIO_s_datagram());
183 if (bio == NULL) {
184 BIO_closesocket(sock);
185 return NULL;
186 }
187
188 /*
189 * Associate the newly created BIO with the underlying socket. By
190 * passing BIO_CLOSE here the socket will be automatically closed when
191 * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which
192 * case you must close the socket explicitly when it is no longer
193 * needed.
194 */
195 if (BIO_set_fd(bio, sock, BIO_CLOSE) <= 0) {
196 BIO_closesocket(sock);
197 BIO_free(bio);
198 return NULL;
199 }
200
201 return bio;
202 }
203
204 /**
205 * @brief Waits for activity on the SSL socket, either for reading or writing.
206 *
207 * This function monitors the underlying file descriptor of the given SSL
208 * connection to determine when it is ready for reading or writing, or both.
209 * It uses the select function to wait until the socket is either readable
210 * or writable, depending on what the SSL connection requires.
211 *
212 * @param ssl A pointer to the SSL object representing the connection.
213 *
214 * @note This function blocks until there is activity on the socket or
215 * until the timeout specified by OpenSSL is reached. In a real application,
216 * you might want to perform other tasks while waiting, such as updating a
217 * GUI or handling other connections.
218 *
219 * @note This function uses select for simplicity and portability. Depending
220 * on your application's requirements, you might consider using other
221 * mechanisms like poll or epoll for handling multiple file descriptors.
222 */
wait_for_activity(SSL * ssl)223 static void wait_for_activity(SSL *ssl)
224 {
225 fd_set wfds, rfds;
226 int width, sock, isinfinite;
227 struct timeval tv;
228 struct timeval *tvp = NULL;
229
230 /* Get hold of the underlying file descriptor for the socket */
231 sock = SSL_get_fd(ssl);
232
233 FD_ZERO(&wfds);
234 FD_ZERO(&rfds);
235
236 /*
237 * Find out if we would like to write to the socket, or read from it (or
238 * both)
239 */
240 if (SSL_net_write_desired(ssl))
241 FD_SET(sock, &wfds);
242 if (SSL_net_read_desired(ssl))
243 FD_SET(sock, &rfds);
244 width = sock + 1;
245
246 /*
247 * Find out when OpenSSL would next like to be called, regardless of
248 * whether the state of the underlying socket has changed or not.
249 */
250 if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite)
251 tvp = &tv;
252
253 /*
254 * Wait until the socket is writeable or readable. We use select here
255 * for the sake of simplicity and portability, but you could equally use
256 * poll/epoll or similar functions
257 *
258 * NOTE: For the purposes of this demonstration code this effectively
259 * makes this demo block until it has something more useful to do. In a
260 * real application you probably want to go and do other work here (e.g.
261 * update a GUI, or service other connections).
262 *
263 * Let's say for example that you want to update the progress counter on
264 * a GUI every 100ms. One way to do that would be to use the timeout in
265 * the last parameter to "select" below. If the tvp value is greater
266 * than 100ms then use 100ms instead. Then, when select returns, you
267 * check if it did so because of activity on the file descriptors or
268 * because of the timeout. If the 100ms GUI timeout has expired but the
269 * tvp timeout has not then go and update the GUI and then restart the
270 * "select" (with updated timeouts).
271 */
272
273 select(width, &rfds, &wfds, NULL, tvp);
274 }
275
276 /**
277 * @brief Handles I/O failures on an SSL connection based on the result code.
278 *
279 * This function processes the result of an SSL I/O operation and handles
280 * different types of errors that may occur during the operation. It takes
281 * appropriate actions such as retrying the operation, reporting errors, or
282 * returning specific status codes based on the error type.
283 *
284 * @param ssl A pointer to the SSL object representing the connection.
285 * @param res The result code from the SSL I/O operation.
286 * @return An integer indicating the outcome:
287 * - 1: Temporary failure, the operation should be retried.
288 * - 0: EOF, indicating the connection has been closed.
289 * - -1: A fatal error occurred or the connection has been reset.
290 *
291 * @note This function may block if a temporary failure occurs and
292 * wait_for_activity() is called.
293 *
294 * @note If the failure is due to an SSL verification error, additional
295 * information will be logged to stderr.
296 */
handle_io_failure(SSL * ssl,int res)297 static int handle_io_failure(SSL *ssl, int res)
298 {
299 switch (SSL_get_error(ssl, res)) {
300 case SSL_ERROR_WANT_READ:
301 case SSL_ERROR_WANT_WRITE:
302 /* Temporary failure. Wait until we can read/write and try again */
303 wait_for_activity(ssl);
304 return 1;
305
306 case SSL_ERROR_ZERO_RETURN:
307 /* EOF */
308 return 0;
309
310 case SSL_ERROR_SYSCALL:
311 return -1;
312
313 case SSL_ERROR_SSL:
314 /*
315 * Some stream fatal error occurred. This could be because of a
316 * stream reset - or some failure occurred on the underlying
317 * connection.
318 */
319 switch (SSL_get_stream_read_state(ssl)) {
320 case SSL_STREAM_STATE_RESET_REMOTE:
321 fprintf(stderr, "Stream reset occurred\n");
322 /*
323 * The stream has been reset but the connection is still
324 * healthy.
325 */
326 break;
327
328 case SSL_STREAM_STATE_CONN_CLOSED:
329 fprintf(stderr, "Connection closed\n");
330 /* Connection is already closed. */
331 break;
332
333 default:
334 fprintf(stderr, "Unknown stream failure\n");
335 break;
336 }
337 /*
338 * If the failure is due to a verification error we can get more
339 * information about it from SSL_get_verify_result().
340 */
341 if (SSL_get_verify_result(ssl) != X509_V_OK)
342 fprintf(stderr, "Verify error: %s\n",
343 X509_verify_cert_error_string(SSL_get_verify_result(ssl)));
344 return -1;
345
346 default:
347 return -1;
348 }
349 }
350
351 /**
352 * @brief Callback function to log key material during an SSL session.
353 *
354 * This function is invoked by OpenSSL when key material needs to be logged
355 * for debugging purposes. It writes the provided key log line to the
356 * `bio_keylog` BIO, ensuring thread-safe output by writing the entire line
357 * at once.
358 *
359 * @param ssl A pointer to the SSL object associated with the session.
360 * @param line The key log line to be written.
361 *
362 * @note If `bio_keylog` is NULL, an error message is printed to stderr, and
363 * the function returns without logging the key material.
364 */
keylog_callback(const SSL * ssl,const char * line)365 static void keylog_callback(const SSL *ssl, const char *line)
366 {
367 if (bio_keylog == NULL) {
368 fprintf(stderr, "Keylog callback is invoked without valid file!\n");
369 return;
370 }
371
372 /*
373 * There might be concurrent writers to the keylog file, so we must ensure
374 * that the given line is written at once.
375 */
376 BIO_printf(bio_keylog, "%s\n", line);
377 (void)BIO_flush(bio_keylog);
378 }
379
380 /**
381 * @brief Sets up the key logging file for an SSL context.
382 *
383 * This function configures a file to log SSL/TLS key material for the
384 * provided SSL context. If a keylog file is specified, it will be opened
385 * in append mode, allowing for concurrent writes and preserving existing
386 * logs. If no keylog file is provided, key logging is disabled.
387 *
388 * @param ctx A pointer to the SSL_CTX object where the keylog file is set.
389 * @param keylog_file The path to the keylog file. If NULL, key logging is
390 * disabled.
391 * @return 0 on success, or 1 if there was an error opening the keylog file.
392 *
393 * @note The function writes a header to the keylog file if it is empty and
394 * seekable. It also ensures that any previously opened keylog files are
395 * closed before opening a new one.
396 */
set_keylog_file(SSL_CTX * ctx,const char * keylog_file)397 static int set_keylog_file(SSL_CTX *ctx, const char *keylog_file)
398 {
399 /* Close any open files */
400 BIO_free_all(bio_keylog);
401 bio_keylog = NULL;
402
403 if (ctx == NULL || keylog_file == NULL) {
404 /* Keylogging is disabled, OK. */
405 return 0;
406 }
407
408 /*
409 * Append rather than write in order to allow concurrent modification.
410 * Furthermore, this preserves existing keylog files which is useful when
411 * the tool is run multiple times.
412 */
413 bio_keylog = BIO_new_file(keylog_file, "a");
414 if (bio_keylog == NULL) {
415 printf("Error writing keylog file %s\n", keylog_file);
416 return 1;
417 }
418
419 /* Write a header for seekable, empty files (this excludes pipes). */
420 if (BIO_tell(bio_keylog) == 0) {
421 BIO_puts(bio_keylog,
422 "# SSL/TLS secrets log file, generated by OpenSSL\n");
423 (void)BIO_flush(bio_keylog);
424 }
425 SSL_CTX_set_keylog_callback(ctx, keylog_callback);
426 return 0;
427 }
428
429 /**
430 * @brief A static integer indicating whether the session is cached.
431 *
432 * This variable is used to track the state of session caching. It is
433 * initialized to 0, meaning no session is cached. The value may be updated
434 * to indicate that a session has been successfully cached.
435 *
436 * @note This variable is static, meaning it is only accessible within the
437 * file in which it is declared.
438 */
439 static int session_cached = 0;
440
441 /**
442 * @brief Caches a new SSL session if one is not already cached.
443 *
444 * This function writes a new SSL session to the session BIO and caches it.
445 * It ensures that only one session is cached at a time by checking the
446 * `session_cached` flag. If a session is already cached, the function
447 * returns without caching the new session.
448 *
449 * @param ssl A pointer to the SSL object associated with the session.
450 * @param sess A pointer to the SSL_SESSION object to be cached.
451 * @return 1 if the session is successfully cached, 0 otherwise.
452 *
453 * @note This function only allows one session to be cached. Subsequent
454 * sessions will not be cached unless `session_cached` is reset.
455 */
cache_new_session(struct ssl_st * ssl,SSL_SESSION * sess)456 static int cache_new_session(struct ssl_st *ssl, SSL_SESSION *sess)
457 {
458
459 if (session_cached == 1)
460 return 0;
461
462 /* Just write the new session to our bio */
463 if (!PEM_write_bio_SSL_SESSION(session_bio, sess))
464 return 0;
465
466 (void)BIO_flush(session_bio);
467 /* only cache one session */
468 session_cached = 1;
469 return 1;
470 }
471
472 /**
473 * @brief Sets up the session cache for the SSL connection.
474 *
475 * This function configures session caching for the given SSL connection
476 * and context. It attempts to load a session from the specified cache file
477 * or creates a new one if the file does not exist. It also configures the
478 * session cache mode and disables stateless session tickets.
479 *
480 * @param ssl A pointer to the SSL object for the connection.
481 * @param ctx A pointer to the SSL_CTX object representing the context.
482 * @param filename The name of the file used to store the session cache.
483 * @return 1 on success, 0 on failure.
484 *
485 * @note If the cache file does not exist, a new file is created and the
486 * session cache is initialized. If a session is successfully loaded from
487 * the file, it is added to the context and set for the SSL connection.
488 * If an error occurs during setup, the session BIO is freed.
489 */
setup_session_cache(SSL * ssl,SSL_CTX * ctx,const char * filename)490 static int setup_session_cache(SSL *ssl, SSL_CTX *ctx, const char *filename)
491 {
492 SSL_SESSION *sess = NULL;
493 int rc = 0;
494 int new_cache = 0;
495
496 /*
497 * Because we cache sessions to a file in this client, we don't
498 * actualy need to internally store sessions, because we restore them
499 * from the file with SSL_set_session below, but we want to ensure
500 * that caching is enabled so that the session cache callbacks get called
501 * properly. The documentation is a bit unclear under what conditions
502 * the callback is made, so play it safe here, by enforcing enablement
503 */
504 if (!SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT |
505 SSL_SESS_CACHE_NO_INTERNAL_STORE |
506 SSL_SESS_CACHE_NO_AUTO_CLEAR))
507 return rc;
508
509 /* open our cache file */
510 session_bio = BIO_new_file(filename, "r+");
511 if (session_bio == NULL) {
512 /* file might need to be created */
513 session_bio = BIO_new_file(filename, "w+");
514 if (session_bio == NULL)
515 return rc;
516 new_cache = 1;
517 }
518
519 if (new_cache == 0) {
520 /* read in our cached session */
521 if (PEM_read_bio_SSL_SESSION(session_bio, &sess, NULL, NULL)) {
522 /* set our session */
523 if (!SSL_set_session(ssl, sess))
524 goto err;
525 }
526 } else {
527 /* Set the callback to store new sessions */
528 SSL_CTX_sess_set_new_cb(ctx, cache_new_session);
529 }
530
531 rc = 1;
532
533 err:
534 if (rc == 0)
535 BIO_free(session_bio);
536 return rc;
537 }
538
539 /**
540 * @brief Pointer to a list of SSL polling items.
541 *
542 * This static variable holds the reference to a dynamically allocated list
543 * of SSL_POLL_ITEM structures used for SSL polling operations. It is
544 * initialized to NULL and will be populated as needed.
545 */
546 static SSL_POLL_ITEM *poll_list = NULL;
547
548 /**
549 * @brief Pointer to an array of BIO objects for output.
550 *
551 * This static variable holds the reference to a dynamically allocated array
552 * of BIO structures, which are used for handling output in SSL operations.
553 * It is initialized to NULL and will be set when needed. This array holds
554 * the out bio's for all received data from GET requests
555 */
556 static BIO **outbiolist = NULL;
557
558 /**
559 * @brief Pointer to an array of output names.
560 *
561 * This static variable holds the reference to a dynamically allocated array
562 * of strings, representing output names. It is initialized to NULL and
563 * populated as required during operation. This array holds the names of the
564 * output files from http GET requests. Indicies are correlated with the
565 * corresponding outbiolist and poll_list arrays
566 */
567 static char **outnames = NULL;
568
569 /**
570 * @brief Counter for the number of poll items.
571 *
572 * This static variable holds the count of items in the poll_list. It is
573 * initialized to 0 and updated as items are added or removed from the list.
574 */
575 static size_t poll_count = 0;
576
577 /**
578 * @brief Pointer to an array of request strings.
579 *
580 * This static variable holds the reference to a dynamically allocated array
581 * of strings, representing requests. It is initialized to NULL and populated
582 * as requests are added during execution.
583 */
584 static char **req_array = NULL;
585
586 /**
587 * @brief Counter for the total number of requests.
588 *
589 * This static variable tracks the total number of parsed from reqfile. It is
590 * initialized to 0 and incremented as new requests are processed.
591 */
592 static size_t total_requests = 0;
593
594 /**
595 * @brief Index for the current request in the request array.
596 *
597 * This static variable keeps track of the index of the current request being
598 * processed in the request array. It is initialized to 0 and updated as
599 * requests are handled.
600 */
601 static size_t req_idx = 0;
602
603 /**
604 * @brief Builds and processes a set of SSL poll requests.
605 *
606 * This function creates a new set of SSL poll requests based on the current
607 * request array. It allocates and manages memory for poll lists, BIO output
608 * files, and associated request names. Each request sends an HTTP GET to the
609 * corresponding peer. The function processes the requests until a batch limit
610 * or error is encountered.
611 *
612 * @param ssl A pointer to the SSL object to use for creating new streams.
613 *
614 * @return The number of poll requests successfully built, or 0 on error.
615 */
build_request_set(SSL * ssl)616 static size_t build_request_set(SSL *ssl)
617 {
618 size_t poll_idx;
619 char *req;
620 char outfilename[REQ_STRING_SZ];
621 char req_string[REQ_STRING_SZ];
622 SSL *new_stream;
623 size_t written;
624
625 /*
626 * Free any previous poll list
627 */
628 for (poll_idx = 0; poll_idx < poll_count; poll_idx++) {
629 (void)BIO_flush(outbiolist[poll_idx]);
630 BIO_free(outbiolist[poll_idx]);
631 SSL_free(poll_list[poll_idx].desc.value.ssl);
632 }
633
634 /*
635 * Reset out lists and poll_count
636 */
637 OPENSSL_free(outbiolist);
638 OPENSSL_free(outnames);
639 OPENSSL_free(poll_list);
640 outnames = NULL;
641 poll_list = NULL;
642 outbiolist = NULL;
643
644 poll_count = 0;
645
646 /*
647 * Iterate through our parsed lists of requests
648 * note req_idx may start at a non-zero value if
649 * multiple calls to build_request_list are made
650 */
651 while (req_idx < total_requests) {
652 req = req_array[req_idx];
653 /* Up our poll count and set our poll_list index */
654 poll_count++;
655 poll_idx = poll_count - 1;
656
657 /*
658 * Expand our poll_list, outbiolist, and outnames arrays
659 */
660 poll_list = OPENSSL_realloc(poll_list,
661 sizeof(SSL_POLL_ITEM) * poll_count);
662 if (poll_list == NULL) {
663 fprintf(stderr, "Unable to realloc poll_list\n");
664 goto err;
665 }
666
667 outbiolist = OPENSSL_realloc(outbiolist,
668 sizeof(BIO *) * poll_count);
669 if (outbiolist == NULL) {
670 fprintf(stderr, "Unable to realloc outbiolist\n");
671 goto err;
672 }
673
674 outnames = OPENSSL_realloc(outnames, sizeof(char *) * poll_count);
675 if (outnames == NULL) {
676 fprintf(stderr, "Unable to realloc outnames\n");
677 goto err;
678 }
679
680 /* set the output file name for this index */
681 outnames[poll_idx] = req;
682
683 /* Format the http request */
684 BIO_snprintf(req_string, REQ_STRING_SZ, "GET /%s\r\n", req);
685
686 /* build the outfile request path */
687 memset(outfilename, 0, REQ_STRING_SZ);
688 BIO_snprintf(outfilename, REQ_STRING_SZ, "/downloads/%s", req);
689
690 /* open a bio to write the file */
691 outbiolist[poll_idx] = BIO_new_file(outfilename, "w+");
692 if (outbiolist[poll_idx] == NULL) {
693 fprintf(stderr, "Failed to open outfile %s\n", outfilename);
694 goto err;
695 }
696
697 /* create a request stream */
698 new_stream = NULL;
699
700 /*
701 * We don't strictly have to do this check, but our quic client limits
702 * our max data streams to 100, so we're just batching in groups of 100
703 * for now
704 */
705 if (poll_count <= 99)
706 new_stream = SSL_new_stream(ssl, 0);
707
708 if (new_stream == NULL) {
709 /*
710 * We ran out of new streams to allocate
711 * return and process this batch before getting more
712 */
713 poll_count--;
714 return poll_count;
715 }
716
717 /*
718 * Create a poll descriptor for this stream
719 */
720 poll_list[poll_idx].desc = SSL_as_poll_descriptor(new_stream);
721 poll_list[poll_idx].revents = 0;
722 poll_list[poll_idx].events = SSL_POLL_EVENT_R;
723
724 /* Write an HTTP GET request to the peer */
725 while (!SSL_write_ex2(poll_list[poll_idx].desc.value.ssl,
726 req_string, strlen(req_string),
727 SSL_WRITE_FLAG_CONCLUDE, &written)) {
728 if (handle_io_failure(poll_list[poll_idx].desc.value.ssl, 0) == 1)
729 continue; /* Retry */
730 fprintf(stderr, "Failed to write start of HTTP request\n");
731 goto err; /* Cannot retry: error */
732 }
733
734 req_idx++;
735 }
736 return poll_count;
737
738 err:
739 for (poll_idx = 0; poll_idx < poll_count; poll_idx++) {
740 BIO_free(outbiolist[poll_idx]);
741 SSL_free(poll_list[poll_idx].desc.value.ssl);
742 }
743 OPENSSL_free(poll_list);
744 OPENSSL_free(outbiolist);
745 poll_list = NULL;
746 outbiolist = NULL;
747 poll_count = 0;
748 return poll_count;
749 }
750
751 /**
752 * @brief Static pointer to a BIO_ADDR structure representing the peer's address.
753 *
754 * This variable is used to store the address of a peer for network communication.
755 * It is statically allocated and should be initialized appropriately.
756 */
757 static BIO_ADDR *peer_addr = NULL;
758
759 /**
760 * @brief Set up a TLS/QUIC connection to the specified hostname and port.
761 *
762 * This function creates and configures an SSL context for a client connection
763 * using the QUIC client method. It sets up the necessary certificates,
764 * performs host verification, configures ALPN, and establishes a non-blocking
765 * connection.
766 *
767 * @param hostname Hostname to connect to.
768 * @param port Port to connect to.
769 * @param ipv6 Whether to use IPv6 (non-zero for IPv6, zero for IPv4).
770 * @param ctx Pointer to an SSL_CTX object, which will be created.
771 * @param ssl Pointer to an SSL object, which will be created.
772 *
773 * @return Returns 0 on success, 1 on error.
774 */
setup_connection(char * hostname,char * port,int ipv6,SSL_CTX ** ctx,SSL ** ssl)775 static int setup_connection(char *hostname, char *port, int ipv6,
776 SSL_CTX **ctx, SSL **ssl)
777 {
778 unsigned char alpn[] = {10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p'};
779 int ret = 0;
780 char *sslkeylogfile = NULL;
781 BIO *bio = NULL;
782
783 /*
784 * Create an SSL_CTX which we can use to create SSL objects from. We
785 * want an SSL_CTX for creating clients so we use
786 * OSSL_QUIC_client_method() here.
787 */
788 *ctx = SSL_CTX_new(OSSL_QUIC_client_method());
789 if (*ctx == NULL) {
790 fprintf(stderr, "Failed to create the SSL_CTX\n");
791 goto end;
792 }
793
794 /*
795 * Configure the client to abort the handshake if certificate
796 * verification fails. Virtually all clients should do this unless you
797 * really know what you are doing.
798 */
799 SSL_CTX_set_verify(*ctx, SSL_VERIFY_PEER, NULL);
800
801 /*
802 * Use the default trusted certificate store
803 * Note: The store is read from SSL_CERT_DIR and SSL_CERT_FILE
804 * environment variables in the default case, so users can set those
805 * When running this application to direct where the store is loaded from
806 */
807 if (!SSL_CTX_set_default_verify_paths(*ctx)) {
808 fprintf(stderr, "Failed to set the default trusted certificate store\n");
809 goto end;
810 }
811
812 sslkeylogfile = getenv("SSLKEYLOGFILE");
813 if (sslkeylogfile != NULL)
814 if (set_keylog_file(*ctx, sslkeylogfile))
815 goto end;
816
817 /* Create an SSL object to represent the TLS connection */
818 *ssl = SSL_new(*ctx);
819 if (*ssl == NULL) {
820 fprintf(stderr, "Failed to create the SSL object\n");
821 goto end;
822 }
823
824 if (getenv("SSL_SESSION_FILE") != NULL) {
825 if (!setup_session_cache(*ssl, *ctx, getenv("SSL_SESSION_FILE"))) {
826 fprintf(stderr, "Unable to setup session cache\n");
827 goto end;
828 }
829 }
830
831 /*
832 * Create the underlying transport socket/BIO and associate it with the
833 * connection.
834 */
835 bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET,
836 &peer_addr);
837 if (bio == NULL) {
838 fprintf(stderr, "Failed to crete the BIO\n");
839 goto end;
840 }
841 SSL_set_bio(*ssl, bio, bio);
842
843 /*
844 * Tell the server during the handshake which hostname we are attempting
845 * to connect to in case the server supports multiple hosts.
846 */
847 if (!SSL_set_tlsext_host_name(*ssl, hostname)) {
848 fprintf(stderr, "Failed to set the SNI hostname\n");
849 goto end;
850 }
851
852 /*
853 * Ensure we check during certificate verification that the server has
854 * supplied a certificate for the hostname that we were expecting.
855 * Virtually all clients should do this unless you really know what you
856 * are doing.
857 */
858 if (!SSL_set1_host(*ssl, hostname)) {
859 fprintf(stderr, "Failed to set the certificate verification hostname");
860 goto end;
861 }
862
863 /* SSL_set_alpn_protos returns 0 for success! */
864 if (SSL_set_alpn_protos(*ssl, alpn, sizeof(alpn)) != 0) {
865 fprintf(stderr, "Failed to set the ALPN for the connection\n");
866 goto end;
867 }
868
869 /* Set the IP address of the remote peer */
870 if (!SSL_set1_initial_peer_addr(*ssl, peer_addr)) {
871 fprintf(stderr, "Failed to set the initial peer address\n");
872 goto end;
873 }
874
875 /*
876 * The underlying socket is always nonblocking with QUIC, but the default
877 * behaviour of the SSL object is still to block. We set it for nonblocking
878 * mode in this demo.
879 */
880 if (!SSL_set_blocking_mode(*ssl, 0)) {
881 fprintf(stderr, "Failed to turn off blocking mode\n");
882 goto end;
883 }
884
885 /* Do the handshake with the server */
886 while ((ret = SSL_connect(*ssl)) != 1) {
887 if (handle_io_failure(*ssl, ret) == 1)
888 continue; /* Retry */
889 fprintf(stderr, "Failed to connect to server\n");
890 goto end; /* Cannot retry: error */
891 }
892
893 return 1;
894 end:
895 SSL_CTX_free(*ctx);
896 SSL_free(*ssl);
897 BIO_ADDR_free(peer_addr);
898 return 0;
899 }
900
901 /**
902 * @brief Entry point for the QUIC hq-interop client demo application.
903 *
904 * This function sets up an SSL/TLS connection using QUIC, sends HTTP GET
905 * requests for files specified in the command-line arguments, and saves
906 * the responses to disk. It handles various configurations such as IPv6
907 * support, session caching, and key logging.
908 *
909 * @param argc The number of command-line arguments.
910 * @param argv The array of command-line arguments. The expected format is
911 * "[-6] hostname port file".
912 * @return EXIT_SUCCESS on success, or EXIT_FAILURE on error.
913 *
914 * @note The function performs the following main tasks:
915 * - Parses command-line arguments and configures IPv6 if specified.
916 * - Reads the list of requests from the specified file.
917 * - Sets up the SSL context and configures certificate verification.
918 * - Optionally enables key logging and session caching.
919 * - Establishes a non-blocking QUIC connection to the server.
920 * - Sends an HTTP GET request for each file and writes the response
921 * to the corresponding output file.
922 * - Gracefully shuts down the SSL connection and frees resources.
923 * - Prints any OpenSSL error stack information on failure.
924 */
main(int argc,char * argv[])925 int main(int argc, char *argv[])
926 {
927 SSL_CTX *ctx = NULL;
928 SSL *ssl = NULL;
929 BIO *req_bio = NULL;
930 int res = EXIT_FAILURE;
931 int ret;
932 size_t readbytes = 0;
933 char buf[160];
934 int eof = 0;
935 int argnext = 1;
936 char *reqfile = NULL;
937 char *reqnames = OPENSSL_zalloc(1025);
938 size_t read_offset = 0;
939 size_t bytes_read = 0;
940 size_t poll_idx = 0;
941 size_t poll_done = 0;
942 size_t result_count = 0;
943 struct timeval poll_timeout;
944 size_t this_poll_count = 0;
945 char *req = NULL;
946 char *hostname, *port;
947 int ipv6 = 0;
948
949 if (argc < 4) {
950 fprintf(stderr, "Usage: quic-hq-interop [-6] hostname port reqfile\n");
951 goto end;
952 }
953
954 if (!strcmp(argv[argnext], "-6")) {
955 if (argc < 5) {
956 fprintf(stderr, "Usage: quic-hq-interop [-6] hostname port reqfile\n");
957 goto end;
958 }
959 ipv6 = 1;
960 argnext++;
961 }
962 hostname = argv[argnext++];
963 port = argv[argnext++];
964 reqfile = argv[argnext];
965
966 req_bio = BIO_new_file(reqfile, "r");
967 if (req_bio == NULL) {
968 fprintf(stderr, "Failed to open request file %s\n", reqfile);
969 goto end;
970 }
971
972 /* Get the list of requests */
973 while (!BIO_eof(req_bio)) {
974 if (!BIO_read_ex(req_bio, &reqnames[read_offset], REQ_STRING_SZ, &bytes_read)) {
975 fprintf(stderr, "Failed to read some data from request file\n");
976 goto end;
977 }
978 read_offset += bytes_read;
979 reqnames = OPENSSL_realloc(reqnames, read_offset + REQ_STRING_SZ);
980 if (reqnames == NULL) {
981 fprintf(stderr, "Realloc failure\n");
982 goto end;
983 }
984 }
985 BIO_free(req_bio);
986 req_bio = NULL;
987 reqnames[read_offset + 1] = '\0';
988
989 if (!setup_connection(hostname, port, ipv6, &ctx, &ssl)) {
990 fprintf(stderr, "Unable to establish connection\n");
991 goto end;
992 }
993
994 req = strtok(reqnames, " ");
995
996 while (req != NULL) {
997 total_requests++;
998 req_array = OPENSSL_realloc(req_array, sizeof(char *) * total_requests);
999 req_array[total_requests - 1] = req;
1000 req = strtok(NULL, " ");
1001 }
1002
1003 /* get a list of requests to poll */
1004 this_poll_count = build_request_set(ssl);
1005
1006 /*
1007 * Now poll all our descriptors for events
1008 */
1009 while (this_poll_count != 0 && poll_done < this_poll_count) {
1010 result_count = 0;
1011 poll_timeout.tv_sec = 0;
1012 poll_timeout.tv_usec = 0;
1013 if (!SSL_poll(poll_list, this_poll_count, sizeof(SSL_POLL_ITEM),
1014 &poll_timeout, 0, &result_count)) {
1015 fprintf(stderr, "Failed to poll\n");
1016 goto end;
1017 }
1018
1019 /* Iterate over our poll array looking for ready SSL's */
1020 for (poll_idx = 0; poll_idx < this_poll_count; poll_idx++) {
1021 /*
1022 * If we have visited the number of SSL's that SSL_poll
1023 * indicated were ready, we can go poll again
1024 */
1025 if (result_count == 0)
1026 break;
1027
1028 if (poll_list[poll_idx].revents == SSL_POLL_EVENT_R) {
1029 /*
1030 * We found an SSL that we can read, drop our result count
1031 */
1032 result_count--;
1033
1034 /* And clear the revents for the next poll */
1035 poll_list[poll_idx].revents = 0;
1036
1037 /*
1038 * Get up to sizeof(buf) bytes of the response. We keep reading until
1039 * the server closes the connection.
1040 */
1041 eof = 0;
1042
1043 /* Read our data, and handle any errors/eof conditions */
1044 if (!SSL_read_ex(poll_list[poll_idx].desc.value.ssl, buf,
1045 sizeof(buf), &readbytes)) {
1046 switch (handle_io_failure(poll_list[poll_idx].desc.value.ssl,
1047 0)) {
1048 case 1:
1049 eof = 0;
1050 break; /* Retry on next poll */
1051 case 0:
1052 eof = 1;
1053 break;
1054 case -1:
1055 default:
1056 fprintf(stderr, "Failed reading remaining data\n");
1057 goto end; /* Cannot retry: error */
1058 }
1059 }
1060
1061 /*
1062 * If error handling indicated that this SSL is in an EOF state
1063 * we mark the SSL as not needing any more polling, and up our
1064 * poll_done count. Otherwise, just write to the outbio
1065 */
1066 if (!eof) {
1067 BIO_write(outbiolist[poll_idx], buf, readbytes);
1068 } else {
1069 fprintf(stderr, "completed %s\n", outnames[poll_idx]);
1070 /* This file is done, take it out of polling contention */
1071 poll_list[poll_idx].events = 0;
1072 poll_done++;
1073 }
1074 }
1075 }
1076
1077 /*
1078 * If we've completed this poll set, try get another one
1079 */
1080 if (poll_done == this_poll_count) {
1081 this_poll_count = build_request_set(ssl);
1082 poll_done = 0;
1083 }
1084 }
1085
1086 /*
1087 * Repeatedly call SSL_shutdown() until the connection is fully
1088 * closed.
1089 */
1090 fprintf(stderr, "Shutting down\n");
1091 while ((ret = SSL_shutdown(ssl)) != 1) {
1092 if (ret < 0 && handle_io_failure(ssl, ret) == 1)
1093 continue; /* Retry */
1094 }
1095
1096 /* Success! */
1097 res = EXIT_SUCCESS;
1098 end:
1099 /*
1100 * If something bad happened then we will dump the contents of the
1101 * OpenSSL error stack to stderr. There might be some useful diagnostic
1102 * information there.
1103 */
1104 if (res == EXIT_FAILURE)
1105 ERR_print_errors_fp(stderr);
1106
1107 /*
1108 * Free the resources we allocated. We do not free the BIO object here
1109 * because ownership of it was immediately transferred to the SSL object
1110 * via SSL_set_bio(). The BIO will be freed when we free the SSL object.
1111 */
1112 BIO_ADDR_free(peer_addr);
1113 OPENSSL_free(reqnames);
1114 BIO_free(session_bio);
1115 for (poll_idx = 0; poll_idx < poll_count; poll_idx++) {
1116 BIO_free(outbiolist[poll_idx]);
1117 SSL_free(poll_list[poll_idx].desc.value.ssl);
1118 }
1119 SSL_free(ssl);
1120 SSL_CTX_free(ctx);
1121 OPENSSL_free(outbiolist);
1122 OPENSSL_free(poll_list);
1123 return res;
1124 }
1125