Improve protocol-level handshaking protocol:

This commit restructures the HTTP based protocol negotiation that `rippled`
executes and introduces support for negotiation of compression for peer
links which, if implemented, should result in significant bandwidth savings
for some server roles.

This commit also introduces the new `[network_id]` configuration option
that administrators can use to specify which network the server is part of
and intends to join. This makes it possible for servers from different
networks to drop the link early.

The changeset also improves the log messages generated when negotiation
of a peer link upgrade fails. In the past, no useful information would
be logged, making it more difficult for admins to troubleshoot errors.

This commit also fixes RIPD-237 and RIPD-451
This commit is contained in:
Nik Bougalis
2019-06-22 17:02:13 -07:00
parent 3ea525430e
commit f6916bfd42
41 changed files with 1496 additions and 1581 deletions

View File

@@ -176,7 +176,6 @@ install (
install (
FILES
src/ripple/crypto/GenerateDeterministicKey.h
src/ripple/crypto/KeyType.h
src/ripple/crypto/RFC1751.h
src/ripple/crypto/csprng.h
DESTINATION include/ripple/crypto)
@@ -214,6 +213,7 @@ install (
src/ripple/protocol/Indexes.h
src/ripple/protocol/InnerObjectFormats.h
src/ripple/protocol/Issue.h
src/ripple/protocol/KeyType.h
src/ripple/protocol/Keylet.h
src/ripple/protocol/KnownFormats.h
src/ripple/protocol/LedgerFormats.h
@@ -602,12 +602,13 @@ else ()
#]===============================]
src/ripple/overlay/impl/Cluster.cpp
src/ripple/overlay/impl/ConnectAttempt.cpp
src/ripple/overlay/impl/Handshake.cpp
src/ripple/overlay/impl/Message.cpp
src/ripple/overlay/impl/OverlayImpl.cpp
src/ripple/overlay/impl/PeerImp.cpp
src/ripple/overlay/impl/PeerReservationTable.cpp
src/ripple/overlay/impl/PeerSet.cpp
src/ripple/overlay/impl/TMHello.cpp
src/ripple/overlay/impl/ProtocolVersion.cpp
src/ripple/overlay/impl/TrafficCount.cpp
#[===============================[
nounity, main sources:
@@ -920,7 +921,7 @@ else ()
nounity, test sources:
subdir: overlay
#]===============================]
src/test/overlay/TMHello_test.cpp
src/test/overlay/ProtocolVersion_test.cpp
src/test/overlay/cluster_test.cpp
src/test/overlay/short_read_test.cpp
#[===============================[
@@ -933,7 +934,6 @@ else ()
nounity, test sources:
subdir: protocol
#]===============================]
src/test/protocol/BuildInfo_test.cpp
src/test/protocol/IOUAmount_test.cpp
src/test/protocol/InnerObjectFormats_test.cpp
src/test/protocol/Issue_test.cpp

View File

@@ -733,6 +733,24 @@
# node is a validator.
#
#
#
# [network_id]
#
# Specify the network which this server is configured to connect to and
# track. If set, the server will not establish connections with servers
# that are explicitly configured to track another network.
#
# Network identifiers are usually unsigned integers in the range 0 to
# 4294967295 inclusive. The server also maps the following well-known
# names to the corresponding numerical identifier:
#
# main -> 0
# testnet -> 1
#
# If this value is not specified the server is not explicitly configured
# to track a particular network.
#
#
#-------------------------------------------------------------------------------
#
# 4. HTTPS Client

View File

@@ -24,6 +24,7 @@
#include <boost/asio/buffer.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <algorithm>
#include <array>
#include <cstdint>
#include <iterator>
#include <memory>
@@ -47,26 +48,9 @@ namespace ripple {
class Message : public std::enable_shared_from_this <Message>
{
public:
using pointer = std::shared_ptr<Message>;
public:
/** Number of bytes in a message header.
A message will not be processed unless a full header is received and
no messages smaller than this will be serialized.
*/
static std::size_t constexpr kHeaderBytes = 6;
/** The largest size that a message can be.
Sending a message whose size exceeds this may result in the connection
being dropped. A larger message size may be supported in the future or
negotiated as part of a protocol upgrade.
*/
static std::size_t constexpr kMaxMessageSize = 64 * 1024 * 1024;
Message (::google::protobuf::Message const& message, int type);
public:
/** Retrieve the packed message data. */
std::vector <uint8_t> const&
getBuffer () const
@@ -81,92 +65,8 @@ public:
return mCategory;
}
/** Determine bytewise equality. */
bool operator == (Message const& other) const;
/** Calculate the length of a packed message. */
/** @{ */
static unsigned getLength (std::vector <uint8_t> const& buf);
template <class FwdIter>
static
std::enable_if_t<std::is_same<typename
FwdIter::value_type, std::uint8_t>::value, std::size_t>
size (FwdIter first, FwdIter last)
{
if (std::distance(first, last) <
Message::kHeaderBytes)
return 0;
std::size_t n;
n = std::size_t{*first++} << 24;
n += std::size_t{*first++} << 16;
n += std::size_t{*first++} << 8;
n += std::size_t{*first};
return n;
}
template <class BufferSequence>
static
std::size_t
size (BufferSequence const& buffers)
{
return size(buffers_begin(buffers),
buffers_end(buffers));
}
/** @} */
/** Determine the type of a packed message. */
/** @{ */
static int getType (std::vector <uint8_t> const& buf);
template <class FwdIter>
static
std::enable_if_t<std::is_same<typename
FwdIter::value_type, std::uint8_t>::value, int>
type (FwdIter first, FwdIter last)
{
if (std::distance(first, last) <
Message::kHeaderBytes)
return 0;
return (int{*std::next(first, 4)} << 8) |
*std::next(first, 5);
}
template <class BufferSequence>
static
int
type (BufferSequence const& buffers)
{
return type(buffers_begin(buffers),
buffers_end(buffers));
}
/** @} */
private:
template <class BufferSequence, class Value = std::uint8_t>
static
boost::asio::buffers_iterator<BufferSequence, Value>
buffers_begin (BufferSequence const& buffers)
{
return boost::asio::buffers_iterator<
BufferSequence, Value>::begin (buffers);
}
template <class BufferSequence, class Value = std::uint8_t>
static
boost::asio::buffers_iterator<BufferSequence, Value>
buffers_end (BufferSequence const& buffers)
{
return boost::asio::buffers_iterator<
BufferSequence, Value>::end (buffers);
}
// Encodes the size and type into a header at the beginning of buf
//
void encodeHeader (unsigned size, int type);
std::vector <uint8_t> mBuffer;
std::size_t mCategory;
};

View File

@@ -32,6 +32,7 @@
#include <type_traits>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/optional.hpp>
#include <functional>
namespace boost { namespace asio { namespace ssl { class context; } } }
@@ -71,6 +72,7 @@ public:
beast::IP::Address public_ip;
int ipLimit = 0;
std::uint32_t crawlOptions = 0;
boost::optional<std::uint32_t> networkID;
};
using PeerSequence = std::vector <std::shared_ptr<Peer>>;

View File

@@ -56,7 +56,7 @@ public:
virtual
void
send (Message::pointer const& m) = 0;
send (std::shared_ptr<Message> const& m) = 0;
virtual
beast::IP::Endpoint
@@ -105,7 +105,6 @@ public:
virtual bool hasShard (std::uint32_t shardIndex) const = 0;
virtual bool hasTxSet (uint256 const& hash) const = 0;
virtual void cycleStatus () = 0;
virtual bool supportsVersion (int version) = 0;
virtual bool hasRange (std::uint32_t uMin, std::uint32_t uMax) = 0;
};

View File

