summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorMatt Caswell <matt@openssl.org>2023-03-09 17:06:33 +0000
committerMatt Caswell <matt@openssl.org>2023-05-08 10:13:39 +0100
commitf34e5d7a12775ce2fb84e4c5d8b830b5a9f06566 (patch)
treedbda2178a24e0033977db2cccdb599b65f5b4759 /apps
parent6aeb42eca97227c8235af0986d1525ee4a916504 (diff)
downloadopenssl-new-f34e5d7a12775ce2fb84e4c5d8b830b5a9f06566.tar.gz
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.h2
-rw-r--r--apps/lib/s_socket.c9
-rw-r--r--apps/s_client.c144
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);