How to use BIO_do_connect(), blocking and non-blocking with timeout, coping with errors

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

How to use BIO_do_connect(), blocking and non-blocking with timeout, coping with errors

David von Oheimb
Hi all,

I'm currently enhancing HTTP(S) clients based on OpenSSL in several
flavors, in particular a CMP client, which in turn uses simple HTTP
clients for contacting CRL distribution points or OCSP responders.

Getting the client connect right appears surprisingly messy when one
needs to cope with all kinds of network error situations including
domain name resolution issues and temporarily unreachable servers.
Both indefinitely blocking and non-blocking behavior (i.e., connection
attempts with and without a timeout) should be supported.

Since these are pretty general problems I wonder why there there is
rather limited support via generic higher-level OpenSSL or C library
functions, or at least I was unable to find it. Instead, the OpenSSL
apps contain code that calls BIO_do_connect directly (or the equivalent
BIO_do_handshake), in particular query_responder() in apps/ocsp.c.
(The situation is similar for the subsequent exchange of data via the
BIO, optionally with a timeout).

So I constructed my own abstraction, called bio_connect, which took
quite some effort testing network error situations. Please see below its
code including comments on some strange behavior I experienced and my
workarounds for that. Does this code make sense, or do I miss anything?

How about adding such a function for instance to crypto/bio/bio_lib.c?

BTW, my code uses a handy generic helper function, socket_wait, for
waiting for read/write form/to a socket, with a given timeout. Since
several instances of that pretty common code pattern using select() are
spread over the OpenSSL apps (and crypto lib), I suggest adding this
function to the library. Where would be a good place to put it?

Thanks,
        David

> /* returns -1 on error, 0 on timeout, 1 on success */
> int bio_connect(BIO *bio, int timeout) {
>     int blocking;
>     time_t max_time;
>     int rv;
>
>     blocking = timeout == 0;
>     max_time = timeout != 0 ? time(NULL) + timeout : 0;
>
>     if (!blocking)
>         BIO_set_nbio(bio, 1);
>  retry:
>     rv = BIO_do_connect(bio);
>     if (rv <= 0 && (errno == ETIMEDOUT /* in blocking case,
>           despite blocking BIO, BIO_do_connect() timed out */ ||
>           ERR_GET_REASON(ERR_peek_error()) == ETIMEDOUT/* when non-blocking,
>           BIO_do_connect() timed out early with rv == -1 and errno == 0 */)) {
>         ERR_clear_error();
>         (void)BIO_reset(bio); /* otherwise, blocking next connect() may crash
>                              and non-blocking next BIO_do_connect() will fail */
>         goto retry;
>     }
>     if (!blocking && rv <= 0 && BIO_should_retry(bio)) {
>         int fd;
>         if (BIO_get_fd(bio, &fd) <= 0)
>             return -1;
>         rv = socket_wait(fd, 1, max_time - time(NULL));
>         if (rv > 0)
>             /* for some reason, select() may wrongly have returned success */
>             goto retry;
>     }
>     return rv;
> }

> /* returns < 0 on error, 0 on timeout, >0 on success */
> int socket_wait(int fd, int for_read, int timeout)
> {
>     fd_set confds;
>     struct timeval tv;
>
>     if (timeout <= 0)
>         return 0;
>
>     FD_ZERO(&confds);
>     openssl_fdset(fd, &confds);
>     tv.tv_usec = 0;
>     tv.tv_sec = timeout;
>     return select(fd + 1, for_read ? &confds : NULL,
>                   for_read ? NULL : &confds, NULL, &tv);
> }

--
openssl-dev mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-dev
Reply | Threaded
Open this post in threaded view
|

Re: How to use BIO_do_connect(), blocking and non-blocking with timeout, coping with errors

OpenSSL - Dev mailing list

    Getting the client connect right appears surprisingly messy when one
    needs to cope with all kinds of network error situations including
    domain name resolution issues and temporarily unreachable servers.
    Both indefinitely blocking and non-blocking behavior (i.e., connection
    attempts with and without a timeout) should be supported.
   

It is a complicated issue and hard to get right for all definitions of right for all applications ☺

A set of API’s that set up all the TLS “metadata”, and took a connected socket might be a way through the maze.  For example:
    SSL *SSL_connection(int socket, const char *servername, …whatever…)


--
openssl-dev mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-dev
Reply | Threaded
Open this post in threaded view
|

Re: How to use BIO_do_connect(), blocking and non-blocking with timeout, coping with errors

David von Oheimb
On 29.08.2017 16:15, Salz, Rich via openssl-dev wrote:
    Getting the client connect right appears surprisingly messy when one
    needs to cope with all kinds of network error situations including
    domain name resolution issues and temporarily unreachable servers.
    Both indefinitely blocking and non-blocking behavior (i.e., connection
    attempts with and without a timeout) should be supported.
