Skip to content

Commit 34b27ba

Browse files
committed
test: net:in/out_message tracepoint tests
This adds tests for the net:inbound_message and net:outbound_message tracepoint interface.
1 parent c934087 commit 34b27ba

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

test/functional/interface_usdt_net.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
""" Tests the net:* tracepoint API interface.
7+
See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
8+
"""
9+
10+
import ctypes
11+
from io import BytesIO
12+
# Test will be skipped if we don't have bcc installed
13+
try:
14+
from bcc import BPF, USDT # type: ignore[import]
15+
except ImportError:
16+
pass
17+
from test_framework.messages import msg_version
18+
from test_framework.p2p import P2PInterface
19+
from test_framework.test_framework import BitcoinTestFramework
20+
from test_framework.util import assert_equal
21+
22+
# Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
23+
MAX_PEER_ADDR_LENGTH = 68
24+
MAX_PEER_CONN_TYPE_LENGTH = 20
25+
MAX_MSG_TYPE_LENGTH = 20
26+
# We won't process messages larger than 150 byte in this test. For reading
27+
# larger messanges see contrib/tracing/log_raw_p2p_msgs.py
28+
MAX_MSG_DATA_LENGTH = 150
29+
30+
net_tracepoints_program = """
31+
#include <uapi/linux/ptrace.h>
32+
33+
#define MAX_PEER_ADDR_LENGTH {}
34+
#define MAX_PEER_CONN_TYPE_LENGTH {}
35+
#define MAX_MSG_TYPE_LENGTH {}
36+
#define MAX_MSG_DATA_LENGTH {}
37+
""".format(
38+
MAX_PEER_ADDR_LENGTH,
39+
MAX_PEER_CONN_TYPE_LENGTH,
40+
MAX_MSG_TYPE_LENGTH,
41+
MAX_MSG_DATA_LENGTH
42+
) + """
43+
#define MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
44+
45+
struct p2p_message
46+
{
47+
u64 peer_id;
48+
char peer_addr[MAX_PEER_ADDR_LENGTH];
49+
char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
50+
char msg_type[MAX_MSG_TYPE_LENGTH];
51+
u64 msg_size;
52+
u8 msg[MAX_MSG_DATA_LENGTH];
53+
};
54+
55+
BPF_PERF_OUTPUT(inbound_messages);
56+
int trace_inbound_message(struct pt_regs *ctx) {
57+
struct p2p_message msg = {};
58+
bpf_usdt_readarg(1, ctx, &msg.peer_id);
59+
bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
60+
bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
61+
bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
62+
bpf_usdt_readarg(5, ctx, &msg.msg_size);
63+
bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH));
64+
inbound_messages.perf_submit(ctx, &msg, sizeof(msg));
65+
return 0;
66+
}
67+
68+
BPF_PERF_OUTPUT(outbound_messages);
69+
int trace_outbound_message(struct pt_regs *ctx) {
70+
struct p2p_message msg = {};
71+
bpf_usdt_readarg(1, ctx, &msg.peer_id);
72+
bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
73+
bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
74+
bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
75+
bpf_usdt_readarg(5, ctx, &msg.msg_size);
76+
bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH));
77+
outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
78+
return 0;
79+
};
80+
"""
81+
82+
83+
class NetTracepointTest(BitcoinTestFramework):
84+
def set_test_params(self):
85+
self.num_nodes = 1
86+
87+
def skip_test_if_missing_module(self):
88+
self.skip_if_platform_not_linux()
89+
self.skip_if_no_bitcoind_tracepoints()
90+
self.skip_if_no_python_bcc()
91+
self.skip_if_no_bpf_permissions()
92+
93+
def run_test(self):
94+
# Tests the net:inbound_message and net:outbound_message tracepoints
95+
# See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
96+
97+
class P2PMessage(ctypes.Structure):
98+
_fields_ = [
99+
("peer_id", ctypes.c_uint64),
100+
("peer_addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
101+
("peer_conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
102+
("msg_type", ctypes.c_char * MAX_MSG_TYPE_LENGTH),
103+
("msg_size", ctypes.c_uint64),
104+
("msg", ctypes.c_ubyte * MAX_MSG_DATA_LENGTH),
105+
]
106+
107+
def __repr__(self):
108+
return f"P2PMessage(peer={self.peer_id}, addr={self.peer_addr.decode('utf-8')}, conn_type={self.peer_conn_type.decode('utf-8')}, msg_type={self.msg_type.decode('utf-8')}, msg_size={self.msg_size})"
109+
110+
self.log.info(
111+
"hook into the net:inbound_message and net:outbound_message tracepoints")
112+
ctx = USDT(path=str(self.options.bitcoind))
113+
ctx.enable_probe(probe="net:inbound_message",
114+
fn_name="trace_inbound_message")
115+
ctx.enable_probe(probe="net:outbound_message",
116+
fn_name="trace_outbound_message")
117+
bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0)
118+
119+
# The handle_* function is a ctypes callback function called from C. When
120+
# we assert in the handle_* function, the AssertError doesn't propagate
121+
# back to Python. The exception is ignored. We manually count and assert
122+
# that the handle_* functions succeeded.
123+
EXPECTED_INOUTBOUND_VERSION_MSG = 1
124+
checked_inbound_version_msg = 0
125+
checked_outbound_version_msg = 0
126+
127+
def check_p2p_message(event, inbound):
128+
nonlocal checked_inbound_version_msg, checked_outbound_version_msg
129+
if event.msg_type.decode("utf-8") == "version":
130+
self.log.info(
131+
f"check_p2p_message(): {'inbound' if inbound else 'outbound'} {event}")
132+
peer = self.nodes[0].getpeerinfo()[0]
133+
msg = msg_version()
134+
msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size])))
135+
assert_equal(peer["id"], event.peer_id, peer["id"])
136+
assert_equal(peer["addr"], event.peer_addr.decode("utf-8"))
137+
assert_equal(peer["connection_type"],
138+
event.peer_conn_type.decode("utf-8"))
139+
if inbound:
140+
checked_inbound_version_msg += 1
141+
else:
142+
checked_outbound_version_msg += 1
143+
144+
def handle_inbound(_, data, __):
145+
event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
146+
check_p2p_message(event, True)
147+
148+
def handle_outbound(_, data, __):
149+
event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
150+
check_p2p_message(event, False)
151+
152+
bpf["inbound_messages"].open_perf_buffer(handle_inbound)
153+
bpf["outbound_messages"].open_perf_buffer(handle_outbound)
154+
155+
self.log.info("connect a P2P test node to our bitcoind node")
156+
test_node = P2PInterface()
157+
self.nodes[0].add_p2p_connection(test_node)
158+
bpf.perf_buffer_poll(timeout=200)
159+
160+
self.log.info(
161+
"check that we got both an inbound and outbound version message")
162+
assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
163+
checked_inbound_version_msg)
164+
assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
165+
checked_outbound_version_msg)
166+
167+
bpf.cleanup()
168+
169+
170+
if __name__ == '__main__':
171+
NetTracepointTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
'wallet_reorgsrestore.py',
169169
'interface_http.py',
170170
'interface_rpc.py',
171+
'interface_usdt_net.py',
171172
'rpc_psbt.py --legacy-wallet',
172173
'rpc_psbt.py --descriptors',
173174
'rpc_users.py',

0 commit comments

Comments
 (0)