我使用带有阻塞套接字的 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);
}
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;
}
//.....
}