HTTP handshake in peer protocol (RIPD-351):

* New I/O paths for client and server role
* New handshake_analyzer detects the peer protocol
* New basic_message class for parsing and storing HTTP messages
* Conditional compilation for selective feature enabling.
* Server supports both current handshake and HTTP handshake
This commit is contained in:
Vinnie Falco
2014-07-31 15:46:05 -07:00
parent 723d7d1263
commit 6e934ee6a1
13 changed files with 2335 additions and 626 deletions

View File

@@ -3105,6 +3105,8 @@
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ripple\overlay\impl\PeerImp.h"> <ClInclude Include="..\..\src\ripple\overlay\impl\PeerImp.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\overlay\impl\peer_info.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\overlay\impl\peer_protocol_detector.h"> <ClInclude Include="..\..\src\ripple\overlay\impl\peer_protocol_detector.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\overlay\impl\Tuning.h"> <ClInclude Include="..\..\src\ripple\overlay\impl\Tuning.h">
@@ -3121,6 +3123,9 @@
</ClInclude> </ClInclude>
<None Include="..\..\src\ripple\overlay\README.md"> <None Include="..\..\src\ripple\overlay\README.md">
</None> </None>
<ClCompile Include="..\..\src\ripple\overlay\tests\peer_info.test.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\peerfinder\api\Callback.h"> <ClInclude Include="..\..\src\ripple\peerfinder\api\Callback.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\peerfinder\api\Config.h"> <ClInclude Include="..\..\src\ripple\peerfinder\api\Config.h">

View File

@@ -463,6 +463,9 @@
<Filter Include="ripple\overlay\impl"> <Filter Include="ripple\overlay\impl">
<UniqueIdentifier>{07E4BC73-2B68-D0D1-D922-FEBBB573F503}</UniqueIdentifier> <UniqueIdentifier>{07E4BC73-2B68-D0D1-D922-FEBBB573F503}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="ripple\overlay\tests">
<UniqueIdentifier>{630E81FA-2122-38EA-81BD-636140BF270C}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\peerfinder"> <Filter Include="ripple\peerfinder">
<UniqueIdentifier>{186385AD-A056-FA3A-7E0E-759EB55E9EAB}</UniqueIdentifier> <UniqueIdentifier>{186385AD-A056-FA3A-7E0E-759EB55E9EAB}</UniqueIdentifier>
</Filter> </Filter>
@@ -4281,6 +4284,9 @@
<ClInclude Include="..\..\src\ripple\overlay\impl\PeerImp.h"> <ClInclude Include="..\..\src\ripple\overlay\impl\PeerImp.h">
<Filter>ripple\overlay\impl</Filter> <Filter>ripple\overlay\impl</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\overlay\impl\peer_info.h">
<Filter>ripple\overlay\impl</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\overlay\impl\peer_protocol_detector.h"> <ClInclude Include="..\..\src\ripple\overlay\impl\peer_protocol_detector.h">
<Filter>ripple\overlay\impl</Filter> <Filter>ripple\overlay\impl</Filter>
</ClInclude> </ClInclude>
@@ -4305,6 +4311,9 @@
<None Include="..\..\src\ripple\overlay\README.md"> <None Include="..\..\src\ripple\overlay\README.md">
<Filter>ripple\overlay</Filter> <Filter>ripple\overlay</Filter>
</None> </None>
<ClCompile Include="..\..\src\ripple\overlay\tests\peer_info.test.cpp">
<Filter>ripple\overlay\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\peerfinder\api\Callback.h"> <ClInclude Include="..\..\src\ripple\peerfinder\api\Callback.h">
<Filter>ripple\peerfinder\api</Filter> <Filter>ripple\peerfinder\api</Filter>
</ClInclude> </ClInclude>

View File

