xrpld
Loading...
Searching...
No Matches
Door.h
1#pragma once
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/server/Port.h>
7#include <xrpl/server/detail/PlainHTTPPeer.h>
8#include <xrpl/server/detail/SSLHTTPPeer.h>
9#include <xrpl/server/detail/io_list.h>
10
11#include <boost/asio/basic_waitable_timer.hpp>
12#include <boost/asio/buffer.hpp>
13#include <boost/asio/io_context.hpp>
14#include <boost/asio/ip/tcp.hpp>
15#include <boost/asio/post.hpp>
16#include <boost/asio/spawn.hpp>
17#include <boost/asio/steady_timer.hpp>
18#include <boost/beast/core/detect_ssl.hpp>
19#include <boost/beast/core/multi_buffer.hpp>
20#include <boost/beast/core/tcp_stream.hpp>
21#include <boost/container/flat_map.hpp>
22#include <boost/predef.h>
23
24#include <exception>
25#include <stdexcept>
26
27#if !BOOST_OS_WINDOWS
28#include <sys/resource.h>
29
30#include <dirent.h>
31#endif
32
33#include <algorithm>
34#include <chrono>
35#include <cstdint>
36#include <memory>
37#include <optional>
38#include <sstream>
39#include <utility>
40
41namespace xrpl {
42
46template <class Handler>
47class Door : public IOList::Work, public std::enable_shared_from_this<Door<Handler>>
48{
49private:
51 using timer_type = boost::asio::basic_waitable_timer<clock_type>;
52 using error_code = boost::system::error_code;
53 using yield_context = boost::asio::yield_context;
54 using protocol_type = boost::asio::ip::tcp;
55 using acceptor_type = protocol_type::acceptor;
56 using endpoint_type = protocol_type::endpoint;
57 using socket_type = boost::asio::ip::tcp::socket;
58 using stream_type = boost::beast::tcp_stream;
59
60 // Detects SSL on a socket
61 class Detector : public IOList::Work, public std::enable_shared_from_this<Detector>
62 {
63 private:
64 Port const& port_;
65 Handler& handler_;
66 boost::asio::io_context& ioc_;
70 boost::asio::strand<boost::asio::io_context::executor_type> strand_;
72
73 public:
75 Port const& port,
76 Handler& handler,
77 boost::asio::io_context& ioc,
78 stream_type&& stream,
79 endpoint_type remoteAddress,
81 void
82 run();
83 void
84 close() override;
85
86 private:
87 void
89 };
90
92 Port const& port_;
93 Handler& handler_;
94 boost::asio::io_context& ioc_;
96 boost::asio::strand<boost::asio::io_context::executor_type> strand_;
97 bool ssl_{
98 port_.protocol.contains("https") || port_.protocol.contains("wss") ||
99 port_.protocol.contains("wss2") || port_.protocol.contains("peer")};
100 bool plain_{
101 port_.protocol.contains("http") || port_.protocol.contains("ws") ||
102 (port_.protocol.contains("ws2"))};
106 boost::asio::steady_timer backoffTimer_;
107 static constexpr std::uint64_t kMaxUsedFdPercent = 70;
109 clock_type::time_point fdSampleAt_;
110 bool cachedThrottle_{false};
111
117
118 void
119 reOpen();
120
122 queryFdStats() const;
123
124 bool
126
127public:
128 Door(Handler& handler, boost::asio::io_context& ioContext, Port const& port, beast::Journal j);
129
130 // Work-around because we can't call shared_from_this in ctor
131 void
132 run();
133
141 void
142 close() override;
143
144 [[nodiscard]] endpoint_type
146 {
147 return acceptor_.local_endpoint();
148 }
149
150private:
151 template <class ConstBufferSequence>
152 void
153 create(
154 bool ssl,
155 ConstBufferSequence const& buffers,
156 stream_type&& stream,
157 endpoint_type remoteAddress);
158
159 void
160 doAccept(yield_context yield);
161};
162
163template <class Handler>
165 Port const& port,
166 Handler& handler,
167 boost::asio::io_context& ioc,
168 stream_type&& stream,
169 endpoint_type remoteAddress,
171 : port_(port)
172 , handler_(handler)
173 , ioc_(ioc)
174 , stream_(std::move(stream))
175 , socket_(stream_.socket())
176 , remoteAddress_(std::move(remoteAddress))
177 , strand_(boost::asio::make_strand(ioc_))
178 , j_(j)
179{
180}
181
182template <class Handler>
183void
185{
187 strand_, [self = this->shared_from_this()](yield_context yield) { self->doDetect(yield); });
188}
189
190template <class Handler>
191void
196
197template <class Handler>
198void
199Door<Handler>::Detector::doDetect(boost::asio::yield_context doYield)
200{
201 boost::beast::multi_buffer buf(16);
202 stream_.expires_after(std::chrono::seconds(15));
203 boost::system::error_code ec;
204 bool const ssl = async_detect_ssl(stream_, buf, doYield[ec]);
205 stream_.expires_never();
206 if (!ec)
207 {
208 if (ssl)
209 {
210 if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
211 port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
212 sp->run();
213 return;
214 }
215 if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
216 port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
217 sp->run();
218 return;
219 }
220 if (ec != boost::asio::error::operation_aborted)
221 {
222 JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remoteAddress_;
223 }
224}
225
226//------------------------------------------------------------------------------
227
228template <class Handler>
229void
231{
232 error_code ec;
233
234 if (acceptor_.is_open())
235 {
236 acceptor_.close(ec);
237 if (ec)
238 {
240 ss << "Can't close acceptor: " << port_.name << ", " << ec.message();
241 JLOG(j_.error()) << ss.str();
243 }
244 }
245
246 endpoint_type const localAddress = endpoint_type(port_.ip, port_.port);
247
248 acceptor_.open(localAddress.protocol(), ec);
249 if (ec)
250 {
251 JLOG(j_.error()) << "Open port '" << port_.name << "' failed:" << ec.message();
253 }
254
255 acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
256 if (ec)
257 {
258 JLOG(j_.error()) << "Option for port '" << port_.name << "' failed:" << ec.message();
260 }
261
262 acceptor_.bind(localAddress, ec);
263 if (ec)
264 {
265 JLOG(j_.error()) << "Bind port '" << port_.name << "' failed:" << ec.message();
267 }
268
269 acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
270 if (ec)
271 {
272 JLOG(j_.error()) << "Listen on port '" << port_.name << "' failed:" << ec.message();
274 }
275
276 JLOG(j_.info()) << "Opened " << port_;
277}
278
279template <class Handler>
281 Handler& handler,
282 boost::asio::io_context& ioContext,
283 Port const& port,
285 : j_(j)
286 , port_(port)
287 , handler_(handler)
288 , ioc_(ioContext)
289 , acceptor_(ioContext)
290 , strand_(boost::asio::make_strand(ioContext))
291 , backoffTimer_(ioContext)
293{
294 reOpen();
295}
296
297template <class Handler>
298void
300{
302 strand_, [self = this->shared_from_this()](yield_context yield) { self->doAccept(yield); });
303}
304
305template <class Handler>
306void
308{
309 if (!strand_.running_in_this_thread())
310 {
311 return boost::asio::post(strand_, [self = this->shared_from_this()] { self->close(); });
312 }
313 backoffTimer_.cancel();
314 error_code ec;
315 acceptor_.close(ec);
316}
317
318//------------------------------------------------------------------------------
319
320template <class Handler>
321template <class ConstBufferSequence>
322void
324 bool ssl,
325 ConstBufferSequence const& buffers,
326 stream_type&& stream,
327 endpoint_type remoteAddress)
328{
329 if (ssl)
330 {
331 if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
332 port_, handler_, ioc_, j_, remoteAddress, buffers, std::move(stream)))
333 sp->run();
334 return;
335 }
336 if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
337 port_, handler_, ioc_, j_, remoteAddress, buffers, std::move(stream)))
338 sp->run();
339}
340
341template <class Handler>
342void
343Door<Handler>::doAccept(boost::asio::yield_context doYield)
344{
345 while (acceptor_.is_open())
346 {
348 {
349 JLOG(j_.warn()) << "Throttling do_accept for " << acceptDelay_.count() << "ms.";
350 backoffTimer_.expires_after(acceptDelay_);
351 boost::system::error_code tec;
352 backoffTimer_.async_wait(doYield[tec]);
354 continue;
355 }
356
357 error_code ec;
358 endpoint_type remoteAddress;
359 stream_type stream(ioc_);
360 socket_type& socket = stream.socket();
361 acceptor_.async_accept(socket, remoteAddress, doYield[ec]);
362 if (ec)
363 {
364 if (ec == boost::asio::error::operation_aborted)
365 break;
366
367 if (ec == boost::asio::error::no_descriptors ||
368 ec == boost::asio::error::no_buffer_space)
369 {
370 char const* const cause = (ec == boost::asio::error::no_descriptors)
371 ? "too many open files"
372 : "kernel buffer space exhausted";
373 JLOG(j_.warn()) << "accept: " << cause << ". Pausing for " << acceptDelay_.count()
374 << "ms.";
375
376 backoffTimer_.expires_after(acceptDelay_);
377 boost::system::error_code tec;
378 backoffTimer_.async_wait(doYield[tec]);
379
381 }
382 else
383 {
384 JLOG(j_.error()) << "accept error: " << ec.message();
385 }
386 continue;
387 }
388
390
391 if (ssl_ && plain_)
392 {
393 if (auto sp = ios().template emplace<Detector>(
394 port_, handler_, ioc_, std::move(stream), remoteAddress, j_))
395 sp->run();
396 }
397 else if (ssl_ || plain_)
398 {
399 create(ssl_, boost::asio::null_buffers{}, std::move(stream), remoteAddress);
400 }
401 }
402}
403
404template <class Handler>
407{
408#if BOOST_OS_WINDOWS
409 return std::nullopt;
410#else
411 FDStats s;
412 struct rlimit rl{};
413 if (getrlimit(RLIMIT_NOFILE, &rl) != 0 || rl.rlim_cur == RLIM_INFINITY)
414 return std::nullopt;
415 s.limit = static_cast<std::uint64_t>(rl.rlim_cur);
416#if BOOST_OS_LINUX
417 static constexpr char const* kFdDir = "/proc/self/fd";
418#else
419 static constexpr char const* kFdDir = "/dev/fd";
420#endif
421 if (DIR* d = ::opendir(kFdDir))
422 {
423 std::uint64_t cnt = 0;
424 while (::readdir(d) != nullptr)
425 ++cnt;
426 ::closedir(d);
427 // readdir counts '.', '..', and the DIR* itself shows in the list
428 s.used = (cnt >= 3) ? (cnt - 3) : 0;
429 return s;
430 }
431 return std::nullopt;
432#endif
433}
434
435template <class Handler>
436bool
438{
439#if BOOST_OS_WINDOWS
440 return false;
441#else
442 auto const now = clock_type::now();
443 if (now - fdSampleAt_ < kFdSampleInterval)
444 return cachedThrottle_;
445
446 fdSampleAt_ = now;
447 auto const stats = queryFdStats();
449 stats && stats->limit > 0 && stats->used * 100 > stats->limit * kMaxUsedFdPercent;
450 return cachedThrottle_;
451#endif
452}
453
454} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Handler & handler_
Definition Door.h:65
stream_type stream_
Definition Door.h:67
Port const & port_
Definition Door.h:64
boost::asio::strand< boost::asio::io_context::executor_type > strand_
Definition Door.h:70
void close() override
Definition Door.h:192
beast::Journal const j_
Definition Door.h:71
void doDetect(yield_context yield)
Definition Door.h:199
boost::asio::io_context & ioc_
Definition Door.h:66
socket_type & socket_
Definition Door.h:68
endpoint_type remoteAddress_
Definition Door.h:69
Detector(Port const &port, Handler &handler, boost::asio::io_context &ioc, stream_type &&stream, endpoint_type remoteAddress, beast::Journal j)
Definition Door.h:164
beast::Journal const j_
Definition Door.h:91
protocol_type::acceptor acceptor_type
Definition Door.h:55
void run()
Definition Door.h:299
boost::asio::ip::tcp::socket socket_type
Definition Door.h:57
boost::asio::io_context & ioc_
Definition Door.h:94
bool ssl_
Definition Door.h:97
static constexpr std::chrono::milliseconds kFdSampleInterval
Definition Door.h:108
boost::asio::strand< boost::asio::io_context::executor_type > strand_
Definition Door.h:96
void create(bool ssl, ConstBufferSequence const &buffers, stream_type &&stream, endpoint_type remoteAddress)
Definition Door.h:323
acceptor_type acceptor_
Definition Door.h:95
bool plain_
Definition Door.h:100
endpoint_type getEndpoint() const
Definition Door.h:145
boost::asio::yield_context yield_context
Definition Door.h:53
boost::beast::tcp_stream stream_type
Definition Door.h:58
static constexpr std::uint64_t kMaxUsedFdPercent
Definition Door.h:107
protocol_type::endpoint endpoint_type
Definition Door.h:56
void doAccept(yield_context yield)
Definition Door.h:343
static constexpr std::chrono::milliseconds kMaxAcceptDelay
Definition Door.h:104
boost::system::error_code error_code
Definition Door.h:52
std::chrono::milliseconds acceptDelay_
Definition Door.h:105
boost::asio::basic_waitable_timer< clock_type > timer_type
Definition Door.h:51
void close() override
Close the Door listening socket and connections.
Definition Door.h:307
clock_type::time_point fdSampleAt_
Definition Door.h:109
Door(Handler &handler, boost::asio::io_context &ioContext, Port const &port, beast::Journal j)
Definition Door.h:280
bool shouldThrottleForFds()
Definition Door.h:437
boost::asio::ip::tcp protocol_type
Definition Door.h:54
std::optional< FDStats > queryFdStats() const
Definition Door.h:406
Handler & handler_
Definition Door.h:93
std::chrono::steady_clock clock_type
Definition Door.h:50
Port const & port_
Definition Door.h:92
bool cachedThrottle_
Definition Door.h:110
void reOpen()
Definition Door.h:230
static constexpr std::chrono::milliseconds kInitialAcceptDelay
Definition Door.h:103
boost::asio::steady_timer backoffTimer_
Definition Door.h:106
IOList & ios()
Return the IOList associated with the work.
Definition io_list.h:44
T min(T... args)
STL namespace.
void spawn(Ctx &&ctx, F &&func)
Spawns a coroutine using boost::asio::spawn.
Definition Spawn.h:67
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
boost::beast::ssl_stream< socket_type > stream_type
Definition Handshake.h:24
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T str(T... args)
std::uint64_t limit
Definition Door.h:115
std::uint64_t used
Definition Door.h:114
Configuration information for a Server listening port.
Definition Port.h:29