diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 71c065f7f7..0358e592d9 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4752,6 +4752,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index c7f958424b..53b8a32907 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5439,6 +5439,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..efd03a5ae4 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 @@ -50,6 +52,72 @@ namespace ripple { +// Returns `true` if the HTTP request is a Websockets Upgrade +// http://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header#Use_with_WebSockets +static +bool +isWebsocketUpgrade( + http_request_type const& request) +{ + if (is_upgrade(request)) + return beast::detail::ci_equal( + request.headers["Upgrade"], "websocket"); + return false; +} + +static +bool +isStatusRequest( + http_request_type const& request) +{ + return request.version >= 11 && request.url == "/" && + request.body.size() == 0 && request.method == "GET"; +} + +static +Handoff +unauthorizedResponse( + http_request_type const& request) +{ + 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 +static +bool +authorized ( + Port const& port, + std::map const& h) +{ + if (port.user.empty() || port.password.empty()) + return true; + + auto const it = h.find ("authorization"); + if ((it == h.end ()) || (it->second.substr (0, 6) != "Basic ")) + return false; + std::string strUserPass64 = it->second.substr (6); + boost::trim (strUserPass64); + std::string strUserPass = beast::detail::base64_decode (strUserPass64); + std::string::size_type nColon = strUserPass.find (":"); + if (nColon == std::string::npos) + return false; + std::string strUser = strUserPass.substr (0, nColon); + std::string strPassword = strUserPass.substr (nColon + 1); + return strUser == port.user && strPassword == port.password; +} + + ServerHandlerImp::ServerHandlerImp (Application& app, Stoppable& parent, boost::asio::io_service& io_service, JobQueue& jobQueue, NetworkOPs& networkOPs, Resource::Manager& resourceManager, @@ -119,9 +187,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 +203,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 +214,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 +228,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 @@ -616,36 +697,40 @@ 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) +/* 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 { - if (is_upgrade(request)) - return request.headers["Upgrade"] == "websocket"; - return false; -} - -// VFALCO TODO Rewrite to use beast::http::headers -bool -ServerHandlerImp::authorized (Port const& port, - std::map const& h) -{ - if (port.user.empty() || port.password.empty()) - return true; - - auto const it = h.find ("authorization"); - if ((it == h.end ()) || (it->second.substr (0, 6) != "Basic ")) - return false; - std::string strUserPass64 = it->second.substr (6); - boost::trim (strUserPass64); - std::string strUserPass = beast::detail::base64_decode (strUserPass64); - std::string::size_type nColon = strUserPass.find (":"); - if (nColon == std::string::npos) - return false; - std::string strUser = strUserPass.substr (0, nColon); - std::string strPassword = strUserPass.substr (nColon + 1); - return strUser == port.user && strPassword == port.password; + 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; } //------------------------------------------------------------------------------ diff --git a/src/ripple/rpc/impl/ServerHandlerImp.h b/src/ripple/rpc/impl/ServerHandlerImp.h index de33b8b153..45ea0daf97 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,10 @@ public: std::shared_ptr jobCoro, std::string forwardedFor, std::string user); -private: - bool - isWebsocketUpgrade (http_request_type const& request); + Handoff + statusResponse(http_request_type const& request) const; + - bool - authorized (Port const& port, - std::map const& h); }; } diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp new file mode 100644 index 0000000000..3ccbc39ae9 --- /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(! BEAST_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(! BEAST_EXPECTS(! ec, ec.message())) + return; + write(sock, req, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + read(sock, sb, resp, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_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(! BEAST_EXPECTS(! ec, ec.message())) + return; + write(ss, req, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + read(ss, sb, resp, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_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(! BEAST_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(! BEAST_EXPECTS(! ec, ec.message())) + return; + write(sock, req, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + read(sock, sb, resp, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_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(! BEAST_EXPECTS(! ec, ec.message())) + return; + write(ss, req, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + read(ss, sb, resp, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_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