rahman
rahman

Reputation: 4948

boost ssl connection procedure fails

I am trying to combine the famous boost ssl client/server connection examples into a single program. For your kind reference, the base classes are like this:

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

namespace bt
{
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//


enum { max_length = 1024 };

class client
{
public:
  client(boost::asio::io_service& io_service, boost::asio::ssl::context& context,
      boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
    : socket_(io_service, context)
  {
    boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
    socket_.lowest_layer().async_connect(endpoint,
        boost::bind(&client::handle_connect, this,
          boost::asio::placeholders::error, ++endpoint_iterator));
  }

  void handle_connect(const boost::system::error_code& error,
      boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
  {
      std::cout << "handle_connect\n";
    if (!error)
    {
        std::cout << "handle_connect No error\n";
      socket_.async_handshake(boost::asio::ssl::stream_base::client,
          boost::bind(&client::handle_handshake, this,
            boost::asio::placeholders::error));
    }
    else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
    {
        std::cout << "handle_connect retry!\n";
      socket_.lowest_layer().close();
      boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
      socket_.lowest_layer().async_connect(endpoint,
          boost::bind(&client::handle_connect, this,
            boost::asio::placeholders::error, ++endpoint_iterator));
    }
    else
    {
      std::cout << "Connect failed: " << error << "\n";
    }
  }

  void handle_handshake(const boost::system::error_code& error)
  {
      std::cout << "client handle_handshake\n";
    if (!error)
    {
      std::cout << "Enter message: ";
//      std::cin.getline(request_, max_length);
      sprintf(request_, "%s", "Hi Testing...");
      size_t request_length = strlen(request_);

      boost::asio::async_write(socket_,
          boost::asio::buffer(request_, request_length),
          boost::bind(&client::handle_write, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      std::cout << "Handshake failed: " << error << "\n";
    }
  }

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

  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      std::cout << "Reply: ";
      std::cout.write(reply_, bytes_transferred);
      std::cout << "\n";
    }
    else
    {
      std::cout << "Read failed: " << error << "\n";
    }
  }

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


//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//


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)
    : socket_(io_service, context)
  {
  }

  ssl_socket::lowest_layer_type& socket()
  {
    return socket_.lowest_layer();
  }

  void start()
  {
      std::cout << "session start->handshake\n";
    socket_.async_handshake(boost::asio::ssl::stream_base::server,
        boost::bind(&session::handle_handshake, this,
          boost::asio::placeholders::error));
  }

  void handle_handshake(const boost::system::error_code& error)
  {
      std::cout << "session handle_handshake\n";
    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
    {
      delete this;
    }
  }

  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
    {
      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
    {
      delete this;
    }
  }

private:
  ssl_socket socket_;
  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_(io_service, boost::asio::ssl::context::sslv23)
  {
      //std::cout << "server()\n";
    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("dh1024.pem");

    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));
  }

  std::string get_password() const
  {
    return "test";
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
      std::cout << "server() handle_accept\n";
    if (!error)
    {
        std::cout << "server() handle_accept  !error\n";
      new_session->start();
      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));
    }
    else
    {
        std::cout << "server() handle_accept  error:" << error.message() << std::endl;
      delete new_session;
    }
  }

private:
  boost::asio::io_service& io_service_;
  boost::asio::ip::tcp::acceptor acceptor_;
  boost::asio::ssl::context context_;
};

}//namespace bt

And the the main program is:

