diff --git a/doc/tracing.md b/doc/tracing.md index a0720b68cf4..5f7f80c1d99 100644 --- a/doc/tracing.md +++ b/doc/tracing.md @@ -117,6 +117,17 @@ Arguments passed: 4. Network of the peer as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`. 5. Number of existing outbound connections as `uint64` including the newly opened outbound connection. +#### Tracepoint `net:evicted_inbound_connection` + +Is called when a inbound connection is evicted by us. Passes information about the evicted peer and the time at connection establishment. + +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. Connection established UNIX epoch timestamp in seconds as `uint64`. + ### Context `validation` #### Tracepoint `validation:block_connected` diff --git a/src/net.cpp b/src/net.cpp index 623901d9593..de947a1a98d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -53,6 +53,7 @@ #include #include +TRACEPOINT_SEMAPHORE(net, evicted_inbound_connection); TRACEPOINT_SEMAPHORE(net, inbound_connection); TRACEPOINT_SEMAPHORE(net, outbound_connection); TRACEPOINT_SEMAPHORE(net, outbound_message); @@ -1710,6 +1711,12 @@ bool CConnman::AttemptToEvictConnection() for (CNode* pnode : m_nodes) { if (pnode->GetId() == *node_id_to_evict) { LogDebug(BCLog::NET, "selected %s connection for eviction, %s", pnode->ConnectionTypeAsString(), pnode->DisconnectMsg(fLogIPs)); + TRACEPOINT(net, evicted_inbound_connection, + pnode->GetId(), + pnode->m_addr_name.c_str(), + pnode->ConnectionTypeAsString().c_str(), + pnode->ConnectedThroughNetwork(), + Ticks(pnode->m_connected)); pnode->fDisconnect = true; return true; } diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py index f4f77ab25bc..00ddf498491 100755 --- a/test/functional/interface_usdt_net.py +++ b/test/functional/interface_usdt_net.py @@ -29,6 +29,9 @@ MAX_MSG_DATA_LENGTH = 150 # from net_address.h NETWORK_TYPE_UNROUTABLE = 0 +# Use in -maxconnections. Results in a maximum of 21 inbound connections +MAX_CONNECTIONS = 32 +MAX_INBOUND_CONNECTIONS = MAX_CONNECTIONS - 10 - 1 # 10 outbound and 1 feeler net_tracepoints_program = """ #include @@ -70,6 +73,12 @@ struct NewConnection u64 existing; }; +struct ClosedConnection +{ + struct Connection conn; + u64 time_established; +}; + BPF_PERF_OUTPUT(inbound_messages); int trace_inbound_message(struct pt_regs *ctx) { struct p2p_message msg = {}; @@ -126,6 +135,21 @@ int trace_outbound_connection(struct pt_regs *ctx) { return 0; }; +BPF_PERF_OUTPUT(evicted_inbound_connections); +int trace_evicted_inbound_connection(struct pt_regs *ctx) { + void *conn_type_pointer = NULL, *address_pointer = NULL; + void* address_pointer; + bpf_usdt_readarg(1, ctx, &evicted.conn.id); + bpf_usdt_readarg(2, ctx, &address_pointer); + bpf_usdt_readarg(3, ctx, &conn_type_pointer); + bpf_usdt_readarg(4, ctx, &evicted.conn.network); + bpf_usdt_readarg(5, ctx, &evicted.time_established); + bpf_probe_read_user_str(&evicted.conn.addr, sizeof(evicted.conn.addr), address_pointer); + bpf_probe_read_user_str(&evicted.conn.type, sizeof(evicted.conn.type), conn_type_pointer); + evicted_inbound_connections.perf_submit(ctx, &evicted, sizeof(evicted)); + return 0; +}; + """ @@ -150,9 +174,19 @@ class NewConnection(ctypes.Structure): def __repr__(self): return f"NewConnection(conn={self.conn}, existing={self.existing})" +class ClosedConnection(ctypes.Structure): + _fields_ = [ + ("conn", Connection), + ("time_established", ctypes.c_uint64), + ] + + def __repr__(self): + return f"ClosedConnection(conn={self.conn}, time_established={self.time_established})" + class NetTracepointTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [[f'-maxconnections={MAX_CONNECTIONS}']] def skip_test_if_missing_module(self): self.skip_if_platform_not_linux() @@ -164,6 +198,7 @@ class NetTracepointTest(BitcoinTestFramework): self.p2p_message_tracepoint_test() self.inbound_conn_tracepoint_test() self.outbound_conn_tracepoint_test() + self.evicted_inbound_conn_tracepoint_test() def p2p_message_tracepoint_test(self): # Tests the net:inbound_message and net:outbound_message tracepoints @@ -320,5 +355,42 @@ class NetTracepointTest(BitcoinTestFramework): for node in testnodes: node.peer_disconnect() + def evicted_inbound_conn_tracepoint_test(self): + self.log.info("hook into the net:evicted_inbound_connection tracepoint") + ctx = USDT(pid=self.nodes[0].process.pid) + ctx.enable_probe(probe="net:evicted_inbound_connection", + fn_name="trace_evicted_inbound_connection") + bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + EXPECTED_EVICTED_CONNECTIONS = 2 + evicted_connections = [] + + def handle_evicted_inbound_connection(_, data, __): + event = ctypes.cast(data, ctypes.POINTER(ClosedConnection)).contents + self.log.info(f"handle_evicted_inbound_connection(): {event}") + evicted_connections.append(event) + + bpf["evicted_inbound_connections"].open_perf_buffer(handle_evicted_inbound_connection) + + self.log.info( + f"connect {MAX_INBOUND_CONNECTIONS + EXPECTED_EVICTED_CONNECTIONS} P2P test nodes to our bitcoind node and expect {EXPECTED_EVICTED_CONNECTIONS} evictions") + testnodes = list() + for p2p_idx in range(MAX_INBOUND_CONNECTIONS + EXPECTED_EVICTED_CONNECTIONS): + testnode = P2PInterface() + self.nodes[0].add_p2p_connection(testnode) + testnodes.append(testnode) + bpf.perf_buffer_poll(timeout=200) + + assert_equal(EXPECTED_EVICTED_CONNECTIONS, len(evicted_connections)) + for evicted_connection in evicted_connections: + assert evicted_connection.conn.id > 0 + assert evicted_connection.time_established > 0 + assert_equal("inbound", evicted_connection.conn.conn_type.decode('utf-8')) + assert_equal(NETWORK_TYPE_UNROUTABLE, evicted_connection.conn.network) + + bpf.cleanup() + for node in testnodes: + node.peer_disconnect() + if __name__ == '__main__': NetTracepointTest(__file__).main()