From b2499c8fa015ac0121b81dcf8f1a92d9e1fd6a4b Mon Sep 17 00:00:00 2001 From: Miguel Portilla Date: Wed, 14 Sep 2016 21:01:35 -0400 Subject: [PATCH] Add Status page: * Make HTTP(S) requests on websocket ports reply with Status page * Fix isWebsocketUpgrade to compare case insensitive * Make websocket upgrades with no websocket protocols configured report error * Create unit test for unauthorized requets and the status page --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/rpc/impl/ServerHandlerImp.cpp | 115 +++++++++-- src/ripple/rpc/impl/ServerHandlerImp.h | 19 +- src/test/server/ServerStatus_test.cpp | 187 ++++++++++++++++++ src/unity/server_test_unity.cpp | 3 +- 6 files changed, 306 insertions(+), 25 deletions(-) create mode 100644 src/test/server/ServerStatus_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 7c6678c5f8..c3cd6323e9 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4744,6 +4744,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 77c978491c..943c9c752b 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5433,6 +5433,9 @@ test\rpc + + test\server + test\server diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index c180f95a9d..0919d9cdb7 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -39,8 +39,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -119,9 +121,9 @@ ServerHandlerImp::onHandoff (Session& session, { if(isWebsocketUpgrade(request)) { - Handoff handoff; if(session.port().protocol.count("wss2") > 0) { + Handoff handoff; auto const ws = session.websocketUpgrade(); auto is = std::make_shared(m_networkOPs, ws); is->getConsumer() = requestInboundEndpoint( @@ -135,7 +137,9 @@ ServerHandlerImp::onHandoff (Session& session, } if(session.port().protocol.count("wss") > 0) - return handoff; // Pass to websocket + return {}; // Pass to websocket + + return unauthorizedResponse(request); } if(session.port().protocol.count("peer") > 0) @@ -144,6 +148,9 @@ ServerHandlerImp::onHandoff (Session& session, std::move(request), remote_address); } + if (session.port().protocol.count("wss2") > 0 && isStatusRequest(request)) + return statusResponse(request); + // Pass to legacy onRequest return {}; } @@ -155,22 +162,30 @@ ServerHandlerImp::onHandoff (Session& session, boost::asio::ip::tcp::endpoint remote_address) -> Handoff { - Handoff handoff; - if(session.port().protocol.count("ws2") > 0 && - isWebsocketUpgrade (request)) + if(isWebsocketUpgrade(request)) { - auto const ws = session.websocketUpgrade(); - auto is = std::make_shared(m_networkOPs, ws); - is->getConsumer() = requestInboundEndpoint( - m_resourceManager, - beast::IPAddressConversion::from_asio(remote_address), - session.port(), is->user()); - ws->appDefined = std::move(is); - ws->run(); - handoff.moved = true; + if (session.port().protocol.count("ws2") > 0) + { + Handoff handoff; + auto const ws = session.websocketUpgrade(); + auto is = std::make_shared(m_networkOPs, ws); + is->getConsumer() = requestInboundEndpoint( + m_resourceManager, beast::IPAddressConversion::from_asio( + remote_address), session.port(), is->user()); + ws->appDefined = std::move(is); + ws->run(); + handoff.moved = true; + return handoff; + } + + return unauthorizedResponse(request); } + + if (session.port().protocol.count("ws2") > 0 && isStatusRequest(request)) + return statusResponse(request); + // Otherwise pass to legacy onRequest or websocket - return handoff; + return {}; } static inline @@ -619,17 +634,81 @@ ServerHandlerImp::processRequest (Port const& port, // Returns `true` if the HTTP request is a Websockets Upgrade // http://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header#Use_with_WebSockets bool -ServerHandlerImp::isWebsocketUpgrade (http_request_type const& request) +ServerHandlerImp::isWebsocketUpgrade( + http_request_type const& request) const { if (is_upgrade(request)) - return request.headers["Upgrade"] == "websocket"; + return beast::detail::ci_equal( + request.headers["Upgrade"], "websocket"); return false; } +bool +ServerHandlerImp::isStatusRequest( + http_request_type const& request) const +{ + return request.version >= 11 && request.url == "/" && + request.body.size() == 0 && request.method == "GET"; +} + +/* This response is used with load balancing. + If the server is overloaded, status 500 is reported. Otherwise status 200 + is reported, meaning the server can accept more connections. +*/ +Handoff +ServerHandlerImp::statusResponse( + http_request_type const& request) const +{ + using namespace beast::http; + Handoff handoff; + response_v1 msg; + std::string reason; + if (app_.serverOkay(reason)) + { + msg.status = 200; + msg.reason = "OK"; + msg.body = "" + systemName() + + " Test page for rippled

" + + systemName() + " Test

This page shows rippled http(s) " + "connectivity is working.

"; + } + else + { + msg.status = 500; + msg.reason = "Internal Server Error"; + msg.body = "Server cannot accept clients: " + + reason + ""; + } + msg.version = request.version; + msg.headers.insert("Server", BuildInfo::getFullVersionString()); + msg.headers.insert("Content-Type", "text/html"); + prepare(msg, beast::http::connection::close); + handoff.response = std::make_shared(msg); + return handoff; +} + +Handoff +ServerHandlerImp::unauthorizedResponse( + http_request_type const& request) const +{ + using namespace beast::http; + Handoff handoff; + response_v1 msg; + msg.version = request.version; + msg.status = 401; + msg.reason = "Unauthorized"; + msg.headers.insert("Server", BuildInfo::getFullVersionString()); + msg.headers.insert("Content-Type", "text/html"); + msg.body = "Invalid protocol."; + prepare(msg, beast::http::connection::close); + handoff.response = std::make_shared(msg); + return handoff; +} + // VFALCO TODO Rewrite to use beast::http::headers bool ServerHandlerImp::authorized (Port const& port, - std::map const& h) + std::map const& h) const { if (port.user.empty() || port.password.empty()) return true; diff --git a/src/ripple/rpc/impl/ServerHandlerImp.h b/src/ripple/rpc/impl/ServerHandlerImp.h index de33b8b153..f42d7fffdc 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.h +++ b/src/ripple/rpc/impl/ServerHandlerImp.h @@ -149,8 +149,7 @@ public: void onStopped (Server&); - //-------------------------------------------------------------------------- - +private: Json::Value processSession( std::shared_ptr const& session, @@ -167,13 +166,21 @@ public: std::shared_ptr jobCoro, std::string forwardedFor, std::string user); -private: bool - isWebsocketUpgrade (http_request_type const& request); + isWebsocketUpgrade(http_request_type const& request) const; bool - authorized (Port const& port, - std::map const& h); + isStatusRequest(http_request_type const& request) const; + + Handoff + statusResponse(http_request_type const& request) const; + + Handoff + unauthorizedResponse(http_request_type const& request) const; + + bool + authorized (Port const& port, std::map const& h) const; }; } diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp new file mode 100644 index 0000000000..3a1f69d0b1 --- /dev/null +++ b/src/test/server/ServerStatus_test.cpp @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class ServerStatus_test : public beast::unit_test::suite +{ +public: + void + testUnauthorizedRequest() + { + using namespace jtx; + Env env(*this, []() + { + auto p = std::make_unique(); + setupConfigForUnitTests(*p); + p->section("port_ws").set("protocol", "http,https"); + return p; + }()); + auto const port = env.app().config()["port_ws"]. + get("port"); + if(! expect(port)) + return; + + using namespace boost::asio; + using namespace beast::http; + io_service ios; + ip::tcp::resolver r{ios}; + beast::streambuf sb; + response_v1 resp; + boost::system::error_code ec; + + beast::websocket::detail::maskgen maskgen; + request_v1 req; + req.url = "/"; + req.version = 11; + req.method = "GET"; + req.headers.insert("Host", "127.0.0.1:" + to_string(*port)); + req.headers.insert("Upgrade", "websocket"); + std::string key = beast::websocket::detail::make_sec_ws_key(maskgen); + req.headers.insert("Sec-WebSocket-Key", key); + req.headers.insert("Sec-WebSocket-Version", "13"); + prepare(req, connection::upgrade); + + // non secure socket + { + ip::tcp::socket sock{ios}; + connect(sock, r.resolve( + ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}), ec); + if(! expect(! ec)) + return; + write(sock, req, ec); + if(! expect(! ec)) + return; + read(sock, sb, resp, ec); + if(! expect(! ec)) + return; + expect(resp.status == 401); + } + + // secure socket + { + ssl::context ctx{ssl::context::sslv23}; + ctx.set_verify_mode(ssl::verify_none); + ssl::stream ss{ios, ctx}; + connect(ss.next_layer(), r.resolve( + ip::tcp::resolver::query{"127.0.0.1", to_string(*port)})); + ss.handshake(ssl::stream_base::client, ec); + if(! expect(! ec)) + return; + write(ss, req, ec); + if(! expect(! ec)) + return; + read(ss, sb, resp, ec); + if(! expect(! ec)) + return; + expect(resp.status == 401); + } + } + + void + testStatusRequest() + { + using namespace jtx; + Env env(*this, []() + { + auto p = std::make_unique(); + setupConfigForUnitTests(*p); + p->section("port_ws").set("protocol", "ws2,wss2"); + return p; + }()); + auto const port = env.app().config()["port_ws"]. + get("port"); + if(! expect(port)) + return; + + using namespace boost::asio; + using namespace beast::http; + io_service ios; + ip::tcp::resolver r{ios}; + beast::streambuf sb; + response_v1 resp; + boost::system::error_code ec; + + request_v1 req; + req.url = "/"; + req.version = 11; + req.method = "GET"; + req.headers.insert("Host", "127.0.0.1:" + to_string(*port)); + req.headers.insert("User-Agent", "test"); + prepare(req); + + // Request the status page on a non secure socket + { + ip::tcp::socket sock{ios}; + connect(sock, r.resolve( + ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}), ec); + if(! expect(! ec)) + return; + write(sock, req, ec); + if(! expect(! ec)) + return; + read(sock, sb, resp, ec); + if(! expect(! ec)) + return; + expect(resp.status == 200); + } + + // Request the status page on a secure socket + { + ssl::context ctx{ssl::context::sslv23}; + ctx.set_verify_mode(ssl::verify_none); + ssl::stream ss{ios, ctx}; + connect(ss.next_layer(), r.resolve( + ip::tcp::resolver::query{"127.0.0.1", to_string(*port)})); + ss.handshake(ssl::stream_base::client, ec); + if(! expect(! ec)) + return; + write(ss, req, ec); + if(! expect(! ec)) + return; + read(ss, sb, resp, ec); + if(! expect(! ec)) + return; + expect(resp.status == 200); + } + }; + + void + run() + { + testUnauthorizedRequest(); + testStatusRequest(); + }; +}; + +BEAST_DEFINE_TESTSUITE(ServerStatus, server, ripple); + +} // test +} // ripple + diff --git a/src/unity/server_test_unity.cpp b/src/unity/server_test_unity.cpp index 1d52762897..1cb2570f1e 100644 --- a/src/unity/server_test_unity.cpp +++ b/src/unity/server_test_unity.cpp @@ -18,4 +18,5 @@ */ //============================================================================== -#include \ No newline at end of file +#include +#include