成功握手后SSL_read()不返回

时间:2019-10-03 12:08:26

标签: c++ c sockets select openssl

我创建了一个服务器和一个客户端,它们在安全的TLSv1.3,OpenSSL 3.0.0通道中进行通信。它们必须能够发送和接收,但是在此测试中,服务器仅接收,而客户端仅定期发送应用程序数据。

握手成功后,客户端select()发出信号,表明有可供读取的数据。当我尝试这样做时,SSL_read()不会返回。我检查了Wireshark中的网络流量,并调试了OpenSSL库,以发现服务器握手后向客户端发送了2个 SESSION_TICKET ,这就是消息SSL_read()失败处理。出于好奇,我在握手后立即与服务器发送了一条消息,以查看客户端对此有何反应。令人惊讶的是,客户端离开了阻塞状态,此后通信正常。

由于观察到的行为,我唯一的猜测是我尝试读取供OpenSSL使用的数据,当我调用SSL_read()时,该数据可能由其状态机处理。当库调用read()时,实际上没有可用的应用程序数据,因此它会阻塞线程。

我用来读取“计数”字节的成员函数如下所示。

int EventHandler::readByte(CommBuffer &buffer, ssize_t count, struct timeval &tmout){
  fd_set rfds;
  int retSelect, retRead;
  int readByte=0;
  int endRead=0;
  int readSize=count;

  buffer.setBufferSize(count+1);
  do{
    FD_ZERO(&rfds);
    FD_SET(fd,&rfds);
    retSelect=select(fd+1,&rfds,NULL,NULL,&tmout);
    if(retSelect>0){
      if (encryptionOn) {
        retRead = SSL_read(ssl, (const_cast<char*>(&buffer.get()[readByte])), readSize); // Thread blocks here.
      } else {
        retRead = read(fd, (const_cast<char*>(&buffer.get()[readByte])), readSize);
      }
      if( retRead > 0 ){
        readByte+=retRead;
        allReadBytes+=retRead;
        buffer.setDataSize(readByte);
        if(readByte>=count) endRead=1;
        readSize=count-readByte;
      }
      else{
        if( retRead == -1 && errno == EINTR  ){
          continue;
        }
        if(fd>=0){
          close(fd);
        }
        fd=CLOSED_FD;
        return (CLOSED_FD);
      }
    }
    else if(retSelect==0){
      // handle timeout error
      return(TIMEOUT_FD);
    }
    else{
      return(retSelect);
    }
  }while(endRead!=1);
  return(readByte);
} 

请注意,此功能无需加密即可正常工作。 ({read()而不是SSL_read()

  • 我在做什么错了?
  • 我应该如何读取数据?
  • 您将如何修改此功能?

2 个答案:

答案 0 :(得分:1)

select在TCP级别工作,而SSL_read在TLS级别工作。通常,只有在应用程序数据可用时,阻塞套接字上的SSL_read才会返回,而套接字上的任何类型的数据可用时,select都会发出信号,即也用于非应用程序记录或不完整的SSL帧

在您的特定情况下,这些数据是会话票证,使用TLS 1.3的会话票证不再作为TLS握手的一部分发送,而是在握手之后发送。但是,对于较低的TLS版本,即使SSL_read返回了可用数据,select也会被阻塞:TLS中的应用程序数据在SSL帧内发送,并且可能发生select发出信号即使只有部分SSL帧可用,也可以使用可用数据。在这种情况下,SSL_read将阻塞直到可以读取完整的SSL帧。此外,即使SSL_read发出可用数据信号,select也会阻止SSL重新协商。

此外,可能发生select不会发出可用数据的信号,但是SSL_read会返回一些数据。以前的SSL_read并没有消耗掉已经从TCP套接字读取的最后一个SSL帧中包含的所有数据时,就是这种情况。

换句话说:select不应与SSL中的阻塞套接字一起使用。相反,应将套接字设为非阻塞套接字,以便SSL_readSSL_ERROR_WANT_READ可能因记录而处理的SSL_ERROR_WANT_WRITESSL_pending失败。另外,应调用SSL_read来检查是否已从TCP套接字读取但import ntc_templates from ntc_templates.parse import parse_output vlan_output = ( "VLAN Name Status Ports\n" "---- -------------------------------- --------- -------------------------------\n" "1 default active Po1, Eth1/1, Eth1/2, Eth1/3\n" " Eth1/4, Eth1/5, Eth1/6, Eth1/7\n" " Eth1/8, Eth1/9, Eth1/10, Eth1/11\n" " Eth1/12, Eth1/13, Eth1/14\n" " Eth1/15, Eth1/16, Eth1/17\n" " Eth1/18, Eth1/19, Eth1/20\n" " Eth1/21, Eth1/22, Eth1/23\n" " Eth1/24, Eth1/25, Eth1/26\n" " Eth1/27, Eth1/28, Eth1/29\n" " Eth1/30, Eth1/31, Eth1/32\n" " Eth1/33, Eth1/34, Eth1/35\n" " Eth1/36, Eth1/38, Eth1/39\n" " Eth1/40, Eth1/41, Eth1/42\n" " Eth1/43, Eth1/44, Eth1/45\n" " Eth1/46, Eth1/47, Eth1/48\n" "10 test active Po1, Eth1/37, Eth1/41, Eth1/42\n" " Eth1/43, Eth1/44, Eth1/45\n" " Eth1/46, Eth1/47, Eth1/48\n") vlan_parsed = parse_output(platform="cisco_ios", command="show vlan", data=vlan_output) print(vlan_parsed) 尚未使用的数据。

答案 1 :(得分:0)

如果使用的是阻塞插槽,则可能启用了模式SSL_MODE_AUTO_RETRY。如果没有要读取的应用程序数据,这可能会导致SSL_read挂起。 在这种情况下,您可以禁用此模式(但是您需要处理SSL_ERROR_WANT_READ错误),也可以使用非阻塞套接字。 建议您您可以阅读有关SSL_MODE_AUTO_RETRY的信息。