@@ -2,17 +2,18 @@
## Introduction
The _Ripple payment network_ consists of a collection of _peers_ running
**rippled**. Each peer maintains multiple outgoing connections and optional
incoming connections to other peers. These connections are made over both
the public Internet and private local area networks. This network defines a
fully connected directed graph of nodes where vertices are instances of rippled
and edges are persistent TCP/IP connections. Peers send and receive messages to
other connected peers. This peer to peer network, layered on top of the public
and private Internet, forms an [_overlay network_][overlay_network]. The
contents of the messages and the behavior of peers in response to the messages,
plus the information exchanged during the handshaking phase of connection
establishment, defines the _Ripple peer protocol_ (_protocol_ in this context).
The _XRP Ledger network_ consists of a collection of _peers_ running
**`rippled`** or other compatible software. Each peer maintains multiple
outgoing connections and optional incoming connections to other peers.
These connections are made over both the public Internet and private local
area networks. This network defines a connected directed graph of nodes
where vertices are instances of `rippled` and edges are persistent TCP/IP
connections. Peers send and receive messages to other connected peers. This
peer to peer network, layered on top of the public and private Internet,
forms an [_overlay network_][overlay_network]. The contents of the messages
and the behavior of peers in response to the messages, plus the information
exchanged during the handshaking phase of connection establishment, defines
the _XRP Ledger peer protocol_ (or _protocol_ in this context).
## Overview
@@ -24,71 +25,69 @@ messages are exchanged between peers and serialized using
### 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.
affects the behavior of message routing. At present, only a single connection
type is supported: **Peer**.
## Handshake
To establish a protocol connection, a peer makes an outgoing TLS encrypted
connection to a remote peer, then sends a HTTP request with no message body.
The request uses the [_HTTP/1.1 Upgrade_][upgrade_header] mechanism with some
custom fields to communicate protocol specific information:
connection to a remote peer, then sends an HTTP request with no message body.
### HTTP
The HTTP [request](https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html) must:
- Use HTTP version 1.1.
- Specify a request URI consisting of a single forward slash character ("/")
indicating the server root. Requests using different URIs are reserved for
future protocol implementations.
- Use the [_HTTP/1.1 Upgrade_][upgrade_header] mechanism with additional custom
fields to communicate protocol specific information related to the upgrade.
HTTP requests which do not conform to this requirements must generate an
appropriate HTTP error and result in the connection being closed.
Upon receipt of a well-formed HTTP upgrade request, and validation of the
protocol specific parameters, a peer will either send back a HTTP 101 response
and switch to the requested protocol, or a message indicating that the request
failed (e.g. by sending HTTP 400 "Bad Request" or HTTP 503 "Service Unavailable").
##### Example HTTP Upgrade Request
```
GET / HTTP/1.1
User-Agent: rippled-0.27.0
Local-Address: 192.168.0.101:8421
Upgrade: RTXP/1.2, RTXP/1.3
User-Agent: rippled-1.4.0-b1+DEBUG
Upgrade: RTXP/1.2, XRPL/2.0
Connection: Upgrade
Connect-As: Leaf, Peer
Accept-Encoding: identity, zlib, snappy
Public-Key: aBRoQibi2jpDofohooFuzZi9nEzKw9Zdfc4ExVNmuXHaJpSPh8uJ
Session-Signature: 71ED064155FFADFA38782C5E0158CB26
Connect-As: Peer
Crawl: public
Network-ID: 1
Network-Time: 619234489
Public-Key: n94MvLTiHQJjByfGZzvQewTxQP2qjF6shQcuHwCjh5WoiozBrdpX
Session-Signature: MEUCIQCOO8tHOh/tgCSRNe6WwOwmIF6urZ5uSB8l9aAf5q7iRAIgA4aONKBZhpP5RuOuhJP2dP+2UIRioEJcfU4/m4gZdYo=
Remote-IP: 192.0.2.79
Closed-Ledger: llRZSKqvNieGpPqbFGnm358pmF1aW96SDIUQcnMh6HI=
Previous-Ledger: q4aKbP7sd5wv+EXArwCmQiWZhq9AwBl2p/hCtpGJNsc=
```
Upon receipt of a well-formed HTTP request the remote peer will send back a
HTTP response indicating the connection status:
##### Example HTTP Upgrade Response (Success)
```
HTTP/1.1 101 Switching Protocols
Server: rippled-0.27.0
Remote-Address: 63.104.209.13
Upgrade: RTXP/1.2
Connection: Upgrade
Connect-As: Leaf
Transfer-Encoding: snappy
Public-Key: aBRoQibi2jpDofohooFuzZi9nEzKw9Zdfc4ExVNmuXHaJpSPh8uJ
Session-Signature: 71ED064155FFADFA38782C5E0158CB26
Upgrade: RTXP/1.2
Connect-As: Peer
Server: rippled-1.3.1
Crawl: public
Public-Key: n9K1ZXXXzzA3dtgKBuQUnZXkhygMRgZbSo3diFNPVHLMsUG5osJM
Session-Signature: MEQCIHMlLGTcGyPvHji7WY2nRM2B0iSBnw9xeDUGW7bPq7IjAiAmy+ofEu+8nOq2eChRTr3wjoKi3EYRqLgzP+q+ORFcig==
Network-Time: 619234797
Closed-Ledger: h7HL85W9ywkex+G7p42USVeV5kE04CWK+4DVI19Of8I=
Previous-Ledger: EPvIpAD2iavGFyyZYi8REexAXyKGXsi1jMF7OIBY6/Y=
```
If the remote peer has no available slots, the HTTP status code 503 (Service
Unavailable) is returned, with an optional content body in JSON format that
may contain additional information such as IP and port addresses of other
servers that may have open slots:
##### Example HTTP Upgrade Response (Failure: no slots available)
```
HTTP/1.1 503 Service Unavailable
@@ -101,87 +100,252 @@ Content-Type: application/json
"85.127.34.221:51235","50.43.33.236:51235","54.187.138.75:51235"]}
```
### Fields
#### Standard Fields
* *URL*
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `User-Agent` | :heavy_check_mark: | |
The URL in the request line must be a single forward slash character
("/"). Requests with any other URL must be rejected. Different URL strings
are reserved for future protocol implementations.
The `User-Agent` field indicates the version of the software that the
peer that is making the HTTP request is using. No semantic meaning is
assigned to the value in this field but it is recommended that implementations
specify the version of the software that is used.
* *HTTP Version*
See [RFC2616 &sect;14.43](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43).
The minimum required HTTP version is 1.1. Requests for HTTP versions
earlier than 1.1 must be rejected.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Server` | | :heavy_check_mark: |
* `User-Agent`
The `Server` field indicates the version of the software that the
peer that is processing the HTTP request is using. No semantic meaning is
assigned to the value in this field but it is recommended that implementations
specify the version of the software that is used.
Contains information about the software originating the request.
The specification is identical to RFC2616 Section 14.43.
See [RFC2616 &sect;14.38](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.38).
* `Server`
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Connection` | :heavy_check_mark: | :heavy_check_mark: |
Contains information about the software providing the response. The
specification is identical to RFC2616 Section 14.38.
The `Connection` field should have a value of `Upgrade` to indicate that a
request to upgrade the connection is being performed.
* `Remote-Address` (optional)
See [RFC2616 &sect;14.10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10).
This optional field contains the string representation of the IP
address of the remote end of the connection as seen by the peer.
By observing values of this field from a sufficient number of different
servers, a peer making outgoing connections can deduce its own IP address.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Upgrade` | :heavy_check_mark: | :heavy_check_mark: |
* `Upgrade`
The `Upgrade` field is part of the standard connection upgrade mechanism and
must be present in both requests and responses. It is used to negotiate the
version of the protocol that will be used after the upgrade request completes.
This field must be present and for requests consist of a comma delimited
list of at least one element where each element is of the form "RTXP/"
followed by the dotted major and minor protocol version number. For
responses the value must be a single element matching one of the elements
provided in the corresponding request field. If the server does not
understand any of the requested protocols, the request is rejected.
For requests, it should consist of a comma delimited list of at least one
element, where each element specifies a protocol version that the requesting
server is willing to use.
* `Connection`
For responses, it should a consist of _single element_ matching one of the
elements provided in the corresponding request. If the server does not understand
any of the available protocol versions, the upgrade request should fail with an
appropriate HTTP error code (e.g. by sending an HTTP 400 "Bad Request" response).
This field must be present, containing the value 'Upgrade'.
Protocol versions are string of the form `XRPL/` followed by a dotted major
and minor protocol version number, where the major number is greater than or
equal to 2 and the minor is greater than or equal to 0. The legacy version
`RTXP/1.2` is also supported at this time and is an alias for `XRPL/2.0`.
* `Connect-As`
See [RFC 2616 &sect;14.42](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.42)
For requests the value consists of a comma delimited list of elements
where each element describes a possible connection type. Current connection
types are:
- leaf
- peer
#### Custom Fields
If this field is omitted or the value is the empty string, then 'leaf' is
assumed.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Connect-As` | :heavy_check_mark: | :heavy_check_mark: |
For responses, the value must consist of exactly one element from the list
of elements specified in the request. If a server does not recognize any
of the connection types it must return a HTTP error response.
The mandatory `Connect-As` field is used to specify that type of connection
that is being requested.
* `Public-Key`
For requests the value consists of a comma delimited list of elements, where
each element describes a possible connection type. Only one connection types
is supported at present: **`peer`**.
This field value must be present, and contain a base 64 encoded value used
as a server public key identifier.
For responses, the value must consist of exactly one element from the list of
elements specified in the request. If a server processing a request does not
recognize any of the connection types, the request should fail with an
appropriate HTTP error code (e.g. by sending an HTTP 400 "Bad Request" response).
* `Session-Signature`
This field must be present. It contains a cryptographic token formed
from the SHA512 hash of the shared data exchanged during SSL handshaking.
For more details see the corresponding source code.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Remote-IP` | :white_check_mark: | :white_check_mark: |
* `Crawl` (optional)
The optional `Remote-IP` field contains the string representation of the IP
address of the remote end of the connection as seen from the peer that is
sending the field.
If present, and the value is "public" then neighbors will report the IP
address to crawler requests. If absent, neighbor's default behavior is to
not report IP addresses.
By observing values of this field from a sufficient number of different
servers, a peer making outgoing connections can deduce its own IP address.
* _User Defined_ (Unimplemented)
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.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Local-IP` | :white_check_mark: | :white_check_mark: |
The optional `Local-IP` field contains the string representation of the IP
address that the peer sending the field believes to be its own.
Servers receiving this field can detect IP address mismatches, which may
indicate a potential man-in-the-middle attack.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Network-ID` | :white_check_mark: | :white_check_mark: |
The optional `Network-ID` can be used to identify to which of several
[parallel networks](https://xrpl.org/parallel-networks.html) the server
sending the field is joined.
The value, if the field is present, is a 32-bit unsigned integer. The
following well-known values are in use:
- **0**: The "main net"
- **1**: The Ripple-operated [Test Net](https://xrpl.org/xrp-test-net-faucet.html).
If a server configured to join one network receives a connection request from a
server configured to join another network, the request should fail with an
appropriate HTTP error code (e.g. by sending an HTTP 400 "Bad Request" response).
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Network-Time` | :white_check_mark: | :white_check_mark: |
The optional `Network-Time` field reports the current [time](https://xrpl.org/basic-data-types.html#specifying-time)
according to sender's internal clock.
Servers should fail a connection if their clocks are not within 20 seconds of
each other with an appropriate HTTP error code (e.g. by sending an HTTP 400
"Bad Request" response).
It is highly recommended that servers synchronize their clocks using time
synchronization software. For more on this topic, please visit [ntp.org](http://www.ntp.org/).
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Public-Key` | :heavy_check_mark: | :heavy_check_mark: |
The mandatory `Public-Key` field identifies the sending server's public key,
encoded in base58 using the standard encoding for node public keys.
See: https://xrpl.org/base58-encodings.html
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Session-Signature` | :heavy_check_mark: | :heavy_check_mark: |
The `Session-Signature` field is mandatory and is used to secure the peer link
against certain types of attack. For more details see "Session Signature" below.
The value is presently encoded using **Base64** encoding, but implementations
should support both **Base64** and **HEX** encoding for this value.
For more details on this field, please see **Session Signature** below.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Crawl` | :white_check_mark: | :white_check_mark: |
The optional `Crawl` field can be used by a server to indicate whether peers
should include it in crawl reports.
The field can take two values:
- **`Public`**: The server's IP address and port should be included in crawl
reports.
- **`Private`**: The server's IP address and port should not be included in
crawl reports. _This is the default, if the field is omitted._
For more on the Peer Crawler, please visit https://xrpl.org/peer-crawler.html.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Closed-Ledger` | :white_check_mark: | :white_check_mark: |
If present, identifies the hash of the last ledger that the sending server
considers to be closed.
The value is presently encoded using **Base64** encoding, but implementations
should support both **Base64** and **HEX** encoding for this value.
| Field Name | Request | Response |
|--------------------- |:-----------------: |:-----------------: |
| `Previous-Ledger` | :white_check_mark: | :white_check_mark: |
If present, identifies the hash of the parent ledger that the sending server
considers to be closed.
The value is presently encoded using **Base64** encoding, but implementations
should support both **Base64** and **HEX** encoding for this value.
#### Additional Headers
An implementation or operator may specify additional, optional fields
and values in both requests and responses.
Implementations should not reject requests because of the presence of fields
that they do not understand.
### Session Signature
Even for SSL/TLS encrypted connections, it is possible for an attacker to mount
relatively inexpensive MITM attacks that can be extremely hard to detect and
may afford the attacker the ability to intelligently tamper with messages
exchanged between the two endpoints.
This risk can be mitigated if at least one side has a certificate from a certificate
authority trusted by the other endpoint, but having a certificate is not always
possible (or even desirable) in a decentralized and permissionless network.
Ultimately, the goal is to ensure that two endpoints A and B know that they are
talking directly to each other over a single end-to-end SSL/TLS session instead
of two separate SSL/TLS sessions, with an attacker acting as a proxy.
The XRP Ledger protocol prevents this attack by leveraging the fact that the two
servers each have a node identity, in the form of **`secp256k1`** keypairs, and
use that to strongly bind the SSL/TLS session to the node identities of each of
the two servers at the end of the SSL/TLS session.
To do this we "reach into" the SSL/TLS session, and extract the **`finished`**
messages for the local and remote endpoints, and combine them to generate a unique
"fingerprint". By design, this fingerprint should be the same for both SSL/TLS
endpoints.
That fingerprint, which is never shared over the wire (since each endpoint will
calculate it independently), is then signed by each server using its public
**`secp256k1`** node identity and the signature is transferred over the SSL/TLS
encrypted link during the protocol handshake phase.
Each side of the link will verify that the provided signature is from the claimed
public key against the session's unique fingerprint. If this signature check fails
then the link **MUST** be dropped.
If an attacker, Eve, establishes two separate SSL sessions with Alice and Bob, the
fingerprints of the two sessions will be different, and Eve will not be able to
sign the fingerprint of her session with Bob with Alice's private key, or the
fingerprint of her session with Alice with Bob's private key, and so both A and
B will know that an active MITM attack is in progress and will close their
connections.
If Eve simply proxies the raw bytes, she will be unable to decrypt the data being
transferred between A and B and will not be able to intelligently tamper with the
message stream between Alice and Bob, although she may be still be able to inject
delays or terminate the link.
# Ripple Clustering #

View File

@@ -19,6 +19,7 @@
#include <ripple/overlay/impl/ConnectAttempt.h>
#include <ripple/overlay/impl/PeerImp.h>
#include <ripple/overlay/impl/ProtocolVersion.h>
#include <ripple/overlay/Cluster.h>
#include <ripple/json/json_reader.h>
@@ -27,7 +28,7 @@ namespace ripple {
ConnectAttempt::ConnectAttempt (Application& app, boost::asio::io_service& io_service,
endpoint_type const& remote_endpoint, Resource::Consumer usage,
beast::asio::ssl_bundle::shared_context const& context,
std::uint32_t id, PeerFinder::Slot::ptr const& slot,
std::uint32_t id, std::shared_ptr<PeerFinder::Slot> const& slot,
beast::Journal journal, OverlayImpl& overlay)
: Child (overlay)
, app_ (app)
@@ -89,32 +90,21 @@ ConnectAttempt::close()
error_code ec;
timer_.cancel(ec);
socket_.close(ec);
JLOG(journal_.debug()) <<
"Closed";
JLOG(journal_.debug()) << "Closed";
}
}
void
ConnectAttempt::fail (std::string const& reason)
{
assert(strand_.running_in_this_thread());
if (stream_.next_layer().is_open())
{
JLOG(journal_.debug()) <<
reason;
}
JLOG(journal_.debug()) << reason;
close();
}
void
ConnectAttempt::fail (std::string const& name, error_code ec)
{
assert(strand_.running_in_this_thread());
if (stream_.next_layer().is_open())
{
JLOG(journal_.debug()) <<
name << ": " << ec.message();
}
JLOG(journal_.debug()) << name << ": " << ec.message();
close();
}
@@ -203,19 +193,14 @@ ConnectAttempt::onHandshake (error_code ec)
beast::IPAddressConversion::from_asio (local_endpoint)))
return fail("Duplicate connection");
auto sharedValue = makeSharedValue(
stream_.native_handle(), journal_);
auto const sharedValue = makeSharedValue(*ssl_bundle_, journal_);
if (! sharedValue)
return close(); // makeSharedValue logs
req_ = makeRequest(! overlay_.peerFinder().config().peerPrivate,
remote_endpoint_.address());
auto const hello = buildHello (
*sharedValue,
overlay_.setup().public_ip,
beast::IPAddressConversion::from_asio(remote_endpoint_),
app_);
appendHello (req_, hello);
req_ = makeRequest(!overlay_.peerFinder().config().peerPrivate);
buildHandshake(req_, *sharedValue, overlay_.setup().networkID,
overlay_.setup().public_ip, remote_endpoint_.address(), app_);
setTimer();
boost::beast::http::async_write(stream_, req_,
@@ -279,17 +264,14 @@ ConnectAttempt::onShutdown (error_code ec)
//--------------------------------------------------------------------------
auto
ConnectAttempt::makeRequest (bool crawl,
boost::asio::ip::address const& remote_address) ->
request_type
ConnectAttempt::makeRequest (bool crawl) -> request_type
{
request_type m;
m.method(boost::beast::http::verb::get);
m.target("/");
m.version(11);
m.insert ("User-Agent", BuildInfo::getFullVersionString());
m.insert ("Upgrade", "RTXP/1.2");
//std::string("RTXP/") + to_string (BuildInfo::getCurrentProtocol()));
m.insert ("Upgrade", supportedProtocolVersions());
m.insert ("Connection", "Upgrade");
m.insert ("Connect-As", "Peer");
m.insert ("Crawl", crawl ? "public" : "private");
@@ -305,7 +287,7 @@ ConnectAttempt::processResponse()
Json::Reader r;
std::string s;
s.reserve(boost::asio::buffer_size(response_.body().data()));
for(auto const& buffer : response_.body().data())
for (auto const& buffer : response_.body().data())
s.append(
boost::asio::buffer_cast<char const*>(buffer),
boost::asio::buffer_size(buffer));
@@ -329,64 +311,70 @@ ConnectAttempt::processResponse()
eps.push_back(ep);
}
}
overlay_.peerFinder().onRedirects(
remote_endpoint_, eps);
overlay_.peerFinder().onRedirects(remote_endpoint_, eps);
}
}
}
}
if (! OverlayImpl::isPeerUpgrade(response_))
if (!OverlayImpl::isPeerUpgrade(response_))
{
JLOG(journal_.info()) <<
"HTTP Response: " << response_.result() << " " << response_.reason();
JLOG(journal_.info()) << "Unable to upgrade to peer protocol: " <<
response_.result() << " (" << response_.reason() << ")";
return close();
}
auto hello = parseHello (false, response_, journal_);
if(! hello)
return fail("processResponse: Bad TMHello");
// Just because our peer selected a particular protocol version doesn't
// mean that it's acceptable to us. Check that it is:
boost::optional<ProtocolVersion> negotiatedProtocol;
auto sharedValue = makeSharedValue(
ssl_bundle_->stream.native_handle(), journal_);
{
auto const pvs = parseProtocolVersions(response_["Upgrade"]);
if (pvs.size() == 1 && isProtocolSupported(pvs[0]))
negotiatedProtocol = pvs[0];
if (!negotiatedProtocol)
return fail("processResponse: Unable to negotiate protocol version");
}
auto const sharedValue = makeSharedValue(*ssl_bundle_, journal_);
if(! sharedValue)
return close(); // makeSharedValue logs
auto publicKey = verifyHello (*hello,
*sharedValue,
overlay_.setup().public_ip,
beast::IPAddressConversion::from_asio(remote_endpoint_),
journal_, app_);
if(! publicKey)
return close(); // verifyHello logs
JLOG(journal_.info()) <<
"Public Key: " << toBase58 (
TokenType::NodePublic,
*publicKey);
auto const protocol =
BuildInfo::make_protocol(hello->protoversion());
JLOG(journal_.info()) <<
"Protocol: " << to_string(protocol);
auto member = app_.cluster().member(*publicKey);
if (member)
try
{
auto publicKey = verifyHandshake(response_, *sharedValue,
overlay_.setup().networkID, overlay_.setup().public_ip,
remote_endpoint_.address(), app_);
JLOG(journal_.info()) <<
"Cluster name: " << *member;
"Public Key: " << toBase58(TokenType::NodePublic, publicKey);
JLOG(journal_.debug()) <<
"Protocol: " << to_string(*negotiatedProtocol);
auto const member = app_.cluster().member(publicKey);
if (member)
{
JLOG(journal_.info()) << "Cluster name: " << *member;
}
auto const result = overlay_.peerFinder().activate (slot_,
publicKey, static_cast<bool>(member));
if (result != PeerFinder::Result::success)
return fail("Outbound slots full");
auto const peer = std::make_shared<PeerImp>(app_, std::move(ssl_bundle_),
read_buf_.data(), std::move(slot_), std::move(response_), usage_,
publicKey, *negotiatedProtocol, id_, overlay_);
overlay_.add_active (peer);
}
catch (std::exception const& e)
{
return fail(std::string("Handshake failure (")+ e.what() + ")");
}
auto const result = overlay_.peerFinder().activate (slot_,
*publicKey, static_cast<bool>(member));
if (result != PeerFinder::Result::success)
return fail("Outbound slots full");
auto const peer = std::make_shared<PeerImp>(app_,
std::move(ssl_bundle_), read_buf_.data(),
std::move(slot_), std::move(response_),
usage_, *hello, *publicKey, id_, overlay_);
overlay_.add_active (peer);
}
} // ripple

View File

@@ -20,7 +20,6 @@
#ifndef RIPPLE_OVERLAY_CONNECTATTEMPT_H_INCLUDED
#define RIPPLE_OVERLAY_CONNECTATTEMPT_H_INCLUDED
#include <ripple/protocol/messages.h>
#include <ripple/overlay/impl/OverlayImpl.h>
#include <ripple/overlay/impl/Tuning.h>
@@ -55,14 +54,14 @@ private:
beast::asio::ssl_bundle::stream_type& stream_;
boost::beast::multi_buffer read_buf_;
response_type response_;
PeerFinder::Slot::ptr slot_;
std::shared_ptr<PeerFinder::Slot> slot_;
request_type req_;
public:
ConnectAttempt (Application& app, boost::asio::io_service& io_service,
endpoint_type const& remote_endpoint, Resource::Consumer usage,
beast::asio::ssl_bundle::shared_context const& context,
std::uint32_t id, PeerFinder::Slot::ptr const& slot,
std::uint32_t id, std::shared_ptr<PeerFinder::Slot> const& slot,
beast::Journal journal, OverlayImpl& overlay);
~ConnectAttempt();
@@ -89,8 +88,7 @@ private:
static
request_type
makeRequest (bool crawl,
boost::asio::ip::address const& remote_address);
makeRequest (bool crawl);
void processResponse();

