interface_usdt_net.py raw

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