0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-09 10:43:19 -05:00

net: expose transport types/session IDs of connections in RPC and logs

Co-authored-by: Dhruv Mehta <856960+dhruv@users.noreply.github.com>
This commit is contained in:
Pieter Wuille 2023-07-30 23:26:04 -04:00
parent 432a62c4dc
commit b815cce50e
9 changed files with 104 additions and 4 deletions

View file

@ -667,6 +667,9 @@ void CNode::CopyStats(CNodeStats& stats)
LOCK(cs_vRecv); LOCK(cs_vRecv);
X(mapRecvBytesPerMsgType); X(mapRecvBytesPerMsgType);
X(nRecvBytes); X(nRecvBytes);
Transport::Info info = m_transport->GetInfo();
stats.m_transport_type = info.transport_type;
if (info.session_id) stats.m_session_id = HexStr(*info.session_id);
} }
X(m_permission_flags); X(m_permission_flags);
@ -734,6 +737,11 @@ V1Transport::V1Transport(const NodeId node_id, int nTypeIn, int nVersionIn) noex
Reset(); Reset();
} }
Transport::Info V1Transport::GetInfo() const noexcept
{
return {.transport_type = TransportProtocolType::V1, .session_id = {}};
}
int V1Transport::readHeader(Span<const uint8_t> msg_bytes) int V1Transport::readHeader(Span<const uint8_t> msg_bytes)
{ {
AssertLockHeld(m_recv_mutex); AssertLockHeld(m_recv_mutex);
@ -1582,6 +1590,27 @@ size_t V2Transport::GetSendMemoryUsage() const noexcept
return sizeof(m_send_buffer) + memusage::DynamicUsage(m_send_buffer); return sizeof(m_send_buffer) + memusage::DynamicUsage(m_send_buffer);
} }
Transport::Info V2Transport::GetInfo() const noexcept
{
AssertLockNotHeld(m_recv_mutex);
LOCK(m_recv_mutex);
if (m_recv_state == RecvState::V1) return m_v1_fallback.GetInfo();
Transport::Info info;
// Do not report v2 and session ID until the version packet has been received
// and verified (confirming that the other side very likely has the same keys as us).
if (m_recv_state != RecvState::KEY_MAYBE_V1 && m_recv_state != RecvState::KEY &&
m_recv_state != RecvState::GARB_GARBTERM && m_recv_state != RecvState::VERSION) {
info.transport_type = TransportProtocolType::V2;
info.session_id = uint256(MakeUCharSpan(m_cipher.GetSessionID()));
} else {
info.transport_type = TransportProtocolType::DETECTING;
}
return info;
}
std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
{ {
auto it = node.vSendMsg.begin(); auto it = node.vSendMsg.begin();

View file

@ -232,6 +232,10 @@ public:
Network m_network; Network m_network;
uint32_t m_mapped_as; uint32_t m_mapped_as;
ConnectionType m_conn_type; ConnectionType m_conn_type;
/** Transport protocol type. */
TransportProtocolType m_transport_type;
/** BIP324 session id string in hex, if any. */
std::string m_session_id;
}; };
@ -268,6 +272,15 @@ class Transport {
public: public:
virtual ~Transport() {} virtual ~Transport() {}
struct Info
{
TransportProtocolType transport_type;
std::optional<uint256> session_id;
};
/** Retrieve information about this transport. */
virtual Info GetInfo() const noexcept = 0;
// 1. Receiver side functions, for decoding bytes received on the wire into transport protocol // 1. Receiver side functions, for decoding bytes received on the wire into transport protocol
// agnostic CNetMessage (message type & payload) objects. // agnostic CNetMessage (message type & payload) objects.
@ -426,6 +439,8 @@ public:
return WITH_LOCK(m_recv_mutex, return CompleteInternal()); return WITH_LOCK(m_recv_mutex, return CompleteInternal());
} }
Info GetInfo() const noexcept override;
bool ReceivedBytes(Span<const uint8_t>& msg_bytes) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex) bool ReceivedBytes(Span<const uint8_t>& msg_bytes) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex)
{ {
AssertLockNotHeld(m_recv_mutex); AssertLockNotHeld(m_recv_mutex);
@ -664,6 +679,7 @@ public:
// Miscellaneous functions. // Miscellaneous functions.
bool ShouldReconnectV1() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex); bool ShouldReconnectV1() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex);
Info GetInfo() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);
}; };
struct CNodeOptions struct CNodeOptions

View file

@ -3585,13 +3585,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return; return;
} }
if (!pfrom.IsInboundConn()) { // Log succesful connections unconditionally for outbound, but not for inbound as those
// can be triggered by an attacker at high rate.
if (!pfrom.IsInboundConn() || LogAcceptCategory(BCLog::NET, BCLog::Level::Debug)) {
const auto mapped_as{m_connman.GetMappedAS(pfrom.addr)}; const auto mapped_as{m_connman.GetMappedAS(pfrom.addr)};
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s%s (%s)\n", LogPrintf("New %s %s peer connected: version: %d, blocks=%d, peer=%d%s%s\n",
pfrom.ConnectionTypeAsString(),
TransportTypeAsString(pfrom.m_transport->GetInfo().transport_type),
pfrom.nVersion.load(), peer->m_starting_height, pfrom.nVersion.load(), peer->m_starting_height,
pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""), pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""),
(mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""), (mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""));
pfrom.ConnectionTypeAsString());
} }
if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) { if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) {

View file

@ -24,3 +24,17 @@ std::string ConnectionTypeAsString(ConnectionType conn_type)
assert(false); assert(false);
} }
std::string TransportTypeAsString(TransportProtocolType transport_type)
{
switch (transport_type) {
case TransportProtocolType::DETECTING:
return "detecting";
case TransportProtocolType::V1:
return "v1";
case TransportProtocolType::V2:
return "v2";
} // no default case, so the compiler can warn about missing cases
assert(false);
}

