diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 16781ac09d4..8ed328df440 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -602,6 +602,13 @@ class ApplicationImp : public Application, public BasicApp return *m_networkOPs; } + virtual ServerHandlerImp& + getServerHandler() override + { + assert(serverHandler_); + return *serverHandler_; + } + boost::asio::io_service& getIOService() override { diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index d8cb7d31815..d2ba8f7cc75 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -89,6 +89,7 @@ class Overlay; class PathRequests; class PendingSaves; class PublicKey; +class ServerHandlerImp; class SecretKey; class STLedgerEntry; class TimeKeeper; @@ -231,6 +232,8 @@ class Application : public beast::PropertyStream::Source getOPs() = 0; virtual OrderBookDB& getOrderBookDB() = 0; + virtual ServerHandlerImp& + getServerHandler() = 0; virtual TransactionMaster& getMasterTransaction() = 0; virtual perf::PerfLog& diff --git a/src/ripple/app/main/GRPCServer.cpp b/src/ripple/app/main/GRPCServer.cpp index fdef8c1cec8..3a5e96b0ed9 100644 --- a/src/ripple/app/main/GRPCServer.cpp +++ b/src/ripple/app/main/GRPCServer.cpp @@ -429,7 +429,7 @@ GRPCServerImpl::GRPCServerImpl(Application& app) // if present, get endpoint from config if (app_.config().exists("port_grpc")) { - Section section = app_.config().section("port_grpc"); + const auto& section = app_.config().section("port_grpc"); auto const optIp = section.get("ip"); if (!optIp) diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 6be11c7dd6c..6f51f811055 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -65,9 +65,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -2661,6 +2663,51 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) info["reporting"] = app_.getReportingETL().getInfo(); } + // This array must be sorted in increasing order. + static constexpr std::array protocols{ + "http", "https", "peer", "ws", "ws2", "wss", "wss2"}; + static_assert(std::is_sorted(std::begin(protocols), std::end(protocols))); + { + Json::Value ports{Json::arrayValue}; + for (auto const& port : app_.getServerHandler().setup().ports) + { + // Don't publish admin ports for non-admin users + if (!admin && + !(port.admin_nets_v4.empty() && port.admin_nets_v6.empty() && + port.admin_user.empty() && port.admin_password.empty())) + continue; + std::vector proto; + std::set_intersection( + std::begin(port.protocol), + std::end(port.protocol), + std::begin(protocols), + std::end(protocols), + std::back_inserter(proto)); + if (!proto.empty()) + { + auto& jv = ports.append(Json::Value(Json::objectValue)); + jv[jss::port] = std::to_string(port.port); + jv[jss::protocol] = Json::Value{Json::arrayValue}; + for (auto const& p : proto) + jv[jss::protocol].append(p); + } + } + + if (app_.config().exists("port_grpc")) + { + auto const& grpcSection = app_.config().section("port_grpc"); + auto const optPort = grpcSection.get("port"); + if (optPort && grpcSection.get("ip")) + { + auto& jv = ports.append(Json::Value(Json::objectValue)); + jv[jss::port] = *optPort; + jv[jss::protocol] = Json::Value{Json::arrayValue}; + jv[jss::protocol].append("grpc"); + } + } + info[jss::ports] = std::move(ports); + } + return info; } diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 92d9096da92..8fa1b6cc24c 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -465,13 +465,14 @@ JSS(peers); // out: InboundLedger, handlers/Peers, Overlay JSS(peer_disconnects); // Severed peer connection counter. JSS(peer_disconnects_resources); // Severed peer connections because of // excess resource consumption. -JSS(port); // in: Connect +JSS(port); // in: Connect, out: NetworkOPs +JSS(ports); // out: NetworkOPs JSS(previous); // out: Reservations JSS(previous_ledger); // out: LedgerPropose JSS(proof); // in: BookOffers JSS(propose_seq); // out: LedgerPropose JSS(proposers); // out: NetworkOPs, LedgerConsensus -JSS(protocol); // out: PeerImp +JSS(protocol); // out: NetworkOPs, PeerImp JSS(proxied); // out: RPC ping JSS(pubkey_node); // out: NetworkOPs JSS(pubkey_publisher); // out: ValidatorList diff --git a/src/test/rpc/ServerInfo_test.cpp b/src/test/rpc/ServerInfo_test.cpp index 24cfd12299a..a69483cb130 100644 --- a/src/test/rpc/ServerInfo_test.cpp +++ b/src/test/rpc/ServerInfo_test.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include #include @@ -55,6 +56,16 @@ class ServerInfo_test : public beast::unit_test::suite [validators] %2% + +[port_grpc] +ip = 0.0.0.0 +port = 50051 + +[port_admin] +ip = 0.0.0.0 +port = 50052 +protocol = wss2 +admin = 127.0.0.1 )rippleConfig"); p->loadFromString(boost::str( @@ -77,8 +88,30 @@ class ServerInfo_test : public beast::unit_test::suite BEAST_EXPECT(result[jss::result][jss::status] == "success"); BEAST_EXPECT(result[jss::result].isMember(jss::info)); } + { - Env env(*this, makeValidatorConfig()); + Env env(*this); + + // Call NetworkOPs directly and set the admin flag to false. + // Expect that the admin ports are not included in the result. + auto const result = + env.app().getOPs().getServerInfo(true, false, 0); + auto const& ports = result[jss::ports]; + BEAST_EXPECT(ports.isArray() && ports.size() == 0); + } + + { + auto config = makeValidatorConfig(); + auto const rpc_port = + (*config)["port_rpc"].get("port"); + auto const grpc_port = + (*config)["port_grpc"].get("port"); + auto const ws_port = (*config)["port_ws"].get("port"); + BEAST_EXPECT(grpc_port); + BEAST_EXPECT(rpc_port); + BEAST_EXPECT(ws_port); + + Env env(*this, std::move(config)); auto const result = env.rpc("server_info"); BEAST_EXPECT(!result[jss::result].isMember(jss::error)); BEAST_EXPECT(result[jss::result][jss::status] == "success"); @@ -86,6 +119,32 @@ class ServerInfo_test : public beast::unit_test::suite BEAST_EXPECT( result[jss::result][jss::info][jss::pubkey_validator] == validator_data::public_key); + + auto const& ports = result[jss::result][jss::info][jss::ports]; + BEAST_EXPECT(ports.isArray() && ports.size() == 3); + for (auto const& port : ports) + { + auto const& proto = port[jss::protocol]; + BEAST_EXPECT(proto.isArray()); + auto const p = port[jss::port].asUInt(); + BEAST_EXPECT(p == rpc_port || p == ws_port || p == grpc_port); + if (p == grpc_port) + { + BEAST_EXPECT(proto.size() == 1); + BEAST_EXPECT(proto[0u].asString() == "grpc"); + } + if (p == rpc_port) + { + BEAST_EXPECT(proto.size() == 2); + BEAST_EXPECT(proto[0u].asString() == "http"); + BEAST_EXPECT(proto[1u].asString() == "ws2"); + } + if (p == ws_port) + { + BEAST_EXPECT(proto.size() == 1); + BEAST_EXPECT(proto[0u].asString() == "ws"); + } + } } }