C++ 带阻塞套接字的SSL\u接受

C++ 带阻塞套接字的SSL\u接受,c++,ssl,tcp,C++,Ssl,Tcp,我制作了一个带有SSL和阻塞套接字的服务器。 当我连接到telnet(因此它不进行握手)时,SSL_accept会无限期地阻塞,并阻塞每个新的握手/接受(以及根据定义的新连接) 如何解决这个可怕的问题?您可以将套接字置于非阻塞模式,然后您将从SSL接受中获得SSL错误或SSL错误。然后你可以睡一会儿,然后再次尝试接受SSL。在某个超时值之后,可以退出并关闭ssl和套接字句柄 请注意,这将影响所有SSL操作,这意味着您需要为所有读/写/关机调用执行类似的循环。基本上,任何可以返回WANT_READ

我制作了一个带有SSL和阻塞套接字的服务器。 当我连接到telnet(因此它不进行握手)时,SSL_accept会无限期地阻塞,并阻塞每个新的握手/接受(以及根据定义的新连接)


如何解决这个可怕的问题?

您可以将套接字置于非阻塞模式,然后您将从SSL接受中获得SSL错误或SSL错误。然后你可以睡一会儿,然后再次尝试接受SSL。在某个超时值之后,可以退出并关闭ssl和套接字句柄

请注意,这将影响所有SSL操作,这意味着您需要为所有读/写/关机调用执行类似的循环。基本上,任何可以返回WANT_READ或WANT_WRITE的调用

如果您不喜欢轮询的想法,可以使用select来确定套接字上是否有可用数据……但这可能会变得有点复杂


您还可以尝试在SSL\u accept循环之后将套接字重新置于阻塞模式,然后继续应用程序。

为什么不在调用之前将套接字流设置为非阻塞模式,然后在SSL\u accept()返回SSL\u ERROR\u READ或SSL\u ERROR\u WRITE时以超时的方式阻塞?或者,您可以在调用SSL_accept()之前阻止select()。两者都应该有效。这样,您至少可以限制由于类似行为/攻击而阻塞连接的时间


请记住,SSL/TLS是面向记录的,这意味着您必须循环直到读取完整记录。在这种情况下可能会有所帮助。

我认为下面的代码可能会帮助其他人解决这个问题。它没有经过充分的测试,只是作为灵感

  //Nonblocking SSL accept based on ACE/ace/SSL/SSL_SOCK_Acceptor.cpp
  SSL_CTX* ctx;
  ctx = initServerCTX(); // initialize SSL
  loadCertificates(ctx, certificate, privateKey); // load certs

  ...

  SSL* ssl = SSL_new(ctx); /* get new SSL state with context */
  SSL_set_fd(ssl, fd); /* set connection socket to SSL state */

  int flags = fcntl(fd, F_GETFL, 0);
  if (flags < 0)
  {
    printf("fcntl: F_GETFL \n");
    return false;
  }
  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
  {
    printf("fcntl: F_SETFL \n");
    return false;
  }

  int status = -1;
  struct timeval tv, tvRestore;
  tv.tv_sec = 2;
  tv.tv_usec = 0;
  tvRestore = tv;

  fd_set writeFdSet;
  fd_set readFdSet;

  do
  {
    tv = tvRestore;
    FD_ZERO(&writeFdSet);
    FD_ZERO(&readFdSet);

    status = ::SSL_accept(ssl);
    switch (::SSL_get_error(ssl, status))
    {
    case SSL_ERROR_NONE:
      status = 0; // To tell caller about success
      break; // Done

    case SSL_ERROR_WANT_WRITE:
      FD_SET(fd, &writeFdSet);
      status = 1; // Wait for more activity
      break;

    case SSL_ERROR_WANT_READ:
      FD_SET(fd, &readFdSet);
      status = 1; // Wait for more activity
      break;

    case SSL_ERROR_ZERO_RETURN:
    case SSL_ERROR_SYSCALL:
      // The peer has notified us that it is shutting down via
      // the SSL "close_notify" message so we need to
      // shutdown, too.
      printf("Peer closed connection during SSL handshake,status:%d", status);
      status = -1;
      break;
    default:
      printf("Unexpected error during SSL handshake,status:%d", status);
      status = -1;
      break;
    }

    if (status == 1)
    {
      // Must have at least one handle to wait for at this point.
      status = select(fd + 1, &readFdSet, &writeFdSet, NULL, &tv);

      // 0 is timeout, so we're done.
      // -1 is error, so we're done.
      // Could be both handles set (same handle in both masks) so
      // set to 1.
      if (status >= 1)
      {
        status = 1;
      }
      else // Timeout or failure
      {
        printf("SSL handshake - peer timeout or failure");
        status = -1;
      }
    }

  }
  while (status == 1 && !SSL_is_init_finished(ssl));

  flags = fcntl(fd, F_GETFL, 0);
  if (flags < 0)
  {
    printf("fcntl: F_GETFL \n");
    return false;
  }
  if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0)
  {
    printf("fcntl: F_SETFL \n");
    return false;
  }


  return (status >= 0);
