WebSocket handshake declined by remote peer

397 views Asked by At

So I've been trying to follow the boost code for ssl websocket connections to binance, but I keep getting getting an error when trying to initiate the handshake. I've removed the load_root_certificates function btw because it was saying that the function was undefined and I've seen a post saying that you don't need as it'll load the deafault certificate in my machine. Not sure if the last statement was true though.

#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/core/ostream.hpp>
#include <string>
#include <utility>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <iostream>
#include "JSONParser.hpp"
#include "data.hpp"


#define ONEHOUR_ONEMONTH 672
#define ONEMIN_ONEWEEK 10080
#define ONESEC_ONEDAY 86400

std::string create_subscription_message() {
    boost::property_tree::ptree message;
    message.put("method", "SUBSCRIBE");

    std::vector<std::string> streams = {"btcusdt@kline_1m"};
    boost::property_tree::ptree params;
    for(auto& stream: streams)
        params.push_back(std::make_pair("", boost::property_tree::ptree(stream)));

    message.add_child("params", params);
    message.put("id", 1);

    std::stringstream ss;
    boost::property_tree::write_json(ss, message);
    return ss.str();
}

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp; 

int main() {
    try {
        cData candlesticks(ONEMIN_ONEWEEK);
        std::string s = "{\n  \"e\": \"kline\",\n  \"E\": 123456789,\n  \"s\": \"BNBBTC\",\n  \"k\": {\n    \"t\": 123400000,\n    \"T\": 123460000,\n    \"s\": \"BNBBTC\",\n    \"i\": \"1m\",\n    \"f\": 100,\n    \"L\": 200,\n    \"o\": \"0.0010\",\n    \"c\": \"0.0020\",\n    \"h\": \"0.0025\",\n    \"l\": \"0.0015\",\n    \"v\": \"1000\",\n    \"n\": 100,\n    \"x\": false,\n    \"q\": \"1.0000\",\n    \"V\": \"500\",\n    \"Q\": \"0.500\",\n    \"B\": \"123456\"\n  }\n}";
        candlesticks.addCandlestick(s);
        candlesticks.printCandlestick(candlesticks.accessDataAtIndex(0));
        // WebSocket endpoint
        std::string host = "wss://stream.binance.com";
        std::string port = "443";
        // Create the I/O context
        boost::asio::io_context ioc;

        // Creates SSL context and holds certificate
         ssl::context ctx{ssl::context::tlsv12_client};

    
        tcp::resolver resolver(ioc);
        // Create the WebSocket stream
        websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc, ctx};

        // Resolve the hostname
        auto endpoints = resolver.resolve(host, port);

        // Connect to the first endpoint in the list
        auto ep = net::connect(get_lowest_layer(ws), endpoints);
        
        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");
        host += ':' + std::to_string(ep.port());

        ws.next_layer().handshake(ssl::stream_base::client);
        
        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");
            }));

        boost::beast::error_code ec;

        ws.handshake(host, "/ws/ethusdt@kline_5m", ec);

        if(ec) {
            std::cerr << "Error: " << ec.message() << std::endl;
            return EXIT_FAILURE;
        }
        //subscription message
        std::string subscription_message = create_subscription_message();

        // Send the subscription message
        ws.write(boost::asio::buffer(subscription_message));

        // Receive messages
        for (;;) {
            boost::beast::multi_buffer buffer;
            ws.read(buffer);
            std::cout << boost::beast::make_printable(buffer.data()) << std::endl;
            if (buffer.size() == 0) {
                break;
            }
            auto message = boost::beast::buffers_to_string(buffer.data());
            if (message == "ping") {
                buffer.consume(buffer.size());
                ws.write(boost::asio::buffer("pong"));
            }
        }

    }
    catch (std::exception const& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    
}

Error: The WebSocket handshake was declined by the remote peer

1

There are 1 answers

2
sehe On

The entire problem would seem to be that the host is malformed:

std::string host = "wss://stream.binance.com";

That's a url, not a FQDN or valid IP address. The remainder assumed FQDN (as it is being used for TCP address resolution, SNI and HTTP Host header). Instead I'd expect:

std::string host = "stream.binance.com";

Indeed, with that change, the error changes from

