From bd21db125f9944ef278c0cdb8fb50195d4cb6a21 Mon Sep 17 00:00:00 2001 From: pajlada Date: Sun, 7 Dec 2025 17:26:43 +0100 Subject: [PATCH] fix(websocket): try connecting to all hosts (#6631) --- CHANGELOG.md | 1 + .../detail/WebSocketConnectionImpl.cpp | 58 ++++++++++++++++--- .../detail/WebSocketConnectionImpl.hpp | 21 ++++++- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a24d8c..8baa7655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Bugfix: Fixed settings occasionally not opening when clicking on "Manage Accounts" in the account switcher. (#6543) - Bugfix: Fixed font change not resulting in forced layout update. (#6536) - Bugfix: Fixed scrollbar rect computation potentially resulting in overflows. (#6547) +- Bugfix: Fixed websocket connections failing if the DNS resolving gave bad hosts. (#6631) - Bugfix: Forward query params to websocket URLs. (#6141) - Bugfix: Fixed Lua errors from handlers of HTTP requests not being logged. (#6452) - Bugfix: Fixed restore button not showing on Windows. (#6565) diff --git a/src/common/websockets/detail/WebSocketConnectionImpl.cpp b/src/common/websockets/detail/WebSocketConnectionImpl.cpp index b4b9d8d1..d5dd8d32 100644 --- a/src/common/websockets/detail/WebSocketConnectionImpl.cpp +++ b/src/common/websockets/detail/WebSocketConnectionImpl.cpp @@ -7,6 +7,8 @@ #include #include +using namespace std::literals::string_view_literals; + namespace chatterino::ws::detail { namespace asio = boost::asio; @@ -93,30 +95,61 @@ void WebSocketConnectionHelper::onResolve( return; } - qCDebug(chatterinoWebsocket) << *this << "Resolved host"; + this->resolvedEndpoints = results; + + this->tryConnect(this->resolvedEndpoints.begin()); +} + +template +void WebSocketConnectionHelper::tryConnect( + boost::asio::ip::tcp::resolver::results_type::const_iterator + endpointIterator) +{ + if (endpointIterator == this->resolvedEndpoints.end()) + { + this->fail("Ran out of resolved endpoints"sv, u"connect"); + return; + } + + const auto &endpoint = endpointIterator->endpoint(); + + qCDebug(chatterinoWebsocket) + << *this << "connect to" << endpoint.address().to_string(); beast::get_lowest_layer(this->stream) .expires_after(std::chrono::seconds{30}); + beast::get_lowest_layer(this->stream) - .async_connect(results, beast::bind_front_handler( - &WebSocketConnectionHelper::onTcpHandshake, - this->shared_from_this())); + .async_connect(endpoint, + beast::bind_front_handler( + &WebSocketConnectionHelper::onTcpHandshake, + this->shared_from_this(), endpointIterator)); } template void WebSocketConnectionHelper::onTcpHandshake( - boost::system::error_code ec, - const asio::ip::tcp::resolver::endpoint_type &ep) + boost::asio::ip::tcp::resolver::results_type::const_iterator + endpointIterator, + boost::system::error_code ec) { + const auto &ep = endpointIterator->endpoint(); + if (ec) { - this->fail(ec, u"TCP handshake"); + qCDebug(chatterinoWebsocket) + << *this << "error in tcp handshake" << ep.address().to_string() + << ec.message(); + this->tryConnect(++endpointIterator); return; } - qCDebug(chatterinoWebsocket) << *this << "TCP handshake done"; + qCDebug(chatterinoWebsocket) + << *this << "TCP handshake done" << ep.address().to_string(); this->options.url.setPort(ep.port()); + // We are done with the endpoints, we can clear the range. + this->resolvedEndpoints = {}; + this->derived()->afterTcpHandshake(); } @@ -319,9 +352,16 @@ void WebSocketConnectionHelper::closeImpl() template void WebSocketConnectionHelper::fail( boost::system::error_code ec, QStringView op) +{ + this->fail(ec.message(), op); +} + +template +void WebSocketConnectionHelper::fail(std::string_view ec, + QStringView op) { qCWarning(chatterinoWebsocket) - << *this << "Failed:" << op << QUtf8StringView(ec.message()); + << *this << "Failed:" << op << QUtf8StringView(ec); if (this->stream.is_open()) { this->closeImpl(); diff --git a/src/common/websockets/detail/WebSocketConnectionImpl.hpp b/src/common/websockets/detail/WebSocketConnectionImpl.hpp index f9405ec3..4e6b7209 100644 --- a/src/common/websockets/detail/WebSocketConnectionImpl.hpp +++ b/src/common/websockets/detail/WebSocketConnectionImpl.hpp @@ -41,6 +41,7 @@ protected: Derived *derived(); void fail(boost::system::error_code ec, QStringView op); + void fail(std::string_view ec, QStringView op); void doWsHandshake(); void closeImpl(); @@ -57,15 +58,31 @@ private: void onResolve(boost::system::error_code ec, const boost::asio::ip::tcp::resolver::results_type &results); + + /// Initialize a TCP connection to the given endpoint iterator in `resolvedEndpoints`. + /// + /// If we failed to connect, try the next iterator. + /// + /// If the iterator is invalid, we have run out of endpoints to try, and deem this + /// connection a failure. + void tryConnect(boost::asio::ip::tcp::resolver::results_type::const_iterator + endpointIterator); void onTcpHandshake( - boost::system::error_code ec, - const boost::asio::ip::tcp::resolver::endpoint_type &ep); + boost::asio::ip::tcp::resolver::results_type::const_iterator + endpointIterator, + boost::system::error_code ec); void onWsHandshake(boost::system::error_code ec); void onReadDone(boost::system::error_code ec, size_t bytesRead); void onWriteDone(boost::system::error_code ec, size_t bytesWritten); friend Derived; + + /// A range of endpoints from the `onResolve` function. + /// + /// When we successfully resolve the host, we try to connect by + /// iterating over these results. + boost::asio::ip::tcp::resolver::results_type resolvedEndpoints; }; /// A WebSocket connection over TLS (wss://).