rippled
Loading...
Searching...
No Matches
Door.h
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#ifndef RIPPLE_SERVER_DOOR_H_INCLUDED
21#define RIPPLE_SERVER_DOOR_H_INCLUDED
22
23#include <xrpl/basics/Log.h>
24#include <xrpl/basics/contract.h>
25#include <xrpl/server/detail/PlainHTTPPeer.h>
26#include <xrpl/server/detail/SSLHTTPPeer.h>
27#include <xrpl/server/detail/io_list.h>
28
29#include <boost/asio/basic_waitable_timer.hpp>
30#include <boost/asio/buffer.hpp>
31#include <boost/asio/io_context.hpp>
32#include <boost/asio/ip/tcp.hpp>
33#include <boost/asio/post.hpp>
34#include <boost/asio/spawn.hpp>
35#include <boost/asio/steady_timer.hpp>
36#include <boost/beast/core/detect_ssl.hpp>
37#include <boost/beast/core/multi_buffer.hpp>
38#include <boost/beast/core/tcp_stream.hpp>
39#include <boost/container/flat_map.hpp>
40#include <boost/predef.h>
41
42#if !BOOST_OS_WINDOWS
43#include <sys/resource.h>
44
45#include <dirent.h>
46#include <unistd.h>
47#endif
48
49#include <algorithm>
50#include <chrono>
51#include <cstdint>
52#include <functional>
53#include <memory>
54#include <optional>
55#include <sstream>
56
57namespace ripple {
58
60template <class Handler>
61class Door : public io_list::work,
62 public std::enable_shared_from_this<Door<Handler>>
63{
64private:
66 using timer_type = boost::asio::basic_waitable_timer<clock_type>;
67 using error_code = boost::system::error_code;
68 using yield_context = boost::asio::yield_context;
69 using protocol_type = boost::asio::ip::tcp;
70 using acceptor_type = protocol_type::acceptor;
71 using endpoint_type = protocol_type::endpoint;
72 using socket_type = boost::asio::ip::tcp::socket;
73 using stream_type = boost::beast::tcp_stream;
74
75 // Detects SSL on a socket
76 class Detector : public io_list::work,
77 public std::enable_shared_from_this<Detector>
78 {
79 private:
80 Port const& port_;
81 Handler& handler_;
82 boost::asio::io_context& ioc_;
86 boost::asio::strand<boost::asio::io_context::executor_type> strand_;
88
89 public:
91 Port const& port,
92 Handler& handler,
93 boost::asio::io_context& ioc,
94 stream_type&& stream,
95 endpoint_type remote_address,
97 void
98 run();
99 void
100 close() override;
101
102 private:
103 void
105 };
106
108 Port const& port_;
109 Handler& handler_;
110 boost::asio::io_context& ioc_;
112 boost::asio::strand<boost::asio::io_context::executor_type> strand_;
113 bool ssl_;
114 bool plain_;
118 boost::asio::steady_timer backoff_timer_;
119 static constexpr double FREE_FD_THRESHOLD = 0.70;
120
126
127 void
128 reOpen();
129
131 query_fd_stats() const;
132
133 bool
135
136public:
137 Door(
138 Handler& handler,
139 boost::asio::io_context& io_context,
140 Port const& port,
142
143 // Work-around because we can't call shared_from_this in ctor
144 void
145 run();
146
153 void
154 close() override;
155
158 {
159 return acceptor_.local_endpoint();
160 }
161
162private:
163 template <class ConstBufferSequence>
164 void
165 create(
166 bool ssl,
167 ConstBufferSequence const& buffers,
168 stream_type&& stream,
169 endpoint_type remote_address);
170
171 void
173};
174
175template <class Handler>
177 Port const& port,
178 Handler& handler,
179 boost::asio::io_context& ioc,
180 stream_type&& stream,
181 endpoint_type remote_address,
183 : port_(port)
184 , handler_(handler)
185 , ioc_(ioc)
186 , stream_(std::move(stream))
187 , socket_(stream_.socket())
188 , remote_address_(remote_address)
189 , strand_(boost::asio::make_strand(ioc_))
190 , j_(j)
191{
192}
193
194template <class Handler>
195void
197{
199 strand_,
200 std::bind(
202 this->shared_from_this(),
203 std::placeholders::_1));
204}
205
206template <class Handler>
207void
209{
210 stream_.close();
211}
212
213template <class Handler>
214void
215Door<Handler>::Detector::do_detect(boost::asio::yield_context do_yield)
216{
217 boost::beast::multi_buffer buf(16);
218 stream_.expires_after(std::chrono::seconds(15));
219 boost::system::error_code ec;
220 bool const ssl = async_detect_ssl(stream_, buf, do_yield[ec]);
221 stream_.expires_never();
222 if (!ec)
223 {
224 if (ssl)
225 {
226 if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
227 port_,
228 handler_,
229 ioc_,
230 j_,
231 remote_address_,
232 buf.data(),
233 std::move(stream_)))
234 sp->run();
235 return;
236 }
237 if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
238 port_,
239 handler_,
240 ioc_,
241 j_,
242 remote_address_,
243 buf.data(),
244 std::move(stream_)))
245 sp->run();
246 return;
247 }
248 if (ec != boost::asio::error::operation_aborted)
249 {
250 JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from "
251 << remote_address_;
252 }
253}
254
255//------------------------------------------------------------------------------
256
257template <class Handler>
258void
260{
261 error_code ec;
262
263 if (acceptor_.is_open())
264 {
265 acceptor_.close(ec);
266 if (ec)
267 {
269 ss << "Can't close acceptor: " << port_.name << ", "
270 << ec.message();
271 JLOG(j_.error()) << ss.str();
272 Throw<std::runtime_error>(ss.str());
273 }
274 }
275
276 endpoint_type const local_address = endpoint_type(port_.ip, port_.port);
277
278 acceptor_.open(local_address.protocol(), ec);
279 if (ec)
280 {
281 JLOG(j_.error()) << "Open port '" << port_.name
282 << "' failed:" << ec.message();
283 Throw<std::exception>();
284 }
285
286 acceptor_.set_option(
287 boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
288 if (ec)
289 {
290 JLOG(j_.error()) << "Option for port '" << port_.name
291 << "' failed:" << ec.message();
292 Throw<std::exception>();
293 }
294
295 acceptor_.bind(local_address, ec);
296 if (ec)
297 {
298 JLOG(j_.error()) << "Bind port '" << port_.name
299 << "' failed:" << ec.message();
300 Throw<std::exception>();
301 }
302
303 acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
304 if (ec)
305 {
306 JLOG(j_.error()) << "Listen on port '" << port_.name
307 << "' failed:" << ec.message();
308 Throw<std::exception>();
309 }
310
311 JLOG(j_.info()) << "Opened " << port_;
312}
313
314template <class Handler>
316 Handler& handler,
317 boost::asio::io_context& io_context,
318 Port const& port,
320 : j_(j)
321 , port_(port)
322 , handler_(handler)
323 , ioc_(io_context)
324 , acceptor_(io_context)
325 , strand_(boost::asio::make_strand(io_context))
326 , ssl_(
327 port_.protocol.count("https") > 0 ||
328 port_.protocol.count("wss") > 0 || port_.protocol.count("wss2") > 0 ||
329 port_.protocol.count("peer") > 0)
330 , plain_(
331 port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 ||
332 port_.protocol.count("ws2"))
333 , backoff_timer_(io_context)
334{
335 reOpen();
336}
337
338template <class Handler>
339void
341{
343 strand_,
344 std::bind(
346 this->shared_from_this(),
347 std::placeholders::_1));
348}
349
350template <class Handler>
351void
353{
354 if (!strand_.running_in_this_thread())
355 return boost::asio::post(
356 strand_,
357 std::bind(&Door<Handler>::close, this->shared_from_this()));
358 backoff_timer_.cancel();
359 error_code ec;
360 acceptor_.close(ec);
361}
362
363//------------------------------------------------------------------------------
364
365template <class Handler>
366template <class ConstBufferSequence>
367void
369 bool ssl,
370 ConstBufferSequence const& buffers,
371 stream_type&& stream,
372 endpoint_type remote_address)
373{
374 if (ssl)
375 {
376 if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
377 port_,
378 handler_,
379 ioc_,
380 j_,
381 remote_address,
382 buffers,
383 std::move(stream)))
384 sp->run();
385 return;
386 }
387 if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
388 port_,
389 handler_,
390 ioc_,
391 j_,
392 remote_address,
393 buffers,
394 std::move(stream)))
395 sp->run();
396}
397
398template <class Handler>
399void
400Door<Handler>::do_accept(boost::asio::yield_context do_yield)
401{
402 while (acceptor_.is_open())
403 {
404 if (should_throttle_for_fds())
405 {
406 backoff_timer_.expires_after(accept_delay_);
407 boost::system::error_code tec;
408 backoff_timer_.async_wait(do_yield[tec]);
409 accept_delay_ = std::min(accept_delay_ * 2, MAX_ACCEPT_DELAY);
410 JLOG(j_.warn()) << "Throttling do_accept for "
411 << accept_delay_.count() << "ms.";
412 continue;
413 }
414
415 error_code ec;
416 endpoint_type remote_address;
417 stream_type stream(ioc_);
418 socket_type& socket = stream.socket();
419 acceptor_.async_accept(socket, remote_address, do_yield[ec]);
420 if (ec)
421 {
422 if (ec == boost::asio::error::operation_aborted)
423 break;
424
425 if (ec == boost::asio::error::no_descriptors ||
426 ec == boost::asio::error::no_buffer_space)
427 {
428 JLOG(j_.warn()) << "accept: Too many open files. Pausing for "
429 << accept_delay_.count() << "ms.";
430
431 backoff_timer_.expires_after(accept_delay_);
432 boost::system::error_code tec;
433 backoff_timer_.async_wait(do_yield[tec]);
434
435 accept_delay_ = std::min(accept_delay_ * 2, MAX_ACCEPT_DELAY);
436 }
437 else
438 {
439 JLOG(j_.error()) << "accept error: " << ec.message();
440 }
441 continue;
442 }
443
444 accept_delay_ = INITIAL_ACCEPT_DELAY;
445
446 if (ssl_ && plain_)
447 {
448 if (auto sp = ios().template emplace<Detector>(
449 port_,
450 handler_,
451 ioc_,
452 std::move(stream),
453 remote_address,
454 j_))
455 sp->run();
456 }
457 else if (ssl_ || plain_)
458 {
459 create(
460 ssl_,
461 boost::asio::null_buffers{},
462 std::move(stream),
463 remote_address);
464 }
465 }
466}
467
468template <class Handler>
471{
472#if BOOST_OS_WINDOWS
473 return std::nullopt;
474#else
475 FDStats s;
476 struct rlimit rl;
477 if (getrlimit(RLIMIT_NOFILE, &rl) != 0 || rl.rlim_cur == RLIM_INFINITY)
478 return std::nullopt;
479 s.limit = static_cast<std::uint64_t>(rl.rlim_cur);
480#if BOOST_OS_LINUX
481 constexpr char const* kFdDir = "/proc/self/fd";
482#else
483 constexpr char const* kFdDir = "/dev/fd";
484#endif
485 if (DIR* d = ::opendir(kFdDir))
486 {
487 std::uint64_t cnt = 0;
488 while (::readdir(d) != nullptr)
489 ++cnt;
490 ::closedir(d);
491 // readdir counts '.', '..', and the DIR* itself shows in the list
492 s.used = (cnt >= 3) ? (cnt - 3) : 0;
493 return s;
494 }
495 return std::nullopt;
496#endif
497}
498
499template <class Handler>
500bool
502{
503#if BOOST_OS_WINDOWS
504 return false;
505#else
506 auto const stats = query_fd_stats();
507 if (!stats || stats->limit == 0)
508 return false;
509
510 auto const& s = *stats;
511 auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull;
512 double const free_ratio =
513 static_cast<double>(free) / static_cast<double>(s.limit);
514 if (free_ratio < FREE_FD_THRESHOLD)
515 {
516 return true;
517 }
518 return false;
519#endif
520}
521
522} // namespace ripple
523
524#endif
T bind(T... args)
A generic endpoint for log messages.
Definition Journal.h:60
Stream error() const
Definition Journal.h:346
Stream info() const
Definition Journal.h:334
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
Stream warn() const
Definition Journal.h:340
boost::asio::strand< boost::asio::io_context::executor_type > strand_
Definition Door.h:86
Handler & handler_
Definition Door.h:81
beast::Journal const j_
Definition Door.h:87
socket_type & socket_
Definition Door.h:84
void close() override
Definition Door.h:208
endpoint_type remote_address_
Definition Door.h:85
void do_detect(yield_context yield)
Definition Door.h:215
stream_type stream_
Definition Door.h:83
Port const & port_
Definition Door.h:80
boost::asio::io_context & ioc_
Definition Door.h:82
Detector(Port const &port, Handler &handler, boost::asio::io_context &ioc, stream_type &&stream, endpoint_type remote_address, beast::Journal j)
Definition Door.h:176
A listening socket.
Definition Door.h:63
void do_accept(yield_context yield)
Definition Door.h:400
void create(bool ssl, ConstBufferSequence const &buffers, stream_type &&stream, endpoint_type remote_address)
Definition Door.h:368
static constexpr std::chrono::milliseconds INITIAL_ACCEPT_DELAY
Definition Door.h:115
protocol_type::endpoint endpoint_type
Definition Door.h:71
boost::asio::io_context & ioc_
Definition Door.h:110
Door(Handler &handler, boost::asio::io_context &io_context, Port const &port, beast::Journal j)
Definition Door.h:315
boost::beast::tcp_stream stream_type
Definition Door.h:73
boost::asio::basic_waitable_timer< clock_type > timer_type
Definition Door.h:66
static constexpr double FREE_FD_THRESHOLD
Definition Door.h:119
beast::Journal const j_
Definition Door.h:107
bool should_throttle_for_fds()
Definition Door.h:501
std::optional< FDStats > query_fd_stats() const
Definition Door.h:470
protocol_type::acceptor acceptor_type
Definition Door.h:70
boost::system::error_code error_code
Definition Door.h:67
bool ssl_
Definition Door.h:113
boost::asio::ip::tcp protocol_type
Definition Door.h:69
boost::asio::strand< boost::asio::io_context::executor_type > strand_
Definition Door.h:112
boost::asio::steady_timer backoff_timer_
Definition Door.h:118
std::chrono::milliseconds accept_delay_
Definition Door.h:117
void reOpen()
Definition Door.h:259
boost::asio::yield_context yield_context
Definition Door.h:68
Handler & handler_
Definition Door.h:109
void close() override
Close the Door listening socket and connections.
Definition Door.h:352
endpoint_type get_endpoint() const
Definition Door.h:157
Port const & port_
Definition Door.h:108
acceptor_type acceptor_
Definition Door.h:111
void run()
Definition Door.h:340
bool plain_
Definition Door.h:114
boost::asio::ip::tcp::socket socket_type
Definition Door.h:72
static constexpr std::chrono::milliseconds MAX_ACCEPT_DELAY
Definition Door.h:116
io_list & ios()
Return the io_list associated with the work.
Definition io_list.h:60
T is_same_v
T min(T... args)
void spawn(Ctx &&ctx, F &&func)
Spawns a coroutine using boost::asio::spawn
Definition Spawn.h:87
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
boost::beast::ssl_stream< socket_type > stream_type
Definition Handshake.h:42
STL namespace.
T str(T... args)
std::uint64_t used
Definition Door.h:123
std::uint64_t limit
Definition Door.h:124
Configuration information for a Server listening port.
Definition Port.h:50
std::uint16_t port
Definition Port.h:55
boost::asio::ip::address ip
Definition Port.h:54
std::string name
Definition Port.h:53