c++ - boost ssl connection procedure fails -
i trying combine famous boost ssl client/server connection examples single program. kind reference, base classes 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 @ kohlhoff dot com) // // distributed under boost software license, version 1.0. (see accompanying // file license_1_0.txt or copy @ 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 @ kohlhoff dot com) // // distributed under boost software license, version 1.0. (see accompanying // file license_1_0.txt or copy @ 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 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(""); << 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 output getting:
port is:7200 server() handle_accept handle_connect connect failed: system:111 server() handle_accept error:operation canceled
the program works if clien , server built , run individually. guess have mistake in io_service usage. please me detect issue?
1. style
i suggest put more effort in making code readable.
code humans read, not computers
in case, extreme brevity like
bt::client c(...);
leads bugs like
bt::server(io_service_1, port);
there's not lot of difference - intended - variable declaration
bt::server s(io_service_1, port);
otherwise, newly constructed server immediately destructed , thereby cancels pending operations.
2. debugging
try 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 tell "125" means "operation aborted" etc.. made me add little trace here , there:
~session() { std::cout << "deleting session!\n"; } ~server() { std::cout << "deleting server!\n"; }
2. asio review, more style
instead of doing things manually, prefer 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 whole iterator dance. less error-prone.
use namespace aliases readable/manageable lines
using boost::asio::ip::tcp; namespace ba = boost::asio; namespace bap = boost::asio::placeholders; namespace ssl = boost::asio::ssl;
use smart pointers (
delete this
? ugh)consider using 1 io_service. using 2 doesn't add anything, really, , names didn't clarify thing. in fact, first minutes of staring @ code had me dis-entangling code client , server, painstakingly verifying didn't mistakenly use wrong service, leading premature
run()
completion.account race conditions. in code, server , client run independently unsynchronized. @ least add delay:
boost::this_thread::sleep_for(boost::chrono::seconds(1));
to avoid
client
connectingserver
before started accepting connections.prefer
boost::thread_group
on 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();
in fact, 1 io_service , 1 thread, sidestep of above (the async operations synchronized due implicit strand)
use higherlevel standard library features (e.g.
std::to_string(int)
instead ofstd::ostringstream
; if cannot use c++11, useboost::lexical_cast
or write ownto_string
-type helper function).if address hardcoded loopback, no need "resolve" anything: connect
tcp::endpoint{{}, port}
consider moving
ctx
client
(like moved ssl paramsserver
class too)prefer boost::array/std::array on raw arrays (
request_
,reply_
)why read many bytes sent? did mean
ba::async_read(socket_, ba::buffer(reply_, bytes_transferred), boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
i'd expect 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));
consider composed operations on
read_some
again.read_some
may not read complete request. consider adding framing protocol or sending request length front.avoid code duplication:
async_accept
coded twice. instead make separate function , call 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
add deadline accept can stop server @ idle time interval
since using smart pointers (aren't you?) it's easy add session shutdown @ place (
session::close()
)let's 2 client price of one, fun
//#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(); (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
wiki
Comments
Post a Comment