【问题标题】:Non-blocking BIO and hang after BIO_do_connect非阻塞 BIO 并在 BIO_do_connect 后挂起
【发布时间】:2016-08-20 21:02:33
【问题描述】:

我正在用 C 语言编写一个小的 IRC 机器人,使用 openssl 来启动一个安全套接字。它不是写得最漂亮的机器人,但主要是为了看看 openssl API 是如何工作的。目前我有以下代码:

#include <stdio.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

int main() {
    SSL_load_error_strings();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();

    BIO *bio;
    SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method());
    SSL * ssl;

    SSL_CTX_load_verify_locations(ctx, NULL, "/etc/ssl/certs/");
    bio = BIO_new_ssl_connect(ctx);
    BIO_get_ssl(bio, & ssl);
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
    BIO_set_nbio(bio, 1);
    BIO_set_conn_hostname(bio, "irc.freenode.net:6697");
    BIO_do_connect(bio);

    if(SSL_get_verify_result(ssl) != X509_V_OK) {
        printf("error\n");
    }

    char irc1[] = "NICK bartender\r\n";
    char irc2[] = "USER bartender * * :serve(&drinks);\r\n";

    BIO_write(bio, irc1, strlen(irc1));
    BIO_write(bio, irc2, strlen(irc2));

    fd_set read_set;
    int sock = BIO_get_fd(bio, NULL);

    while(1) {
        FD_ZERO(&read_set);
        FD_SET(sock, &read_set);

        struct timeval timeout = { 0, 0 };
        select(sock+1, &read_set, NULL, NULL, &timeout);

        if(FD_ISSET(sock, &read_set)) {
            char buf[21];
            size_t x = BIO_read(bio, buf, 20);

            if(x == 0) {
                continue;
            } else if(x == -1){
                int code = ERR_get_error();

                if(code == 0) {
                    continue;
                }

                printf("(%d)%s\n", code, ERR_error_string(code, NULL));
            } else {
                buf[x] = '\0';
                printf("%s", buf);
            }
        }
    }
}

每当我编译和运行这段代码时,它都会挂起并且什么也不打印。但是,如果我删除第 20 行(当前将套接字置于非阻塞模式),它工作正常。为什么将套接字置于非阻塞模式会导致这种行为?谢谢你,祝你有美好的一天!

【问题讨论】:

  • 您不能只是将任何套接字更改为 Jon 阻塞模式并期望相同的代码继续工作。还有更多工作要做,在 SSL 套接字的情况下还有很多工作要做。请参阅 OpenSSL 文档。太宽泛了。
  • 我看不出“将 BIO 对象置于非阻塞模式”是多么的宽泛。我确信还有更多工作要做,否则它会起作用。需要做什么?我已经阅读了 openssl 手册页,并咨询了 google 和以前的 SO 答案。我找不到其他需要做的事情。
  • 还有很多工作要做,所有这些都记录在 man select 和 OpenSSL 文档中,在这里无法回答。跨度>
  • 它不是。我已经完成了选择所需的一切。如果给出简短的答案,显然不会太宽泛。

标签: c ssl network-programming openssl


【解决方案1】:

每当我运行此代码时,它都会挂起并且什么也不打印。但是,如果我删除第 20 行(当前将套接字置于非阻塞模式),它工作正常。

BIO_do_connect 立即以非阻塞模式返回。你应该循环BIO_should_retry。以下是手册页中关于 BIO_do_connect 的内容:

BIO_do_connect() 尝试连接提供的 BIO。如果返回 1 连接已成功建立。零或负值 如果无法建立连接,则返回调用 BIO_should_retry() 应该用于非阻塞连接 BIO 到 确定是否应该重试调用。


为什么将套接字置于非阻塞模式会导致这种行为?

BIO_do_connect 的调用立即返回;套接字/bio 可能还没有准备好接收数据。


BIO_do_connect/BIO_should_retry 上循环的另一种方法是等待底层文件描述符。它是 OpenSSL 在 ocsp 子命令中使用的技术(源代码可以在 &lt;openssl src&gt;/apps/ocsp.c 中找到):

if (req_timeout != -1)
    BIO_set_nbio(cbio, 1);

rv = BIO_do_connect(cbio);

if ((rv <= 0) && ((req_timeout == -1) || !BIO_should_retry(cbio))) {
    BIO_puts(err, "Error connecting BIO\n");
    return NULL;
}

if (BIO_get_fd(cbio, &fd) < 0) {
    BIO_puts(bio_err, "Can't get connection fd\n");
    goto err;
}

if (req_timeout != -1 && rv <= 0) {
    FD_ZERO(&confds);
    openssl_fdset(fd, &confds);
    tv.tv_usec = 0;
    tv.tv_sec = req_timeout;
    rv = select(fd + 1, NULL, (void *)&confds, NULL, &tv);
    if (rv == 0) {
        BIO_puts(err, "Timeout on connect\n");
        return NULL;
    }
}

另请参阅 OpenSSL 用户邮件列表中的 Non-blocking BIO and BIO_do_connect problem。还有一个few hits on Stack Overflow,但我不确定哪个最适合这个问题:

【讨论】:

  • 您的代码与您的报价不一致。当BIO_should_retry() 为真时,您需要循环。在BIO_do_cnnect() 失败时循环肯定是不正确的。
  • @EJP - “BIO_do_cnnect() 失败时循环肯定是不正确的......” - 你应该熟悉 OpenSSL 实践。例如,请参阅 OpenSSL 邮件列表中的 Non-blocking BIO and BIO_do_connect problem。 Henson 博士是维护者之一,因此请特别注意他所说的话。
猜你喜欢
  • 1970-01-01
  • 2012-01-14
  • 1970-01-01
  • 1970-01-01
  • 2011-12-12
  • 1970-01-01
  • 2013-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多