aboutsummaryrefslogtreecommitdiff
path: root/src/net.h
diff options
context:
space:
mode:
authorfanquake <fanquake@gmail.com>2023-09-08 10:06:32 +0100
committerfanquake <fanquake@gmail.com>2023-09-08 10:24:03 +0100
commit4e1a38c6df91f96ca8a2ef07413ffdb1d59c30cc (patch)
treeb34b18a1ff5cd9d7e1d534951090d75dd5c5eda0 /src/net.h
parent238d29aff9b43234e340a9cf17742b2be5d1e97d (diff)
parentdb9888feec48c6220a2fcf92865503bbbdab02a4 (diff)
Merge bitcoin/bitcoin#28196: BIP324 connection support
db9888feec48c6220a2fcf92865503bbbdab02a4 net: detect wrong-network V1 talking to V2Transport (Pieter Wuille) 91e1ef8684997fb4b3e8b64ef3935a936445066b test: add unit tests for V2Transport (Pieter Wuille) 297c8889975a18258d6cc39b1ec1e94fed6630fb net: make V2Transport preallocate receive buffer space (Pieter Wuille) 3ffa5fb49ee4a6d9502aa957093bd94058630282 net: make V2Transport send uniformly random number garbage bytes (Pieter Wuille) 0be752d9f8ca27320bc3e82498c7640fabd7e8de net: add short message encoding/decoding support to V2Transport (Pieter Wuille) 8da8642062fa2c7aa2f49995b832c3d0897e37ed net: make V2Transport auto-detect incoming V1 and fall back to it (Pieter Wuille) 13a7f01557272db652b3f333af3f06af6897253f net: add V2Transport class with subset of BIP324 functionality (Pieter Wuille) dc2d7eb810ef95b06620f334c198687579916435 crypto: Spanify EllSwiftPubKey constructor (Pieter Wuille) 5f4b2c6d79e81ee0445752ad558fcc17831f4b2f net: remove unused Transport::SetReceiveVersion (Pieter Wuille) c3fad1f29df093e8fd03d70eb43f25ee9d531bf7 net: add have_next_message argument to Transport::GetBytesToSend() (Pieter Wuille) Pull request description: This is part of #27634. This implements the BIP324 v2 transport (which implements all of what the BIP calls transport layer *and* application layer), though in a non-exposed way. It is tested through an extensive fuzz test, which verifies that v2 transports can talk to v2 transports, and v1 transports can talk to v2 transports, and a unit test that exercises a number of unusual scenarios. The transport is functionally complete, including: * Autodetection of incoming V1 connections. * Garbage, both sending and receiving. * Short message type IDs, both sending and receiving. * Ignore packets (receiving only, but tested in a unit test). * Session IDs are visible in `getpeerinfo` output (for manual comparison). Things that are not included, left for future PRs, are: * Actually using the v2 transport for connections. * Support for the `NODE_P2P_V2` service flag. * Retrying downgrade to V1 when attempted outbound V2 connections immediately fail. * P2P functional and unit tests ACKs for top commit: naumenkogs: ACK db9888feec48c6220a2fcf92865503bbbdab02a4 theStack: re-ACK db9888feec48c6220a2fcf92865503bbbdab02a4 mzumsande: Code Review ACK db9888feec48c6220a2fcf92865503bbbdab02a4 Tree-SHA512: 8906ac1e733a99e1f31c9111055611f706d80bbfc2edf6a07fa6e47b21bb65baacd1ff17993cbbf588063b2f5ad30b3af674a50c7bc8e8ebf4671483a21bbfeb
Diffstat (limited to 'src/net.h')
-rw-r--r--src/net.h267
1 files changed, 248 insertions, 19 deletions
diff --git a/src/net.h b/src/net.h
index 60a15fea55..cf7a240202 100644
--- a/src/net.h
+++ b/src/net.h
@@ -6,6 +6,7 @@
#ifndef BITCOIN_NET_H
#define BITCOIN_NET_H
+#include <bip324.h>
#include <chainparams.h>
#include <common/bloom.h>
#include <compat/compat.h>
@@ -266,8 +267,6 @@ public:
/** Returns true if the current message is complete (so GetReceivedMessage can be called). */
virtual bool ReceivedMessageComplete() const = 0;
- /** Set the deserialization context version for objects returned by GetReceivedMessage. */
- virtual void SetReceiveVersion(int version) = 0;
/** Feed wire bytes to the transport.
*
@@ -300,7 +299,8 @@ public:
* - Span<const uint8_t> to_send: span of bytes to be sent over the wire (possibly empty).
* - bool more: whether there will be more bytes to be sent after the ones in to_send are
* all sent (as signaled by MarkBytesSent()).
- * - const std::string& m_type: message type on behalf of which this is being sent.
+ * - const std::string& m_type: message type on behalf of which this is being sent
+ * ("" for bytes that are not on behalf of any message).
*/
using BytesToSend = std::tuple<
Span<const uint8_t> /*to_send*/,
@@ -308,19 +308,42 @@ public:
const std::string& /*m_type*/
>;
- /** Get bytes to send on the wire.
+ /** Get bytes to send on the wire, if any, along with other information about it.
*
* As a const function, it does not modify the transport's observable state, and is thus safe
* to be called multiple times.
*
- * The bytes returned by this function act as a stream which can only be appended to. This
- * means that with the exception of MarkBytesSent, operations on the transport can only append
- * to what is being returned.
+ * @param[in] have_next_message If true, the "more" return value reports whether more will
+ * be sendable after a SetMessageToSend call. It is set by the caller when they know
+ * they have another message ready to send, and only care about what happens
+ * after that. The have_next_message argument only affects this "more" return value
+ * and nothing else.
*
- * Note that m_type and to_send refer to data that is internal to the transport, and calling
- * any non-const function on this object may invalidate them.
+ * Effectively, there are three possible outcomes about whether there are more bytes
+ * to send:
+ * - Yes: the transport itself has more bytes to send later. For example, for
+ * V1Transport this happens during the sending of the header of a
+ * message, when there is a non-empty payload that follows.
+ * - No: the transport itself has no more bytes to send, but will have bytes to
+ * send if handed a message through SetMessageToSend. In V1Transport this
+ * happens when sending the payload of a message.
+ * - Blocked: the transport itself has no more bytes to send, and is also incapable
+ * of sending anything more at all now, if it were handed another
+ * message to send. This occurs in V2Transport before the handshake is
+ * complete, as the encryption ciphers are not set up for sending
+ * messages before that point.
+ *
+ * The boolean 'more' is true for Yes, false for Blocked, and have_next_message
+ * controls what is returned for No.
+ *
+ * @return a BytesToSend object. The to_send member returned acts as a stream which is only
+ * ever appended to. This means that with the exception of MarkBytesSent (which pops
+ * bytes off the front of later to_sends), operations on the transport can only append
+ * to what is being returned. Also note that m_type and to_send refer to data that is
+ * internal to the transport, and calling any non-const function on this object may
+ * invalidate them.
*/
- virtual BytesToSend GetBytesToSend() const noexcept = 0;
+ virtual BytesToSend GetBytesToSend(bool have_next_message) const noexcept = 0;
/** Report how many bytes returned by the last GetBytesToSend() have been sent.
*
@@ -392,14 +415,6 @@ public:
return WITH_LOCK(m_recv_mutex, return CompleteInternal());
}
- void SetReceiveVersion(int nVersionIn) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex)
- {
- AssertLockNotHeld(m_recv_mutex);
- LOCK(m_recv_mutex);
- hdrbuf.SetVersion(nVersionIn);
- vRecv.SetVersion(nVersionIn);
- }
-
bool ReceivedBytes(Span<const uint8_t>& msg_bytes) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex)
{
AssertLockNotHeld(m_recv_mutex);
@@ -416,7 +431,221 @@ public:
CNetMessage GetReceivedMessage(std::chrono::microseconds time, bool& reject_message) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);
bool SetMessageToSend(CSerializedNetMsg& msg) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
- BytesToSend GetBytesToSend() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
+ BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
+ void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
+ size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
+};
+
+class V2Transport final : public Transport
+{
+private:
+ /** Contents of the version packet to send. BIP324 stipulates that senders should leave this
+ * empty, and receivers should ignore it. Future extensions can change what is sent as long as
+ * an empty version packet contents is interpreted as no extensions supported. */
+ static constexpr std::array<std::byte, 0> VERSION_CONTENTS = {};
+
+ /** The length of the V1 prefix to match bytes initially received by responders with to
+ * determine if their peer is speaking V1 or V2. */
+ static constexpr size_t V1_PREFIX_LEN = 12;
+
+ // The sender side and receiver side of V2Transport are state machines that are transitioned
+ // through, based on what has been received. The receive state corresponds to the contents of,
+ // and bytes received to, the receive buffer. The send state controls what can be appended to
+ // the send buffer and what can be sent from it.
+
+ /** State type that defines the current contents of the receive buffer and/or how the next
+ * received bytes added to it will be interpreted.
+ *
+ * Diagram:
+ *
+ * start(responder)
+ * |
+ * | start(initiator) /---------\
+ * | | | |
+ * v v v |
+ * KEY_MAYBE_V1 -> KEY -> GARB_GARBTERM -> GARBAUTH -> VERSION -> APP -> APP_READY
+ * |
+ * \-------> V1
+ */
+ enum class RecvState : uint8_t {
+ /** (Responder only) either v2 public key or v1 header.
+ *
+ * This is the initial state for responders, before data has been received to distinguish
+ * v1 from v2 connections. When that happens, the state becomes either KEY (for v2) or V1
+ * (for v1). */
+ KEY_MAYBE_V1,
+
+ /** Public key.
+ *
+ * This is the initial state for initiators, during which the other side's public key is
+ * received. When that information arrives, the ciphers get initialized and the state
+ * becomes GARB_GARBTERM. */
+ KEY,
+
+ /** Garbage and garbage terminator.
+ *
+ * Whenever a byte is received, the last 16 bytes are compared with the expected garbage
+ * terminator. When that happens, the state becomes GARBAUTH. If no matching terminator is
+ * received in 4111 bytes (4095 for the maximum garbage length, and 16 bytes for the
+ * terminator), the connection aborts. */
+ GARB_GARBTERM,
+
+ /** Garbage authentication packet.
+ *
+ * A packet is received, and decrypted/verified with AAD set to the garbage received during
+ * the GARB_GARBTERM state. If that succeeds, the state becomes VERSION. If it fails the
+ * connection aborts. */
+ GARBAUTH,
+
+ /** Version packet.
+ *
+ * A packet is received, and decrypted/verified. If that succeeds, the state becomes APP,
+ * and the decrypted contents is interpreted as version negotiation (currently, that means
+ * ignoring it, but it can be used for negotiating future extensions). If it fails, the
+ * connection aborts. */
+ VERSION,
+
+ /** Application packet.
+ *
+ * A packet is received, and decrypted/verified. If that succeeds, the state becomes
+ * APP_READY and the decrypted contents is kept in m_recv_decode_buffer until it is
+ * retrieved as a message by GetMessage(). */
+ APP,
+
+ /** Nothing (an application packet is available for GetMessage()).
+ *
+ * Nothing can be received in this state. When the message is retrieved by GetMessage,
+ * the state becomes APP again. */
+ APP_READY,
+
+ /** Nothing (this transport is using v1 fallback).
+ *
+ * All receive operations are redirected to m_v1_fallback. */
+ V1,
+ };
+
+ /** State type that controls the sender side.
+ *
+ * Diagram:
+ *
+ * start(responder)
+ * |
+ * | start(initiator)
+ * | |
+ * v v
+ * MAYBE_V1 -> AWAITING_KEY -> READY
+ * |
+ * \-----> V1
+ */
+ enum class SendState : uint8_t {
+ /** (Responder only) Not sending until v1 or v2 is detected.
+ *
+ * This is the initial state for responders. The send buffer contains the public key to
+ * send, but nothing is sent in this state yet. When the receiver determines whether this
+ * is a V1 or V2 connection, the sender state becomes AWAITING_KEY (for v2) or V1 (for v1).
+ */
+ MAYBE_V1,
+
+ /** Waiting for the other side's public key.
+ *
+ * This is the initial state for initiators. The public key is sent out. When the receiver
+ * receives the other side's public key and transitions to GARB_GARBTERM, the sender state
+ * becomes READY. */
+ AWAITING_KEY,
+
+ /** Normal sending state.
+ *
+ * In this state, the ciphers are initialized, so packets can be sent. When this state is
+ * entered, the garbage, garbage terminator, garbage authentication packet, and version
+ * packet are appended to the send buffer (in addition to the key which may still be
+ * there). In this state a message can be provided if the send buffer is empty. */
+ READY,
+
+ /** This transport is using v1 fallback.
+ *
+ * All send operations are redirected to m_v1_fallback. */
+ V1,
+ };
+
+ /** Cipher state. */
+ BIP324Cipher m_cipher;
+ /** Whether we are the initiator side. */
+ const bool m_initiating;
+ /** NodeId (for debug logging). */
+ const NodeId m_nodeid;
+ /** Encapsulate a V1Transport to fall back to. */
+ V1Transport m_v1_fallback;
+
+ /** Lock for receiver-side fields. */
+ mutable Mutex m_recv_mutex ACQUIRED_BEFORE(m_send_mutex);
+ /** In {GARBAUTH, VERSION, APP}, the decrypted packet length, if m_recv_buffer.size() >=
+ * BIP324Cipher::LENGTH_LEN. Unspecified otherwise. */
+ uint32_t m_recv_len GUARDED_BY(m_recv_mutex) {0};
+ /** Receive buffer; meaning is determined by m_recv_state. */
+ std::vector<uint8_t> m_recv_buffer GUARDED_BY(m_recv_mutex);
+ /** During GARBAUTH, the garbage received during GARB_GARBTERM. */
+ std::vector<uint8_t> m_recv_garbage GUARDED_BY(m_recv_mutex);
+ /** Buffer to put decrypted contents in, for converting to CNetMessage. */
+ std::vector<uint8_t> m_recv_decode_buffer GUARDED_BY(m_recv_mutex);
+ /** Deserialization type. */
+ const int m_recv_type;
+ /** Deserialization version number. */
+ const int m_recv_version;
+ /** Current receiver state. */
+ RecvState m_recv_state GUARDED_BY(m_recv_mutex);
+
+ /** Lock for sending-side fields. If both sending and receiving fields are accessed,
+ * m_recv_mutex must be acquired before m_send_mutex. */
+ mutable Mutex m_send_mutex ACQUIRED_AFTER(m_recv_mutex);
+ /** The send buffer; meaning is determined by m_send_state. */
+ std::vector<uint8_t> m_send_buffer GUARDED_BY(m_send_mutex);
+ /** How many bytes from the send buffer have been sent so far. */
+ uint32_t m_send_pos GUARDED_BY(m_send_mutex) {0};
+ /** Type of the message being sent. */
+ std::string m_send_type GUARDED_BY(m_send_mutex);
+ /** Current sender state. */
+ SendState m_send_state GUARDED_BY(m_send_mutex);
+
+ /** Change the receive state. */
+ void SetReceiveState(RecvState recv_state) noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex);
+ /** Change the send state. */
+ void SetSendState(SendState send_state) noexcept EXCLUSIVE_LOCKS_REQUIRED(m_send_mutex);
+ /** Given a packet's contents, find the message type (if valid), and strip it from contents. */
+ static std::optional<std::string> GetMessageType(Span<const uint8_t>& contents) noexcept;
+ /** Determine how many received bytes can be processed in one go (not allowed in V1 state). */
+ size_t GetMaxBytesToProcess() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex);
+ /** Process bytes in m_recv_buffer, while in KEY_MAYBE_V1 state. */
+ void ProcessReceivedMaybeV1Bytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex);
+ /** Process bytes in m_recv_buffer, while in KEY state. */
+ bool ProcessReceivedKeyBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex);
+ /** Process bytes in m_recv_buffer, while in GARB_GARBTERM state. */
+ bool ProcessReceivedGarbageBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex);
+ /** Process bytes in m_recv_buffer, while in GARBAUTH/VERSION/APP state. */
+ bool ProcessReceivedPacketBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex);
+
+public:
+ static constexpr uint32_t MAX_GARBAGE_LEN = 4095;
+
+ /** Construct a V2 transport with securely generated random keys.
+ *
+ * @param[in] nodeid the node's NodeId (only for debug log output).
+ * @param[in] initiating whether we are the initiator side.
+ * @param[in] type_in the serialization type of returned CNetMessages.
+ * @param[in] version_in the serialization version of returned CNetMessages.
+ */
+ V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in) noexcept;
+
+ /** Construct a V2 transport with specified keys and garbage (test use only). */
+ V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, Span<const uint8_t> garbage) noexcept;
+
+ // Receive side functions.
+ bool ReceivedMessageComplete() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);
+ bool ReceivedBytes(Span<const uint8_t>& msg_bytes) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex);
+ CNetMessage GetReceivedMessage(std::chrono::microseconds time, bool& reject_message) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);
+
+ // Send side functions.
+ bool SetMessageToSend(CSerializedNetMsg& msg) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
+ BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
};