View File

@@ -0,0 +1,287 @@
//------------------------------------------------------------------------------
/*
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/Handshake.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/main/Application.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/safe_cast.h>
#include <ripple/beast/rfc2616.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/protocol/digest.h>
#include <boost/regex.hpp>
#include <algorithm>
#include <chrono>
// VFALCO Shouldn't we have to include the OpenSSL
// headers or something for SSL_get_finished?
namespace ripple {
/** Hashes the latest finished message from an SSL stream.
@param ssl the session to get the message from.
@param get a pointer to the function to call to retrieve the finished
message. This can be either:
- `SSL_get_finished` or
- `SSL_get_peer_finished`.
@return `true` if successful, `false` otherwise.
@note This construct is non-standard. There are potential "standard"
alternatives that should be considered. For a discussion, on
this topic, see https://github.com/openssl/openssl/issues/5509 and
https://github.com/ripple/rippled/issues/2413.
*/
static
boost::optional<base_uint<512>>
hashLastMessage (SSL const* ssl,
size_t (*get)(const SSL *, void *, size_t))
{
constexpr std::size_t sslMinimumFinishedLength = 12;
unsigned char buf[1024];
size_t len = get(ssl, buf, sizeof(buf));
if(len < sslMinimumFinishedLength)
return boost::none;
sha512_hasher h;
base_uint<512> cookie;
SHA512 (buf, len, cookie.data());
return cookie;
}
boost::optional<uint256>
makeSharedValue (beast::asio::ssl_bundle& ssl, beast::Journal journal)
{
auto const cookie1 = hashLastMessage(
ssl.stream.native_handle(), SSL_get_finished);
if (!cookie1)
{
JLOG (journal.error()) << "Cookie generation: local setup not complete";
return boost::none;
}
auto const cookie2 = hashLastMessage(
ssl.stream.native_handle(), SSL_get_peer_finished);
if (!cookie2)
{
JLOG (journal.error()) << "Cookie generation: peer setup not complete";
return boost::none;
}
auto const result = (*cookie1 ^ *cookie2);
// Both messages hash to the same value and the cookie
// is 0. Don't allow this.
if (result == beast::zero)
{
JLOG(journal.error()) << "Cookie generation: identical finished messages";
return boost::none;
}
return sha512Half (Slice (result.data(), result.size()));
}
void
buildHandshake(
boost::beast::http::fields& h,
ripple::uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote_ip,
Application& app)
{
if (networkID)
{
// The network identifier, if configured, can be used to specify
// what network we intend to connect to and detect if the remote
// end connects to the same network.
h.insert("Network-ID", std::to_string(*networkID));
}
h.insert ("Network-Time",
std::to_string(app.timeKeeper().now().time_since_epoch().count()));
h.insert ("Public-Key",
toBase58(TokenType::NodePublic, app.nodeIdentity().first));
{
auto const sig = signDigest(app.nodeIdentity().first,
app.nodeIdentity().second, sharedValue);
h.insert("Session-Signature",
base64_encode(sig.data(), sig.size()));
}
if (beast::IP::is_public (remote_ip))
h.insert ("Remote-IP", remote_ip.to_string());
if (!public_ip.is_unspecified())
h.insert ("Local-IP", public_ip.to_string());
if (auto const cl = app.getLedgerMaster().getClosedLedger())
{
// TODO: Use hex for these
h.insert ("Closed-Ledger", base64_encode(
cl->info().hash.begin(), cl->info().hash.size()));
h.insert ("Previous-Ledger", base64_encode(
cl->info().parentHash.begin(), cl->info().parentHash.size()));
}
}
PublicKey
verifyHandshake(
boost::beast::http::fields const& headers,
ripple::uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote,
Application& app)
{
if (networkID)
{
if (auto const iter = headers.find("Network-ID"); iter != headers.end())
{
std::uint32_t nid;
if (!beast::lexicalCastChecked(nid, iter->value().to_string()))
throw std::runtime_error("Invalid peer network identifier");
if(nid != *networkID)
throw std::runtime_error("Peer is on a different network");
}
}
if (auto const iter = headers.find("Network-Time"); iter != headers.end())
{
auto const netTime =
[str = iter->value().to_string()]() -> TimeKeeper::time_point
{
TimeKeeper::duration::rep val;
if (beast::lexicalCastChecked(val, str))
return TimeKeeper::time_point{TimeKeeper::duration{val}};
// It's not an error for the header field to not be present but if
// it is present and it contains junk data, that is an error.
throw std::runtime_error("Invalid peer clock timestamp");
}();
using namespace std::chrono;
auto const ourTime = app.timeKeeper().now();
auto const tolerance = 20s;
// We can't blindly "return a-b;" because TimeKeeper::time_point
// uses an unsigned integer for representing durations, which is
// a problem when trying to subtract time points.
// FIXME: @HowardHinnant, should we migrate to using std::int64_t?
auto calculateOffset = [](
TimeKeeper::time_point a, TimeKeeper::time_point b)
{
if (a > b)
return duration_cast<std::chrono::seconds>(a - b);
return - duration_cast<std::chrono::seconds>(b - a);
};
auto const offset = calculateOffset(netTime, ourTime);
if (date::abs(offset) > tolerance)
throw std::runtime_error("Peer clock is too far off");
}
PublicKey const publicKey = [&headers]
{
if (auto const iter = headers.find ("Public-Key"); iter != headers.end())
{
auto pk = parseBase58<PublicKey>(
TokenType::NodePublic, iter->value().to_string());
if(pk)
{
if (publicKeyType(*pk) != KeyType::secp256k1)
throw std::runtime_error("Unsupported public key type");
return *pk;
}
}
throw std::runtime_error("Bad node public key");
}();
if (publicKey == app.nodeIdentity().first)
throw std::runtime_error("Self connection");
// This check gets two birds with one stone:
//
// 1) it verifies that the node we are talking to has access to the
// private key corresponding to the public node identity it claims.
// 2) it verifies that our SSL session is end-to-end with that node
// and not through a proxy that establishes two separate sessions.
{
auto const iter = headers.find("Session-Signature");
if (iter == headers.end())
throw std::runtime_error("No session signature specified");
auto sig = base64_decode(iter->value().to_string());
if (! verifyDigest(publicKey, sharedValue, makeSlice(sig), false))
throw std::runtime_error("Failed to verify session");
}
if (auto const iter = headers.find ("Local-IP"); iter != headers.end())
{
boost::system::error_code ec;
auto const local_ip = boost::asio::ip::address::from_string(
iter->value().to_string(), ec);
if (ec)
throw std::runtime_error("Invalid Local-IP");
if (beast::IP::is_public(remote) && remote != local_ip)
throw std::runtime_error("Incorrect Local-IP: " +
remote.to_string() + " instead of " + local_ip.to_string());
}
if (auto const iter = headers.find("Remote-IP"); iter != headers.end())
{
boost::system::error_code ec;
auto const remote_ip = boost::asio::ip::address::from_string(
iter->value().to_string(), ec);
if (ec)
throw std::runtime_error("Invalid Remote-IP");
if (beast::IP::is_public(remote) && !beast::IP::is_unspecified(public_ip))
{
// We know our public IP and peer reports our connection came
// from some other IP.
if (remote_ip != public_ip)
throw std::runtime_error("Incorrect Remote-IP: " +
public_ip.to_string() + " instead of " + remote_ip.to_string());
}
}
return publicKey;
}
}

View File

@@ -0,0 +1,78 @@
//------------------------------------------------------------------------------
/*
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_HANDSHAKE_H_INCLUDED
#define RIPPLE_OVERLAY_HANDSHAKE_H_INCLUDED
#include <ripple/app/main/Application.h>
#include <ripple/beast/asio/ssl_bundle.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/protocol/BuildInfo.h>
#include <boost/beast/http/fields.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/optional.hpp>
#include <utility>
namespace ripple {
/** Computes a shared value based on the SSL connection state.
When there is no man in the middle, both sides will compute the same
value. In the presence of an attacker, the computed values will be
different.
@param ssl the SSL/TLS connection state.
@return A 256-bit value on success; an unseated optional otherwise.
*/
boost::optional<uint256>
makeSharedValue (beast::asio::ssl_bundle& ssl, beast::Journal journal);
/** Insert fields headers necessary for upgrading the link to the peer protocol. */
void
buildHandshake(
boost::beast::http::fields& h,
uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote_ip,
Application& app);
/** Validate header fields necessary for upgrading the link to the peer protocol.
This performs critical security checks that ensure that prevent
MITM attacks on our peer-to-peer links and that the remote peer
has the private keys that correspond to the public identity it
claims.
@return The public key of the remote peer.
@throw A class derived from std::exception.
*/
PublicKey
verifyHandshake(
boost::beast::http::fields const& headers,
uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote,
Application& app);
}
#endif

View File

@@ -25,70 +25,28 @@
namespace ripple {
Message::Message (::google::protobuf::Message const& message, int type)
: mCategory(TrafficCount::categorize(message, type, false))
{
unsigned const messageBytes = message.ByteSize ();
assert (messageBytes != 0);
mBuffer.resize (kHeaderBytes + messageBytes);
/** Number of bytes in a message header. */
std::size_t constexpr headerBytes = 6;
encodeHeader (messageBytes, type);
mBuffer.resize (headerBytes + messageBytes);
auto ptr = mBuffer.data();
*ptr++ = static_cast<std::uint8_t>((messageBytes >> 24) & 0xFF);
*ptr++ = static_cast<std::uint8_t>((messageBytes >> 16) & 0xFF);
*ptr++ = static_cast<std::uint8_t>((messageBytes >> 8) & 0xFF);
*ptr++ = static_cast<std::uint8_t>(messageBytes & 0xFF);
*ptr++ = static_cast<std::uint8_t>((type >> 8) & 0xFF);
*ptr++ = static_cast<std::uint8_t> (type & 0xFF);
if (messageBytes != 0)
{
message.SerializeToArray (&mBuffer [Message::kHeaderBytes], messageBytes);
}
mCategory = TrafficCount::categorize(message, type, false);
}
bool Message::operator== (Message const& other) const
{
return mBuffer == other.mBuffer;
}
unsigned Message::getLength (std::vector <uint8_t> const& buf)
{
unsigned result;
if (buf.size () >= Message::kHeaderBytes)
{
result = buf [0];
result <<= 8;
result |= buf [1];
result <<= 8;
result |= buf [2];
result <<= 8;
result |= buf [3];
}
else
{
result = 0;
}
return result;
}
int Message::getType (std::vector<uint8_t> const& buf)
{
if (buf.size () < Message::kHeaderBytes)
return 0;
int ret = buf[4];
ret <<= 8;
ret |= buf[5];
return ret;
}
void Message::encodeHeader (unsigned size, int type)
{
assert (mBuffer.size () >= Message::kHeaderBytes);
mBuffer[0] = static_cast<std::uint8_t> ((size >> 24) & 0xFF);
mBuffer[1] = static_cast<std::uint8_t> ((size >> 16) & 0xFF);
mBuffer[2] = static_cast<std::uint8_t> ((size >> 8) & 0xFF);
mBuffer[3] = static_cast<std::uint8_t> (size & 0xFF);
mBuffer[4] = static_cast<std::uint8_t> ((type >> 8) & 0xFF);
mBuffer[5] = static_cast<std::uint8_t> (type & 0xFF);
message.SerializeToArray(ptr, messageBytes);
}
}

View File

@@ -238,88 +238,84 @@ OverlayImpl::onHandoff (std::unique_ptr <beast::asio::ssl_bundle>&& ssl_bundle,
}
}
auto hello = parseHello (true, request, journal);
if(! hello)
auto const negotiatedVersion = negotiateProtocolVersion(request["Upgrade"]);
if (!negotiatedVersion)
{
m_peerFinder->on_closed(slot);
handoff.moved = false;
handoff.response = makeErrorResponse (slot, request,
remote_endpoint.address(),
"Unable to parse HELLO message");
remote_endpoint.address(), "Unable to agree on a protocol version");
handoff.keep_alive = false;
return handoff;
}
auto sharedValue = makeSharedValue(
ssl_bundle->stream.native_handle(), journal);
auto const sharedValue = makeSharedValue(*ssl_bundle, journal);
if(! sharedValue)
{
m_peerFinder->on_closed(slot);
handoff.moved = false;
handoff.response = makeErrorResponse (slot, request,
remote_endpoint.address(),
"Incorrect security cookie (possible MITM detected)");
remote_endpoint.address(), "Incorrect security cookie");
handoff.keep_alive = false;
return handoff;
}
auto publicKey = verifyHello (*hello,
*sharedValue,
setup_.public_ip,
beast::IPAddressConversion::from_asio(
remote_endpoint), journal, app_);
if(! publicKey)
try
{
auto publicKey = verifyHandshake(request, *sharedValue,
setup_.networkID, setup_.public_ip, remote_endpoint.address(), app_);
{
// The node gets a reserved slot if it is in our cluster
// or if it has a reservation.
bool const reserved =
static_cast<bool>(app_.cluster().member(publicKey))
|| app_.peerReservations().contains(publicKey);
auto const result = m_peerFinder->activate(slot, publicKey, reserved);
if (result != PeerFinder::Result::success)
{
m_peerFinder->on_closed(slot);
JLOG(journal.debug()) << "Peer " << remote_endpoint << " redirected, slots full";
handoff.moved = false;
handoff.response = makeRedirectResponse(slot, request,
remote_endpoint.address());
handoff.keep_alive = false;
return handoff;
}
}
auto const peer = std::make_shared<PeerImp>(app_, id, slot,
std::move(request), publicKey, *negotiatedVersion, consumer,
std::move(ssl_bundle), *this);
{
// As we are not on the strand, run() must be called
// while holding the lock, otherwise new I/O can be
// queued after a call to stop().
std::lock_guard <decltype(mutex_)> lock (mutex_);
{
auto const result = m_peers.emplace (peer->slot(), peer);
assert (result.second);
(void) result.second;
}
list_.emplace(peer.get(), peer);
peer->run();
}
handoff.moved = true;
return handoff;
}
catch (std::exception const& e)
{
JLOG(journal.debug()) << "Peer " << remote_endpoint <<
" fails handshake (" << e.what() << ")";
m_peerFinder->on_closed(slot);
handoff.moved = false;
handoff.response = makeErrorResponse (slot, request,
remote_endpoint.address(),
"Unable to verify HELLO message");
remote_endpoint.address(), e.what());
handoff.keep_alive = false;
return handoff;
}
{
// The node gets a reserved slot if it is in our cluster
// or if it has a reservation.
bool const reserved {
static_cast<bool>(app_.cluster().member(*publicKey))
|| app_.peerReservations().contains(*publicKey)
};
auto const result = m_peerFinder->activate(slot, *publicKey, reserved);
if (result != PeerFinder::Result::success)
{
m_peerFinder->on_closed(slot);
JLOG(journal.debug())
<< "Peer " << remote_endpoint << " redirected, slots full";
handoff.moved = false;
handoff.response = makeRedirectResponse(
slot, request, remote_endpoint.address());
handoff.keep_alive = beast::rfc2616::is_keep_alive(request);
return handoff;
}
}
auto const peer = std::make_shared<PeerImp>(app_, id,
remote_endpoint, slot, std::move(request), *hello,
*publicKey, consumer, std::move(ssl_bundle), *this);
{
// As we are not on the strand, run() must be called
// while holding the lock, otherwise new I/O can be
// queued after a call to stop().
std::lock_guard lock (mutex_);
{
auto const result =
m_peers.emplace (peer->slot(), peer);
assert (result.second);
(void) result.second;
}
list_.emplace(peer.get(), peer);
peer->run();
}
handoff.moved = true;
return handoff;
}
//------------------------------------------------------------------------------
@@ -329,11 +325,8 @@ OverlayImpl::isPeerUpgrade(http_request_type const& request)
{
if (! is_upgrade(request))
return false;
auto const versions = parse_ProtocolVersions(
request["Upgrade"]);
if (versions.size() == 0)
return false;
return true;
auto const versions = parseProtocolVersions(request["Upgrade"]);
return !versions.empty();
}
std::string
@@ -345,7 +338,7 @@ OverlayImpl::makePrefix (std::uint32_t id)
}
std::shared_ptr<Writer>
OverlayImpl::makeRedirectResponse (PeerFinder::Slot::ptr const& slot,
OverlayImpl::makeRedirectResponse (std::shared_ptr<PeerFinder::Slot> const& slot,
http_request_type const& request, address_type remote_address)
{
boost::beast::http::response<json_body> msg;
@@ -367,18 +360,18 @@ OverlayImpl::makeRedirectResponse (PeerFinder::Slot::ptr const& slot,
}
std::shared_ptr<Writer>
OverlayImpl::makeErrorResponse (PeerFinder::Slot::ptr const& slot,
OverlayImpl::makeErrorResponse (std::shared_ptr<PeerFinder::Slot> const& slot,
http_request_type const& request,
address_type remote_address,
std::string text)
{
boost::beast::http::response<boost::beast::http::string_body> msg;
boost::beast::http::response<boost::beast::http::empty_body> msg;
msg.version(request.version());
msg.result(boost::beast::http::status::bad_request);
msg.reason("Bad Request (" + text + ")");
msg.insert("Server", BuildInfo::getFullVersionString());
msg.insert("Remote-Address", remote_address.to_string());
msg.insert(boost::beast::http::field::connection, "close");
msg.body() = text;
msg.prepare_payload();
return std::make_shared<SimpleWriter>(msg);
}
@@ -454,7 +447,7 @@ OverlayImpl::add_active (std::shared_ptr<PeerImp> const& peer)
}
void
OverlayImpl::remove (PeerFinder::Slot::ptr const& slot)
OverlayImpl::remove (std::shared_ptr<PeerFinder::Slot> const& slot)
{
std::lock_guard lock (mutex_);
auto const iter = m_peers.find (slot);
@@ -1275,6 +1268,7 @@ setup_Overlay (BasicConfig const& config)
Throw<std::runtime_error>("Configured public IP is invalid");
}
}
{
auto const& section = config.section("crawl");
auto const& values = section.values();
@@ -1322,6 +1316,28 @@ setup_Overlay (BasicConfig const& config)
}
}
try
{
auto id = config.legacy("network_id");
if (!id.empty())
{
if (id == "main")
id = "0";
if (id == "testnet")
id = "1";
setup.networkID = beast::lexicalCastThrow<std::uint32_t>(id);
}
}
catch (...)
{
Throw<std::runtime_error>(
"Configured [network_id] section is invalid: "
"must be a number or one of the strings 'main' or 'testnet'");
}
return setup;
}

