带有阻塞套接字的BIO_read返回SSL_ERROR_NONE。是什么原因造成的?

问题描述 投票:0回答:1

我使用带有阻塞套接字的 OpenSSL Bio 接口来创建 TCP SSL 连接。

多次成功发送/读取后...

BIO_write()
成功了。我立即开始等待响应,但
BIO_read()
失败了。我使用 OpenSSL 枚举处理返回值,但它映射到
SSL_ERROR_NONE

鉴于它是一个阻塞套接字,这是客户端必须关闭连接的唯一解释吗?在这种情况下,客户端关闭连接没有多大意义。还有其他可能性或方法可以确定确切原因吗?

代码如下:

std::optional<Response> sendThenRecv()
{
    // .....

    if(BIO_write(bio_, write_buf, strlen(write_buf)) <= 0)
    {
        return std::nullopt;
    }

    const int res = BIO_read(bio_, read_buf, read_size);

    if(res <= 0)
    {
        LOG("BIO_read() failed. errno details: " << GetSSLError(res));
        return std::nullopt;
    }

    //.....
}

static inline std::string GetSSLError(const int reason)
{
    switch(reason)
    {
        case 0:
            return "SSL_ERROR_NONE";

        case 1:
            return "SSL_ERROR_SSL";

        case 2:
            return "SSL_ERROR_WANT_READ";

        case 3:
            return "SSL_ERROR_WANT_WRITE";

        case 4:
            return "SSL_ERROR_WANT_X509_LOOKUP";

        case 5:
            return "SSL_ERROR_SYSCALL: " + std::string(strerror(errno));

        case 6:
            return "SSL_ERROR_ZERO_RETURN";

        case 7:
            return "SSL_ERROR_WANT_CONNECT";

        case 8:
            return "SSL_ERROR_WANT_ACCEPT";

        case 9:
            return "SSL_ERROR_WANT_ASYNC";

        case 10:
            return "SSL_ERROR_WANT_ASYNC_JOB";

        case 11:
            return "SSL_ERROR_WANT_CLIENT_HELLO_CB";

        case 12:
            return "SSL_ERROR_WANT_RETRY_VERIFY";
        default:
            MY_ASSERT(false);
    }

    return std::to_string(reason);
}
c++ ssl openssl network-programming tls1.2
1个回答
0
投票

BIO_read 手册页详细介绍了 BIO_read 的以下行为:

返回值: 所有这些函数要么返回成功读取或写入的数据量(如果返回值为正),要么返回没有成功读取或写入数据(如果结果为 0 或 -1)。如果返回值为-2,则该操作未在特定 BIO 类型中实现。

而且:

注意事项: 返回 0 或 -1 并不一定表示出现错误。特别是当源/接收器是非阻塞或某种类型时,它可能只是表明当前没有可用数据,并且应用程序应稍后重试该操作。

有关如何确定重试原因和其他 I/O 问题的详细信息,请参阅 BIO_should_retry(3)。

还有

如果 BIO_should_retry() 返回 false,则精确的“错误条件”取决于导致该错误的 BIO 类型以及 BIO 操作的返回代码。例如,如果在套接字 BIO 上调用 BIO_read_ex() 返回 0 并且 BIO_should_retry() 为 false,则原因将是连接关闭。文件 BIO 上的类似条件意味着它已达到 EOF。

所以,BIO_read的返回并不是错误本身,只是表示无法读取。要检查是否发生实际错误,需要使用 BIO_get_retry_error 或 SSL_get_error 并将 BIO_read 的返回代码作为参数,如果发生错误,则应用程序可以采取控制流决策。

BIO_get_retry_error 和 SSL_get_error 之间的选择取决于 BIO_read 使用的上下文。如果 BIO_read 用于 BIO 对象(不一定是 SSL/TLS 连接)上的一般 I/O 操作,则应使用 BIO_get_retry_error 来检查可恢复的错误。 根据您的情况,BIO_read 在 SSL/TLS 连接的上下文中使用,如果应用程序怀疑存在 SSL/TLS 相关错误,则可以使用 SSL_get_error 来检索特定的 SSL 错误代码。

BIO_read 返回 0 可能表示来自服务器的空回复,但也可能是 EOF,只有 SSL_get_error 和 BIO_get_retry_reason 可以返回实际错误。

也就是说,应用程序应该检查 BIO_read(和 BIO_write 以及其手册页上的指示)是否返回<= 0 and if so, then check what is the error to read/write with the appropriate error stack check and map over the possible error return codes to take a control flow action.

基于您工作的代码示例

std::optional<Response> sendThenRecv()
{
    // .....

    if(BIO_write(bio_, write_buf, strlen(write_buf)) <= 0)
    {
        return std::nullopt;
    }

    const int res = BIO_read(bio_, read_buf, read_size);

    if(res <= 0)
    {
        int ssl_ret = SSL_get_error(bio_, res);
        if(ssl_ret == SSL_ERROR_WANT_READ){
            LOG("BIO_read() failed. not fatal, retry later.");
        }
        else if(ssl_ret == SSL_ERROR_WANT_WRITE){
            LOG("BIO_read() failed. not fatal, retry later.");
        }
        else if(ssl_ret == SSL_ERROR_ZERO_RETURN){
            LOG("BIO_read() failed. connection closed by the server
.");
        }
        else if(ssl_ret == SSL_ERROR_SSL){
            // ony for OpenSSL >= 3.0, for < use SSL_ERROR_SYSCALL and errno 0
            LOG("BIO_read() failed. possible EOF on OpenSSL3.0.");
        }
        else if(ssl_ret == SSL_ERROR_NONE){
           LOG("BIO_read() failed. unsure of ssl error, may be an IO failure, check with BIO_get_retry_reason.");
        }
        else{
           LOG("BIO_read() failed. fatal error as not mapped as expected.");
        }
        return std::nullopt;
    }

    //.....
}

© www.soinside.com 2019 - 2024. All rights reserved.