add BIO_socket_wait(), BIO_wait(), and BIO_connect_retry() improving timeout support

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
(Merged from https://github.com/openssl/openssl/pull/10667)
This commit is contained in:
Dr. David von Oheimb 2020-02-04 09:55:35 +01:00
parent b0593c086d
commit bcbb30afe2
9 changed files with 195 additions and 3 deletions

View File

@ -9,7 +9,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#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 <unistd.h>
# include <sys/select.h>
# else
# include <winsock.h> /* 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) */

View File

@ -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),

View File

@ -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

View File

@ -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:

View File

@ -0,0 +1,53 @@
=pod
=head1 NAME
BIO_socket_wait,
BIO_wait,
BIO_connect_retry
- BIO socket utility functions
=head1 SYNOPSIS
#include <openssl/bio.h>
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<fd> for reading if B<for_read> is not 0,
else for writing, at most until B<max_time>.
It succeeds immediately if B<max_time> == 0 (which means no timeout given).
BIO_wait() waits on the socket underlying the given B<bio>, for reading if
B<bio> is supposed to read, else for writing, at most until B<max_time>.
It succeeds immediately if B<max_time> == 0 (which means no timeout given).
BIO_connect_retry() connects via the given B<bio>, retrying BIO_do_connect()
until success or a timeout or error condition is reached.
If the B<timeout> 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<https://www.openssl.org/source/license.html>.
=cut

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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