1

I am relatively new to C and C++ programming and I am trying to connect to a server using Websockets using the boost and beast libraries in C++. I followed the tutorial here but I get the following error

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  The WebSocket handshake was declined by the remote peer [boost.beast.websocket:20]

This is my code so far. I need help figuring out the problem and also if you guys can include resources where I can become better at C++ and networking in general I would really appreciate it.

#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <rapidjson/allocators.h>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <ta_libc.h>
#include <cstdlib>
#include <iostream>
#include <string>

using namespace std;
using namespace rapidjson;

namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;

int main() {

    string host = "ws-feed.exchange.coinbase.com";
    auto const port = "443";
    const char doc[] =
        "{ \'type\': \'subscribe\', \'product_ids\': [\'ETH-USD\'], \'channels\': [\'matches\'] }";

    Document document;
    document.Parse(doc);

    net::io_context ioc;
    tcp::resolver resolver{ioc};
    websocket::stream<tcp::socket> ws{ioc};

    auto const results = resolver.resolve(host, port);

    auto ep = net::connect(ws.next_layer(), results);

    host += ":" + to_string(ep.port());

    ws.set_option(websocket::stream_base::decorator(
        [](websocket::request_type& req)
            {
                req.set(http::field::user_agent,
                string(BOOST_BEAST_VERSION_STRING) +
                " websocket-client-coro");
            }));

    ws.handshake(host, "/");

    ws.write(net::buffer(string(doc)));

    beast::flat_buffer buffer;

    ws.read(buffer);

    ws.close(websocket::close_code::normal);

    cout << beast::make_printable(buffer.data()) << endl;

} // main

1 Answers1

2

Like others said, you don't use SSL where the server requires it.

Here's a demo. The subtler point is that the server required SNI.

#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp> // for header-only lib
#include <iostream>

namespace beast     = boost::beast;
namespace http      = beast::http;
namespace websocket = beast::websocket;
namespace net       = boost::asio;
namespace ssl       = net::ssl;
namespace json      = boost::json;

using tcp = boost::asio::ip::tcp;

int main() {
    std::string host = "ws-feed.exchange.coinbase.com";
    auto const  port = "443";

    json::value doc = {
        {"type", "subscribe"},
        {"product_ids", {"ETH-USD"}},
        {"channels", {"matches"}},
    };

    net::io_context ioc;
    tcp::resolver   resolver{ioc};
    ssl::context    ctx(ssl::context::sslv23_client);
    ctx.set_default_verify_paths();
    websocket::stream<ssl::stream<tcp::socket>> ws{ioc, ctx};

    auto const results = resolver.resolve(host, port);

    // connect raw socket
    auto& raw = beast::get_lowest_layer(ws);
    auto ep = net::connect(raw, results);

    // Set SNI Hostname (many hosts need this to handshake successfully)
    if (!SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str())) {
        throw boost::system::system_error(
            ::ERR_get_error(), boost::asio::error::get_ssl_category());
    }
    ws.next_layer().handshake(ssl::stream_base::client);

    // ssl handshake
    host += ":" + std::to_string(ep.port());

    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-coro");
        }));

    // websocket handshake
    ws.handshake(host, "/");

    std::cout << doc << std::endl;
    ws.write(net::buffer(serialize(doc)));

    beast::flat_buffer buffer;

    ws.read(buffer);

    std::cout << beast::make_printable(buffer.data()) << std::endl;

    ws.close(websocket::close_code::normal);
}

Results in:

{"type":"subscribe","product_ids":["ETH-USD"],"channels":["matches"]}
{"type":"subscriptions","channels":[{"name":"matches","product_ids":["ETH-USD"]}]}
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  stream truncated [asio.ssl.stream:1]

The error indicates that the server doesn't gracefully shutdown the connection. That's probably by design.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks @sehe I used flags -lcrypto -lssl for compiling btw – Isaac Martinez Jr. Mora May 27 '22 at 01:54
  • How did you know the server required SNI? Can some servers requires SSL but not SNI? – user997112 Jun 26 '22 at 15:52
  • 2
    Because it rejected the handshake. Not all servers require SNI, in fact for a large period of time SNI didn't exist. However in the world of shared hosting SNI made a lot of sense, so it has quietly taken over, and in general it's a good fist line of defense against rogue traffic. – sehe Jun 26 '22 at 16:48
  • Thanks for the response. So from the OP's perspective, next he would have tried SSL and if the server still rejected the request, then you would try SNI? – user997112 Jun 26 '22 at 17:23
  • 1
    @user997112 Indeed. The SSL was documented, the SNI requires an educated guess. The Beast examples show this consistently, I think, so it's possible to "accidentally" get it even without knowledge of SNI – sehe Jun 26 '22 at 20:23
  • @sehe Thanks again, your replies have always been appreciated. Sorry if this is obvious, but which part of the code is the SNI? I'm just trying to understand how the SSL but no-SLI would look, compared to SSL with SNI. – user997112 Jun 26 '22 at 21:50
  • 1
    @user997112 Good point, it's the call to `SSL_set_tlsext_host_name` I will add a comment accordingly. The beast examples already do this, see e.g. https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/client/sync-ssl/http_client_sync_ssl.cpp – sehe Jun 26 '22 at 23:16