18

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?

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
Amdei
  • 223
  • 3
  • 8

2 Answers2

16

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.

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • I don't see a short read. I see this: If the remote host calls shutdown then the local host's read will complete with eof. If both hosts call shutdown then the calls to shutdown will complete with eof. These are ssl::stream shutdowns, not TCP/IP shutdown. I have written a test that confirms this. – Vinnie Falco Apr 12 '16 at 20:50
6

You may be interested in these discussions:

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.

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • 1
    SSL/TLS has notation to inform other party about closing connection. It close_notify alert. Also underlying TCP socket being closed. So, basically, my question was: 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 feature? – Amdei Dec 11 '11 at 23:25
  • Sure, `close_notify` is for clean closures, that won't help you to detect the bad ones. I don't know much about `boost::asio::ssl::stream`, but my guess is that you're doing asynchronous operations and SSL/TLS closure will cause problems with asynchronous operations (see "Properly closing SSLSocket" link above). It might explain a slightly different behaviour. Either way, it doesn't really matter. It wouldn't be a bug that needs fixing, since it's just not the correct way of finding out when to stop reading from a stream/socket. – Bruno Dec 11 '11 at 23:44
  • By the way, I'm not sure what you meant by "*It close_notify alert. Also underlying TCP socket being closed.*", but a TLS closure alert doesn't not imply that the underlying TCP socket must be closed. – Bruno Dec 11 '11 at 23:52
  • I'm working on SSL level. So I would expect to get an EOF if peer send close_notify. And of cource I would expect to get an EOF if underlying TCP socket was closed. But it is not the case! Ok, dead SSL-connections can be detected via timeouts. But what about properly finished/closed? asio didn't give me anything, except short_read. What is not really suitable in this case, from common sence point of view. :( – Amdei Dec 13 '11 at 07:48
  • Since you can't distinguish between a remote party that's "dead" or not sending anything, detecting proper closures of TCP/TLS isn't generally useful: knowing when to stop reading is up to the app. layer. Presumably, asio throws an except. in case of truncation attack (i.e. TCP closure w/o TLS `close_notify`). Once you've read what you were meant to read (at the HTTP layer here), presence/absence of exception will indicate problems. Knowing whether there's a proper closure is barely relevant. If it occurs in the middle of an HTTP (or other application) message, you'll have a problem anyway. – Bruno Dec 13 '11 at 19:46