4

SSL 接続を介して未承諾メッセージを送信したいと考えています。サーバーがクライアントからの要求に基づいてではなく、クライアントが知る必要がある何らかのイベントが発生したためにメッセージを送信することを意味します。

ブーストサイトのSSLサーバーの例を使用し、10秒後に「こんにちは」を送信するタイマーを追加しました。タイマーが期限切れになる前にすべてが正常に機能し(サーバーはすべてをエコーし​​ます)、「こんにちは」も受信されますが、その後次にテキストがサーバーに送信されたときに、アプリケーションがクラッシュします。

私にとってさらに奇妙なのは、SSLコードを無効にすると、通常のソケットを使用してtelnetを使用して同じことを行うと、正常に動作し、正常に動作し続けるという事実です!!!

私はこの問題に 2 回遭遇しましたが、なぜこのようなことが起こっているのか本当にわかりません。

以下は、問題を示すために変更したソース全体です。SSLを定義せずにコンパイルしてtelnetを使用すると、すべてが正常に機能し、SSLを定義してopensslを使用するか、ブーストWebサイトのクライアント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;
}

私はブースト 1.49 と OpenSSL 1.0.0i-fips を 2012 年 4 月 19 日に使用しています。この問題を可能な限り調査しようとしましたが、最後にこの問題が発生したとき (数か月前)、追跡できるエラー番号を受け取りました。このエラー メッセージ: error: decryption failed or bad record mac

しかし、何が問題なのか、これを修正する方法がわかりません。提案は大歓迎です。

4

1 に答える 1

9

問題は、複数の同時非同期読み取りおよび書き込みです。生のソケットでもこのプログラムをクラッシュさせることができました ( glibc は二重解放または破損を検出しました)。セッションの開始後に何が起こるか見てみましょう (中括弧内に、同時スケジュールされた非同期読み取りと書き込みの数を入れます)。

  1. スケジュール非同期読み取り (1, 0)

  2. (データが来ると仮定)handle_readが実行され、非同期書き込み(0、1)がスケジュールされます

  3. (データが書き込まれます)handle_writeが実行され、非同期読み取りがスケジュールされます (1, 0)

    これで、1. - 3. を無限に問題なくループできます。しかし、その後タイマーが切れます...

  4. (クライアントから新しいデータが来ないので、まだ 1 つの非同期読み取りがスケジュールされていると仮定します) タイマーが期限切れになり、SayHello実行され、非同期書き込みがスケジュールされますが、それでも問題はありません (1, 1)

  5. (からのデータSayHelloは書き込まれますが、クライアントからの新しいデータはまだありません)handle_writeが実行され、非同期読み取りがスケジュールされます (2, 0)

    これで完了です。クライアントからの新しいデータが来る場合、それらの一部はある非同期読み取りによって読み取られ、一部は別の非同期読み取りによって読み取られる可能性があります。raw ソケットの場合、動作しているように見えることもあります (2 つの同時書き込みがスケジュールされている可能性があるにもかかわらず、クライアント側のエコーが混在しているように見える場合があります)。SSL の場合、これにより着信データ ストリームが破損する可能性があり、これがおそらく発生します。

修正方法:

  1. strandこの場合は役に立ちません (ハンドラーの同時実行ではなく、スケジュールされた非同期の読み取りと書き込みです)。
  2. 非同期書き込みハンドラーが何もしない場合は十分ではありSayHelloません (その場合、同時読み取りはありませんが、同時書き込みは発生する可能性があります)。
  3. 本当に 2 つの異なる種類の書き込み (エコーとタイマー) が必要な場合は、エコーとタイマーからの書き込みが混在するのを避けるために、書き込みメッセージのある種のキューを実装する必要があります。
  4. 総評: これは単純な例でしたが、boost::asio でメモリ割り当てを処理するには、shared_ptr代わりに使用するdelete this方がはるかに優れています。メモリ リークの原因となるエラーの欠落を防ぎます。
于 2012-08-12T11:23:01.693 に答える