interface_usdt_net.py
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(pid=self.nodes[0].process.pid) 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, cflags=["-Wno-error=implicit-function-declaration"]) 118 119 EXPECTED_INOUTBOUND_VERSION_MSG = 1 120 checked_inbound_version_msg = 0 121 checked_outbound_version_msg = 0 122 events = [] 123 124 def check_p2p_message(event, is_inbound): 125 nonlocal checked_inbound_version_msg, checked_outbound_version_msg 126 if event.msg_type.decode("utf-8") == "version": 127 self.log.info( 128 f"check_p2p_message(): {'inbound' if is_inbound else 'outbound'} {event}") 129 peer = self.nodes[0].getpeerinfo()[0] 130 msg = msg_version() 131 msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size]))) 132 assert_equal(peer["id"], event.peer_id, peer["id"]) 133 assert_equal(peer["addr"], event.peer_addr.decode("utf-8")) 134 assert_equal(peer["connection_type"], 135 event.peer_conn_type.decode("utf-8")) 136 if is_inbound: 137 checked_inbound_version_msg += 1 138 else: 139 checked_outbound_version_msg += 1 140 141 def handle_inbound(_, data, __): 142 event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents 143 events.append((event, True)) 144 145 def handle_outbound(_, data, __): 146 event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents 147 events.append((event, False)) 148 149 bpf["inbound_messages"].open_perf_buffer(handle_inbound) 150 bpf["outbound_messages"].open_perf_buffer(handle_outbound) 151 152 self.log.info("connect a P2P test node to our bitcoind node") 153 test_node = P2PInterface() 154 self.nodes[0].add_p2p_connection(test_node) 155 bpf.perf_buffer_poll(timeout=200) 156 157 self.log.info( 158 "check receipt and content of in- and outbound version messages") 159 for event, is_inbound in events: 160 check_p2p_message(event, is_inbound) 161 assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG, 162 checked_inbound_version_msg) 163 assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG, 164 checked_outbound_version_msg) 165 166 167 bpf.cleanup() 168 169 170 if __name__ == '__main__': 171 NetTracepointTest().main()