rippled
Loading...
Searching...
No Matches
ConnectAttempt.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/overlay/Cluster.h>
21#include <xrpld/overlay/detail/ConnectAttempt.h>
22#include <xrpld/overlay/detail/PeerImp.h>
23#include <xrpld/overlay/detail/ProtocolVersion.h>
24
25#include <xrpl/json/json_reader.h>
26
27namespace ripple {
28
30 Application& app,
31 boost::asio::io_service& io_service,
32 endpoint_type const& remote_endpoint,
34 shared_context const& context,
37 beast::Journal journal,
38 OverlayImpl& overlay)
39 : Child(overlay)
40 , app_(app)
41 , id_(id)
42 , sink_(journal, OverlayImpl::makePrefix(id))
43 , journal_(sink_)
44 , remote_endpoint_(remote_endpoint)
45 , usage_(usage)
46 , strand_(io_service)
47 , timer_(io_service)
48 , stream_ptr_(std::make_unique<stream_type>(
49 socket_type(std::forward<boost::asio::io_service&>(io_service)),
50 *context))
51 , socket_(stream_ptr_->next_layer().socket())
52 , stream_(*stream_ptr_)
53 , slot_(slot)
54{
55 JLOG(journal_.debug()) << "Connect " << remote_endpoint;
56}
57
59{
60 if (slot_ != nullptr)
62 JLOG(journal_.trace()) << "~ConnectAttempt";
63}
64
65void
67{
68 if (!strand_.running_in_this_thread())
69 return strand_.post(
71 if (socket_.is_open())
72 {
73 JLOG(journal_.debug()) << "Stop";
74 }
75 close();
76}
77
78void
80{
81 stream_.next_layer().async_connect(
83 strand_.wrap(std::bind(
86 std::placeholders::_1)));
87}
88
89//------------------------------------------------------------------------------
90
91void
93{
94 XRPL_ASSERT(
95 strand_.running_in_this_thread(),
96 "ripple::ConnectAttempt::close : strand in this thread");
97 if (socket_.is_open())
98 {
99 error_code ec;
100 timer_.cancel(ec);
101 socket_.close(ec);
102 JLOG(journal_.debug()) << "Closed";
103 }
104}
105
106void
108{
109 JLOG(journal_.debug()) << reason;
110 close();
111}
112
113void
115{
116 JLOG(journal_.debug()) << name << ": " << ec.message();
117 close();
118}
119
120void
122{
123 error_code ec;
124 timer_.expires_from_now(std::chrono::seconds(15), ec);
125 if (ec)
126 {
127 JLOG(journal_.error()) << "setTimer: " << ec.message();
128 return;
129 }
130
131 timer_.async_wait(strand_.wrap(std::bind(
132 &ConnectAttempt::onTimer, shared_from_this(), std::placeholders::_1)));
133}
134
135void
137{
138 error_code ec;
139 timer_.cancel(ec);
140}
141
142void
144{
145 if (!socket_.is_open())
146 return;
147 if (ec == boost::asio::error::operation_aborted)
148 return;
149 if (ec)
150 {
151 // This should never happen
152 JLOG(journal_.error()) << "onTimer: " << ec.message();
153 return close();
154 }
155 fail("Timeout");
156}
157
158void
160{
161 cancelTimer();
162
163 if (ec == boost::asio::error::operation_aborted)
164 return;
165 endpoint_type local_endpoint;
166 if (!ec)
167 local_endpoint = socket_.local_endpoint(ec);
168 if (ec)
169 return fail("onConnect", ec);
170 if (!socket_.is_open())
171 return;
172 JLOG(journal_.trace()) << "onConnect";
173
174 setTimer();
175 stream_.set_verify_mode(boost::asio::ssl::verify_none);
176 stream_.async_handshake(
177 boost::asio::ssl::stream_base::client,
178 strand_.wrap(std::bind(
181 std::placeholders::_1)));
182}
183
184void
186{
187 cancelTimer();
188 if (!socket_.is_open())
189 return;
190 if (ec == boost::asio::error::operation_aborted)
191 return;
192 endpoint_type local_endpoint;
193 if (!ec)
194 local_endpoint = socket_.local_endpoint(ec);
195 if (ec)
196 return fail("onHandshake", ec);
197 JLOG(journal_.trace()) << "onHandshake";
198
201 return fail("Duplicate connection");
202
203 auto const sharedValue = makeSharedValue(*stream_ptr_, journal_);
204 if (!sharedValue)
205 return close(); // makeSharedValue logs
206
213
215 req_,
216 *sharedValue,
219 remote_endpoint_.address(),
220 app_);
221
222 setTimer();
223 boost::beast::http::async_write(
224 stream_,
225 req_,
226 strand_.wrap(std::bind(
229 std::placeholders::_1)));
230}
231
232void
234{
235 cancelTimer();
236 if (!socket_.is_open())
237 return;
238 if (ec == boost::asio::error::operation_aborted)
239 return;
240 if (ec)
241 return fail("onWrite", ec);
242 boost::beast::http::async_read(
243 stream_,
244 read_buf_,
245 response_,
246 strand_.wrap(std::bind(
249 std::placeholders::_1)));
250}
251
252void
254{
255 cancelTimer();
256
257 if (!socket_.is_open())
258 return;
259 if (ec == boost::asio::error::operation_aborted)
260 return;
261 if (ec == boost::asio::error::eof)
262 {
263 JLOG(journal_.info()) << "EOF";
264 setTimer();
265 return stream_.async_shutdown(strand_.wrap(std::bind(
268 std::placeholders::_1)));
269 }
270 if (ec)
271 return fail("onRead", ec);
273}
274
275void
277{
278 cancelTimer();
279 if (!ec)
280 {
281 JLOG(journal_.error()) << "onShutdown: expected error condition";
282 return close();
283 }
284 if (ec != boost::asio::error::eof)
285 return fail("onShutdown", ec);
286 close();
287}
288
289//--------------------------------------------------------------------------
290
291void
293{
294 if (response_.result() == boost::beast::http::status::service_unavailable)
295 {
296 Json::Value json;
297 Json::Reader r;
298 std::string s;
299 s.reserve(boost::asio::buffer_size(response_.body().data()));
300 for (auto const buffer : response_.body().data())
301 s.append(
302 boost::asio::buffer_cast<char const*>(buffer),
303 boost::asio::buffer_size(buffer));
304 auto const success = r.parse(s, json);
305 if (success)
306 {
307 if (json.isObject() && json.isMember("peer-ips"))
308 {
309 Json::Value const& ips = json["peer-ips"];
310 if (ips.isArray())
311 {
313 eps.reserve(ips.size());
314 for (auto const& v : ips)
315 {
316 if (v.isString())
317 {
318 error_code ec;
319 auto const ep = parse_endpoint(v.asString(), ec);
320 if (!ec)
321 eps.push_back(ep);
322 }
323 }
325 }
326 }
327 }
328 }
329
331 {
332 JLOG(journal_.info())
333 << "Unable to upgrade to peer protocol: " << response_.result()
334 << " (" << response_.reason() << ")";
335 return close();
336 }
337
338 // Just because our peer selected a particular protocol version doesn't
339 // mean that it's acceptable to us. Check that it is:
340 std::optional<ProtocolVersion> negotiatedProtocol;
341
342 {
343 auto const pvs = parseProtocolVersions(response_["Upgrade"]);
344
345 if (pvs.size() == 1 && isProtocolSupported(pvs[0]))
346 negotiatedProtocol = pvs[0];
347
348 if (!negotiatedProtocol)
349 return fail(
350 "processResponse: Unable to negotiate protocol version");
351 }
352
353 auto const sharedValue = makeSharedValue(*stream_ptr_, journal_);
354 if (!sharedValue)
355 return close(); // makeSharedValue logs
356
357 try
358 {
359 auto publicKey = verifyHandshake(
360 response_,
361 *sharedValue,
364 remote_endpoint_.address(),
365 app_);
366
367 JLOG(journal_.info())
368 << "Public Key: " << toBase58(TokenType::NodePublic, publicKey);
369
370 JLOG(journal_.debug())
371 << "Protocol: " << to_string(*negotiatedProtocol);
372
373 auto const member = app_.cluster().member(publicKey);
374 if (member)
375 {
376 JLOG(journal_.info()) << "Cluster name: " << *member;
377 }
378
379 auto const result = overlay_.peerFinder().activate(
380 slot_, publicKey, static_cast<bool>(member));
381 if (result != PeerFinder::Result::success)
382 return fail("Outbound slots full");
383
384 auto const peer = std::make_shared<PeerImp>(
385 app_,
386 std::move(stream_ptr_),
387 read_buf_.data(),
388 std::move(slot_),
389 std::move(response_),
390 usage_,
391 publicKey,
392 *negotiatedProtocol,
393 id_,
394 overlay_);
395
396 overlay_.add_active(peer);
397 }
398 catch (std::exception const& e)
399 {
400 return fail(std::string("Handshake failure (") + e.what() + ")");
401 }
402}
403
404} // namespace ripple
T append(T... args)
T bind(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:39
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:78
Represents a JSON value.
Definition: json_value.h:148
bool isArray() const
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:712
bool isObject() const
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
Stream info() const
Definition: Journal.h:334
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
virtual Config & config()=0
virtual Cluster & cluster()=0
std::optional< std::string > member(PublicKey const &node) const
Determines whether a node belongs in the cluster.
Definition: Cluster.cpp:38
bool VP_REDUCE_RELAY_ENABLE
Definition: Config.h:248
bool LEDGER_REPLAY
Definition: Config.h:223
bool TX_REDUCE_RELAY_ENABLE
Definition: Config.h:259
bool COMPRESSION
Definition: Config.h:220
std::uint32_t const id_
boost::asio::ip::tcp::socket socket_type
boost::asio::io_service::strand strand_
boost::system::error_code error_code
std::unique_ptr< stream_type > stream_ptr_
std::shared_ptr< PeerFinder::Slot > slot_
ConnectAttempt(Application &app, boost::asio::io_service &io_service, endpoint_type const &remote_endpoint, Resource::Consumer usage, shared_context const &context, std::uint32_t id, std::shared_ptr< PeerFinder::Slot > const &slot, beast::Journal journal, OverlayImpl &overlay)
Resource::Consumer usage_
void onHandshake(error_code ec)
response_type response_
boost::asio::ip::tcp::endpoint endpoint_type
void onWrite(error_code ec)
void onTimer(error_code ec)
void onShutdown(error_code ec)
boost::beast::ssl_stream< middle_type > stream_type
boost::beast::multi_buffer read_buf_
void onConnect(error_code ec)
void fail(std::string const &reason)
void onRead(error_code ec)
static boost::asio::ip::tcp::endpoint parse_endpoint(std::string const &s, boost::system::error_code &ec)
beast::Journal const journal_
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_
endpoint_type remote_endpoint_
PeerFinder::Manager & peerFinder()
Definition: OverlayImpl.h:161
void add_active(std::shared_ptr< PeerImp > const &peer)
static bool isPeerUpgrade(http_request_type const &request)
Setup const & setup() const
Definition: OverlayImpl.h:173
virtual bool onConnected(std::shared_ptr< Slot > const &slot, beast::IP::Endpoint const &local_endpoint)=0
Called when an outbound connection attempt succeeds.
virtual Config config()=0
Returns the configuration for the manager.
virtual void on_closed(std::shared_ptr< Slot > const &slot)=0
Called when the slot is closed.
virtual void onRedirects(boost::asio::ip::tcp::endpoint const &remote_address, std::vector< boost::asio::ip::tcp::endpoint > const &eps)=0
Called when we received redirect IPs from a busy peer.
virtual Result activate(std::shared_ptr< Slot > const &slot, PublicKey const &key, bool reserved)=0
Request an active slot type.
An endpoint that consumes resources.
Definition: Consumer.h:35
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
std::vector< ProtocolVersion > parseProtocolVersions(boost::beast::string_view const &value)
Parse a set of protocol versions.
void buildHandshake(boost::beast::http::fields &h, ripple::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::IP::Address public_ip, beast::IP::Address remote_ip, Application &app)
Insert fields headers necessary for upgrading the link to the peer protocol.
Definition: Handshake.cpp:178
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
Definition: Handshake.cpp:146
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:365
bool isProtocolSupported(ProtocolVersion const &v)
Determine whether we support a specific protocol version.
PublicKey verifyHandshake(boost::beast::http::fields const &headers, ripple::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::IP::Address public_ip, beast::IP::Address remote, Application &app)
Validate header fields necessary for upgrading the link to the peer protocol.
Definition: Handshake.cpp:227
STL namespace.
T push_back(T... args)
T reserve(T... args)
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
beast::IP::Address public_ip
Definition: Overlay.h:69
std::optional< std::uint32_t > networkID
Definition: Overlay.h:72
bool peerPrivate
true if we want our IP address kept private.
T what(T... args)