BOOST_AUTO_TEST_CASE(accept_ssl_connection_1)
{
    boost::asio::io_service io_service_1;
    boost::asio::io_service io_service_2;

    int port = random_port();
    std::stringstream i("");
    i << port;
    std::cout << "Port is:" << i.str() << std::endl;
    //server
    bt::server(io_service_1, port);
    //client
    boost::asio::ip::tcp::resolver resolver(io_service_2);
    boost::asio::ip::tcp::resolver::query query("127.0.0.1", i.str());
    boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);

    boost::asio::ssl::context ctx(io_service_2, boost::asio::ssl::context::sslv23);
    ctx.set_verify_mode(boost::asio::ssl::context::verify_peer);
    ctx.load_verify_file("server.crt");

    bt::client c(io_service_2, ctx, iterator);

    boost::thread thread1(boost::bind(&boost::asio::io_service::run, &io_service_1));
    boost::thread thread2(boost::bind(&boost::asio::io_service::run, &io_service_2));

    thread1.join();
    thread2.join();
}

And here is the output I am getting:

Port is:7200
server() handle_accept
handle_connect
Connect failed: system:111
server() handle_accept  error:Operation canceled

The program works if clien and server are built and run individually. I guess I have a mistake in io_service usage. Could you please help me detect the issue?

Upvotes: 1

Views: 1307

Answers (2)

sehe
sehe

Reputation: 393769

1. Style

I suggest you put more effort in making the code readable.

Code is for humans to read, not computers

In your case, the extreme brevity like

bt::client c(...);

Leads to bugs like

bt::server(io_service_1, port);

There's not a lot of difference with the - probably intended - variable declaration

bt::server s(io_service_1, port);

Otherwise, the newly constructed server is immediately destructed and thereby cancels all pending operations.

2. Debugging

Try to actually present readable messages:

std::cout << "Connect failed: " << error.message() << "\n";
std::cout << "Handshake failed: " << error.message() << "\n";
std::cout << "Write failed: " << error.message() << "\n";
std::cout << "Read failed: " << error.message() << "\n";
std::cout << "server() handle_accept  error:" << error.message() << std::endl;

This would tell you that "125" means "Operation aborted" etc.. This is what made me add a little trace here and there:

~session() { std::cout << "Deleting session!\n"; }
~server() { std::cout << "Deleting server!\n"; }

