如何在非阻塞套接字上处理 OpenSSL SSL_ERROR_WANT_READ / WANT_WRITE

Posted

技术标签:

【中文标题】如何在非阻塞套接字上处理 OpenSSL SSL_ERROR_WANT_READ / WANT_WRITE【英文标题】:How to handle OpenSSL SSL_ERROR_WANT_READ / WANT_WRITE on non-blocking sockets 【发布时间】:2011-04-26 12:15:38 【问题描述】:

OpenSSL 库允许使用 SSL_read 从底层套接字读取并使用 SSL_write 写入它。这些函数可能会返回 SSL_ERROR_WANT_READ 或 SSL_ERROR_WANT_WRITE,具体取决于它们的 ssl 协议需求(例如在重新协商连接时)。

我真的不明白 API 要我对这些结果做什么。

想象一个接受客户端连接的服务器应用程序,设置一个新的 ssl 会话,使底层套接字非阻塞,然后将文件描述符添加到 select/poll/epoll 循环中。

如果客户端发送数据,主循环会将其分派给 ssl_read。如果返回 SSL_ERROR_WANT_READ 或 SSL_ERROR_WANT_WRITE,这里必须做什么? WANT_READ 可能很容易,因为下一次主循环迭代可能会导致另一个 ssl_read。但是如果 ssl_read 返回 WANT_WRITE,应该用什么参数调用它?为什么图书馆不自己发出调用?

如果服务器想向客户端发送一些数据,它将使用 ssl_write。同样,如果返回 WANT_READ 或 WANT_WRITE 该怎么办? WANT_WRITE 是否可以通过重复刚刚调用的相同调用来回答?如果返回 WANT_READ,是否应该返回主循环并让 select/poll/epoll 处理这个问题?但是首先应该写的信息呢?

还是应该在写入失败后立即进行读取?那么,当真正的解析器位于主循环中时,什么可以防止从应用程序协议读取字节然后不得不在应用程序外围的某个地方处理它?

【问题讨论】:

【参考方案1】:

对于非阻塞套接字,SSL_WANT_READ 表示“等待套接字可读,然后再次调用此函数。”;相反,SSL_WANT_WRITE 表示“等待套接字可写,然后再次调用此函数。”。您可以通过SSL_read()SSL_write() 调用获得SSL_WANT_WRITESSL_WANT_READ

【讨论】:

这里最好的解释【参考方案2】:

您是否阅读过 ssl_read 和 ssl_get_error 的 OpenSSL 文档?

ssl_read:

如果底层 BIO 阻塞, SSL_read() 只会返回,一旦 读操作已完成或 发生错误,除非出现 重新谈判发生,其中 情况下可能会发生 SSL_ERROR_WANT_READ。 这种行为可以控制 的 SSL_MODE_AUTO_RETRY 标志 SSL_CTX_set_mode(3) 调用。

如果底层 BIO 是非阻塞的, SSL_read() 也会在 底层 BIO 无法满足 SSL_read() 需要继续 手术。在这种情况下,调用 SSL_get_error(3) 与返回值 SSL_read() 将产生 SSL_ERROR_WANT_READ 或 SSL_ERROR_WANT_WRITE。就像任何时候一样 重新谈判是可能的,打电话给 SSL_read() 也可能导致写入 操作!然后调用过程 接听后必须重复通话 适当的行动来满足 SSL_read() 的需求。那个行动 取决于底层的 BIO。什么时候 使用非阻塞套接字,什么都没有 是要做的,但 select() 可以 用于检查所需的 条件。

ssl_get_error:

SSL_ERROR_WANT_READ、SSL_ERROR_WANT_WRITE

操作没有完成;这 相同的 TLS/SSL I/O 功能应该是 稍后再次调用。如果到那时, 底层 BIO 有数据可用于 阅读(如果结果代码是 SSL_ERROR_WANT_READ)或允许写入 数据(SSL_ERROR_WANT_WRITE),然后是一些 TLS/SSL 协议进展将需要 位置,即 TLS/SSL 的至少一部分 记录将被读取或写入。笔记 重试可能会再次导致 SSL_ERROR_WANT_READ 或 SSL_ERROR_WANT_WRITE 健康)状况。没有固定的上限 对于可能的迭代次数 有必要直到取得进展 在应用程序协议级别可见。

