From 85b2603eec634257cd3b398900dbb92251db71e6 Mon Sep 17 00:00:00 2001 From: 0xb10c <0xb10c@gmail.com> Date: Mon, 2 May 2022 16:26:22 +0200 Subject: [PATCH] tracing: add inbound connection tracepoint --- doc/tracing.md | 12 ++++ src/net.cpp | 7 ++ test/functional/interface_usdt_net.py | 97 ++++++++++++++++++++++++++- 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/doc/tracing.md b/doc/tracing.md index 184f6b01002..80edea1e646 100644 --- a/doc/tracing.md +++ b/doc/tracing.md @@ -93,6 +93,18 @@ to user-space in full. Messages longer than a 32kb might be cut off. This can be detected in tracing scripts by comparing the message size to the length of the passed message. +#### Tracepoint `net:inbound_connection` + +Is called when a new inbound connection is opened to us. Passes information about +the peer and the number of inbound connections including the newly opened connection. + +Arguments passed: +1. Peer ID as `int64` +2. Peer address and port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (max. length 68 characters) +3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters) +4. Network the peer connects from as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`. +5. Number of existing inbound connections as `uint64` including the newly opened inbound connection. + ### Context `validation` #### Tracepoint `validation:block_connected` diff --git a/src/net.cpp b/src/net.cpp index 28499788711..d2056412c9f 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -53,6 +53,7 @@ #include #include +TRACEPOINT_SEMAPHORE(net, inbound_connection); TRACEPOINT_SEMAPHORE(net, outbound_message); /** Maximum number of block-relay-only anchor connections */ @@ -1833,6 +1834,12 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr&& sock, m_nodes.push_back(pnode); } LogDebug(BCLog::NET, "connection from %s accepted\n", addr.ToStringAddrPort()); + TRACEPOINT(net, inbound_connection, + pnode->GetId(), + pnode->m_addr_name.c_str(), + pnode->ConnectionTypeAsString().c_str(), + pnode->ConnectedThroughNetwork(), + GetNodeCount(ConnectionDirection::In)); // We received a new connection, harvest entropy from the time (and our peer count) RandAddEvent((uint32_t)id); diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py index 5468ddf8586..6849a210f08 100755 --- a/test/functional/interface_usdt_net.py +++ b/test/functional/interface_usdt_net.py @@ -11,7 +11,7 @@ import ctypes from io import BytesIO # Test will be skipped if we don't have bcc installed try: - from bcc import BPF, USDT # type: ignore[import] + from bcc import BPF, USDT # type: ignore[import] except ImportError: pass from test_framework.messages import msg_version @@ -27,6 +27,9 @@ MAX_MSG_TYPE_LENGTH = 20 # larger messanges see contrib/tracing/log_raw_p2p_msgs.py MAX_MSG_DATA_LENGTH = 150 +# from net_address.h +NETWORK_TYPE_UNROUTABLE = 0 + net_tracepoints_program = """ #include @@ -53,6 +56,20 @@ struct p2p_message u8 msg[MAX_MSG_DATA_LENGTH]; }; +struct Connection +{ + u64 id; + char addr[MAX_PEER_ADDR_LENGTH]; + char type[MAX_PEER_CONN_TYPE_LENGTH]; + u32 network; +}; + +struct NewConnection +{ + struct Connection conn; + u64 existing; +}; + BPF_PERF_OUTPUT(inbound_messages); int trace_inbound_message(struct pt_regs *ctx) { struct p2p_message msg = {}; @@ -78,9 +95,46 @@ int trace_outbound_message(struct pt_regs *ctx) { outbound_messages.perf_submit(ctx, &msg, sizeof(msg)); return 0; }; + +BPF_PERF_OUTPUT(inbound_connections); +int trace_inbound_connection(struct pt_regs *ctx) { + struct NewConnection inbound = {}; + void *conn_type_pointer = NULL, *address_pointer = NULL; + bpf_usdt_readarg(1, ctx, &inbound.conn.id); + bpf_usdt_readarg(2, ctx, &address_pointer); + bpf_usdt_readarg(3, ctx, &conn_type_pointer); + bpf_usdt_readarg(4, ctx, &inbound.conn.network); + bpf_usdt_readarg(5, ctx, &inbound.existing); + bpf_probe_read_user_str(&inbound.conn.addr, sizeof(inbound.conn.addr), address_pointer); + bpf_probe_read_user_str(&inbound.conn.type, sizeof(inbound.conn.type), conn_type_pointer); + inbound_connections.perf_submit(ctx, &inbound, sizeof(inbound)); + return 0; +}; + """ +class Connection(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_uint64), + ("addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH), + ("conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH), + ("network", ctypes.c_uint32), + ] + + def __repr__(self): + return f"Connection(peer={self.id}, addr={self.addr.decode('utf-8')}, conn_type={self.conn_type.decode('utf-8')}, network={self.network})" + + +class NewConnection(ctypes.Structure): + _fields_ = [ + ("conn", Connection), + ("existing", ctypes.c_uint64), + ] + + def __repr__(self): + return f"NewConnection(conn={self.conn}, existing={self.existing})" + class NetTracepointTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -92,6 +146,10 @@ class NetTracepointTest(BitcoinTestFramework): self.skip_if_no_bpf_permissions() def run_test(self): + self.p2p_message_tracepoint_test() + self.inbound_conn_tracepoint_test() + + def p2p_message_tracepoint_test(self): # Tests the net:inbound_message and net:outbound_message tracepoints # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net @@ -166,7 +224,44 @@ class NetTracepointTest(BitcoinTestFramework): bpf.cleanup() + test_node.peer_disconnect() + def inbound_conn_tracepoint_test(self): + self.log.info("hook into the net:inbound_connection tracepoint") + ctx = USDT(pid=self.nodes[0].process.pid) + ctx.enable_probe(probe="net:inbound_connection", + fn_name="trace_inbound_connection") + bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + inbound_connections = [] + EXPECTED_INBOUND_CONNECTIONS = 2 + + def handle_inbound_connection(_, data, __): + nonlocal inbound_connections + event = ctypes.cast(data, ctypes.POINTER(NewConnection)).contents + self.log.info(f"handle_inbound_connection(): {event}") + inbound_connections.append(event) + + bpf["inbound_connections"].open_perf_buffer(handle_inbound_connection) + + self.log.info("connect two P2P test nodes to our bitcoind node") + testnodes = list() + for _ in range(EXPECTED_INBOUND_CONNECTIONS): + testnode = P2PInterface() + self.nodes[0].add_p2p_connection(testnode) + testnodes.append(testnode) + bpf.perf_buffer_poll(timeout=200) + + assert_equal(EXPECTED_INBOUND_CONNECTIONS, len(inbound_connections)) + for inbound_connection in inbound_connections: + assert inbound_connection.conn.id > 0 + assert inbound_connection.existing > 0 + assert_equal(b'inbound', inbound_connection.conn.conn_type) + assert_equal(NETWORK_TYPE_UNROUTABLE, inbound_connection.conn.network) + + bpf.cleanup() + for node in testnodes: + node.peer_disconnect() if __name__ == '__main__': NetTracepointTest(__file__).main()