Skip to content

Commit 85b2603

Browse files
committed
tracing: add inbound connection tracepoint
1 parent 1172bc4 commit 85b2603

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

doc/tracing.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ to user-space in full. Messages longer than a 32kb might be cut off. This can
9393
be detected in tracing scripts by comparing the message size to the length of
9494
the passed message.
9595

96+
#### Tracepoint `net:inbound_connection`
97+
98+
Is called when a new inbound connection is opened to us. Passes information about
99+
the peer and the number of inbound connections including the newly opened connection.
100+
101+
Arguments passed:
102+
1. Peer ID as `int64`
103+
2. Peer address and port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (max. length 68 characters)
104+
3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
105+
4. Network the peer connects from as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`.
106+
5. Number of existing inbound connections as `uint64` including the newly opened inbound connection.
107+
96108
### Context `validation`
97109

98110
#### Tracepoint `validation:block_connected`

src/net.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <optional>
5454
#include <unordered_map>
5555

56+
TRACEPOINT_SEMAPHORE(net, inbound_connection);
5657
TRACEPOINT_SEMAPHORE(net, outbound_message);
5758

5859
/** Maximum number of block-relay-only anchor connections */
@@ -1833,6 +1834,12 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
18331834
m_nodes.push_back(pnode);
18341835
}
18351836
LogDebug(BCLog::NET, "connection from %s accepted\n", addr.ToStringAddrPort());
1837+
TRACEPOINT(net, inbound_connection,
1838+
pnode->GetId(),
1839+
pnode->m_addr_name.c_str(),
1840+
pnode->ConnectionTypeAsString().c_str(),
1841+
pnode->ConnectedThroughNetwork(),
1842+
GetNodeCount(ConnectionDirection::In));
18361843

18371844
// We received a new connection, harvest entropy from the time (and our peer count)
18381845
RandAddEvent((uint32_t)id);

test/functional/interface_usdt_net.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from io import BytesIO
1212
# Test will be skipped if we don't have bcc installed
1313
try:
14-
from bcc import BPF, USDT # type: ignore[import]
14+
from bcc import BPF, USDT # type: ignore[import]
1515
except ImportError:
1616
pass
1717
from test_framework.messages import msg_version
@@ -27,6 +27,9 @@
2727
# larger messanges see contrib/tracing/log_raw_p2p_msgs.py
2828
MAX_MSG_DATA_LENGTH = 150
2929

30+
# from net_address.h
31+
NETWORK_TYPE_UNROUTABLE = 0
32+
3033
net_tracepoints_program = """
3134
#include <uapi/linux/ptrace.h>
3235
@@ -53,6 +56,20 @@
5356
u8 msg[MAX_MSG_DATA_LENGTH];
5457
};
5558
59+
struct Connection
60+
{
61+
u64 id;
62+
char addr[MAX_PEER_ADDR_LENGTH];
63+
char type[MAX_PEER_CONN_TYPE_LENGTH];
64+
u32 network;
65+
};
66+
67+
struct NewConnection
68+
{
69+
struct Connection conn;
70+
u64 existing;
71+
};
72+
5673
BPF_PERF_OUTPUT(inbound_messages);
5774
int trace_inbound_message(struct pt_regs *ctx) {
5875
struct p2p_message msg = {};
@@ -78,9 +95,46 @@
7895
outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
7996
return 0;
8097
};
98+
99+
BPF_PERF_OUTPUT(inbound_connections);
100+
int trace_inbound_connection(struct pt_regs *ctx) {
101+
struct NewConnection inbound = {};
102+
void *conn_type_pointer = NULL, *address_pointer = NULL;
103+
bpf_usdt_readarg(1, ctx, &inbound.conn.id);
104+
bpf_usdt_readarg(2, ctx, &address_pointer);
105+
bpf_usdt_readarg(3, ctx, &conn_type_pointer);
106+
bpf_usdt_readarg(4, ctx, &inbound.conn.network);
107+
bpf_usdt_readarg(5, ctx, &inbound.existing);
108+
bpf_probe_read_user_str(&inbound.conn.addr, sizeof(inbound.conn.addr), address_pointer);
109+
bpf_probe_read_user_str(&inbound.conn.type, sizeof(inbound.conn.type), conn_type_pointer);
110+
inbound_connections.perf_submit(ctx, &inbound, sizeof(inbound));
111+
return 0;
112+
};
113+
81114
"""
82115

83116

117+
class Connection(ctypes.Structure):
118+
_fields_ = [
119+
("id", ctypes.c_uint64),
120+
("addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
121+
("conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
122+
("network", ctypes.c_uint32),
123+
]
124+
125+
def __repr__(self):
126+
return f"Connection(peer={self.id}, addr={self.addr.decode('utf-8')}, conn_type={self.conn_type.decode('utf-8')}, network={self.network})"
127+
128+
129+
class NewConnection(ctypes.Structure):
130+
_fields_ = [
131+
("conn", Connection),
132+
("existing", ctypes.c_uint64),
133+
]
134+
135+
def __repr__(self):
136+
return f"NewConnection(conn={self.conn}, existing={self.existing})"
137+
84138
class NetTracepointTest(BitcoinTestFramework):
85139
def set_test_params(self):
86140
self.num_nodes = 1
@@ -92,6 +146,10 @@ def skip_test_if_missing_module(self):
92146
self.skip_if_no_bpf_permissions()
93147

94148
def run_test(self):
149+
self.p2p_message_tracepoint_test()
150+
self.inbound_conn_tracepoint_test()
151+
152+
def p2p_message_tracepoint_test(self):
95153
# Tests the net:inbound_message and net:outbound_message tracepoints
96154
# See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
97155

@@ -166,7 +224,44 @@ def handle_outbound(_, data, __):
166224

167225

168226
bpf.cleanup()
227+
test_node.peer_disconnect()
228+
229+
def inbound_conn_tracepoint_test(self):
230+
self.log.info("hook into the net:inbound_connection tracepoint")
231+
ctx = USDT(pid=self.nodes[0].process.pid)
232+
ctx.enable_probe(probe="net:inbound_connection",
233+
fn_name="trace_inbound_connection")
234+
bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"])
235+
236+
inbound_connections = []
237+
EXPECTED_INBOUND_CONNECTIONS = 2
169238

239+
def handle_inbound_connection(_, data, __):
240+
nonlocal inbound_connections
241+
event = ctypes.cast(data, ctypes.POINTER(NewConnection)).contents
242+
self.log.info(f"handle_inbound_connection(): {event}")
243+
inbound_connections.append(event)
244+
245+
bpf["inbound_connections"].open_perf_buffer(handle_inbound_connection)
246+
247+
self.log.info("connect two P2P test nodes to our bitcoind node")
248+
testnodes = list()
249+
for _ in range(EXPECTED_INBOUND_CONNECTIONS):
250+
testnode = P2PInterface()
251+
self.nodes[0].add_p2p_connection(testnode)
252+
testnodes.append(testnode)
253+
bpf.perf_buffer_poll(timeout=200)
254+
255+
assert_equal(EXPECTED_INBOUND_CONNECTIONS, len(inbound_connections))
256+
for inbound_connection in inbound_connections:
257+
assert inbound_connection.conn.id > 0
258+
assert inbound_connection.existing > 0
259+
assert_equal(b'inbound', inbound_connection.conn.conn_type)
260+
assert_equal(NETWORK_TYPE_UNROUTABLE, inbound_connection.conn.network)
261+
262+
bpf.cleanup()
263+
for node in testnodes:
264+
node.peer_disconnect()
170265

171266
if __name__ == '__main__':
172267
NetTracepointTest(__file__).main()

0 commit comments

Comments
 (0)