View File

@@ -29,7 +29,7 @@
#include <ripple/basics/Resolver.h>
#include <ripple/basics/chrono.h>
#include <ripple/basics/UnorderedContainers.h>
#include <ripple/overlay/impl/TMHello.h>
#include <ripple/overlay/impl/Handshake.h>
#include <ripple/peerfinder/PeerfinderManager.h>
#include <ripple/resource/ResourceManager.h>
#include <boost/asio/ip/tcp.hpp>
@@ -37,6 +37,7 @@
#include <boost/asio/strand.hpp>
#include <boost/asio/basic_waitable_timer.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/optional.hpp>
#include <atomic>
#include <cassert>
#include <chrono>
@@ -111,7 +112,7 @@ private:
Resource::Manager& m_resourceManager;
std::unique_ptr <PeerFinder::Manager> m_peerFinder;
TrafficCount m_traffic;
hash_map <PeerFinder::Slot::ptr,
hash_map <std::shared_ptr<PeerFinder::Slot>,
std::weak_ptr <PeerImp>> m_peers;
hash_map<Peer::id_t, std::weak_ptr<PeerImp>> ids_;
Resolver& m_resolver;
@@ -128,6 +129,8 @@ private:
// Peer IDs expecting to receive a last link notification
std::set<std::uint32_t> csIDs_;
boost::optional<std::uint32_t> networkID_;
//--------------------------------------------------------------------------
public:
@@ -220,7 +223,7 @@ public:
add_active (std::shared_ptr<PeerImp> const& peer);
void
remove (PeerFinder::Slot::ptr const& slot);
remove (std::shared_ptr<PeerFinder::Slot> const& slot);
/** Called when a peer has connected successfully
This is called after the peer handshake has been completed and during
@@ -281,13 +284,7 @@ public:
{
if (! is_upgrade(response))
return false;
if(response.result() != boost::beast::http::status::switching_protocols)
return false;
auto const versions = parse_ProtocolVersions(
response["Upgrade"]);
if (versions.size() == 0)
return false;
return true;
return response.result() == boost::beast::http::status::switching_protocols;
}
template<class Fields>
@@ -375,11 +372,11 @@ public:
private:
std::shared_ptr<Writer>
makeRedirectResponse (PeerFinder::Slot::ptr const& slot,
makeRedirectResponse (std::shared_ptr<PeerFinder::Slot> const& slot,
http_request_type const& request, address_type remote_address);
std::shared_ptr<Writer>
makeErrorResponse (PeerFinder::Slot::ptr const& slot,
makeErrorResponse (std::shared_ptr<PeerFinder::Slot> const& slot,
http_request_type const& request, address_type remote_address,
std::string msg);

View File

@@ -29,6 +29,7 @@
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/random.h>
#include <ripple/basics/safe_cast.h>
#include <ripple/basics/UptimeClock.h>
@@ -53,10 +54,10 @@ using namespace std::chrono_literals;
namespace ripple {
PeerImp::PeerImp (Application& app, id_t id, endpoint_type remote_endpoint,
PeerFinder::Slot::ptr const& slot, http_request_type&& request,
protocol::TMHello const& hello, PublicKey const& publicKey,
Resource::Consumer consumer,
PeerImp::PeerImp (Application& app, id_t id,
std::shared_ptr<PeerFinder::Slot> const& slot, http_request_type&& request,
PublicKey const& publicKey,
ProtocolVersion protocol, Resource::Consumer consumer,
std::unique_ptr<beast::asio::ssl_bundle>&& ssl_bundle,
OverlayImpl& overlay)
: Child (overlay)
@@ -71,16 +72,15 @@ PeerImp::PeerImp (Application& app, id_t id, endpoint_type remote_endpoint,
, stream_ (ssl_bundle_->stream)
, strand_ (socket_.get_executor())
, timer_ (beast::create_waitable_timer<waitable_timer>(socket_))
, remote_address_ (
beast::IPAddressConversion::from_asio(remote_endpoint))
, remote_address_ (slot->remote_endpoint())
, overlay_ (overlay)
, m_inbound (true)
, protocol_ (protocol)
, state_ (State::active)
, sanity_ (Sanity::unknown)
, insaneTime_ (clock_type::now())
, publicKey_(publicKey)
, creationTime_ (clock_type::now())
, hello_(hello)
, usage_(consumer)
, fee_ (Resource::feeLightPeer)
, slot_ (slot)
@@ -116,8 +116,51 @@ void
PeerImp::run()
{
if(! strand_.running_in_this_thread())
return post(strand_, std::bind (
&PeerImp::run, shared_from_this()));
return post(strand_, std::bind(&PeerImp::run, shared_from_this()));
// We need to decipher
auto parseLedgerHash = [](std::string const& value) -> boost::optional<uint256>
{
uint256 ret;
if (ret.SetHexExact(value))
return { ret };
auto const s = base64_decode(value);
if (s.size() != uint256::size())
return boost::none;
return uint256{ s };
};
boost::optional<uint256> closed;
boost::optional<uint256> previous;
if (auto const iter = headers_.find("Closed-Ledger"); iter != headers_.end())
{
closed = parseLedgerHash(iter->value().to_string());
if (!closed)
fail("Malformed handshake data (1)");
}
if (auto const iter = headers_.find("Previous-Ledger"); iter != headers_.end())
{
previous = parseLedgerHash(iter->value().to_string());
if (!previous)
fail("Malformed handshake data (2)");
}
if (previous && !closed)
fail("Malformed handshake data (3)");
{
std::lock_guard<std::mutex> sl(recentLock_);
if (closed)
closedLedgerHash_ = *closed;
if (previous)
previousLedgerHash_ = *previous;
}
if (m_inbound)
{
doAccept();
@@ -127,27 +170,6 @@ PeerImp::run()
assert (state_ == State::active);
// XXX Set timer: connection is in grace period to be useful.
// XXX Set timer: connection idle (idle may vary depending on connection type.)
if (hello_.has_ledgerclosed() &&
stringIsUint256Sized (hello_.ledgerclosed()))
{
// Operations on closedLedgerHash_ and previousLedgerHash_ must be
// guarded by recentLock_.
std::lock_guard sl(recentLock_);
closedLedgerHash_ = hello_.ledgerclosed();
if (hello_.has_ledgerprevious() &&
stringIsUint256Sized (hello_.ledgerprevious()))
{
previousLedgerHash_ = hello_.ledgerprevious();
addLedger (previousLedgerHash_, sl);
}
else
{
previousLedgerHash_.zero();
}
}
doProtocolStart();
}
@@ -186,7 +208,7 @@ PeerImp::stop()
//------------------------------------------------------------------------------
void
PeerImp::send (Message::pointer const& m)
PeerImp::send (std::shared_ptr<Message> const& m)
{
if (! strand_.running_in_this_thread())
return post(strand_, std::bind(&PeerImp::send, shared_from_this(), m));
@@ -266,10 +288,9 @@ PeerImp::cluster() const
std::string
PeerImp::getVersion() const
{
if (hello_.has_fullversion ())
return hello_.fullversion ();
return std::string ();
if (m_inbound)
return headers_["User-Agent"].to_string();
return headers_["Server"].to_string();
}
Json::Value
@@ -295,17 +316,14 @@ PeerImp::json()
ret[jss::load] = usage_.balance ();
if (hello_.has_fullversion ())
ret[jss::version] = hello_.fullversion ();
if (hello_.has_protoversion ())
{
auto protocol = BuildInfo::make_protocol (hello_.protoversion ());
if (protocol != BuildInfo::getCurrentProtocol())
ret[jss::protocol] = to_string (protocol);
auto const version = getVersion();
if (!version.empty())
ret[jss::version] = version;
}
ret[jss::protocol] = to_string (protocol_);
{
std::lock_guard sl (recentLock_);
if (latency_)
@@ -444,12 +462,6 @@ PeerImp::cycleStatus ()
closedLedgerHash_.zero ();
}
bool
PeerImp::supportsVersion (int version)
{
return hello_.has_protoversion () && (hello_.protoversion () >= version);
}
bool
PeerImp::hasRange (std::uint32_t uMin, std::uint32_t uMax)
{
@@ -682,12 +694,11 @@ PeerImp::onShutdown(error_code ec)
void PeerImp::doAccept()
{
assert(read_buffer_.size() == 0);
// assert(request_.upgrade);
JLOG(journal_.debug()) << "doAccept: " << remote_address_;
auto sharedValue = makeSharedValue(
ssl_bundle_->stream.native_handle(), journal_);
auto const sharedValue = makeSharedValue(*ssl_bundle_, journal_);
// This shouldn't fail since we already computed
// the shared value successfully in OverlayImpl
if(! sharedValue)
@@ -697,14 +708,13 @@ void PeerImp::doAccept()
boost::beast::ostream(write_buffer_) << makeResponse(
! overlay_.peerFinder().config().peerPrivate,
request_, remote_address_, *sharedValue);
request_, remote_address_.address(), *sharedValue);
JLOG(journal_.info()) << "Protocol: " <<
to_string(protocol_);
JLOG(journal_.info()) << "Public Key: " <<
toBase58 (TokenType::NodePublic, publicKey_);
auto const protocol = BuildInfo::make_protocol(hello_.protoversion());
JLOG(journal_.info()) << "Protocol: " << to_string(protocol);
JLOG(journal_.info()) <<
"Public Key: " << toBase58 (
TokenType::NodePublic,
publicKey_);
if (auto member = app_.cluster().member(publicKey_))
{
{
@@ -718,26 +728,6 @@ void PeerImp::doAccept()
// XXX Set timer: connection is in grace period to be useful.
// XXX Set timer: connection idle (idle may vary depending on connection type.)
if (hello_.has_ledgerclosed() &&
stringIsUint256Sized (hello_.ledgerclosed()))
{
// Operations on closedLedgerHash_ and previousLedgerHash_ must be
// guarded by recentLock_.
std::lock_guard sl(recentLock_);
closedLedgerHash_ = hello_.ledgerclosed();
if (hello_.has_ledgerprevious() &&
stringIsUint256Sized (hello_.ledgerprevious()))
{
previousLedgerHash_ = hello_.ledgerprevious();
addLedger (previousLedgerHash_, sl);
}
else
{
previousLedgerHash_.zero();
}
}
onWriteResponse(error_code(), 0);
}
@@ -745,20 +735,21 @@ void PeerImp::doAccept()
http_response_type
PeerImp::makeResponse (bool crawl,
http_request_type const& req,
beast::IP::Endpoint remote,
beast::IP::Address remote_ip,
uint256 const& sharedValue)
{
http_response_type resp;
resp.result(boost::beast::http::status::switching_protocols);
resp.version(req.version());
resp.insert("Connection", "Upgrade");
resp.insert("Upgrade", "RTXP/1.2");
resp.insert("Upgrade", to_string(protocol_));
resp.insert("Connect-As", "Peer");
resp.insert("Server", BuildInfo::getFullVersionString());
resp.insert("Crawl", crawl ? "public" : "private");
protocol::TMHello hello = buildHello(sharedValue,
overlay_.setup().public_ip, remote, app_);
appendHello(resp, hello);
buildHandshake(resp, sharedValue, overlay_.setup().networkID,
overlay_.setup().public_ip, remote_ip, app_);
return resp;
}
@@ -940,15 +931,13 @@ PeerImp::onWriteMessage (error_code ec, std::size_t bytes_transferred)
//
//------------------------------------------------------------------------------
PeerImp::error_code
void
PeerImp::onMessageUnknown (std::uint16_t type)
{
error_code ec;
// TODO
return ec;
}
PeerImp::error_code
void
PeerImp::onMessageBegin (std::uint16_t type,
std::shared_ptr <::google::protobuf::Message> const& m,
std::size_t size)
@@ -958,7 +947,6 @@ PeerImp::onMessageBegin (std::uint16_t type,
fee_ = Resource::feeLightPeer;
overlay_.reportTraffic (TrafficCount::categorize (*m, type, true),
true, static_cast<int>(size));
return error_code{};
}
void
@@ -969,12 +957,6 @@ PeerImp::onMessageEnd (std::uint16_t,
charge (fee_);
}
void
PeerImp::onMessage (std::shared_ptr <protocol::TMHello> const& m)
{
fail("Deprecated TMHello");
}
void
PeerImp::onMessage (std::shared_ptr<protocol::TMManifests> const& m)
{
@@ -1366,20 +1348,6 @@ PeerImp::onMessage(std::shared_ptr <protocol::TMPeerShardInfo> const& m)
overlay_.lastLink(id_);
}
void
PeerImp::onMessage (std::shared_ptr <protocol::TMGetPeers> const& m)
{
// This message is obsolete due to PeerFinder and
// we no longer provide a response to it.
}
void
PeerImp::onMessage (std::shared_ptr <protocol::TMPeers> const& m)
{
// This message is obsolete due to PeerFinder and
// we no longer process it.
}
void
PeerImp::onMessage (std::shared_ptr <protocol::TMEndpoints> const& m)
{
@@ -2701,7 +2669,7 @@ PeerImp::getLedger (std::shared_ptr<protocol::TMGetLedger> const& m)
}
}
Message::pointer oPacket = std::make_shared<Message> (
auto oPacket = std::make_shared<Message> (
reply, protocol::mtLEDGER_DATA);
send (oPacket);
return;
@@ -2806,7 +2774,7 @@ PeerImp::getLedger (std::shared_ptr<protocol::TMGetLedger> const& m)
"Got request for " << packet.nodeids().size() << " nodes at depth " <<
depth << ", return " << reply.nodes().size() << " nodes";
Message::pointer oPacket = std::make_shared<Message> (
auto oPacket = std::make_shared<Message> (
reply, protocol::mtLEDGER_DATA);
send (oPacket);
}

View File

@@ -26,6 +26,7 @@
#include <ripple/beast/asio/waitable_timer.h>
#include <ripple/beast/utility/WrappedSink.h>
#include <ripple/overlay/impl/ProtocolMessage.h>
#include <ripple/overlay/impl/ProtocolVersion.h>
#include <ripple/overlay/impl/OverlayImpl.h>
#include <ripple/peerfinder/PeerfinderManager.h>
#include <ripple/protocol/Protocol.h>
@@ -99,9 +100,6 @@ private:
using endpoint_type = boost::asio::ip::tcp::endpoint;
using waitable_timer = boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
// The length of the smallest valid finished message
static const size_t sslMinimumFinishedLength = 12;
Application& app_;
id_t const id_;
beast::WrappedSink sink_;
@@ -124,6 +122,10 @@ private:
//
OverlayImpl& overlay_;
bool const m_inbound;
// Protocol version to use for this link
ProtocolVersion protocol_;
State state_; // Current state
std::atomic<Sanity> sanity_;
clock_type::time_point insaneTime_;
@@ -179,16 +181,15 @@ private:
std::mutex mutable recentLock_;
protocol::TMStatusChange last_status_;
protocol::TMHello const hello_;
Resource::Consumer usage_;
Resource::Charge fee_;
PeerFinder::Slot::ptr const slot_;
std::shared_ptr<PeerFinder::Slot> const slot_;
boost::beast::multi_buffer read_buffer_;
http_request_type request_;
http_response_type response_;
boost::beast::http::fields const& headers_;
boost::beast::multi_buffer write_buffer_;
std::queue<Message::pointer> send_queue_;
std::queue<std::shared_ptr<Message>> send_queue_;
bool gracefulClose_ = false;
int large_sendq_ = 0;
int no_ping_ = 0;
@@ -230,10 +231,10 @@ public:
PeerImp& operator= (PeerImp const&) = delete;
/** Create an active incoming peer from an established ssl connection. */
PeerImp (Application& app, id_t id, endpoint_type remote_endpoint,
PeerFinder::Slot::ptr const& slot, http_request_type&& request,
protocol::TMHello const& hello, PublicKey const& publicKey,
Resource::Consumer consumer,
PeerImp (Application& app, id_t id,
std::shared_ptr<PeerFinder::Slot> const& slot, http_request_type&& request,
PublicKey const& publicKey,
ProtocolVersion protocol, Resource::Consumer consumer,
std::unique_ptr<beast::asio::ssl_bundle>&& ssl_bundle,
OverlayImpl& overlay);
@@ -241,11 +242,10 @@ public:
// VFALCO legacyPublicKey should be implied by the Slot
template <class Buffers>
PeerImp (Application& app, std::unique_ptr<beast::asio::ssl_bundle>&& ssl_bundle,
Buffers const& buffers, PeerFinder::Slot::ptr&& slot,
Buffers const& buffers, std::shared_ptr<PeerFinder::Slot>&& slot,
http_response_type&& response, Resource::Consumer usage,
protocol::TMHello const& hello,
PublicKey const& publicKey, id_t id,
OverlayImpl& overlay);
PublicKey const& publicKey,
ProtocolVersion protocol, id_t id, OverlayImpl& overlay);
virtual
~PeerImp();
@@ -256,7 +256,7 @@ public:
return p_journal_;
}
PeerFinder::Slot::ptr const&
std::shared_ptr<PeerFinder::Slot> const&
slot()
{
return slot_;
@@ -275,7 +275,7 @@ public:
//
void
send (Message::pointer const& m) override;
send (std::shared_ptr<Message> const& m) override;
/** Send a set of PeerFinder endpoints as a protocol message. */
template <class FwdIt, class = typename std::enable_if_t<std::is_same<
@@ -367,9 +367,6 @@ public:
void
cycleStatus () override;
bool
supportsVersion (int version) override;
bool
hasRange (std::uint32_t uMin, std::uint32_t uMax) override;
@@ -424,7 +421,7 @@ private:
http_response_type
makeResponse (bool crawl, http_request_type const& req,
beast::IP::Endpoint remoteAddress,
beast::IP::Address remote_ip,
uint256 const& sharedValue);
void
@@ -465,10 +462,10 @@ public:
boost::system::errc::invalid_argument);
}
error_code
void
onMessageUnknown (std::uint16_t type);
error_code
void
onMessageBegin (std::uint16_t type,
std::shared_ptr <::google::protobuf::Message> const& m,
std::size_t size);
@@ -477,7 +474,6 @@ public:
onMessageEnd (std::uint16_t type,
std::shared_ptr <::google::protobuf::Message> const& m);
void onMessage (std::shared_ptr <protocol::TMHello> const& m);
void onMessage (std::shared_ptr <protocol::TMManifests> const& m);
void onMessage (std::shared_ptr <protocol::TMPing> const& m);
void onMessage (std::shared_ptr <protocol::TMCluster> const& m);
@@ -485,8 +481,6 @@ public:
void onMessage (std::shared_ptr <protocol::TMShardInfo> const& m);
void onMessage (std::shared_ptr <protocol::TMGetPeerShardInfo> const& m);
void onMessage (std::shared_ptr <protocol::TMPeerShardInfo> const& m);
void onMessage (std::shared_ptr <protocol::TMGetPeers> const& m);
void onMessage (std::shared_ptr <protocol::TMPeers> const& m);
void onMessage (std::shared_ptr <protocol::TMEndpoints> const& m);
void onMessage (std::shared_ptr <protocol::TMTransaction> const& m);
void onMessage (std::shared_ptr <protocol::TMGetLedger> const& m);
@@ -509,6 +503,8 @@ private:
}
//--------------------------------------------------------------------------
void
setLedgerState();
// lockedRecentLock is passed as a reminder to callers that recentLock_
// must be locked.
@@ -547,11 +543,10 @@ private:
template <class Buffers>
PeerImp::PeerImp (Application& app, std::unique_ptr<beast::asio::ssl_bundle>&& ssl_bundle,
Buffers const& buffers, PeerFinder::Slot::ptr&& slot,
Buffers const& buffers, std::shared_ptr<PeerFinder::Slot>&& slot,
http_response_type&& response, Resource::Consumer usage,
protocol::TMHello const& hello,
PublicKey const& publicKey, id_t id,
OverlayImpl& overlay)
PublicKey const& publicKey,
ProtocolVersion protocol, id_t id, OverlayImpl& overlay)
: Child (overlay)
, app_ (app)
, id_ (id)
@@ -567,12 +562,12 @@ PeerImp::PeerImp (Application& app, std::unique_ptr<beast::asio::ssl_bundle>&& s
, remote_address_ (slot->remote_endpoint())
, overlay_ (overlay)
, m_inbound (false)
, protocol_ (protocol)
, state_ (State::active)
, sanity_ (Sanity::unknown)
, insaneTime_ (clock_type::now())
, publicKey_ (publicKey)
, creationTime_ (clock_type::now())
, hello_ (hello)
, usage_ (usage)
, fee_ (Resource::feeLightPeer)
, slot_ (std::move(slot))

View File

@@ -125,8 +125,7 @@ void PeerSet::sendRequest (const protocol::TMGetLedger& tmGL)
if (mPeers.empty ())
return;
Message::pointer packet (
std::make_shared<Message> (tmGL, protocol::mtGET_LEDGER));
auto packet = std::make_shared<Message>(tmGL, protocol::mtGET_LEDGER);
for (auto id : mPeers)
{

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_OVERLAY_PROTOCOLMESSAGE_H_INCLUDED
#define RIPPLE_OVERLAY_PROTOCOLMESSAGE_H_INCLUDED
#include <ripple/basics/ByteUtilities.h>
#include <ripple/protocol/messages.h>
#include <ripple/overlay/Message.h>
#include <ripple/overlay/impl/ZeroCopyStream.h>
@@ -41,17 +42,13 @@ protocolMessageName (int type)
{
switch (type)
{
case protocol::mtHELLO: return "hello";
case protocol::mtMANIFESTS: return "manifests";
case protocol::mtPING: return "ping";
case protocol::mtPROOFOFWORK: return "proof_of_work";
case protocol::mtCLUSTER: return "cluster";
case protocol::mtGET_SHARD_INFO: return "get_shard_info";
case protocol::mtSHARD_INFO: return "shard_info";
case protocol::mtGET_PEER_SHARD_INFO: return "get_peer_shard_info";
case protocol::mtPEER_SHARD_INFO: return "peer_shard_info";
case protocol::mtGET_PEERS: return "get_peers";
case protocol::mtPEERS: return "peers";
case protocol::mtENDPOINTS: return "endpoints";
case protocol::mtTRANSACTION: return "tx";
case protocol::mtGET_LEDGER: return "get_ledger";
@@ -63,33 +60,83 @@ protocolMessageName (int type)
case protocol::mtGET_OBJECTS: return "get_objects";
default:
break;
};
}
return "unknown";
}
namespace detail {
template <class T, class Buffers, class Handler>
std::enable_if_t<std::is_base_of<
::google::protobuf::Message, T>::value,
boost::system::error_code>
invoke (int type, Buffers const& buffers,
struct MessageHeader
{
/** The size of the message on the wire.
@note This is the sum of sizes of the header and the payload.
*/
std::uint32_t total_wire_size = 0;
/** The size of the header associated with this message. */
std::uint32_t header_size = 0;
/** The size of the payload on the wire. */
std::uint32_t payload_wire_size = 0;
/** The type of the message. */
std::uint16_t message_type = 0;
};
template <class BufferSequence>
boost::optional<MessageHeader> parseMessageHeader(
BufferSequence const& bufs,
std::size_t size)
{
auto iter = boost::asio::buffers_iterator<BufferSequence, std::uint8_t>::begin(bufs);
MessageHeader hdr;
// Version 1 header: uncompressed payload.
// The top six bits of the first byte are 0.
if ((*iter & 0xFC) == 0)
{
hdr.header_size = 6;
if (size < hdr.header_size)
return {};
for (int i = 0; i != 4; ++i)
hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++;
hdr.total_wire_size = hdr.header_size + hdr.payload_wire_size;
for (int i = 0; i != 2; ++i)
hdr.message_type = (hdr.message_type << 8) + *iter++;
return hdr;
}
return {};
}
template <class T, class Buffers, class Handler,
class = std::enable_if_t<std::is_base_of<::google::protobuf::Message, T>::value>>
bool
invoke (
MessageHeader const& header,
Buffers const& buffers,
Handler& handler)
{
auto const m = std::make_shared<T>();
ZeroCopyInputStream<Buffers> stream(buffers);
stream.Skip(Message::kHeaderBytes);
auto const m (std::make_shared<T>());
stream.Skip(header.header_size);
if (! m->ParseFromZeroCopyStream(&stream))
return boost::system::errc::make_error_code(
boost::system::errc::invalid_argument);
auto ec = handler.onMessageBegin (type, m,
Message::kHeaderBytes + Message::size (buffers));
if (! ec)
{
handler.onMessage (m);
handler.onMessageEnd (type, m);
}
return ec;
return false;
handler.onMessageBegin (header.message_type, m, header.payload_wire_size);
handler.onMessage (m);
handler.onMessageEnd (header.message_type, m);
return true;
}
}
@@ -106,82 +153,100 @@ std::pair <std::size_t, boost::system::error_code>
invokeProtocolMessage (Buffers const& buffers, Handler& handler)
{
std::pair<std::size_t,boost::system::error_code> result = { 0, {} };
boost::system::error_code& ec = result.second;
auto const bs = boost::asio::buffer_size(buffers);
auto const size = boost::asio::buffer_size(buffers);
// If we don't even have enough bytes for the header, there's no point
// in doing any work.
if (bs < Message::kHeaderBytes)
if (size == 0)
return result;
if (bs > Message::kMaxMessageSize)
auto header = detail::parseMessageHeader(buffers, size);
// If we can't parse the header then it may be that we don't have enough
// bytes yet, or because the message was cut off.
if (!header)
return result;
// We implement a maximum size for protocol messages. Sending a message
// whose size exceeds this may result in the connection being dropped. A
// larger message size may be supported in the future or negotiated as
// part of a protocol upgrade.
if (header->payload_wire_size > megabytes(64))
{
result.second = make_error_code(boost::system::errc::message_size);
return result;
}
auto const size = Message::kHeaderBytes + Message::size(buffers);
if (bs < size)
// We don't have the whole message yet. This isn't an error but we have
// nothing to do.
if (header->total_wire_size > size)
return result;
auto const type = Message::type(buffers);
bool success;
switch (type)
switch (header->message_type)
{
case protocol::mtHELLO: ec = detail::invoke<protocol::TMHello> (type, buffers, handler); break;
case protocol::mtMANIFESTS: ec = detail::invoke<protocol::TMManifests> (type, buffers, handler); break;
case protocol::mtPING: ec = detail::invoke<protocol::TMPing> (type, buffers, handler); break;
case protocol::mtCLUSTER: ec = detail::invoke<protocol::TMCluster> (type, buffers, handler); break;
case protocol::mtGET_SHARD_INFO: ec = detail::invoke<protocol::TMGetShardInfo> (type, buffers, handler); break;
case protocol::mtSHARD_INFO: ec = detail::invoke<protocol::TMShardInfo>(type, buffers, handler); break;
case protocol::mtGET_PEER_SHARD_INFO: ec = detail::invoke<protocol::TMGetPeerShardInfo> (type, buffers, handler); break;
case protocol::mtPEER_SHARD_INFO: ec = detail::invoke<protocol::TMPeerShardInfo>(type, buffers, handler); break;
case protocol::mtGET_PEERS: ec = detail::invoke<protocol::TMGetPeers> (type, buffers, handler); break;
case protocol::mtPEERS: ec = detail::invoke<protocol::TMPeers> (type, buffers, handler); break;
case protocol::mtENDPOINTS: ec = detail::invoke<protocol::TMEndpoints> (type, buffers, handler); break;
case protocol::mtTRANSACTION: ec = detail::invoke<protocol::TMTransaction> (type, buffers, handler); break;
case protocol::mtGET_LEDGER: ec = detail::invoke<protocol::TMGetLedger> (type, buffers, handler); break;
case protocol::mtLEDGER_DATA: ec = detail::invoke<protocol::TMLedgerData> (type, buffers, handler); break;
case protocol::mtPROPOSE_LEDGER: ec = detail::invoke<protocol::TMProposeSet> (type, buffers, handler); break;
case protocol::mtSTATUS_CHANGE: ec = detail::invoke<protocol::TMStatusChange> (type, buffers, handler); break;
case protocol::mtHAVE_SET: ec = detail::invoke<protocol::TMHaveTransactionSet> (type, buffers, handler); break;
case protocol::mtVALIDATION: ec = detail::invoke<protocol::TMValidation> (type, buffers, handler); break;
case protocol::mtGET_OBJECTS: ec = detail::invoke<protocol::TMGetObjectByHash> (type, buffers, handler); break;
case protocol::mtMANIFESTS:
success = detail::invoke<protocol::TMManifests>(*header, buffers, handler);
break;
case protocol::mtPING:
success = detail::invoke<protocol::TMPing>(*header, buffers, handler);
break;
case protocol::mtCLUSTER:
success = detail::invoke<protocol::TMCluster>(*header, buffers, handler);
break;
case protocol::mtGET_SHARD_INFO:
success = detail::invoke<protocol::TMGetShardInfo>(*header, buffers, handler);
break;
case protocol::mtSHARD_INFO:
success = detail::invoke<protocol::TMShardInfo>(*header, buffers, handler);
break;
case protocol::mtGET_PEER_SHARD_INFO:
success = detail::invoke<protocol::TMGetPeerShardInfo>(*header, buffers, handler);
break;
case protocol::mtPEER_SHARD_INFO:
success = detail::invoke<protocol::TMPeerShardInfo>(*header, buffers, handler);
break;
case protocol::mtENDPOINTS:
success = detail::invoke<protocol::TMEndpoints>(*header, buffers, handler);
break;
case protocol::mtTRANSACTION:
success = detail::invoke<protocol::TMTransaction>(*header, buffers, handler);
break;
case protocol::mtGET_LEDGER:
success = detail::invoke<protocol::TMGetLedger>(*header, buffers, handler);
break;
case protocol::mtLEDGER_DATA:
success = detail::invoke<protocol::TMLedgerData>(*header, buffers, handler);
break;
case protocol::mtPROPOSE_LEDGER:
success = detail::invoke<protocol::TMProposeSet>(*header, buffers, handler);
break;
case protocol::mtSTATUS_CHANGE:
success = detail::invoke<protocol::TMStatusChange>(*header, buffers, handler);
break;
case protocol::mtHAVE_SET:
success = detail::invoke<protocol::TMHaveTransactionSet>(*header, buffers, handler);
break;
case protocol::mtVALIDATION:
success = detail::invoke<protocol::TMValidation>(*header, buffers, handler);
break;
case protocol::mtGET_OBJECTS:
success = detail::invoke<protocol::TMGetObjectByHash>(*header, buffers, handler);
break;
default:
ec = handler.onMessageUnknown (type);
handler.onMessageUnknown (header->message_type);
success = true;
break;
}
if (! ec)
result.first = size;
result.first = header->total_wire_size;
if (!success)
result.second = make_error_code(boost::system::errc::bad_message);
return result;
}
/** Write a protocol message to a streambuf. */
template <class Streambuf>
void
write (Streambuf& streambuf,
::google::protobuf::Message const& m, int type,
std::size_t blockBytes)
{
auto const size = m.ByteSize();
std::array<std::uint8_t, 6> v;
v[0] = static_cast<std::uint8_t>((size >> 24) & 0xFF);
v[1] = static_cast<std::uint8_t>((size >> 16) & 0xFF);
v[2] = static_cast<std::uint8_t>((size >> 8) & 0xFF);
v[3] = static_cast<std::uint8_t>( size & 0xFF);
v[4] = static_cast<std::uint8_t>((type >> 8) & 0xFF);
v[5] = static_cast<std::uint8_t>( type & 0xFF);
streambuf.commit(boost::asio::buffer_copy(
streambuf.prepare(Message::kHeaderBytes),
boost::asio::buffer(v)));
ZeroCopyOutputStream<Streambuf> stream (
streambuf, blockBytes);
m.SerializeToZeroCopyStream(&stream);
}
} // ripple
#endif

View File

@@ -0,0 +1,184 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2019 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/ProtocolVersion.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/beast/rfc2616.h>
#include <boost/function_output_iterator.hpp>
#include <boost/regex.hpp>
#include <algorithm>
#include <functional>
namespace ripple {
/** The list of protocol versions we speak and we prefer to use.
@note The list must be sorted in strictly ascending order (and so
it may not contain any duplicates!)
*/
constexpr
ProtocolVersion const supportedProtocolList[]
{
{ 1, 2 },
{ 2, 0 }
};
// This ugly construct ensures that supportedProtocolList is sorted in strictly
// ascending order and doesn't contain any duplicates.
// FIXME: With C++20 we can use std::is_sorted with an appropriate comparator
static_assert(
[]() constexpr -> bool
{
auto const len = std::distance(
std::begin(supportedProtocolList),
std::end(supportedProtocolList));
// There should be at least one protocol we're willing to speak.
if (len == 0)
return false;
// A list with only one entry is, by definition, sorted so we don't
// need to check it.
if (len != 1)
{
for (auto i = 0; i != len - 1; ++i)
{
if (supportedProtocolList[i] >= supportedProtocolList[i+1])
return false;
}
}
return true;
}(), "The list of supported protocols isn't properly sorted.");
std::string
to_string(ProtocolVersion const& p)
{
// The legacy protocol uses a different name. This can be removed when we
// migrate away from it and require 2.0 or later.
if (p == ProtocolVersion{ 1, 2 })
return "RTXP/1.2";
return "XRPL/" + std::to_string(p.first) + "." + std::to_string(p.second);
}
std::vector<ProtocolVersion>
parseProtocolVersions(boost::beast::string_view const& value)
{
static boost::regex re(
"^" // start of line
"XRPL/" // The string "XRPL/"
"([2-9]|(?:[1-9][0-9]+))" // a number (greater than 2 with no leading zeroes)
"\\." // a period
"(0|(?:[1-9][0-9]*))" // a number (no leading zeroes unless exactly zero)
"$" // The end of the string
, boost::regex_constants::optimize);
std::vector<ProtocolVersion> result;
for (auto const& s : beast::rfc2616::split_commas(value))
{
if (s == "RTXP/1.2")
{
result.push_back(make_protocol(1, 2));
continue;
}
boost::smatch m;
if (boost::regex_match(s, m, re))
{
std::uint16_t major;
std::uint16_t minor;
if (!beast::lexicalCastChecked(major, std::string(m[1])))
continue;
if (!beast::lexicalCastChecked(minor, std::string(m[2])))
continue;
auto const proto = make_protocol(major, minor);
// This is an extra sanity check: we check that the protocol we just
// decoded corresponds to the token we were parsing.
if (to_string(proto) == s)
result.push_back(make_protocol(major, minor));
}
}
// We guarantee that the returned list is sorted and contains no duplicates:
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
return result;
}
boost::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions)
{
auto const them = parseProtocolVersions(versions);
boost::optional<ProtocolVersion> result;
// The protocol version we want to negotiate is the largest item in the
// intersection of the versions supported by us and the peer. Since the
// output of std::set_intersection is sorted, that item is always going
// to be the last one. So we get a little clever and avoid the need for
// a container:
std::function<void(ProtocolVersion const&)> pickVersion =
[&result](ProtocolVersion const& v)
{
result = v;
};
std::set_intersection(
std::begin(them), std::end(them),
std::begin(supportedProtocolList), std::end(supportedProtocolList),
boost::make_function_output_iterator(pickVersion));
return result;
}
std::string const&
supportedProtocolVersions()
{
static std::string const supported = []()
{
std::string ret;
for (auto const& v : supportedProtocolList)
{
if (!ret.empty())
ret += ", ";
ret += to_string(v);
}
return ret;
}();
return supported;
}
bool
isProtocolSupported(ProtocolVersion const& v)
{
return std::end(supportedProtocolList) != std::find(
std::begin(supportedProtocolList), std::end(supportedProtocolList), v);
}
}