It is a complicated issue and hard to get right for all definitions of right for all applications ☺
Hmm - on the one hand, good to get confirmation that I did not just miss a simple way of out the maze, ...
A set of API’s that set up all the TLS “metadata”, and took a connected socket might be a way through the maze.  For example:
    SSL *SSL_connection(int socket, const char *servername, …whatever…)
... on the other hand, it's a pity that such a high-level API does not (yet) exist, at least not in OpenSSL.

How about adding at least some clearly useful abstractions like the below socket_wait() function,
which would reduce code duplication in the OpenSSL crypto lib and apps and help application developers?

Maybe other OpenSSL users have specific experience on error and timeout handling for BIO_do_connect() etc.
and can comment in more detail on the (approximate) solution, bio_connect(), that I gave below?

On 28.08.2017 13:46, David von Oheimb wrote:
Hi all,

I'm currently enhancing HTTP(S) clients based on OpenSSL in several
flavors, in particular a CMP client, which in turn uses simple HTTP
clients for contacting CRL distribution points or OCSP responders.

Getting the client connect right appears surprisingly messy when one
needs to cope with all kinds of network error situations including
domain name resolution issues and temporarily unreachable servers.
Both indefinitely blocking and non-blocking behavior (i.e., connection
attempts with and without a timeout) should be supported.

Since these are pretty general problems I wonder why there there is
rather limited support via generic higher-level OpenSSL or C library
functions, or at least I was unable to find it. Instead, the OpenSSL
apps contain code that calls BIO_do_connect directly (or the equivalent
BIO_do_handshake), in particular query_responder() in apps/ocsp.c.
(The situation is similar for the subsequent exchange of data via the
BIO, optionally with a timeout).

So I constructed my own abstraction, called bio_connect, which took
quite some effort testing network error situations. Please see below its
code including comments on some strange behavior I experienced and my
workarounds for that. Does this code make sense, or do I miss anything?

How about adding such a function for instance to crypto/bio/bio_lib.c?

BTW, my code uses a handy generic helper function, socket_wait, for
waiting for read/write form/to a socket, with a given timeout. Since
several instances of that pretty common code pattern using select() are
spread over the OpenSSL apps (and crypto lib), I suggest adding this
function to the library. Where would be a good place to put it?

Thanks,
	David
/* returns -1 on error, 0 on timeout, 1 on success */
int bio_connect(BIO *bio, int timeout) {
    int blocking;
    time_t max_time;
    int rv;

    blocking = timeout == 0;
    max_time = timeout != 0 ? time(NULL) + timeout : 0;

    if (!blocking)
        BIO_set_nbio(bio, 1);
 retry:
    rv = BIO_do_connect(bio);
    if (rv <= 0 && (errno == ETIMEDOUT /* in blocking case,
          despite blocking BIO, BIO_do_connect() timed out */ ||
          ERR_GET_REASON(ERR_peek_error()) == ETIMEDOUT/* when non-blocking,
          BIO_do_connect() timed out early with rv == -1 and errno == 0 */)) {
        ERR_clear_error();
        (void)BIO_reset(bio); /* otherwise, blocking next connect() may crash
                             and non-blocking next BIO_do_connect() will fail */
        goto retry;
    }
    if (!blocking && rv <= 0 && BIO_should_retry(bio)) {
        int fd;
        if (BIO_get_fd(bio, &fd) <= 0)
            return -1;
        rv = socket_wait(fd, 1, max_time - time(NULL));
        if (rv > 0)
            /* for some reason, select() may wrongly have returned success */
            goto retry;
    }
    return rv;
}
/* returns < 0 on error, 0 on timeout, >0 on success */
int socket_wait(int fd, int for_read, int timeout)
{
    fd_set confds;
    struct timeval tv;

    if (timeout <= 0)
        return 0;

    FD_ZERO(&confds);
    openssl_fdset(fd, &confds);
    tv.tv_usec = 0;
    tv.tv_sec = timeout;
    return select(fd + 1, for_read ? &confds : NULL,
                  for_read ? NULL : &confds, NULL, &tv);
}

    

--
openssl-dev mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-dev
Reply | Threaded
Open this post in threaded view
|

Re: How to use BIO_do_connect(), blocking and non-blocking with timeout, coping with errors

David von Oheimb
[ Further below I quote my first two messages including my original questions and tentative code,
 since Cc'ing to openssl-users did not work when I tried first. In this way I hope to get further,
 more detailed responses by people with specific experience on the issues I mentioned,
 possibly even concrete feedback how to enhance my code or where to find a better solution. ]


On 09/01/2017 06:32 PM, Salz, Rich via openssl-users wrote:

FWIW, there’s a ‘libtls’ library from the libre folks that might be worth looking at.

This looks very nice. Yet is this of any practical benefit when using OpenSSL?

If you come up with useful snippets we can start by posting them to the wiki, for example

Which wiki do you mean? I could not find anything related on https://wiki.openssl.org/

