boost :: asio中的未经请求的消息崩溃应用程序,没有SSL它工作正常,为什么?

ikk*_*kku 4 c++ ssl boost openssl boost-asio

我想通过SSL连接发送未经请求的消息.这意味着服务器不是基于来自客户端的请求发送消息,而是因为发生了客户端需要知道的某些事件.

我只是使用来自boost站点的SSL服务器示例,添加了一个在10秒后发送'hello'的计时器,在计时器到期之前一切正常(服务器echo的所有内容),'hello'也被收到,但之后应用程序在下次将文本发送到服务器时崩溃.

对我来说更奇怪的是,当我禁用SSL代码时,所以使用普通套接字并使用telnet执行相同操作,它可以正常工作并继续正常工作!

我现在第二次遇到这个问题了,我真的不知道为什么会发生这种情况.

以下是我为证明问题而改变的总来源.编译它没有SSL定义并使用telnet,一切正常,定义SSL并使用openssl,或来自boost网站的客户端SSL示例,事情崩溃.

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

//#define SSL

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;

class session
{
public:
  session(boost::asio::io_service& io_service,
      boost::asio::ssl::context& context)
#ifdef SSL  
    : socket_(io_service, context)
#else
    : socket_(io_service)

#endif    
  {
  }

  ssl_socket::lowest_layer_type& socket()
  {
    return socket_.lowest_layer();
  }

  void start()
  {
#ifdef SSL      
    socket_.async_handshake(boost::asio::ssl::stream_base::server,
        boost::bind(&session::handle_handshake, this,
          boost::asio::placeholders::error));
#else
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));

      boost::shared_ptr< boost::asio::deadline_timer > timer(new     boost::asio::deadline_timer( socket_.get_io_service() ));
      timer->expires_from_now( boost::posix_time::seconds( 10 ) );
      timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );

#endif    
  }

  void handle_handshake(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));

      boost::shared_ptr< boost::asio::deadline_timer > timer(new     boost::asio::deadline_timer( socket_.get_io_service() ));
      timer->expires_from_now( boost::posix_time::seconds( 10 ) );
      timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );

    }
    else
    {
      delete this;
    }
  }

  void SayHello(const boost::system::error_code& error, boost::shared_ptr<     boost::asio::deadline_timer > timer) {
      std::string hello = "hello";

        boost::asio::async_write(socket_,
          boost::asio::buffer(hello, hello.length()),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));

      timer->expires_from_now( boost::posix_time::seconds( 10 ) );
      timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );

  }


  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(data_, bytes_transferred),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));
    }
    else
    {
      std::cout << "session::handle_read() -> Delete, ErrorCode: "<< error.value() <<     std::endl;
      delete this;
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      std::cout << "session::handle_write() -> Delete, ErrorCode: "<< error.value() <<     std::endl;
      delete this;
    }
  }

private:
#ifdef SSL    
  ssl_socket socket_;
#else
  boost::asio::ip::tcp::socket socket_;
#endif  
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_service& io_service, unsigned short port)
    : io_service_(io_service),
      acceptor_(io_service,
          boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
      context_(boost::asio::ssl::context::sslv23)
  {
#ifdef SSL        
    context_.set_options(
        boost::asio::ssl::context::default_workarounds
        | boost::asio::ssl::context::no_sslv2
        | boost::asio::ssl::context::single_dh_use);
    context_.set_password_callback(boost::bind(&server::get_password, this));
    context_.use_certificate_chain_file("server.crt");
    context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
    context_.use_tmp_dh_file("dh512.pem");
#endif
    start_accept();
  }

  std::string get_password() const
  {
    return "test";
  }

  void start_accept()
  {
    session* new_session = new session(io_service_, context_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }
    else
    {
      delete new_session;
    }

    start_accept();
  }

private:
  boost::asio::io_service& io_service_;
  boost::asio::ip::tcp::acceptor acceptor_;
  boost::asio::ssl::context context_;
};

int main(int argc, char* argv[])
{
  try
  {
    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, 7777 /*atoi(argv[1])*/);

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我使用boost 1.49和OpenSSL 1.0.0i-fips 2012年4月19日.我试图尽可能地调查这个问题,上次我遇到这个问题(几个月前),我收到了一个错误号码,我可以追溯到此错误消息:error: decryption failed or bad record mac.

但我不知道出了什么问题以及如何解决这个问题,欢迎任何建议.

Gre*_*reg 8

问题是多个并发异步读取和写入.即使使用原始套接字(glibc检测到双重释放或损坏),我也能够使此程序崩溃.让我们看看会话开始后会发生什么(在括号中我放置并发计划的异步读写次数):

  1. 调度异步读取(1,0)
  2. (假设数据来了)handle_read执行,它调度async write(0,1)
  3. (写入数据)handle_write执行,调度异步读取(1,0)

    现在,它可以循环超过1. - 3.无限期地没有任何问题.但是计时器到期......

  4. (假设,没有新数据来自客户端,因此仍有一个异步读取计划)计时器到期,因此SayHello执行,它调度异步写入,仍然没有问题(1,1)
  5. (SayHello写入数据,但仍然没有来自客户端的新数据)handle_write,它会调度异步读取(2,0)

    现在,我们完成了.如果来自客户端的任何新数据将会出现,其中一部分可以通过一个异步读取和另一个异步读取来读取.对于原始套接字,它甚至可能工作(尽管有可能,可能会安排2个并发写入,因此客户端上的回显可能看起来混合).对于SSL,这可能会破坏传入的数据流,这可能就是这种情况.

如何解决:

  1. strand 在这种情况下将无济于事(它不是并发处理程序执行,而是计划的异步读取和写入).
  2. 这是不够的,如果异步写入处理程序SayHello不执行任何操作(那时将不会有并发读取,但仍可能发生并发写入).
  3. 如果你真的想要有两种不同的写入(echo和timer),你必须实现某种消息队列来写,以避免混合来自echo和timer的写入.
  4. 一般说法:这是一个简单的例子,但使用boost :: asio 来shared_ptr代替delete this处理内存分配的方法要好得多.它将防止错过导致内存泄漏的错误.

  • 不幸的是,程序员不能安排多个async_writes.请参阅http://stackoverflow.com/questions/7754695/boost-asio-async-write-how-to-not-interleaving-async-write-calls.对于bool标志和额外的缓冲区,你可以标记"正在写入进行中",在这种情况下你将数据复制到额外的缓冲区并设置另一个标志"缓冲区中的数据"(抱歉,你需要两个标志),当写处理程序时完成并看到"缓冲区中的数据",它再次写入缓冲区而不是调度读取.这假设没有源(定时器,回声)一次发送两条消息.棘手,但可能. (2认同)