带有阻塞套接字的SSL_accept

时间:2009-11-16 20:02:52

标签: c++ ssl tcp

我使用SSL和阻止套接字创建了一个服务器。 当我使用telnet连接时(因此它不进行握手),SSL_accept无限期地阻塞并阻止每次新的握手/接受(并且根据定义新的连接)。

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

4 个答案:

答案 0 :(得分:2)

为什么不在调用SSL_accept()之前将套接字流设置为非阻塞模式,然后在SSL_accept()返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE时以超时阻塞select()之类的内容?或者,您可以在调用SSL_accept()之前阻塞select()。要么应该工作。这样,您至少可以限制连接因DoS行为/攻击而被阻止的时间。

请记住,SSL / TLS是面向记录的,这意味着您必须循环直到读取完整记录。 SSL_pending()可以在这种情况下提供帮助。

答案 1 :(得分:1)

不要用telnet连接?

通常,可以使用TLS或纯文本连接的服务通过以明文建立连接,然后提供请求“升级”连接以使用TLS的命令来工作。扩展SMTP和“STARTTLS”命令就是一个例子。

没有此类命令的服务通常会为TLS和非TLS流量使用不同的端口。例如,端口80上的HTTP和端口443上的HTTPS。到端口443的明文连接不起作用。

您希望看到什么行为?

答案 2 :(得分:0)

您可以将套接字置于非阻塞模式,然后从SSL_accept获取SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE。然后你可以睡一会儿,然后再次尝试SSL_accept。在超时值之后,您可以退出并关闭ssl和socket句柄。

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

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

您也可以尝试在SSL_accept循环后将套接字重新置于阻塞模式,然后继续使用您的应用程序。

答案 3 :(得分:0)

我认为以下代码可能有助于其他人解决问题。它没有经过充分测试,将其作为灵感。

  //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);