View File

@@ -0,0 +1,79 @@
//------------------------------------------------------------------------------
/*
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_PROTOCOLVERSION_H_INCLUDED
#define RIPPLE_OVERLAY_PROTOCOLVERSION_H_INCLUDED
#include <boost/beast/core/string.hpp>
#include <boost/optional.hpp>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
namespace ripple {
/** Represents a particular version of the peer-to-peer protocol.
The protocol is represented as two pairs of 16-bit integers; a major
and a minor.
* */
using ProtocolVersion = std::pair<std::uint16_t, std::uint16_t>;
inline
ProtocolVersion
make_protocol(std::uint16_t major, std::uint16_t minor)
{
return { major, minor };
}
/** Print a protocol version a human-readable string. */
std::string
to_string(ProtocolVersion const& p);
/** Parse a set of protocol versions.
Given a comma-separated string, extract and return all those that look
like valid protocol versions (i.e. RTXP/1.2 and XRPL/2.0 and later). Any
strings that are not parseable as valid protocol strings are excluded
from the result set.
@return A list of all apparently valid protocol versions.
@note The returned list of protocol versions is guaranteed to contain
no duplicates and will be sorted in ascending protocol order.
*/
std::vector<ProtocolVersion>
parseProtocolVersions(boost::beast::string_view const& s);
/** Given a list of supported protocol versions, choose the one we prefer. */
boost::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions);
/** The list of all the protocol versions we support. */
std::string const&
supportedProtocolVersions();
/** Determine whether we support a specific protocol version. */
bool
isProtocolSupported(ProtocolVersion const& v);
}
#endif

