diff options
author | Matt Caswell <matt@openssl.org> | 2023-03-09 17:06:33 +0000 |
---|---|---|
committer | Matt Caswell <matt@openssl.org> | 2023-05-08 10:13:39 +0100 |
commit | f34e5d7a12775ce2fb84e4c5d8b830b5a9f06566 (patch) | |
tree | dbda2178a24e0033977db2cccdb599b65f5b4759 /apps | |
parent | 6aeb42eca97227c8235af0986d1525ee4a916504 (diff) |
Add QUIC support to s_client
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20580)
Diffstat (limited to 'apps')
-rw-r--r-- | apps/include/s_apps.h | 2 | ||||
-rw-r--r-- | apps/lib/s_socket.c | 9 | ||||
-rw-r--r-- | apps/s_client.c | 144 |
3 files changed, 125 insertions, 30 deletions
diff --git a/apps/include/s_apps.h b/apps/include/s_apps.h index d4425b9061..bc8f4bf27b 100644 --- a/apps/include/s_apps.h +++ b/apps/include/s_apps.h @@ -36,7 +36,7 @@ int ssl_print_groups(BIO *out, SSL *s, int noshared); int ssl_print_tmp_key(BIO *out, SSL *s); int init_client(int *sock, const char *host, const char *port, const char *bindhost, const char *bindport, - int family, int type, int protocol, int tfo, + int family, int type, int protocol, int tfo, int doconn, BIO_ADDR **ba_ret); int should_retry(int i); void do_ssl_shutdown(SSL *ssl); diff --git a/apps/lib/s_socket.c b/apps/lib/s_socket.c index 4d31e82032..5ae689a4db 100644 --- a/apps/lib/s_socket.c +++ b/apps/lib/s_socket.c @@ -65,7 +65,8 @@ BIO_ADDR *ourpeer = NULL; * @type: socket type, must be SOCK_STREAM or SOCK_DGRAM * @protocol: socket protocol, e.g. IPPROTO_TCP or IPPROTO_UDP (or 0 for any) * @tfo: flag to enable TCP Fast Open - * @ba_ret: BIO_ADDR that was connected to for TFO, to be freed by caller + * @doconn: whether we should call BIO_connect() on the socket + * @ba_ret: BIO_ADDR for the remote peer, to be freed by caller * * This will create a socket and use it to connect to a host:port, or if * family == AF_UNIX, to the path found in host. @@ -78,7 +79,7 @@ BIO_ADDR *ourpeer = NULL; */ int init_client(int *sock, const char *host, const char *port, const char *bindhost, const char *bindport, - int family, int type, int protocol, int tfo, + int family, int type, int protocol, int tfo, int doconn, BIO_ADDR **ba_ret) { BIO_ADDRINFO *res = NULL; @@ -173,14 +174,14 @@ int init_client(int *sock, const char *host, const char *port, options |= BIO_SOCK_TFO; } - if (!BIO_connect(*sock, BIO_ADDRINFO_address(ai), options)) { + if (doconn && !BIO_connect(*sock, BIO_ADDRINFO_address(ai), options)) { BIO_closesocket(*sock); *sock = INVALID_SOCKET; continue; } /* Save the address */ - if (tfo && ba_ret != NULL) + if (tfo || !doconn) *ba_ret = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); /* Success, don't try any more addresses */ diff --git a/apps/s_client.c b/apps/s_client.c index d6fd124da8..1c94e90dfe 100644 --- a/apps/s_client.c +++ b/apps/s_client.c @@ -487,8 +487,8 @@ typedef enum OPTION_choice { #endif OPT_SSL3, OPT_SSL_CONFIG, OPT_TLS1_3, OPT_TLS1_2, OPT_TLS1_1, OPT_TLS1, OPT_DTLS, OPT_DTLS1, - OPT_DTLS1_2, OPT_SCTP, OPT_TIMEOUT, OPT_MTU, OPT_KEYFORM, OPT_PASS, - OPT_CERT_CHAIN, OPT_KEY, OPT_RECONNECT, OPT_BUILD_CHAIN, + OPT_DTLS1_2, OPT_QUIC, OPT_SCTP, OPT_TIMEOUT, OPT_MTU, OPT_KEYFORM, + OPT_PASS, OPT_CERT_CHAIN, OPT_KEY, OPT_RECONNECT, OPT_BUILD_CHAIN, OPT_NEXTPROTONEG, OPT_ALPN, OPT_CAPATH, OPT_NOCAPATH, OPT_CHAINCAPATH, OPT_VERIFYCAPATH, OPT_CAFILE, OPT_NOCAFILE, OPT_CHAINCAFILE, OPT_VERIFYCAFILE, @@ -681,6 +681,7 @@ const OPTIONS s_client_options[] = { #endif #ifndef OPENSSL_NO_DTLS {"dtls", OPT_DTLS, '-', "Use any version of DTLS"}, + {"quic", OPT_QUIC, '-', "Use QUIC"}, {"timeout", OPT_TIMEOUT, '-', "Enable send/receive timeout on DTLS connections"}, {"mtu", OPT_MTU, 'p', "Set the link layer MTU"}, @@ -792,7 +793,8 @@ static const OPT_PAIR services[] = { #define IS_PROT_FLAG(o) \ (o == OPT_SSL3 || o == OPT_TLS1 || o == OPT_TLS1_1 || o == OPT_TLS1_2 \ - || o == OPT_TLS1_3 || o == OPT_DTLS || o == OPT_DTLS1 || o == OPT_DTLS1_2) + || o == OPT_TLS1_3 || o == OPT_DTLS || o == OPT_DTLS1 || o == OPT_DTLS1_2 \ + || o == OPT_QUIC) /* Free |*dest| and optionally set it to a copy of |source|. */ static void freeandcopy(char **dest, const char *source) @@ -882,6 +884,7 @@ int s_client_main(int argc, char **argv) int socket_family = AF_UNSPEC, socket_type = SOCK_STREAM, protocol = 0; int starttls_proto = PROTO_OFF, crl_format = FORMAT_UNDEF, crl_download = 0; int write_tty, read_tty, write_ssl, read_ssl, tty_on, ssl_pending; + int first_loop; #if !defined(OPENSSL_SYS_WINDOWS) && !defined(OPENSSL_SYS_MSDOS) int at_eof = 0; #endif @@ -937,9 +940,8 @@ int s_client_main(int argc, char **argv) #endif BIO *bio_c_msg = NULL; const char *keylog_file = NULL, *early_data_file = NULL; -#ifndef OPENSSL_NO_DTLS int isdtls = 0; -#endif + int isquic = 0; char *psksessf = NULL; int enable_pha = 0; int enable_client_rpk = 0; @@ -951,7 +953,7 @@ int s_client_main(int argc, char **argv) int enable_ktls = 0; #endif int tfo = 0; - BIO_ADDR *tfo_addr = NULL; + BIO_ADDR *peer_addr = NULL; struct user_data_st user_data; FD_ZERO(&readfds); @@ -1280,6 +1282,9 @@ int s_client_main(int argc, char **argv) #ifndef OPENSSL_NO_DTLS isdtls = 0; #endif +#ifndef OPENSS_NO_QUIC + isquic = 0; +#endif break; case OPT_TLS1_3: min_version = TLS1_3_VERSION; @@ -1288,6 +1293,9 @@ int s_client_main(int argc, char **argv) #ifndef OPENSSL_NO_DTLS isdtls = 0; #endif +#ifndef OPENSS_NO_QUIC + isquic = 0; +#endif break; case OPT_TLS1_2: min_version = TLS1_2_VERSION; @@ -1296,6 +1304,9 @@ int s_client_main(int argc, char **argv) #ifndef OPENSSL_NO_DTLS isdtls = 0; #endif +#ifndef OPENSS_NO_QUIC + isquic = 0; +#endif break; case OPT_TLS1_1: min_version = TLS1_1_VERSION; @@ -1304,6 +1315,9 @@ int s_client_main(int argc, char **argv) #ifndef OPENSSL_NO_DTLS isdtls = 0; #endif +#ifndef OPENSS_NO_QUIC + isquic = 0; +#endif break; case OPT_TLS1: min_version = TLS1_VERSION; @@ -1312,12 +1326,18 @@ int s_client_main(int argc, char **argv) #ifndef OPENSSL_NO_DTLS isdtls = 0; #endif +#ifndef OPENSS_NO_QUIC + isquic = 0; +#endif break; case OPT_DTLS: #ifndef OPENSSL_NO_DTLS meth = DTLS_client_method(); socket_type = SOCK_DGRAM; isdtls = 1; +# ifndef OPENSS_NO_QUIC + isquic = 0; +# endif #endif break; case OPT_DTLS1: @@ -1327,6 +1347,9 @@ int s_client_main(int argc, char **argv) max_version = DTLS1_VERSION; socket_type = SOCK_DGRAM; isdtls = 1; +# ifndef OPENSS_NO_QUIC + isquic = 0; +# endif #endif break; case OPT_DTLS1_2: @@ -1336,6 +1359,21 @@ int s_client_main(int argc, char **argv) max_version = DTLS1_2_VERSION; socket_type = SOCK_DGRAM; isdtls = 1; +# ifndef OPENSS_NO_QUIC + isquic = 0; +# endif +#endif + break; + case OPT_QUIC: +#ifndef OPENSSL_NO_QUIC + meth = OSSL_QUIC_client_method(); + min_version = 0; + max_version = 0; + socket_type = SOCK_DGRAM; +# ifndef OPENSSL_NO_DTLS + isdtls = 0; +# endif + isquic = 1; #endif break; case OPT_SCTP: @@ -2121,24 +2159,38 @@ int s_client_main(int argc, char **argv) goto end; } #endif +#ifndef OPENSSL_NO_QUIC + if (isquic && tfo) { + BIO_printf(bio_err, "%s: QUIC does not support the -tfo option\n", prog); + goto end; + } +#endif if (tfo) BIO_printf(bio_c_out, "Connecting via TFO\n"); re_start: if (init_client(&sock, host, port, bindhost, bindport, socket_family, - socket_type, protocol, tfo, &tfo_addr) == 0) { + socket_type, protocol, tfo, !isquic, &peer_addr) == 0) { BIO_printf(bio_err, "connect:errno=%d\n", get_last_socket_error()); BIO_closesocket(sock); goto end; } BIO_printf(bio_c_out, "CONNECTED(%08X)\n", sock); - if (c_nbio) { + /* + * QUIC always uses a non-blocking socket - and we have to switch on + * non-blocking mode at the SSL level + */ + if (c_nbio || isquic) { if (!BIO_socket_nbio(sock, 1)) { ERR_print_errors(bio_err); goto end; } - BIO_printf(bio_c_out, "Turned on non blocking io\n"); + if (c_nbio) { + if (isquic && !SSL_set_blocking_mode(con, 0)) + goto end; + BIO_printf(bio_c_out, "Turned on non blocking io\n"); + } } #ifndef OPENSSL_NO_DTLS if (isdtls) { @@ -2199,6 +2251,15 @@ int s_client_main(int argc, char **argv) } } else #endif /* OPENSSL_NO_DTLS */ +#ifndef OPENSSL_NO_QUIC + if (isquic) { + sbio = BIO_new_dgram(sock, BIO_NOCLOSE); + if (!SSL_set_initial_peer_addr(con, peer_addr)) { + BIO_printf(bio_err, "Failed to set the inital peer address\n"); + goto shut; + } + } else +#endif sbio = BIO_new_socket(sock, BIO_NOCLOSE); if (sbio == NULL) { @@ -2209,10 +2270,10 @@ int s_client_main(int argc, char **argv) } /* Now that we're using a BIO... */ - if (tfo_addr != NULL) - (void)BIO_set_conn_address(sbio, tfo_addr); - if (tfo) + if (tfo) { + (void)BIO_set_conn_address(sbio, peer_addr); (void)BIO_set_tfo(sbio, 1); + } if (nbio_test) { BIO *test; @@ -2266,6 +2327,7 @@ int s_client_main(int argc, char **argv) tty_on = 0; read_ssl = 1; write_ssl = 1; + first_loop = 1; cbuf_len = 0; cbuf_off = 0; @@ -2841,10 +2903,23 @@ int s_client_main(int argc, char **argv) FD_ZERO(&readfds); FD_ZERO(&writefds); - if (SSL_is_dtls(con) && DTLSv1_get_timeout(con, &timeout)) - timeoutp = &timeout; - else + if ((isdtls || isquic) && SSL_get_tick_timeout(con, &timeout)) { + /* + * TODO(QUIC): The need to do this seems like an unintended + * consequence of the SSL_get_tick_timeout() design. You cannot + * call select with the returned timeout if tv_sec < 0 because it + * errors out. Possibly SSL_get_tick_timeout() should return 0 if + * tv_sec < 0 to make it easier to detect this? Or alternatively + * make it work the same way that DTLSv1_get_timeout() did which + * would make SSL_get_tick_timeout() a drop in replacement + */ + if (timeout.tv_sec < 0) + timeoutp = NULL; + else + timeoutp = &timeout; + } else { timeoutp = NULL; + } if (!SSL_is_init_finished(con) && SSL_total_renegotiations(con) == 0 && SSL_get_key_update_type(con) == SSL_KEY_UPDATE_NONE) { @@ -2928,15 +3003,26 @@ int s_client_main(int argc, char **argv) openssl_fdset(fileno_stdout(), &writefds); #endif } - if (read_ssl) + + /* + * Note that for QUIC we never actually check FD_ISSET() for the + * underlying network fds. We just rely on select waking up when + * they become readable/writeable and then SSL_tick() doing the + * right thing. + */ + if ((!isquic && read_ssl) + || (isquic && SSL_net_read_desired(con))) openssl_fdset(SSL_get_fd(con), &readfds); - if (write_ssl) + if ((!isquic && write_ssl) + || (isquic && (first_loop || SSL_net_write_desired(con)))) openssl_fdset(SSL_get_fd(con), &writefds); #else if (!tty_on || !write_tty) { - if (read_ssl) + if ((!isquic && read_ssl) + || (isquic && SSL_net_read_desired(con))) openssl_fdset(SSL_get_fd(con), &readfds); - if (write_ssl) + if ((!isquic && write_ssl) + || (isquic && (first_loop || SSL_net_write_desired(con)))) openssl_fdset(SSL_get_fd(con), &writefds); } #endif @@ -2980,10 +3066,17 @@ int s_client_main(int argc, char **argv) } } - if (SSL_is_dtls(con) && DTLSv1_handle_timeout(con) > 0) - BIO_printf(bio_err, "TIMEOUT occurred\n"); + if (timeoutp != NULL) { + SSL_tick(con); + if (isdtls + && !FD_ISSET(SSL_get_fd(con), &readfds) + && !FD_ISSET(SSL_get_fd(con), &writefds)) + BIO_printf(bio_err, "TIMEOUT occurred\n"); + } - if (!ssl_pending && FD_ISSET(SSL_get_fd(con), &writefds)) { + if (!ssl_pending + && ((!isquic && FD_ISSET(SSL_get_fd(con), &writefds)) + || (isquic && (cbuf_len > 0 || first_loop)))) { k = SSL_write(con, &(cbuf[cbuf_off]), (unsigned int)cbuf_len); switch (SSL_get_error(con, k)) { case SSL_ERROR_NONE: @@ -3076,7 +3169,8 @@ int s_client_main(int argc, char **argv) read_ssl = 1; write_tty = 0; } - } else if (ssl_pending || FD_ISSET(SSL_get_fd(con), &readfds)) { + } else if (ssl_pending + || (!isquic && FD_ISSET(SSL_get_fd(con), &readfds))) { #ifdef RENEG { static int iiii; @@ -3187,9 +3281,9 @@ int s_client_main(int argc, char **argv) ret = 0; goto shut; } - write_ssl = 1; read_tty = 0; } + first_loop = 0; } shut: @@ -3243,7 +3337,7 @@ int s_client_main(int argc, char **argv) OPENSSL_free(srp_arg.srppassin); #endif OPENSSL_free(sname_alloc); - BIO_ADDR_free(tfo_addr); + BIO_ADDR_free(peer_addr); OPENSSL_free(connectstr); OPENSSL_free(bindstr); OPENSSL_free(bindhost); |