From bcbb30afe2ef51c7affaaa7ce4db67e26e7ff6b7 Mon Sep 17 00:00:00 2001 From: "Dr. David von Oheimb" Date: Tue, 4 Feb 2020 09:55:35 +0100 Subject: [PATCH] add BIO_socket_wait(), BIO_wait(), and BIO_connect_retry() improving timeout support Reviewed-by: Matt Caswell Reviewed-by: David von Oheimb (Merged from https://github.com/openssl/openssl/pull/10667) --- crypto/bio/b_sock.c | 121 +++++++++++++++++++++++++++++++++- crypto/bio/bio_err.c | 3 + crypto/err/openssl.txt | 3 + doc/man3/BIO_set_callback.pod | 2 +- doc/man3/BIO_socket_wait.pod | 53 +++++++++++++++ include/internal/sockets.h | 7 ++ include/openssl/bio.h | 3 + include/openssl/bioerr.h | 3 + util/libcrypto.num | 3 + 9 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 doc/man3/BIO_socket_wait.pod diff --git a/crypto/bio/b_sock.c b/crypto/bio/b_sock.c index 78bcffdb13..966bd64356 100644 --- a/crypto/bio/b_sock.c +++ b/crypto/bio/b_sock.c @@ -9,7 +9,6 @@ #include #include -#include #include "bio_local.h" #ifndef OPENSSL_NO_SOCK # define SOCKET_PROTOCOL IPPROTO_TCP @@ -24,6 +23,13 @@ static int wsa_init_done = 0; # endif +# ifndef _WIN32 +# include +# include +# else +# include /* for type fd_set */ +# endif + # ifndef OPENSSL_NO_DEPRECATED_1_1_0 int BIO_get_host_ip(const char *str, unsigned char *ip) { @@ -369,4 +375,115 @@ int BIO_sock_info(int sock, return 1; } -#endif +/* TODO simplify by BIO_socket_wait() further other uses of select() in apps/ */ +/* + * Wait on fd at most until max_time; succeed immediately if max_time == 0. + * If for_read == 0 then assume to wait for writing, else wait for reading. + * Returns -1 on error, 0 on timeout, and 1 on success. + */ +int BIO_socket_wait(int fd, int for_read, time_t max_time) +{ + fd_set confds; + struct timeval tv; + time_t now; + + if (max_time == 0) + return 1; + + now = time(NULL); + if (max_time <= now) + return 0; + + FD_ZERO(&confds); + openssl_fdset(fd, &confds); + tv.tv_usec = 0; + tv.tv_sec = (long)(max_time - now); /* this might overflow */ + return select(fd + 1, for_read ? &confds : NULL, + for_read ? NULL : &confds, NULL, &tv); +} + +/* + * Wait on BIO at most until max_time; succeed immediately if max_time == 0. + * Returns -1 on error, 0 on timeout, and 1 on success. + */ +static int bio_wait(BIO *bio, time_t max_time) +{ + int fd; + + if (BIO_get_fd(bio, &fd) <= 0) + return -1; + return BIO_socket_wait(fd, BIO_should_read(bio), max_time); +} + +/* + * Wait on BIO at most until max_time; succeed immediately if max_time == 0. + * Call BIOerr(...) unless success. + * Returns -1 on error, 0 on timeout, and 1 on success. + */ +int BIO_wait(BIO *bio, time_t max_time) +{ + int rv = bio_wait(bio, max_time); + + if (rv <= 0) + BIOerr(0, rv == 0 ? BIO_R_TRANSFER_TIMEOUT : BIO_R_TRANSFER_ERROR); + return rv; +} + +/* + * Connect via the given BIO using BIO_do_connect() until success/timeout/error. + * Parameter timeout == 0 means infinite, < 0 leads to immediate timeout error. + * Returns -1 on error, 0 on timeout, and 1 on success. + */ +int BIO_connect_retry(BIO *bio, int timeout) +{ + int blocking = timeout == 0; + time_t max_time = timeout > 0 ? time(NULL) + timeout : 0; + int rv; + + if (bio == NULL) { + BIOerr(0, ERR_R_PASSED_NULL_PARAMETER); + return -1; + } + + if (timeout < 0) { + BIOerr(0, BIO_R_CONNECT_TIMEOUT); + return 0; + } + + if (!blocking) + BIO_set_nbio(bio, 1); + + retry: /* it does not help here to set SSL_MODE_AUTO_RETRY */ + rv = BIO_do_connect(bio); /* This indirectly calls ERR_clear_error(); */ + + if (rv <= 0) { + if (get_last_sys_error() == ETIMEDOUT) { + /* + * if blocking, despite blocking BIO, BIO_do_connect() timed out + * when non-blocking, BIO_do_connect() timed out early + * with rv == -1 and get_last_sys_error() == 0 + */ + ERR_clear_error(); + (void)BIO_reset(bio); + /* + * unless using BIO_reset(), blocking next connect() may crash and + * non-blocking next BIO_do_connect() will fail + */ + goto retry; + } else if (BIO_should_retry(bio)) { + /* will not actually wait if timeout == 0 (i.e., blocking BIO) */ + rv = bio_wait(bio, max_time); + if (rv > 0) + goto retry; + BIOerr(0, rv == 0 ? BIO_R_CONNECT_TIMEOUT : BIO_R_CONNECT_ERROR); + } else { + rv = -1; + if (ERR_peek_error() == 0) /* missing error queue entry */ + BIOerr(0, BIO_R_CONNECT_ERROR); /* workaround: general error */ + } + } + + return rv; +} + +#endif /* !defined(OPENSSL_NO_SOCK) */ diff --git a/crypto/bio/bio_err.c b/crypto/bio/bio_err.c index 178fdd6a79..b2a3d8e102 100644 --- a/crypto/bio/bio_err.c +++ b/crypto/bio/bio_err.c @@ -22,6 +22,7 @@ static const ERR_STRING_DATA BIO_str_reasons[] = { {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_BAD_FOPEN_MODE), "bad fopen mode"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_BROKEN_PIPE), "broken pipe"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_CONNECT_ERROR), "connect error"}, + {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_CONNECT_TIMEOUT), "connect timeout"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_GETHOSTBYNAME_ADDR_IS_NOT_AF_INET), "gethostbyname addr is not af inet"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_GETSOCKNAME_ERROR), "getsockname error"}, @@ -45,6 +46,8 @@ static const ERR_STRING_DATA BIO_str_reasons[] = { {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_NO_PORT_DEFINED), "no port defined"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_NO_SUCH_FILE), "no such file"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_NULL_PARAMETER), "null parameter"}, + {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_TRANSFER_ERROR), "transfer error"}, + {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_TRANSFER_TIMEOUT), "transfer timeout"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNABLE_TO_BIND_SOCKET), "unable to bind socket"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNABLE_TO_CREATE_SOCKET), diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index b59c8ba1c6..84a8adc52c 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -2013,6 +2013,7 @@ BIO_R_AMBIGUOUS_HOST_OR_SERVICE:129:ambiguous host or service BIO_R_BAD_FOPEN_MODE:101:bad fopen mode BIO_R_BROKEN_PIPE:124:broken pipe BIO_R_CONNECT_ERROR:103:connect error +BIO_R_CONNECT_TIMEOUT:147:connect timeout BIO_R_GETHOSTBYNAME_ADDR_IS_NOT_AF_INET:107:gethostbyname addr is not af inet BIO_R_GETSOCKNAME_ERROR:132:getsockname error BIO_R_GETSOCKNAME_TRUNCATED_ADDRESS:133:getsockname truncated address @@ -2031,6 +2032,8 @@ BIO_R_NO_HOSTNAME_OR_SERVICE_SPECIFIED:144:no hostname or service specified BIO_R_NO_PORT_DEFINED:113:no port defined BIO_R_NO_SUCH_FILE:128:no such file BIO_R_NULL_PARAMETER:115:null parameter +BIO_R_TRANSFER_ERROR:104:transfer error +BIO_R_TRANSFER_TIMEOUT:105:transfer timeout BIO_R_UNABLE_TO_BIND_SOCKET:117:unable to bind socket BIO_R_UNABLE_TO_CREATE_SOCKET:118:unable to create socket BIO_R_UNABLE_TO_KEEPALIVE:137:unable to keepalive diff --git a/doc/man3/BIO_set_callback.pod b/doc/man3/BIO_set_callback.pod index 9537a2e168..dd3aa7b11c 100644 --- a/doc/man3/BIO_set_callback.pod +++ b/doc/man3/BIO_set_callback.pod @@ -48,7 +48,7 @@ out information relating to each BIO operation. If the callback argument is set it is interpreted as a BIO to send the information to, otherwise stderr is used. -BIO_callback_fn_ex() is the type of the callback function and BIO_callback_fn() +BIO_callback_fn_ex is the type of the callback function and BIO_callback_fn is the type of the old format callback function. The meaning of each argument is described below: diff --git a/doc/man3/BIO_socket_wait.pod b/doc/man3/BIO_socket_wait.pod new file mode 100644 index 0000000000..845389215b --- /dev/null +++ b/doc/man3/BIO_socket_wait.pod @@ -0,0 +1,53 @@ +=pod + +=head1 NAME + +BIO_socket_wait, +BIO_wait, +BIO_connect_retry +- BIO socket utility functions + +=head1 SYNOPSIS + + #include + + int BIO_socket_wait(int fd, int for_read, time_t max_time); + int BIO_wait(BIO *bio, time_t max_time); + int BIO_connect_retry(BIO *bio, long timeout); + +=head1 DESCRIPTION + +BIO_socket_wait() waits on the socket B for reading if B is not 0, +else for writing, at most until B. +It succeeds immediately if B == 0 (which means no timeout given). + +BIO_wait() waits on the socket underlying the given B, for reading if +B is supposed to read, else for writing, at most until B. +It succeeds immediately if B == 0 (which means no timeout given). + +BIO_connect_retry() connects via the given B, retrying BIO_do_connect() +until success or a timeout or error condition is reached. +If the B parameter is > 0 this indicates the maximum number of seconds +to wait until the connection is established. A value of 0 enables waiting +indefinitely, while a value < 0 immediately leads to a timeout condition. + +=head1 RETURN VALUES + +BIO_socket_wait(), BIO_wait(), and BIO_connect_retry() +return -1 on error, 0 on timeout, and 1 on success. + +=head1 HISTORY + +BIO_socket_wait(), BIO_wait(), and BIO_connect_retry() +were added in OpenSSL 3.0. + +=head1 COPYRIGHT + +Copyright 2019-2020 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the Apache License 2.0 (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/include/internal/sockets.h b/include/internal/sockets.h index e444766dec..97ae2f6dcd 100644 --- a/include/internal/sockets.h +++ b/include/internal/sockets.h @@ -152,4 +152,11 @@ struct servent *PASCAL getservbyname(const char *, const char *); # define writesocket(s,b,n) write((s),(b),(n)) # endif +/* also in apps/include/apps.h */ +# if defined(OPENSSL_SYS_WIN32) || defined(OPENSSL_SYS_WINCE) +# define openssl_fdset(a,b) FD_SET((unsigned int)a, b) +# else +# define openssl_fdset(a,b) FD_SET(a, b) +# endif + #endif diff --git a/include/openssl/bio.h b/include/openssl/bio.h index 6b494a1026..1a06d72dc0 100644 --- a/include/openssl/bio.h +++ b/include/openssl/bio.h @@ -661,6 +661,9 @@ int BIO_dgram_sctp_msg_waiting(BIO *b); # ifndef OPENSSL_NO_SOCK int BIO_sock_should_retry(int i); int BIO_sock_non_fatal_error(int error); +int BIO_socket_wait(int fd, int for_read, time_t max_time); +int BIO_wait(BIO *bio, time_t max_time); +int BIO_connect_retry(BIO *bio, int timeout); # endif int BIO_fd_should_retry(int i); diff --git a/include/openssl/bioerr.h b/include/openssl/bioerr.h index 46e1840700..95cc05651f 100644 --- a/include/openssl/bioerr.h +++ b/include/openssl/bioerr.h @@ -97,6 +97,7 @@ int ERR_load_BIO_strings(void); # define BIO_R_BAD_FOPEN_MODE 101 # define BIO_R_BROKEN_PIPE 124 # define BIO_R_CONNECT_ERROR 103 +# define BIO_R_CONNECT_TIMEOUT 147 # define BIO_R_GETHOSTBYNAME_ADDR_IS_NOT_AF_INET 107 # define BIO_R_GETSOCKNAME_ERROR 132 # define BIO_R_GETSOCKNAME_TRUNCATED_ADDRESS 133 @@ -114,6 +115,8 @@ int ERR_load_BIO_strings(void); # define BIO_R_NO_PORT_DEFINED 113 # define BIO_R_NO_SUCH_FILE 128 # define BIO_R_NULL_PARAMETER 115 +# define BIO_R_TRANSFER_ERROR 104 +# define BIO_R_TRANSFER_TIMEOUT 105 # define BIO_R_UNABLE_TO_BIND_SOCKET 117 # define BIO_R_UNABLE_TO_CREATE_SOCKET 118 # define BIO_R_UNABLE_TO_KEEPALIVE 137 diff --git a/util/libcrypto.num b/util/libcrypto.num index 777db89d9f..e648370dd8 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -4920,3 +4920,6 @@ EVP_PKEY_pairwise_check ? 3_0_0 EXIST::FUNCTION: ASN1_item_verify_ctx ? 3_0_0 EXIST::FUNCTION: RAND_DRBG_set_callback_data ? 3_0_0 EXIST::FUNCTION: RAND_DRBG_get_callback_data ? 3_0_0 EXIST::FUNCTION: +BIO_wait ? 3_0_0 EXIST::FUNCTION:SOCK +BIO_socket_wait ? 3_0_0 EXIST::FUNCTION:SOCK +BIO_connect_retry ? 3_0_0 EXIST::FUNCTION:SOCK -- 2.34.1