rippled
Loading...
Searching...
No Matches
ServerHandler.cpp
1#include <xrpld/app/main/Application.h>
2#include <xrpld/core/ConfigSections.h>
3#include <xrpld/overlay/Overlay.h>
4#include <xrpld/rpc/RPCHandler.h>
5#include <xrpld/rpc/Role.h>
6#include <xrpld/rpc/ServerHandler.h>
7#include <xrpld/rpc/detail/Tuning.h>
8#include <xrpld/rpc/detail/WSInfoSub.h>
9#include <xrpld/rpc/json_body.h>
10
11#include <xrpl/basics/base64.h>
12#include <xrpl/basics/contract.h>
13#include <xrpl/basics/make_SSLContext.h>
14#include <xrpl/beast/net/IPAddressConversion.h>
15#include <xrpl/beast/rfc2616.h>
16#include <xrpl/core/JobQueue.h>
17#include <xrpl/json/json_reader.h>
18#include <xrpl/json/to_string.h>
19#include <xrpl/protocol/ApiVersion.h>
20#include <xrpl/protocol/ErrorCodes.h>
21#include <xrpl/protocol/RPCErr.h>
22#include <xrpl/resource/Fees.h>
23#include <xrpl/resource/ResourceManager.h>
24#include <xrpl/server/NetworkOPs.h>
25#include <xrpl/server/Server.h>
26#include <xrpl/server/SimpleWriter.h>
27#include <xrpl/server/detail/JSONRPCUtil.h>
28
29#include <boost/algorithm/string.hpp>
30#include <boost/beast/http/fields.hpp>
31#include <boost/beast/http/string_body.hpp>
32
33#include <algorithm>
34#include <memory>
35#include <stdexcept>
36
37namespace xrpl {
38
39class Peer;
40class LedgerMaster;
41class Transaction;
42class ValidatorKeys;
43class CanonicalTXSet;
44
45static bool
47{
48 return request.version() >= 11 && request.target() == "/" && request.body().size() == 0 &&
49 request.method() == boost::beast::http::verb::get;
50}
51
52static Handoff
53statusRequestResponse(http_request_type const& request, boost::beast::http::status status)
54{
55 using namespace boost::beast::http;
56 Handoff handoff;
57 response<string_body> msg;
58 msg.version(request.version());
59 msg.result(status);
60 msg.insert("Server", BuildInfo::getFullVersionString());
61 msg.insert("Content-Type", "text/html");
62 msg.insert("Connection", "close");
63 msg.body() = "Invalid protocol.";
64 msg.prepare_payload();
66 return handoff;
67}
68
69// VFALCO TODO Rewrite to use boost::beast::http::fields
70static bool
72{
73 if (port.user.empty() || port.password.empty())
74 return true;
75
76 auto const it = h.find("authorization");
77 if ((it == h.end()) || (it->second.substr(0, 6) != "Basic "))
78 return false;
79 std::string strUserPass64 = it->second.substr(6);
80 boost::trim(strUserPass64);
81 std::string const strUserPass = base64_decode(strUserPass64);
82 std::string::size_type const nColon = strUserPass.find(':');
83 if (nColon == std::string::npos)
84 return false;
85 std::string const strUser = strUserPass.substr(0, nColon);
86 std::string const strPassword = strUserPass.substr(nColon + 1);
87 return strUser == port.user && strPassword == port.password;
88}
89
92 Application& app,
93 boost::asio::io_context& io_context,
94 JobQueue& jobQueue,
95 NetworkOPs& networkOPs,
96 Resource::Manager& resourceManager,
98 : app_(app)
99 , m_resourceManager(resourceManager)
100 , m_journal(app_.getJournal("Server"))
101 , m_networkOPs(networkOPs)
102 , m_server(make_Server(*this, io_context, app_.getJournal("Server")))
103 , m_jobQueue(jobQueue)
104{
105 auto const& group(cm.group("rpc"));
106 rpc_requests_ = group->make_counter("requests");
107 rpc_size_ = group->make_event("size");
108 rpc_time_ = group->make_event("time");
109}
110
112{
113 m_server = nullptr;
114}
115
116void
118{
119 setup_ = setup;
120 endpoints_ = m_server->ports(setup.ports);
121
122 // fix auto ports
123 for (auto& port : setup_.ports)
124 {
125 if (auto it = endpoints_.find(port.name); it != endpoints_.end())
126 {
127 auto const endpointPort = it->second.port();
128 if (port.port == 0u)
129 port.port = endpointPort;
130
131 if ((setup_.client.port == 0u) &&
132 (port.protocol.count("http") > 0 || port.protocol.count("https") > 0))
133 setup_.client.port = endpointPort;
134
135 if ((setup_.overlay.port() == 0u) && (port.protocol.count("peer") > 0))
136 setup_.overlay.port(endpointPort);
137 }
138 }
139}
140
141//------------------------------------------------------------------------------
142
143void
145{
146 m_server->close();
147 {
149 condition_.wait(lock, [this] { return stopped_; });
150 }
151}
152
153//------------------------------------------------------------------------------
154
155bool
156ServerHandler::onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
157{
158 auto const& port = session.port();
159
160 auto const c = [this, &port]() {
161 std::lock_guard const lock(mutex_);
162 return ++count_[port];
163 }();
164
165 if ((port.limit != 0) && c >= port.limit)
166 {
167 JLOG(m_journal.trace()) << port.name << " is full; dropping " << endpoint;
168 return false;
169 }
170
171 return true;
172}
173
176 Session& session,
178 http_request_type&& request,
179 boost::asio::ip::tcp::endpoint const& remote_address)
180{
181 using namespace boost::beast;
182 auto const& p{session.port().protocol};
183 bool const is_ws{
184 p.count("ws") > 0 || p.count("ws2") > 0 || p.count("wss") > 0 || p.count("wss2") > 0};
185
186 if (websocket::is_upgrade(request))
187 {
188 if (!is_ws)
189 return statusRequestResponse(request, http::status::unauthorized);
190
192 try
193 {
194 ws = session.websocketUpgrade();
195 }
196 catch (std::exception const& e)
197 {
198 JLOG(m_journal.error()) << "Exception upgrading websocket: " << e.what() << "\n";
199 return statusRequestResponse(request, http::status::internal_server_error);
200 }
201
203 auto const beast_remote_address = beast::IPAddressConversion::from_asio(remote_address);
204 is->getConsumer() = requestInboundEndpoint(
206 beast_remote_address,
208 Role::GUEST, session.port(), Json::Value(), beast_remote_address, is->user()),
209 is->user(),
210 is->forwarded_for());
211 ws->appDefined = std::move(is);
212 ws->run();
213
214 Handoff handoff;
215 handoff.moved = true;
216 return handoff;
217 }
218
219 if (bundle && p.count("peer") > 0)
220 return app_.getOverlay().onHandoff(std::move(bundle), std::move(request), remote_address);
221
222 if (is_ws && isStatusRequest(request))
223 return statusResponse(request);
224
225 // Otherwise pass to legacy onRequest or websocket
226 return {};
227}
228
229static inline Json::Output
231{
232 return [&](boost::beast::string_view const& b) { session.write(b.data(), b.size()); };
233}
234
236build_map(boost::beast::http::fields const& h)
237{
239 for (auto const& e : h)
240 {
241 // key cannot be a std::string_view because it needs to be used in
242 // map and along with iterators
243 std::string key(e.name_string());
244 std::transform(key.begin(), key.end(), key.begin(), [](auto kc) {
245 return std::tolower(static_cast<unsigned char>(kc));
246 });
247 c[key] = e.value();
248 }
249 return c;
250}
251
252template <class ConstBufferSequence>
253static std::string
254buffers_to_string(ConstBufferSequence const& bs)
255{
256 using boost::asio::buffer_size;
257 std::string s;
258 s.reserve(buffer_size(bs));
259 // Use auto&& so the right thing happens whether bs returns a copy or
260 // a reference
261 for (auto&& b : bs)
262 s.append(static_cast<char const*>(b.data()), buffer_size(b));
263 return s;
264}
265
266void
268{
269 // Make sure RPC is enabled on the port
270 if (session.port().protocol.count("http") == 0 && session.port().protocol.count("https") == 0)
271 {
272 HTTPReply(403, "Forbidden", makeOutput(session), app_.getJournal("RPC"));
273 session.close(true);
274 return;
275 }
276
277 // Check user/password authorization
278 if (!authorized(session.port(), build_map(session.request())))
279 {
280 HTTPReply(403, "Forbidden", makeOutput(session), app_.getJournal("RPC"));
281 session.close(true);
282 return;
283 }
284
285 std::shared_ptr<Session> const detachedSession = session.detach();
286 auto const postResult = m_jobQueue.postCoro(
287 jtCLIENT_RPC, "RPC-Client", [this, detachedSession](std::shared_ptr<JobQueue::Coro> coro) {
288 processSession(detachedSession, coro);
289 });
290 if (postResult == nullptr)
291 {
292 // The coroutine was rejected, probably because we're shutting down.
293 HTTPReply(503, "Service Unavailable", makeOutput(*detachedSession), app_.getJournal("RPC"));
294 detachedSession->close(true);
295 return;
296 }
297}
298
299void
303{
304 Json::Value jv;
305 auto const size = boost::asio::buffer_size(buffers);
306 if (size > RPC::Tuning::maxRequestSize || !Json::Reader{}.parse(jv, buffers) || !jv.isObject())
307 {
309 jvResult[jss::type] = jss::error;
310 jvResult[jss::error] = "jsonInvalid";
311 jvResult[jss::value] = buffers_to_string(buffers);
312 boost::beast::multi_buffer sb;
313 Json::stream(jvResult, [&sb](auto const p, auto const n) {
314 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(p, n)));
315 });
316 JLOG(m_journal.trace()) << "Websocket sending '" << jvResult << "'";
317 session->send(std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
318 session->complete();
319 return;
320 }
321
322 JLOG(m_journal.trace()) << "Websocket received '" << jv << "'";
323
324 auto const postResult = m_jobQueue.postCoro(
326 "WS-Client",
327 [this, session, jv = std::move(jv)](std::shared_ptr<JobQueue::Coro> const& coro) {
328 auto const jr = this->processSession(session, coro, jv);
329 auto const s = to_string(jr);
330 auto const n = s.length();
331 boost::beast::multi_buffer sb(n);
332 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
333 session->send(std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
334 session->complete();
335 });
336 if (postResult == nullptr)
337 {
338 // The coroutine was rejected, probably because we're shutting down.
339 session->close({boost::beast::websocket::going_away, "Shutting Down"});
340 }
341}
342
343void
344ServerHandler::onClose(Session& session, boost::system::error_code const&)
345{
346 std::lock_guard const lock(mutex_);
347 --count_[session.port()];
348}
349
350void
357
358//------------------------------------------------------------------------------
359
360template <class T>
361void
362logDuration(Json::Value const& request, T const& duration, beast::Journal& journal)
363{
364 using namespace std::chrono_literals;
365 auto const level = [&]() {
366 if (duration >= 10s)
367 return journal.error();
368 if (duration >= 1s)
369 return journal.warn();
370 return journal.debug();
371 }();
372
373 JLOG(level) << "RPC request processing duration = "
374 << std::chrono::duration_cast<std::chrono::microseconds>(duration).count()
375 << " microseconds. request = " << request;
376}
377
380 std::shared_ptr<WSSession> const& session,
382 Json::Value const& jv)
383{
384 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
385 if (is->getConsumer().disconnect(m_journal))
386 {
387 session->close({boost::beast::websocket::policy_error, "threshold exceeded"});
388 // FIX: This rpcError is not delivered since the session
389 // was just closed.
390 return rpcError(rpcSLOW_DOWN);
391 }
392
393 // Requests without "command" are invalid.
396 try
397 {
398 auto apiVersion = RPC::getAPIVersionNumber(jv, app_.config().BETA_RPC_API);
399 if (apiVersion == RPC::apiInvalidVersion ||
400 (!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
401 (jv.isMember(jss::command) && !jv[jss::command].isString()) ||
402 (jv.isMember(jss::method) && !jv[jss::method].isString()) ||
403 (jv.isMember(jss::command) && jv.isMember(jss::method) &&
404 jv[jss::command].asString() != jv[jss::method].asString()))
405 {
406 jr[jss::type] = jss::response;
407 jr[jss::status] = jss::error;
408 jr[jss::error] = apiVersion == RPC::apiInvalidVersion ? jss::invalid_API_version
409 : jss::missingCommand;
410 jr[jss::request] = jv;
411 if (jv.isMember(jss::id))
412 jr[jss::id] = jv[jss::id];
413 if (jv.isMember(jss::jsonrpc))
414 jr[jss::jsonrpc] = jv[jss::jsonrpc];
415 if (jv.isMember(jss::ripplerpc))
416 jr[jss::ripplerpc] = jv[jss::ripplerpc];
417 if (jv.isMember(jss::api_version))
418 jr[jss::api_version] = jv[jss::api_version];
419
420 is->getConsumer().charge(Resource::feeMalformedRPC);
421 return jr;
422 }
423
424 auto required = RPC::roleRequired(
425 apiVersion,
427 jv.isMember(jss::command) ? jv[jss::command].asString() : jv[jss::method].asString());
428 auto role = requestRole(
429 required,
430 session->port(),
431 jv,
432 beast::IP::from_asio(session->remote_endpoint().address()),
433 is->user());
434 if (Role::FORBID == role)
435 {
436 loadType = Resource::feeMalformedRPC;
437 jr[jss::result] = rpcError(rpcFORBIDDEN);
438 }
439 else
440 {
441 RPC::JsonContext context{
442 {app_.getJournal("RPCHandler"),
443 app_,
444 loadType,
445 app_.getOPs(),
447 is->getConsumer(),
448 role,
449 coro,
450 is,
451 apiVersion},
452 jv,
453 {is->user(), is->forwarded_for()}};
454
455 auto start = std::chrono::system_clock::now();
456 RPC::doCommand(context, jr[jss::result]);
458 logDuration(jv, end - start, m_journal);
459 }
460 }
461 catch (std::exception const& ex)
462 {
463 // LCOV_EXCL_START
464 jr[jss::result] = RPC::make_error(rpcINTERNAL);
465 JLOG(m_journal.error()) << "Exception while processing WS: " << ex.what() << "\n"
466 << "Input JSON: " << Json::Compact{Json::Value{jv}};
467 // LCOV_EXCL_STOP
468 }
469
470 is->getConsumer().charge(loadType);
471 if (is->getConsumer().warn())
472 jr[jss::warning] = jss::load;
473
474 // Currently we will simply unwrap errors returned by the RPC
475 // API, in the future maybe we can make the responses
476 // consistent.
477 //
478 // Regularize result. This is duplicate code.
479 if (jr[jss::result].isMember(jss::error))
480 {
481 jr = jr[jss::result];
482 jr[jss::status] = jss::error;
483
484 auto rq = jv;
485
486 if (rq.isObject())
487 {
488 if (rq.isMember(jss::passphrase.c_str()))
489 rq[jss::passphrase.c_str()] = "<masked>";
490 if (rq.isMember(jss::secret.c_str()))
491 rq[jss::secret.c_str()] = "<masked>";
492 if (rq.isMember(jss::seed.c_str()))
493 rq[jss::seed.c_str()] = "<masked>";
494 if (rq.isMember(jss::seed_hex.c_str()))
495 rq[jss::seed_hex.c_str()] = "<masked>";
496 }
497
498 jr[jss::request] = rq;
499 }
500 else
501 {
502 if (jr[jss::result].isMember("forwarded") && jr[jss::result]["forwarded"])
503 jr = jr[jss::result];
504 jr[jss::status] = jss::success;
505 }
506
507 if (jv.isMember(jss::id))
508 jr[jss::id] = jv[jss::id];
509 if (jv.isMember(jss::jsonrpc))
510 jr[jss::jsonrpc] = jv[jss::jsonrpc];
511 if (jv.isMember(jss::ripplerpc))
512 jr[jss::ripplerpc] = jv[jss::ripplerpc];
513 if (jv.isMember(jss::api_version))
514 jr[jss::api_version] = jv[jss::api_version];
515
516 jr[jss::type] = jss::response;
517 return jr;
518}
519
520// Run as a coroutine.
521void
523 std::shared_ptr<Session> const& session,
525{
527 session->port(),
528 buffers_to_string(session->request().body().data()),
529 session->remoteAddress().at_port(0),
530 makeOutput(*session),
531 coro,
532 forwardedFor(session->request()),
533 [&] {
534 auto const iter = session->request().find("X-User");
535 if (iter != session->request().end())
536 return iter->value();
537 return boost::beast::string_view{};
538 }());
539
540 if (beast::rfc2616::is_keep_alive(session->request()))
541 {
542 session->complete();
543 }
544 else
545 {
546 session->close(true);
547 }
548}
549
550static Json::Value
552{
554 sub["code"] = code;
555 sub["message"] = std::move(message);
557 r["error"] = sub;
558 return r;
559}
560
561Json::Int constexpr method_not_found = -32601;
562Json::Int constexpr server_overloaded = -32604;
563Json::Int constexpr forbidden = -32605;
564Json::Int constexpr wrong_version = -32606;
565
566void
567ServerHandler::processRequest(
568 Port const& port,
569 std::string const& request,
570 beast::IP::Endpoint const& remoteIPAddress,
571 Output const& output,
573 std::string_view forwardedFor,
574 std::string_view user)
575{
576 auto rpcJ = app_.getJournal("RPC");
577
578 Json::Value jsonOrig;
579 {
580 Json::Reader reader;
581 if ((request.size() > RPC::Tuning::maxRequestSize) || !reader.parse(request, jsonOrig) ||
582 !jsonOrig || !jsonOrig.isObject())
583 {
584 HTTPReply(
585 400,
586 "Unable to parse request: " + reader.getFormattedErrorMessages(),
587 output,
588 rpcJ);
589 return;
590 }
591 }
592
593 bool batch = false;
594 unsigned size = 1;
595 if (jsonOrig.isMember(jss::method) && jsonOrig[jss::method] == "batch")
596 {
597 batch = true;
598 if (!jsonOrig.isMember(jss::params) || !jsonOrig[jss::params].isArray())
599 {
600 HTTPReply(400, "Malformed batch request", output, rpcJ);
601 return;
602 }
603 size = jsonOrig[jss::params].size();
604 }
605
607 auto const start(std::chrono::high_resolution_clock::now());
608 for (unsigned i = 0; i < size; ++i)
609 {
610 Json::Value const& jsonRPC = batch ? jsonOrig[jss::params][i] : jsonOrig;
611
612 if (!jsonRPC.isObject())
613 {
615 r[jss::request] = jsonRPC;
616 r[jss::error] = make_json_error(method_not_found, "Method not found");
617 reply.append(r);
618 continue;
619 }
620
621 unsigned apiVersion = RPC::apiVersionIfUnspecified;
622 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
623 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject())
624 {
625 apiVersion = RPC::getAPIVersionNumber(
626 jsonRPC[jss::params][Json::UInt(0)], app_.config().BETA_RPC_API);
627 }
628
629 if (apiVersion == RPC::apiVersionIfUnspecified && batch)
630 {
631 // for batch request, api_version may be at a different level
632 apiVersion = RPC::getAPIVersionNumber(jsonRPC, app_.config().BETA_RPC_API);
633 }
634
635 if (apiVersion == RPC::apiInvalidVersion)
636 {
637 if (!batch)
638 {
639 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
640 return;
641 }
643 r[jss::request] = jsonRPC;
644 r[jss::error] = make_json_error(wrong_version, jss::invalid_API_version.c_str());
645 reply.append(r);
646 continue;
647 }
648
649 /* ------------------------------------------------------------------ */
650 auto role = Role::FORBID;
651 auto required = Role::FORBID;
652 if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
653 {
654 required = RPC::roleRequired(
655 apiVersion, app_.config().BETA_RPC_API, jsonRPC[jss::method].asString());
656 }
657
658 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
659 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][Json::UInt(0)].isObjectOrNull())
660 {
661 role = requestRole(
662 required, port, jsonRPC[jss::params][Json::UInt(0)], remoteIPAddress, user);
663 }
664 else
665 {
666 role = requestRole(required, port, Json::objectValue, remoteIPAddress, user);
667 }
668
669 Resource::Consumer usage;
670 if (isUnlimited(role))
671 {
672 usage = m_resourceManager.newUnlimitedEndpoint(remoteIPAddress);
673 }
674 else
675 {
676 usage = m_resourceManager.newInboundEndpoint(
677 remoteIPAddress, role == Role::PROXY, forwardedFor);
678 if (usage.disconnect(m_journal))
679 {
680 if (!batch)
681 {
682 HTTPReply(503, "Server is overloaded", output, rpcJ);
683 return;
684 }
685 Json::Value r = jsonRPC;
686 r[jss::error] = make_json_error(server_overloaded, "Server is overloaded");
687 reply.append(r);
688 continue;
689 }
690 }
691
692 if (role == Role::FORBID)
693 {
694 usage.charge(Resource::feeMalformedRPC);
695 if (!batch)
696 {
697 HTTPReply(403, "Forbidden", output, rpcJ);
698 return;
699 }
700 Json::Value r = jsonRPC;
701 r[jss::error] = make_json_error(forbidden, "Forbidden");
702 reply.append(r);
703 continue;
704 }
705
706 if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull())
707 {
708 usage.charge(Resource::feeMalformedRPC);
709 if (!batch)
710 {
711 HTTPReply(400, "Null method", output, rpcJ);
712 return;
713 }
714 Json::Value r = jsonRPC;
715 r[jss::error] = make_json_error(method_not_found, "Null method");
716 reply.append(r);
717 continue;
718 }
719
720 Json::Value const& method = jsonRPC[jss::method];
721 if (!method.isString())
722 {
723 usage.charge(Resource::feeMalformedRPC);
724 if (!batch)
725 {
726 HTTPReply(400, "method is not string", output, rpcJ);
727 return;
728 }
729 Json::Value r = jsonRPC;
730 r[jss::error] = make_json_error(method_not_found, "method is not string");
731 reply.append(r);
732 continue;
733 }
734
735 std::string const strMethod = method.asString();
736 if (strMethod.empty())
737 {
738 usage.charge(Resource::feeMalformedRPC);
739 if (!batch)
740 {
741 HTTPReply(400, "method is empty", output, rpcJ);
742 return;
743 }
744 Json::Value r = jsonRPC;
745 r[jss::error] = make_json_error(method_not_found, "method is empty");
746 reply.append(r);
747 continue;
748 }
749
750 // Extract request parameters from the request Json as `params`.
751 //
752 // If the field "params" is empty, `params` is an empty object.
753 //
754 // Otherwise, that field must be an array of length 1 (why?)
755 // and we take that first entry and validate that it's an object.
756 Json::Value params;
757 if (!batch)
758 {
759 params = jsonRPC[jss::params];
760 if (!params)
761 {
763 }
764 else if (!params.isArray() || params.size() != 1)
765 {
766 usage.charge(Resource::feeMalformedRPC);
767 HTTPReply(400, "params unparsable", output, rpcJ);
768 return;
769 }
770 else
771 {
772 params = std::move(params[0u]);
773 if (!params.isObjectOrNull())
774 {
775 usage.charge(Resource::feeMalformedRPC);
776 HTTPReply(400, "params unparsable", output, rpcJ);
777 return;
778 }
779 }
780 }
781 else // batch
782 {
783 params = jsonRPC;
784 }
785
786 std::string ripplerpc = "1.0";
787 if (params.isMember(jss::ripplerpc))
788 {
789 if (!params[jss::ripplerpc].isString())
790 {
791 usage.charge(Resource::feeMalformedRPC);
792 if (!batch)
793 {
794 HTTPReply(400, "ripplerpc is not a string", output, rpcJ);
795 return;
796 }
797
798 Json::Value r = jsonRPC;
799 r[jss::error] = make_json_error(method_not_found, "ripplerpc is not a string");
800 reply.append(r);
801 continue;
802 }
803 ripplerpc = params[jss::ripplerpc].asString();
804 }
805
810 if (role != Role::IDENTIFIED && role != Role::PROXY)
811 {
813 user.remove_suffix(user.size());
814 }
815
816 JLOG(m_journal.debug()) << "Query: " << strMethod << params;
817
818 // Provide the JSON-RPC method as the field "command" in the request.
819 params[jss::command] = strMethod;
820 JLOG(m_journal.trace()) << "doRpcCommand:" << strMethod << ":" << params;
821
822 Resource::Charge loadType = Resource::feeReferenceRPC;
823
824 RPC::JsonContext context{
825 {m_journal,
826 app_,
827 loadType,
828 m_networkOPs,
829 app_.getLedgerMaster(),
830 usage,
831 role,
832 coro,
834 apiVersion},
835 params,
836 {user, forwardedFor}};
837 Json::Value result;
838
839 auto start = std::chrono::system_clock::now();
840
841 try
842 {
843 RPC::doCommand(context, result);
844 }
845 catch (std::exception const& ex)
846 {
847 // LCOV_EXCL_START
848 result = RPC::make_error(rpcINTERNAL);
849 JLOG(m_journal.error())
850 << "Internal error : " << ex.what()
851 << " when processing request: " << Json::Compact{Json::Value{params}};
852 // LCOV_EXCL_STOP
853 }
854
856
857 logDuration(params, end - start, m_journal);
858
859 usage.charge(loadType);
860 if (usage.warn())
861 result[jss::warning] = jss::load;
862
864 if (ripplerpc >= "2.0")
865 {
866 if (result.isMember(jss::error))
867 {
868 result[jss::status] = jss::error;
869 result["code"] = result[jss::error_code];
870 result["message"] = result[jss::error_message];
871 result.removeMember(jss::error_message);
872 JLOG(m_journal.debug())
873 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
874 r[jss::error] = std::move(result);
875 }
876 else
877 {
878 result[jss::status] = jss::success;
879 r[jss::result] = std::move(result);
880 }
881 }
882 else
883 {
884 // Always report "status". On an error report the request as
885 // received.
886 if (result.isMember(jss::error))
887 {
888 auto rq = params;
889
890 if (rq.isObject())
891 { // But mask potentially sensitive information.
892 if (rq.isMember(jss::passphrase.c_str()))
893 rq[jss::passphrase.c_str()] = "<masked>";
894 if (rq.isMember(jss::secret.c_str()))
895 rq[jss::secret.c_str()] = "<masked>";
896 if (rq.isMember(jss::seed.c_str()))
897 rq[jss::seed.c_str()] = "<masked>";
898 if (rq.isMember(jss::seed_hex.c_str()))
899 rq[jss::seed_hex.c_str()] = "<masked>";
900 }
901
902 result[jss::status] = jss::error;
903 result[jss::request] = rq;
904
905 JLOG(m_journal.debug())
906 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
907 }
908 else
909 {
910 result[jss::status] = jss::success;
911 }
912 r[jss::result] = std::move(result);
913 }
914
915 if (params.isMember(jss::jsonrpc))
916 r[jss::jsonrpc] = params[jss::jsonrpc];
917 if (params.isMember(jss::ripplerpc))
918 r[jss::ripplerpc] = params[jss::ripplerpc];
919 if (params.isMember(jss::id))
920 r[jss::id] = params[jss::id];
921 if (batch)
922 {
923 reply.append(std::move(r));
924 }
925 else
926 {
927 reply = std::move(r);
928 }
929
930 if (reply.isMember(jss::result) && reply[jss::result].isMember(jss::result))
931 {
932 reply = reply[jss::result];
933 if (reply.isMember(jss::status))
934 {
935 reply[jss::result][jss::status] = reply[jss::status];
936 reply.removeMember(jss::status);
937 }
938 }
939 }
940
941 // If we're returning an error_code, use that to determine the HTTP status.
942 int const httpStatus = [&reply]() {
943 // This feature is enabled with ripplerpc version 3.0 and above.
944 // Before ripplerpc version 3.0 always return 200.
945 if (reply.isMember(jss::ripplerpc) && reply[jss::ripplerpc].isString() &&
946 reply[jss::ripplerpc].asString() >= "3.0")
947 {
948 // If there's an error_code, use that to determine the HTTP Status.
949 if (reply.isMember(jss::error) && reply[jss::error].isMember(jss::error_code) &&
950 reply[jss::error][jss::error_code].isInt())
951 {
952 int const errCode = reply[jss::error][jss::error_code].asInt();
953 return RPC::error_code_http_status(static_cast<error_code_i>(errCode));
954 }
955 }
956 // Return OK.
957 return 200;
958 }();
959
960 auto response = to_string(reply);
961
962 rpc_time_.notify(
963 std::chrono::duration_cast<std::chrono::milliseconds>(
965 ++rpc_requests_;
966 rpc_size_.notify(beast::insight::Event::value_type{response.size()});
967
968 response += '\n';
969
970 if (auto stream = m_journal.debug())
971 {
972 static int const maxSize = 10000;
973 if (response.size() <= maxSize)
974 {
975 stream << "Reply: " << response;
976 }
977 else
978 {
979 stream << "Reply: " << response.substr(0, maxSize);
980 }
981 }
982
983 HTTPReply(httpStatus, response, output, rpcJ);
984}
985
986//------------------------------------------------------------------------------
987
988/* This response is used with load balancing.
989 If the server is overloaded, status 500 is reported. Otherwise status 200
990 is reported, meaning the server can accept more connections.
991*/
993ServerHandler::statusResponse(http_request_type const& request) const
994{
995 using namespace boost::beast::http;
996 Handoff handoff;
997 response<string_body> msg;
998 std::string reason;
999 if (app_.serverOkay(reason))
1000 {
1001 msg.result(boost::beast::http::status::ok);
1002 msg.body() = "<!DOCTYPE html><html><head><title>Test page for " + systemName() +
1003 "</title></head><body><h1>Test</h1><p>This page shows " + systemName() +
1004 " http(s) connectivity is working.</p></body></html>";
1005 }
1006 else
1007 {
1008 msg.result(boost::beast::http::status::internal_server_error);
1009 msg.body() = "<HTML><BODY>Server cannot accept clients: " + reason + "</BODY></HTML>";
1010 }
1011 msg.version(request.version());
1012 msg.insert("Server", BuildInfo::getFullVersionString());
1013 msg.insert("Content-Type", "text/html");
1014 msg.insert("Connection", "close");
1015 msg.prepare_payload();
1017 return handoff;
1018}
1019
1020//------------------------------------------------------------------------------
1021
1022void
1023ServerHandler::Setup::makeContexts()
1024{
1025 for (auto& p : ports)
1026 {
1027 if (p.secure())
1028 {
1029 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1030 {
1031 p.context = make_SSLContext(p.ssl_ciphers);
1032 }
1033 else
1034 {
1035 p.context =
1036 make_SSLContextAuthed(p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1037 }
1038 }
1039 else
1040 {
1041 p.context =
1042 std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
1043 }
1044 }
1045}
1046
1047static Port
1048to_Port(ParsedPort const& parsed, std::ostream& log)
1049{
1050 Port p;
1051 p.name = parsed.name;
1052
1053 if (!parsed.ip)
1054 {
1055 log << "Missing 'ip' in [" << p.name << "]";
1056 Throw<std::exception>();
1057 }
1058 p.ip = *parsed.ip;
1059
1060 if (!parsed.port)
1061 {
1062 log << "Missing 'port' in [" << p.name << "]";
1063 Throw<std::exception>();
1064 }
1065 p.port = *parsed.port;
1066
1067 if (parsed.protocol.empty())
1068 {
1069 log << "Missing 'protocol' in [" << p.name << "]";
1070 Throw<std::exception>();
1071 }
1072 p.protocol = parsed.protocol;
1073
1074 p.user = parsed.user;
1075 p.password = parsed.password;
1076 p.admin_user = parsed.admin_user;
1077 p.admin_password = parsed.admin_password;
1078 p.ssl_key = parsed.ssl_key;
1079 p.ssl_cert = parsed.ssl_cert;
1080 p.ssl_chain = parsed.ssl_chain;
1081 p.ssl_ciphers = parsed.ssl_ciphers;
1082 p.pmd_options = parsed.pmd_options;
1083 p.ws_queue_limit = parsed.ws_queue_limit;
1084 p.limit = parsed.limit;
1085 p.admin_nets_v4 = parsed.admin_nets_v4;
1086 p.admin_nets_v6 = parsed.admin_nets_v6;
1089
1090 return p;
1091}
1092
1093static std::vector<Port>
1094parse_Ports(Config const& config, std::ostream& log)
1095{
1096 std::vector<Port> result;
1097
1098 if (!config.exists("server"))
1099 {
1100 log << "Required section [server] is missing";
1101 Throw<std::exception>();
1102 }
1103
1104 ParsedPort common;
1105 parse_Port(common, config["server"], log);
1106
1107 auto const& names = config.section("server").values();
1108 result.reserve(names.size());
1109 for (auto const& name : names)
1110 {
1111 if (!config.exists(name))
1112 {
1113 log << "Missing section: [" << name << "]";
1114 Throw<std::exception>();
1115 }
1116
1117 // grpc ports are parsed by GRPCServer class. Do not validate
1118 // grpc port information in this file.
1119 if (name == SECTION_PORT_GRPC)
1120 continue;
1121
1122 ParsedPort parsed = common;
1123 parse_Port(parsed, config[name], log);
1124 result.push_back(to_Port(parsed, log));
1125 }
1126
1127 if (config.standalone())
1128 {
1129 auto it = result.begin();
1130
1131 while (it != result.end())
1132 {
1133 auto& p = it->protocol;
1134
1135 // Remove the peer protocol, and if that would
1136 // leave the port empty, remove the port as well
1137 if ((p.erase("peer") != 0u) && p.empty())
1138 {
1139 it = result.erase(it);
1140 }
1141 else
1142 {
1143 ++it;
1144 }
1145 }
1146 }
1147 else
1148 {
1149 auto const count = std::count_if(result.cbegin(), result.cend(), [](Port const& p) {
1150 return p.protocol.count("peer") != 0;
1151 });
1152
1153 if (count > 1)
1154 {
1155 log << "Error: More than one peer protocol configured in [server]";
1156 Throw<std::exception>();
1157 }
1158
1159 if (count == 0)
1160 log << "Warning: No peer protocol configured";
1161 }
1162
1163 return result;
1164}
1165
1166// Fill out the client portion of the Setup
1167static void
1169{
1170 decltype(setup.ports)::const_iterator iter;
1171 for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter)
1172 {
1173 if (iter->protocol.count("http") > 0 || iter->protocol.count("https") > 0)
1174 break;
1175 }
1176 if (iter == setup.ports.cend())
1177 return;
1178 setup.client.secure = iter->protocol.count("https") > 0;
1179 if (beast::IP::is_unspecified(iter->ip))
1180 {
1181 // VFALCO HACK! to make localhost work
1182 setup.client.ip = iter->ip.is_v6() ? "::1" : "127.0.0.1";
1183 }
1184 else
1185 {
1186 setup.client.ip = iter->ip.to_string();
1187 }
1188 setup.client.port = iter->port;
1189 setup.client.user = iter->user;
1190 setup.client.password = iter->password;
1191 setup.client.admin_user = iter->admin_user;
1192 setup.client.admin_password = iter->admin_password;
1193}
1194
1195// Fill out the overlay portion of the Setup
1196static void
1198{
1199 auto const iter = std::find_if(setup.ports.cbegin(), setup.ports.cend(), [](Port const& port) {
1200 return port.protocol.count("peer") != 0;
1201 });
1202 if (iter == setup.ports.cend())
1203 {
1204 setup.overlay = {};
1205 return;
1206 }
1207 setup.overlay = {iter->ip, iter->port};
1208}
1209
1210ServerHandler::Setup
1212{
1214 setup.ports = parse_Ports(config, log);
1215
1216 setup_Client(setup);
1217 setup_Overlay(setup);
1218
1219 return setup;
1220}
1221
1224 Application& app,
1225 boost::asio::io_context& io_context,
1226 JobQueue& jobQueue,
1227 NetworkOPs& networkOPs,
1228 Resource::Manager& resourceManager,
1229 CollectorManager& cm)
1230{
1233 app,
1234 io_context,
1235 jobQueue,
1236 networkOPs,
1237 resourceManager,
1238 cm);
1239}
1240
1241} // namespace xrpl
T append(T... args)
T begin(T... args)
Decorator for streaming out compact json.
Unserialize a JSON document into a Value.
Definition json_reader.h:17
std::string getFormattedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
bool isArray() const
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
bool isObjectOrNull() const
Int asInt() const
bool isString() const
bool isObject() const
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
bool isNull() const
isNull() tests to see if this field is null.
bool isMember(char const *key) const
Return true if the object has a member named key.
bool isInt() const
A version-independent IP address and port combination.
Definition IPEndpoint.h:18
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
virtual Config & config()=0
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
Section & section(std::string const &name)
Returns the section with the given name.
Provides the beast::insight::Collector service.
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
bool BETA_RPC_API
Definition Config.h:272
bool standalone() const
Definition Config.h:316
A pool of threads to perform work.
Definition JobQueue.h:38
std::shared_ptr< Coro > postCoro(JobType t, std::string const &name, F &&f)
Creates a coroutine and adds a job to the queue which will run it.
Definition JobQueue.h:390
Provides server functionality for clients.
Definition NetworkOPs.h:71
virtual Handoff onHandoff(std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)=0
Conditionally accept an incoming HTTP request.
A consumption charge.
Definition Charge.h:10
An endpoint that consumes resources.
Definition Consumer.h:16
bool warn()
Returns true if the consumer should be warned.
Definition Consumer.cpp:99
bool disconnect(beast::Journal const &j)
Returns true if the consumer should be disconnected.
Definition Consumer.cpp:106
Disposition charge(Charge const &fee, std::string const &context={})
Apply a load charge to the consumer.
Definition Consumer.cpp:88
Tracks load and resource consumption.
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition BasicConfig.h:58
Resource::Manager & m_resourceManager
std::condition_variable condition_
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_context &io_context, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remote_address)
Application & app_
void onClose(Session &session, boost::system::error_code const &)
Setup const & setup() const
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
beast::insight::Event rpc_size_
void onStopped(Server &)
Handoff statusResponse(http_request_type const &request) const
void processRequest(Port const &port, std::string const &request, beast::IP::Endpoint const &remoteIPAddress, Output const &, std::shared_ptr< JobQueue::Coro > coro, std::string_view forwardedFor, std::string_view user)
std::map< std::reference_wrapper< Port const >, int > count_
void onRequest(Session &session)
beast::insight::Event rpc_time_
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
NetworkOPs & m_networkOPs
beast::Journal m_journal
std::unique_ptr< Server > m_server
beast::insight::Counter rpc_requests_
Json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, Json::Value const &jv)
A multi-protocol server.
Definition ServerImpl.h:29
virtual beast::Journal getJournal(std::string const &name)=0
virtual NetworkOPs & getOPs()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual Overlay & getOverlay()=0
Persistent state information for a connection session.
Definition Session.h:23
virtual Port const & port()=0
Returns the Port settings for this connection.
virtual void close(bool graceful)=0
Close the session.
virtual std::shared_ptr< Session > detach()=0
Detach the session.
virtual http_request_type & request()=0
Returns the current HTTP request.
virtual std::shared_ptr< WSSession > websocketUpgrade()=0
Convert the connection to WebSocket.
void write(std::string const &s)
Send a copy of data asynchronously.
Definition Session.h:56
T count(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T insert(T... args)
T is_same_v
T make_shared(T... args)
void stream(Json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
int Int
unsigned int UInt
Endpoint from_asio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
Definition IPAddress.h:37
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition rfc2616.h:359
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:82
static int constexpr maxRequestSize
static constexpr auto apiInvalidVersion
Definition ApiVersion.h:40
Role roleRequired(unsigned int version, bool betaEnabled, std::string const &method)
Status doCommand(RPC::JsonContext &context, Json::Value &result)
Execute an RPC command and store the results in a Json::Value.
unsigned int getAPIVersionNumber(Json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
Definition ApiVersion.h:99
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Charge const feeReferenceRPC
Charge const feeMalformedRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static std::map< std::string, std::string > build_map(boost::beast::http::fields const &h)
void parse_Port(ParsedPort &port, Section const &section, std::ostream &log)
Definition Port.cpp:194
void HTTPReply(int nStatus, std::string const &strMsg, Json::Output const &, beast::Journal j)
static std::string buffers_to_string(ConstBufferSequence const &bs)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
static void setup_Client(ServerHandler::Setup &setup)
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
std::string base64_decode(std::string_view data)
static std::vector< Port > parse_Ports(Config const &config, std::ostream &log)
Json::Int constexpr wrong_version
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
Role requestRole(Role const &required, Port const &port, Json::Value const &params, beast::IP::Endpoint const &remoteIp, std::string_view user)
Return the allowed privilege role.
Definition Role.cpp:71
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, std::string_view user, std::string_view forwardedFor)
Definition Role.cpp:115
static Json::Value make_json_error(Json::Int code, Json::Value &&message)
std::string_view forwardedFor(http_request_type const &request)
Definition Role.cpp:234
static Port to_Port(ParsedPort const &parsed, std::ostream &log)
std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_context &io_context, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
@ jtCLIENT_RPC
Definition Job.h:29
@ jtCLIENT_WEBSOCKET
Definition Job.h:30
Json::Int constexpr forbidden
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
Json::Int constexpr method_not_found
std::unique_ptr< Server > make_Server(Handler &handler, boost::asio::io_context &io_context, beast::Journal journal)
Create the HTTP server using the specified handler.
Definition Server.h:15
Json::Int constexpr server_overloaded
void logDuration(Json::Value const &request, T const &duration, beast::Journal &journal)
Overlay::Setup setup_Overlay(BasicConfig const &config)
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:98
std::shared_ptr< boost::asio::ssl::context > make_SSLContextAuthed(std::string const &keyFile, std::string const &certFile, std::string const &chainFile, std::string const &cipherList)
Create an authenticated SSL context using the specified files.
ServerHandler::Setup setup_ServerHandler(Config const &config, std::ostream &log)
static Json::Output makeOutput(Session &session)
static bool isStatusRequest(http_request_type const &request)
error_code_i
Definition ErrorCodes.h:20
@ rpcSLOW_DOWN
Definition ErrorCodes.h:37
@ rpcINTERNAL
Definition ErrorCodes.h:110
@ rpcFORBIDDEN
Definition ErrorCodes.h:28
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
T push_back(T... args)
T remove_suffix(T... args)
T reserve(T... args)
T size(T... args)
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
Used to indicate the result of a server connection handoff.
Definition Handoff.h:18
std::shared_ptr< Writer > response
Definition Handoff.h:27
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:82
boost::beast::websocket::permessage_deflate pmd_options
Definition Port.h:91
std::optional< boost::asio::ip::address > ip
Definition Port.h:95
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition Port.h:100
std::string admin_password
Definition Port.h:86
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition Port.h:99
std::string ssl_chain
Definition Port.h:89
std::string ssl_key
Definition Port.h:87
std::string name
Definition Port.h:81
std::uint16_t ws_queue_limit
Definition Port.h:93
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition Port.h:97
std::string user
Definition Port.h:83
std::optional< std::uint16_t > port
Definition Port.h:96
std::string ssl_ciphers
Definition Port.h:90
std::string password
Definition Port.h:84
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition Port.h:98
std::string admin_user
Definition Port.h:85
std::string ssl_cert
Definition Port.h:88
Configuration information for a Server listening port.
Definition Port.h:30
int limit
Definition Port.h:54
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition Port.h:37
std::string admin_password
Definition Port.h:44
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition Port.h:40
std::string ssl_key
Definition Port.h:45
std::string password
Definition Port.h:42
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:36
std::string ssl_ciphers
Definition Port.h:48
std::string ssl_cert
Definition Port.h:46
std::string ssl_chain
Definition Port.h:47
boost::beast::websocket::permessage_deflate pmd_options
Definition Port.h:49
std::string admin_user
Definition Port.h:43
std::uint16_t ws_queue_limit
Definition Port.h:57
boost::asio::ip::address ip
Definition Port.h:34
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition Port.h:39
std::string name
Definition Port.h:33
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition Port.h:38
std::string user
Definition Port.h:41
std::uint16_t port
Definition Port.h:35
boost::asio::ip::tcp::endpoint overlay
std::vector< Port > ports
T substr(T... args)
T transform(T... args)
T what(T... args)