//基于ACE/ACE/SSL/SSL\u SOCK\u Acceptor.cpp的非阻塞SSL接受
SSL_CTX*CTX;
ctx=initServerCTX();//初始化SSL
加载证书(ctx、证书、私钥);//加载证书
...
SSL*SSL=SSL_new(ctx);/*使用上下文获取新的SSL状态*/
SSL_set_fd(SSL,fd);/*将连接套接字设置为SSL状态*/
int flags=fcntl(fd,F_GETFL,0);
如果(标志<0)
{
printf(“fcntl:F_GETFL\n”);
返回false;
}
if(fcntl(fd、F_设置FL、标志| O_非块)<0)
{
printf(“fcntl:F_SETFL\n”);
返回false;
}
int status=-1;
结构timeval tv、tvRestore;
tv.tv_sec=2;
tv.tv_usec=0;
tvRestore=tv;
fd_集writeFdSet;
fd_集readFdSet;
做
{
tv=tv恢复;
FD_ZERO(&writeFdSet);
FD_ZERO(&readFdSet);
状态=::SSL_接受(SSL);
开关(::SSL\u获取\u错误(SSL,状态))
{
案例SSL\u错误\u无:
status=0;//通知调用方成功
中断;//完成
案例SSL\u错误\u需要\u写入:
FD_集(FD和writeFdSet);
status=1;//等待更多活动
打破
案例SSL\u错误\u需要\u读取:
FD_集(FD和readFdSet);
status=1;//等待更多活动
打破
案例SSL\u错误\u零\u返回:
案例SSL\u错误\u系统调用:
//对等机已通知我们它将通过
//SSL“close_notify”消息,因此我们需要
//也关闭了。
printf(“SSL握手期间对等端关闭连接,状态:%d”,状态);
状态=-1;
打破
违约:
printf(“SSL握手期间出现意外错误,状态:%d”,状态);
状态=-1;
打破
}
如果(状态==1)
{
//此时必须至少有一个句柄等待。
状态=选择(fd+1,&readFdSet,&writeFdSet,NULL,&tv);
//0是超时,所以我们完成了。
//-1是错误,所以我们完成了。
//可以设置两个句柄(两个掩码中的句柄相同),以便
//设置为1。
如果(状态>=1)
{
状态=1;
}
else//超时或失败
{
printf(“SSL握手-对等超时或失败”);
状态=-1;
}
}
}
而(status==1&&!SSL_是_init_finished(SSL));
标志=fcntl(fd,F_GETFL,0);
如果(标志<0)
{
printf(“fcntl:F_GETFL\n”);
返回false;
}
如果(fcntl(fd、F_设置FL、标志和(~O_非块))<0)
{
printf(“fcntl:F_SETFL\n”);
返回false;
}
返回(状态>=0);