我无法理解如何在OpenSSL中正确使用SSL_Shutdown命令。类似的问题在不同的地方出现了好几次,但我找不到符合我情况的解决方案。我在VirtualBox的Ubuntu下使用了包libssl-dev 1.0.1f-1ubuntu2.15(现在最新)。
我正在使用OpenSSL库上的一个小型遗留C ++包装器,其中包含用于服务器和客户端套接字的非阻塞IO。包装似乎工作正常,除了在下面的测试用例中(我不提供单元测试本身的代码,因为它包含许多与问题无关的代码):
对SSL_read和SSL_write的所有调用都是同步的,还设置了locking_callback。在第3步之后,除了关闭其中一个之外,套接字上没有其他操作。
在下面的代码片段中,为了清楚起见,我省略了所有错误处理和调试代码,没有一个OpenSSL / POSIX调用失败(除非我保留了错误处理的情况)。我还提供连接功能,以防这很重要:
void OpenSslWrapper::ConnectToHost( ErrorCode& ec )
{
ctx_ = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_load_verify_locations(ctx_, NULL, config_.verify_locations.c_str());
if (config_.use_standard_verify_locations)
{
SSL_CTX_set_default_verify_paths(ctx_);
}
bio_ = BIO_new_ssl_connect(ctx_);
BIO_get_ssl(bio_, &ssl_);
SSL_set_mode(ssl_, SSL_MODE_AUTO_RETRY);
std::string hostname = config_.address + ":" + to_string(config_.port);
BIO_set_conn_hostname(bio_, hostname.c_str());
BIO_set_nbio(bio_, 1);
int res = 0;
while ((res = BIO_do_connect(bio_)) <= 0)
{
BIO_get_fd(bio_, &fd_);
if (!BIO_should_retry(bio_))
{ /* Never happens */}
WaitAfterError(res);
}
res = SSL_get_verify_result(ssl_);
if (res != X509_V_OK && res != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)
{ /* Never happens */ }
SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_mode(ssl_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
}
// config_.handle is a file descriptor which was got from
// accept function, stx_ is also set in advance
void OpenSslWrapper::ConnectAsServer( ErrorCode& ec )
{
ssl_ = SSL_new(ctx_);
int flags = fcntl(config_.handle, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(config_.handle, F_SETFL, flags);
SSL_set_fd(ssl_, config_.handle);
while (true)
{
int res = SSL_accept(ssl_);
if( res > 0) {break;}
if( !WaitAfterError(res).isSucceded() )
{ /* never happens */ }
}
SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_mode(ssl_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
}
// The trouble is here
void OpenSSLWrapper::Shutdown()
{
// ...
while (true)
{
int ret = SSL_shutdown(ssl_);
if (ret > 0) {break;}
else if (ret == 0) {continue;}
else {WaitAfterError(ret);}
}
// ...
}
ErrorCode OpenSSLWrapper::WaitAfterError(int res)
{
int err = SSL_get_error(ssl_, res);
switch (ret)
{
case SSL_ERROR_WANT_READ:
WaitForFd(fd_, k_WaitRead);
return ErrorCode::Success;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
WaitForFd(fd_, k_WaitWrite);
return ErrorCode::Success;
default:
return ErrorCode::Fail;
}
}
WaitForFd只是select的一个简单包装器,它在指定的FD_SET上等待给定的套接字无限长以进行读或写。
当客户端调用Shutdown时,第一次调用SSL_Shutdown返回0.第二次调用后,它返回-1,SSL_get_error返回SSL_ERROR_WANT_READ,但选择文件描述符进行读取永远不会返回。如果在select上指定timeout,则SSL_Shutdown将继续返回-1并且SSL_get_error继续返回SSL_ERROR_WANT_READ。循环永不退出。在第一次调用SSL_Shutdown之后,关闭状态始终为SSL_SENT_SHUTDOWN。
如果我关闭服务器或客户端并不重要:两者都有相同的行为。
当我连接到某个外部主机时,也有一种奇怪的情况。第一次调用SSL_Shutdown返回0,第二次调用返回-1,返回SSL_ERROR_WANT_READ。在套接字上选择成功完成,但是当我下次调用SSL_Shutdown时,我再次得到-1,错误为SSL_ERROR_SYSCALL,errno = 0。正如我在其他地方读到的那样,这并不是什么大问题,虽然它看起来仍然很奇怪,也许它在某种程度上是相关的,所以我在这里提到它。
UPD。我移植了相同的Windows代码,行为没有改变。
P.S。我很抱歉我的英语错误,如果有人纠正我的语言,我将不胜感激。