@@ -220,11 +220,17 @@
#define RIPPLE_SINGLE_IO_SERVICE_THREAD 0 #define RIPPLE_SINGLE_IO_SERVICE_THREAD 0
#endif #endif
/** Config: RIPPLE_STRUCTURED_OVERLAY /** Config: RIPPLE_STRUCTURED_OVERLAY_CLIENT
Enables Structured Overlay support (unfinished) RIPPLE_STRUCTURED_OVERLAY_SERVER
Enables Structured Overlay support for the client or server roles.
This feature is currently in development:
https://ripplelabs.atlassian.net/browse/RIPD-157
*/ */
#ifndef RIPPLE_STRUCTURED_OVERLAY #ifndef RIPPLE_STRUCTURED_OVERLAY_CLIENT
#define RIPPLE_STRUCTURED_OVERLAY 0 #define RIPPLE_STRUCTURED_OVERLAY_CLIENT 0
#endif
#ifndef RIPPLE_STRUCTURED_OVERLAY_SERVER
#define RIPPLE_STRUCTURED_OVERLAY_SERVER 1
#endif #endif
/** Config: RIPPLE_ASYNC_RPC_HANDLER /** Config: RIPPLE_ASYNC_RPC_HANDLER

View File

@@ -21,6 +21,39 @@ establishes, receives, and maintains connections to peers. Protocol
messages are exchanged between peers and serialized using messages are exchanged between peers and serialized using
[_Google Protocol Buffers_][protocol_buffers]. [_Google Protocol Buffers_][protocol_buffers].
### Structure
Each connection between peers is identified by its connection type, which
affects the behavior of message routing:
* Leaf
* Peer
## Roles
Depending on the type of connection desired, the peers will modify their
behavior according to certain roles:
### Leaf or Superpeer
A peer in the leaf role does not route messages. In the superpeer role, a
peer accepts incoming connections from other leaves and superpeers up to the
configured slot limit. It also routes messages. For a particular connection,
the choice of leaf or superpeer is mutually exclusive. However, a peer can
operate in both the leaf and superpeer role for different connections. One of
the requirements
### Client Handler
While not part of the responsibilities of the Overlay module, a peer
operating in the Client Handler role accepts incoming connections from clients
and services them through the JSON-RPC interface. A peer can operate in either
the leaf or superpeer roles while also operating as a client handler.
## Handshake ## Handshake
To establish a protocol connection, a peer makes an outgoing TLS encrypted To establish a protocol connection, a peer makes an outgoing TLS encrypted
@@ -128,6 +161,13 @@ Protocol-Session-Cookie: 71ED064155FFADFA38782C5E0158CB26
This field must be present (TODO) This field must be present (TODO)
* _User Defined_
The rippled operator may specify additional, optional fields and values
through the configuration. These headers will be transmitted in the
corresponding request or response messages.
--- ---
[overlay_network]: http://en.wikipedia.org/wiki/Overlay_network [overlay_network]: http://en.wikipedia.org/wiki/Overlay_network

View File

@@ -291,7 +291,7 @@ OverlayImpl::disconnect (PeerFinder::Slot::ptr const& slot, bool graceful)
{ {
if (m_journal.trace) m_journal.trace << if (m_journal.trace) m_journal.trace <<
"Disconnect " << slot->remote_endpoint () << "Disconnect " << slot->remote_endpoint () <<
(graceful ? "gracefully" : ""); (graceful ? " gracefully" : "");
std::lock_guard <decltype(m_mutex)> lock (m_mutex); std::lock_guard <decltype(m_mutex)> lock (m_mutex);

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,6 @@
#include <ripple/common/MultiSocket.h> #include <ripple/common/MultiSocket.h>
#include <ripple/nodestore/Database.h> #include <ripple/nodestore/Database.h>
#include <ripple/overlay/predicates.h> #include <ripple/overlay/predicates.h>
#include <ripple/overlay/impl/basic_message.h>
#include <ripple/overlay/impl/message_name.h> #include <ripple/overlay/impl/message_name.h>
#include <ripple/overlay/impl/message_stream.h> #include <ripple/overlay/impl/message_stream.h>
#include <ripple/overlay/impl/OverlayImpl.h> #include <ripple/overlay/impl/OverlayImpl.h>
@@ -40,7 +39,7 @@
#include <beast/asio/IPAddressConversion.h> #include <beast/asio/IPAddressConversion.h>
#include <beast/asio/placeholders.h> #include <beast/asio/placeholders.h>
#include <beast/http/message_parser.h> #include <beast/http/basic_message.h>
#include <cstdint> #include <cstdint>
@@ -80,58 +79,6 @@ private:
/** The length of the smallest valid finished message */ /** The length of the smallest valid finished message */
static const size_t sslMinimumFinishedLength = 12; static const size_t sslMinimumFinishedLength = 12;
//--------------------------------------------------------------------------
/** We have accepted an inbound connection.
The connection state transitions from `stateConnect` to `stateConnected`
as `stateConnect`.
*/
void accept ()
{
m_journal.info << "Accepted " << m_remoteAddress;
m_socket->set_verify_mode (boost::asio::ssl::verify_none);
m_socket->async_handshake (
boost::asio::ssl::stream_base::server,
m_strand.wrap (std::bind (
&PeerImp::handleStart,
std::static_pointer_cast <PeerImp> (shared_from_this ()),
beast::asio::placeholders::error)));
}
/** Attempt an outbound connection.
The connection may fail (for a number of reasons) and we do not know
what will happen at this point.
The connection state does not transition with this function and remains
as `stateConnecting`.
*/
void connect ()
{
m_journal.info << "Connecting to " << m_remoteAddress;
boost::system::error_code err;
m_timer.expires_from_now (nodeVerifySeconds, err);
m_timer.async_wait (m_strand.wrap (std::bind (
&PeerImp::handleVerifyTimer,
shared_from_this (), beast::asio::placeholders::error)));
if (err)
{
m_journal.error << "Failed to set verify timer.";
detach ("c2");
return;
}
m_socket->next_layer <NativeSocketType>().async_connect (
beast::IPAddressConversion::to_asio_endpoint (m_remoteAddress),
m_strand.wrap (std::bind (&PeerImp::onConnect,
shared_from_this (), beast::asio::placeholders::error)));
}
public: public:
/** Current state */ /** Current state */
enum State enum State
@@ -201,7 +148,7 @@ public:
std::list<uint256> m_recentTxSets; std::list<uint256> m_recentTxSets;
mutable std::mutex m_recentLock; mutable std::mutex m_recentLock;
boost::asio::deadline_timer m_timer; boost::asio::deadline_timer timer_;
std::vector<uint8_t> m_readBuffer; std::vector<uint8_t> m_readBuffer;
std::list<Message::pointer> mSendQ; std::list<Message::pointer> mSendQ;
@@ -217,13 +164,20 @@ public:
// True if close was called // True if close was called
bool m_was_canceled; bool m_was_canceled;
boost::asio::streambuf read_buffer_; boost::asio::streambuf read_buffer_;
boost::optional <basic_message> http_message_; boost::optional <beast::http::basic_message> http_message_;
boost::optional <basic_message::parser> http_parser_; boost::optional <beast::http::basic_message::parser> http_parser_;
message_stream message_stream_; message_stream message_stream_;
boost::asio::streambuf write_buffer_;
bool write_pending_;
std::unique_ptr <LoadEvent> load_event_; std::unique_ptr <LoadEvent> load_event_;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
/** New incoming peer from the specified socket */ /** New incoming peer from the specified socket */
PeerImp ( PeerImp (
NativeSocketType&& socket, NativeSocketType&& socket,
@@ -250,10 +204,11 @@ public:
, m_clusterNode (false) , m_clusterNode (false)
, m_minLedger (0) , m_minLedger (0)
, m_maxLedger (0) , m_maxLedger (0)
, m_timer (m_owned_socket.get_io_service()) , timer_ (m_owned_socket.get_io_service())
, m_slot (slot) , m_slot (slot)
, m_was_canceled (false) , m_was_canceled (false)
, message_stream_(*this) , message_stream_(*this)
, write_pending_ (false)
{ {
} }
@@ -288,10 +243,11 @@ public:
, m_clusterNode (false) , m_clusterNode (false)
, m_minLedger (0) , m_minLedger (0)
, m_maxLedger (0) , m_maxLedger (0)
, m_timer (io_service) , timer_ (io_service)
, m_slot (slot) , m_slot (slot)
, m_was_canceled (false) , m_was_canceled (false)
, message_stream_(*this) , message_stream_(*this)
, write_pending_ (false)
{ {
} }
@@ -313,22 +269,66 @@ public:
void getLedger (protocol::TMGetLedger& packet); void getLedger (protocol::TMGetLedger& packet);
private:
// //
// i/o // client role
// //
void void
start_read(); do_connect();
void void
on_read_detect (error_code ec, std::size_t bytes_transferred); on_connect (error_code ec);
beast::http::basic_message
make_request();
void void
on_read_http (error_code ec, std::size_t bytes_transferred); on_connect_ssl (error_code ec);
void
on_write_http_request (error_code ec, std::size_t bytes_transferred);
void
on_read_http_response (error_code ec, std::size_t bytes_transferred);
//
// server role
//
void
do_accept();
void
on_accept_ssl (error_code ec);
void
on_read_http_detect (error_code ec, std::size_t bytes_transferred);
void
on_read_http_request (error_code ec, std::size_t bytes_transferred);
beast::http::basic_message
make_response (beast::http::basic_message const& req);
void
on_write_http_response (error_code ec, std::size_t bytes_transferred);
//
// protocol
//
void
do_protocol_start();
void void
on_read_protocol (error_code ec, std::size_t bytes_transferred); on_read_protocol (error_code ec, std::size_t bytes_transferred);
void
on_write_protocol (error_code ec, std::size_t bytes_transferred);
//--------------------------------------------------------------------------
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// //
// abstract_protocol_handler // abstract_protocol_handler
@@ -354,6 +354,7 @@ public:
on_message_end (std::uint16_t type, on_message_end (std::uint16_t type,
std::shared_ptr <::google::protobuf::Message> const& m) override; std::shared_ptr <::google::protobuf::Message> const& m) override;
// message handlers
error_code on_message (std::shared_ptr <protocol::TMHello> const& m) override; error_code on_message (std::shared_ptr <protocol::TMHello> const& m) override;
error_code on_message (std::shared_ptr <protocol::TMPing> const& m) override; error_code on_message (std::shared_ptr <protocol::TMPing> const& m) override;
error_code on_message (std::shared_ptr <protocol::TMProofWork> const& m) override; error_code on_message (std::shared_ptr <protocol::TMProofWork> const& m) override;
@@ -372,6 +373,7 @@ public:
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
public:
State state() const State state() const
{ {
return m_state; return m_state;
@@ -422,7 +424,7 @@ public:
mSendQ.clear (); mSendQ.clear ();
(void) m_timer.cancel (); (void) timer_.cancel ();
if (graceful) if (graceful)
{ {
@@ -450,55 +452,6 @@ public:
detach ("stop", graceful); detach ("stop", graceful);
} }
/** Outbound connection attempt has completed (not necessarily successfully)
The connection may fail for a number of reasons. Perhaps we do not have
a route to the remote endpoint, or there is no server listening at that
address.
If the connection succeeded, we transition to the `stateConnected` state
and move on.
If the connection failed, we simply disconnect.
@param ec indicates success or an error code.
*/
void onConnect (boost::system::error_code ec)
{
if (m_detaching)
return;
NativeSocketType::endpoint_type local_endpoint;
if (! ec)
local_endpoint = m_socket->this_layer <
NativeSocketType> ().local_endpoint (ec);
if (ec)
{
// VFALCO NOTE This log statement looks like ass
m_journal.info <<
"Connect to " << m_remoteAddress <<
" failed: " << ec.message();
// This should end up calling onPeerClosed()
detach ("hc");
return;
}
bassert (m_state == stateConnecting);
m_state = stateConnected;
m_peerFinder.on_connected (m_slot,
beast::IPAddressConversion::from_asio (local_endpoint));
m_socket->set_verify_mode (boost::asio::ssl::verify_none);
m_socket->async_handshake (
boost::asio::ssl::stream_base::client,
m_strand.wrap (std::bind (&PeerImp::handleStart,
std::static_pointer_cast <PeerImp> (shared_from_this ()),
beast::asio::placeholders::error)));
}
/** Indicates that the peer must be activated. /** Indicates that the peer must be activated.
A peer is activated after the handshake is completed and if it is not A peer is activated after the handshake is completed and if it is not
a second connection from a peer that we already have. Once activated a second connection from a peer that we already have. Once activated
@@ -516,9 +469,9 @@ public:
void start () void start ()
{ {
if (m_inbound) if (m_inbound)
accept (); do_accept ();
else else
connect (); do_connect ();
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -773,47 +726,6 @@ private:
} }
} }
// We have an encrypted connection to the peer.
// Have it say who it is so we know to avoid redundant connections.
// Establish that it really who we are talking to by having it sign a
// connection detail. Also need to establish no man in the middle attack
// is in progress.
void handleStart (boost::system::error_code const& ec)
{
if (m_detaching)
return;
if (ec == boost::asio::error::operation_aborted)
return;
if (ec)
{
m_journal.info << "Handshake: " << ec.message ();
detach ("hs");
return;
}
if (m_inbound)
m_usage = m_resourceManager.newInboundEndpoint (m_remoteAddress);
else
m_usage = m_resourceManager.newOutboundEndpoint (m_remoteAddress);
if (m_usage.disconnect ())
{
detach ("resource");
return;
}
if(!sendHello ())
{
m_journal.error << "Unable to send HELLO to " << m_remoteAddress;
detach ("hello");
return;
}
start_read();
}
void handleVerifyTimer (boost::system::error_code const& ec) void handleVerifyTimer (boost::system::error_code const& ec)
{ {
if (m_detaching) if (m_detaching)

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,6 @@
*/ */
//============================================================================== //==============================================================================
#ifndef RIPPLE_OVERLAY_MESSAGE_NAME_H_INCLUDED
#define RIPPLE_OVERLAY_MESSAGE_NAME_H_INCLUDED
namespace ripple { namespace ripple {
char const* char const*
@@ -49,5 +46,3 @@ protocol_message_name (int type)
} }
} }
#endif

View File

@@ -0,0 +1,36 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_OVERLAY_PEER_INFO_H_INCLUDED
#define RIPPLE_OVERLAY_PEER_INFO_H_INCLUDED
#include <beast/http/basic_message.h>
namespace ripple {
struct parsed_request
{
int version_major;
int version_minor;
};
}
#endif

View File

@@ -39,30 +39,35 @@ public:
*/ */
template <class ConstBufferSequence> template <class ConstBufferSequence>
boost::tribool boost::tribool
operator() (ConstBufferSequence const& buffers) operator() (ConstBufferSequence const& buffers);
{
std::array <std::uint8_t, 6> data;
auto const n (boost::asio::buffer_copy (
boost::asio::buffer(data), buffers));
/*
Protocol messages are framed by a 6 byte header which includes
a big-endian 4-byte length followed by a big-endian 2-byte type.
The type for 'hello' is 1.
*/
if (n>=1 && data[0] != 0)
return false;
if (n>=2 && data[1] != 0)
return false;
if (n>=5 && data[4] != 0)
return false;
if (n>=6 && data[5] != 1)
return false;
if (n>=6)
return true;
return boost::indeterminate;
}
}; };
template <class ConstBufferSequence>
boost::tribool
peer_protocol_detector::operator() (
ConstBufferSequence const& buffers)
{
std::array <std::uint8_t, 6> data;
auto const n (boost::asio::buffer_copy (
boost::asio::buffer(data), buffers));
/*
Protocol messages are framed by a 6 byte header which includes
a big-endian 4-byte length followed by a big-endian 2-byte type.
The type for 'hello' is 1.
*/
if (n>=1 && data[0] != 0)
return false;
if (n>=2 && data[1] != 0)
return false;
if (n>=5 && data[4] != 0)
return false;
if (n>=6 && data[5] != 1)
return false;
if (n>=6)
return true;
return boost::indeterminate;
}
} // ripple } // ripple
#endif #endif

