1#include <xrpld/rpc/ServerHandler.h>
3#include <xrpld/app/main/Application.h>
4#include <xrpld/overlay/Overlay.h>
5#include <xrpld/rpc/RPCHandler.h>
6#include <xrpld/rpc/Role.h>
7#include <xrpld/rpc/detail/Tuning.h>
8#include <xrpld/rpc/detail/WSInfoSub.h>
10#include <xrpl/basics/Log.h>
11#include <xrpl/basics/base64.h>
12#include <xrpl/basics/contract.h>
13#include <xrpl/basics/make_SSLContext.h>
14#include <xrpl/beast/net/IPAddress.h>
15#include <xrpl/beast/net/IPAddressConversion.h>
16#include <xrpl/beast/rfc2616.h>
17#include <xrpl/beast/utility/Journal.h>
18#include <xrpl/config/Constants.h>
19#include <xrpl/core/Job.h>
20#include <xrpl/core/JobQueue.h>
21#include <xrpl/json/Output.h>
22#include <xrpl/json/json_forwards.h>
23#include <xrpl/json/json_reader.h>
24#include <xrpl/json/json_value.h>
25#include <xrpl/json/json_writer.h>
26#include <xrpl/json/to_string.h>
27#include <xrpl/protocol/ApiVersion.h>
28#include <xrpl/protocol/BuildInfo.h>
29#include <xrpl/protocol/ErrorCodes.h>
30#include <xrpl/protocol/RPCErr.h>
31#include <xrpl/protocol/SystemParameters.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/resource/Charge.h>
34#include <xrpl/resource/Consumer.h>
35#include <xrpl/resource/Fees.h>
36#include <xrpl/resource/ResourceManager.h>
37#include <xrpl/server/Handoff.h>
38#include <xrpl/server/InfoSub.h>
39#include <xrpl/server/NetworkOPs.h>
40#include <xrpl/server/Port.h>
41#include <xrpl/server/Server.h>
42#include <xrpl/server/Session.h>
43#include <xrpl/server/SimpleWriter.h>
44#include <xrpl/server/WSSession.h>
45#include <xrpl/server/detail/JSONRPCUtil.h>
47#include <boost/algorithm/string/trim.hpp>
48#include <boost/asio/buffer.hpp>
49#include <boost/asio/io_context.hpp>
50#include <boost/asio/ip/tcp.hpp>
51#include <boost/beast/core/multi_buffer.hpp>
52#include <boost/beast/http/fields.hpp>
53#include <boost/beast/http/status.hpp>
54#include <boost/beast/http/string_body.hpp>
55#include <boost/beast/http/verb.hpp>
56#include <boost/beast/websocket/impl/rfc6455.hpp>
57#include <boost/beast/websocket/rfc6455.hpp>
58#include <boost/system/detail/error_code.hpp>
84 return request.version() >= 11 && request.target() ==
"/" && request.body().
size() == 0 &&
85 request.method() == boost::beast::http::verb::get;
91 using namespace boost::beast::http;
93 response<string_body> msg;
94 msg.version(request.version());
97 msg.insert(
"Content-Type",
"text/html");
98 msg.insert(
"Connection",
"close");
99 msg.body() =
"Invalid protocol.";
100 msg.prepare_payload();
112 auto const it = h.
find(
"authorization");
113 if ((it == h.
end()) || (!it->second.starts_with(
"Basic ")))
116 boost::trim(strUserPass64);
118 std::string::size_type
const nColon = strUserPass.
find(
':');
119 if (nColon == std::string::npos)
123 return strUser == port.
user && strPassword == port.
password;
129 boost::asio::io_context& ioContext,
141 auto const& group(cm.
group(
"rpc"));
143 rpcSize_ = group->makeEvent(
"size");
144 rpcTime_ = group->makeEvent(
"time");
159 for (
auto& port :
setup_.ports)
163 auto const endpointPort = it->second.port();
165 port.port = endpointPort;
167 if ((
setup_.client.port == 0u) &&
168 (port.protocol.contains(
"http") || port.protocol.contains(
"https")))
169 setup_.client.port = endpointPort;
171 if ((
setup_.overlay.port() == 0u) && (port.protocol.contains(
"peer")))
172 setup_.overlay.port(endpointPort);
194 auto const& port = session.
port();
196 auto const c = [
this, &port]() {
201 if ((port.limit != 0) && c >= port.limit)
203 JLOG(
journal_.trace()) << port.name <<
" is full; dropping " << endpoint;
215 boost::asio::ip::tcp::endpoint
const& remoteAddress)
217 using namespace boost::beast;
220 p.contains(
"ws") || p.contains(
"ws2") || p.contains(
"wss") || p.contains(
"wss2")};
222 if (websocket::is_upgrade(request))
234 JLOG(
journal_.error()) <<
"Exception upgrading websocket: " << e.
what() <<
"\n";
246 ws->appDefined = std::move(is);
250 handoff.
moved =
true;
254 if (bundle && p.contains(
"peer"))
255 return app_.getOverlay().onHandoff(std::move(bundle), std::move(request), remoteAddress);
267 return [&](boost::beast::string_view
const& b) { session.
write(b.data(), b.size()); };
274 for (
auto const& e : h)
280 key, key.
begin(), [](
auto kc) { return std::tolower(static_cast<unsigned char>(kc)); });
286template <
class ConstBufferSequence>
290 using boost::asio::buffer_size;
296 s.
append(
static_cast<char const*
>(b.data()), buffer_size(b));
320 auto const postResult =
jobQueue_.postCoro(
324 if (postResult ==
nullptr)
328 detachedSession->close(
true);
339 auto const size = boost::asio::buffer_size(buffers);
343 jvResult[jss::type] = jss::error;
344 jvResult[jss::error] =
"jsonInvalid";
346 boost::beast::multi_buffer sb;
347 json::stream(jvResult, [&sb](
auto const p,
auto const n) {
348 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(p, n)));
350 JLOG(
journal_.trace()) <<
"Websocket sending '" << jvResult <<
"'";
356 JLOG(
journal_.trace()) <<
"Websocket received '" << jv <<
"'";
358 auto const postResult =
jobQueue_.postCoro(
364 auto const n = s.length();
365 boost::beast::multi_buffer sb(n);
366 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
370 if (postResult ==
nullptr)
373 session->close({boost::beast::websocket::going_away,
"Shutting Down"});
398 using namespace std::chrono_literals;
399 auto const level = [&]() {
401 return journal.
error();
403 return journal.
warn();
404 return journal.
debug();
407 JLOG(level) <<
"RPC request processing duration = "
409 <<
" microseconds. request = " << request;
419 if (is->getConsumer().disconnect(
journal_))
421 session->close({boost::beast::websocket::policy_error,
"threshold exceeded"});
440 jr[jss::type] = jss::response;
441 jr[jss::status] = jss::error;
443 : jss::missingCommand;
444 jr[jss::request] = jv;
446 jr[jss::id] = jv[jss::id];
448 jr[jss::jsonrpc] = jv[jss::jsonrpc];
450 jr[jss::ripplerpc] = jv[jss::ripplerpc];
452 jr[jss::api_version] = jv[jss::api_version];
460 app_.config().betaRpcApi,
476 {.j =
app_.getJournal(
"RPCHandler"),
478 .loadType = loadType,
479 .netOps =
app_.getOPs(),
480 .ledgerMaster =
app_.getLedgerMaster(),
481 .consumer = is->getConsumer(),
485 .apiVersion = apiVersion},
487 {.user = is->user(), .forwardedFor = is->forwardedFor()}};
499 JLOG(
journal_.error()) <<
"Exception while processing WS: " << ex.
what() <<
"\n"
504 is->getConsumer().charge(loadType);
505 if (is->getConsumer().warn())
506 jr[jss::warning] = jss::load;
513 if (jr[jss::result].isMember(jss::error))
515 jr = jr[jss::result];
516 jr[jss::status] = jss::error;
522 if (rq.isMember(jss::passphrase.cStr()))
523 rq[jss::passphrase.cStr()] =
"<masked>";
524 if (rq.isMember(jss::secret.cStr()))
525 rq[jss::secret.cStr()] =
"<masked>";
526 if (rq.isMember(jss::seed.cStr()))
527 rq[jss::seed.cStr()] =
"<masked>";
528 if (rq.isMember(jss::seed_hex.cStr()))
529 rq[jss::seed_hex.cStr()] =
"<masked>";
532 jr[jss::request] = rq;
536 if (jr[jss::result].isMember(
"forwarded") && jr[jss::result][
"forwarded"])
537 jr = jr[jss::result];
538 jr[jss::status] = jss::success;
542 jr[jss::id] = jv[jss::id];
544 jr[jss::jsonrpc] = jv[jss::jsonrpc];
546 jr[jss::ripplerpc] = jv[jss::ripplerpc];
548 jr[jss::api_version] = jv[jss::api_version];
550 jr[jss::type] = jss::response;
563 session->remoteAddress().atPort(0),
568 auto const iter = session->request().find(
"X-User");
569 if (iter != session->request().end())
570 return iter->value();
571 return boost::beast::string_view{};
580 session->close(
true);
589 sub[
"message"] = std::move(message);
610 auto rpcJ =
app_.getJournal(
"RPC");
629 if (jsonOrig.
isMember(jss::method) && jsonOrig[jss::method] ==
"batch")
632 if (!jsonOrig.
isMember(jss::params) || !jsonOrig[jss::params].
isArray())
634 httpReply(400,
"Malformed batch request", output, rpcJ);
637 size = jsonOrig[jss::params].
size();
642 for (
unsigned i = 0; i < size; ++i)
644 json::Value const& jsonRPC = batch ? jsonOrig[jss::params][i] : jsonOrig;
649 r[jss::request] = jsonRPC;
657 jsonRPC[jss::params].
size() > 0 && jsonRPC[jss::params][0u].
isObject())
673 httpReply(400, jss::invalid_API_version.cStr(), output, rpcJ);
677 r[jss::request] = jsonRPC;
689 apiVersion,
app_.config().betaRpcApi, jsonRPC[jss::method].
asString());
696 required, port, jsonRPC[jss::params][
json::UInt(0)], remoteIPAddress, user);
716 httpReply(503,
"Server is overloaded", output, rpcJ);
731 httpReply(403,
"Forbidden", output, rpcJ);
740 if (!jsonRPC.
isMember(jss::method) || jsonRPC[jss::method].
isNull())
745 httpReply(400,
"Null method", output, rpcJ);
760 httpReply(400,
"method is not string", output, rpcJ);
770 if (strMethod.
empty())
775 httpReply(400,
"method is empty", output, rpcJ);
793 params = jsonRPC[jss::params];
801 httpReply(400,
"params unparsable", output, rpcJ);
806 params = std::move(params[0u]);
810 httpReply(400,
"params unparsable", output, rpcJ);
821 if (params.
isMember(jss::ripplerpc))
823 if (!params[jss::ripplerpc].isString())
828 httpReply(400,
"ripplerpc is not a string", output, rpcJ);
837 ripplerpc = params[jss::ripplerpc].
asString();
850 JLOG(
journal_.debug()) <<
"Query: " << strMethod << params;
853 params[jss::command] = strMethod;
854 JLOG(
journal_.trace()) <<
"doRpcCommand:" << strMethod <<
":" << params;
861 .loadType = loadType,
863 .ledgerMaster =
app_.getLedgerMaster(),
868 .apiVersion = apiVersion},
884 <<
"Internal error : " << ex.
what()
895 result[jss::warning] = jss::load;
898 if (ripplerpc >=
"2.0")
902 result[jss::status] = jss::error;
903 result[
"code"] = result[jss::error_code];
904 result[
"message"] = result[jss::error_message];
907 <<
"rpcError: " << result[jss::error] <<
": " << result[jss::error_message];
908 r[jss::error] = std::move(result);
912 result[jss::status] = jss::success;
913 r[jss::result] = std::move(result);
926 if (rq.isMember(jss::passphrase.cStr()))
927 rq[jss::passphrase.cStr()] =
"<masked>";
928 if (rq.isMember(jss::secret.cStr()))
929 rq[jss::secret.cStr()] =
"<masked>";
930 if (rq.isMember(jss::seed.cStr()))
931 rq[jss::seed.cStr()] =
"<masked>";
932 if (rq.isMember(jss::seed_hex.cStr()))
933 rq[jss::seed_hex.cStr()] =
"<masked>";
936 result[jss::status] = jss::error;
937 result[jss::request] = rq;
940 <<
"rpcError: " << result[jss::error] <<
": " << result[jss::error_message];
944 result[jss::status] = jss::success;
946 r[jss::result] = std::move(result);
950 r[jss::jsonrpc] = params[jss::jsonrpc];
951 if (params.
isMember(jss::ripplerpc))
952 r[jss::ripplerpc] = params[jss::ripplerpc];
954 r[jss::id] = params[jss::id];
957 reply.
append(std::move(r));
961 reply = std::move(r);
964 if (reply.
isMember(jss::result) && reply[jss::result].
isMember(jss::result))
966 reply = reply[jss::result];
969 reply[jss::result][jss::status] = reply[jss::status];
976 int const httpStatus = [&reply]() {
980 reply[jss::ripplerpc].
asString() >=
"3.0")
983 if (reply.
isMember(jss::error) && reply[jss::error].
isMember(jss::error_code) &&
984 reply[jss::error][jss::error_code].
isInt())
986 int const errCode = reply[jss::error][jss::error_code].
asInt();
1004 if (
auto stream =
journal_.debug())
1006 static int const kMaxSize = 10000;
1007 if (response.size() <= kMaxSize)
1009 stream <<
"Reply: " << response;
1013 stream <<
"Reply: " << response.substr(0, kMaxSize);
1017 httpReply(httpStatus, response, output, rpcJ);
1029 using namespace boost::beast::http;
1031 response<string_body> msg;
1033 if (
app_.serverOkay(reason))
1035 msg.result(boost::beast::http::status::ok);
1036 msg.body() =
"<!DOCTYPE html><html><head><title>Test page for " +
systemName() +
1037 "</title></head><body><h1>Test</h1><p>This page shows " +
systemName() +
1038 " http(s) connectivity is working.</p></body></html>";
1042 msg.result(boost::beast::http::status::internal_server_error);
1043 msg.body() =
"<HTML><BODY>Server cannot accept clients: " + reason +
"</BODY></HTML>";
1045 msg.version(request.version());
1047 msg.insert(
"Content-Type",
"text/html");
1048 msg.insert(
"Connection",
"close");
1049 msg.prepare_payload();
1059 for (
auto& p :
ports)
1063 if (p.sslKey.empty() && p.sslCert.empty() && p.sslChain.empty())
1088 log <<
"Missing 'ip' in [" << p.
name <<
"]";
1095 log <<
"Missing 'port' in [" << p.
name <<
"]";
1102 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1133 log <<
"Required section [server] is missing";
1142 for (
auto const& name : names)
1144 if (!config.
exists(name))
1146 log <<
"Missing section: [" << name <<
"]";
1162 auto it = result.
begin();
1164 while (it != result.
end())
1166 auto& p = it->protocol;
1170 if ((p.erase(
"peer") != 0u) && p.empty())
1172 it = result.
erase(it);
1187 log <<
"Error: More than one peer protocol configured in [server]";
1192 log <<
"Warning: No peer protocol configured";
1203 for (iter = setup.
ports.cbegin(); iter != setup.
ports.cend(); ++iter)
1205 if (iter->protocol.contains(
"http") || iter->protocol.contains(
"https"))
1208 if (iter == setup.
ports.cend())
1210 setup.
client.
secure = iter->protocol.contains(
"https");
1214 setup.
client.
ip = iter->ip.is_v6() ?
"::1" :
"127.0.0.1";
1218 setup.
client.
ip = iter->ip.to_string();
1232 setup.
ports, [](
Port const& port) { return port.protocol.contains(
"peer"); });
1233 if (iter == setup.
ports.cend())
1238 setup.
overlay = {iter->ip, iter->port};
1256 boost::asio::io_context& ioContext,
A version-independent IP address and port combination.
A generic endpoint for log messages.
EventImpl::value_type value_type
Decorator for streaming out compact json.
Unserialize a JSON document into a Value.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
std::string getFormattedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
Value removeMember(char const *key)
Remove and return the named member.
bool isNull() const
isNull() tests to see if this field is null.
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string asString() const
Returns the unquoted string value.
bool isObjectOrNull() const
bool isMember(char const *key) const
Return true if the object has a member named key.
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.
Holds transactions which were deferred to the next pass of consensus.
Provides the beast::insight::Collector service.
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
std::shared_ptr< InfoSub > pointer
A pool of threads to perform work.
Provides server functionality for clients.
Represents a peer connection in the overlay.
An endpoint that consumes resources.
bool warn()
Returns true if the consumer should be warned.
bool disconnect(beast::Journal const &j)
Returns true if the consumer should be disconnected.
Disposition charge(Charge const &fee, std::string const &context={})
Apply a load charge to the consumer.
Tracks load and resource consumption.
std::vector< std::string > const & values() const
Returns all the values in the section.
std::condition_variable condition_
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_context &ioContext, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
friend std::unique_ptr< ServerHandler > makeServerHandler(Application &app, boost::asio::io_context &, JobQueue &, NetworkOPs &, Resource::Manager &, CollectorManager &cm)
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remoteAddress)
beast::insight::Event rpcTime_
Resource::Manager & resourceManager_
beast::insight::Event rpcSize_
json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, json::Value const &jv)
void onClose(Session &session, boost::system::error_code const &)
Setup const & setup() const
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
void setup(Setup const &setup, beast::Journal journal)
Handoff statusResponse(http_request_type const &request) const
void processRequest(Port const &port, std::string const &request, beast::IP::Endpoint const &remoteIPAddress, Output const &, std::shared_ptr< JobQueue::Coro > coro, std::string_view forwardedFor, std::string_view user)
std::map< std::reference_wrapper< Port const >, int > count_
void onRequest(Session &session)
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
std::unique_ptr< Server > server_
beast::insight::Counter rpcRequests_
Persistent state information for a connection session.
virtual Port const & port()=0
Returns the Port settings for this connection.
virtual void close(bool graceful)=0
Close the session.
virtual std::shared_ptr< Session > detach()=0
Detach the session.
void write(std::string_view s)
Send a copy of data asynchronously.
virtual http_request_type & request()=0
Returns the current HTTP request.
virtual std::shared_ptr< WSSession > websocketUpgrade()=0
Convert the connection to WebSocket.
Validator keys and manifest as set in configuration file.
T duration_cast(T... args)
Endpoint fromAsio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool isUnspecified(Address const &addr)
Returns true if the address is unspecified.
bool isKeepAlive(boost::beast::http::message< IsRequest, Body, Fields > const &m)
void stream(json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
std::function< void(boost::beast::string_view const &)> Output
std::string const & getFullVersionString()
Full server version string.
static constexpr int kMaxRequestSize
Role roleRequired(unsigned int version, bool betaEnabled, std::string const &method)
static constexpr auto kApiInvalidVersion
json::Value makeError(ErrorCodeI code)
Returns a new json object that reflects the error code.
int errorCodeHttpStatus(ErrorCodeI code)
Returns http status that corresponds to the error code.
static constexpr auto kApiVersionIfUnspecified
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.
Charge const kFeeMalformedRpc
Charge const kFeeReferenceRpc
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static void setupClient(ServerHandler::Setup &setup)
std::string base64Decode(std::string_view data)
static json::Value makeJsonError(json::Int code, json::Value &&message)
ServerHandler::Setup setupServerHandler(Config const &config, std::ostream &log)
constexpr json::Int kWrongVersion
void parsePort(ParsedPort &port, Section const §ion, std::ostream &log)
static Port toPort(ParsedPort const &parsed, std::ostream &log)
Role requestRole(Role const &required, Port const &port, json::Value const ¶ms, beast::IP::Endpoint const &remoteIp, std::string_view user)
Return the allowed privilege role.
std::unique_ptr< Server > makeServer(Handler &handler, boost::asio::io_context &ioContext, beast::Journal journal)
Create the HTTP server using the specified handler.
static std::map< std::string, std::string > buildMap(boost::beast::http::fields const &h)
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, std::string_view user, std::string_view forwardedFor)
constexpr json::Int kMethodNotFound
static std::vector< Port > parsePorts(Config const &config, std::ostream &log)
std::string_view forwardedFor(http_request_type const &request)
std::string to_string(BaseUInt< Bits, Tag > const &a)
void logDuration(json::Value const &request, T const &duration, beast::Journal &journal)
static json::Output makeOutput(Session &session)
Dir::ConstIterator const_iterator
json::Value rpcError(ErrorCodeI iError)
void httpReply(int nStatus, std::string const &strMsg, json::Output const &, beast::Journal j)
std::shared_ptr< boost::asio::ssl::context > makeSslContextAuthed(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.
constexpr json::Int kForbidden
static std::string const & systemName()
std::shared_ptr< boost::asio::ssl::context > makeSslContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
static std::string buffersToString(ConstBufferSequence const &bs)
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
constexpr json::Int kServerOverloaded
Overlay::Setup setupOverlay(BasicConfig const &config, beast::Journal j)
static bool isStatusRequest(http_request_type const &request)
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
T static_pointer_cast(T... args)
T remove_suffix(T... args)
static IP::Endpoint fromAsio(boost::asio::ip::address const &address)
Used to indicate the result of a server connection handoff.
std::shared_ptr< Writer > response
std::set< std::string, boost::beast::iless > protocol
std::vector< boost::asio::ip::network_v4 > adminNetsV4
std::optional< boost::asio::ip::address > ip
std::vector< boost::asio::ip::network_v6 > adminNetsV6
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
std::optional< std::uint16_t > port
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
std::uint16_t wsQueueLimit
boost::beast::websocket::permessage_deflate pmdOptions
std::string adminPassword
Configuration information for a Server listening port.
std::vector< boost::asio::ip::network_v6 > adminNetsV6
std::set< std::string, boost::beast::iless > protocol
boost::beast::websocket::permessage_deflate pmdOptions
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
std::vector< boost::asio::ip::network_v4 > adminNetsV4
std::uint16_t wsQueueLimit
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
std::string adminPassword
boost::asio::ip::address ip
static constexpr auto kServer
static constexpr auto kPortGrpc
std::string adminPassword
boost::asio::ip::tcp::endpoint overlay
std::vector< Port > ports