2. Asio Review, more style

  1. Instead of doing things manually, prefer the composed operations defined in boost:

    client(ba::io_service &io_service, ssl::context &context, tcp::resolver::iterator endpoint_iterator)
            : socket_(io_service, context) 
    {
        ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
                          boost::bind(&client::handle_connect, this, bap::error));
    }
    
    void handle_connect(const boost::system::error_code &error) {
        std::cout << "handle_connect\n";
        if (!error) {
            std::cout << "handle_connect No error\n";
            socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
        } else {
            std::cout << "Connect failed: " << error.message() << "\n";
        }
    }
    

    This does the whole iterator dance. But less error-prone.

  2. Use namespace aliases to get readable/manageable lines

    using boost::asio::ip::tcp;
    namespace ba = boost::asio;
    namespace bap = boost::asio::placeholders;
    namespace ssl = boost::asio::ssl;
    
  3. Use smart pointers (delete this? ugh)

  4. Consider using 1 io_service. Using two doesn't add anything, really, and the names didn't clarify a thing. In fact, the first minutes of staring at your code had me dis-entangling the code for client and server, painstakingly verifying that they didn't mistakenly use the wrong service, leading to premature run() completion.

  5. Account for race conditions. In your code, server and client run independently unsynchronized. At least add a delay:

    boost::this_thread::sleep_for(boost::chrono::seconds(1));
    

    to avoid the client connecting to the server before it started accepting connections.

  6. Prefer boost::thread_group over lose threads:

    boost::thread_group tg;
    // ...
    tg.create_thread(boost::bind(&ba::io_service::run, &io_service_1));
    // ...
    tg.create_thread(boost::bind(&ba::io_service::run, &io_service_2));
    // ...
    tg.join_all();
    
  7. In fact, with 1 io_service and 1 thread, you sidestep all of the above (the async operations are synchronized due the implicit strand)

  8. use higherlevel standard library features (e.g. std::to_string(int) instead of std::ostringstream; if you cannot use c++11, use boost::lexical_cast or write your own to_string-type helper function).

  9. If the address is hardcoded to loopback, no need to "resolve" anything: just connect to tcp::endpoint{{}, port}

  10. Consider moving ctx into client (like you moved the ssl params for the server into that class too)

  11. prefer boost::array/std::array over raw arrays (request_ and reply_)

  12. Why do you read as many bytes as you sent? Did you mean

    ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
                   boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
    

    I'd expect something like

    ba::async_read(socket_, ba::buffer(reply_, reply.size()), // assuming array<>, see previous
                   boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
    
  13. Consider composed operations over read_some again. read_some may not read a complete request. Consider adding a framing protocol or sending request length up front.

  14. Avoid code duplication: async_accept is coded twice. Instead make it a separate function and call it twice:

    void do_accept() {
        session::ptr new_session = boost::make_shared<session>(io_service_, context_);
        acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
    }
    

BONUS

  1. Add a deadline to the accept so we can stop the server at a certain idle time interval

  2. Since you are using smart pointers now (aren't you?) it's easy to add a session shutdown at this place too (session::close())

  3. Let's do two client for the price of one, just for fun

Live On Coliru

//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <cstdlib>
#include <iostream>

using boost::asio::ip::tcp;
namespace ba = boost::asio;
namespace bap = boost::asio::placeholders;
namespace ssl = boost::asio::ssl;

namespace bt {

enum { max_length = 1024, idle_timeout_seconds = 2 };

class client {
  public:
    client(ba::io_service &io_service, tcp::resolver::iterator endpoint_iterator, std::string const& request)
            : ctx_(io_service, ssl::context::sslv23),
              socket_(io_service, ctx_),
              request_(request)
    {
        ctx_.set_verify_mode(ssl::context::verify_peer);
        ctx_.load_verify_file("server.crt");
        ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
                          boost::bind(&client::handle_connect, this, bap::error));
    }

    void handle_connect(const boost::system::error_code &error) {
        std::cout << "handle_connect\n";
        if (!error) {
            std::cout << "handle_connect No error\n";
            socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
        } else {
            std::cout << "Connect failed: " << error.message() << "\n";
        }
    }

    void handle_handshake(const boost::system::error_code &error) {
        std::cout << "client handle_handshake\n";
        if (!error) {
            ba::async_write(socket_, ba::buffer(request_),
                            boost::bind(&client::handle_write, this, bap::error, bap::bytes_transferred));
        } else {
            std::cout << "Handshake failed: " << error.message() << "\n";
        }
    }

    void handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
        if (!error) {
            ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
                           boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
        } else {
            std::cout << "Write failed: " << error.message() << "\n";
        }
    }

    void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
        if (!error) {
            std::cout << "Reply: ";
            std::cout.write(reply_.data(), bytes_transferred);
            std::cout << "\n";
        } else {
            std::cout << "Read failed: " << error.message() << "\n";
        }
    }

  private:
    ssl::context ctx_;
    ssl::stream<tcp::socket> socket_;
    std::string request_;
    std::array<char, max_length> reply_;
};

class session : public boost::enable_shared_from_this<session> {
  public:
    using ptr = boost::shared_ptr<session>;
    session(ba::io_service &io_service, ssl::context &context) : socket_(io_service, context) {}

    typedef ssl::stream<tcp::socket> ssl_socket;
    ssl_socket::lowest_layer_type &socket() { return socket_.lowest_layer(); }

    void start() {
        std::cout << "session start->handshake\n";
        socket_.async_handshake(ssl::stream_base::server, boost::bind(&session::handle_handshake, shared_from_this(), bap::error));
    }

    void handle_handshake(const boost::system::error_code &error) {
        std::cout << "session handle_handshake\n";
        if (error) return;
        socket_.async_read_some(ba::buffer(data_),
                boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
    }

    void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
        if (error) return;
        ba::async_write(socket_, ba::buffer(data_, bytes_transferred),
                boost::bind(&session::handle_write, shared_from_this(), bap::error));
    }

