rippled
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 <ripple/overlay/impl/ConnectAttempt.h>
21 #include <ripple/overlay/impl/PeerImp.h>
22 #include <ripple/overlay/impl/ProtocolVersion.h>
23 #include <ripple/overlay/Cluster.h>
24 #include <ripple/json/json_reader.h>
25 
26 namespace ripple {
27 
28 ConnectAttempt::ConnectAttempt (Application& app, boost::asio::io_service& io_service,
29  endpoint_type const& remote_endpoint, Resource::Consumer usage,
30  shared_context const& context,
32  beast::Journal journal, OverlayImpl& overlay)
33  : Child (overlay)
34  , app_ (app)
35  , id_ (id)
36  , sink_ (journal, OverlayImpl::makePrefix(id))
37  , journal_ (sink_)
38  , remote_endpoint_ (remote_endpoint)
39  , usage_ (usage)
40  , strand_ (io_service)
41  , timer_ (io_service)
42  , stream_ptr_ (std::make_unique<stream_type>(
43  socket_type(std::forward<boost::asio::io_service&>(io_service)), *context))
44  , socket_ (stream_ptr_->next_layer().socket())
45  , stream_ (*stream_ptr_)
46  , slot_ (slot)
47 {
48  JLOG(journal_.debug()) <<
49  "Connect " << remote_endpoint;
50 }
51 
53 {
54  if (slot_ != nullptr)
56  JLOG(journal_.trace()) <<
57  "~ConnectAttempt";
58 }
59 
60 void
62 {
63  if (! strand_.running_in_this_thread())
64  return strand_.post(std::bind(
66  if (socket_.is_open())
67  {
68  JLOG(journal_.debug()) <<
69  "Stop";
70  }
71  close();
72 }
73 
74 void
76 {
77  stream_.next_layer().async_connect (remote_endpoint_,
79  shared_from_this(), std::placeholders::_1)));
80 }
81 
82 //------------------------------------------------------------------------------
83 
84 void
86 {
87  assert(strand_.running_in_this_thread());
88  if (socket_.is_open())
89  {
90  error_code ec;
91  timer_.cancel(ec);
92  socket_.close(ec);
93  JLOG(journal_.debug()) << "Closed";
94  }
95 }
96 
97 void
99 {
100  JLOG(journal_.debug()) << reason;
101  close();
102 }
103 
104 void
106 {
107  JLOG(journal_.debug()) << name << ": " << ec.message();
108  close();
109 }
110 
111 void
113 {
114  error_code ec;
115  timer_.expires_from_now(std::chrono::seconds(15), ec);
116  if (ec)
117  {
118  JLOG(journal_.error()) <<
119  "setTimer: " << ec.message();
120  return;
121  }
122 
123  timer_.async_wait(strand_.wrap(std::bind(
125  std::placeholders::_1)));
126 }
127 
128 void
130 {
131  error_code ec;
132  timer_.cancel(ec);
133 }
134 
135 void
137 {
138  if (! socket_.is_open())
139  return;
140  if (ec == boost::asio::error::operation_aborted)
141  return;
142  if (ec)
143  {
144  // This should never happen
145  JLOG(journal_.error()) <<
146  "onTimer: " << ec.message();
147  return close();
148  }
149  fail("Timeout");
150 }
151 
152 void
154 {
155  cancelTimer();
156 
157  if(ec == boost::asio::error::operation_aborted)
158  return;
159  endpoint_type local_endpoint;
160  if(! ec)
161  local_endpoint = socket_.local_endpoint(ec);
162  if(ec)
163  return fail("onConnect", ec);
164  if(! socket_.is_open())
165  return;
166  JLOG(journal_.trace()) <<
167  "onConnect";
168 
169  setTimer();
170  stream_.set_verify_mode (boost::asio::ssl::verify_none);
171  stream_.async_handshake (boost::asio::ssl::stream_base::client,
173  shared_from_this(), std::placeholders::_1)));
174 }
175 
176 void
178 {
179  cancelTimer();
180  if(! socket_.is_open())
181  return;
182  if(ec == boost::asio::error::operation_aborted)
183  return;
184  endpoint_type local_endpoint;
185  if (! ec)
186  local_endpoint = socket_.local_endpoint(ec);
187  if(ec)
188  return fail("onHandshake", ec);
189  JLOG(journal_.trace()) <<
190  "onHandshake";
191 
193  beast::IPAddressConversion::from_asio (local_endpoint)))
194  return fail("Duplicate connection");
195 
196  auto const sharedValue = makeSharedValue(*stream_ptr_, journal_);
197  if (! sharedValue)
198  return close(); // makeSharedValue logs
199 
201 
202  buildHandshake(req_, *sharedValue, overlay_.setup().networkID,
203  overlay_.setup().public_ip, remote_endpoint_.address(), app_);
204 
205  setTimer();
206  boost::beast::http::async_write(stream_, req_,
208  shared_from_this(), std::placeholders::_1)));
209 }
210 
211 void
213 {
214  cancelTimer();
215  if(! socket_.is_open())
216  return;
217  if(ec == boost::asio::error::operation_aborted)
218  return;
219  if(ec)
220  return fail("onWrite", ec);
221  boost::beast::http::async_read(stream_, read_buf_, response_,
223  shared_from_this(), std::placeholders::_1)));
224 }
225 
226 void
228 {
229  cancelTimer();
230 
231  if(! socket_.is_open())
232  return;
233  if(ec == boost::asio::error::operation_aborted)
234  return;
235  if(ec == boost::asio::error::eof)
236  {
237  JLOG(journal_.info()) <<
238  "EOF";
239  setTimer();
240  return stream_.async_shutdown(strand_.wrap(std::bind(
242  std::placeholders::_1)));
243  }
244  if(ec)
245  return fail("onRead", ec);
246  processResponse();
247 }
248 
249 void
251 {
252  cancelTimer();
253  if (! ec)
254  {
255  JLOG(journal_.error()) <<
256  "onShutdown: expected error condition";
257  return close();
258  }
259  if (ec != boost::asio::error::eof)
260  return fail("onShutdown", ec);
261  close();
262 }
263 
264 //--------------------------------------------------------------------------
265 
266 auto
267 ConnectAttempt::makeRequest (bool crawl, bool compressionEnabled) -> request_type
268 {
269  request_type m;
270  m.method(boost::beast::http::verb::get);
271  m.target("/");
272  m.version(11);
273  m.insert ("User-Agent", BuildInfo::getFullVersionString());
274  m.insert ("Upgrade", supportedProtocolVersions());
275  m.insert ("Connection", "Upgrade");
276  m.insert ("Connect-As", "Peer");
277  m.insert ("Crawl", crawl ? "public" : "private");
278  if (compressionEnabled)
279  m.insert("X-Offer-Compression", "lz4");
280  return m;
281 }
282 
283 void
285 {
286  if (response_.result() == boost::beast::http::status::service_unavailable)
287  {
288  Json::Value json;
289  Json::Reader r;
290  std::string s;
291  s.reserve(boost::asio::buffer_size(response_.body().data()));
292  for (auto const& buffer : response_.body().data())
293  s.append(
294  boost::asio::buffer_cast<char const*>(buffer),
295  boost::asio::buffer_size(buffer));
296  auto const success = r.parse(s, json);
297  if (success)
298  {
299  if (json.isObject() && json.isMember("peer-ips"))
300  {
301  Json::Value const& ips = json["peer-ips"];
302  if (ips.isArray())
303  {
305  eps.reserve(ips.size());
306  for (auto const& v : ips)
307  {
308  if (v.isString())
309  {
310  error_code ec;
311  auto const ep = parse_endpoint(v.asString(), ec);
312  if (!ec)
313  eps.push_back(ep);
314  }
315  }
317  }
318  }
319  }
320  }
321 
323  {
324  JLOG(journal_.info()) << "Unable to upgrade to peer protocol: " <<
325  response_.result() << " (" << response_.reason() << ")";
326  return close();
327  }
328 
329  // Just because our peer selected a particular protocol version doesn't
330  // mean that it's acceptable to us. Check that it is:
331  boost::optional<ProtocolVersion> negotiatedProtocol;
332 
333  {
334  auto const pvs = parseProtocolVersions(response_["Upgrade"]);
335 
336  if (pvs.size() == 1 && isProtocolSupported(pvs[0]))
337  negotiatedProtocol = pvs[0];
338 
339  if (!negotiatedProtocol)
340  return fail("processResponse: Unable to negotiate protocol version");
341  }
342 
343  auto const sharedValue = makeSharedValue(*stream_ptr_, journal_);
344  if(! sharedValue)
345  return close(); // makeSharedValue logs
346 
347  try
348  {
349  auto publicKey = verifyHandshake(response_, *sharedValue,
350  overlay_.setup().networkID, overlay_.setup().public_ip,
351  remote_endpoint_.address(), app_);
352 
353  JLOG(journal_.info()) <<
354  "Public Key: " << toBase58(TokenType::NodePublic, publicKey);
355 
356  JLOG(journal_.debug()) <<
357  "Protocol: " << to_string(*negotiatedProtocol);
358 
359  auto const member = app_.cluster().member(publicKey);
360  if (member)
361  {
362  JLOG(journal_.info()) << "Cluster name: " << *member;
363  }
364 
365  auto const result = overlay_.peerFinder().activate (slot_,
366  publicKey, static_cast<bool>(member));
367  if (result != PeerFinder::Result::success)
368  return fail("Outbound slots full");
369 
370  auto const peer = std::make_shared<PeerImp>(app_, std::move(stream_ptr_),
371  read_buf_.data(), std::move(slot_), std::move(response_), usage_,
372  publicKey, *negotiatedProtocol, id_, overlay_);
373 
374  overlay_.add_active (peer);
375  }
376  catch (std::exception const& e)
377  {
378  return fail(std::string("Handshake failure (")+ e.what() + ")");
379  }
380 }
381 
382 } // ripple
ripple::ConnectAttempt::onHandshake
void onHandshake(error_code ec)
Definition: ConnectAttempt.cpp:177
ripple::Application
Definition: Application.h:85
ripple::ConnectAttempt::cancelTimer
void cancelTimer()
Definition: ConnectAttempt.cpp:129
ripple::ConnectAttempt::remote_endpoint_
endpoint_type remote_endpoint_
Definition: ConnectAttempt.h:53
ripple::Application::cluster
virtual Cluster & cluster()=0
std::bind
T bind(T... args)
Json::Value::isObject
bool isObject() const
Definition: json_value.cpp:1069
std::string
STL class.
std::shared_ptr< boost::asio::ssl::context >
std::exception
STL class.
ripple::ConnectAttempt::strand_
boost::asio::io_service::strand strand_
Definition: ConnectAttempt.h:55
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:287
ripple::OverlayImpl::Child::overlay_
OverlayImpl & overlay_
Definition: OverlayImpl.h:63
std::string::reserve
T reserve(T... args)
std::vector
STL class.
std::chrono::seconds
ripple::ConnectAttempt::journal_
const beast::Journal journal_
Definition: ConnectAttempt.h:52
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
ripple::parseProtocolVersions
std::vector< ProtocolVersion > parseProtocolVersions(boost::beast::string_view const &value)
Parse a set of protocol versions.
Definition: ProtocolVersion.cpp:84
boost
Definition: IPAddress.h:127
ripple::ConnectAttempt::stream_type
boost::beast::ssl_stream< middle_type > stream_type
Definition: ConnectAttempt.h:46
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::ConnectAttempt::request_type
boost::beast::http::request< boost::beast::http::empty_body > request_type
Definition: ConnectAttempt.h:39
ripple::ConnectAttempt::processResponse
void processResponse()
Definition: ConnectAttempt.cpp:284
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::buildHandshake
void buildHandshake(boost::beast::http::fields &h, ripple::uint256 const &sharedValue, boost::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:105
ripple::OverlayImpl::setup
Setup const & setup() const
Definition: OverlayImpl.h:166
ripple::ConnectAttempt::socket_type
boost::asio::ip::tcp::socket socket_type
Definition: ConnectAttempt.h:44
ripple::ConnectAttempt::response_
response_type response_
Definition: ConnectAttempt.h:61
ripple::PeerFinder::Config::peerPrivate
bool peerPrivate
true if we want our IP address kept private.
Definition: PeerfinderManager.h:61
ripple::ConnectAttempt::onRead
void onRead(error_code ec)
Definition: ConnectAttempt.cpp:227
ripple::ConnectAttempt::setTimer
void setTimer()
Definition: ConnectAttempt.cpp:112
ripple::ConnectAttempt::slot_
std::shared_ptr< PeerFinder::Slot > slot_
Definition: ConnectAttempt.h:62
ripple::PeerFinder::Manager::activate
virtual Result activate(std::shared_ptr< Slot > const &slot, PublicKey const &key, bool reserved)=0
Request an active slot type.
ripple::verifyHandshake
PublicKey verifyHandshake(boost::beast::http::fields const &headers, ripple::uint256 const &sharedValue, boost::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:151
std::vector::push_back
T push_back(T... args)
beast::IPAddressConversion::from_asio
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
Definition: IPAddressConversion.h:58
ripple::Cluster::member
boost::optional< std::string > member(PublicKey const &node) const
Determines whether a node belongs in the cluster.
Definition: Cluster.cpp:40
ripple::PeerFinder::Manager::on_closed
virtual void on_closed(std::shared_ptr< Slot > const &slot)=0
Called when the slot is closed.
ripple::PeerFinder::Result::success
@ success
ripple::OverlayImpl::peerFinder
PeerFinder::Manager & peerFinder()
Definition: OverlayImpl.h:148
ripple::ConnectAttempt::makeRequest
static request_type makeRequest(bool crawl, bool compressionEnabled)
Definition: ConnectAttempt.cpp:267
ripple::ConnectAttempt::id_
const std::uint32_t id_
Definition: ConnectAttempt.h:50
ripple::ConnectAttempt::socket_
socket_type & socket_
Definition: ConnectAttempt.h:58
ripple::ConnectAttempt::error_code
boost::system::error_code error_code
Definition: ConnectAttempt.h:34
std::enable_shared_from_this< ConnectAttempt >::shared_from_this
T shared_from_this(T... args)
ripple::Application::config
virtual Config & config()=0
ripple::makeSharedValue
boost::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
Definition: Handshake.cpp:73
ripple::ConnectAttempt::onShutdown
void onShutdown(error_code ec)
Definition: ConnectAttempt.cpp:250
beast::Journal::error
Stream error() const
Definition: Journal.h:307
beast::Journal::info
Stream info() const
Definition: Journal.h:297
Json::Value::size
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:720
ripple::ConnectAttempt::usage_
Resource::Consumer usage_
Definition: ConnectAttempt.h:54
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:961
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
std::uint32_t
ripple::ConnectAttempt::stop
void stop() override
Definition: ConnectAttempt.cpp:61
ripple::ConnectAttempt::endpoint_type
boost::asio::ip::tcp::endpoint endpoint_type
Definition: ConnectAttempt.h:36
std::string::append
T append(T... args)
ripple::ConnectAttempt::app_
Application & app_
Definition: ConnectAttempt.h:49
ripple::ConnectAttempt::fail
void fail(std::string const &reason)
Definition: ConnectAttempt.cpp:98
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1056
ripple::BuildInfo::getFullVersionString
std::string const & getFullVersionString()
Full server version string.
Definition: BuildInfo.cpp:70
ripple::supportedProtocolVersions
std::string const & supportedProtocolVersions()
The list of all the protocol versions we support.
Definition: ProtocolVersion.cpp:166
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::PeerFinder::Manager::onConnected
virtual bool onConnected(std::shared_ptr< Slot > const &slot, beast::IP::Endpoint const &local_endpoint)=0
Called when an outbound connection attempt succeeds.
ripple::PeerFinder::Manager::onRedirects
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.
ripple::PeerFinder::Manager::config
virtual Config config()=0
Returns the configuration for the manager.
std
STL namespace.
ripple::ConnectAttempt::stream_ptr_
std::unique_ptr< stream_type > stream_ptr_
Definition: ConnectAttempt.h:57
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:76
ripple::Resource::Consumer
An endpoint that consumes resources.
Definition: Consumer.h:33
ripple::ConnectAttempt::run
void run()
Definition: ConnectAttempt.cpp:75
ripple::ConnectAttempt::timer_
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_
Definition: ConnectAttempt.h:56
ripple::ConnectAttempt::onConnect
void onConnect(error_code ec)
Definition: ConnectAttempt.cpp:153
ripple::Config::COMPRESSION
bool COMPRESSION
Definition: Config.h:175
ripple::isProtocolSupported
bool isProtocolSupported(ProtocolVersion const &v)
Determine whether we support a specific protocol version.
Definition: ProtocolVersion.cpp:185
ripple::TokenType::NodePublic
@ NodePublic
beast::Journal::debug
Stream debug() const
Definition: Journal.h:292
ripple::ConnectAttempt::stream_
stream_type & stream_
Definition: ConnectAttempt.h:59
ripple::ConnectAttempt::req_
request_type req_
Definition: ConnectAttempt.h:63
ripple::ConnectAttempt::close
void close()
Definition: ConnectAttempt.cpp:85
ripple::OverlayImpl
Definition: OverlayImpl.h:57
ripple::ConnectAttempt::ConnectAttempt
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)
Definition: ConnectAttempt.cpp:28
ripple::ConnectAttempt::~ConnectAttempt
~ConnectAttempt()
Definition: ConnectAttempt.cpp:52
ripple::ConnectAttempt::parse_endpoint
static boost::asio::ip::tcp::endpoint parse_endpoint(std::string const &s, boost::system::error_code &ec)
Definition: ConnectAttempt.h:103
ripple::OverlayImpl::isPeerUpgrade
static bool isPeerUpgrade(http_request_type const &request)
Definition: OverlayImpl.cpp:340
ripple::OverlayImpl::add_active
void add_active(std::shared_ptr< PeerImp > const &peer)
Definition: OverlayImpl.cpp:430
std::exception::what
T what(T... args)
ripple::ConnectAttempt::read_buf_
boost::beast::multi_buffer read_buf_
Definition: ConnectAttempt.h:60
ripple::ConnectAttempt::onTimer
void onTimer(error_code ec)
Definition: ConnectAttempt.cpp:136
ripple::ConnectAttempt::onWrite
void onWrite(error_code ec)
Definition: ConnectAttempt.cpp:212
Json::Value
Represents a JSON value.
Definition: json_value.h:141