|
Boost Users : |
Subject: [Boost-users] [boost::asio] [stream::ssl] stackful coroutine yield from SSL handshake in servername callback
From: SamJam Liddicott (samjam_at_[hidden])
Date: 2015-11-03 10:07:43
I'm testing a spoofing MITM SSL proxy based on boost::asio and
boost::asio::stream::ssl, using boost 1.55
I register the tlsext_servername_callback, so that I can observe the called
name during SSL handshake with the client.
In the callback, I have the information needed to either get/create an
appropriate SSL_CTX with certificates to use with the client, or start an
SSL handshake with the server, to obtain the server certificate for
re-signing and then put that back into the SSL_CTX to be passed to the
client, before returning the tlsext_servername callback.
It is clear that I must block the tlsext_servername callback from returning
until I have the new SSL_CTX ready, which could take some time if I need to
wait on a handshake with the real server.
I can block return by performing a blocking handshake with the server, but
then I consume one of the io_service threads merely blocking, and I don't
want to waste them like that.
I can successfully run it all as a new thread using blocking IO, in this
form:
ssl_connect(...) {
boost::thread([...]() ({
sni_callback = [...](std::string server_name) {
server.handshake(...);
SSL_set_SSL_CTX(client.native_handle(), server.ctx));
};
client_socket.handshake(...);
handler(); // really done by io_service.post
}).detach();
}
But I want to use stackful coroutines in the belief that thay are less
costly and that I will be able to have more connections in progress (though
I may have to link with BoringSSL).
Using a coroutine spawn, I can convert the handshake with the server, like
this:
ssl_connect(...) {
boost::asio::spawn(my_strand, [...](boost::asio::yield_context yield) {
sni_callback = [...](std::string server_name) {
server.async_handshake(..., yield[ec]); // <---- works nicely
SSL_set_SSL_CTX(client.native_handle(), server.ctx));
};
client_socket.handshake(...);
handler(); // really done by io_service.post
});
}
but the handshake with the client must remain blocking, otherwise, with:
client_socket.async_handshake(..., yield[ec]);
I get segfaults in the coroutine with unhelpful stack traces, mentioning
only some SSL cleanup functions. However if the client connection hangs,
then the client handshake can still hang and block a worker thread.
My guess is that if the handshake with the client is not blocking, the SNI
callback does not occur on the coroutine, and so I cannot properly block
the return and possibly the use of yield in the SNI callback is invalid.
If there is a solution that doesn't involve patching either libssl or
stream::ssl, it will involve some asio_invoke_handler hooks.
However, for that to work, the coroutine would have to be activated, but
not to complete the async_handshake call. Instead it would be activated to
handle the handshake read completion, leading to the parsing, SNI callback
& return, further handshake write, and yield back to the io_service. I
don't know if the coroutine/strand implementation can even do that -- be
activated for work and not for return.
So my first question is: is it likely possible to make coroutine do this?
Looking in boost source detail/io.hpp for case engine::want_input_and_retry
I notice:
next_layer_.async_read_some(
boost::asio::buffer(core_.input_buffer_),
BOOST_ASIO_MOVE_CAST(io_op)(*this));
which seems to make no attempt to use asio_hanlder_invoke to allow it to be
hooked, so that might need fixing.
What sort of asio_handler_invoke hook would cause the handler to be invoked
on the coroutine? Could strand.dispatch() do it? If it would, I'm not sure
how to write such a hook either to work with argument dependent lookup, or
to invoke in the coroutine.
Otherwise I may as well stick to starting a scope-limited thread, and using
blocking handshake operations.
But I like to know the limits...
Samjam
Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net