Skip to content

Commit

Permalink
net: expose transport types/session IDs of connections in RPC and logs
Browse files Browse the repository at this point in the history
Co-authored-by: Dhruv Mehta <856960+dhruv@users.noreply.github.com>
  • Loading branch information
sipa and dhruv committed Oct 2, 2023
1 parent 432a62c commit b815cce
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 4 deletions.
29 changes: 29 additions & 0 deletions src/net.cpp
Expand Up @@ -667,6 +667,9 @@ void CNode::CopyStats(CNodeStats& stats)
LOCK(cs_vRecv);
X(mapRecvBytesPerMsgType);
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);

Expand Down Expand Up @@ -734,6 +737,11 @@ V1Transport::V1Transport(const NodeId node_id, int nTypeIn, int nVersionIn) noex
Reset();
}

Transport::Info V1Transport::GetInfo() const noexcept
{
return {.transport_type = TransportProtocolType::V1, .session_id = {}};
}

int V1Transport::readHeader(Span<const uint8_t> msg_bytes)
{
AssertLockHeld(m_recv_mutex);
Expand Down Expand Up @@ -1582,6 +1590,27 @@ size_t V2Transport::GetSendMemoryUsage() const noexcept
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
{
auto it = node.vSendMsg.begin();
Expand Down
16 changes: 16 additions & 0 deletions src/net.h
Expand Up @@ -232,6 +232,10 @@ class CNodeStats
Network m_network;
uint32_t m_mapped_as;
ConnectionType m_conn_type;
/** Transport protocol type. */
TransportProtocolType m_transport_type;
/** BIP324 session id string in hex, if any. */
std::string m_session_id;
};


Expand Down Expand Up @@ -268,6 +272,15 @@ class Transport {
public:
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
// agnostic CNetMessage (message type & payload) objects.

Expand Down Expand Up @@ -426,6 +439,8 @@ class V1Transport final : public Transport
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)
{
AssertLockNotHeld(m_recv_mutex);
Expand Down Expand Up @@ -664,6 +679,7 @@ class V2Transport final : public Transport

// Miscellaneous functions.
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
Expand Down
11 changes: 7 additions & 4 deletions src/net_processing.cpp
Expand Up @@ -3585,13 +3585,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
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)};
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.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""),
(mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""),
pfrom.ConnectionTypeAsString());
(mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""));
}

if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) {
Expand Down
14 changes: 14 additions & 0 deletions src/node/connection_types.cpp
Expand Up @@ -24,3 +24,17 @@ std::string ConnectionTypeAsString(ConnectionType conn_type)

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);
}
11 changes: 11 additions & 0 deletions src/node/connection_types.h
Expand Up @@ -6,6 +6,7 @@
#define BITCOIN_NODE_CONNECTION_TYPES_H

#include <string>
#include <stdint.h>

/** Different types of connections to a peer. This enum encapsulates the
* information we have available at the time of opening or accepting the
Expand Down Expand Up @@ -79,4 +80,14 @@ enum class ConnectionType {
/** Convert ConnectionType enum to a string value */
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
10 changes: 10 additions & 0 deletions src/rpc/net.cpp
Expand Up @@ -45,6 +45,12 @@ const std::vector<std::string> CONNECTION_TYPE_DOC{
"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()
{
return RPCHelpMan{"getconnectioncount",
Expand Down Expand Up @@ -164,6 +170,8 @@ static RPCHelpMan getpeerinfo()
{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"
"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"},
}},
}},
},
Expand Down Expand Up @@ -268,6 +276,8 @@ static RPCHelpMan getpeerinfo()
}
obj.pushKV("bytesrecv_per_msg", recvPerMsgType);
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);
}
Expand Down
3 changes: 3 additions & 0 deletions src/test/fuzz/p2p_transport_serialization.cpp
Expand Up @@ -328,6 +328,9 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
// Make sure all expected messages were received.
assert(expected[0].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
Expand Down
12 changes: 12 additions & 0 deletions src/test/net_tests.cpp
Expand Up @@ -1321,6 +1321,14 @@ class V2TransportTester
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. */
void Damage()
{
Expand All @@ -1346,6 +1354,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage();
tester.ReceiveVersion();
tester.CompareSessionIDs();
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));
tester.SendMessage(uint8_t(4), msg_data_1); // cmpctblock short id
Expand Down Expand Up @@ -1386,6 +1395,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage();
tester.ReceiveVersion();
tester.CompareSessionIDs();
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));
tester.SendMessage(uint8_t(14), msg_data_1); // inv short id
Expand Down Expand Up @@ -1439,6 +1449,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage();
tester.ReceiveVersion();
tester.CompareSessionIDs();
for (unsigned d = 0; d < num_decoys_1; ++d) {
auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true);
Expand Down Expand Up @@ -1516,6 +1527,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage();
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_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
Expand Down
2 changes: 2 additions & 0 deletions test/functional/rpc_net.py
Expand Up @@ -142,11 +142,13 @@ def test_getpeerinfo(self):
"relaytxes": False,
"services": "0000000000000000",
"servicesnames": [],
"session_id": "",
"startingheight": -1,
"subver": "",
"synced_blocks": -1,
"synced_headers": -1,
"timeoffset": 0,
"transport_protocol_type": "v1",
"version": 0,
},
)
Expand Down

0 comments on commit b815cce

Please sign in to comment.