Opened 4 years ago

#13587 new Bugs

ssl::stream::async_shutdown() never completes when async_read is active

Reported by: anonymous Owned by: chris_kohlhoff
Milestone: To Be Determined Component: asio
Version: Boost 1.67.0 Severity: Problem
Keywords: async_shutdown ssl async_read Cc:

Description

I have a connected ssl::stream. If I do an asio::async_read followed by an async_shutdown on the stream, the read operation will complete with stream_truncated error (as expected) but the async_shutdown operation never completes (i.e. handler never gets called). See a minimal reproduce below.

Environment: Debian stretch, gcc 6.3.0, Boost 1.67, BoringSSL.

My questions:

  1. Is it allowed to call async_shutdown on an ssl::stream when there is an async_read pending?
  2. If yes, is the above behavior expected?
  3. If not, how do I gracefully shutdown an SSL stream when an async_read is pending? And is this restriction documented anywhere?

Code:

#include <boost/asio.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>

void
fail(boost::system::error_code ec, char const* what)
{
    std::cerr << what << ": " << ec.message() << "\n";
}

class session : public std::enable_shared_from_this<session>
{
    tcp::resolver resolver_;
    ssl::stream<tcp::socket> stream_;
    std::string buffer_;

public:
    explicit
    session(boost::asio::io_context& ioc, ssl::context& ctx)
        : resolver_(ioc)
        , stream_(ioc, ctx)
    {
    }

    void
    run(
        char const* host,
        char const* port)
    {
        // Set SNI Hostname (many hosts need this to handshake successfully)
        if(! SSL_set_tlsext_host_name(stream_.native_handle(), host))
        {
            boost::system::error_code ec{static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category()};
            std::cerr << ec.message() << "\n";
            return;
        }

	resolver_.async_resolve(
            host,
            port,
            std::bind(
                &session::on_resolve,
                shared_from_this(),
                std::placeholders::_1,
                std::placeholders::_2));
    }

    void
    on_resolve(
        boost::system::error_code ec,
        tcp::resolver::results_type results)
    {
        if(ec)
            return fail(ec, "resolve");

        boost::asio::async_connect(
            stream_.next_layer(),
            results.begin(),
            results.end(),
            std::bind(
                &session::on_connect,
                shared_from_this(),
                std::placeholders::_1));
    }

    void
    on_connect(boost::system::error_code ec)
    {
        if(ec)
            return fail(ec, "connect");

        stream_.async_handshake(
            ssl::stream_base::client,
            std::bind(
                &session::on_handshake,
                shared_from_this(),
                std::placeholders::_1));
    }

    void
    on_handshake(boost::system::error_code ec)
    {
        if(ec)
            return fail(ec, "handshake");
	std::cout << "Connected" << std::endl;

	boost::asio::async_read(stream_, boost::asio::dynamic_buffer(buffer_),
            std::bind(
                &session::on_read,
                shared_from_this(),
                std::placeholders::_1,
                std::placeholders::_2));

        stream_.async_shutdown(
            std::bind(
                &session::on_shutdown,
                shared_from_this(),
                std::placeholders::_1));
    }

    void
    on_read(
        boost::system::error_code ec,
        std::size_t)
    {
        if(ec)
            return fail(ec, "read");

        std::cout << "Message received" << std::endl;
    }

    void
    on_shutdown(boost::system::error_code ec)
    {
	std::cout << "Closed" << std::endl;
    }
};

int main(int argc, char** argv)
{
    boost::asio::io_context ioc;
    ssl::context ctx{ssl::context::sslv23_client};
    std::make_shared<session>(ioc, ctx)->run("www.google.com", "443");
    ioc.run();
    return EXIT_SUCCESS;
}

The program will output

Connected
read: stream truncated

and hangs.

Change History (0)

Note: See TracTickets for help on using tickets.