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#include <xrpl/json/json_reader.h>
25
26namespace ripple {
27
29 Application& app,
30 boost::asio::io_service& io_service,
31 endpoint_type const& remote_endpoint,
33 shared_context const& context,
36 beast::Journal journal,
37 OverlayImpl& overlay)
38 : Child(overlay)
39 , app_(app)
40 , id_(id)
41 , sink_(journal, OverlayImpl::makePrefix(id))
42 , journal_(sink_)
43 , remote_endpoint_(remote_endpoint)
44 , usage_(usage)
45 , strand_(io_service)
46 , timer_(io_service)
47 , stream_ptr_(std::make_unique<stream_type>(
48 socket_type(std::forward<boost::asio::io_service&>(io_service)),
49 *context))
50 , socket_(stream_ptr_->next_layer().socket())
51 , stream_(*stream_ptr_)
52 , slot_(slot)
53{
54 JLOG(journal_.debug()) << "Connect " << remote_endpoint;
55}
56
58{
59 if (slot_ != nullptr)
61 JLOG(journal_.trace()) << "~ConnectAttempt";
62}
63
64void
66{
67 if (!strand_.running_in_this_thread())
68 return strand_.post(
70 if (socket_.is_open())
71 {
72 JLOG(journal_.debug()) << "Stop";
73 }
74 close();
75}
76
77void
79{
80 stream_.next_layer().async_connect(
82 strand_.wrap(std::bind(
85 std::placeholders::_1)));
86}
87
88//------------------------------------------------------------------------------
89
90void
92{
93 XRPL_ASSERT(
94 strand_.running_in_this_thread(),
95 "ripple::ConnectAttempt::close : strand in this thread");
96 if (socket_.is_open())
97 {
98 error_code ec;
99 timer_.cancel(ec);
100 socket_.close(ec);
101 JLOG(journal_.debug()) << "Closed";
102 }
103}
104
105void
107{
108 JLOG(journal_.debug()) << reason;
109 close();
110}
111
112void
114{
115 JLOG(journal_.debug()) << name << ": " << ec.message();
116 close();
117}
118
119void
121{
122 error_code ec;
123 timer_.expires_from_now(std::chrono::seconds(15), ec);
124 if (ec)
125 {
126 JLOG(journal_.error()) << "setTimer: " << ec.message();
127 return;
128 }
129
130 timer_.async_wait(strand_.wrap(std::bind(
131 &ConnectAttempt::onTimer, shared_from_this(), std::placeholders::_1)));
132}
133
134void
136{
137 error_code ec;
138 timer_.cancel(ec);
139}
140
141void
143{
144 if (!socket_.is_open())
145 return;
146 if (ec == boost::asio::error::operation_aborted)
147 return;
148 if (ec)
149 {
150 // This should never happen
151 JLOG(journal_.error()) << "onTimer: " << ec.message();
152 return close();
153 }
154 fail("Timeout");
155}
156
157void
159{
160 cancelTimer();
161
162 if (ec == boost::asio::error::operation_aborted)
163 return;
164 endpoint_type local_endpoint;
165 if (!ec)
166 local_endpoint = socket_.local_endpoint(ec);
167 if (ec)
168 return fail("onConnect", ec);
169 if (!socket_.is_open())
170 return;
171 JLOG(journal_.trace()) << "onConnect";
172
173 setTimer();
174 stream_.set_verify_mode(boost::asio::ssl::verify_none);
175 stream_.async_handshake(
176 boost::asio::ssl::stream_base::client,
177 strand_.wrap(std::bind(
180 std::placeholders::_1)));
181}
182
183void
185{
186 cancelTimer();
187 if (!socket_.is_open())
188 return;
189 if (ec == boost::asio::error::operation_aborted)
190 return;
191 endpoint_type local_endpoint;
192 if (!ec)
193 local_endpoint = socket_.local_endpoint(ec);
194 if (ec)
195 return fail("onHandshake", ec);
196 JLOG(journal_.trace()) << "onHandshake";
197
200 return fail("Duplicate connection");
201
202 auto const sharedValue = makeSharedValue(*stream_ptr_, journal_);
203 if (!sharedValue)
204 return close(); // makeSharedValue logs
205
212
214 req_,
215 *sharedValue,
218 remote_endpoint_.address(),
219 app_);
220
221 setTimer();
222 boost::beast::http::async_write(
223 stream_,
224 req_,
225 strand_.wrap(std::bind(
228 std::placeholders::_1)));
229}
230
231void
233{
234 cancelTimer();
235 if (!socket_.is_open())
236 return;
237 if (ec == boost::asio::error::operation_aborted)
238 return;
239 if (ec)
240 return fail("onWrite", ec);
241 boost::beast::http::async_read(
242 stream_,
243 read_buf_,
244 response_,
245 strand_.wrap(std::bind(
248 std::placeholders::_1)));
249}
250
251void
253{
254 cancelTimer();
255
256 if (!socket_.is_open())
257 return;
258 if (ec == boost::asio::error::operation_aborted)
259 return;
260 if (ec == boost::asio::error::eof)
261 {
262 JLOG(journal_.info()) << "EOF";
263 setTimer();
264 return stream_.async_shutdown(strand_.wrap(std::bind(
267 std::placeholders::_1)));
268 }
269 if (ec)
270 return fail("onRead", ec);
272}
273
274void
276{
277 cancelTimer();
278 if (!ec)
279 {
280 JLOG(journal_.error()) << "onShutdown: expected error condition";
281 return close();
282 }
283 if (ec != boost::asio::error::eof)
284 return fail("onShutdown", ec);
285 close();
286}
287
288//--------------------------------------------------------------------------
289
290void
292{
293 if (response_.result() == boost::beast::http::status::service_unavailable)
294 {
295 Json::Value json;
296 Json::Reader r;
297 std::string s;
298 s.reserve(boost::asio::buffer_size(response_.body().data()));
299 for (auto const buffer : response_.body().data())
300 s.append(
301 boost::asio::buffer_cast<char const*>(buffer),
302 boost::asio::buffer_size(buffer));
303 auto const success = r.parse(s, json);
304 if (success)
305 {
306 if (json.isObject() && json.isMember("peer-ips"))
307 {
308 Json::Value const& ips = json["peer-ips"];
309 if (ips.isArray())
310 {
312 eps.reserve(ips.size());
313 for (auto const& v : ips)
314 {
315 if (v.isString())
316 {
317 error_code ec;
318 auto const ep = parse_endpoint(v.asString(), ec);
319 if (!ec)
320 eps.push_back(ep);
321 }
322 }
324 }
325 }
326 }
327 }
328
330 {
331 JLOG(journal_.info())
332 << "Unable to upgrade to peer protocol: " << response_.result()
333 << " (" << response_.reason() << ")";
334 return close();
335 }
336
337 // Just because our peer selected a particular protocol version doesn't
338 // mean that it's acceptable to us. Check that it is:
339 std::optional<ProtocolVersion> negotiatedProtocol;
340
341 {
342 auto const pvs = parseProtocolVersions(response_["Upgrade"]);
343
344 if (pvs.size() == 1 && isProtocolSupported(pvs[0]))
345 negotiatedProtocol = pvs[0];
346
347 if (!negotiatedProtocol)
348 return fail(
349 "processResponse: Unable to negotiate protocol version");
350 }
351
352 auto const sharedValue = makeSharedValue(*stream_ptr_, journal_);
353 if (!sharedValue)
354 return close(); // makeSharedValue logs
355
356 try
357 {
358 auto publicKey = verifyHandshake(
359 response_,
360 *sharedValue,
363 remote_endpoint_.address(),
364 app_);
365
366 JLOG(journal_.info())
367 << "Public Key: " << toBase58(TokenType::NodePublic, publicKey);
368
369 JLOG(journal_.debug())
370 << "Protocol: " << to_string(*negotiatedProtocol);
371
372 auto const member = app_.cluster().member(publicKey);
373 if (member)
374 {
375 JLOG(journal_.info()) << "Cluster name: " << *member;
376 }
377
378 auto const result = overlay_.peerFinder().activate(
379 slot_, publicKey, static_cast<bool>(member));
380 if (result != PeerFinder::Result::success)
381 return fail("Outbound slots full");
382
383 auto const peer = std::make_shared<PeerImp>(
384 app_,
385 std::move(stream_ptr_),
386 read_buf_.data(),
387 std::move(slot_),
388 std::move(response_),
389 usage_,
390 publicKey,
391 *negotiatedProtocol,
392 id_,
393 overlay_);
394
395 overlay_.add_active(peer);
396 }
397 catch (std::exception const& e)
398 {
399 return fail(std::string("Handshake failure (") + e.what() + ")");
400 }
401}
402
403} // namespace ripple
T append(T... args)
T bind(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:37
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:73
Represents a JSON value.
Definition: json_value.h:147
bool isArray() const
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
bool isObject() const
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:943
A generic endpoint for log messages.
Definition: Journal.h:59
Stream error() const
Definition: Journal.h:335
Stream debug() const
Definition: Journal.h:317
Stream info() const
Definition: Journal.h:323
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
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:37
bool VP_REDUCE_RELAY_ENABLE
Definition: Config.h:255
bool LEDGER_REPLAY
Definition: Config.h:230
bool TX_REDUCE_RELAY_ENABLE
Definition: Config.h:266
bool COMPRESSION
Definition: Config.h:227
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:158
void add_active(std::shared_ptr< PeerImp > const &peer)
static bool isPeerUpgrade(http_request_type const &request)
Setup const & setup() const
Definition: OverlayImpl.h:170
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:106
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:175
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
Definition: Handshake.cpp:143
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:362
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:224
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:72
std::optional< std::uint32_t > networkID
Definition: Overlay.h:75
bool peerPrivate
true if we want our IP address kept private.
T what(T... args)