CppServer
CppServer copied to clipboard
How to set SNI & connect to wss:// ?
Hi @chronoxor,
Really liking the design of CppServer!....however, unfortunately, I am running into issues attempting to connect to a wss:// endpoint.
In general, I am unable to get a simple example of connecting working so any help explaining how to correctly resolve and connect to a websocket endpoint specified by a wss:// uri would be greatly appreciated!
For example to connect to wss://ws.okx.com:8443/ws/v5/public (taken from here) I attempted the following based on the wss client example:
// ...int main() {
// Create a new Asio service
auto service = std::make_shared<AsioService>();
service->Start();
// Create and prepare a new SSL client context
auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
context->set_default_verify_paths();
context->set_root_certs();
context->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
context->load_verify_file("../external/CppServer/tools/certificates/ca.pem");
// Use TCPResolver to lookup DNS
// See: https://github.com/chronoxor/CppServer/issues/55
auto resolver = std::make_shared<CppServer::Asio::TCPResolver>(service);
// @NOTE ClientWebsocket is a child class of `public CppServer::WS::WSSClient`
auto ws = std::make_shared<ClientWebsocket>(service, context, "ws.okx.com", 8443);
ws->Connect(resolver);
and the upgrade request attempt:
class ClientWebsocket : public CppServer::WS::WSSClient
{
public:
using CppServer::WS::WSSClient::WSSClient;
protected:
void onWSConnecting(CppServer::HTTP::HTTPRequest &request) override
{
request.SetBegin("GET", "/ws/v5/public", "HTTP/2");
request.SetHeader("Host", address() + ":8443"); // @todo
request.SetHeader("Upgrade", "websocket");
request.SetHeader("Connection", "upgrade");
request.SetHeader("Sec-WebSocket-Key", CppCommon::Encoding::Base64Encode(ws_nonce()));
request.SetHeader("Accept", "/");
// request.SetHeader("Sec-WebSocket-Version", "13");
request.SetHeader("User-Agent", "beast.v1");
}
This did not work, and my comments are:
(1) Do I need to set SNI somewhere on the stream native handle (using SSL_set_tlsext_host_name)
(2) Why (once SNI is set) the upgrade request is not working? If you have any experience with this.
NOTE: something like this seemed to get round initial SSL v3 handshake errors
bool SSLClient::Connect(const std::shared_ptr<TCPResolver>& resolver) {
// ...
std::cout << "Setting SNI Hostname: " << _address << std::endl;
if(SSL_set_tlsext_host_name(_stream.native_handle(), _address.c_str())) {
std::cout << "Success!" << std::endl;
}
else {
std::cout << "Failed!" << std::endl;
}
// ...
Hi,
same here. SNI doesn't seem to work.
I tried with your URL @davidtwomey , but the same happens with wss://api.huobi.com:443/ws, while same implementation works fine with wss://stream.binance.com:443.
I managed to set the server address manually like this, but without effect. No matter if I set it before the connection is started, or later. It prints SNI Failed!:
if (SSL_CTX_ctrl(_ssl_context->native_handle(),
SSL_CTRL_SET_TLSEXT_HOSTNAME,TLSEXT_NAMETYPE_host_name,
(void *) _configuration->getWebSocketServerHost().c_str()))
{
std::cout << "SNI Success!" << std::endl;
}
else {
std::cout << "SNI Failed!" << std::endl;
}
The error on connecting is always the same:
WebSocketClient caught an error with code 167773200 and category 'asio.ssl': sslv3 alert handshake failure (SSL routines)
I don't even get to void onWSConnecting(CppServer::HTTP::HTTPRequest &request) callback.
I just get disconnected right on the connection attempt.
Any idea?
Thank you, Alex
After more research I found out that the following piece of Code now says that SNI is successful, but it is still not working:
if (SSL_set_tlsext_host_name(stream().native_handle(), host.c_str())) {
std::cout << "SNI Success!" << std::endl;
} else {
std::cout << "SNI Failed!" << std::endl;
}
Btw... same code is working on Boost::Beast and connection can be established and data received from the same source:
if ( ! SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str()))
throw beast::system_error(
beast::error_code(
static_cast<int>(::ERR_get_error()),
net::error::get_ssl_category()),
"Failed to set SNI Hostname");
Both native_handle() function documentations say that a pointer of type SSL* is returned:
Example The native_handle() function returns a pointer of type SSL* that is suitable for passing
to functions such as SSL_get_verify_result and
SSL_get_peer_certificate: asio::ssl::stream sock(my_context, ctx); // ... establish connection
and perform handshake ...
if (X509* cert = SSL_get_peer_certificate(sock.native_handle())) {
if (SSL_get_verify_result(sock.native_handle()) == X509_V_OK) {
// ...
}
}