Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::asio and async SSL stream: how to detect end of data/connection close?

Tags:

ssl

boost-asio

I'm trying to make asio and SSL friends. Everything going well, but one thing is causing inconvenience: how to detect if peer close connection, and distinguish it from situation when peer just take a short break in sending data, aiming to continue it few seconds later?

  • boost 1.48
  • OpenSSL 1.0.0e
  • Compiled to 32-bit code using VS10
  • Working on W7 x64.

My confusion comes from the fact, that asio behaviour is different for ordinary socket and SSL-stream. If I use tcp::socket - I receive EOF error when peer close connection. But for boost::asio::ssl::stream - it is not the case. Instead, async_read_some returns 0 as bytes transfered, and if I try to continue to read from SSL stream - returns short_error ( http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/overview/core/streams.html ).

So, the questions is: is it expected behaviour, or I misconfigure anything?

Client code snippet:

class client
{
public:

    // bla-bla-bla-bla-bla ....
    //
   void handle_write(const boost::system::error_code& error)
   {
       if (!error)
       {
           socket_.async_read_some(boost::asio::buffer(reply_, max_length),
               boost::bind(&client::handle_read, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
       }
       else
       {
           std::cout << "Write failed: " << error.message() << "\n";
       }
   }

   void handle_read(const boost::system::error_code& error,
                   size_t bytes_transferred)
   {

       std::cout << "Bytes transfered: " << bytes_transferred << "\n";
       if (!error)
       {
           std::cout << "Reply: ";
           std::cout.write(reply_, bytes_transferred);
           std::cout << "\n";

           std::cout << "Reading...\n";
           socket_.async_read_some(boost::asio::buffer(reply_, max_length),
               boost::bind(&client::handle_read, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
       }
       else if (0 != bytes_transferred)
       {
           std::cout << "Read failed: " << error.message() << ":" 
                     << error.value() <<  "\n";
       }
   }

private:
   boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
   boost::asio::streambuf request_;
   char reply_[max_length];
};

If we remove if (0 != bytes_transferred), we'll get "short read" :(.

If we'll use code as-ai, output will be something like this:

Request is:

GET / HTTP/1.0

Cookie: Nama-nama=Vala-vala

Bytes transfered: 1024

Reply: HTTP/1.0 200 ok Content-type: text/html

..... bla-bla-bla ....

Reading... Bytes transfered: 1024

..... bla-bla-bla .... ..... bla-bla-bla ....

Reading... Bytes transfered: 482

..... bla-bla-bla ....

Reading...

Bytes transfered: 0

At the same time, if instead async_read_some we write code, what for ordinary socket will return EOF:

boost::asio::async_read(socket_, response_,
    boost::asio::transfer_at_least(1),
    boost::bind(&client::handle_read_content, this,
    boost::asio::placeholders::error));

then for SSL-socket we'll get 0 as bytes transfered, and then short_read.

I know that there is not way to detect disconnect in case if peer, for example, was just unplugged from the network. But how to detect explicit clean peer disconnect from situation when peer just do not send data for a some time, but may be will do it little bit later?

Or, may be I do not understant something?

WBR, Andrey

Some addentum: SSL/TLS has notation to inform other party about closing connection. It close_notify alert. Also underlying TCP socket can be closed.

So, basically, my question is: why, in the same conditions (TCP socket was closed clearly) I receive EOF in case of tcp::socket, and do not receive anything for boost::asio::ssl::stream.

Is it bug or asio feature?

Yet another addentum: For some reasons asio didn't give me a EOF neither if SSL receive close_notify nor underlying TCP socket was closed.

Yes, I can detect dead connections by timeout. But how can I detect properly closed SSL-connections? By receiving short_read?

like image 855
Amdei Avatar asked Dec 11 '11 20:12

Amdei


2 Answers

The SSL_R_SHORT_READ error is expected here. When the server initiates a clean shutdown with SSL_Shutdown this sends the close notify shutdown alert to the client. The Asio implementation maps this into an SSL_R_SHORT_READ error with the category of error::get_ssl_category(). It does this by detecting if the peer has initiated shutdown via SSL_get_shutdown.

This can be seen by inspecting asio/ssl/detail/impl/engine.ipp header and specifically the function engine::map_error_code(boost::system::error_code&).

I believe the ssl implementation was rewritten in boost 1.47, so earlier versions have potentially different behavior.

like image 72
Sam Miller Avatar answered Oct 17 '22 19:10

Sam Miller


You may be interested in these discussions:

  • how to detect a TCP socket disconnection (with c berkeley socket)
  • Properly closing SSLSocket

Essentially, the fact that you get an EOF sometimes (even most of the time) when the remote party disconnects a plain TCP socket is just luck. You can't rely on it in the general case, since it's not possibly to distinguish between an inactive socket and a socket closed abruptly without writing to it.

You need to define some delimiters, at the application protocol level, to know when to stop reading. In HTTP, this is done either via the blank line that ends the header (for the header), the Content-Length header that defines the body length, or the chunked transfer encoding delimiters when the body length isn't known in advance.

like image 6
Bruno Avatar answered Oct 17 '22 17:10

Bruno