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