简体   繁体   中英

How to set a timeout for BIO_do_connect?

I found a SSL/TLS client example here , it works well.

#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>

void connect(const char* host, int port) {
    BIO* sbio, * out;
    int len;
    char tmpbuf[1024];
    SSL_CTX* ctx;
    SSL* ssl;
    char server[200];
    snprintf(server, sizeof(server), "%s:%d", host, port);
    /* XXX Seed the PRNG if needed. */
    ctx = SSL_CTX_new(TLS_client_method());
    /* XXX Set verify paths and mode here. */
    sbio = BIO_new_ssl_connect(ctx);
    BIO_get_ssl(sbio, &ssl);
    if (ssl == NULL) {
        fprintf(stderr, "Can't locate SSL pointer\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    /* Don't want any retries */
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
    /* XXX We might want to do other things with ssl here */
    /* An empty host part means the loopback address */
    BIO_set_conn_hostname(sbio, server);
    out = BIO_new_fp(stdout, BIO_NOCLOSE);
    if (BIO_do_connect(sbio) <= 0) {
        fprintf(stderr, "Error connecting to server\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    int ret = 0;
    if ((ret = BIO_do_handshake(sbio)) <= 0) {
        fprintf(stderr, "Error establishing SSL connection\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    /* XXX Could examine ssl here to get connection info */
    BIO_puts(sbio, "Hi, this message is from client c++");
    for (;;) {
        len = BIO_read(sbio, tmpbuf, 1024);
        if (len <= 0) {
            break;
        }
        BIO_write(out, tmpbuf, len);
    }
    BIO_free_all(sbio);
    BIO_free(out);
}
int main() {
    connect("127.0.0.1", 5555);
}

but i need to set a timeout for this connection. then i found How to set connection timeout and operation timeout in OpenSSL . so i change the codes

    if (BIO_do_connect(sbio) <= 0) {
        fprintf(stderr, "Error connecting to server\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }

to

    {
        BIO_set_nbio(sbio, 1);
        if (1 > BIO_do_connect(sbio)) {
            if (!BIO_should_retry(sbio)) {
                fprintf(stderr, "Error: should not retry\n");
                ERR_print_errors_fp(stderr);
                exit(1);
            }
            int fdSocket = 0;
            if (BIO_get_fd(sbio, &fdSocket) < 0) {
                fprintf(stderr, "Error: can not get socket\n");
                ERR_print_errors_fp(stderr);
                exit(1);
            }
            struct timeval timeout;
            fd_set connectionfds;
            FD_ZERO(&connectionfds);
            FD_SET(fdSocket, &connectionfds);
            timeout.tv_usec = 0;
            timeout.tv_sec = 4;
            if (0 == select(fdSocket + 1, NULL, &connectionfds, NULL, &timeout)) {
                fprintf(stderr, "Error: timeout\n");
                ERR_print_errors_fp(stderr);
                exit(1);
            }
        }
    }

now BIO_do_handshake returns -1 and the program exits. 在此处输入图像描述

How can i set a timeout correctly for my ssl connection?

Please give me some advice! help me!

I would go about this in two steps:

  1. I would deal with connection setup on my own. That way you can use non-blocking socket, connect(2) and select(2) and have complete control over timing of this part.
  2. I would also implement by own BIO. You can use an existing BIO and only implement read , write and puts methods. This will allow you to control socket accesses.

With this in place you can have total control over how much time you spend. You can implement different timeouts for session setup, renegotiation, normal operation...

The problem with BIO_set_nbio is that you set I/O to non blocking mode. So you have to process further steps in non blocking mode. I made an example how to process the request with sleep and non blocking mode. Maybe it is a bit ugly. But it worked for me.

#include <openssl/err.h>
#include <openssl/ssl.h>

#include <unistd.h>
#include <stdio.h>


void connect(const char* host, int port) {
    const long timeout_nsec = 4 * (long)1000000000, dt_nsec = 100000;

    char tmpbuf[1024];

    char server[200];
    snprintf(server, sizeof(server), "%s:%d", host, port);
    
    struct timespec dt;
    dt.tv_sec = 0;
    dt.tv_nsec = dt_nsec;

    /* XXX Seed the PRNG if needed. */
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
    /* XXX Set verify paths and mode here. */
    BIO *sbio = BIO_new_ssl_connect(ctx);

    SSL* ssl = nullptr;
    BIO_get_ssl(sbio, &ssl);
    if (ssl == NULL) {
        fprintf(stderr, "Can't locate SSL pointer\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    /* Don't want any retries */
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
    /* XXX We might want to do other things with ssl here */
    /* An empty host part means the loopback address */
    BIO_set_conn_hostname(sbio, server);
    BIO *out = BIO_new_fp(stdout, BIO_NOCLOSE);
    
    BIO_set_nbio(sbio, 1);

    {
        long time_remained = timeout_nsec;
        while(1) {
            int res = BIO_do_connect(sbio);
            if (res <= 0 && BIO_should_retry(sbio)) {
                clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &dt, NULL);
                time_remained -= dt_nsec;
                if (time_remained <= 0) {
                    fprintf(stderr, "Timeout\n");
                    exit(1);
                }
                continue;
            }
            if (res <= 0) {
                fprintf(stderr, "BIO_do_connect error\n");
                ERR_print_errors_fp(stderr);
                exit(1);
            }
            break;
        }
    }

    {
        long time_remained = timeout_nsec;
        while(1) {
            int res = BIO_do_handshake(sbio);
            if (res <= 0 && BIO_should_retry(sbio)) {
                clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &dt, NULL);
                time_remained -= dt_nsec;
                if (time_remained <= 0) {
                    fprintf(stderr, "Timeout\n");
                    exit(1);
                }
                continue;
            }
            if (res <= 0) {
                fprintf(stderr, "BIO_do_handshake error\n");
                ERR_print_errors_fp(stderr);
                exit(1);
            }
            break;
        }
    }

    /* XXX Could examine ssl here to get connection info */
    int a = BIO_puts(sbio, "Hi, this message is from client c++");
    for (;;) {
        int len = -1;
        {
            long time_remained = timeout_nsec;
            while(1) {
                len = BIO_read(sbio, tmpbuf, 1024);
                if (len < 0 && BIO_should_retry(sbio)) {
                    clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &dt, NULL);
                    time_remained -= dt_nsec;
                    if (time_remained <= 0) {
                        fprintf(stderr, "Timeout\n");
                        exit(1);
                    }
                    continue;
                }
                if (len < 0) {
                    fprintf(stderr, "BIO_read error\n");
                    ERR_print_errors_fp(stderr);
                    exit(1);
                }
                break;
            }
        }
        if (len == 0) {
            break;
        }
        BIO_write(out, tmpbuf, len);
    }

    BIO_free_all(sbio);
    BIO_free(out);
}

int main() {
    connect("127.0.0.1", 5555);
}

I think you should set a timeout for handshake, not connection. in your code the connection has no problem because "select" returned non-zero value. in fact BIO_do_connect does handshake after connection is available. BIO_do_connect and BIO_do_handshake are the same in header file.

#  define BIO_do_connect(b)       BIO_do_handshake(b)

So i think this problem is handshake. eg. you connect to a server which uses a normal tcp socket without ssl. the server will not send "server_hallo" and certificate. then the client will wait for these "server_hallo" and certificate. BIO_do_handshake returns -1 if the handshake progress is still not finished. maybe you can use BIO_set_ssl_renegotiate_timeout to set a timeout.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM