Skip to content

Commit cacb30c

Browse files
dhruvjonasschnelli
andcommitted
Add BIP324 v2 transport serializer and deserializer
Co-authored-by: Jonas Schnelli <[email protected]>
1 parent 22d578d commit cacb30c

File tree

4 files changed

+367
-12
lines changed

4 files changed

+367
-12
lines changed

src/net.cpp

+191-5
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,19 @@
1616
#include <compat/compat.h>
1717
#include <consensus/consensus.h>
1818
#include <crypto/sha256.h>
19-
#include <node/eviction.h>
2019
#include <fs.h>
2120
#include <i2p.h>
2221
#include <net_permissions.h>
2322
#include <netaddress.h>
2423
#include <netbase.h>
24+
#include <node/eviction.h>
2525
#include <node/interface_ui.h>
2626
#include <protocol.h>
2727
#include <random.h>
2828
#include <scheduler.h>
2929
#include <util/sock.h>
3030
#include <util/strencodings.h>
31+
#include <util/string.h>
3132
#include <util/syscall_sandbox.h>
3233
#include <util/system.h>
3334
#include <util/thread.h>
@@ -64,6 +65,8 @@ static_assert (MAX_BLOCK_RELAY_ONLY_ANCHORS <= static_cast<size_t>(MAX_BLOCK_REL
6465
/** Anchor IP address database file name */
6566
const char* const ANCHORS_DATABASE_FILENAME = "anchors.dat";
6667

68+
static constexpr uint64_t V2_MAX_CONTENTS_LENGTH = 0x01000000 - 1; // 2^24 - 1
69+
6770
// How often to dump addresses to peers.dat
6871
static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15};
6972

@@ -109,6 +112,8 @@ const std::string NET_MESSAGE_TYPE_OTHER = "*other*";
109112
static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8]
110113
static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8]
111114
static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256("addrcache")[0:8]
115+
116+
static constexpr uint8_t V2_MAX_MSG_TYPE_LEN = 12; // maximum length for V2 (BIP324) string message types
112117
//
113118
// Global state variables
114119
//
@@ -668,7 +673,14 @@ bool CNode::ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete)
668673
if (m_deserializer->Complete()) {
669674
// decompose a transport agnostic CNetMessage from the deserializer
670675
bool reject_message{false};
671-
CNetMessage msg = m_deserializer->GetMessage(time, reject_message);
676+
bool disconnect{false};
677+
CNetMessage msg = m_deserializer->GetMessage(time, reject_message, disconnect);
678+
679+
if (disconnect) {
680+
// v2 p2p incorrect MAC tag. Disconnect from peer.
681+
return false;
682+
}
683+
672684
if (reject_message) {
673685
// Message deserialization failed. Drop the message but don't disconnect the peer.
674686
// store the size of the corrupt message
@@ -760,10 +772,12 @@ const uint256& V1TransportDeserializer::GetMessageHash() const
760772
return data_hash;
761773
}
762774

763-
CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message)
775+
CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message, bool& disconnect)
764776
{
765777
// Initialize out parameter
766778
reject_message = false;
779+
disconnect = false;
780+
767781
// decompose a single CNetMessage from the TransportDeserializer
768782
CNetMessage msg(std::move(vRecv));
769783

@@ -785,6 +799,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds
785799
HexStr(Span{hash}.first(CMessageHeader::CHECKSUM_SIZE)),
786800
HexStr(hdr.pchChecksum),
787801
m_node_id);
802+
// TODO: Should we disconnect the v1 peer in this case?
788803
reject_message = true;
789804
} else if (!hdr.IsCommandValid()) {
790805
LogPrint(BCLog::NET, "Header error: Invalid message type (%s, %u bytes), peer=%d\n",
@@ -797,7 +812,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds
797812
return msg;
798813
}
799814

800-
void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
815+
bool V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
801816
{
802817
// create dbl-sha256 checksum
803818
uint256 hash = Hash(msg.data);
@@ -809,6 +824,174 @@ void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vec
809824
// serialize header
810825
header.reserve(CMessageHeader::HEADER_SIZE);
811826
CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, header, 0, hdr};
827+
return true;
828+
}
829+
830+
int V2TransportDeserializer::readHeader(Span<const uint8_t> pkt_bytes)
831+
{
832+
// copy data to temporary parsing buffer
833+
const size_t remaining = BIP324_LENGTH_FIELD_LEN - m_hdr_pos;
834+
const size_t copy_bytes = std::min<unsigned int>(remaining, pkt_bytes.size());
835+
836+
memcpy(&vRecv[m_hdr_pos], pkt_bytes.data(), copy_bytes);
837+
m_hdr_pos += copy_bytes;
838+
839+
// if we don't have the encrypted length yet, exit
840+
if (m_hdr_pos < BIP324_LENGTH_FIELD_LEN) {
841+
return copy_bytes;
842+
}
843+
844+
// we have the 3 bytes encrypted packet length at this point
845+
std::array<std::byte, BIP324_LENGTH_FIELD_LEN> encrypted_pkt_len;
846+
memcpy(encrypted_pkt_len.data(), vRecv.data(), BIP324_LENGTH_FIELD_LEN);
847+
848+
// the encrypted packet data = bip324 header + contents (message type + message payload)
849+
m_contents_size = m_cipher_suite->DecryptLength(encrypted_pkt_len);
850+
851+
// m_contents_size is the size of the p2p message
852+
if (m_contents_size > V2_MAX_CONTENTS_LENGTH) {
853+
return -1;
854+
}
855+
856+
// switch state to reading message data
857+
m_in_data = true;
858+
859+
return copy_bytes;
860+
}
861+
862+
int V2TransportDeserializer::readData(Span<const uint8_t> pkt_bytes)
863+
{
864+
// Read the BIP324 encrypted packet data.
865+
const size_t remaining = BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION - m_data_pos;
866+
const size_t copy_bytes = std::min<unsigned int>(remaining, pkt_bytes.size());
867+
868+
// extend buffer, respect previous copied encrypted length
869+
if (vRecv.size() < BIP324_LENGTH_FIELD_LEN + m_data_pos + copy_bytes) {
870+
// Allocate up to 256 KiB ahead, but never more than the total message size.
871+
vRecv.resize(BIP324_LENGTH_FIELD_LEN + std::min(BIP324_HEADER_LEN + m_contents_size, m_data_pos + copy_bytes + 256 * 1024) + RFC8439_EXPANSION, std::byte{0x00});
872+
}
873+
874+
memcpy(&vRecv[BIP324_LENGTH_FIELD_LEN + m_data_pos], pkt_bytes.data(), copy_bytes);
875+
m_data_pos += copy_bytes;
876+
877+
return copy_bytes;
878+
}
879+
880+
CNetMessage V2TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message, bool& disconnect)
881+
{
882+
const size_t min_contents_size = 1; // BIP324 1-byte message type id is the minimum contents
883+
884+
// Initialize out parameters
885+
reject_message = (vRecv.size() < V2_MIN_PACKET_LENGTH + min_contents_size);
886+
disconnect = false;
887+
888+
// In v2, vRecv contains:
889+
// 3 bytes of encrypted packet length
890+
// 1-byte encrypted bip324 header
891+
// variable length encrypted contents(message type and message payload) and
892+
// mac tag
893+
assert(Complete());
894+
895+
std::string msg_type;
896+
897+
BIP324HeaderFlags flags;
898+
size_t msg_type_size = 1; // at least one byte needed for message type
899+
if (m_cipher_suite->Crypt({},
900+
Span{reinterpret_cast<const std::byte*>(vRecv.data() + BIP324_LENGTH_FIELD_LEN), BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION},
901+
Span{reinterpret_cast<std::byte*>(vRecv.data()), m_contents_size}, flags, false)) {
902+
// MAC check was successful
903+
vRecv.resize(m_contents_size);
904+
reject_message = reject_message || (BIP324HeaderFlags(BIP324_IGNORE & flags) != BIP324_NONE);
905+
906+
if (!reject_message) {
907+
uint8_t size_or_shortid = 0;
908+
try {
909+
vRecv >> size_or_shortid;
910+
} catch (const std::ios_base::failure&) {
911+
LogPrint(BCLog::NET, "Invalid message type, peer=%d\n", m_node_id);
912+
reject_message = true;
913+
}
914+
915+
if (size_or_shortid > 0 && size_or_shortid <= V2_MAX_MSG_TYPE_LEN && vRecv.size() >= size_or_shortid) {
916+
// first byte is a number between 1 and 12. Must be a string command.
917+
// use direct read since we already read the varlen size
918+
msg_type.resize(size_or_shortid);
919+
vRecv.read(MakeWritableByteSpan(msg_type));
920+
msg_type_size += size_or_shortid;
921+
} else {
922+
auto mtype = GetMessageTypeFromShortID(size_or_shortid);
923+
if (mtype.has_value()) {
924+
msg_type = mtype.value();
925+
} else {
926+
// unknown-short-id results in a valid but unknown message (will be skipped)
927+
msg_type = "unknown-" + ToString(size_or_shortid);
928+
}
929+
}
930+
}
931+
} else {
932+
// Invalid mac tag
933+
LogPrint(BCLog::NET, "Invalid v2 mac tag, peer=%d\n", m_node_id);
934+
disconnect = true;
935+
reject_message = true;
936+
}
937+
938+
// we'll always return a CNetMessage (even if decryption fails)
939+
// decompose a single CNetMessage from the TransportDeserializer
940+
CNetMessage msg(std::move(vRecv));
941+
msg.m_type = msg_type;
942+
msg.m_time = time;
943+
944+
if (!reject_message) {
945+
msg.m_message_size = m_contents_size - msg_type_size;
946+
msg.m_raw_message_size = V2_MIN_PACKET_LENGTH + m_contents_size; // raw wire size
947+
}
948+
949+
Reset();
950+
return msg;
951+
}
952+
953+
bool V2TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
954+
{
955+
size_t serialized_msg_type_size = 1; // short-IDs are 1 byte
956+
std::optional<uint8_t> short_msg_type = GetShortIDFromMessageType(msg.m_type);
957+
if (!short_msg_type) {
958+
// message type without an assigned short-ID
959+
assert(msg.m_type.size() <= V2_MAX_MSG_TYPE_LEN);
960+
// encode as varstr, max 12 chars
961+
serialized_msg_type_size = ::GetSerializeSize(msg.m_type, PROTOCOL_VERSION);
962+
}
963+
964+
std::vector<unsigned char> msg_type_bytes(serialized_msg_type_size);
965+
// append the short-ID or the varstr of the msg type
966+
CVectorWriter vector_writer(SER_NETWORK, INIT_PROTO_VERSION, msg_type_bytes, 0);
967+
if (short_msg_type) {
968+
// append the single byte short ID
969+
vector_writer << short_msg_type.value();
970+
} else {
971+
// or the ASCII command string
972+
vector_writer << msg.m_type;
973+
}
974+
975+
// insert message type directly into the CSerializedNetMsg data buffer (insert at begin)
976+
// TODO: if we refactor the BIP324CipherSuite::Crypt() function to allow separate buffers for
977+
// the message type and payload we could avoid a insert and thus a potential reallocation
978+
msg.data.insert(msg.data.begin(), msg_type_bytes.begin(), msg_type_bytes.end());
979+
980+
auto contents_size = msg.data.size();
981+
auto encrypted_pkt_size = V2_MIN_PACKET_LENGTH + contents_size;
982+
// resize the message buffer to make space for the MAC tag
983+
msg.data.resize(encrypted_pkt_size, 0);
984+
985+
BIP324HeaderFlags flags{BIP324_NONE};
986+
// encrypt the payload, this should always succeed (controlled buffers, don't check the MAC during encrypting)
987+
auto success = m_cipher_suite->Crypt({},
988+
Span{reinterpret_cast<const std::byte*>(msg.data.data()), contents_size},
989+
Span{reinterpret_cast<std::byte*>(msg.data.data()), encrypted_pkt_size},
990+
flags, true);
991+
if (!success) {
992+
LogPrint(BCLog::NET, "error in v2 p2p encryption for message type: %s\n", msg.m_type);
993+
}
994+
return success;
812995
}
813996

814997
size_t CConnman::SocketSendData(CNode& node) const
@@ -2790,7 +2973,10 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
27902973

27912974
// make sure we use the appropriate network transport format
27922975
std::vector<unsigned char> serializedHeader;
2793-
pnode->m_serializer->prepareForTransport(msg, serializedHeader);
2976+
if (!pnode->m_serializer->prepareForTransport(msg, serializedHeader)) {
2977+
return;
2978+
}
2979+
27942980
size_t nTotalSize = nMessageSize + serializedHeader.size();
27952981

27962982
size_t nBytesSent = 0;

0 commit comments

Comments
 (0)