zaphoyd / websocketpp

C++ websocket client/server library
http://www.zaphoyd.com/websocketpp
Other
7.07k stars 1.98k forks source link

How to get a hostname from connection handle in on_tls_init callback #1049

Open karlisolte opened 2 years ago

karlisolte commented 2 years ago

Hello! I would like to verify TLS certificate. A convenient way of doing that seems to be ctx->set_verify_callback(asio::ssl::rfc2818_verification("host.name"));, but it needs a hostname passed to it.

Handler for set_tls_init_handler has connection_hdl parameter and I would expect to be able to somehow retrieve hostname from it, but I have not succeeded. In particular:

auto wss_connection = client_endpoint_tls.get_con_from_hdl(hdl);
ws_connection->get_uri(); // returns nullptr
ws_connection->get_request().get_uri(); // returns emtpy string
std::shared_ptr<asio::ssl::context> on_tls_init(websocketpp::connection_hdl hdl)
{
    auto ctx = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
    try {
        ctx->set_options(asio::ssl::context::default_workarounds
            | asio::ssl::context::no_sslv2
            | asio::ssl::context::no_sslv3
            | asio::ssl::context::single_dh_use);

        ctx->set_verify_mode(asio::ssl::verify_peer);
        ctx->set_default_verify_paths();
        ctx->set_verify_callback(asio::ssl::rfc2818_verification("ws.ifelse.io"));
    } catch (std::exception& e) {
        LOG_ERROR("Couldn't initialize ssl context: {}", e.what());
    }
    return ctx;
}
Jacob-Burckhardt commented 2 years ago

Add a hostname argument as the first arg in your on_tls_init function.

Extract the hostname from the URL that you pass to m_client.get_connection. Pass that hostname here:

m_client.set_tls_init_handler (bind (&on_tls_init, hostname, _1));

karlisolte commented 2 years ago

I am calling set_tls_init_handler as a step during tls_client_endpoint initialization. Binding hostname to on_tls_initcallback requires that hostname is known at the time when set_tls_init_handler is called and also limits endpoint to be able to connect to only this hostname (at least without calling set_tls_init_handler again).

I would much prefer to be able to reuse a single tls client endpoint to be able to connect to different hostnames. Or is it not an intended way to use client endpoint?

Jacob-Burckhardt commented 2 years ago

In the code excerpt below, create_connection calls on_tls_init which occurs before the set_uri call in the excerpt below which explains why get_uri returned null during the call to on_tls_init. That seems to contradict the documentation which says:

A new WebSocket connection is initiated via a three step process. First, a connection request is created by endpoint::get_connection(uri). Next, the connection request is configured. Lastly, the connection request is submitted back to the endpoint via endpoint::connect() which adds it to the queue of new connections to make.

That quote says get_connection makes a request, but it seems to imply the request is not executed until the call to endpoint::connect, but since on_tls_init is called at the request creation stage that shows that it did connect.

You said "calling set_tls_init_handler again". You seem reluctant to do that, and it seems strange you would need to do that, but I think that might be a way to solve your problem.

    connection_ptr get_connection(uri_ptr location, lib::error_code & ec) {
        if (location->get_secure() && !transport_type::is_secure()) {
            ec = error::make_error_code(error::endpoint_not_secure);
            return connection_ptr();
        }

        connection_ptr con = endpoint_type::create_connection();

        if (!con) {
            ec = error::make_error_code(error::con_creation_failed);
            return con;
        }

        con->set_uri(location);

        ec = lib::error_code();
        return con;
    }
karlisolte commented 2 years ago

That quote says get_connection makes a request, but it seems to imply the request is not executed until the call to endpoint::connect, but since on_tls_init is called at the request creation stage that shows that it did connect.

Yes, so in other words handlers are normally set on the connection object before connect():

auto con = client_endpoint.get_connection(uri, ec);
con->set_open_handler();
con->set_fail_handler();
con->set_close_handler();
client_endpoint.connect(con);

but in case of set_tls_init_handler() there is no point to call this on connection object after client_endpoint.get_connection(uri, ec) (even though connection object has such method) as handler will already have executed. Instead we should set it on the endpoint object right before client_endpoint.get_connection(uri, ec);.

neongreen-sc commented 2 months ago

@karlisolte did you ever find a good solution for this?