View file

@ -6,6 +6,7 @@
#define BITCOIN_NODE_CONNECTION_TYPES_H #define BITCOIN_NODE_CONNECTION_TYPES_H
#include <string> #include <string>
#include <stdint.h>
/** Different types of connections to a peer. This enum encapsulates the /** Different types of connections to a peer. This enum encapsulates the
* information we have available at the time of opening or accepting the * information we have available at the time of opening or accepting the
@ -79,4 +80,14 @@ enum class ConnectionType {
/** Convert ConnectionType enum to a string value */ /** Convert ConnectionType enum to a string value */
std::string ConnectionTypeAsString(ConnectionType conn_type); std::string ConnectionTypeAsString(ConnectionType conn_type);
/** Transport layer version */
enum class TransportProtocolType : uint8_t {
DETECTING, //!< Peer could be v1 or v2
V1, //!< Unencrypted, plaintext protocol
V2, //!< BIP324 protocol
};
/** Convert TransportProtocolType enum to a string value */
std::string TransportTypeAsString(TransportProtocolType transport_type);
#endif // BITCOIN_NODE_CONNECTION_TYPES_H #endif // BITCOIN_NODE_CONNECTION_TYPES_H

View file

@ -45,6 +45,12 @@ const std::vector<std::string> CONNECTION_TYPE_DOC{
"feeler (short-lived automatic connection for testing addresses)" "feeler (short-lived automatic connection for testing addresses)"
}; };
const std::vector<std::string> TRANSPORT_TYPE_DOC{
"detecting (peer could be v1 or v2)",
"v1 (plaintext transport protocol)",
"v2 (BIP324 encrypted transport protocol)"
};
static RPCHelpMan getconnectioncount() static RPCHelpMan getconnectioncount()
{ {
return RPCHelpMan{"getconnectioncount", return RPCHelpMan{"getconnectioncount",
@ -164,6 +170,8 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n" {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n"
"Please note this output is unlikely to be stable in upcoming releases as we iterate to\n" "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n"
"best capture connection behaviors."}, "best capture connection behaviors."},
{RPCResult::Type::STR, "transport_protocol_type", "Type of transport protocol: \n" + Join(TRANSPORT_TYPE_DOC, ",\n") + ".\n"},
{RPCResult::Type::STR, "session_id", "The session ID for this connection, or \"\" if there is none (\"v2\" transport protocol only).\n"},
}}, }},
}}, }},
}, },
@ -268,6 +276,8 @@ static RPCHelpMan getpeerinfo()
} }
obj.pushKV("bytesrecv_per_msg", recvPerMsgType); obj.pushKV("bytesrecv_per_msg", recvPerMsgType);
obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type)); obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type));
obj.pushKV("transport_protocol_type", TransportTypeAsString(stats.m_transport_type));
obj.pushKV("session_id", stats.m_session_id);
ret.push_back(obj); ret.push_back(obj);
} }

View file

@ -328,6 +328,9 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
// Make sure all expected messages were received. // Make sure all expected messages were received.
assert(expected[0].empty()); assert(expected[0].empty());
assert(expected[1].empty()); assert(expected[1].empty());
// Compare session IDs.
assert(transports[0]->GetInfo().session_id == transports[1]->GetInfo().session_id);
} }
std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept

View file

@ -1321,6 +1321,14 @@ public:
SendPacket(contents); SendPacket(contents);
} }
/** Test whether the transport's session ID matches the session ID we expect. */
void CompareSessionIDs() const
{
auto info = m_transport.GetInfo();
BOOST_CHECK(info.session_id);
BOOST_CHECK(uint256(MakeUCharSpan(m_cipher.GetSessionID())) == *info.session_id);
}
/** Introduce a bit error in the data scheduled to be sent. */ /** Introduce a bit error in the data scheduled to be sent. */
void Damage() void Damage()
{ {
@ -1346,6 +1354,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000)); auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000));
auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000)); auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendMessage(uint8_t(4), msg_data_1); // cmpctblock short id tester.SendMessage(uint8_t(4), msg_data_1); // cmpctblock short id
@ -1386,6 +1395,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000)); auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000));
auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000)); auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendMessage(uint8_t(14), msg_data_1); // inv short id tester.SendMessage(uint8_t(14), msg_data_1); // inv short id
@ -1439,6 +1449,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
for (unsigned d = 0; d < num_decoys_1; ++d) { for (unsigned d = 0; d < num_decoys_1; ++d) {
auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000)); auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true); tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true);
@ -1516,6 +1527,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that receiving 4M payload works auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that receiving 4M payload works
auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that sending 4M payload works auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that sending 4M payload works
tester.SendMessage(uint8_t(InsecureRandRange(223) + 33), {}); // unknown short id tester.SendMessage(uint8_t(InsecureRandRange(223) + 33), {}); // unknown short id

View file

@ -142,11 +142,13 @@ class NetTest(BitcoinTestFramework):
"relaytxes": False, "relaytxes": False,
"services": "0000000000000000", "services": "0000000000000000",
"servicesnames": [], "servicesnames": [],
"session_id": "",
"startingheight": -1, "startingheight": -1,
"subver": "", "subver": "",
"synced_blocks": -1, "synced_blocks": -1,
"synced_headers": -1, "synced_headers": -1,
"timeoffset": 0, "timeoffset": 0,
"transport_protocol_type": "v1",
"version": 0, "version": 0,
}, },
) )