[boost::asio] [stream::ssl] stackful coroutine yield from SSL handshake in servername callback
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
participants (1)
-
SamJam Liddicott