对于套接字 BIO(例如,当 SSL_set_fd() 已使用)、select() 或 poll() 底层socket可以用来查找 出时 TLS/SSL I/O 功能 应该重试。

警告:任何 TLS/SSL I/O 功能都可以 导致 SSL_ERROR_WANT_READ 和 SSL_ERROR_WANT_WRITE。特别是, SSL_read() 或 SSL_peek() 可能想要 写入数据和 SSL_write() 可能想要 读取数据。这主要是因为 TLS/SSL 握手可能发生在任何时间 协议期间的时间(由 客户端或服务器); SSL_read()、SSL_peek() 和 SSL_write() 将处理任何未决的握手。

OpenSSL 被实现为状态机。 SSL_ERROR_WANT_READ 表示需要更多入站数据,SSL_ERROR_WANT_WRITE 表示需要更多出站数据才能在连接上取得进展。如果在 ssl_read() 操作中得到SSL_ERROR_WANT_WRITE,则需要发送出站数据,或者至少等待套接字变为可写。如果在 ssl_write() 操作中得到SSL_ERROR_WANT_READ,则需要读取入站数据。

您应该订阅OpenSSL mailing lists。这个问题被问了很多。

【讨论】:

是的,我查看了文档。这就是把我带到这里的原因 :-) 我现在开始明白的是 SSL_WANT_READ 确实要求我发出 SSL_read() 也没有 SSL_WANT_WRITE 要求我调用 SSL_write(),而只是表明我当下面的套接字变为可读或可写时,应该重试刚刚返回的相同调用。因此,应该将它们解释为 SSL_WANTS_A_READABLE_SOCKET 和 SSL_WANTS_A_WRITEABLE_SOCKET。 是的。我认为说 SSL_ERROR_WANT_WRITE 意味着需要更多出站数据的答案是错误的。这意味着等到底层套接字变为可写。【参考方案3】:

SSL_WANT_READ 表示 SSL 引擎当前无法为您加密,因为它正在等待更多输入数据(作为初始握手的一部分或作为重新协商的一部分),因此,一旦您的下一次读取完成并且您已经推送了通过 SSL 引擎到达的数据,您可以重试写入操作。

同样,SSL_WANT_WRITE 表示 SSL 引擎正在等待您从中提取一些数据并将其发送给对等方。

早在 2002 年,我就在 Windows 开发者日志(重印 here)上写过关于使用带有非阻塞和异步套接字的 OpenSSL 的文章,尽管这篇文章表面上是针对 Windows 代码的,但其他平台的原理是相同的。这篇文章附带了一些代码,将 OpenSSL 与 Windows 上的异步套接字集成,并处理整个 SSL_WANT_READ/SSL_WANT_WRITE 问题。

基本上,当您收到 SSL_WANT_READ 时,您需要将出站数据排队,直到您完成读取并将新的入站数据传递到 SSL 引擎,一旦发生这种情况,您可以重试发送出站数据。

【讨论】:

好的,从 SSL_write() 返回的 SSL_WANT_READ 可以理解,我只是在应用程序中的某处将所有传出消息排队,直到 SSL_read() 成功,然后我再试一次。因此,如果我在缓冲时内存不足,我必须丢弃应用程序消息或丢弃该连接。现在,如果 SSL_read() 返回 SSL_WANT_WRITE 并且我没有任何数据,这意味着我必须执行零长度 SSL_write() 以满足该请求,然后才能再次成功读取? 使用 SSL_WANT_WRITE 您可能会发现您有需要从 BIO 中提取并发送到另一端的数据。据我所知,您不必通过 BIO 推送零字节写入来生成它。

以上是关于如何在非阻塞套接字上处理 OpenSSL SSL_ERROR_WANT_READ / WANT_WRITE的主要内容,如果未能解决你的问题,请参考以下文章

windows下在非阻塞TCP套接字上使用SO_SNDBUF的奇怪行为

C++ 中的 Bittorrent 客户端,在非阻塞套接字上连接到对等点总是超时

怎么查看openssl支持的编码

Linux之SSL安全套接字20160704

使用 OpenSSL BIO 的非阻塞 I/O

C中的OpenSSL-例程:ssl3_get_record:尝试使用TLS1_2连接到服务器时版本号错误