xrpld
Loading...
Searching...
No Matches
ConnectAttempt.cpp
1#include <xrpld/overlay/detail/ConnectAttempt.h>
2
3#include <xrpld/app/main/Application.h>
4#include <xrpld/overlay/Cluster.h>
5#include <xrpld/overlay/Peer.h>
6#include <xrpld/overlay/detail/Handshake.h>
7#include <xrpld/overlay/detail/OverlayImpl.h>
8#include <xrpld/overlay/detail/PeerImp.h>
9#include <xrpld/overlay/detail/ProtocolVersion.h>
10
11#include <xrpl/basics/Log.h>
12#include <xrpl/beast/net/IPAddressConversion.h>
13#include <xrpl/beast/utility/Journal.h>
14#include <xrpl/beast/utility/instrumentation.h>
15#include <xrpl/json/json_reader.h>
16#include <xrpl/json/json_value.h>
17#include <xrpl/peerfinder/Config.h>
18#include <xrpl/peerfinder/Slot.h>
19#include <xrpl/protocol/PublicKey.h>
20#include <xrpl/protocol/tokens.h>
21#include <xrpl/resource/Consumer.h>
22
23#include <boost/asio/bind_executor.hpp>
24#include <boost/asio/buffer.hpp>
25#include <boost/asio/error.hpp>
26#include <boost/asio/io_context.hpp>
27#include <boost/asio/ip/tcp.hpp>
28#include <boost/asio/post.hpp>
29#include <boost/asio/ssl/stream_base.hpp>
30#include <boost/asio/ssl/verify_mode.hpp>
31#include <boost/asio/strand.hpp>
32#include <boost/beast/http/impl/read.hpp>
33#include <boost/beast/http/impl/write.hpp>
34#include <boost/beast/http/status.hpp>
35#include <boost/system/system_error.hpp>
36
37#include <chrono>
38#include <cstddef>
39#include <exception>
40#include <memory>
41#include <optional>
42#include <string>
43#include <utility>
44#include <vector>
45
46namespace xrpl {
47
49 Application& app,
50 boost::asio::io_context& ioContext,
51 endpoint_type remoteEndpoint,
53 shared_context const& context,
54 Peer::id_t id,
56 beast::Journal journal,
57 OverlayImpl& overlay)
58 : Child(overlay)
59 , app_(app)
60 , id_(id)
61 , sink_(journal, OverlayImpl::makePrefix(id))
63 , remoteEndpoint_(std::move(remoteEndpoint))
64 , usage_(usage)
65 , strand_(boost::asio::make_strand(ioContext))
66 , timer_(ioContext)
67 , streamPtr_(
68 std::make_unique<stream_type>(
69 socket_type(std::forward<boost::asio::io_context&>(ioContext)),
70 *context))
71 , socket_(streamPtr_->next_layer().socket())
73 , slot_(slot)
74{
75}
76
78{
79 if (slot_ != nullptr)
80 overlay_.peerFinder().onClosed(slot_);
81 JLOG(journal_.trace()) << "~ConnectAttempt";
82}
83
84void
86{
87 if (!strand_.running_in_this_thread())
88 {
89 boost::asio::post(strand_, [self = shared_from_this()] { self->stop(); });
90 return;
91 }
92 if (socket_.is_open())
93 {
94 JLOG(journal_.debug()) << "Stop";
95 }
96 close();
97}
98
99void
101{
102 setTimer();
103
104 stream_.next_layer().async_connect(
106 boost::asio::bind_executor(
107 strand_, [self = shared_from_this()](error_code const& ec) { self->onConnect(ec); }));
108}
109
110//------------------------------------------------------------------------------
111
112void
114{
115 XRPL_ASSERT(
116 strand_.running_in_this_thread(), "xrpl::ConnectAttempt::close : strand in this thread");
117 if (!socket_.is_open())
118 return;
119
120 try
121 {
122 timer_.cancel();
123 socket_.close();
124 }
125 catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch)
126 {
127 // ignored
128 }
129
130 JLOG(journal_.debug()) << "Closed";
131}
132
133void
135{
136 JLOG(journal_.debug()) << reason;
137 close();
138}
139
140void
142{
143 JLOG(journal_.debug()) << name << ": " << ec.message();
144 close();
145}
146
147void
149{
150 try
151 {
152 timer_.expires_after(std::chrono::seconds(15));
153 }
154 catch (boost::system::system_error const& e)
155 {
156 JLOG(journal_.error()) << "setTimer: " << e.code();
157 return;
158 }
159
160 timer_.async_wait(
161 boost::asio::bind_executor(
162 strand_, [self = shared_from_this()](error_code const& ec) { self->onTimer(ec); }));
163}
164
165void
167{
168 try
169 {
170 timer_.cancel();
171 }
172 catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch)
173 {
174 // ignored
175 }
176}
177
178void
180{
181 if (!socket_.is_open())
182 return;
183
184 if (ec)
185 {
186 // do not initiate shutdown, timers are frequently cancelled
187 if (ec == boost::asio::error::operation_aborted)
188 return;
189
190 // This should never happen
191 JLOG(journal_.error()) << "onTimer: " << ec.message();
192 close();
193 return;
194 }
195 fail("Timeout");
196}
197
198void
200{
201 cancelTimer();
202
203 if (ec)
204 {
205 if (ec == boost::asio::error::operation_aborted)
206 return;
207
208 fail("onConnect", ec);
209 return;
210 }
211
212 if (!socket_.is_open())
213 return;
214
215 // check if connection has really been established
216 socket_.local_endpoint(ec);
217 if (ec)
218 {
219 fail("onConnect", ec);
220 return;
221 }
222
223 setTimer();
224
225 stream_.set_verify_mode(boost::asio::ssl::verify_none);
226 stream_.async_handshake(
227 boost::asio::ssl::stream_base::client,
228 boost::asio::bind_executor(
229 strand_, [self = shared_from_this()](error_code const& ec) { self->onHandshake(ec); }));
230}
231
232void
234{
235 cancelTimer();
236 if (!socket_.is_open())
237 return;
238
239 if (ec)
240 {
241 if (ec == boost::asio::error::operation_aborted)
242 return;
243
244 fail("onHandshake", ec);
245 return;
246 }
247
248 auto const localEndpoint = socket_.local_endpoint(ec);
249 if (ec)
250 {
251 fail("onHandshake", ec);
252 return;
253 }
254
255 if (!overlay_.peerFinder().onConnected(
257 {
258 fail("Duplicate connection");
259 return;
260 }
261
262 auto const sharedValue = makeSharedValue(*streamPtr_, journal_);
263 if (!sharedValue)
264 {
265 close(); // makeSharedValue logs
266 return;
267 }
268
270 !overlay_.peerFinder().config().peerPrivate,
271 app_.config().compression,
272 app_.config().ledgerReplay,
273 app_.config().txReduceRelayEnable,
274 app_.config().vpReduceRelayBaseSquelchEnable);
275
277 req_,
278 *sharedValue,
279 overlay_.setup().networkID,
280 overlay_.setup().publicIp,
281 remoteEndpoint_.address(),
282 app_);
283
284 setTimer();
285 boost::beast::http::async_write(
286 stream_,
287 req_,
288 boost::asio::bind_executor(
289 strand_,
290 [self = shared_from_this()](error_code const& ec, std::size_t) { self->onWrite(ec); }));
291}
292
293void
295{
296 cancelTimer();
297
298 if (!socket_.is_open())
299 return;
300
301 if (ec)
302 {
303 if (ec == boost::asio::error::operation_aborted)
304 return;
305
306 fail("onWrite", ec);
307 return;
308 }
309
310 boost::beast::http::async_read(
311 stream_,
312 readBuf_,
313 response_,
314 boost::asio::bind_executor(
315 strand_,
316 [self = shared_from_this()](error_code const& ec, std::size_t) { self->onRead(ec); }));
317}
318
319void
321{
322 cancelTimer();
323
324 if (!socket_.is_open())
325 return;
326
327 if (ec)
328 {
329 if (ec == boost::asio::error::operation_aborted)
330 return;
331
332 if (ec == boost::asio::error::eof)
333 {
334 JLOG(journal_.debug()) << "EOF";
335 setTimer();
336 stream_.async_shutdown(
337 boost::asio::bind_executor(
338 strand_,
339 [self = shared_from_this()](error_code const& ec) { self->onShutdown(ec); }));
340 return;
341 }
342
343 fail("onRead", ec);
344 return;
345 }
346
348}
349
350void
352{
353 cancelTimer();
354 if (!ec)
355 {
356 close();
357 return;
358 }
359
360 if (ec != boost::asio::error::eof)
361 {
362 fail("onShutdown", ec);
363 return;
364 }
365 close();
366}
367
368//--------------------------------------------------------------------------
369
370void
372{
373 if (response_.result() == boost::beast::http::status::service_unavailable)
374 {
376 json::Reader r;
377 std::string s;
378 s.reserve(boost::asio::buffer_size(response_.body().data()));
379 for (auto const buffer : response_.body().data())
380 {
381 s.append(static_cast<char const*>(buffer.data()), boost::asio::buffer_size(buffer));
382 }
383 auto const success = r.parse(s, json);
384 if (success)
385 {
386 if (json.isObject() && json.isMember("peer-ips"))
387 {
388 json::Value const& ips = json["peer-ips"];
389 if (ips.isArray())
390 {
392 eps.reserve(ips.size());
393 for (auto const& v : ips)
394 {
395 if (v.isString())
396 {
397 error_code ec;
398 auto const ep = parseEndpoint(v.asString(), ec);
399 if (!ec)
400 eps.push_back(ep);
401 }
402 }
403 overlay_.peerFinder().onRedirects(remoteEndpoint_, eps);
404 }
405 }
406 }
407 }
408
410 {
411 JLOG(journal_.info()) << "Unable to upgrade to peer protocol: " << response_.result()
412 << " (" << response_.reason() << ")";
413 close();
414 return;
415 }
416
417 // Just because our peer selected a particular protocol version doesn't
418 // mean that it's acceptable to us. Check that it is:
419 std::optional<ProtocolVersion> negotiatedProtocol;
420
421 {
422 auto const pvs = parseProtocolVersions(response_["Upgrade"]);
423
424 if (pvs.size() == 1 && isProtocolSupported(pvs[0]))
425 negotiatedProtocol = pvs[0];
426
427 if (!negotiatedProtocol)
428 {
429 fail("processResponse: Unable to negotiate protocol version");
430 return;
431 }
432 }
433
434 auto const sharedValue = makeSharedValue(*streamPtr_, journal_);
435 if (!sharedValue)
436 {
437 close(); // makeSharedValue logs
438 return;
439 }
440
441 try
442 {
443 auto const publicKey = verifyHandshake(
444 response_,
445 *sharedValue,
446 overlay_.setup().networkID,
447 overlay_.setup().publicIp,
448 remoteEndpoint_.address(),
449 app_);
450
451 usage_.setPublicKey(publicKey);
452
453 JLOG(journal_.info()) << "Public Key: " << toBase58(TokenType::NodePublic, publicKey);
454
455 JLOG(journal_.debug()) << "Protocol: " << to_string(*negotiatedProtocol);
456
457 auto const member = app_.getCluster().member(publicKey);
458 if (member)
459 {
460 JLOG(journal_.info()) << "Cluster name: " << *member;
461 }
462
463 auto const result =
464 overlay_.peerFinder().activate(slot_, publicKey, static_cast<bool>(member));
465 if (result != peer_finder::Result::Success)
466 {
467 fail("Outbound " + std::string(to_string(result)));
468 return;
469 }
470
471 auto const peer = std::make_shared<PeerImp>(
472 app_,
473 std::move(streamPtr_),
474 readBuf_.data(),
475 std::move(slot_),
476 std::move(response_),
477 usage_,
478 publicKey,
479 *negotiatedProtocol,
480 id_,
481 overlay_);
482
483 overlay_.addActive(peer);
484 }
485 catch (std::exception const& e)
486 {
487 fail(std::string("Handshake failure (") + e.what() + ")");
488 return;
489 }
490}
491
492} // namespace xrpl
T append(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:117
bool isArray() const
UInt size() const
Number of values in array or object.
boost::system::error_code error_code
boost::beast::ssl_stream< middle_type > stream_type
void fail(std::string const &reason)
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_
socket_type & socket_
boost::beast::multi_buffer readBuf_
boost::asio::strand< boost::asio::io_context::executor_type > strand_
static boost::asio::ip::tcp::endpoint parseEndpoint(std::string const &s, boost::system::error_code &ec)
std::shared_ptr< boost::asio::ssl::context > shared_context
void onRead(error_code ec)
endpoint_type remoteEndpoint_
void onConnect(error_code ec)
stream_type & stream_
void onShutdown(error_code ec)
response_type response_
boost::asio::ip::tcp::socket socket_type
std::shared_ptr< peer_finder::Slot > slot_
std::uint32_t const id_
std::unique_ptr< stream_type > streamPtr_
boost::asio::ip::tcp::endpoint endpoint_type
void onHandshake(error_code ec)
resource::Consumer usage_
beast::WrappedSink sink_
beast::Journal const journal_
void onWrite(error_code ec)
void onTimer(error_code ec)
ConnectAttempt(Application &app, boost::asio::io_context &ioContext, endpoint_type remoteEndpoint, resource::Consumer usage, shared_context const &context, Peer::id_t id, std::shared_ptr< peer_finder::Slot > const &slot, beast::Journal journal, OverlayImpl &overlay)
Child(OverlayImpl &overlay)
static bool isPeerUpgrade(http_request_type const &request)
std::uint32_t id_t
Uniquely identifies a peer.
An endpoint that consumes resources.
Definition Consumer.h:20
T make_shared(T... args)
JSON (JavaScript Object Notation).
Definition json_errors.h:5
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::vector< ProtocolVersion > parseProtocolVersions(boost::beast::string_view const &value)
Parse a set of protocol versions.
bool isProtocolSupported(ProtocolVersion const &v)
Determine whether we support a specific protocol version.
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
void buildHandshake(boost::beast::http::fields &h, xrpl::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::ip::Address publicIp, beast::ip::Address remoteIp, Application &app)
Insert fields headers necessary for upgrading the link to the peer protocol.
PublicKey verifyHandshake(boost::beast::http::fields const &headers, xrpl::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::ip::Address publicIp, beast::ip::Address remote, Application &app)
Validate header fields necessary for upgrading the link to the peer protocol.
T push_back(T... args)
T reserve(T... args)
static ip::Endpoint fromAsio(boost::asio::ip::address const &address)
T what(T... args)