/ test / functional / interface_usdt_net.py
interface_usdt_net.py
  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()