草庐IT

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

coder 2024-02-16 原文

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

我只是使用来自 boost 站点的 SSL 服务器示例,添加了一个在 10 秒后发送“hello”的计时器,在计时器到期之前一切正常(服务器回显的一切),也收到了“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;
}

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

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

最佳答案

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

  1. 安排异步读取 (1, 0)

  2. (假设数据来了)handle_read被执行,它调度async write (0, 1)

  3. (data are written) handle_write 执行,它调度async read (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. 一般评论:这是一个简单的例子,但是使用 shared_ptr 而不是 delete this 是用 boost::asio 处理内存分配的更好方法。它将防止遗漏错误导致内存泄漏。

关于c++ - boost::asio 中的未经请求的消息使应用程序崩溃,没有 SSL 它工作正常,为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11919612/

有关c++ - boost::asio 中的未经请求的消息使应用程序崩溃,没有 SSL 它工作正常,为什么?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  5. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  7. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  8. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  9. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  10. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

随机推荐