View File

@@ -1,462 +0,0 @@
//------------------------------------------------------------------------------
/*
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/TMHello.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/main/Application.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/safe_cast.h>
#include <ripple/beast/rfc2616.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/protocol/digest.h>
#include <boost/regex.hpp>
#include <algorithm>
// VFALCO Shouldn't we have to include the OpenSSL
// headers or something for SSL_get_finished?
namespace ripple {
/** Hashes the latest finished message from an SSL stream
@param sslSession the session to get the message from.
@param hash the buffer into which the hash of the retrieved
message will be saved. The buffer MUST be at least
64 bytes long.
@param getMessage a pointer to the function to call to retrieve the
finished message. This be either:
`SSL_get_finished` or
`SSL_get_peer_finished`.
@return `true` if successful, `false` otherwise.
*/
static
boost::optional<base_uint<512>>
hashLastMessage (SSL const* ssl,
size_t (*get)(const SSL *, void *buf, size_t))
{
enum
{
sslMinimumFinishedLength = 12
};
unsigned char buf[1024];
size_t len = get (ssl, buf, sizeof (buf));
if(len < sslMinimumFinishedLength)
return boost::none;
base_uint<512> cookie;
SHA512 (buf, len, cookie.data());
return cookie;
}
boost::optional<uint256>
makeSharedValue (SSL* ssl, beast::Journal journal)
{
auto const cookie1 = hashLastMessage(ssl, SSL_get_finished);
if (!cookie1)
{
JLOG (journal.error()) << "Cookie generation: local setup not complete";
return boost::none;
}
auto const cookie2 = hashLastMessage(ssl, SSL_get_peer_finished);
if (!cookie2)
{
JLOG (journal.error()) << "Cookie generation: peer setup not complete";
return boost::none;
}
auto const result = (*cookie1 ^ *cookie2);
// Both messages hash to the same value and the cookie
// is 0. Don't allow this.
if (result == beast::zero)
{
JLOG(journal.error()) << "Cookie generation: identical finished messages";
return boost::none;
}
return sha512Half (Slice (result.data(), result.size()));
}
protocol::TMHello
buildHello (
uint256 const& sharedValue,
beast::IP::Address public_ip,
beast::IP::Endpoint remote,
Application& app)
{
protocol::TMHello h;
auto const sig = signDigest (
app.nodeIdentity().first,
app.nodeIdentity().second,
sharedValue);
h.set_protoversion (to_packed (BuildInfo::getCurrentProtocol()));
h.set_protoversionmin (to_packed (BuildInfo::getMinimumProtocol()));
h.set_fullversion (BuildInfo::getFullVersionString ());
h.set_nettime (app.timeKeeper().now().time_since_epoch().count());
h.set_nodepublic (
toBase58 (
TokenType::NodePublic,
app.nodeIdentity().first));
h.set_nodeproof (sig.data(), sig.size());
h.set_testnet (false);
if (beast::IP::is_public (remote))
{
// Connection is to a public IP
h.set_remote_ip_str (remote.to_string());
if (! public_ip.is_unspecified())
h.set_local_ip_str (public_ip.to_string());
}
// We always advertise ourselves as private in the HELLO message. This
// suppresses the old peer advertising code and allows PeerFinder to
// take over the functionality.
h.set_nodeprivate (true);
auto const closedLedger = app.getLedgerMaster().getClosedLedger();
assert(! closedLedger->open());
// VFALCO There should ALWAYS be a closed ledger
if (closedLedger)
{
uint256 hash = closedLedger->info().hash;
h.set_ledgerclosed (hash.begin (), hash.size ());
hash = closedLedger->info().parentHash;
h.set_ledgerprevious (hash.begin (), hash.size ());
}
return h;
}
void
appendHello (boost::beast::http::fields& h,
protocol::TMHello const& hello)
{
//h.append ("Protocol-Versions",...
h.insert ("Public-Key", hello.nodepublic());
h.insert ("Session-Signature", base64_encode (
hello.nodeproof()));
if (hello.has_nettime())
h.insert ("Network-Time", std::to_string (hello.nettime()));
if (hello.has_ledgerindex())
h.insert ("Ledger", std::to_string (hello.ledgerindex()));
if (hello.has_ledgerclosed())
h.insert ("Closed-Ledger", base64_encode (
hello.ledgerclosed()));
if (hello.has_ledgerprevious())
h.insert ("Previous-Ledger", base64_encode (
hello.ledgerprevious()));
if (hello.has_local_ip_str())
h.insert ("Local-IP", hello.local_ip_str());
if (hello.has_remote_ip())
h.insert ("Remote-IP", hello.remote_ip_str());
}
std::vector<ProtocolVersion>
parse_ProtocolVersions(boost::beast::string_view const& value)
{
static boost::regex re (
"^" // start of line
"RTXP/" // the string "RTXP/"
"([1-9][0-9]*)" // a number (non-zero and with no leading zeroes)
"\\." // a period
"(0|[1-9][0-9]*)" // a number (no leading zeroes unless exactly zero)
"$" // The end of the string
, boost::regex_constants::optimize);
auto const list = beast::rfc2616::split_commas(value);
std::vector<ProtocolVersion> result;
for (auto const& s : list)
{
boost::smatch m;
if (! boost::regex_match (s, m, re))
continue;
int major;
int minor;
if (! beast::lexicalCastChecked (
major, std::string (m[1])))
continue;
if (! beast::lexicalCastChecked (
minor, std::string (m[2])))
continue;
result.push_back (std::make_pair (major, minor));
}
std::sort(result.begin(), result.end());
result.erase(std::unique (result.begin(), result.end()), result.end());
return result;
}
boost::optional<protocol::TMHello>
parseHello (bool request, boost::beast::http::fields const& h, beast::Journal journal)
{
// protocol version in TMHello is obsolete,
// it is supplanted by the values in the headers.
protocol::TMHello hello;
{
// Required
auto const iter = h.find ("Upgrade");
if (iter == h.end())
return boost::none;
auto const versions = parse_ProtocolVersions(iter->value().to_string());
if (versions.empty())
return boost::none;
hello.set_protoversion(
(safe_cast<std::uint32_t>(versions.back().first) << 16) |
(safe_cast<std::uint32_t>(versions.back().second)));
hello.set_protoversionmin(
(safe_cast<std::uint32_t>(versions.front().first) << 16) |
(safe_cast<std::uint32_t>(versions.front().second)));
}
{
// Required
auto const iter = h.find ("Public-Key");
if (iter == h.end())
return boost::none;
auto const pk = parseBase58<PublicKey>(
TokenType::NodePublic, iter->value().to_string());
if (!pk)
return boost::none;
hello.set_nodepublic (iter->value().to_string());
}
{
// Required
auto const iter = h.find ("Session-Signature");
if (iter == h.end())
return boost::none;
// TODO Security Review
hello.set_nodeproof (base64_decode (iter->value().to_string()));
}
{
auto const iter = h.find (request ?
"User-Agent" : "Server");
if (iter != h.end())
hello.set_fullversion (iter->value().to_string());
}
{
auto const iter = h.find ("Network-Time");
if (iter != h.end())
{
std::uint64_t nettime;
if (! beast::lexicalCastChecked(nettime, iter->value().to_string()))
return boost::none;
hello.set_nettime (nettime);
}
}
{
auto const iter = h.find ("Ledger");
if (iter != h.end())
{
LedgerIndex ledgerIndex;
if (! beast::lexicalCastChecked(ledgerIndex, iter->value().to_string()))
return boost::none;
hello.set_ledgerindex (ledgerIndex);
}
}
{
auto const iter = h.find ("Closed-Ledger");
if (iter != h.end())
hello.set_ledgerclosed (base64_decode (iter->value().to_string()));
}
{
auto const iter = h.find ("Previous-Ledger");
if (iter != h.end())
hello.set_ledgerprevious (base64_decode (iter->value().to_string()));
}
{
auto const iter = h.find ("Local-IP");
if (iter != h.end())
{
boost::system::error_code ec;
auto address =
beast::IP::Address::from_string (iter->value().to_string(), ec);
if (ec)
{
JLOG(journal.warn()) << "invalid Local-IP: "
<< iter->value().to_string();
return boost::none;
}
hello.set_local_ip_str(address.to_string());
}
}
{
auto const iter = h.find ("Remote-IP");
if (iter != h.end())
{
boost::system::error_code ec;
auto address =
beast::IP::Address::from_string (iter->value().to_string(), ec);
if (ec)
{
JLOG(journal.warn()) << "invalid Remote-IP: "
<< iter->value().to_string();
return boost::none;
}
hello.set_remote_ip_str(address.to_string());
}
}
return hello;
}
boost::optional<PublicKey>
verifyHello (protocol::TMHello const& h,
uint256 const& sharedValue,
beast::IP::Address public_ip,
beast::IP::Endpoint remote,
beast::Journal journal,
Application& app)
{
if (h.has_nettime ())
{
auto const ourTime = app.timeKeeper().now().time_since_epoch().count();
auto const minTime = ourTime - clockToleranceDeltaSeconds;
auto const maxTime = ourTime + clockToleranceDeltaSeconds;
if (h.nettime () > maxTime)
{
JLOG(journal.info()) <<
"Clock for is off by +" << h.nettime() - ourTime;
return boost::none;
}
if (h.nettime () < minTime)
{
JLOG(journal.info()) <<
"Clock is off by -" << ourTime - h.nettime();
return boost::none;
}
JLOG(journal.trace()) <<
"Connect: time offset " <<
safe_cast<std::int64_t>(ourTime) - h.nettime();
}
if (h.protoversionmin () > to_packed (BuildInfo::getCurrentProtocol()))
{
JLOG(journal.info()) <<
"Hello: Disconnect: Protocol mismatch [" <<
"Peer expects " << to_string (
BuildInfo::make_protocol(h.protoversion())) <<
" and we run " << to_string (
BuildInfo::getCurrentProtocol()) << "]";
return boost::none;
}
auto const publicKey = parseBase58<PublicKey>(
TokenType::NodePublic, h.nodepublic());
if (! publicKey)
{
JLOG(journal.info()) <<
"Hello: Disconnect: Bad node public key.";
return boost::none;
}
if (publicKeyType(*publicKey) != KeyType::secp256k1)
{
JLOG(journal.info()) <<
"Hello: Disconnect: Unsupported public key type.";
return boost::none;
}
if (*publicKey == app.nodeIdentity().first)
{
JLOG(journal.info()) <<
"Hello: Disconnect: Self connection.";
return boost::none;
}
if (! verifyDigest (*publicKey, sharedValue,
makeSlice (h.nodeproof()), false))
{
// Unable to verify they have private key for claimed public key.
JLOG(journal.info()) <<
"Hello: Disconnect: Failed to verify session.";
return boost::none;
}
if (h.has_local_ip_str () &&
is_public (remote))
{
boost::system::error_code ec;
auto local_ip =
beast::IP::Address::from_string (h.local_ip_str(), ec);
if (ec)
{
JLOG(journal.warn()) << "invalid local-ip: " << h.local_ip_str();
return boost::none;
}
if (remote.address() != local_ip)
{
// Remote asked us to confirm connection is from correct IP
JLOG(journal.info()) <<
"Hello: Disconnect: Peer IP is " << remote.address().to_string()
<< " not " << local_ip.to_string();
return boost::none;
}
}
if (h.has_remote_ip_str () &&
is_public (remote) &&
(! beast::IP::is_unspecified(public_ip)))
{
boost::system::error_code ec;
auto remote_ip =
beast::IP::Address::from_string (h.remote_ip_str(), ec);
if (ec)
{
JLOG(journal.warn()) << "invalid remote-ip: " << h.remote_ip_str();
return boost::none;
}
if (remote_ip != public_ip)
{
// We know our public IP and peer reports connection from some
// other IP
JLOG(journal.info()) <<
"Hello: Disconnect: Our IP is " << public_ip.to_string()
<< " not " << remote_ip.to_string();
return boost::none;
}
}
return publicKey;
}
}