Anyway, most people (including me) would not search through wikis for finding useful code,
and it would be much more useful if code like the bio_connect() function I mentioned below
was readily available within the OpenSSL libraries, in an official high-level API.
[ Since this is worth a topic of its own, I'll write more on this in my next email. ]

More low-level code that is already used by the crypto lib itself (e.g., using select() in rand_unix.c)
would be better packaged into abstractions within that library, for instance the socket_wait() function
for waiting on a socket with a timeout that I proposed below. I'd contribute pull requests for those I'm aware of.


On 29.08.2017 16:15, Salz, Rich via openssl-dev wrote:
    Getting the client connect right appears surprisingly messy when one
    needs to cope with all kinds of network error situations including
    domain name resolution issues and temporarily unreachable servers.
    Both indefinitely blocking and non-blocking behavior (i.e., connection
    attempts with and without a timeout) should be supported.
It is a complicated issue and hard to get right for all definitions of right for all applications ☺
Hmm - on the one hand, good to get confirmation that I did not just miss a simple way of out the maze, ...
A set of API’s that set up all the TLS “metadata”, and took a connected socket might be a way through the maze.  For example:
    SSL *SSL_connection(int socket, const char *servername, …whatever…)
... on the other hand, it's a pity that such a high-level API does not (yet) exist, at least not in OpenSSL.

How about adding at least some clearly useful abstractions like the below socket_wait() function,
which would reduce code duplication in the OpenSSL crypto lib and apps and help application developers?

Maybe other OpenSSL users have specific experience on error and timeout handling for BIO_do_connect() etc.
and can comment in more detail on the (approximate) solution, bio_connect(), that I gave below?

On 28.08.2017 13:46, David von Oheimb wrote:
Hi all,

I'm currently enhancing HTTP(S) clients based on OpenSSL in several
flavors, in particular a CMP client, which in turn uses simple HTTP
clients for contacting CRL distribution points or OCSP responders.

Getting the client connect right appears surprisingly messy when one
needs to cope with all kinds of network error situations including
domain name resolution issues and temporarily unreachable servers.
Both indefinitely blocking and non-blocking behavior (i.e., connection
attempts with and without a timeout) should be supported.

Since these are pretty general problems I wonder why there there is
rather limited support via generic higher-level OpenSSL or C library
functions, or at least I was unable to find it. Instead, the OpenSSL
apps contain code that calls BIO_do_connect directly (or the equivalent
BIO_do_handshake), in particular query_responder() in apps/ocsp.c.
(The situation is similar for the subsequent exchange of data via the
BIO, optionally with a timeout).

So I constructed my own abstraction, called bio_connect, which took
quite some effort testing network error situations. Please see below its
code including comments on some strange behavior I experienced and my
workarounds for that. Does this code make sense, or do I miss anything?

How about adding such a function for instance to crypto/bio/bio_lib.c?

BTW, my code uses a handy generic helper function, socket_wait, for
waiting for read/write form/to a socket, with a given timeout. Since
several instances of that pretty common code pattern using select() are
spread over the OpenSSL apps (and crypto lib), I suggest adding this
function to the library. Where would be a good place to put it?

Thanks,
	David
/* returns -1 on error, 0 on timeout, 1 on success */
int bio_connect(BIO *bio, int timeout) {
    int blocking;
    time_t max_time;
    int rv;

    blocking = timeout == 0;
    max_time = timeout != 0 ? time(NULL) + timeout : 0;

    if (!blocking)
        BIO_set_nbio(bio, 1);
 retry:
    rv = BIO_do_connect(bio);
    if (rv <= 0 && (errno == ETIMEDOUT /* in blocking case,
          despite blocking BIO, BIO_do_connect() timed out */ ||
          ERR_GET_REASON(ERR_peek_error()) == ETIMEDOUT/* when non-blocking,
          BIO_do_connect() timed out early with rv == -1 and errno == 0 */)) {
        ERR_clear_error();
        (void)BIO_reset(bio); /* otherwise, blocking next connect() may crash
                             and non-blocking next BIO_do_connect() will fail */
        goto retry;
    }
    if (!blocking && rv <= 0 && BIO_should_retry(bio)) {
        int fd;
        if (BIO_get_fd(bio, &fd) <= 0)
            return -1;
        rv = socket_wait(fd, 1, max_time - time(NULL));
        if (rv > 0)
            /* for some reason, select() may wrongly have returned success */
            goto retry;
    }
    return rv;
}
/* returns < 0 on error, 0 on timeout, >0 on success */
int socket_wait(int fd, int for_read, int timeout)
{
    fd_set confds;
    struct timeval tv;

    if (timeout <= 0)
        return 0;

    FD_ZERO(&confds);
    openssl_fdset(fd, &confds);
    tv.tv_usec = 0;
    tv.tv_sec = timeout;
    return select(fd + 1, for_read ? &confds : NULL,
                  for_read ? NULL : &confds, NULL, &tv);
}


--
openssl-dev mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-dev