SSL_shutdown返回-1,SSL_ERROR_WANT_READ无限长

时间:2015-10-06 14:08:09

标签: c++ openssl nonblocking

我无法理解如何在OpenSSL中正确使用SSL_Shutdown命令。类似的问题在不同的地方出现了好几次,但我找不到符合我情况的解决方案。我在VirtualBox的Ubuntu下使用了包libssl-dev 1.0.1f-1ubuntu2.15(现在最新)。

我正在使用OpenSSL库上的一个小型遗留C ++包装器,其中包含用于服务器和客户端套接字的非阻塞IO。包装似乎工作正常,除了在下面的测试用例中(我不提供单元测试本身的代码,因为它包含许多与问题无关的代码):

  1. 使用自签名证书初始化服务器套接字
  2. 连接到该套接字。 SSL握手成功完成,但我暂时忽略了SSL_get_verify_result的X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT返回。
  3. 通过连接成功发送/接收一些数据。此步骤是可选的,它不会影响后面的问题。我提到它只是为了表明连接已经建立并设置为正确的状态。
  4. 试图关闭导致无限等待选择的SSL连接(服务器或客户端,无关紧要)。
  5. 对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。我很抱歉我的英语错误,如果有人纠正我的语言,我将不胜感激。

0 个答案:

没有答案