aboutsummaryrefslogtreecommitdiff
path: root/test/functional/interface_usdt_net.py
diff options
context:
space:
mode:
author0xb10c <0xb10c@gmail.com>2022-02-15 19:59:09 +0100
committer0xb10c <0xb10c@gmail.com>2022-02-20 14:59:12 +0100
commit34b27bac684f2f373c5e1d90697d6bc8a014f45a (patch)
tree91ad66c3098f93e612211cb5ce5b8612e9c457da /test/functional/interface_usdt_net.py
parentc934087b627f7d368458781944f990b0eb479634 (diff)
downloadbitcoin-34b27bac684f2f373c5e1d90697d6bc8a014f45a.tar.xz
test: net:in/out_message tracepoint tests
This adds tests for the net:inbound_message and net:outbound_message tracepoint interface.
Diffstat (limited to 'test/functional/interface_usdt_net.py')
-rwxr-xr-xtest/functional/interface_usdt_net.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py
new file mode 100755
index 0000000000..9522cd8c59
--- /dev/null
+++ b/test/functional/interface_usdt_net.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+""" Tests the net:* tracepoint API interface.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
+"""
+
+import ctypes
+from io import BytesIO
+# Test will be skipped if we don't have bcc installed
+try:
+ from bcc import BPF, USDT # type: ignore[import]
+except ImportError:
+ pass
+from test_framework.messages import msg_version
+from test_framework.p2p import P2PInterface
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+# Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
+MAX_PEER_ADDR_LENGTH = 68
+MAX_PEER_CONN_TYPE_LENGTH = 20
+MAX_MSG_TYPE_LENGTH = 20
+# We won't process messages larger than 150 byte in this test. For reading
+# larger messanges see contrib/tracing/log_raw_p2p_msgs.py
+MAX_MSG_DATA_LENGTH = 150
+
+net_tracepoints_program = """
+#include <uapi/linux/ptrace.h>
+
+#define MAX_PEER_ADDR_LENGTH {}
+#define MAX_PEER_CONN_TYPE_LENGTH {}
+#define MAX_MSG_TYPE_LENGTH {}
+#define MAX_MSG_DATA_LENGTH {}
+""".format(
+ MAX_PEER_ADDR_LENGTH,
+ MAX_PEER_CONN_TYPE_LENGTH,
+ MAX_MSG_TYPE_LENGTH,
+ MAX_MSG_DATA_LENGTH
+) + """
+#define MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
+
+struct p2p_message
+{
+ u64 peer_id;
+ char peer_addr[MAX_PEER_ADDR_LENGTH];
+ char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
+ char msg_type[MAX_MSG_TYPE_LENGTH];
+ u64 msg_size;
+ u8 msg[MAX_MSG_DATA_LENGTH];
+};
+
+BPF_PERF_OUTPUT(inbound_messages);
+int trace_inbound_message(struct pt_regs *ctx) {
+ struct p2p_message msg = {};
+ bpf_usdt_readarg(1, ctx, &msg.peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg.msg_size);
+ bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH));
+ inbound_messages.perf_submit(ctx, &msg, sizeof(msg));
+ return 0;
+}
+
+BPF_PERF_OUTPUT(outbound_messages);
+int trace_outbound_message(struct pt_regs *ctx) {
+ struct p2p_message msg = {};
+ bpf_usdt_readarg(1, ctx, &msg.peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg.msg_size);
+ bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH));
+ outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
+ return 0;
+};
+"""
+
+
+class NetTracepointTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_platform_not_linux()
+ self.skip_if_no_bitcoind_tracepoints()
+ self.skip_if_no_python_bcc()
+ self.skip_if_no_bpf_permissions()
+
+ def run_test(self):
+ # Tests the net:inbound_message and net:outbound_message tracepoints
+ # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
+
+ class P2PMessage(ctypes.Structure):
+ _fields_ = [
+ ("peer_id", ctypes.c_uint64),
+ ("peer_addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
+ ("peer_conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
+ ("msg_type", ctypes.c_char * MAX_MSG_TYPE_LENGTH),
+ ("msg_size", ctypes.c_uint64),
+ ("msg", ctypes.c_ubyte * MAX_MSG_DATA_LENGTH),
+ ]
+
+ def __repr__(self):
+ 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})"
+
+ self.log.info(
+ "hook into the net:inbound_message and net:outbound_message tracepoints")
+ ctx = USDT(path=str(self.options.bitcoind))
+ ctx.enable_probe(probe="net:inbound_message",
+ fn_name="trace_inbound_message")
+ ctx.enable_probe(probe="net:outbound_message",
+ fn_name="trace_outbound_message")
+ bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0)
+
+ # The handle_* function is a ctypes callback function called from C. When
+ # we assert in the handle_* function, the AssertError doesn't propagate
+ # back to Python. The exception is ignored. We manually count and assert
+ # that the handle_* functions succeeded.
+ EXPECTED_INOUTBOUND_VERSION_MSG = 1
+ checked_inbound_version_msg = 0
+ checked_outbound_version_msg = 0
+
+ def check_p2p_message(event, inbound):
+ nonlocal checked_inbound_version_msg, checked_outbound_version_msg
+ if event.msg_type.decode("utf-8") == "version":
+ self.log.info(
+ f"check_p2p_message(): {'inbound' if inbound else 'outbound'} {event}")
+ peer = self.nodes[0].getpeerinfo()[0]
+ msg = msg_version()
+ msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size])))
+ assert_equal(peer["id"], event.peer_id, peer["id"])
+ assert_equal(peer["addr"], event.peer_addr.decode("utf-8"))
+ assert_equal(peer["connection_type"],
+ event.peer_conn_type.decode("utf-8"))
+ if inbound:
+ checked_inbound_version_msg += 1
+ else:
+ checked_outbound_version_msg += 1
+
+ def handle_inbound(_, data, __):
+ event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
+ check_p2p_message(event, True)
+
+ def handle_outbound(_, data, __):
+ event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
+ check_p2p_message(event, False)
+
+ bpf["inbound_messages"].open_perf_buffer(handle_inbound)
+ bpf["outbound_messages"].open_perf_buffer(handle_outbound)
+
+ self.log.info("connect a P2P test node to our bitcoind node")
+ test_node = P2PInterface()
+ self.nodes[0].add_p2p_connection(test_node)
+ bpf.perf_buffer_poll(timeout=200)
+
+ self.log.info(
+ "check that we got both an inbound and outbound version message")
+ assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
+ checked_inbound_version_msg)
+ assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
+ checked_outbound_version_msg)
+
+ bpf.cleanup()
+
+
+if __name__ == '__main__':
+ NetTracepointTest().main()