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