View File

@@ -0,0 +1,68 @@
//------------------------------------------------------------------------------
/*
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 <ripple/overlay/impl/peer_info.h>
#include <beast/unit_test/suite.h>
#include <beast/http/rfc2616.h>
#include <algorithm>
#include <string>
#include <utility>
namespace ripple {
class peer_info_test : public beast::unit_test::suite
{
public:
// Parse a comma delimited list of strings
// Leading and trailing whitespace is removed from each element
static
std::vector <std::string>
parse_list (std::string const& value)
{
std::vector <std::string> list;
auto first (value.begin());
auto last (value.end());
for(;;)
{
auto const found (std::find (first, last, ','));
if (found != first)
{
auto p0 (first);
auto p1 (found - 1);
}
if (found == last)
break;
first = found + 1;
}
return list;
}
void
run()
{
expect (beast::http::rfc2616::trim("x") == "x");
}
};
BEAST_DEFINE_TESTSUITE(peer_info,overlay,ripple);
} // ripple

View File

@@ -25,3 +25,5 @@
#include <ripple/overlay/impl/PeerImp.cpp> #include <ripple/overlay/impl/PeerImp.cpp>
#include <ripple/overlay/impl/PeerDoor.cpp> #include <ripple/overlay/impl/PeerDoor.cpp>
#include <ripple/overlay/tests/peer_info.test.cpp>