Error: resolve: Host not found (authoritative) [asio.netdb:1 at /home/sehe/custom/superboost/boost/asio/detail/resolver_service.hpp:84:5 in function 'boost::asio::detail::resolver_service<Protocol>::results_type boost::asio::detail::resolver_service<Protocol>::resolve(implementation_type&, const query_type&, boost::system::error_code&)']

Into

{"error":{"code":2,"msg":"Invalid request: request ID must be an unsigned integer"}}
Error: stream truncated [asio.ssl.stream:1]

Which is excellent, because it implies that the connection is accepted, and only your request is invalid. That's the next thing for you to look at.

Tested Code

Made self contained as: Coliru

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>

#include <iomanip>
#include <iostream>

#define ONEHOUR_ONEMONTH 672
#define ONEMIN_ONEWEEK 10080
#define ONESEC_ONEDAY 86400

struct cData {
    //unsigned ticks;
    std::vector<std::string> what_ever;

    cData(unsigned /*t*/) //: ticks(t)
    {}

    void        addCandlestick(std::string_view cs) { what_ever.emplace_back(cs); }
    std::string accessDataAtIndex(size_t idx) const { return what_ever.at(idx); }
    void        printCandlestick(std::string_view cs) const {
        std::cout << "printCandlestick: " << quoted(cs) << std::endl;
    }
};

std::string create_subscription_message() {
    boost::property_tree::ptree message;
    message.put("method", "SUBSCRIBE");

    std::vector<std::string>    streams = {"btcusdt@kline_1m"};
    boost::property_tree::ptree params;
    for (auto& stream : streams)
        params.push_back(std::make_pair("", boost::property_tree::ptree(stream)));

    message.add_child("params", params);
    message.put("id", 1);

    std::stringstream ss;
    boost::property_tree::write_json(ss, message);
    return ss.str();
}

namespace beast     = boost::beast;     // from <boost/beast.hpp>
namespace http      = beast::http;      // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net       = boost::asio;      // from <boost/asio.hpp>
namespace ssl       = boost::asio::ssl; // from <boost/asio/ssl.hpp>
using tcp           = boost::asio::ip::tcp;

int main() {
    try {
        cData       candlesticks(ONEMIN_ONEWEEK);
        std::string s =
            R"({
  "e": "kline",
  "E": 123456789,
  "s": "BNBBTC",
  "k": {
    "t": 123400000,
    "T": 123460000,
    "s": "BNBBTC",
    "i": "1m",
    "f": 100,
    "L": 200,
    "o": "0.0010",
    "c": "0.0020",
    "h": "0.0025",
    "l": "0.0015",
    "v": "1000",
    "n": 100,
    "x": false,
    "q": "1.0000",
    "V": "500",
    "Q": "0.500",
    "B": "123456"
  }
})";
        candlesticks.addCandlestick(s);
        candlesticks.printCandlestick(candlesticks.accessDataAtIndex(0));

        // WebSocket endpoint
        std::string host = "stream.binance.com";
        std::string port = "443";

        boost::asio::io_context ioc;

        ssl::context ctx{ssl::context::tlsv12_client};

        tcp::resolver resolver(ioc);
        websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc, ctx};

        // Resolve the hostname
        auto endpoints = resolver.resolve(host, port);

        // Connect to the first endpoint in the list
        auto ep = net::connect(get_lowest_layer(ws), endpoints);

        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");
        host += ':' + std::to_string(ep.port());

        ws.next_layer().handshake(ssl::stream_base::client);

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

        boost::beast::error_code ec;

        ws.handshake(host, "/ws/ethusdt@kline_5m", ec);

        if (ec) {
            std::cerr << "Error: " << ec.message() << std::endl;
            return EXIT_FAILURE;
        }
        // subscription message
        std::string subscription_message = create_subscription_message();

        // Send the subscription message
        ws.write(boost::asio::buffer(subscription_message));

        // Receive messages
        for (;;) {
            boost::beast::multi_buffer buffer;
            ws.read(buffer);
            std::cout << boost::beast::make_printable(buffer.data()) << std::endl;
            if (buffer.size() == 0) {
                break;
            }
            auto message = boost::beast::buffers_to_string(buffer.data());
            if (message == "ping") {
                buffer.consume(buffer.size());
                ws.write(boost::asio::buffer("pong"));
            }
        }

    } catch (std::exception const& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
}

Local output:

enter image description here