I am using Boost Asio and my goal is to send an HTTPS GET request to www.realmofthemadgod.com. I found some code on GitHub claiming to do that which I've included below but first I'll go through some observations:
The code fails with an error sslv3 alert handshake failure.
On command line, the command openssl s_client -connect www.realmofthemadgod.com:443 results in the same error and some other messages about no certificates being available
However, the command openssl s_client -connect www.realmofthemadgod.com:443 -servername www.realmofthemadgod.com does find the correct certificate!
Now, the problem is how do I achieve what the -servername switch does, in code.
What I have so far:
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#pragma comment(lib, "libcryptoMD.lib")
#pragma comment(lib, "libsslMD.lib")
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class client
{
public:
client(boost::asio::io_service& io_service, boost::asio::ssl::context& context, const std::string& server, const std::string& path)
: resolver_(io_service), socket_(io_service, context)
{
// Form the request. We specify the "Connection: close" header so that the
// server will close the socket after transmitting the response. This will
// allow us to treat all data up until the EOF as the content.
std::ostream request_stream(&request_);
request_stream << "GET " << path << " HTTP/1.1\r\n";
request_stream << "Host: " << server << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n\r\n";
// Start an asynchronous resolve to translate the server and service names
// into a list of endpoints.
tcp::resolver::query query(server, "https");
resolver_.async_resolve(query,
boost::bind(&client::handle_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
private:
void handle_resolve(const boost::system::error_code& err,
tcp::resolver::iterator endpoint_iterator)
{
if (!err)
{
std::cout << "Resolve OK" << "\n";
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
//socket_.set_verify_mode(boost::asio::ssl::verify_none);
socket_.set_verify_callback(
boost::bind(&client::verify_certificate, this, _1, _2));
boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error resolve: " << err.message() << "\n";
}
}
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.
// In this example we will simply print the certificate's subject name.
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying " << subject_name << "\n";
return preverified;
}
void handle_connect(const boost::system::error_code& err)
{
if (!err)
{
std::cout << "Connect OK " << "\n";
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Connect failed: " << err.message() << "\n";
}
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "Handshake OK " << "\n";
std::cout << "Request: " << "\n";
const char* header = boost::asio::buffer_cast<const char*>(request_.data());
std::cout << header << "\n";
// The handshake was successful. Send the request.
boost::asio::async_write(socket_, request_,
boost::bind(&client::handle_write_request, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
}
}
void handle_write_request(const boost::system::error_code& err)
{
if (!err)
{
// Read the response status line. The response_ streambuf will
// automatically grow to accommodate the entire line. The growth may be
// limited by passing a maximum size to the streambuf constructor.
boost::asio::async_read_until(socket_, response_, "\r\n",
boost::bind(&client::handle_read_status_line, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error write req: " << err.message() << "\n";
}
}
void handle_read_status_line(const boost::system::error_code& err)
{
if (!err)
{
// Check that response is OK.
std::istream response_stream(&response_);
std::string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
if (!response_stream || http_version.substr(0, 5) != "HTTP/")
{
std::cout << "Invalid response\n";
return;
}
if (status_code != 200)
{
std::cout << "Response returned with status code ";
std::cout << status_code << "\n";
return;
}
std::cout << "Status code: " << status_code << "\n";
// Read the response headers, which are terminated by a blank line.
boost::asio::async_read_until(socket_, response_, "\r\n\r\n",
boost::bind(&client::handle_read_headers, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_read_headers(const boost::system::error_code& err)
{
if (!err)
{
// Process the response headers.
std::istream response_stream(&response_);
std::string header;
while (std::getline(response_stream, header) && header != "\r")
std::cout << header << "\n";
std::cout << "\n";
// Write whatever content we already have to output.
if (response_.size() > 0)
std::cout << &response_;
// Start reading remaining data until EOF.
boost::asio::async_read(socket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&client::handle_read_content, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err << "\n";
}
}
void handle_read_content(const boost::system::error_code& err)
{
if (!err)
{
// Write all of the data that has been read so far.
std::cout << &response_;
// Continue reading remaining data until EOF.
boost::asio::async_read(socket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&client::handle_read_content, this,
boost::asio::placeholders::error));
}
else if (err != boost::asio::error::eof)
{
std::cout << "Error: " << err << "\n";
}
}
tcp::resolver resolver_;
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::streambuf request_;
boost::asio::streambuf response_;
};
int main(int argc, char* argv[])
{
try
{
//boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12);
ctx.set_default_verify_paths();
boost::asio::io_service io_service;
client c(io_service, ctx, "www.realmofthemadgod.com", "/");
io_service.run();
}
catch (std::exception& e)
{
std::cout << "Exception: " << e.what() << "\n";
}
return 0;
}
I did some digging and learned that the switch -servername enables something called server name indication (SNI) and I found a way to enable it in code.
This is added to the client constructor:
// 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((int)ERR_get_error(), boost::asio::error::get_ssl_category());
throw boost::system::system_error(ec);
}
The code still doesn't quite work (it waits for an async callback to happen and times out) but this solves the original problem presented in the question.
Related
I am writing a c++ project to forward clients websocket requests to server and return their message to each other as soon as each one, this code handles multi clients, but for connecting each client to server async_read stays on the first endpoint, therefore next endpoints however are connected but blocked for reading, by the way every thing is asynchronous.
// my server
class websocket_session : public std::enable_shared_from_this<websocket_session>
{
std::string response = "";
std::shared_ptr<websocket::stream<beast::tcp_stream>> ws_;
boost::beast::multi_buffer buffer_;
boost::beast::multi_buffer buffer_client;
std::string meth = "", endpoint = "", message = "", host = "", port = "";
shared_ptr<SocketClient> javaClient;
net::io_context ioc_client;
public:
// Take ownership of the socket
explicit websocket_session(tcp::socket &&socket)
{
ws_ = make_shared<websocket::stream<beast::tcp_stream>>(std::move(socket));
}
// Start the asynchronous accept operation
template <class Body, class Allocator>
void do_accept(http::request<Body, http::basic_fields<Allocator>> req)
{
stringstream temprequest;
temprequest << req.target();
endpoint = temprequest.str();
temprequest.str(std::string());
temprequest << req.method();
meth = temprequest.str();
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
//Get IP and port in case endpoint is eligible
if (EndPointSelection::getWebsocketHost(endpoint, meth, req.version(), host, port) == false)
{
std::cerr << "\n\t Websocket is NOT Authorized!!\n";
return;
}
// Set suggested timeout settings for the websocket
ws_->set_option(websocket::stream_base::timeout::suggested(beast::role_type::server));
// Set a decorator to change the Server of the handshake
ws_->set_option(websocket::stream_base::decorator(
[](websocket::response_type &res) {
res.set(http::field::server,
std::string(BOOST_BEAST_VERSION_STRING) +
" advanced-server");
}));
// Accept the websocket handshake
ws_->async_accept(req, beast::bind_front_handler(&websocket_session::on_accept, shared_from_this()));
}
private:
void on_accept(beast::error_code ec)
{
if (ec)
fail(ec, "accept");
std::cout << "\n\t websocket_session::" << __FUNCTION__ << " Endpoint: " << endpoint << "\n";
javaClient = std::make_shared<SocketClient>(ioc_client);
javaClient->run(host.c_str(), port.c_str(), endpoint.c_str());
javaClient->setWebsocket(ws_);
ioc_client.run();
do_read();
}
void do_read()
{
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
// Read message from client and keep into our buffer
ws_->async_read(buffer_, beast::bind_front_handler(&websocket_session::on_read, shared_from_this()));
}
void on_read(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
// This indicates that the websocket_session was closed
if (ec == websocket::error::closed)
return;
if (ec)
fail(ec, "read");
ws_->text(ws_->got_text());
if (ws_->got_text())
{
std::cout << "\n\t Received Message from Client: " << boost::beast::buffers_to_string(buffer_.data()) << "\n";
sleep(1);
}
ioc_client.stop();
//ws_->async_read(buffer_, beast::bind_front_handler(&websocket_session::on_read, shared_from_this()));
}
void on_write(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
if (ec)
fail(ec, "write");
// Clear the buffer
buffer_client.consume(buffer_client.size());
// Do another read
do_read();
}
};
//my client
void SocketClient::run(char const *host, char const *port, char const *endpoint)
{
// Save these for later
host_ = host;
endpoint_ = endpoint;
std::cout << "\n run Endpoint:" << endpoint_ << "\n";
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Look up the domain name
resolver_.async_resolve(host, port, beast::bind_front_handler(&SocketClient::on_resolve, shared_from_this()));
}
void SocketClient::setWebsocket(std::shared_ptr<websocket::stream<beast::tcp_stream>> ws_cl)
{
ws_client = ws_cl;
};
void SocketClient::getBuffer(boost::beast::multi_buffer &buffer_client)
{
buffer_client = buffer_;
}
void SocketClient::writeBuffer(boost::beast::multi_buffer buffer_client)
{
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
ws_.write(buffer_client.data());
// ws_.async_write(buffer_client.data(), beast::bind_front_handler(&SocketClient::on_write, shared_from_this()));
//ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::on_resolve(beast::error_code ec, tcp::resolver::results_type results)
{
if (ec)
fail(ec, "SocketClient::on_resolve Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Set the timeout for the operation
beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
// Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(ws_).async_connect(results, beast::bind_front_handler(&SocketClient::on_connect, shared_from_this()));
}
void SocketClient::on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep)
{
if (ec)
fail(ec, "connect");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Turn off the timeout on the tcp_stream, because
// the websocket stream has its own timeout system.
beast::get_lowest_layer(ws_).expires_never();
// Set suggested timeout settings for the websocket
ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
// Set a decorator to change the User-Agent of the handshake
ws_.set_option(websocket::stream_base::decorator(
[](websocket::request_type &req) {
req.set(http::field::user_agent,
std::string(BOOST_BEAST_VERSION_STRING) +
" websocket-client-async");
}));
// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
// See https://tools.ietf.org/html/rfc7230#section-5.4
host_ += ':' + std::to_string(ep.port());
// Perform the websocket handshake
ws_.async_handshake(host_, endpoint_,
beast::bind_front_handler(
&SocketClient::on_handshake,
shared_from_this()));
}
void SocketClient::on_handshake(beast::error_code ec)
{
if (ec)
fail(ec, "SocketClient::on_handshake Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::on_write(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
buffer_.consume(buffer_.size());
if (ec)
fail(ec, "SocketClient::on_write Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Read a message from server and keep it into our buffer
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::do_read()
{
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::on_read(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
std::cout << "\nHi from SocketClient::" << __FUNCTION__ << "\n";
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
response_.append(boost::beast::buffers_to_string(buffer_.data())) ;
if (ec)
{
fail(ec, "SocketClient::on_read Error: ");
return;
}
if (response_.empty() == false)
{
std::cout << "\nServer: " << response_ << "\t Size: " << buffer_.size() << "\n";
ws_client->write(net::buffer(response_));
response_="";
}
buffer_.consume(buffer_.size());
sleep(1);
if (ws_client->got_text())
{
std::cout << "\n CLIENT Message\n ";
ws_.write(buffer_.data());
}
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::close_connection()
{
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Close the WebSocket connection
ws_.async_close(websocket::close_code::normal, beast::bind_front_handler(&SocketClient::on_close, shared_from_this()));
};
void SocketClient::on_close(beast::error_code ec)
{
if (ec)
fail(ec, "SocketClient::on_close Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// If we get here then the connection is closed gracefully
// The make_printable() function helps print a ConstBufferSequence
std::cout << beast::make_printable(buffer_.data()) << std::endl;
}
It took me a while to understand what your code is doing - as you left some very relevant portions of your code out. And it appears you've got some boiler plate code that is effectively dead code. The bottom line is this:
I think you need to remove the ioc_client from websocket_session. Introducing a layered set of io_context instances is going to block subsequent connections and just introduce other issues.
I suspect you are trying to create your own instance of io_context because you don't have access to the io_context that's in the run state several layers down your call stack. But I think can get a reference to it.
From the original socket passed into websocket_session, invoke socket.get_executor() (or is it socket.get_executor.context() ?) to get the execution context. That will enable you to create your socket in SocketClient. Don't invoke run on it. It's already in the run state. If that doesn't work, find a way to get the original io_context passed up to your websocket_session.
So I am trying to make a telnet client that connects to some address part for work and part for Boost::Asio learning purpose.
My small project has three handlers:
Resolve handler:
void resolverHandler(const boost::system::error_code& ec, ip::tcp::resolver::iterator iter) {
if (ec) {
cout << "Resolved failed with message: " << ec.message() << endl;
}
else {
ip::tcp::endpoint endpoint = *iter;
cout << "Connection to: " << endpoint.address() << ":" << endpoint.port() << endl;
tcpSocket.async_connect(endpoint, connectHandler);
}
}
Connect handler
void connectHandler(const boost::system::error_code& ec) {
if (ec) {
cout << "Connect failed with message: " << ec.message() << endl;
}
else {
cout << "Connection established" << endl;
tcpSocket.async_read_some(buffer(_data), readHandler);
}
}
Read handler:
void readHandler(const boost::system::error_code& ec, size_t amountOfBytes) {
if (ec) {
cout << "Read failed with message: " << ec.message() << endl;
}
else {
cout << amountOfBytes << endl;
cout << _data.data() << endl;
tcpSocket.async_read_some(buffer(_data), readHandler);
}
}
And this is my main function:
io_service ioservice;
ip::tcp::resolver resolver(ioservice);
ip::tcp::socket tcpSocket(ioservice);
array<char, 16> _data;
ip::tcp::resolver::query query("192.168.200.200", "23");
int main() {
resolver.async_resolve(query, resolverHandler);
ioservice.run();
return 0;
}
But I always get garbage like this:
Connection to: 192.168.206.226:23
Connection established
15
² ² ²# ²' ²$
I admit that I am new to telnet, but I am not sure why do I get this response ? Not sure if I need to null terminate the data that I receive before printing it, but even like that I have the same response.
Here is the normal response I should receive - tried with Windows Telnet:
Welcome Message (localhost) (ttyp0)
login:
Apreciate if someone has any ideas on what to do.
I need to write a class that handle ssl connection (read/write char array) to a black box server. I need to implement disconnect/connect function. But it ain't work as expected.
Use case:
User connect to server (worked: can send and receive messages)
User disconnect to server, wait for a while then reconnect again. (Failed: it only works when the duration is short: like 10 secs. If longer than that handle_connect() return error connection timed out)
Here is the source code to the class and how I use it:
boost::asio::io_service &mioService;
SSLHandler* mpSSLConnection;
void Connector::setupConnection()
{
try{
std::string port = std::to_string(mPort);
boost::asio::ip::tcp::resolver resolver(mioService);
boost::asio::ip::tcp::resolver::query query(mHost, port);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context context(boost::asio::ssl::context::sslv23);
// context.load_verify_file("key.pem");
if(mpSSLConnection == nullptr)
mpSSLConnection = new SSLHandler(mioService,context,iterator,this);
}catch (std::exception& e){
std::cerr << "Exception: " << e.what() << "\n";
}
// actually this line is called from outside the func
mpSSLConnection->connectToServer();
}
and disconnect like this
void Connector::disconnect()
{
isSetDisconnectedToSrv = true;
mpSSLConnection->setIsDestructing(true);
QThread::msleep(500);
delete mpSSLConnection;
mpSSLConnection = nullptr;
// setupConnection();
isConnectedToServer =false; // we did delete the object handle the ssl connection so...
mpHandler->onServerDisconnected(); // just report to upper layer
}
Finally, the class source code:
#ifndef SSLHANDLER_H
#define SSLHANDLER_H
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <queue>
#include <boost/lockfree/spsc_queue.hpp>
class Connector;
const int READ_SIZE =0;
const int READ_MSG=1;
class SSLHandler
{
public:
SSLHandler(boost::asio::io_service& io_service, boost::asio::ssl::context& context, boost::asio::ip::tcp::resolver::iterator endpoint_iterator, Connector* pConnector)
: socket_(io_service, context) , mEndpointIterator (endpoint_iterator) , mpConnector (pConnector),
timer_{ io_service},
isConnectionOk {false}
{
LOG_TRACE << "creating new sslhandler";
socket_.set_verify_mode(boost::asio::ssl::context::verify_none);
socket_.set_verify_callback(boost::bind(&SSLHandler::verify_certificate, this, _1, _2));
mode = READ_SIZE;
}
~SSLHandler();
bool verify_certificate(bool preverified, boost::asio::ssl::verify_context& ctx);
void handle_connect(const boost::system::error_code& error);
void handle_handshake(const boost::system::error_code& error);
void handle_write(const boost::system::error_code& error, size_t bytes_transferred);
void handle_write_auth(const boost::system::error_code& error, size_t bytes_transferred);
void handle_read_msgsize(const boost::system::error_code& error, size_t bytes_transferred);
void handle_read_message(const boost::system::error_code& error, size_t bytes_transferred);
void connectToServer();
void do_reconnect();
void handle_reconnect_timer(boost::system::error_code ec);
void writeMessage(std::vector<char> &array);
void setRequestMsg(std::vector<char> &&array);
void setIsDestructing(bool value);
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::ip::tcp::resolver::iterator mEndpointIterator;
boost::asio::deadline_timer timer_;
char reply_[0x1 << 16]; //=65356 bytes
int mode;
uint32_t size;
std::vector<char> requestMsg;
std::vector<char> replyMsg;
Connector* mpConnector; // ptr to object compose message
std::queue<std::vector < char> > mQueueMsg;
bool isConnectionOk;
bool isDestructing =false;
private:
void writeMessageWithQueue(std::vector<char> &array);
};
#endif // SSLHANDLER_H
#include "sslhandler.h"
#include "connector.h"
#include "BoostLogger.h"
#include <QThread>
#include "boost/enable_shared_from_this.hpp"
SSLHandler::~SSLHandler()
{
LOG_FATAL << "ssl handler shutdown";
if(isConnectionOk){
socket_.lowest_layer().close();
boost::system::error_code ec;
socket_.shutdown(ec);
if(ec){
LOG_FATAL << "ssl handler socket shutdown with err: " << ec.message();
}
LOG_TRACE << "ssl handler shutdown complete";
}
}
bool SSLHandler::verify_certificate(bool preverified, boost::asio::ssl::verify_context &ctx)
{
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying:\n" << subject_name << std::endl;
return preverified;
}
void SSLHandler::handle_connect(const boost::system::error_code &error)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
LOG_TRACE << "get past destructing";
if(!error){
isConnectionOk = true;
LOG_TRACE << "Connection OK!" << std::endl;
socket_.async_handshake(boost::asio::ssl::stream_base::client, boost::bind(&SSLHandler::handle_handshake, this, boost::asio::placeholders::error));
}else{
LOG_FATAL << "Connect failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_handshake(const boost::system::error_code &error)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if(!error){
std::cout << "Sending request: " << std::endl;
boost::asio::async_write(socket_,
boost::asio::buffer(requestMsg.data(), requestMsg.size()),
boost::bind(&SSLHandler::handle_write_auth, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}else{
LOG_FATAL << "Handshake failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_write(const boost::system::error_code &error, size_t bytes_transferred)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (error) {
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
Q_UNUSED(bytes_transferred);
usleep(1e4);
if (!mQueueMsg.empty()) {
mQueueMsg.pop();
if(!mQueueMsg.empty()){
auto msg = mQueueMsg.front();
writeMessageWithQueue(msg);
}
}
else{
LOG_ERROR << "Empty queue messages!";
}
}
void SSLHandler::handle_write_auth(const boost::system::error_code &error, size_t bytes_transferred)
{
usleep(1e5);
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (!error){
if(mode==READ_SIZE){
mode = READ_MSG;
std::cerr << "\nSending request read size OK!\n" << std::endl;
// char respond[bytes_transferred] = "";
boost::asio::async_read(socket_, boost::asio::buffer(reply_,4),
boost::bind(&SSLHandler::handle_read_msgsize,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
// std::cerr << "respond is " ;
}
}else{
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_msgsize(const boost::system::error_code &error, size_t bytes_transferred)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (!error){
//first 4 bytes contain size of message
size = getFirstFour();
mode = READ_SIZE;
boost::asio::async_read(socket_, boost::asio::buffer(reply_,size),
boost::bind(&SSLHandler::handle_read_message,
this,
// mWriteId++,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}else{
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_message(const boost::system::error_code &error, size_t bytes_transferred)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (!error){
replyMsg.clear();
replyMsg.assign(reply_,reply_+ size);
mpConnector->setReadMsg(replyMsg);
mode = READ_SIZE;
// read next message size
boost::asio::async_read(socket_, boost::asio::buffer(reply_,4),
boost::bind(&SSLHandler::handle_read_msgsize,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}else{
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::connectToServer()
{
// boost::asio::ip::tcp::resolver r(socket_.get_io_service());
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
boost::asio::async_connect(socket_.lowest_layer(), mEndpointIterator, boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));
LOG_TRACE << "async_connect called";
}
void SSLHandler::do_reconnect()
{
// return;
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
// socket_.shutdown();
isConnectionOk = false;
else{
socket_.lowest_layer().cancel();
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&SSLHandler::handle_reconnect_timer, this, boost::asio::placeholders::error()));
}
}
void SSLHandler::handle_reconnect_timer(boost::system::error_code ec)
{
if(!ec){
connectToServer();
}
else{
LOG_TRACE << "Error with reconnect timer : " << ec.message();
}
}
void SSLHandler::writeMessageWithQueue(std::vector<char> &array)
{
// std::cerr << "write : " << (void*) array.data() << " | " << array.size() << std::endl;
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
boost::asio::async_write(socket_,
boost::asio::buffer(array.data(), array.size()),
boost::bind(&SSLHandler::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void SSLHandler::writeMessage(std::vector<char> &array)
{
if(mQueueMsg.size()==0){
mQueueMsg.push(array);
writeMessageWithQueue(array);
}
else
mQueueMsg.push(array);
}
void SSLHandler::setRequestMsg(std::vector<char> &&array)
{
requestMsg = std::move(array);
}
void SSLHandler::setIsDestructing(bool value)
{
LOG_INFO << "ssl connection destructing set as " << value;
isDestructing = value;
if(isDestructing == true){
if(isConnectionOk){
socket_.lowest_layer().cancel();
// socket_.shutdown();
LOG_INFO << "ssl connection destructing get pass shutdown";
}
}
}
PS: Here is the handler tracking which is weird, after (call) disconnect() and reconnect again: no hander was fired, so no handshake after connection is established :(
#asio|1521627714.863517|0|resolver#0x7fbe2affc898.cancel Empty fz
params!Empty fz rules!Empty fz param!#asio____________ socket_:
0x7fbe10005150 #asio____________192.168.2.36
#asio|1521627714.864161|0*1|socket#0x7fbe10005150.async_connect #asio:
connect called #asio|1521627714.865136|>1|ec=system:0
#asio|1521627714.865375|1*2|socket#0x7fbe10005150.async_send
#asio|1521627714.865416|<1|
#asio|1521627714.865421|>2|ec=system:0,bytes_transferred=517
#asio|1521627714.865429|2*3|socket#0x7fbe10005150.async_receive
#asio|1521627714.865451|<2|
#asio|1521627714.866829|>3|ec=system:0,bytes_transferred=994
#asio|1521627714.867764|3*4|socket#0x7fbe10005150.async_send
#asio|1521627714.867792|<3|
#asio|1521627714.867798|>4|ec=system:0,bytes_transferred=326
#asio|1521627714.867809|4*5|socket#0x7fbe10005150.async_receive
#asio|1521627714.867817|<4|
#asio|1521627714.870094|>5|ec=system:0,bytes_transferred=234
#asio|1521627714.870271|5*6|socket#0x7fbe10005150.async_send
#asio|1521627714.870318|<5|
#asio|1521627714.870333|>6|ec=system:0,bytes_transferred=154
#asio|1521627714.970430|6*7|socket#0x7fbe10005150.async_receive
#asio|1521627714.970443|<6|
#asio|1521627714.970449|>7|ec=system:0,bytes_transferred=138
#asio|1521627714.970470|7*8|socket#0x7fbe10005150.async_receive
#asio|1521627714.970475|<7|
#asio|1521627714.970479|>8|ec=system:0,bytes_transferred=0
#asio|1521627714.971418|8*9|socket#0x7fbe10005150.async_send
#asio|1521627714.971771|8*10|deadline_timer#0x5290628.async_wait
#asio|1521627714.972004|8*11|socket#0x7fbe10005150.async_receive
#asio|1521627714.972012|<8|
#asio|1521627714.972017|>9|ec=system:0,bytes_transferred=138
#asio|1521627714.982098|9*12|socket#0x7fbe10005150.async_send
#asio|1521627714.982115|<9|
#asio|1521627714.982121|>12|ec=system:0,bytes_transferred=138
#asio|1521627714.992214|12*13|socket#0x7fbe10005150.async_send
#asio|1521627714.992244|<12|
#asio|1521627714.992255|>11|ec=system:0,bytes_transferred=292
#asio|1521627714.992278|11*14|socket#0x7fbe10005150.async_receive
#asio|1521627714.992284|<11|
#asio|1521627714.992290|>13|ec=system:0,bytes_transferred=186
#asio|1521627715.002355|<13|
#asio|1521627715.002363|>14|ec=system:0,bytes_transferred=0
#asio|1521627715.002469|14*15|socket#0x7fbe10005150.async_receive
#asio|1521627715.002479|<14|
#asio|1521627715.002487|>15|ec=system:0,bytes_transferred=0
#asio|1521627715.002495|15*16|socket#0x7fbe10005150.async_receive
#asio|1521627715.002500|<15|
#asio|1521627715.002505|>16|ec=system:0,bytes_transferred=0
#asio|1521627715.002550|16*17|socket#0x7fbe10005150.async_receive
#asio|1521627715.002561|<16|
#asio|1521627715.002566|>17|ec=system:0,bytes_transferred=154
#asio|1521627715.002581|17*18|socket#0x7fbe10005150.async_receive
#asio|1521627715.002586|<17|
#asio|1521627715.002590|>18|ec=system:0,bytes_transferred=0
#asio|1521627715.002636|18*19|socket#0x7fbe10005150.async_receive
#asio|1521627715.002653|<18| #asio|1521627721.861983|>10|ec=system:0
#asio|1521627721.862105|10*20|socket#0x7fbe10005150.async_send
#asio|1521627721.862139|10|deadline_timer#0x5290628.cancel
#asio|1521627721.862144|10*21|deadline_timer#0x5290628.async_wait
#asio|1521627721.862258|<10|
#asio|1521627721.862268|>20|ec=system:0,bytes_transferred=138
#asio|1521627721.872365|<20|
#asio|1521627721.872398|>19|ec=system:0,bytes_transferred=138
#asio|1521627721.872436|19*22|socket#0x7fbe10005150.async_receive
#asio|1521627721.872443|<19|
#asio|1521627721.872447|>22|ec=system:0,bytes_transferred=0
#asio|1521627721.872503|22*23|socket#0x7fbe10005150.async_receive
#asio|1521627721.872515|<22| #asio|1521627724.861966|>21|ec=system:0
#asio|1521627724.862091|21*24|socket#0x7fbe10005150.async_send
#asio|1521627724.862148|21|deadline_timer#0x5290628.cancel
#asio|1521627724.862157|21*25|deadline_timer#0x5290628.async_wait
#asio|1521627724.862272|<21|
#asio|1521627724.862286|>24|ec=system:0,bytes_transferred=138
#asio|1521627724.872375|<24|
#asio|1521627724.872409|>23|ec=system:0,bytes_transferred=138
#asio|1521627724.872457|23*26|socket#0x7fbe10005150.async_receive
#asio|1521627724.872465|<23|
#asio|1521627724.872469|>26|ec=system:0,bytes_transferred=0
#asio|1521627724.872510|26*27|socket#0x7fbe10005150.async_receive
#asio|1521627724.872516|<26| #asio|1521627727.861968|>25|ec=system:0
#asio|1521627727.862084|25*28|socket#0x7fbe10005150.async_send
#asio|1521627727.862120|25|deadline_timer#0x5290628.cancel
#asio|1521627727.862125|25*29|deadline_timer#0x5290628.async_wait
#asio|1521627727.862204|<25|
#asio|1521627727.862211|>28|ec=system:0,bytes_transferred=138
#asio|1521627727.872283|<28|
#asio|1521627727.872314|>27|ec=system:0,bytes_transferred=138
#asio|1521627727.872362|27*30|socket#0x7fbe10005150.async_receive
#asio|1521627727.872366|<27|
#asio|1521627727.872371|>30|ec=system:0,bytes_transferred=0
#asio|1521627727.872412|30*31|socket#0x7fbe10005150.async_receive
#asio|1521627727.872418|<30| #asio|1521627730.861967|>29|ec=system:0
#asio|1521627730.862072|29*32|socket#0x7fbe10005150.async_send
#asio|1521627730.862118|29|deadline_timer#0x5290628.cancel
#asio|1521627730.862125|29*33|deadline_timer#0x5290628.async_wait
#asio|1521627730.862217|<29|
#asio|1521627730.862227|>32|ec=system:0,bytes_transferred=138
#asio|1521627730.872315|<32|
#asio|1521627730.872360|>31|ec=system:0,bytes_transferred=138
#asio|1521627730.872406|31*34|socket#0x7fbe10005150.async_receive
#asio|1521627730.872412|<31|
#asio|1521627730.872416|>34|ec=system:0,bytes_transferred=0
#asio|1521627730.872458|34*35|socket#0x7fbe10005150.async_receive
#asio|1521627730.872465|<34| #asio|1521627733.862001|>33|ec=system:0
#asio|1521627733.862114|33*36|socket#0x7fbe10005150.async_send
#asio|1521627733.862153|33|deadline_timer#0x5290628.cancel
#asio|1521627733.862158|33*37|deadline_timer#0x5290628.async_wait
#asio|1521627733.862244|<33|
#asio|1521627733.862250|>36|ec=system:0,bytes_transferred=138
#asio|1521627733.872342|<36|
#asio|1521627733.872379|>35|ec=system:0,bytes_transferred=138
#asio|1521627733.872416|35*38|socket#0x7fbe10005150.async_receive
#asio|1521627733.872422|<35|
#asio|1521627733.872424|>38|ec=system:0,bytes_transferred=0
#asio|1521627733.872461|38*39|socket#0x7fbe10005150.async_receive
#asio|1521627733.872466|<38| #asio|1521627736.861976|>37|ec=system:0
#asio|1521627736.862158|37*40|socket#0x7fbe10005150.async_send
#asio|1521627736.862235|37|deadline_timer#0x5290628.cancel
#asio|1521627736.862242|37*41|deadline_timer#0x5290628.async_wait
#asio|1521627736.862406|<37|
#asio|1521627736.862414|>40|ec=system:0,bytes_transferred=138
#asio|1521627736.872497|<40|
#asio|1521627736.872555|>39|ec=system:0,bytes_transferred=138
#asio|1521627736.872622|39*42|socket#0x7fbe10005150.async_receive
#asio|1521627736.872638|<39|
#asio|1521627736.872641|>42|ec=system:0,bytes_transferred=0
#asio|1521627736.872720|42*43|socket#0x7fbe10005150.async_receive
#asio|1521627736.872726|<42| #asio|1521627739.861978|>41|ec=system:0
#asio|1521627739.862096|41*44|socket#0x7fbe10005150.async_send
#asio|1521627739.862144|41|deadline_timer#0x5290628.cancel
#asio|1521627739.862148|41*45|deadline_timer#0x5290628.async_wait
#asio|1521627739.862243|<41|
#asio|1521627739.862249|>44|ec=system:0,bytes_transferred=138
#asio|1521627739.872335|<44|
#asio|1521627739.872375|>43|ec=system:0,bytes_transferred=138
#asio|1521627739.872421|43*46|socket#0x7fbe10005150.async_receive
#asio|1521627739.872425|<43|
#asio|1521627739.872429|>46|ec=system:0,bytes_transferred=0
#asio|1521627739.872477|46*47|socket#0x7fbe10005150.async_receive
#asio|1521627739.872492|<46| #asio|1521627742.861953|>45|ec=system:0
#asio|1521627742.862121|45*48|socket#0x7fbe10005150.async_send
#asio|1521627742.862204|45|deadline_timer#0x5290628.cancel
#asio|1521627742.862211|45*49|deadline_timer#0x5290628.async_wait
#asio|1521627742.862392|<45|
#asio|1521627742.862406|>48|ec=system:0,bytes_transferred=138
#asio|1521627742.872491|<48|
#asio|1521627742.872543|>47|ec=system:0,bytes_transferred=138
#asio|1521627742.872592|47*50|socket#0x7fbe10005150.async_receive
#asio|1521627742.872600|<47|
#asio|1521627742.872605|>50|ec=system:0,bytes_transferred=0
#asio|1521627742.872675|50*51|socket#0x7fbe10005150.async_receive
#asio|1521627742.872688|<50|
#asio|1521627745.316714|0|socket#0x7fbe10005150.close
#asio|1521627745.316777|>51|ec=system:125,bytes_transferred=0
#asio|1521627745.316858|<51| #asio: ~SSLHandler
#asio|1521627745.817594|0|resolver#0x7fbe00ff8758.cancel
#asio|1521627745.861965|>49|ec=system:0 #asio|1521627745.861984|<49|
#asio|1521627749.757091|0|resolver#0x7fbe00ff8648.cancel
#asio____________ socket_: 0x7fbde4008890
#asio____________192.168.2.36
#asio|1521627749.757178|0*52|socket#0x7fbde4008890.async_connect
#asio: connect called
I fixed the sample to be selfcontained, and ran it against a demo server:
#include <iostream>
#include <sstream>
#include <vector>
#ifdef STANDALONE
namespace {
struct LogTx {
std::stringstream _ss;
std::ostream& _os;
bool _armed = true;
LogTx(std::ostream& os) : _os(os) {}
LogTx(LogTx&& rhs) : _ss(std::move(rhs._ss)), _os(rhs._os) { rhs._armed = false; }
~LogTx() { if (_armed) _os << _ss.rdbuf() << std::endl; }
LogTx operator<<(std::ostream&(&v)(std::ostream&)) { _ss << v; return std::move(*this); }
template <typename T> LogTx operator<<(T&& v) { _ss << v; return std::move(*this); }
};
}
# define LOG_FATAL LogTx(std::cerr) << "FATAL: "
# define LOG_TRACE LogTx(std::clog) << "TRACE: "
# define LOG_ERROR LogTx(std::cerr) << "ERROR: "
# define LOG_INFO LogTx(std::clog) << "INFO: "
# define Q_UNUSED(a) static_cast<void>(a)
namespace {
struct Connector {
void sendDisconnectedStatus() { LOG_INFO << "Disconnected"; }
void setReadMsg(std::vector<char> const& v) { LOG_INFO << "response: '" << std::string(v.begin(), v.end()) << "'"; }
};
}
#endif
#ifndef SSLHANDLER_H
#define SSLHANDLER_H
#include <boost/endian/arithmetic.hpp> // for big_uint32_t
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <queue>
#include <string>
#include <thread>
const int READ_SIZE = 0;
const int READ_MSG = 1;
class SSLHandler {
public:
SSLHandler(boost::asio::io_service &io_service, boost::asio::ssl::context &context,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator, Connector *pConnector)
: socket_(io_service, context), mEndpointIterator(endpoint_iterator),
mpConnector(pConnector), timer_{ io_service }, isConnectionOk{ false }
{
LOG_TRACE << "creating new sslhandler";
socket_.set_verify_mode(boost::asio::ssl::context::verify_none);
socket_.set_verify_callback(boost::bind(&SSLHandler::verify_certificate, this, _1, _2));
mode = READ_SIZE;
}
~SSLHandler();
bool verify_certificate(bool preverified, boost::asio::ssl::verify_context &ctx);
void handle_connect(const boost::system::error_code &error);
void handle_handshake(const boost::system::error_code &error);
void handle_write(const boost::system::error_code &error, size_t bytes_transferred);
void handle_write_auth(const boost::system::error_code &error, size_t bytes_transferred);
void handle_read_msgsize(const boost::system::error_code &error, size_t bytes_transferred);
void handle_read_message(const boost::system::error_code &error, size_t bytes_transferred);
void connectToServer();
void do_reconnect();
void handle_reconnect_timer(boost::system::error_code ec);
void writeMessage(std::vector<char> &array);
void setRequestMsg(std::vector<char> &&array);
void setIsDestructing(bool value);
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::ip::tcp::resolver::iterator mEndpointIterator;
Connector *mpConnector; // ptr to object compose message
boost::asio::deadline_timer timer_;
char reply_[0x1 << 16]; //=65356 bytes
size_t getFirstFour() {
return *boost::asio::buffer_cast<boost::endian::big_uint32_t *>(boost::asio::buffer(reply_));
};
int mode;
uint32_t size;
std::vector<char> requestMsg;
std::vector<char> replyMsg;
std::queue<std::vector<char> > mQueueMsg;
bool isConnectionOk;
bool isDestructing = false;
private:
void writeMessageWithQueue(std::vector<char> &array);
};
#endif // SSLHANDLER_H
//#include "sslhandler.h"
//#include "connector.h"
//#include "BoostLogger.h"
//#include <QThread>
//#include "boost/enable_shared_from_this.hpp"
SSLHandler::~SSLHandler() {
LOG_FATAL << "ssl handler shutdown";
if (isConnectionOk) {
socket_.lowest_layer().close();
boost::system::error_code ec;
socket_.shutdown(ec);
if (ec) {
LOG_FATAL << "ssl handler socket shutdown with err: " << ec.message();
}
LOG_TRACE << "ssl handler shutdown complete";
}
}
bool SSLHandler::verify_certificate(bool preverified, boost::asio::ssl::verify_context &ctx) {
char subject_name[256];
X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying:\n" << subject_name << std::endl;
return preverified;
}
void SSLHandler::handle_connect(const boost::system::error_code &error) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
LOG_TRACE << "get past destructing";
if (!error) {
isConnectionOk = true;
LOG_TRACE << "Connection OK!" << std::endl;
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&SSLHandler::handle_handshake, this, boost::asio::placeholders::error));
} else {
LOG_FATAL << "Connect failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_handshake(const boost::system::error_code &error) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
std::cout << "Sending request: " << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(requestMsg.data(), requestMsg.size()),
boost::bind(&SSLHandler::handle_write_auth, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
LOG_FATAL << "Handshake failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (error) {
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
Q_UNUSED(bytes_transferred);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (!mQueueMsg.empty()) {
mQueueMsg.pop();
if (!mQueueMsg.empty()) {
auto msg = mQueueMsg.front();
writeMessageWithQueue(msg);
}
} else {
LOG_ERROR << "Empty queue messages!";
}
}
void SSLHandler::handle_write_auth(const boost::system::error_code &error, size_t bytes_transferred) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
if (mode == READ_SIZE) {
mode = READ_MSG;
std::cerr << "\nSending request read size OK!\n" << std::endl;
// char respond[bytes_transferred] = "";
boost::asio::async_read(socket_, boost::asio::buffer(reply_, 4),
boost::bind(&SSLHandler::handle_read_msgsize, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
// std::cerr << "respond is " ;
}
} else {
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_msgsize(const boost::system::error_code &error, size_t bytes_transferred) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
// first 4 bytes contain size of message
size = getFirstFour();
LOG_TRACE << "Decoded size: " << size;
mode = READ_SIZE;
boost::asio::async_read(socket_, boost::asio::buffer(reply_, size),
boost::bind(&SSLHandler::handle_read_message, this,
// mWriteId++,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_message(const boost::system::error_code &error, size_t bytes_transferred) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
replyMsg.clear();
replyMsg.assign(reply_, reply_ + size);
mpConnector->setReadMsg(replyMsg);
mode = READ_SIZE;
// read next message size
boost::asio::async_read(socket_, boost::asio::buffer(reply_, 4),
boost::bind(&SSLHandler::handle_read_msgsize, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::connectToServer() {
// boost::asio::ip::tcp::resolver r(socket_.get_io_service());
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
boost::asio::async_connect(socket_.lowest_layer(), mEndpointIterator,
boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));
LOG_TRACE << "async_connect called";
}
void SSLHandler::do_reconnect() {
// socket_.shutdown();
isConnectionOk = false;
// return;
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
} else {
socket_.lowest_layer().cancel();
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&SSLHandler::handle_reconnect_timer, this, boost::asio::placeholders::error()));
}
}
void SSLHandler::handle_reconnect_timer(boost::system::error_code ec) {
if (!ec) {
connectToServer();
} else {
LOG_TRACE << "Error with reconnect timer : " << ec.message();
}
}
void SSLHandler::writeMessageWithQueue(std::vector<char> &array) {
// std::cerr << "write : " << (void*) array.data() << " | " << array.size() << std::endl;
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
boost::asio::async_write(socket_, boost::asio::buffer(array.data(), array.size()),
boost::bind(&SSLHandler::handle_write, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void SSLHandler::writeMessage(std::vector<char> &array) {
if (mQueueMsg.size() == 0) {
mQueueMsg.push(array);
writeMessageWithQueue(array);
}
else
mQueueMsg.push(array);
}
void SSLHandler::setRequestMsg(std::vector<char> &&array) { requestMsg = std::move(array); }
void SSLHandler::setIsDestructing(bool value) {
LOG_INFO << "ssl connection destructing set as " << value;
isDestructing = value;
if (isDestructing == true) {
if (isConnectionOk) {
socket_.lowest_layer().cancel();
// socket_.shutdown();
LOG_INFO << "ssl connection destructing get pass shutdown";
}
}
}
int main() {
Connector c;
boost::asio::io_service svc;
boost::asio::ssl::context ctx(boost::asio::ssl::context_base::sslv23_client);
SSLHandler h(svc, ctx, boost::asio::ip::tcp::resolver{svc}.resolve({{}, 6767}), &c);
h.setRequestMsg({'h','e','l','l','o','\n','w','o','r','l','d'});
h.connectToServer();
svc.run();
}
Now, I didn't see any issue, regardless of how long it took for the server to be back after interruption.
You mention
Hi, I mean user actively call disconnect function (not connection dropped by network like last time). If the user call disconnect() then wait more than 10 secs, then call connect() the connection is failed with error: connection timed out. – Patton 11 hours ago
There's no such disconnect() function in your code, nor can I see how it's likely implemented. Therefore, either
the problem is on the server-side (which stops accepting connections or completing SSL handshake?)
the problem is in the code you don't show
I am trying to implement a ssl-client with async socket which send and receive protobuf messages from a server. The format of the message is the first 4 bytes is indicated the size of the message follows (X), the rest is the respond message which has X bytes.
The problem is I don't know how to take the first 4 bytes to get the message size to read the rest.
async_read() need the exact size, which is 4 bytes but I don't know what to do next?
async_read_until() required a terminated character which the message doesn't have.
How to do this? I came from Java and C# and not really familiar with boost and C++.
The source code is attached below. Pls search "TODO" to reach the LOC that do the read things...
PS: I can't change anything from the server. Only have the binaries :(
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include "client/connection/authentication.pb.h"
#include "client/connection/authentication.pb.cc"
#include "client/msg.pb.h"
#include "client/msg.pb.cc"
#include "client/common.pb.h"
#include "client/common.pb.cc"
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)
{
socket_.set_verify_mode(boost::asio::ssl::context::verify_none);
socket_.set_verify_callback(boost::bind(&client::verify_certificate, this, _1, _2));
boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error));
}
bool verify_certificate(bool preverified, boost::asio::ssl::verify_context& ctx)
{
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying:\n" << subject_name << std::endl;
return preverified;
}
void handle_connect(const boost::system::error_code& error)
{
if(!error){
std::cout << "Connection OK!" << std::endl;
socket_.async_handshake(boost::asio::ssl::stream_base::client, boost::bind(&client::handle_handshake, this, boost::asio::placeholders::error));
}else{
std::cout << "Connect failed: " << error.message() << std::endl;
}
}
void handle_handshake(const boost::system::error_code& error)
{
if(!error){
std::cout << "Sending request: " << std::endl;
// std::stringstream request_;
// request_ << "GET /api/0/data/ticker.php HTTP 1.1\r\n";
// request_ << "Host: mtgox.com\r\n";
// request_ << "Accept-Encoding: *\r\n";
// request_ << "\r\n";
protobuf::Message msg;
char *data = new char[dlen];
bool ok = msg.SerializeToArray(data,dlen);
// uint32_t n = htonl(dlen);
uint32_t n = dlen;
// char bytes[4];
// bytes[0] = (n >> 24) & 0xFF;
// bytes[1] = (n >> 16) & 0xFF;
// bytes[2] = (n >> 8) & 0xFF;
// bytes[3] = n & 0xFF;
int sizeOfPacket = 4 + dlen;
char* rq = new char[sizeOfPacket];
// strncpy(rq, bytes, 4);
rq[0] = (n >> 24) & 0xFF;
rq[1] = (n >> 16) & 0xFF;
rq[2] = (n >> 8) & 0xFF;
rq[3] = n & 0xFF;
strncpy(rq + 4,data, dlen);
// request_<< rq;
// std::cerr << request_.str() << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(rq,sizeOfPacket), boost::bind(&client::handle_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}else{
std::cout << "Handshake failed: " << error.message() << std::endl;
}
}
void handle_write(const boost::system::error_code& error, size_t bytes_transferred)
{
if (!error){
std::cout << "Sending request OK!" << std::endl;
char respond[4] = "";
boost::asio::async_read(socket_, boost::asio::buffer(respond,4),
boost::bind(&client::handle_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
// std::cerr << "respond is " << respond;
//TODO
}else{
std::cout << "Write failed: " << error.message() << std::endl;
}
}
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.message() << std::endl;
}
}
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
char reply_[0x1 << 16];
};
int main(int argc, char* argv[])
{
try{
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query("192.168.2.32", "443");
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context context(boost::asio::ssl::context::sslv23);
// context.load_verify_file("key.pem");
client c(io_service, context, iterator);
io_service.run();
}catch (std::exception& e){
std::cerr << "Exception: " << e.what() << "\n";
}
std::cin.get();
return 0;
}
Update: Are there anything wrong in this implement?
void handle_write(const boost::system::error_code& error, size_t bytes_transferred)
{
if (!error){
std::cout << "Sending request OK!" << std::endl;
char respond[4] = "";
boost::asio::async_read(socket_, boost::asio::buffer(respond,4),
boost::bind(&client::handle_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
int sizeOfMessage = getSizeFromHeader(respond);//need implement
char message[sizeOfMessage] ="";
boost::asio::async_read(socket_, boost::asio::buffer(message,sizeOfMessage),
boost::bind(&client::handle_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
decodeMessage(message);
// std::cerr << "respond is " << respond;
//TODO
}else{
std::cout << "Write failed: " << error.message() << std::endl;
}
}
char message[sizeOfMessage] ="";
boost::asio::async_read(socket_, boost::asio::buffer(message,sizeOfMessage),
boost::bind(&client::handle_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
decodeMessage(message);
This can't ever work, since message is a local variable so it ceases to exist before the async operation runs/completes.
Same here:
char respond[4] = "";
ba::async_read(
socket_, ba::buffer(respond, 4),
boost::bind(&client::handle_read, this, ba::placeholders::error, ba::placeholders::bytes_transferred));
// std::cerr << "respond is " << respond;
// TODO
Funny thing in handle_read you don't use that, you use reply_ which is a member. That's a lot better.
char *data = new char[dlen];
bool ok = msg.SerializeToArray(data, dlen);
dlen is not defined. Even if you do define it, ok is never checked. Ouch. Next, data is never deleted[]-ed.
uint32_t n = dlen;
// char bytes[4];
// bytes[0] = (n >> 24) & 0xFF;
// bytes[1] = (n >> 16) & 0xFF;
// bytes[2] = (n >> 8) & 0xFF;
// bytes[3] = n & 0xFF;
int sizeOfPacket = 4 + dlen;
char *rq = new char[sizeOfPacket];
// strncpy(rq, bytes, 4);
rq[0] = (n >> 24) & 0xFF;
rq[1] = (n >> 16) & 0xFF;
rq[2] = (n >> 8) & 0xFF;
rq[3] = n & 0xFF;
strncpy(rq + 4, data, dlen);
By now you copied it all (even if serialization failed) another time, and have another memory leak, this time 4 bytes larger.
// std::cerr << request_.str() << std::endl;
Writing binary data to std::cerr is not going to be pretty (and will usually just not work because of 0-chars).
Now finally when you handle_read you're likely to encounter SSL SSL_R_SHORT_READ because of the fact that your buffer has a fixed size and response might be smaller (if the response is larger, you won't know). So handle that case:
if (!error || error == ssl::error::stream_errors::stream_truncated) {
Somewhat Fixed Sample
Live On Coliru
//#include "client/common.pb.cc"
//#include "client/common.pb.h"
//#include "client/connection/authentication.pb.cc"
//#include "client/connection/authentication.pb.h"
//#include "client/msg.pb.cc"
//#include "client/msg.pb.h"
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <cstring>
namespace ba = boost::asio;
namespace ssl = ba::ssl;
using ba::ip::tcp;
static constexpr int dlen = 300;
namespace protobuf {
struct Message {
bool SerializeToArray(char* p, size_t n) {
strncpy(p, "hello world\n", n);
return true;
}
};
} // namespace protobuf
class client {
public:
client(ba::io_service &io_service, ssl::context &context, tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context)
{
socket_.set_verify_mode(ssl::context::verify_none);
socket_.set_verify_callback(boost::bind(&client::verify_certificate, this, _1, _2));
ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this, ba::placeholders::error));
}
bool verify_certificate(bool preverified, ssl::verify_context &ctx) {
char subject_name[256];
X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying:\n" << subject_name << std::endl;
return preverified;
}
void handle_connect(const boost::system::error_code &error) {
if (!error) {
std::cout << "Connection OK!" << std::endl;
socket_.async_handshake(ssl::stream_base::client,
boost::bind(&client::handle_handshake, this, ba::placeholders::error));
} else {
std::cout << "Connect failed: " << error.message() << std::endl;
}
}
void handle_handshake(const boost::system::error_code &error) {
if (!error) {
std::cout << "Sending request: " << std::endl;
// prepare request_ buffer
{
request_.consume(request_.size());
std::ostream os(&request_);
os << "GET /api/0/data/ticker.php HTTP 1.1\r\n";
os << "Host: mtgox.com\r\n";
os << "Accept-Encoding: *\r\n";
os << "\r\n";
std::vector<char> data(dlen + 4); // perhaps avoid SerializeToArray and skip the copy
// fill length
{
uint32_t n = htonl(dlen);
data[0] = (n >> 24) & 0xFF;
data[1] = (n >> 16) & 0xFF;
data[2] = (n >> 8) & 0xFF;
data[3] = n & 0xFF;
}
// fill message
{
protobuf::Message msg;
bool ok = msg.SerializeToArray(data.data() + 4, dlen);
if (!ok)
throw std::runtime_error("Do something!");
}
// write buffer
os.write(data.data(), data.size());
}
ba::async_write(
socket_, request_,
boost::bind(&client::handle_write, this, ba::placeholders::error, ba::placeholders::bytes_transferred));
} else {
std::cout << "Handshake failed: " << error.message() << std::endl;
}
}
void handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
if (!error) {
std::cout << "Sending request OK! (" << bytes_transferred << " bytes)" << std::endl;
ba::async_read(
socket_, ba::buffer(reply_),
boost::bind(&client::handle_read, this, ba::placeholders::error, ba::placeholders::bytes_transferred));
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
}
void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
if (!error || error == ssl::error::stream_errors::stream_truncated) {
std::cout << "Reply: ";
std::cout.write(reply_, bytes_transferred);
std::cout << "\n";
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}
private:
ssl::stream<tcp::socket> socket_;
ba::streambuf request_;
char reply_[1 << 16];
};
int main() {
try {
ba::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("192.168.2.32", "443");
tcp::resolver::iterator iterator = resolver.resolve(query);
ssl::context context(ssl::context::sslv23);
// context.load_verify_file("key.pem");
client c(io_service, context, iterator);
io_service.run();
} catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}
When tested against a local test SSL server (passphrase "test"):
openssl s_server -accept 6767 -cert ~/custom/boost/libs/asio/example/cpp03/ssl/server.pem -CAfile ~/custom/boost/libs/asio/example/cpp03/ssl/ca.pem
It printed:
./sotest
Connection OK!
Verifying:
/C=AU/ST=NSW/L=Sydney/O=asio
Sending request:
Sending request OK! (380 bytes)
error: asio.ssl:335544539 (short read)
Reply: asdasdasdasdasd
Obviously, asdasdasdasdasd is the response I typed in the s_server end.
I am having a problem while creating a client program that sends requests. The request are using keep alive TCP HTTP connections. When a connection is closed(due to timeout or max being hit), I try and start a new connection if none are available, and resend the request. The connect works fine however, when I try and send the write, nothing is sent(according to Wireshark), but my error code for the write was a success. The receiving server does not receive any information either. Here is the main parts of my code:
void request_handler::send_1(std::vector<std::string> *bid_vector, std::string request_path, boost::mutex *bids_mutex)
{
try
{
boost::asio::streambuf request;
std::ostream request_stream(&request);
std::string reply_information;
request_stream << "GET /tests HTTP/1.1\r\n";
request_stream << "Host: 10.1.10.160\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: keep-alive\r\n\r\n";
server1_mutex_.lock();
if(server1_available_map_.size() == 0)
{
server1_mutex_.unlock();
persistent_connection *new_connection = new persistent_connection("10.1.10.160","80");
if(new_connection->send(request, reply_information))
{
server1_mutex_.lock();
server1_available_map_[new_connection->get_id()] = new_connection;
server1_mutex_.unlock();
}
}
else
{
persistent_connection *current_connection = (*(server1_available_map_.begin())).second;
server1_available_map_.erase(current_connection->get_id());
server1_mutex_.unlock();
int retry_counter = 20;
while(!current_connection->query_rtb(request, reply_information) && --retry_counter != 0)
{
delete current_connection;
server1_mutex_.lock();
if(server1_available_map_.size() == 0)
{
server1_mutex_.unlock();
current_connection = new persistent_connection("10.1.10.160","80");
}
else
{
current_connection = (*(server1_available_map_.begin())).second;
server1_available_map_.erase(current_connection->get_id());
server1_mutex_.unlock();
}
}
//Could not connect to 20 connections
if(retry_counter == 0)
{
Log::fatal("Could not connect in 20 tries");
delete current_connection;
return;
}
server1_mutex_.lock();
server1_available_map_[current_connection->get_id()] = current_connection;
server1_mutex_.unlock();
}
bids_mutex->lock();
bid_vector->push_back(reply_information);
bids_mutex->unlock();
}
catch(boost::thread_interrupted& e)
{
std::cout << "before cancel 1" << std::endl;
return;
}
catch(...)
{
std::cout << "blah blah blah" << std::endl;
}
}
And my persistent_connection class
persistent_connection::persistent_connection(std::string ip, std::string port):
io_service_(), socket_(io_service_), host_ip_(ip)
{
boost::uuids::uuid uuid = boost::uuids::random_generator()();
id_ = boost::lexical_cast<std::string>(uuid);
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::resolver::query query(host_ip_,port);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ip::tcp::endpoint endpoint = *iterator;
socket_.async_connect(endpoint, boost::bind(&persistent_connection::handler_connect, this, boost::asio::placeholders::error, iterator));
io_service_.run();
}
void persistent_connection::handler_connect(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if(ec)
{
std::cout << "Couldn't connect" << ec << std::endl;
return;
}
else
{
boost::asio::socket_base::keep_alive keep_option(true);
socket_.set_option(keep_option);
std::cout << "Connect handler" << std::endl;
}
}
bool persistent_connection::send(boost::asio::streambuf &request_information, std::string &reply_information)
{
std::cout << "DOING QUERY in " << id_ << std::endl;
boost::system::error_code write_ec, read_ec;
try
{
std::cout << "Before write" << std::endl;
boost::asio::write(socket_, request_information, write_ec);
std::cout << write_ec.message() << std::endl;
}catch(std::exception& e)
{
std::cout << "Write exception: " << e.what() << std::endl;
}
if(write_ec)
{
std::cout <<"Write error: " << write_ec.message() << std::endl;
return false;
}
boost::array<char,8192> buf;
buf.assign(0);
try
{
std::cout << "Before read" << std::endl;
boost::asio::read(socket_, boost::asio::buffer(buf), boost::asio::transfer_at_least(1), read_ec);
std::cout << read_ec.message() << std::endl;
}catch(std::exception& e)
{
std::cout << "Read exception: " << e.what() << std::endl;
}
if(read_ec)
{
std::cout << "Read error: " << read_ec.message() << std::endl;
return false;
}
reply_information = buf.data();
return true;
}
std::string persistent_connection::get_id()
{
return id_;
}
The path for this to happen is if server1_available_map_.size() > 0, and if the while executes, and fails. And then if the size == 0 on the second server1_available_map_.size();
The output for the call is:
DOING QUERY in 69a8f0ab-2a06-45b4-be26-37aea6d93ff2
Before write
Success
Before read
End of file
Read error: End of file
Connect handler
DOING QUERY in 4eacaa96-1040-4878-8bf5-c29b87fa1232
Before write
Success
Before read
Which shows that the first connection gets an end of file(connection closed by server on other end). The second connection connects fine(Connect handler message), and the query is executed in the second connection(different id), and the write is apparently successful, and the program hangs on the read(because there is nothing to read).
Does anyone have any idea why this would be happening? Is there something I seem to be doing wrong?
Thank you
It looks like you are passing the same boost::asio::streambuf to multiple write calls.
boost::asio::write(socket_, request_information, write_ec);
The contents of the buffer are consumed by the first call to boost::asio::write. This effectively empties the buffer so that there is nothing left to send. Pass a const string if you want to use the same buffer for multiple writes.