View File

@@ -1,89 +0,0 @@
//------------------------------------------------------------------------------
/*
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_TMHELLO_H_INCLUDED
#define RIPPLE_OVERLAY_TMHELLO_H_INCLUDED
#include <ripple/protocol/messages.h>
#include <ripple/app/main/Application.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/protocol/BuildInfo.h>
#include <boost/beast/http/message.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/optional.hpp>
#include <utility>
namespace ripple {
enum
{
// The clock drift we allow a remote peer to have
clockToleranceDeltaSeconds = 20
};
/** Computes a shared value based on the SSL connection state.
When there is no man in the middle, both sides will compute the same
value. In the presence of an attacker, the computed values will be
different.
If the shared value generation fails, the link MUST be dropped.
@return A pair. Second will be false if shared value generation failed.
*/
boost::optional<uint256>
makeSharedValue (SSL* ssl, beast::Journal journal);
/** Build a TMHello protocol message. */
protocol::TMHello
buildHello (uint256 const& sharedValue,
beast::IP::Address public_ip,
beast::IP::Endpoint remote, Application& app);
/** Insert HTTP headers based on the TMHello protocol message. */
void
appendHello (boost::beast::http::fields& h, protocol::TMHello const& hello);
/** Parse HTTP headers into TMHello protocol message.
@return A protocol message on success; an empty optional
if the parsing failed.
*/
boost::optional<protocol::TMHello>
parseHello (bool request, boost::beast::http::fields const& h, beast::Journal journal);
/** Validate and store the public key in the TMHello.
This includes signature verification on the shared value.
@return The remote end public key on success; an empty
optional if the check failed.
*/
boost::optional<PublicKey>
verifyHello (protocol::TMHello const& h, uint256 const& sharedValue,
beast::IP::Address public_ip,
beast::IP::Endpoint remote,
beast::Journal journal, Application& app);
/** Parse a set of protocol versions.
The returned list contains no duplicates and is sorted ascending.
Any strings that are not parseable as RTXP protocol strings are
excluded from the result set.
*/
std::vector<ProtocolVersion>
parse_ProtocolVersions(boost::beast::string_view const& s);
}
#endif

View File

