add BIO_socket_wait(), BIO_wait(), and BIO_connect_retry() improving timeout support
authorDr. David von Oheimb <David.von.Oheimb@siemens.com>
Tue, 4 Feb 2020 08:55:35 +0000 (09:55 +0100)
committerDr. David von Oheimb <David.von.Oheimb@siemens.com>
Mon, 10 Feb 2020 15:49:01 +0000 (16:49 +0100)
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)

crypto/bio/b_sock.c
crypto/bio/bio_err.c
crypto/err/openssl.txt
doc/man3/BIO_set_callback.pod
doc/man3/BIO_socket_wait.pod [new file with mode: 0644]
include/internal/sockets.h
include/openssl/bio.h
include/openssl/bioerr.h
util/libcrypto.num

index 78bcffd..966bd64 100644 (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
 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) */
index 178fdd6..b2a3d8e 100644 (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),
index b59c8ba..84a8adc 100644 (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
index 9537a2e..dd3aa7b 100644 (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:
 
diff --git a/doc/man3/BIO_socket_wait.pod b/doc/man3/BIO_socket_wait.pod
new file mode 100644 (file)
index 0000000..8453892
--- /dev/null
@@ -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
index e444766..97ae2f6 100644 (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
index 6b494a1..1a06d72 100644 (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);
index 46e1840..95cc056 100644 (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
index 777db89..e648370 100644 (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