interface_usdt_net.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2022-present 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 CBlockHeader, MAX_HEADERS_RESULTS, msg_headers, msg_version
18 from test_framework.p2p import P2PInterface
19 from test_framework.test_framework import BitcoinTestFramework
20 from test_framework.util import (
21 assert_equal,
22 assert_greater_than,
23 bpf_cflags,
24 )
25
26 # Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
27 MAX_PEER_ADDR_LENGTH = 68
28 MAX_PEER_CONN_TYPE_LENGTH = 20
29 MAX_MSG_TYPE_LENGTH = 20
30 MAX_MISBEHAVING_MESSAGE_LENGTH = 128
31 # We won't process messages larger than 150 byte in this test. For reading
32 # larger messanges see contrib/tracing/log_raw_p2p_msgs.py
33 MAX_MSG_DATA_LENGTH = 150
34
35 # from net_address.h
36 NETWORK_TYPE_UNROUTABLE = 0
37 # Use in -maxconnections. Results in a maximum of 21 inbound connections
38 MAX_CONNECTIONS = 32
39 MAX_INBOUND_CONNECTIONS = MAX_CONNECTIONS - 10 - 1 # 10 outbound and 1 feeler
40
41 net_tracepoints_program = """
42 #include <uapi/linux/ptrace.h>
43
44 #define MAX_PEER_ADDR_LENGTH {}
45 #define MAX_PEER_CONN_TYPE_LENGTH {}
46 #define MAX_MSG_TYPE_LENGTH {}
47 #define MAX_MSG_DATA_LENGTH {}
48 #define MAX_MISBEHAVING_MESSAGE_LENGTH {}
49 """.format(
50 MAX_PEER_ADDR_LENGTH,
51 MAX_PEER_CONN_TYPE_LENGTH,
52 MAX_MSG_TYPE_LENGTH,
53 MAX_MSG_DATA_LENGTH,
54 MAX_MISBEHAVING_MESSAGE_LENGTH,
55 ) + """
56 // A min() macro. Prefixed with _TRACEPOINT_TEST to avoid collision with other MIN macros.
57 #define _TRACEPOINT_TEST_MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
58
59 struct p2p_message
60 {
61 u64 peer_id;
62 char peer_addr[MAX_PEER_ADDR_LENGTH];
63 char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
64 char msg_type[MAX_MSG_TYPE_LENGTH];
65 u64 msg_size;
66 u8 msg[MAX_MSG_DATA_LENGTH];
67 };
68
69 struct Connection
70 {
71 u64 id;
72 char addr[MAX_PEER_ADDR_LENGTH];
73 char type[MAX_PEER_CONN_TYPE_LENGTH];
74 u32 network;
75 };
76
77 struct NewConnection
78 {
79 struct Connection conn;
80 u64 existing;
81 };
82
83 struct ClosedConnection
84 {
85 struct Connection conn;
86 u64 time_established;
87 };
88
89 struct MisbehavingConnection
90 {
91 u64 id;
92 char message[MAX_MISBEHAVING_MESSAGE_LENGTH];
93 };
94
95 BPF_PERF_OUTPUT(inbound_messages);
96 int trace_inbound_message(struct pt_regs *ctx) {
97 struct p2p_message msg = {};
98 void *paddr = NULL, *pconn_type = NULL, *pmsg_type = NULL, *pmsg = NULL;
99 bpf_usdt_readarg(1, ctx, &msg.peer_id);
100 bpf_usdt_readarg(2, ctx, &paddr);
101 bpf_probe_read_user_str(&msg.peer_addr, sizeof(msg.peer_addr), paddr);
102 bpf_usdt_readarg(3, ctx, &pconn_type);
103 bpf_probe_read_user_str(&msg.peer_conn_type, sizeof(msg.peer_conn_type), pconn_type);
104 bpf_usdt_readarg(4, ctx, &pmsg_type);
105 bpf_probe_read_user_str(&msg.msg_type, sizeof(msg.msg_type), pmsg_type);
106 bpf_usdt_readarg(5, ctx, &msg.msg_size);
107 bpf_usdt_readarg(6, ctx, &pmsg);
108 bpf_probe_read_user(&msg.msg, _TRACEPOINT_TEST_MIN(msg.msg_size, MAX_MSG_DATA_LENGTH), pmsg);
109 inbound_messages.perf_submit(ctx, &msg, sizeof(msg));
110 return 0;
111 }
112
113 BPF_PERF_OUTPUT(outbound_messages);
114 int trace_outbound_message(struct pt_regs *ctx) {
115 struct p2p_message msg = {};
116 void *paddr = NULL, *pconn_type = NULL, *pmsg_type = NULL, *pmsg = NULL;
117 bpf_usdt_readarg(1, ctx, &msg.peer_id);
118 bpf_usdt_readarg(1, ctx, &msg.peer_id);
119 bpf_usdt_readarg(2, ctx, &paddr);
120 bpf_probe_read_user_str(&msg.peer_addr, sizeof(msg.peer_addr), paddr);
121 bpf_usdt_readarg(3, ctx, &pconn_type);
122 bpf_probe_read_user_str(&msg.peer_conn_type, sizeof(msg.peer_conn_type), pconn_type);
123 bpf_usdt_readarg(4, ctx, &pmsg_type);
124 bpf_probe_read_user_str(&msg.msg_type, sizeof(msg.msg_type), pmsg_type);
125 bpf_usdt_readarg(5, ctx, &msg.msg_size);
126 bpf_usdt_readarg(6, ctx, &pmsg);
127 bpf_probe_read_user(&msg.msg, _TRACEPOINT_TEST_MIN(msg.msg_size, MAX_MSG_DATA_LENGTH), pmsg);
128 outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
129 return 0;
130 };
131
132 BPF_PERF_OUTPUT(inbound_connections);
133 int trace_inbound_connection(struct pt_regs *ctx) {
134 struct NewConnection inbound = {};
135 void *conn_type_pointer = NULL, *address_pointer = NULL;
136 bpf_usdt_readarg(1, ctx, &inbound.conn.id);
137 bpf_usdt_readarg(2, ctx, &address_pointer);
138 bpf_usdt_readarg(3, ctx, &conn_type_pointer);
139 bpf_usdt_readarg(4, ctx, &inbound.conn.network);
140 bpf_usdt_readarg(5, ctx, &inbound.existing);
141 bpf_probe_read_user_str(&inbound.conn.addr, sizeof(inbound.conn.addr), address_pointer);
142 bpf_probe_read_user_str(&inbound.conn.type, sizeof(inbound.conn.type), conn_type_pointer);
143 inbound_connections.perf_submit(ctx, &inbound, sizeof(inbound));
144 return 0;
145 };
146
147 BPF_PERF_OUTPUT(outbound_connections);
148 int trace_outbound_connection(struct pt_regs *ctx) {
149 struct NewConnection outbound = {};
150 void *conn_type_pointer = NULL, *address_pointer = NULL;
151 bpf_usdt_readarg(1, ctx, &outbound.conn.id);
152 bpf_usdt_readarg(2, ctx, &address_pointer);
153 bpf_usdt_readarg(3, ctx, &conn_type_pointer);
154 bpf_usdt_readarg(4, ctx, &outbound.conn.network);
155 bpf_usdt_readarg(5, ctx, &outbound.existing);
156 bpf_probe_read_user_str(&outbound.conn.addr, sizeof(outbound.conn.addr), address_pointer);
157 bpf_probe_read_user_str(&outbound.conn.type, sizeof(outbound.conn.type), conn_type_pointer);
158 outbound_connections.perf_submit(ctx, &outbound, sizeof(outbound));
159 return 0;
160 };
161
162 BPF_PERF_OUTPUT(evicted_inbound_connections);
163 int trace_evicted_inbound_connection(struct pt_regs *ctx) {
164 struct ClosedConnection evicted = {};
165 void *conn_type_pointer = NULL, *address_pointer = NULL;
166 bpf_usdt_readarg(1, ctx, &evicted.conn.id);
167 bpf_usdt_readarg(2, ctx, &address_pointer);
168 bpf_usdt_readarg(3, ctx, &conn_type_pointer);
169 bpf_usdt_readarg(4, ctx, &evicted.conn.network);
170 bpf_usdt_readarg(5, ctx, &evicted.time_established);
171 bpf_probe_read_user_str(&evicted.conn.addr, sizeof(evicted.conn.addr), address_pointer);
172 bpf_probe_read_user_str(&evicted.conn.type, sizeof(evicted.conn.type), conn_type_pointer);
173 evicted_inbound_connections.perf_submit(ctx, &evicted, sizeof(evicted));
174 return 0;
175 };
176
177 BPF_PERF_OUTPUT(misbehaving_connections);
178 int trace_misbehaving_connection(struct pt_regs *ctx) {
179 struct MisbehavingConnection misbehaving = {};
180 void *message_pointer = NULL;
181 bpf_usdt_readarg(1, ctx, &misbehaving.id);
182 bpf_usdt_readarg(2, ctx, &message_pointer);
183 bpf_probe_read_user_str(&misbehaving.message, sizeof(misbehaving.message), message_pointer);
184 misbehaving_connections.perf_submit(ctx, &misbehaving, sizeof(misbehaving));
185 return 0;
186 };
187
188 BPF_PERF_OUTPUT(closed_connections);
189 int trace_closed_connection(struct pt_regs *ctx) {
190 struct ClosedConnection closed = {};
191 void *conn_type_pointer = NULL, *address_pointer = NULL;
192 bpf_usdt_readarg(1, ctx, &closed.conn.id);
193 bpf_usdt_readarg(2, ctx, &address_pointer);
194 bpf_usdt_readarg(3, ctx, &conn_type_pointer);
195 bpf_usdt_readarg(4, ctx, &closed.conn.network);
196 bpf_usdt_readarg(5, ctx, &closed.time_established);
197 bpf_probe_read_user_str(&closed.conn.addr, sizeof(closed.conn.addr), address_pointer);
198 bpf_probe_read_user_str(&closed.conn.type, sizeof(closed.conn.type), conn_type_pointer);
199 closed_connections.perf_submit(ctx, &closed, sizeof(closed));
200 return 0;
201 };
202 """
203
204
205 class Connection(ctypes.Structure):
206 _fields_ = [
207 ("id", ctypes.c_uint64),
208 ("addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
209 ("conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
210 ("network", ctypes.c_uint32),
211 ]
212
213 def __repr__(self):
214 return f"Connection(peer={self.id}, addr={self.addr.decode('utf-8')}, conn_type={self.conn_type.decode('utf-8')}, network={self.network})"
215
216
217 class NewConnection(ctypes.Structure):
218 _fields_ = [
219 ("conn", Connection),
220 ("existing", ctypes.c_uint64),
221 ]
222
223 def __repr__(self):
224 return f"NewConnection(conn={self.conn}, existing={self.existing})"
225
226
227 class ClosedConnection(ctypes.Structure):
228 _fields_ = [
229 ("conn", Connection),
230 ("time_established", ctypes.c_uint64),
231 ]
232
233 def __repr__(self):
234 return f"ClosedConnection(conn={self.conn}, time_established={self.time_established})"
235
236
237 class MisbehavingConnection(ctypes.Structure):
238 _fields_ = [
239 ("id", ctypes.c_uint64),
240 ("message", ctypes.c_char * MAX_MISBEHAVING_MESSAGE_LENGTH),
241 ]
242
243 def __repr__(self):
244 return f"MisbehavingConnection(id={self.id}, message={self.message})"
245
246
247 class NetTracepointTest(BitcoinTestFramework):
248 def set_test_params(self):
249 self.num_nodes = 1
250 self.extra_args = [[f'-maxconnections={MAX_CONNECTIONS}']]
251
252 def skip_test_if_missing_module(self):
253 self.skip_if_platform_not_linux()
254 self.skip_if_no_bitcoind_tracepoints()
255 self.skip_if_no_python_bcc()
256 self.skip_if_no_bpf_permissions()
257 self.skip_if_running_under_valgrind()
258
259 def run_test(self):
260 self.p2p_message_tracepoint_test()
261 self.inbound_conn_tracepoint_test()
262 self.outbound_conn_tracepoint_test()
263 self.evicted_inbound_conn_tracepoint_test()
264 self.misbehaving_conn_tracepoint_test()
265 self.closed_conn_tracepoint_test()
266
267 def p2p_message_tracepoint_test(self):
268 # Tests the net:inbound_message and net:outbound_message tracepoints
269 # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
270
271 class P2PMessage(ctypes.Structure):
272 _fields_ = [
273 ("peer_id", ctypes.c_uint64),
274 ("peer_addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
275 ("peer_conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
276 ("msg_type", ctypes.c_char * MAX_MSG_TYPE_LENGTH),
277 ("msg_size", ctypes.c_uint64),
278 ("msg", ctypes.c_ubyte * MAX_MSG_DATA_LENGTH),
279 ]
280
281 def __repr__(self):
282 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})"
283
284 self.log.info(
285 "hook into the net:inbound_message and net:outbound_message tracepoints")
286 ctx = USDT(pid=self.nodes[0].process.pid)
287 ctx.enable_probe(probe="net:inbound_message",
288 fn_name="trace_inbound_message")
289 ctx.enable_probe(probe="net:outbound_message",
290 fn_name="trace_outbound_message")
291 bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())
292
293 EXPECTED_INOUTBOUND_VERSION_MSG = 1
294 checked_inbound_version_msg = 0
295 checked_outbound_version_msg = 0
296 events = []
297
298 def check_p2p_message(event, is_inbound):
299 nonlocal checked_inbound_version_msg, checked_outbound_version_msg
300 if event.msg_type.decode("utf-8") == "version":
301 self.log.info(
302 f"check_p2p_message(): {'inbound' if is_inbound else 'outbound'} {event}")
303 peer = self.nodes[0].getpeerinfo()[0]
304 msg = msg_version()
305 msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size])))
306 assert_equal(peer["id"], event.peer_id, peer["id"])
307 assert_equal(peer["addr"], event.peer_addr.decode("utf-8"))
308 assert_equal(peer["connection_type"],
309 event.peer_conn_type.decode("utf-8"))
310 if is_inbound:
311 checked_inbound_version_msg += 1
312 else:
313 checked_outbound_version_msg += 1
314
315 def handle_inbound(_, data, __):
316 event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
317 events.append((event, True))
318
319 def handle_outbound(_, data, __):
320 event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
321 events.append((event, False))
322
323 bpf["inbound_messages"].open_perf_buffer(handle_inbound)
324 bpf["outbound_messages"].open_perf_buffer(handle_outbound)
325
326 self.log.info("connect a P2P test node to our bitcoind node")
327 test_node = P2PInterface()
328 self.nodes[0].add_p2p_connection(test_node)
329 bpf.perf_buffer_poll(timeout=200)
330
331 self.log.info(
332 "check receipt and content of in- and outbound version messages")
333 for event, is_inbound in events:
334 check_p2p_message(event, is_inbound)
335 assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
336 checked_inbound_version_msg)
337 assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
338 checked_outbound_version_msg)
339
340
341 bpf.cleanup()
342 test_node.peer_disconnect()
343
344 def inbound_conn_tracepoint_test(self):
345 self.log.info("hook into the net:inbound_connection tracepoint")
346 ctx = USDT(pid=self.nodes[0].process.pid)
347 ctx.enable_probe(probe="net:inbound_connection",
348 fn_name="trace_inbound_connection")
349 bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())
350
351 inbound_connections = []
352 EXPECTED_INBOUND_CONNECTIONS = 2
353
354 def handle_inbound_connection(_, data, __):
355 nonlocal inbound_connections
356 event = ctypes.cast(data, ctypes.POINTER(NewConnection)).contents
357 self.log.info(f"handle_inbound_connection(): {event}")
358 inbound_connections.append(event)
359
360 bpf["inbound_connections"].open_perf_buffer(handle_inbound_connection)
361
362 self.log.info("connect two P2P test nodes to our bitcoind node")
363 testnodes = list()
364 for _ in range(EXPECTED_INBOUND_CONNECTIONS):
365 testnode = P2PInterface()
366 self.nodes[0].add_p2p_connection(testnode)
367 testnodes.append(testnode)
368 bpf.perf_buffer_poll(timeout=200)
369
370 assert_equal(EXPECTED_INBOUND_CONNECTIONS, len(inbound_connections))
371 for inbound_connection in inbound_connections:
372 assert_greater_than(inbound_connection.conn.id, 0)
373 assert_greater_than(inbound_connection.existing, 0)
374 assert_equal(b'inbound', inbound_connection.conn.conn_type)
375 assert_equal(NETWORK_TYPE_UNROUTABLE, inbound_connection.conn.network)
376
377 bpf.cleanup()
378 for node in testnodes:
379 node.peer_disconnect()
380
381 def outbound_conn_tracepoint_test(self):
382 self.log.info("hook into the net:outbound_connection tracepoint")
383 ctx = USDT(pid=self.nodes[0].process.pid)
384 ctx.enable_probe(probe="net:outbound_connection",
385 fn_name="trace_outbound_connection")
386 bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())
387
388 # that the handle_* function succeeds.
389 EXPECTED_OUTBOUND_CONNECTIONS = 2
390 EXPECTED_CONNECTION_TYPE = "feeler"
391 outbound_connections = []
392
393 def handle_outbound_connection(_, data, __):
394 event = ctypes.cast(data, ctypes.POINTER(NewConnection)).contents
395 self.log.info(f"handle_outbound_connection(): {event}")
396 outbound_connections.append(event)
397
398 bpf["outbound_connections"].open_perf_buffer(
399 handle_outbound_connection)
400
401 self.log.info(
402 f"connect {EXPECTED_OUTBOUND_CONNECTIONS} P2P test nodes to our bitcoind node")
403 testnodes = list()
404 for p2p_idx in range(EXPECTED_OUTBOUND_CONNECTIONS):
405 testnode = P2PInterface()
406 self.nodes[0].add_outbound_p2p_connection(
407 testnode, p2p_idx=p2p_idx, connection_type=EXPECTED_CONNECTION_TYPE)
408 testnodes.append(testnode)
409 bpf.perf_buffer_poll(timeout=200)
410
411 assert_equal(EXPECTED_OUTBOUND_CONNECTIONS, len(outbound_connections))
412 for outbound_connection in outbound_connections:
413 assert_greater_than(outbound_connection.conn.id, 0)
414 assert_greater_than(outbound_connection.existing, 0)
415 assert_equal(EXPECTED_CONNECTION_TYPE, outbound_connection.conn.conn_type.decode('utf-8'))
416 assert_equal(NETWORK_TYPE_UNROUTABLE, outbound_connection.conn.network)
417
418 bpf.cleanup()
419 for node in testnodes:
420 node.peer_disconnect()
421
422 def evicted_inbound_conn_tracepoint_test(self):
423 self.log.info("hook into the net:evicted_inbound_connection tracepoint")
424 ctx = USDT(pid=self.nodes[0].process.pid)
425 ctx.enable_probe(probe="net:evicted_inbound_connection",
426 fn_name="trace_evicted_inbound_connection")
427 bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())
428
429 EXPECTED_EVICTED_CONNECTIONS = 2
430 evicted_connections = []
431
432 def handle_evicted_inbound_connection(_, data, __):
433 event = ctypes.cast(data, ctypes.POINTER(ClosedConnection)).contents
434 self.log.info(f"handle_evicted_inbound_connection(): {event}")
435 evicted_connections.append(event)
436
437 bpf["evicted_inbound_connections"].open_perf_buffer(handle_evicted_inbound_connection)
438
439 self.log.info(
440 f"connect {MAX_INBOUND_CONNECTIONS + EXPECTED_EVICTED_CONNECTIONS} P2P test nodes to our bitcoind node and expect {EXPECTED_EVICTED_CONNECTIONS} evictions")
441 testnodes = list()
442 for p2p_idx in range(MAX_INBOUND_CONNECTIONS + EXPECTED_EVICTED_CONNECTIONS):
443 testnode = P2PInterface()
444 self.nodes[0].add_p2p_connection(testnode)
445 testnodes.append(testnode)
446 bpf.perf_buffer_poll(timeout=200)
447
448 assert_equal(EXPECTED_EVICTED_CONNECTIONS, len(evicted_connections))
449 for evicted_connection in evicted_connections:
450 assert_greater_than(evicted_connection.conn.id, 0)
451 assert_greater_than(evicted_connection.time_established, 0)
452 assert_equal("inbound", evicted_connection.conn.conn_type.decode('utf-8'))
453 assert_equal(NETWORK_TYPE_UNROUTABLE, evicted_connection.conn.network)
454
455 bpf.cleanup()
456 for node in testnodes:
457 node.peer_disconnect()
458
459 def misbehaving_conn_tracepoint_test(self):
460 self.log.info("hook into the net:misbehaving_connection tracepoint")
461 ctx = USDT(pid=self.nodes[0].process.pid)
462 ctx.enable_probe(probe="net:misbehaving_connection",
463 fn_name="trace_misbehaving_connection")
464 bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())
465
466 EXPECTED_MISBEHAVING_CONNECTIONS = 2
467 misbehaving_connections = []
468
469 def handle_misbehaving_connection(_, data, __):
470 event = ctypes.cast(data, ctypes.POINTER(MisbehavingConnection)).contents
471 self.log.info(f"handle_misbehaving_connection(): {event}")
472 misbehaving_connections.append(event)
473
474 bpf["misbehaving_connections"].open_perf_buffer(handle_misbehaving_connection)
475
476 self.log.info("connect a misbehaving P2P test nodes to our bitcoind node")
477 msg = msg_headers([CBlockHeader()] * (MAX_HEADERS_RESULTS + 1))
478 for _ in range(EXPECTED_MISBEHAVING_CONNECTIONS):
479 testnode = P2PInterface()
480 self.nodes[0].add_p2p_connection(testnode)
481 testnode.send_without_ping(msg)
482 bpf.perf_buffer_poll(timeout=500)
483 testnode.peer_disconnect()
484
485 assert_equal(EXPECTED_MISBEHAVING_CONNECTIONS, len(misbehaving_connections))
486 for misbehaving_connection in misbehaving_connections:
487 assert_greater_than(misbehaving_connection.id, 0)
488 assert_greater_than(len(misbehaving_connection.message), 0)
489 assert_equal(misbehaving_connection.message, b"headers message size = 2001")
490
491 bpf.cleanup()
492
493 def closed_conn_tracepoint_test(self):
494 self.log.info("hook into the net:closed_connection tracepoint")
495 ctx = USDT(pid=self.nodes[0].process.pid)
496 ctx.enable_probe(probe="net:closed_connection",
497 fn_name="trace_closed_connection")
498 bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())
499
500 EXPECTED_CLOSED_CONNECTIONS = 2
501 closed_connections = []
502
503 def handle_closed_connection(_, data, __):
504 event = ctypes.cast(data, ctypes.POINTER(ClosedConnection)).contents
505 self.log.info(f"handle_closed_connection(): {event}")
506 closed_connections.append(event)
507
508 bpf["closed_connections"].open_perf_buffer(handle_closed_connection)
509
510 self.log.info(
511 f"connect {EXPECTED_CLOSED_CONNECTIONS} P2P test nodes to our bitcoind node")
512 testnodes = list()
513 for p2p_idx in range(EXPECTED_CLOSED_CONNECTIONS):
514 testnode = P2PInterface()
515 self.nodes[0].add_p2p_connection(testnode)
516 testnodes.append(testnode)
517 for node in testnodes:
518 node.peer_disconnect()
519 self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 0)
520 bpf.perf_buffer_poll(timeout=400)
521
522 assert_equal(EXPECTED_CLOSED_CONNECTIONS, len(closed_connections))
523 for closed_connection in closed_connections:
524 assert_greater_than(closed_connection.conn.id, 0)
525 assert_equal("inbound", closed_connection.conn.conn_type.decode('utf-8'))
526 assert_equal(0, closed_connection.conn.network)
527 assert_greater_than(closed_connection.time_established, 0)
528
529 bpf.cleanup()
530
531 if __name__ == '__main__':
532 NetTracepointTest(__file__).main()
533