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 jr[jss::result] = RPC::make_error(rpcINTERNAL);
511 JLOG(m_journal.error())
512 << "Exception while processing WS: " << ex.what() << "\n"
513 << "Input JSON: " << Json::Compact{Json::Value{jv}};
514 }
515
516 is->getConsumer().charge(loadType);
517 if (is->getConsumer().warn())
518 jr[jss::warning] = jss::load;
519
520 // Currently we will simply unwrap errors returned by the RPC
521 // API, in the future maybe we can make the responses
522 // consistent.
523 //
524 // Regularize result. This is duplicate code.
525 if (jr[jss::result].isMember(jss::error))
526 {
527 jr = jr[jss::result];
528 jr[jss::status] = jss::error;
529
530 auto rq = jv;
531
532 if (rq.isObject())
533 {
534 if (rq.isMember(jss::passphrase.c_str()))
535 rq[jss::passphrase.c_str()] = "<masked>";
536 if (rq.isMember(jss::secret.c_str()))
537 rq[jss::secret.c_str()] = "<masked>";
538 if (rq.isMember(jss::seed.c_str()))
539 rq[jss::seed.c_str()] = "<masked>";
540 if (rq.isMember(jss::seed_hex.c_str()))
541 rq[jss::seed_hex.c_str()] = "<masked>";
542 }
543
544 jr[jss::request] = rq;
545 }
546 else
547 {
548 if (jr[jss::result].isMember("forwarded") &&
549 jr[jss::result]["forwarded"])
550 jr = jr[jss::result];
551 jr[jss::status] = jss::success;
552 }
553
554 if (jv.isMember(jss::id))
555 jr[jss::id] = jv[jss::id];
556 if (jv.isMember(jss::jsonrpc))
557 jr[jss::jsonrpc] = jv[jss::jsonrpc];
558 if (jv.isMember(jss::ripplerpc))
559 jr[jss::ripplerpc] = jv[jss::ripplerpc];
560 if (jv.isMember(jss::api_version))
561 jr[jss::api_version] = jv[jss::api_version];
562
563 jr[jss::type] = jss::response;
564 return jr;
565}
566
567// Run as a coroutine.
568void
570 std::shared_ptr<Session> const& session,
572{
574 session->port(),
575 buffers_to_string(session->request().body().data()),
576 session->remoteAddress().at_port(0),
577 makeOutput(*session),
578 coro,
579 forwardedFor(session->request()),
580 [&] {
581 auto const iter = session->request().find("X-User");
582 if (iter != session->request().end())
583 return iter->value();
584 return boost::beast::string_view{};
585 }());
586
587 if (beast::rfc2616::is_keep_alive(session->request()))
588 session->complete();
589 else
590 session->close(true);
591}
592
593static Json::Value
595{
597 sub["code"] = code;
598 sub["message"] = std::move(message);
600 r["error"] = sub;
601 return r;
602}
603
604Json::Int constexpr method_not_found = -32601;
605Json::Int constexpr server_overloaded = -32604;
606Json::Int constexpr forbidden = -32605;
607Json::Int constexpr wrong_version = -32606;
608
609void
610ServerHandler::processRequest(
611 Port const& port,
612 std::string const& request,
613 beast::IP::Endpoint const& remoteIPAddress,
614 Output&& output,
616 std::string_view forwardedFor,
617 std::string_view user)
618{
619 auto rpcJ = app_.journal("RPC");
620
621 Json::Value jsonOrig;
622 {
623 Json::Reader reader;
624 if ((request.size() > RPC::Tuning::maxRequestSize) ||
625 !reader.parse(request, jsonOrig) || !jsonOrig ||
626 !jsonOrig.isObject())
627 {
628 HTTPReply(
629 400,
630 "Unable to parse request: " + reader.getFormatedErrorMessages(),
631 output,
632 rpcJ);
633 return;
634 }
635 }
636
637 bool batch = false;
638 unsigned size = 1;
639 if (jsonOrig.isMember(jss::method) && jsonOrig[jss::method] == "batch")
640 {
641 batch = true;
642 if (!jsonOrig.isMember(jss::params) || !jsonOrig[jss::params].isArray())
643 {
644 HTTPReply(400, "Malformed batch request", output, rpcJ);
645 return;
646 }
647 size = jsonOrig[jss::params].size();
648 }
649
651 auto const start(std::chrono::high_resolution_clock::now());
652 for (unsigned i = 0; i < size; ++i)
653 {
654 Json::Value const& jsonRPC =
655 batch ? jsonOrig[jss::params][i] : jsonOrig;
656
657 if (!jsonRPC.isObject())
658 {
660 r[jss::request] = jsonRPC;
661 r[jss::error] =
662 make_json_error(method_not_found, "Method not found");
663 reply.append(r);
664 continue;
665 }
666
667 unsigned apiVersion = RPC::apiVersionIfUnspecified;
668 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
669 jsonRPC[jss::params].size() > 0 &&
670 jsonRPC[jss::params][0u].isObject())
671 {
672 apiVersion = RPC::getAPIVersionNumber(
673 jsonRPC[jss::params][Json::UInt(0)],
674 app_.config().BETA_RPC_API);
675 }
676
677 if (apiVersion == RPC::apiVersionIfUnspecified && batch)
678 {
679 // for batch request, api_version may be at a different level
680 apiVersion =
681 RPC::getAPIVersionNumber(jsonRPC, app_.config().BETA_RPC_API);
682 }
683
684 if (apiVersion == RPC::apiInvalidVersion)
685 {
686 if (!batch)
687 {
688 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
689 return;
690 }
692 r[jss::request] = jsonRPC;
693 r[jss::error] = make_json_error(
694 wrong_version, jss::invalid_API_version.c_str());
695 reply.append(r);
696 continue;
697 }
698
699 /* ------------------------------------------------------------------ */
700 auto role = Role::FORBID;
701 auto required = Role::FORBID;
702 if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
703 required = RPC::roleRequired(
704 apiVersion,
705 app_.config().BETA_RPC_API,
706 jsonRPC[jss::method].asString());
707
708 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
709 jsonRPC[jss::params].size() > 0 &&
710 jsonRPC[jss::params][Json::UInt(0)].isObjectOrNull())
711 {
712 role = requestRole(
713 required,
714 port,
715 jsonRPC[jss::params][Json::UInt(0)],
716 remoteIPAddress,
717 user);
718 }
719 else
720 {
721 role = requestRole(
722 required, port, Json::objectValue, remoteIPAddress, user);
723 }
724
725 Resource::Consumer usage;
726 if (isUnlimited(role))
727 {
728 usage = m_resourceManager.newUnlimitedEndpoint(remoteIPAddress);
729 }
730 else
731 {
732 usage = m_resourceManager.newInboundEndpoint(
733 remoteIPAddress, role == Role::PROXY, forwardedFor);
734 if (usage.disconnect(m_journal))
735 {
736 if (!batch)
737 {
738 HTTPReply(503, "Server is overloaded", output, rpcJ);
739 return;
740 }
741 Json::Value r = jsonRPC;
742 r[jss::error] =
743 make_json_error(server_overloaded, "Server is overloaded");
744 reply.append(r);
745 continue;
746 }
747 }
748
749 if (role == Role::FORBID)
750 {
751 usage.charge(Resource::feeMalformedRPC);
752 if (!batch)
753 {
754 HTTPReply(403, "Forbidden", output, rpcJ);
755 return;
756 }
757 Json::Value r = jsonRPC;
758 r[jss::error] = make_json_error(forbidden, "Forbidden");
759 reply.append(r);
760 continue;
761 }
762
763 if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull())
764 {
765 usage.charge(Resource::feeMalformedRPC);
766 if (!batch)
767 {
768 HTTPReply(400, "Null method", output, rpcJ);
769 return;
770 }
771 Json::Value r = jsonRPC;
772 r[jss::error] = make_json_error(method_not_found, "Null method");
773 reply.append(r);
774 continue;
775 }
776
777 Json::Value const& method = jsonRPC[jss::method];
778 if (!method.isString())
779 {
780 usage.charge(Resource::feeMalformedRPC);
781 if (!batch)
782 {
783 HTTPReply(400, "method is not string", output, rpcJ);
784 return;
785 }
786 Json::Value r = jsonRPC;
787 r[jss::error] =
788 make_json_error(method_not_found, "method is not string");
789 reply.append(r);
790 continue;
791 }
792
793 std::string strMethod = method.asString();
794 if (strMethod.empty())
795 {
796 usage.charge(Resource::feeMalformedRPC);
797 if (!batch)
798 {
799 HTTPReply(400, "method is empty", output, rpcJ);
800 return;
801 }
802 Json::Value r = jsonRPC;
803 r[jss::error] =
804 make_json_error(method_not_found, "method is empty");
805 reply.append(r);
806 continue;
807 }
808
809 // Extract request parameters from the request Json as `params`.
810 //
811 // If the field "params" is empty, `params` is an empty object.
812 //
813 // Otherwise, that field must be an array of length 1 (why?)
814 // and we take that first entry and validate that it's an object.
815 Json::Value params;
816 if (!batch)
817 {
818 params = jsonRPC[jss::params];
819 if (!params)
821
822 else if (!params.isArray() || params.size() != 1)
823 {
824 usage.charge(Resource::feeMalformedRPC);
825 HTTPReply(400, "params unparseable", output, rpcJ);
826 return;
827 }
828 else
829 {
830 params = std::move(params[0u]);
831 if (!params.isObjectOrNull())
832 {
833 usage.charge(Resource::feeMalformedRPC);
834 HTTPReply(400, "params unparseable", output, rpcJ);
835 return;
836 }
837 }
838 }
839 else // batch
840 {
841 params = jsonRPC;
842 }
843
844 std::string ripplerpc = "1.0";
845 if (params.isMember(jss::ripplerpc))
846 {
847 if (!params[jss::ripplerpc].isString())
848 {
849 usage.charge(Resource::feeMalformedRPC);
850 if (!batch)
851 {
852 HTTPReply(400, "ripplerpc is not a string", output, rpcJ);
853 return;
854 }
855
856 Json::Value r = jsonRPC;
857 r[jss::error] = make_json_error(
858 method_not_found, "ripplerpc is not a string");
859 reply.append(r);
860 continue;
861 }
862 ripplerpc = params[jss::ripplerpc].asString();
863 }
864
869 if (role != Role::IDENTIFIED && role != Role::PROXY)
870 {
872 user.remove_suffix(user.size());
873 }
874
875 JLOG(m_journal.debug()) << "Query: " << strMethod << params;
876
877 // Provide the JSON-RPC method as the field "command" in the request.
878 params[jss::command] = strMethod;
879 JLOG(m_journal.trace())
880 << "doRpcCommand:" << strMethod << ":" << params;
881
882 Resource::Charge loadType = Resource::feeReferenceRPC;
883
884 RPC::JsonContext context{
885 {m_journal,
886 app_,
887 loadType,
888 m_networkOPs,
889 app_.getLedgerMaster(),
890 usage,
891 role,
892 coro,
894 apiVersion},
895 params,
896 {user, forwardedFor}};
897 Json::Value result;
898
899 auto start = std::chrono::system_clock::now();
900
901 try
902 {
903 RPC::doCommand(context, result);
904 }
905 catch (std::exception const& ex)
906 {
907 result = RPC::make_error(rpcINTERNAL);
908 JLOG(m_journal.error()) << "Internal error : " << ex.what()
909 << " when processing request: "
910 << Json::Compact{Json::Value{params}};
911 }
912
914
915 logDuration(params, end - start, m_journal);
916
917 usage.charge(loadType);
918 if (usage.warn())
919 result[jss::warning] = jss::load;
920
922 if (ripplerpc >= "2.0")
923 {
924 if (result.isMember(jss::error))
925 {
926 result[jss::status] = jss::error;
927 result["code"] = result[jss::error_code];
928 result["message"] = result[jss::error_message];
929 result.removeMember(jss::error_message);
930 JLOG(m_journal.debug()) << "rpcError: " << result[jss::error]
931 << ": " << result[jss::error_message];
932 r[jss::error] = std::move(result);
933 }
934 else
935 {
936 result[jss::status] = jss::success;
937 r[jss::result] = std::move(result);
938 }
939 }
940 else
941 {
942 // Always report "status". On an error report the request as
943 // received.
944 if (result.isMember(jss::error))
945 {
946 auto rq = params;
947
948 if (rq.isObject())
949 { // But mask potentially sensitive information.
950 if (rq.isMember(jss::passphrase.c_str()))
951 rq[jss::passphrase.c_str()] = "<masked>";
952 if (rq.isMember(jss::secret.c_str()))
953 rq[jss::secret.c_str()] = "<masked>";
954 if (rq.isMember(jss::seed.c_str()))
955 rq[jss::seed.c_str()] = "<masked>";
956 if (rq.isMember(jss::seed_hex.c_str()))
957 rq[jss::seed_hex.c_str()] = "<masked>";
958 }
959
960 result[jss::status] = jss::error;
961 result[jss::request] = rq;
962
963 JLOG(m_journal.debug()) << "rpcError: " << result[jss::error]
964 << ": " << result[jss::error_message];
965 }
966 else
967 {
968 result[jss::status] = jss::success;
969 }
970 r[jss::result] = std::move(result);
971 }
972
973 if (params.isMember(jss::jsonrpc))
974 r[jss::jsonrpc] = params[jss::jsonrpc];
975 if (params.isMember(jss::ripplerpc))
976 r[jss::ripplerpc] = params[jss::ripplerpc];
977 if (params.isMember(jss::id))
978 r[jss::id] = params[jss::id];
979 if (batch)
980 reply.append(std::move(r));
981 else
982 reply = std::move(r);
983
984 if (reply.isMember(jss::result) &&
985 reply[jss::result].isMember(jss::result))
986 {
987 reply = reply[jss::result];
988 if (reply.isMember(jss::status))
989 {
990 reply[jss::result][jss::status] = reply[jss::status];
991 reply.removeMember(jss::status);
992 }
993 }
994 }
995
996 // If we're returning an error_code, use that to determine the HTTP status.
997 int const httpStatus = [&reply]() {
998 // This feature is enabled with ripplerpc version 3.0 and above.
999 // Before ripplerpc version 3.0 always return 200.
1000 if (reply.isMember(jss::ripplerpc) &&
1001 reply[jss::ripplerpc].isString() &&
1002 reply[jss::ripplerpc].asString() >= "3.0")
1003 {
1004 // If there's an error_code, use that to determine the HTTP Status.
1005 if (reply.isMember(jss::error) &&
1006 reply[jss::error].isMember(jss::error_code) &&
1007 reply[jss::error][jss::error_code].isInt())
1008 {
1009 int const errCode = reply[jss::error][jss::error_code].asInt();
1010 return RPC::error_code_http_status(
1011 static_cast<error_code_i>(errCode));
1012 }
1013 }
1014 // Return OK.
1015 return 200;
1016 }();
1017
1018 auto response = to_string(reply);
1019
1020 rpc_time_.notify(std::chrono::duration_cast<std::chrono::milliseconds>(
1022 ++rpc_requests_;
1023 rpc_size_.notify(beast::insight::Event::value_type{response.size()});
1024
1025 response += '\n';
1026
1027 if (auto stream = m_journal.debug())
1028 {
1029 static int const maxSize = 10000;
1030 if (response.size() <= maxSize)
1031 stream << "Reply: " << response;
1032 else
1033 stream << "Reply: " << response.substr(0, maxSize);
1034 }
1035
1036 HTTPReply(httpStatus, response, output, rpcJ);
1037}
1038
1039//------------------------------------------------------------------------------
1040
1041/* This response is used with load balancing.
1042 If the server is overloaded, status 500 is reported. Otherwise status 200
1043 is reported, meaning the server can accept more connections.
1044*/
1045Handoff
1046ServerHandler::statusResponse(http_request_type const& request) const
1047{
1048 using namespace boost::beast::http;
1049 Handoff handoff;
1050 response<string_body> msg;
1051 std::string reason;
1052 if (app_.serverOkay(reason))
1053 {
1054 msg.result(boost::beast::http::status::ok);
1055 msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() +
1056 " Test page for rippled</title></head><body><h1>" + systemName() +
1057 " Test</h1><p>This page shows rippled http(s) "
1058 "connectivity is working.</p></body></html>";
1059 }
1060 else
1061 {
1062 msg.result(boost::beast::http::status::internal_server_error);
1063 msg.body() = "<HTML><BODY>Server cannot accept clients: " + reason +
1064 "</BODY></HTML>";
1065 }
1066 msg.version(request.version());
1067 msg.insert("Server", BuildInfo::getFullVersionString());
1068 msg.insert("Content-Type", "text/html");
1069 msg.insert("Connection", "close");
1070 msg.prepare_payload();
1072 return handoff;
1073}
1074
1075//------------------------------------------------------------------------------
1076
1077void
1078ServerHandler::Setup::makeContexts()
1079{
1080 for (auto& p : ports)
1081 {
1082 if (p.secure())
1083 {
1084 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1085 p.context = make_SSLContext(p.ssl_ciphers);
1086 else
1087 p.context = make_SSLContextAuthed(
1088 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1089 }
1090 else
1091 {
1093 boost::asio::ssl::context::sslv23);
1094 }
1095 }
1096}
1097
1098static Port
1099to_Port(ParsedPort const& parsed, std::ostream& log)
1100{
1101 Port p;
1102 p.name = parsed.name;
1103
1104 if (!parsed.ip)
1105 {
1106 log << "Missing 'ip' in [" << p.name << "]";
1107 Throw<std::exception>();
1108 }
1109 p.ip = *parsed.ip;
1110
1111 if (!parsed.port)
1112 {
1113 log << "Missing 'port' in [" << p.name << "]";
1114 Throw<std::exception>();
1115 }
1116 p.port = *parsed.port;
1117
1118 if (parsed.protocol.empty())
1119 {
1120 log << "Missing 'protocol' in [" << p.name << "]";
1121 Throw<std::exception>();
1122 }
1123 p.protocol = parsed.protocol;
1124
1125 p.user = parsed.user;
1126 p.password = parsed.password;
1127 p.admin_user = parsed.admin_user;
1128 p.admin_password = parsed.admin_password;
1129 p.ssl_key = parsed.ssl_key;
1130 p.ssl_cert = parsed.ssl_cert;
1131 p.ssl_chain = parsed.ssl_chain;
1132 p.ssl_ciphers = parsed.ssl_ciphers;
1133 p.pmd_options = parsed.pmd_options;
1134 p.ws_queue_limit = parsed.ws_queue_limit;
1135 p.limit = parsed.limit;
1136 p.admin_nets_v4 = parsed.admin_nets_v4;
1137 p.admin_nets_v6 = parsed.admin_nets_v6;
1140
1141 return p;
1142}
1143
1144static std::vector<Port>
1145parse_Ports(Config const& config, std::ostream& log)
1146{
1147 std::vector<Port> result;
1148
1149 if (!config.exists("server"))
1150 {
1151 log << "Required section [server] is missing";
1152 Throw<std::exception>();
1153 }
1154
1155 ParsedPort common;
1156 parse_Port(common, config["server"], log);
1157
1158 auto const& names = config.section("server").values();
1159 result.reserve(names.size());
1160 for (auto const& name : names)
1161 {
1162 if (!config.exists(name))
1163 {
1164 log << "Missing section: [" << name << "]";
1165 Throw<std::exception>();
1166 }
1167
1168 // grpc ports are parsed by GRPCServer class. Do not validate
1169 // grpc port information in this file.
1170 if (name == SECTION_PORT_GRPC)
1171 continue;
1172
1173 ParsedPort parsed = common;
1174 parse_Port(parsed, config[name], log);
1175 result.push_back(to_Port(parsed, log));
1176 }
1177
1178 if (config.standalone())
1179 {
1180 auto it = result.begin();
1181
1182 while (it != result.end())
1183 {
1184 auto& p = it->protocol;
1185
1186 // Remove the peer protocol, and if that would
1187 // leave the port empty, remove the port as well
1188 if (p.erase("peer") && p.empty())
1189 it = result.erase(it);
1190 else
1191 ++it;
1192 }
1193 }
1194 else
1195 {
1196 auto const count =
1197 std::count_if(result.cbegin(), result.cend(), [](Port const& p) {
1198 return p.protocol.count("peer") != 0;
1199 });
1200
1201 if (count > 1)
1202 {
1203 log << "Error: More than one peer protocol configured in [server]";
1204 Throw<std::exception>();
1205 }
1206
1207 if (count == 0)
1208 log << "Warning: No peer protocol configured";
1209 }
1210
1211 return result;
1212}
1213
1214// Fill out the client portion of the Setup
1215static void
1217{
1218 decltype(setup.ports)::const_iterator iter;
1219 for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter)
1220 if (iter->protocol.count("http") > 0 ||
1221 iter->protocol.count("https") > 0)
1222 break;
1223 if (iter == setup.ports.cend())
1224 return;
1225 setup.client.secure = iter->protocol.count("https") > 0;
1226 setup.client.ip = beast::IP::is_unspecified(iter->ip)
1227 ?
1228 // VFALCO HACK! to make localhost work
1229 (iter->ip.is_v6() ? "::1" : "127.0.0.1")
1230 : iter->ip.to_string();
1231 setup.client.port = iter->port;
1232 setup.client.user = iter->user;
1233 setup.client.password = iter->password;
1234 setup.client.admin_user = iter->admin_user;
1235 setup.client.admin_password = iter->admin_password;
1236}
1237
1238// Fill out the overlay portion of the Setup
1239static void
1241{
1242 auto const iter = std::find_if(
1243 setup.ports.cbegin(), setup.ports.cend(), [](Port const& port) {
1244 return port.protocol.count("peer") != 0;
1245 });
1246 if (iter == setup.ports.cend())
1247 {
1248 setup.overlay = {};
1249 return;
1250 }
1251 setup.overlay = {iter->ip, iter->port};
1252}
1253
1254ServerHandler::Setup
1256{
1258 setup.ports = parse_Ports(config, log);
1259
1260 setup_Client(setup);
1261 setup_Overlay(setup);
1262
1263 return setup;
1264}
1265
1268 Application& app,
1269 boost::asio::io_context& io_context,
1270 JobQueue& jobQueue,
1271 NetworkOPs& networkOPs,
1272 Resource::Manager& resourceManager,
1273 CollectorManager& cm)
1274{
1277 app,
1278 io_context,
1279 jobQueue,
1280 networkOPs,
1281 resourceManager,
1282 cm);
1283}
1284
1285} // 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)