使用 OpenSSL BIO 的非阻塞 I/O

Posted

技术标签:

【中文标题】使用 OpenSSL BIO 的非阻塞 I/O【英文标题】:Non-Blocking I/O With OpenSSL BIO 【发布时间】:2017-01-30 20:58:12 【问题描述】:

我在 Linux 上使用 OpenSSL 1.0.0-fips。我遇到的问题是SSL_connect() 返回-1,SSL_get_error() 返回SSL_ERROR_WANT_READ。然后我将文件描述符放入select() 中,timeval 结构设置为 10 秒,select() 只是超时。

我启动了 Wireshark,我看到“Client Hello”消失了,我看到 ServerHello 返回到客户端,但它从未在 select() 中“唤醒”。它只是超时。

我的问题是:

    是否必须使用 BIO_new_socket() 创建 BIO 对象,然后使用 SSL_set_bio() 将 BIO 对象分配给我的 SSL 对象? SSL_set_fd() 的手册页说它将自动创建一个 BIO 对象,因此这似乎暗示 SSL_set_bio() 是一种你永远不必真正调用的无用函数。

    假设我们使用SSL_set_fd() 并分配一个已连接的阻塞TCP 文件描述符。假设我们稍后使用fcntl() 将该文件描述符更改为非阻塞。这是否会破坏 SSL 对象(或底层 BIO 对象)?

【问题讨论】:

您可能应该展示一些代码,包括您如何设置上下文。异步 I/O 和非阻塞套接字给人们带来了相当多的麻烦,因为库不太适应。查看apps/ocsp.c 的源代码,了解 OpenSSL 是如何做到的。我似乎记得一个正常的上下文是在阻塞模式下设置的,然后使用底层套接字切换到非阻塞模式。搜索调用BIO_get_fdselect 的代码。 AFAIK,它是自记录代码中非阻塞 I/O 的唯一示例 :) 我没有发布代码,因为我实现的 C++ SSL Socket 类的整个连接函数非常大。但是,我发现了我的问题。我忘记在 select() 的 maxfd 参数中加 1。 【参考方案1】:

1) 我是否必须使用 BIO_new_socket() 创建一个 BIO 对象,然后 使用 SSL_set_bio() 将 BIO 对象分配给我的 SSL 对象?男人 SSL_set_fd() 的页面说它会自动创建一个 BIO 对象 所以这似乎暗示 SSL_set_bio() 有点没用 你永远不需要调用的函数。

如果默认 BIO 对象足以满足您的需求,那么您不必手动创建和安装自己的 BIO 对象。 SSL_set_bio() 调用只是为了以防您想创建/使用与 SSL_set_fd() 代表您创建的默认对象不同的 BIO 对象。

2) 假设我们使用 SSL_set_fd() 并分配一个连接的 TCP 文件 阻塞的描述符。让我们说我们稍后会改变它 使用 fcntl() 将文件描述符变为非阻塞。这会破坏 SSL 对象(或底层 BIO 对象)?

是的,我相信它会坏掉。非阻塞 OpenSSL 的调用模式与阻塞 OpenSSL 的调用模式非常不同,我不相信您可以即时从一种模式来回切换到另一种模式。也就是说,我自己没有尝试过,所以我可能是错的,但我认为为了安全起见(并且保持一致),您应该预先选择是否要使用阻塞或非阻塞 I/ O 并在连接期间坚持使用它。

特别是手册页中的这句话:

BIO 和 SSL 引擎继承 fd 的行为。

...建议 SSL 设置调用将检查您的 fd 的状态,并根据 fd 的阻塞/非阻塞状态在 BIO 和 SSL 对象中设置私有变量。如果您随后“背着 OpenSSL”改变 fd 的行为,OpenSSL 的例程将不会预料到这一点,并且很可能会做错事而无法正常工作。

【讨论】:

因此,我在 C++ SSL Socket 类中编写的连接函数使用 SSL_set_fd() 将文件描述符分配给 SSL 对象,然后使用 fnctl( ) 它工作得很好。我什至可以将其切换回阻塞状态,SSL 层的行为与我期望的一样。因此,该文档似乎具有误导性,或者它按我想要的方式工作可能只是运气。

以上是关于使用 OpenSSL BIO 的非阻塞 I/O的主要内容,如果未能解决你的问题,请参考以下文章

NIO:异步非阻塞I/O,AIO,BIO

java网络通信:同步阻塞式I/O模型(BIO)

java BIO/NIO/AIO 学习

使用 asyncio 的非阻塞 I/O

一站式学习Java网络编程 全面理解BIO/NIO/AIO

大白话五种IO模型