From f6916bfd429ce654e017ae9686cb023d9e05408b Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Sat, 22 Jun 2019 17:02:13 -0700 Subject: [PATCH] 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 --- Builds/CMake/RippledCore.cmake | 8 +- cfg/rippled-example.cfg | 18 + src/ripple/overlay/Message.h | 104 +--- src/ripple/overlay/Overlay.h | 2 + src/ripple/overlay/Peer.h | 3 +- src/ripple/overlay/README.md | 384 ++++++++++----- src/ripple/overlay/impl/ConnectAttempt.cpp | 130 +++-- src/ripple/overlay/impl/ConnectAttempt.h | 8 +- src/ripple/overlay/impl/Handshake.cpp | 287 +++++++++++ src/ripple/overlay/impl/Handshake.h | 78 +++ src/ripple/overlay/impl/Message.cpp | 68 +-- src/ripple/overlay/impl/OverlayImpl.cpp | 148 +++--- src/ripple/overlay/impl/OverlayImpl.h | 21 +- src/ripple/overlay/impl/PeerImp.cpp | 192 +++----- src/ripple/overlay/impl/PeerImp.h | 55 +-- src/ripple/overlay/impl/PeerSet.cpp | 3 +- src/ripple/overlay/impl/ProtocolMessage.h | 221 ++++++--- src/ripple/overlay/impl/ProtocolVersion.cpp | 184 +++++++ src/ripple/overlay/impl/ProtocolVersion.h | 79 +++ src/ripple/overlay/impl/TMHello.cpp | 462 ------------------ src/ripple/overlay/impl/TMHello.h | 89 ---- src/ripple/overlay/impl/TrafficCount.cpp | 8 +- src/ripple/overlay/predicates.h | 16 +- src/ripple/peerfinder/PeerfinderManager.h | 18 +- src/ripple/peerfinder/impl/Logic.h | 4 +- .../peerfinder/impl/PeerfinderManager.cpp | 19 +- src/ripple/proto/ripple.proto | 76 +-- src/ripple/protocol/BuildInfo.h | 19 - src/ripple/{crypto => protocol}/KeyType.h | 4 +- src/ripple/protocol/PublicKey.h | 2 +- src/ripple/protocol/SecretKey.h | 2 +- src/ripple/protocol/impl/BuildInfo.cpp | 61 +-- src/ripple/rpc/handlers/WalletPropose.cpp | 2 +- src/ripple/rpc/impl/ServerHandlerImp.cpp | 3 +- src/ripple/unity/overlay2.cpp | 3 +- src/test/jtx/Account.h | 2 +- src/test/overlay/ProtocolVersion_test.cpp | 97 ++++ src/test/overlay/TMHello_test.cpp | 70 --- src/test/protocol/BuildInfo_test.cpp | 106 ---- src/test/unity/overlay_test_unity.cpp | 2 +- src/test/unity/protocol_test_unity.cpp | 1 - 41 files changed, 1487 insertions(+), 1572 deletions(-) create mode 100644 src/ripple/overlay/impl/Handshake.cpp create mode 100644 src/ripple/overlay/impl/Handshake.h create mode 100644 src/ripple/overlay/impl/ProtocolVersion.cpp create mode 100644 src/ripple/overlay/impl/ProtocolVersion.h delete mode 100644 src/ripple/overlay/impl/TMHello.cpp delete mode 100644 src/ripple/overlay/impl/TMHello.h rename src/ripple/{crypto => protocol}/KeyType.h (95%) create mode 100644 src/test/overlay/ProtocolVersion_test.cpp delete mode 100644 src/test/overlay/TMHello_test.cpp delete mode 100644 src/test/protocol/BuildInfo_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index a6d97ddf888..c8f0b0c58b1 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -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 diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 3450d9ef660..c43f0e2d1f6 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -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 diff --git a/src/ripple/overlay/Message.h b/src/ripple/overlay/Message.h index 498f70ca88d..e186272b3bf 100644 --- a/src/ripple/overlay/Message.h +++ b/src/ripple/overlay/Message.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -47,26 +48,9 @@ namespace ripple { class Message : public std::enable_shared_from_this { public: - using pointer = std::shared_ptr; - -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 const& getBuffer () const @@ -81,92 +65,8 @@ class Message : public std::enable_shared_from_this return mCategory; } - /** Determine bytewise equality. */ - bool operator == (Message const& other) const; - - /** Calculate the length of a packed message. */ - /** @{ */ - static unsigned getLength (std::vector const& buf); - - template - static - std::enable_if_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 - 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 const& buf); - - template - static - std::enable_if_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 - static - int - type (BufferSequence const& buffers) - { - return type(buffers_begin(buffers), - buffers_end(buffers)); - } - /** @} */ - private: - template - static - boost::asio::buffers_iterator - buffers_begin (BufferSequence const& buffers) - { - return boost::asio::buffers_iterator< - BufferSequence, Value>::begin (buffers); - } - - template - static - boost::asio::buffers_iterator - 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 mBuffer; - std::size_t mCategory; }; diff --git a/src/ripple/overlay/Overlay.h b/src/ripple/overlay/Overlay.h index af7821363fa..9d755110d4d 100644 --- a/src/ripple/overlay/Overlay.h +++ b/src/ripple/overlay/Overlay.h @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace boost { namespace asio { namespace ssl { class context; } } } @@ -71,6 +72,7 @@ class Overlay beast::IP::Address public_ip; int ipLimit = 0; std::uint32_t crawlOptions = 0; + boost::optional networkID; }; using PeerSequence = std::vector >; diff --git a/src/ripple/overlay/Peer.h b/src/ripple/overlay/Peer.h index b8864c431d3..583cb04f33a 100644 --- a/src/ripple/overlay/Peer.h +++ b/src/ripple/overlay/Peer.h @@ -56,7 +56,7 @@ class Peer virtual void - send (Message::pointer const& m) = 0; + send (std::shared_ptr const& m) = 0; virtual beast::IP::Endpoint @@ -105,7 +105,6 @@ class Peer 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; }; diff --git a/src/ripple/overlay/README.md b/src/ripple/overlay/README.md index b929d2ee8e2..ca22f1e8aeb 100644 --- a/src/ripple/overlay/README.md +++ b/src/ripple/overlay/README.md @@ -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: +affects the behavior of message routing. At present, only a single connection +type is supported: **Peer**. -* Leaf - -* Peer +## Handshake -## Roles +To establish a protocol connection, a peer makes an outgoing TLS encrypted +connection to a remote peer, then sends an HTTP request with no message body. -Depending on the type of connection desired, the peers will modify their -behavior according to certain roles: +### HTTP -### Leaf or Superpeer +The HTTP [request](https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html) must: -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 +- 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. -### Client Handler +HTTP requests which do not conform to this requirements must generate an +appropriate HTTP error and result in the connection being closed. -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. +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"). -## 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: +##### 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 + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `User-Agent` | :heavy_check_mark: | | + +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. + +See [RFC2616 §14.43](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43). + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Server` | | :heavy_check_mark: | + +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. + +See [RFC2616 §14.38](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.38). + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Connection` | :heavy_check_mark: | :heavy_check_mark: | + +The `Connection` field should have a value of `Upgrade` to indicate that a +request to upgrade the connection is being performed. + +See [RFC2616 §14.10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10). + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Upgrade` | :heavy_check_mark: | :heavy_check_mark: | + +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. + +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. + +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). + +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`. + +See [RFC 2616 §14.42](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.42) + + +#### Custom Fields + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Connect-As` | :heavy_check_mark: | :heavy_check_mark: | + +The mandatory `Connect-As` field is used to specify that type of connection +that is being requested. + +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`**. + +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). + + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Remote-IP` | :white_check_mark: | :white_check_mark: | + +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. + +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 | +|--------------------- |:-----------------: |:-----------------: | +| `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: | -* *URL* +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 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 value is presently encoded using **Base64** encoding, but implementations +should support both **Base64** and **HEX** encoding for this value. -* *HTTP Version* +For more details on this field, please see **Session Signature** below. + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Crawl` | :white_check_mark: | :white_check_mark: | - The minimum required HTTP version is 1.1. Requests for HTTP versions - earlier than 1.1 must be rejected. +The optional `Crawl` field can be used by a server to indicate whether peers +should include it in crawl reports. -* `User-Agent` +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._ - Contains information about the software originating the request. - The specification is identical to RFC2616 Section 14.43. +For more on the Peer Crawler, please visit https://xrpl.org/peer-crawler.html. -* `Server` - Contains information about the software providing the response. The - specification is identical to RFC2616 Section 14.38. +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Closed-Ledger` | :white_check_mark: | :white_check_mark: | -* `Remote-Address` (optional) +If present, identifies the hash of the last ledger that the sending server +considers to be closed. - 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. +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: | -* `Upgrade` +If present, identifies the hash of the parent ledger that the sending server +considers to be closed. - 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. +The value is presently encoded using **Base64** encoding, but implementations +should support both **Base64** and **HEX** encoding for this value. -* `Connection` +#### Additional Headers - This field must be present, containing the value 'Upgrade'. +An implementation or operator may specify additional, optional fields +and values in both requests and responses. -* `Connect-As` +Implementations should not reject requests because of the presence of fields +that they do not understand. - 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 +### Session Signature - If this field is omitted or the value is the empty string, then 'leaf' is - assumed. +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. - 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. +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. -* `Public-Key` +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. - This field value must be present, and contain a base 64 encoded value used - as a server public key identifier. +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. -* `Session-Signature` +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. - 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. +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. -* `Crawl` (optional) +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 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. +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. -* _User Defined_ (Unimplemented) +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. - 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. # Ripple Clustering # diff --git a/src/ripple/overlay/impl/ConnectAttempt.cpp b/src/ripple/overlay/impl/ConnectAttempt.cpp index 157bb68d4d0..5bea51ff9d6 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.cpp +++ b/src/ripple/overlay/impl/ConnectAttempt.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -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 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(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 negotiatedProtocol; + + { + 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 sharedValue = makeSharedValue( - ssl_bundle_->stream.native_handle(), journal_); + 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(member)); - if (result != PeerFinder::Result::success) - return fail("Outbound slots full"); + auto const result = overlay_.peerFinder().activate (slot_, + publicKey, static_cast(member)); + if (result != PeerFinder::Result::success) + return fail("Outbound slots full"); - auto const peer = std::make_shared(app_, - std::move(ssl_bundle_), read_buf_.data(), - std::move(slot_), std::move(response_), - usage_, *hello, *publicKey, id_, overlay_); + auto const peer = std::make_shared(app_, std::move(ssl_bundle_), + read_buf_.data(), std::move(slot_), std::move(response_), usage_, + publicKey, *negotiatedProtocol, id_, overlay_); - overlay_.add_active (peer); + overlay_.add_active (peer); + } + catch (std::exception const& e) + { + return fail(std::string("Handshake failure (")+ e.what() + ")"); + } } } // ripple diff --git a/src/ripple/overlay/impl/ConnectAttempt.h b/src/ripple/overlay/impl/ConnectAttempt.h index 3c63fcf5037..c227e15c03b 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.h +++ b/src/ripple/overlay/impl/ConnectAttempt.h @@ -20,7 +20,6 @@ #ifndef RIPPLE_OVERLAY_CONNECTATTEMPT_H_INCLUDED #define RIPPLE_OVERLAY_CONNECTATTEMPT_H_INCLUDED -#include #include #include @@ -55,14 +54,14 @@ class ConnectAttempt beast::asio::ssl_bundle::stream_type& stream_; boost::beast::multi_buffer read_buf_; response_type response_; - PeerFinder::Slot::ptr slot_; + std::shared_ptr 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 const& slot, beast::Journal journal, OverlayImpl& overlay); ~ConnectAttempt(); @@ -89,8 +88,7 @@ class ConnectAttempt static request_type - makeRequest (bool crawl, - boost::asio::ip::address const& remote_address); + makeRequest (bool crawl); void processResponse(); diff --git a/src/ripple/overlay/impl/Handshake.cpp b/src/ripple/overlay/impl/Handshake.cpp new file mode 100644 index 00000000000..15ea5df47c6 --- /dev/null +++ b/src/ripple/overlay/impl/Handshake.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// 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> +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 +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 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 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(a - b); + return - duration_cast(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( + 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; +} + +} diff --git a/src/ripple/overlay/impl/Handshake.h b/src/ripple/overlay/impl/Handshake.h new file mode 100644 index 00000000000..455780339b2 --- /dev/null +++ b/src/ripple/overlay/impl/Handshake.h @@ -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 +#include +#include +#include + +#include +#include +#include +#include + +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 +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 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 networkID, + beast::IP::Address public_ip, + beast::IP::Address remote, + Application& app); + +} + +#endif diff --git a/src/ripple/overlay/impl/Message.cpp b/src/ripple/overlay/impl/Message.cpp index c73dee6c14f..7e0b81fa986 100644 --- a/src/ripple/overlay/impl/Message.cpp +++ b/src/ripple/overlay/impl/Message.cpp @@ -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); - - encodeHeader (messageBytes, type); - - 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 const& buf) -{ - unsigned result; + /** Number of bytes in a message header. */ + std::size_t constexpr headerBytes = 6; - 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; - } + mBuffer.resize (headerBytes + messageBytes); - return result; -} + auto ptr = mBuffer.data(); -int Message::getType (std::vector const& buf) -{ - if (buf.size () < Message::kHeaderBytes) - return 0; + *ptr++ = static_cast((messageBytes >> 24) & 0xFF); + *ptr++ = static_cast((messageBytes >> 16) & 0xFF); + *ptr++ = static_cast((messageBytes >> 8) & 0xFF); + *ptr++ = static_cast(messageBytes & 0xFF); - int ret = buf[4]; - ret <<= 8; - ret |= buf[5]; - return ret; -} + *ptr++ = static_cast((type >> 8) & 0xFF); + *ptr++ = static_cast (type & 0xFF); -void Message::encodeHeader (unsigned size, int type) -{ - assert (mBuffer.size () >= Message::kHeaderBytes); - mBuffer[0] = static_cast ((size >> 24) & 0xFF); - mBuffer[1] = static_cast ((size >> 16) & 0xFF); - mBuffer[2] = static_cast ((size >> 8) & 0xFF); - mBuffer[3] = static_cast (size & 0xFF); - mBuffer[4] = static_cast ((type >> 8) & 0xFF); - mBuffer[5] = static_cast (type & 0xFF); + if (messageBytes != 0) + message.SerializeToArray(ptr, messageBytes); } } diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index 0d7c6575388..a0c3b74fdfd 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -238,88 +238,84 @@ OverlayImpl::onHandoff (std::unique_ptr && 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 { - m_peerFinder->on_closed(slot); - handoff.moved = false; - handoff.response = makeErrorResponse (slot, request, - remote_endpoint.address(), - "Unable to verify HELLO message"); - handoff.keep_alive = false; - return handoff; - } + 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(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; + // The node gets a reserved slot if it is in our cluster + // or if it has a reservation. + bool const reserved = + static_cast(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(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 peer = std::make_shared(app_, id, slot, + std::move(request), publicKey, *negotiatedVersion, consumer, + std::move(ssl_bundle), *this); { - auto const result = - m_peers.emplace (peer->slot(), peer); - assert (result.second); - (void) result.second; + // 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(); } - list_.emplace(peer.get(), peer); + handoff.moved = true; + return handoff; + } + catch (std::exception const& e) + { + JLOG(journal.debug()) << "Peer " << remote_endpoint << + " fails handshake (" << e.what() << ")"; - peer->run(); + m_peerFinder->on_closed(slot); + handoff.moved = false; + handoff.response = makeErrorResponse (slot, request, + remote_endpoint.address(), e.what()); + handoff.keep_alive = false; + return handoff; } - 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 -OverlayImpl::makeRedirectResponse (PeerFinder::Slot::ptr const& slot, +OverlayImpl::makeRedirectResponse (std::shared_ptr const& slot, http_request_type const& request, address_type remote_address) { boost::beast::http::response msg; @@ -367,18 +360,18 @@ OverlayImpl::makeRedirectResponse (PeerFinder::Slot::ptr const& slot, } std::shared_ptr -OverlayImpl::makeErrorResponse (PeerFinder::Slot::ptr const& slot, +OverlayImpl::makeErrorResponse (std::shared_ptr const& slot, http_request_type const& request, address_type remote_address, std::string text) { - boost::beast::http::response msg; + boost::beast::http::response 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(msg); } @@ -454,7 +447,7 @@ OverlayImpl::add_active (std::shared_ptr const& peer) } void -OverlayImpl::remove (PeerFinder::Slot::ptr const& slot) +OverlayImpl::remove (std::shared_ptr const& slot) { std::lock_guard lock (mutex_); auto const iter = m_peers.find (slot); @@ -1275,6 +1268,7 @@ setup_Overlay (BasicConfig const& config) Throw("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(id); + } + } + catch (...) + { + Throw( + "Configured [network_id] section is invalid: " + "must be a number or one of the strings 'main' or 'testnet'"); + } + return setup; } diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/ripple/overlay/impl/OverlayImpl.h index e599f569ee1..951b66710d1 100644 --- a/src/ripple/overlay/impl/OverlayImpl.h +++ b/src/ripple/overlay/impl/OverlayImpl.h @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -111,7 +112,7 @@ class OverlayImpl : public Overlay Resource::Manager& m_resourceManager; std::unique_ptr m_peerFinder; TrafficCount m_traffic; - hash_map , std::weak_ptr > m_peers; hash_map> ids_; Resolver& m_resolver; @@ -128,6 +129,8 @@ class OverlayImpl : public Overlay // Peer IDs expecting to receive a last link notification std::set csIDs_; + boost::optional networkID_; + //-------------------------------------------------------------------------- public: @@ -220,7 +223,7 @@ class OverlayImpl : public Overlay add_active (std::shared_ptr const& peer); void - remove (PeerFinder::Slot::ptr const& slot); + remove (std::shared_ptr 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 @@ class OverlayImpl : public Overlay { 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 @@ -375,11 +372,11 @@ class OverlayImpl : public Overlay private: std::shared_ptr - makeRedirectResponse (PeerFinder::Slot::ptr const& slot, + makeRedirectResponse (std::shared_ptr const& slot, http_request_type const& request, address_type remote_address); std::shared_ptr - makeErrorResponse (PeerFinder::Slot::ptr const& slot, + makeErrorResponse (std::shared_ptr const& slot, http_request_type const& request, address_type remote_address, std::string msg); diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index 6ddcf9866b8..cda2c80e679 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -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 const& slot, http_request_type&& request, + PublicKey const& publicKey, + ProtocolVersion protocol, Resource::Consumer consumer, std::unique_ptr&& 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(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 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 closed; + boost::optional 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 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 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); - - auto const protocol = BuildInfo::make_protocol(hello_.protoversion()); - JLOG(journal_.info()) << "Protocol: " << to_string(protocol); - JLOG(journal_.info()) << - "Public Key: " << toBase58 ( - TokenType::NodePublic, - publicKey_); + request_, remote_address_.address(), *sharedValue); + + 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(size)); - return error_code{}; } void @@ -969,12 +957,6 @@ PeerImp::onMessageEnd (std::uint16_t, charge (fee_); } -void -PeerImp::onMessage (std::shared_ptr const& m) -{ - fail("Deprecated TMHello"); -} - void PeerImp::onMessage (std::shared_ptr const& m) { @@ -1366,20 +1348,6 @@ PeerImp::onMessage(std::shared_ptr const& m) overlay_.lastLink(id_); } -void -PeerImp::onMessage (std::shared_ptr const& m) -{ - // This message is obsolete due to PeerFinder and - // we no longer provide a response to it. -} - -void -PeerImp::onMessage (std::shared_ptr const& m) -{ - // This message is obsolete due to PeerFinder and - // we no longer process it. -} - void PeerImp::onMessage (std::shared_ptr const& m) { @@ -2701,7 +2669,7 @@ PeerImp::getLedger (std::shared_ptr const& m) } } - Message::pointer oPacket = std::make_shared ( + auto oPacket = std::make_shared ( reply, protocol::mtLEDGER_DATA); send (oPacket); return; @@ -2806,7 +2774,7 @@ PeerImp::getLedger (std::shared_ptr const& m) "Got request for " << packet.nodeids().size() << " nodes at depth " << depth << ", return " << reply.nodes().size() << " nodes"; - Message::pointer oPacket = std::make_shared ( + auto oPacket = std::make_shared ( reply, protocol::mtLEDGER_DATA); send (oPacket); } diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/ripple/overlay/impl/PeerImp.h index 112ec2e8955..17eaf6b9dcb 100644 --- a/src/ripple/overlay/impl/PeerImp.h +++ b/src/ripple/overlay/impl/PeerImp.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -99,9 +100,6 @@ class PeerImp using endpoint_type = boost::asio::ip::tcp::endpoint; using waitable_timer = boost::asio::basic_waitable_timer; - // 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 @@ class PeerImp // OverlayImpl& overlay_; bool const m_inbound; + + // Protocol version to use for this link + ProtocolVersion protocol_; + State state_; // Current state std::atomic sanity_; clock_type::time_point insaneTime_; @@ -179,16 +181,15 @@ class PeerImp 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 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 send_queue_; + std::queue> send_queue_; bool gracefulClose_ = false; int large_sendq_ = 0; int no_ping_ = 0; @@ -230,10 +231,10 @@ class PeerImp 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 const& slot, http_request_type&& request, + PublicKey const& publicKey, + ProtocolVersion protocol, Resource::Consumer consumer, std::unique_ptr&& ssl_bundle, OverlayImpl& overlay); @@ -241,11 +242,10 @@ class PeerImp // VFALCO legacyPublicKey should be implied by the Slot template PeerImp (Application& app, std::unique_ptr&& ssl_bundle, - Buffers const& buffers, PeerFinder::Slot::ptr&& slot, + Buffers const& buffers, std::shared_ptr&& 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 @@ class PeerImp return p_journal_; } - PeerFinder::Slot::ptr const& + std::shared_ptr const& slot() { return slot_; @@ -275,7 +275,7 @@ class PeerImp // void - send (Message::pointer const& m) override; + send (std::shared_ptr const& m) override; /** Send a set of PeerFinder endpoints as a protocol message. */ template const& m, std::size_t size); @@ -477,7 +474,6 @@ class PeerImp onMessageEnd (std::uint16_t type, std::shared_ptr <::google::protobuf::Message> const& m); - void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); @@ -485,8 +481,6 @@ class PeerImp void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); - void onMessage (std::shared_ptr const& m); - void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); @@ -509,6 +503,8 @@ class PeerImp } //-------------------------------------------------------------------------- + void + setLedgerState(); // lockedRecentLock is passed as a reminder to callers that recentLock_ // must be locked. @@ -547,11 +543,10 @@ class PeerImp template PeerImp::PeerImp (Application& app, std::unique_ptr&& ssl_bundle, - Buffers const& buffers, PeerFinder::Slot::ptr&& slot, + Buffers const& buffers, std::shared_ptr&& 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&& 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)) diff --git a/src/ripple/overlay/impl/PeerSet.cpp b/src/ripple/overlay/impl/PeerSet.cpp index 086434f0f88..e17a04a6b1d 100644 --- a/src/ripple/overlay/impl/PeerSet.cpp +++ b/src/ripple/overlay/impl/PeerSet.cpp @@ -125,8 +125,7 @@ void PeerSet::sendRequest (const protocol::TMGetLedger& tmGL) if (mPeers.empty ()) return; - Message::pointer packet ( - std::make_shared (tmGL, protocol::mtGET_LEDGER)); + auto packet = std::make_shared(tmGL, protocol::mtGET_LEDGER); for (auto id : mPeers) { diff --git a/src/ripple/overlay/impl/ProtocolMessage.h b/src/ripple/overlay/impl/ProtocolMessage.h index 129fb295fa1..6dd047f8cf5 100644 --- a/src/ripple/overlay/impl/ProtocolMessage.h +++ b/src/ripple/overlay/impl/ProtocolMessage.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_OVERLAY_PROTOCOLMESSAGE_H_INCLUDED #define RIPPLE_OVERLAY_PROTOCOLMESSAGE_H_INCLUDED +#include #include #include #include @@ -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 -std::enable_if_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 +boost::optional parseMessageHeader( + BufferSequence const& bufs, + std::size_t size) +{ + auto iter = boost::asio::buffers_iterator::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 ::value>> +bool +invoke ( + MessageHeader const& header, + Buffers const& buffers, Handler& handler) { + auto const m = std::make_shared(); + ZeroCopyInputStream stream(buffers); - stream.Skip(Message::kHeaderBytes); - auto const m (std::make_shared()); + 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,80 +153,98 @@ std::pair invokeProtocolMessage (Buffers const& buffers, Handler& handler) { std::pair 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 (type, buffers, handler); break; - case protocol::mtMANIFESTS: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtPING: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtCLUSTER: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtGET_SHARD_INFO: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtSHARD_INFO: ec = detail::invoke(type, buffers, handler); break; - case protocol::mtGET_PEER_SHARD_INFO: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtPEER_SHARD_INFO: ec = detail::invoke(type, buffers, handler); break; - case protocol::mtGET_PEERS: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtPEERS: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtENDPOINTS: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtTRANSACTION: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtGET_LEDGER: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtLEDGER_DATA: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtPROPOSE_LEDGER: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtSTATUS_CHANGE: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtHAVE_SET: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtVALIDATION: ec = detail::invoke (type, buffers, handler); break; - case protocol::mtGET_OBJECTS: ec = detail::invoke (type, buffers, handler); break; + case protocol::mtMANIFESTS: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtPING: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtCLUSTER: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtGET_SHARD_INFO: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtSHARD_INFO: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtGET_PEER_SHARD_INFO: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtPEER_SHARD_INFO: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtENDPOINTS: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtTRANSACTION: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtGET_LEDGER: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtLEDGER_DATA: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtPROPOSE_LEDGER: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtSTATUS_CHANGE: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtHAVE_SET: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtVALIDATION: + success = detail::invoke(*header, buffers, handler); + break; + case protocol::mtGET_OBJECTS: + success = detail::invoke(*header, buffers, handler); + break; default: - ec = handler.onMessageUnknown (type); + handler.onMessageUnknown (header->message_type); + success = true; break; } - if (! ec) - result.first = size; - return result; -} + result.first = header->total_wire_size; -/** Write a protocol message to a streambuf. */ -template -void -write (Streambuf& streambuf, - ::google::protobuf::Message const& m, int type, - std::size_t blockBytes) -{ - auto const size = m.ByteSize(); - std::array v; - v[0] = static_cast((size >> 24) & 0xFF); - v[1] = static_cast((size >> 16) & 0xFF); - v[2] = static_cast((size >> 8) & 0xFF); - v[3] = static_cast( size & 0xFF); - v[4] = static_cast((type >> 8) & 0xFF); - v[5] = static_cast( type & 0xFF); - streambuf.commit(boost::asio::buffer_copy( - streambuf.prepare(Message::kHeaderBytes), - boost::asio::buffer(v))); - ZeroCopyOutputStream stream ( - streambuf, blockBytes); - m.SerializeToZeroCopyStream(&stream); + if (!success) + result.second = make_error_code(boost::system::errc::bad_message); + + return result; } } // ripple diff --git a/src/ripple/overlay/impl/ProtocolVersion.cpp b/src/ripple/overlay/impl/ProtocolVersion.cpp new file mode 100644 index 00000000000..bebcf4f1e70 --- /dev/null +++ b/src/ripple/overlay/impl/ProtocolVersion.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +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 +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 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 +negotiateProtocolVersion(boost::beast::string_view const& versions) +{ + auto const them = parseProtocolVersions(versions); + + boost::optional 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 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); +} + +} diff --git a/src/ripple/overlay/impl/ProtocolVersion.h b/src/ripple/overlay/impl/ProtocolVersion.h new file mode 100644 index 00000000000..6e26d2c278b --- /dev/null +++ b/src/ripple/overlay/impl/ProtocolVersion.h @@ -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 +#include +#include +#include +#include +#include + +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; + +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 +parseProtocolVersions(boost::beast::string_view const& s); + +/** Given a list of supported protocol versions, choose the one we prefer. */ +boost::optional +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 diff --git a/src/ripple/overlay/impl/TMHello.cpp b/src/ripple/overlay/impl/TMHello.cpp deleted file mode 100644 index 45a3d7c39e6..00000000000 --- a/src/ripple/overlay/impl/TMHello.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// 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> -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 -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 -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 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 -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(versions.back().first) << 16) | - (safe_cast(versions.back().second))); - hello.set_protoversionmin( - (safe_cast(versions.front().first) << 16) | - (safe_cast(versions.front().second))); - } - - { - // Required - auto const iter = h.find ("Public-Key"); - if (iter == h.end()) - return boost::none; - auto const pk = parseBase58( - 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 -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(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( - 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; -} - -} diff --git a/src/ripple/overlay/impl/TMHello.h b/src/ripple/overlay/impl/TMHello.h deleted file mode 100644 index ae0afee74dd..00000000000 --- a/src/ripple/overlay/impl/TMHello.h +++ /dev/null @@ -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 -#include -#include -#include - -#include -#include -#include -#include - -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 -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 -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 -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 -parse_ProtocolVersions(boost::beast::string_view const& s); - -} - -#endif diff --git a/src/ripple/overlay/impl/TrafficCount.cpp b/src/ripple/overlay/impl/TrafficCount.cpp index abb4789dbfb..8e0a69f062f 100644 --- a/src/ripple/overlay/impl/TrafficCount.cpp +++ b/src/ripple/overlay/impl/TrafficCount.cpp @@ -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) || diff --git a/src/ripple/overlay/predicates.h b/src/ripple/overlay/predicates.h index 9824609a233..ee88b6a35be 100644 --- a/src/ripple/overlay/predicates.h +++ b/src/ripple/overlay/predicates.h @@ -32,9 +32,9 @@ struct send_always { using return_type = void; - Message::pointer const& msg; + std::shared_ptr const& msg; - send_always(Message::pointer const& m) + send_always(std::shared_ptr const& m) : msg(m) { } @@ -52,10 +52,10 @@ struct send_if_pred { using return_type = void; - Message::pointer const& msg; + std::shared_ptr const& msg; Predicate const& predicate; - send_if_pred(Message::pointer const& m, Predicate const& p) + send_if_pred(std::shared_ptr 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 send_if_pred send_if ( - Message::pointer const& m, + std::shared_ptr const& m, Predicate const &f) { return send_if_pred(m, f); @@ -83,10 +83,10 @@ struct send_if_not_pred { using return_type = void; - Message::pointer const& msg; + std::shared_ptr const& msg; Predicate const& predicate; - send_if_not_pred(Message::pointer const& m, Predicate const& p) + send_if_not_pred(std::shared_ptr 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 send_if_not_pred send_if_not ( - Message::pointer const& m, + std::shared_ptr const& m, Predicate const &f) { return send_if_not_pred(m, f); diff --git a/src/ripple/peerfinder/PeerfinderManager.h b/src/ripple/peerfinder/PeerfinderManager.h index 6c7150d20e2..4d651f72476 100644 --- a/src/ripple/peerfinder/PeerfinderManager.h +++ b/src/ripple/peerfinder/PeerfinderManager.h @@ -174,7 +174,7 @@ class Manager 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 new_inbound_slot ( beast::IP::Endpoint const& local_endpoint, beast::IP::Endpoint const& remote_endpoint) = 0; @@ -182,21 +182,21 @@ class Manager 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 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 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 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 const& slot) = 0; /** Called when we received redirect IPs from a busy peer. */ virtual @@ -215,19 +215,19 @@ class Manager */ virtual bool - onConnected (Slot::ptr const& slot, + onConnected (std::shared_ptr 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 const& slot, PublicKey const& key, bool reserved) = 0; /** Returns a set of endpoints suitable for redirection. */ virtual std::vector - redirect (Slot::ptr const& slot) = 0; + redirect (std::shared_ptr const& slot) = 0; /** Return a set of addresses we should connect to. */ virtual @@ -235,7 +235,7 @@ class Manager autoconnect() = 0; virtual - std::vector>> + std::vector, std::vector>> buildEndpointsForPeers() = 0; /** Perform periodic activity. diff --git a/src/ripple/peerfinder/impl/Logic.h b/src/ripple/peerfinder/impl/Logic.h index d1b70f0f775..931789e025f 100644 --- a/src/ripple/peerfinder/impl/Logic.h +++ b/src/ripple/peerfinder/impl/Logic.h @@ -573,10 +573,10 @@ class Logic return none; } - std::vector>> + std::vector, std::vector>> buildEndpointsForPeers() { - std::vector>> result; + std::vector, std::vector>> result; std::lock_guard _(lock_); diff --git a/src/ripple/peerfinder/impl/PeerfinderManager.cpp b/src/ripple/peerfinder/impl/PeerfinderManager.cpp index 2a0cf623bba..6f4a660f625 100644 --- a/src/ripple/peerfinder/impl/PeerfinderManager.cpp +++ b/src/ripple/peerfinder/impl/PeerfinderManager.cpp @@ -119,7 +119,7 @@ class ManagerImp //-------------------------------------------------------------------------- - Slot::ptr + std::shared_ptr new_inbound_slot ( beast::IP::Endpoint const& local_endpoint, beast::IP::Endpoint const& remote_endpoint) override @@ -127,14 +127,14 @@ class ManagerImp return m_logic.new_inbound_slot (local_endpoint, remote_endpoint); } - Slot::ptr + std::shared_ptr 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 const& slot, Endpoints const& endpoints) override { SlotImp::ptr impl (std::dynamic_pointer_cast (slot)); @@ -142,14 +142,14 @@ class ManagerImp } void - on_closed (Slot::ptr const& slot) override + on_closed (std::shared_ptr const& slot) override { SlotImp::ptr impl (std::dynamic_pointer_cast (slot)); m_logic.on_closed (impl); } void - on_failure (Slot::ptr const& slot) override + on_failure (std::shared_ptr const& slot) override { SlotImp::ptr impl (std::dynamic_pointer_cast (slot)); m_logic.on_failure (impl); @@ -165,7 +165,7 @@ class ManagerImp //-------------------------------------------------------------------------- bool - onConnected (Slot::ptr const& slot, + onConnected (std::shared_ptr const& slot, beast::IP::Endpoint const& local_endpoint) override { SlotImp::ptr impl (std::dynamic_pointer_cast (slot)); @@ -173,14 +173,15 @@ class ManagerImp } Result - activate (Slot::ptr const& slot, PublicKey const& key, bool reserved) override + activate (std::shared_ptr const& slot, + PublicKey const& key, bool reserved) override { SlotImp::ptr impl (std::dynamic_pointer_cast (slot)); return m_logic.activate (impl, key, reserved); } std::vector - redirect (Slot::ptr const& slot) override + redirect (std::shared_ptr const& slot) override { SlotImp::ptr impl (std::dynamic_pointer_cast (slot)); return m_logic.redirect (impl); @@ -198,7 +199,7 @@ class ManagerImp m_logic.once_per_second(); } - std::vector>> + std::vector, std::vector>> buildEndpointsForPeers() override { return m_logic.buildEndpointsForPeers(); diff --git a/src/ripple/proto/ripple.proto b/src/ripple/proto/ripple.proto index ee4dafe2bfc..a82d628704a 100644 --- a/src/ripple/proto/ripple.proto +++ b/src/ripple/proto/ripple.proto @@ -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; - - // = 10; - // = 11; - // = 14; - // = 20; - // = 21; - // = 22; - // = 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 { diff --git a/src/ripple/protocol/BuildInfo.h b/src/ripple/protocol/BuildInfo.h index 5fdc8aa1772..505f0cdc9e0 100644 --- a/src/ripple/protocol/BuildInfo.h +++ b/src/ripple/protocol/BuildInfo.h @@ -25,8 +25,6 @@ namespace ripple { -/** Describes a Ripple/RTXP protocol version. */ -using ProtocolVersion = std::pair; /** 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 diff --git a/src/ripple/crypto/KeyType.h b/src/ripple/protocol/KeyType.h similarity index 95% rename from src/ripple/crypto/KeyType.h rename to src/ripple/protocol/KeyType.h index 32195c7647d..cb96f4a7a57 100644 --- a/src/ripple/crypto/KeyType.h +++ b/src/ripple/protocol/KeyType.h @@ -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 #include diff --git a/src/ripple/protocol/PublicKey.h b/src/ripple/protocol/PublicKey.h index d2e48508167..e4662decdde 100644 --- a/src/ripple/protocol/PublicKey.h +++ b/src/ripple/protocol/PublicKey.h @@ -21,7 +21,7 @@ #define RIPPLE_PROTOCOL_PUBLICKEY_H_INCLUDED #include -#include // move to protocol/ +#include #include #include #include diff --git a/src/ripple/protocol/SecretKey.h b/src/ripple/protocol/SecretKey.h index b371ac65a97..7565564bae3 100644 --- a/src/ripple/protocol/SecretKey.h +++ b/src/ripple/protocol/SecretKey.h @@ -22,7 +22,7 @@ #include #include -#include // move to protocol/ +#include #include #include #include diff --git a/src/ripple/protocol/impl/BuildInfo.cpp b/src/ripple/protocol/impl/BuildInfo.cpp index 8657dc015ca..36ab5fd9c59 100644 --- a/src/ripple/protocol/impl/BuildInfo.cpp +++ b/src/ripple/protocol/impl/BuildInfo.cpp @@ -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 ((version >> 16) & 0xffff), - static_cast (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 (p.first) << 16) + p.second; -} +} // BuildInfo } // ripple diff --git a/src/ripple/rpc/handlers/WalletPropose.cpp b/src/ripple/rpc/handlers/WalletPropose.cpp index b4561516f20..ab1692f312a 100644 --- a/src/ripple/rpc/handlers/WalletPropose.cpp +++ b/src/ripple/rpc/handlers/WalletPropose.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include #include #include #include +#include #include #include #include diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index c764bf7fa40..4212b1901e9 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -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); diff --git a/src/ripple/unity/overlay2.cpp b/src/ripple/unity/overlay2.cpp index 81d07cd949a..271c37c5d7c 100644 --- a/src/ripple/unity/overlay2.cpp +++ b/src/ripple/unity/overlay2.cpp @@ -18,10 +18,11 @@ //============================================================================== +#include #include #include #include -#include +#include #include #if DOXYGEN diff --git a/src/test/jtx/Account.h b/src/test/jtx/Account.h index f8ddf34c7d9..2e9e0b3aa69 100644 --- a/src/test/jtx/Account.h +++ b/src/test/jtx/Account.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TEST_JTX_ACCOUNT_H_INCLUDED #define RIPPLE_TEST_JTX_ACCOUNT_H_INCLUDED +#include #include #include -#include #include #include #include diff --git a/src/test/overlay/ProtocolVersion_test.cpp b/src/test/overlay/ProtocolVersion_test.cpp new file mode 100644 index 00000000000..cf55a76a4ba --- /dev/null +++ b/src/test/overlay/ProtocolVersion_test.cpp @@ -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 +#include + +namespace ripple { + +class ProtocolVersion_test : public beast::unit_test::suite +{ +private: + template + 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); + +} + diff --git a/src/test/overlay/TMHello_test.cpp b/src/test/overlay/TMHello_test.cpp deleted file mode 100644 index 979231d6330..00000000000 --- a/src/test/overlay/TMHello_test.cpp +++ /dev/null @@ -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 -#include - -namespace ripple { - -class TMHello_test : public beast::unit_test::suite -{ -private: - template - 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); - -} diff --git a/src/test/protocol/BuildInfo_test.cpp b/src/test/protocol/BuildInfo_test.cpp deleted file mode 100644 index 40840627fc2..00000000000 --- a/src/test/protocol/BuildInfo_test.cpp +++ /dev/null @@ -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 -#include -#include - -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 diff --git a/src/test/unity/overlay_test_unity.cpp b/src/test/unity/overlay_test_unity.cpp index 7b9cd76acbb..42881ee48d2 100644 --- a/src/test/unity/overlay_test_unity.cpp +++ b/src/test/unity/overlay_test_unity.cpp @@ -18,6 +18,6 @@ */ //============================================================================== +#include #include #include -#include \ No newline at end of file diff --git a/src/test/unity/protocol_test_unity.cpp b/src/test/unity/protocol_test_unity.cpp index ab1aecfc1d8..016770c11cd 100644 --- a/src/test/unity/protocol_test_unity.cpp +++ b/src/test/unity/protocol_test_unity.cpp @@ -18,7 +18,6 @@ */ //============================================================================== -#include #include #include #include