    void handle_write(const boost::system::error_code &error) {
        if (error) return;
        socket_.async_read_some(ba::buffer(data_),
                boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
    }

    void close() {
        socket_.get_io_service().post([this] {
                std::cout << "session::close()\n";
                socket_.lowest_layer().cancel();
                socket_.lowest_layer().close();
            });
    }

    ~session() { std::cout << "Deleting session\n"; }

  private:
    ssl_socket socket_;
    std::array<char, max_length> data_;
};

class server {
  public:
    server(ba::io_service &io_service, unsigned short port)
            : io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
              context_(io_service, ssl::context::sslv23),
              deadline_(io_service)
    {
        // std::cout << "server()\n";
        context_.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | 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.crt", ssl::context::pem);
        context_.use_tmp_dh_file("dh2048.pem");

        do_accept();
    }

    ~server() { std::cout << "Deleting server\n"; }

    std::string get_password() const { return "test"; }

    void do_accept() {
        session::ptr new_session = boost::make_shared<session>(io_service_, context_);
        deadline_.expires_from_now(boost::posix_time::seconds(idle_timeout_seconds));
        deadline_.async_wait(boost::bind(&server::handle_deadline, this, bap::error()));
        acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
    }

    void handle_accept(session::ptr new_session, const boost::system::error_code &error) {
        std::cout << "server() handle_accept\n";
        if (!error) {
            std::cout << "server() handle_accept ok\n";
            sessions_.push_back(new_session);
            new_session->start();
            do_accept();
        } else {
            std::cout << "server() handle_accept error:" << error.message() << std::endl;
        }
    }

    void handle_deadline(boost::system::error_code ec) {
        if (!ec) {
            io_service_.post([this] {
                // assuming 1 thread runs io_service, no more locking required
                std::cout << "server() shutdown after idle timeout\n";
                acceptor_.cancel();
                acceptor_.close();

                for (auto weak_sess : sessions_)
                    if (auto sess = weak_sess.lock())
                        sess->close();
            });
        }
    }

  private:
    ba::io_service &io_service_;
    tcp::acceptor acceptor_;
    ssl::context context_;
    ba::deadline_timer deadline_;
    std::vector<boost::weak_ptr<session> > sessions_;
};

} // namespace bt

void accept_ssl_connection_1() {
    ba::io_service svc;
    int port = 6767;

    std::cout << "Port is:" << port << std::endl;

    // server
    bt::server s(svc, port);

    // client
    tcp::resolver resolver(svc);
    bt::client c(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Bob");
    bt::client d(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Cindy");

    svc.run();
}

int main() {
    accept_ssl_connection_1();
}

Prints

Port is:6767
server() handle_accept
server() handle_accept ok
session start->handshake
handle_connect
handle_connect No error
handle_connect
handle_connect No error
server() handle_accept
server() handle_accept ok
session start->handshake
session handle_handshake
client handle_handshake
session handle_handshake
client handle_handshake
Reply: Hello, I'm Bob
Reply: Hello, I'm Cindy
server() shutdown after idle timeout
server() handle_accept
server() handle_accept error:Operation canceled
Deleting session
session::close()
session::close()
Deleting session
Deleting session
Deleting server

Upvotes: 5

SKi
SKi

Reputation: 8476

Error code 111 (ECONNREFUSED) means (in Linux):

"The target address was not listening for connections or refused the connection request."

It usually occurs when a client try to connect to a server, and no one is listening the port. Possible reasons:

  1. the server program is not running
  2. the server program uses different TCP port number than the client
  3. the server program is still starting. The port is not yet bound, when client try to connect.

In your case, the problem could be the option #3. Because you face the problem when the client and the server are started almost the same time.

I didn't check all of your code, is it really possible that client try connect before the server is ready.

Upvotes: 1

Related Questions