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