@@ -25,9 +25,7 @@ TrafficCount::category TrafficCount::categorize (
::google::protobuf::Message const& message,
int type, bool inbound)
{
if ((type == protocol::mtHELLO) ||
(type == protocol::mtPING) ||
(type == protocol::mtSTATUS_CHANGE))
if ((type == protocol::mtPING) || (type == protocol::mtSTATUS_CHANGE))
return TrafficCount::category::base;
if (type == protocol::mtCLUSTER)
@@ -36,9 +34,7 @@ TrafficCount::category TrafficCount::categorize (
if (type == protocol::mtMANIFESTS)
return TrafficCount::category::manifests;
if ((type == protocol::mtENDPOINTS) ||
(type == protocol::mtPEERS) ||
(type == protocol::mtGET_PEERS))
if (type == protocol::mtENDPOINTS)
return TrafficCount::category::overlay;
if ((type == protocol::mtGET_SHARD_INFO) ||

View File

@@ -32,9 +32,9 @@ struct send_always
{
using return_type = void;
Message::pointer const& msg;
std::shared_ptr<Message> const& msg;
send_always(Message::pointer const& m)
send_always(std::shared_ptr<Message> const& m)
: msg(m)
{ }
@@ -52,10 +52,10 @@ struct send_if_pred
{
using return_type = void;
Message::pointer const& msg;
std::shared_ptr<Message> const& msg;
Predicate const& predicate;
send_if_pred(Message::pointer const& m, Predicate const& p)
send_if_pred(std::shared_ptr<Message> const& m, Predicate const& p)
: msg(m), predicate(p)
{ }
@@ -69,7 +69,7 @@ struct send_if_pred
/** Helper function to aid in type deduction */
template <typename Predicate>
send_if_pred<Predicate> send_if (
Message::pointer const& m,
std::shared_ptr<Message> const& m,
Predicate const &f)
{
return send_if_pred<Predicate>(m, f);
@@ -83,10 +83,10 @@ struct send_if_not_pred
{
using return_type = void;
Message::pointer const& msg;
std::shared_ptr<Message> const& msg;
Predicate const& predicate;
send_if_not_pred(Message::pointer const& m, Predicate const& p)
send_if_not_pred(std::shared_ptr<Message> const& m, Predicate const& p)
: msg(m), predicate(p)
{ }
@@ -100,7 +100,7 @@ struct send_if_not_pred
/** Helper function to aid in type deduction */
template <typename Predicate>
send_if_not_pred<Predicate> send_if_not (
Message::pointer const& m,
std::shared_ptr<Message> const& m,
Predicate const &f)
{
return send_if_not_pred<Predicate>(m, f);

View File

@@ -174,7 +174,7 @@ public:
If nullptr is returned, then the slot could not be assigned.
Usually this is because of a detected self-connection.
*/
virtual Slot::ptr new_inbound_slot (
virtual std::shared_ptr<Slot> new_inbound_slot (
beast::IP::Endpoint const& local_endpoint,
beast::IP::Endpoint const& remote_endpoint) = 0;
@@ -182,21 +182,21 @@ public:
If nullptr is returned, then the slot could not be assigned.
Usually this is because of a duplicate connection.
*/
virtual Slot::ptr new_outbound_slot (
virtual std::shared_ptr<Slot> new_outbound_slot (
beast::IP::Endpoint const& remote_endpoint) = 0;
/** Called when mtENDPOINTS is received. */
virtual void on_endpoints (Slot::ptr const& slot,
virtual void on_endpoints (std::shared_ptr<Slot> const& slot,
Endpoints const& endpoints) = 0;
/** Called when the slot is closed.
This always happens when the socket is closed, unless the socket
was canceled.
*/
virtual void on_closed (Slot::ptr const& slot) = 0;
virtual void on_closed (std::shared_ptr<Slot> const& slot) = 0;
/** Called when an outbound connection is deemed to have failed */
virtual void on_failure (Slot::ptr const& slot) = 0;
virtual void on_failure (std::shared_ptr<Slot> const& slot) = 0;
/** Called when we received redirect IPs from a busy peer. */
virtual
@@ -215,19 +215,19 @@ public:
*/
virtual
bool
onConnected (Slot::ptr const& slot,
onConnected (std::shared_ptr<Slot> const& slot,
beast::IP::Endpoint const& local_endpoint) = 0;
/** Request an active slot type. */
virtual
Result
activate (Slot::ptr const& slot,
activate (std::shared_ptr<Slot> const& slot,
PublicKey const& key, bool reserved) = 0;
/** Returns a set of endpoints suitable for redirection. */
virtual
std::vector <Endpoint>
redirect (Slot::ptr const& slot) = 0;
redirect (std::shared_ptr<Slot> const& slot) = 0;
/** Return a set of addresses we should connect to. */
virtual
@@ -235,7 +235,7 @@ public:
autoconnect() = 0;
virtual
std::vector<std::pair<Slot::ptr, std::vector<Endpoint>>>
std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>>
buildEndpointsForPeers() = 0;
/** Perform periodic activity.

View File

@@ -573,10 +573,10 @@ public:
return none;
}
std::vector<std::pair<Slot::ptr, std::vector<Endpoint>>>
std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>>
buildEndpointsForPeers()
{
std::vector<std::pair<Slot::ptr, std::vector<Endpoint>>> result;
std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>> result;
std::lock_guard _(lock_);

View File

@@ -119,7 +119,7 @@ public:
//--------------------------------------------------------------------------
Slot::ptr
std::shared_ptr<Slot>
new_inbound_slot (
beast::IP::Endpoint const& local_endpoint,
beast::IP::Endpoint const& remote_endpoint) override
@@ -127,14 +127,14 @@ public:
return m_logic.new_inbound_slot (local_endpoint, remote_endpoint);
}
Slot::ptr
std::shared_ptr<Slot>
new_outbound_slot (beast::IP::Endpoint const& remote_endpoint) override
{
return m_logic.new_outbound_slot (remote_endpoint);
}
void
on_endpoints (Slot::ptr const& slot,
on_endpoints (std::shared_ptr<Slot> const& slot,
Endpoints const& endpoints) override
{
SlotImp::ptr impl (std::dynamic_pointer_cast <SlotImp> (slot));
@@ -142,14 +142,14 @@ public:
}
void
on_closed (Slot::ptr const& slot) override
on_closed (std::shared_ptr<Slot> const& slot) override
{
SlotImp::ptr impl (std::dynamic_pointer_cast <SlotImp> (slot));
m_logic.on_closed (impl);
}
void
on_failure (Slot::ptr const& slot) override
on_failure (std::shared_ptr<Slot> const& slot) override
{
SlotImp::ptr impl (std::dynamic_pointer_cast <SlotImp> (slot));
m_logic.on_failure (impl);
@@ -165,7 +165,7 @@ public:
//--------------------------------------------------------------------------
bool
onConnected (Slot::ptr const& slot,
onConnected (std::shared_ptr<Slot> const& slot,
beast::IP::Endpoint const& local_endpoint) override
{
SlotImp::ptr impl (std::dynamic_pointer_cast <SlotImp> (slot));
@@ -173,14 +173,15 @@ public:
}
Result
activate (Slot::ptr const& slot, PublicKey const& key, bool reserved) override
activate (std::shared_ptr<Slot> const& slot,
PublicKey const& key, bool reserved) override
{
SlotImp::ptr impl (std::dynamic_pointer_cast <SlotImp> (slot));
return m_logic.activate (impl, key, reserved);
}
std::vector <Endpoint>
redirect (Slot::ptr const& slot) override
redirect (std::shared_ptr<Slot> const& slot) override
{
SlotImp::ptr impl (std::dynamic_pointer_cast <SlotImp> (slot));
return m_logic.redirect (impl);
@@ -198,7 +199,7 @@ public:
m_logic.once_per_second();
}
std::vector<std::pair<Slot::ptr, std::vector<Endpoint>>>
std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>>
buildEndpointsForPeers() override
{
return m_logic.buildEndpointsForPeers();

View File

@@ -1,15 +1,14 @@
syntax = "proto2";
package protocol;
// Unused numbers in the list below may have been used previously. Please don't
// reassign them for reuse unless you are 100% certain that there won't be a
// conflict. Even if you're sure, it's probably best to assign a new type.
enum MessageType
{
mtHELLO = 1;
mtMANIFESTS = 2;
mtPING = 3;
mtPROOFOFWORK = 4;
mtCLUSTER = 5;
mtGET_PEERS = 12;
mtPEERS = 13;
mtENDPOINTS = 15;
mtTRANSACTION = 30;
mtGET_LEDGER = 31;
@@ -23,14 +22,6 @@ enum MessageType
mtSHARD_INFO = 51;
mtGET_PEER_SHARD_INFO = 52;
mtPEER_SHARD_INFO = 53;
// <available> = 10;
// <available> = 11;
// <available> = 14;
// <available> = 20;
// <available> = 21;
// <available> = 22;
// <available> = 40;
}
// token, iterations, target, challenge = issue demand for proof of work
@@ -56,56 +47,6 @@ message TMManifests
//------------------------------------------------------------------------------
/* Requests or responds to a proof of work.
Unimplemented and unused currently.
*/
message TMProofWork
{
required string token = 1;
optional uint32 iterations = 2;
optional bytes target = 3;
optional bytes challenge = 4;
optional bytes response = 5;
enum PowResult
{
powrOK = 0;
powrREUSED = 1;
powrEXPIRED = 2; // You took too long solving
powrTOOEASY = 3; // Difficulty went way up, sorry
powrINVALID = 4;
powrDISCONNECT = 5; // We are disconnecting
}
optional PowResult result = 6;
}
//------------------------------------------------------------------------------
// Sent on connect
message TMHello
{
// VFALCO NOTE The major and minor parts of the version number are
// encoded in the high and low order 16 bits of the uint32.
//
required uint32 protoVersion = 1;
required uint32 protoVersionMin = 2;
required bytes nodePublic = 3;
required bytes nodeProof = 4;
optional string fullVersion = 5;
optional uint64 netTime = 6;
optional uint32 ipv4Port = 7; // NOT USED
optional uint32 ledgerIndex = 8;
optional bytes ledgerClosed = 9; // our last closed ledger
optional bytes ledgerPrevious = 10; // the ledger before the last closed ledger
optional bool nodePrivate = 11; // Request to not forward IP.
optional TMProofWork proofOfWork = 12; // request/provide proof of work
optional bool testNet = 13; // Running as testnet.
optional uint32 local_ip = 14; // NOT USED -- our public IP
optional uint32 remote_ip = 15; // NOT USED -- IP we see connection from
optional string local_ip_str = 16; // our public IP
optional string remote_ip_str = 17; // IP we see connection from
}
// The status of a node in our cluster
message TMClusterNode
{
@@ -265,11 +206,6 @@ message TMValidation
optional uint32 hops = 3; // Number of hops traveled
}
message TMGetPeers
{
required uint32 doWeNeedThis = 1; // yes since you are asserting that the packet size isn't 0 in Message
}
message TMIPv4Endpoint
{
required uint32 ipv4 = 1;
@@ -280,12 +216,6 @@ message TMIPv4Endpoint
required uint32 ipv4Port = 2;
}
// this message is obsolete/no longer procesed
message TMPeers
{
repeated TMIPv4Endpoint nodes = 1;
}
// An Endpoint describes a network peer that can accept incoming connections
message TMEndpoint
{

View File

@@ -25,8 +25,6 @@
namespace ripple {
/** Describes a Ripple/RTXP protocol version. */
using ProtocolVersion = std::pair<std::uint16_t, std::uint16_t>;
/** Versioning information for this build. */
// VFALCO The namespace is deprecated
@@ -46,25 +44,8 @@ getVersionString();
std::string const&
getFullVersionString();
/** Construct a protocol version from a packed 32-bit protocol identifier */
ProtocolVersion
make_protocol (std::uint32_t version);
/** The protocol version we speak and prefer. */
ProtocolVersion const&
getCurrentProtocol();
/** The oldest protocol version we will accept. */
ProtocolVersion const& getMinimumProtocol ();
} // BuildInfo (DEPRECATED)
std::string
to_string (ProtocolVersion const& p);
std::uint32_t
to_packed (ProtocolVersion const& p);
} // ripple
#endif

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#ifndef RIPPLE_CRYPTO_KEYTYPE_H_INCLUDED
#define RIPPLE_CRYPTO_KEYTYPE_H_INCLUDED
#ifndef RIPPLE_PROTOCOL_KEYTYPE_H_INCLUDED
#define RIPPLE_PROTOCOL_KEYTYPE_H_INCLUDED
#include <string>
#include <boost/optional.hpp>

View File

@@ -21,7 +21,7 @@
#define RIPPLE_PROTOCOL_PUBLICKEY_H_INCLUDED
#include <ripple/basics/Slice.h>
#include <ripple/crypto/KeyType.h> // move to protocol/
#include <ripple/protocol/KeyType.h>
#include <ripple/protocol/STExchange.h>
#include <ripple/protocol/tokens.h>
#include <ripple/protocol/UintTypes.h>

View File

@@ -22,7 +22,7 @@
#include <ripple/basics/Buffer.h>
#include <ripple/basics/Slice.h>
#include <ripple/crypto/KeyType.h> // move to protocol/
#include <ripple/protocol/KeyType.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/Seed.h>
#include <ripple/protocol/tokens.h>

View File

@@ -33,7 +33,7 @@ namespace BuildInfo {
char const* const versionString = "1.4.0"
#if defined(DEBUG) || defined(SANITIZER)
"+"
"+"
#ifdef DEBUG
"DEBUG"
#ifdef SANITIZER
@@ -49,46 +49,9 @@ char const* const versionString = "1.4.0"
//--------------------------------------------------------------------------
;
ProtocolVersion const&
getCurrentProtocol ()
{
static ProtocolVersion currentProtocol (
//--------------------------------------------------------------------------
//
// The protocol version we speak and prefer (edit this if necessary)
//
1, // major
2 // minor
//
//--------------------------------------------------------------------------
);
return currentProtocol;
}
ProtocolVersion const&
getMinimumProtocol ()
{
static ProtocolVersion minimumProtocol (
//--------------------------------------------------------------------------
//
// The oldest protocol version we will accept. (edit this if necessary)
//
1, // major
2 // minor
//
//--------------------------------------------------------------------------
);
return minimumProtocol;
}
//
//
// Don't touch anything below this line
//
//------------------------------------------------------------------------------
std::string const&
getVersionString ()
@@ -110,26 +73,6 @@ std::string const& getFullVersionString ()
return value;
}
ProtocolVersion
make_protocol (std::uint32_t version)
{
return ProtocolVersion(
static_cast<std::uint16_t> ((version >> 16) & 0xffff),
static_cast<std::uint16_t> (version & 0xffff));
}
}
std::string
to_string (ProtocolVersion const& p)
{
return std::to_string (p.first) + "." + std::to_string (p.second);
}
std::uint32_t
to_packed (ProtocolVersion const& p)
{
return (static_cast<std::uint32_t> (p.first) << 16) + p.second;
}
} // BuildInfo
} // ripple

View File

@@ -18,10 +18,10 @@
//==============================================================================
#include <ripple/basics/strHex.h>
#include <ripple/crypto/KeyType.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/KeyType.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/Seed.h>

View File

@@ -284,8 +284,7 @@ ServerHandlerImp::onRequest (Session& session)
}
// Check user/password authorization
if (! authorized (
session.port(), build_map(session.request())))
if (! authorized (session.port(), build_map(session.request())))
{
HTTPReply (403, "Forbidden", makeOutput (session), app_.journal ("RPC"));
session.close (true);

View File

@@ -18,10 +18,11 @@
//==============================================================================
#include <ripple/overlay/impl/Handshake.cpp>
#include <ripple/overlay/impl/PeerImp.cpp>
#include <ripple/overlay/impl/PeerReservationTable.cpp>
#include <ripple/overlay/impl/PeerSet.cpp>
#include <ripple/overlay/impl/TMHello.cpp>
#include <ripple/overlay/impl/ProtocolVersion.cpp>
#include <ripple/overlay/impl/TrafficCount.cpp>
#if DOXYGEN

View File

@@ -20,9 +20,9 @@
#ifndef RIPPLE_TEST_JTX_ACCOUNT_H_INCLUDED
#define RIPPLE_TEST_JTX_ACCOUNT_H_INCLUDED
#include <ripple/protocol/KeyType.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/crypto/KeyType.h>
#include <ripple/beast/hash/uhash.h>
#include <unordered_map>
#include <string>

View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2019 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/ProtocolVersion.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
class ProtocolVersion_test : public beast::unit_test::suite
{
private:
template <class FwdIt>
static
std::string
join (FwdIt first, FwdIt last, char const* sep = ",")
{
std::string result;
if (first == last)
return result;
result = to_string(*first++);
while(first != last)
result += sep + to_string(*first++);
return result;
}
void
check(std::string const& s, std::string const& answer)
{
auto const result = parseProtocolVersions(s);
BEAST_EXPECT(join(result.begin(), result.end()) == answer);
}
public:
void
run() override
{
testcase("Convert protocol version to string");
BEAST_EXPECT(to_string( make_protocol(1,2)) == "RTXP/1.2");
BEAST_EXPECT(to_string( make_protocol(1,3)) == "XRPL/1.3");
BEAST_EXPECT(to_string( make_protocol(2,0)) == "XRPL/2.0");
BEAST_EXPECT(to_string( make_protocol(2,1)) == "XRPL/2.1");
BEAST_EXPECT(to_string(make_protocol(10,10)) == "XRPL/10.10");
{
testcase("Convert strings to protocol versions");
// Empty string
check(
"",
"");
check(
"RTXP/1.1,RTXP/1.3,XRPL/2.1,RTXP/1.2,XRPL/2.0",
"RTXP/1.2,XRPL/2.0,XRPL/2.1");
check(
"RTXP/0.9,RTXP/1.01,XRPL/0.3,XRPL/2.01,XRPL/19.04,Oscar/123,NIKB",
"");
check(
"RTXP/1.2,XRPL/2.0,RTXP/1.2,XRPL/2.0,XRPL/19.4,XRPL/7.89,XRPL/A.1,XRPL/2.01",
"RTXP/1.2,XRPL/2.0,XRPL/7.89,XRPL/19.4");
check(
"XRPL/2.0,XRPL/3.0,XRPL/4,XRPL/,XRPL,OPT XRPL/2.2,XRPL/5.67",
"XRPL/2.0,XRPL/3.0,XRPL/5.67");
}
{
testcase("Protocol version negotiation");
BEAST_EXPECT(negotiateProtocolVersion("RTXP/1.2") == make_protocol(1,2));
BEAST_EXPECT(negotiateProtocolVersion("RTXP/1.2, XRPL/2.0") == make_protocol(2,0));
BEAST_EXPECT(negotiateProtocolVersion("XRPL/2.0") == make_protocol(2,0));
BEAST_EXPECT(negotiateProtocolVersion("RTXP/1.2, XRPL/2.0, XRPL/999.999") == make_protocol(2,0));
BEAST_EXPECT(negotiateProtocolVersion("XRPL/999.999, WebSocket/1.0") == boost::none);
BEAST_EXPECT(negotiateProtocolVersion("") == boost::none);
}
}
};
BEAST_DEFINE_TESTSUITE(ProtocolVersion,overlay,ripple);
}

View File

@@ -1,70 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright 2014 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/TMHello.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
class TMHello_test : public beast::unit_test::suite
{
private:
template <class FwdIt>
static
std::string
join (FwdIt first, FwdIt last, char c = ',')
{
std::string result;
if (first == last)
return result;
result = to_string(*first++);
while(first != last)
result += "," + to_string(*first++);
return result;
}
void
check(std::string const& s, std::string const& answer)
{
auto const result = parse_ProtocolVersions(s);
BEAST_EXPECT(join(result.begin(), result.end()) == answer);
}
public:
void
test_protocolVersions()
{
check("", "");
check("RTXP/1.0", "1.0");
check("RTXP/1.0, Websocket/1.0", "1.0");
check("RTXP/1.0, RTXP/1.0", "1.0");
check("RTXP/1.0, RTXP/1.1", "1.0,1.1");
check("RTXP/1.1, RTXP/1.0", "1.0,1.1");
}
void
run() override
{
test_protocolVersions();
}
};
BEAST_DEFINE_TESTSUITE(TMHello,overlay,ripple);
}

View File

@@ -1,106 +0,0 @@
//------------------------------------------------------------------------------
/*
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/protocol/BuildInfo.h>
#include <ripple/beast/core/SemanticVersion.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
class BuildInfo_test : public beast::unit_test::suite
{
public:
ProtocolVersion
from_version (std::uint16_t major, std::uint16_t minor)
{
return ProtocolVersion (major, minor);
}
void testValues ()
{
testcase ("comparison");
BEAST_EXPECT(from_version (1,2) == from_version (1,2));
BEAST_EXPECT(from_version (3,4) >= from_version (3,4));
BEAST_EXPECT(from_version (5,6) <= from_version (5,6));
BEAST_EXPECT(from_version (7,8) > from_version (6,7));
BEAST_EXPECT(from_version (7,8) < from_version (8,9));
BEAST_EXPECT(from_version (65535,0) < from_version (65535,65535));
BEAST_EXPECT(from_version (65535,65535) >= from_version (65535,65535));
}
void testStringVersion ()
{
testcase ("string version");
for (std::uint16_t major = 0; major < 8; major++)
{
for (std::uint16_t minor = 0; minor < 8; minor++)
{
BEAST_EXPECT(to_string (from_version (major, minor)) ==
std::to_string (major) + "." + std::to_string (minor));
}
}
}
void testVersionPacking ()
{
testcase ("version packing");
BEAST_EXPECT(to_packed (from_version (0, 0)) == 0);
BEAST_EXPECT(to_packed (from_version (0, 1)) == 1);
BEAST_EXPECT(to_packed (from_version (0, 255)) == 255);
BEAST_EXPECT(to_packed (from_version (0, 65535)) == 65535);
BEAST_EXPECT(to_packed (from_version (1, 0)) == 65536);
BEAST_EXPECT(to_packed (from_version (1, 1)) == 65537);
BEAST_EXPECT(to_packed (from_version (1, 255)) == 65791);
BEAST_EXPECT(to_packed (from_version (1, 65535)) == 131071);
BEAST_EXPECT(to_packed (from_version (255, 0)) == 16711680);
BEAST_EXPECT(to_packed (from_version (255, 1)) == 16711681);
BEAST_EXPECT(to_packed (from_version (255, 255)) == 16711935);
BEAST_EXPECT(to_packed (from_version (255, 65535)) == 16777215);
BEAST_EXPECT(to_packed (from_version (65535, 0)) == 4294901760);
BEAST_EXPECT(to_packed (from_version (65535, 1)) == 4294901761);
BEAST_EXPECT(to_packed (from_version (65535, 255)) == 4294902015);
BEAST_EXPECT(to_packed (from_version (65535, 65535)) == 4294967295);
}
void run () override
{
testValues ();
testStringVersion ();
testVersionPacking ();
auto const current_protocol = BuildInfo::getCurrentProtocol ();
auto const minimum_protocol = BuildInfo::getMinimumProtocol ();
BEAST_EXPECT(current_protocol >= minimum_protocol);
log <<
" Ripple Version: " << BuildInfo::getVersionString() << '\n' <<
" Protocol Version: " << to_string (current_protocol) << std::endl;
}
};
BEAST_DEFINE_TESTSUITE(BuildInfo,ripple_data,ripple);
} // ripple

View File

@@ -18,6 +18,6 @@
*/
//==============================================================================
#include <test/overlay/ProtocolVersion_test.cpp>
#include <test/overlay/cluster_test.cpp>
#include <test/overlay/short_read_test.cpp>
#include <test/overlay/TMHello_test.cpp>

View File

@@ -18,7 +18,6 @@
*/
//==============================================================================
#include <test/protocol/BuildInfo_test.cpp>
#include <test/protocol/digest_test.cpp>
#include <test/protocol/InnerObjectFormats_test.cpp>
#include <test/protocol/IOUAmount_test.cpp>