diff --git a/doc/tracing.md b/doc/tracing.md index a092ece9c7e..c374cdb8583 100644 --- a/doc/tracing.md +++ b/doc/tracing.md @@ -137,6 +137,18 @@ Arguments passed: 1. Peer ID as `int64`. 2. Reason why the peer is misbehaving as `pointer to C-style String` (max. length 128 characters). +#### Tracepoint `net:closed_connection` + +Is called when a connection is closed. Passes information about the closed 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 de947a1a98d..ebcd3cb77d4 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -53,6 +53,7 @@ #include #include +TRACEPOINT_SEMAPHORE(net, closed_connection); TRACEPOINT_SEMAPHORE(net, evicted_inbound_connection); TRACEPOINT_SEMAPHORE(net, inbound_connection); TRACEPOINT_SEMAPHORE(net, outbound_connection); @@ -563,6 +564,13 @@ void CNode::CloseSocketDisconnect() if (m_sock) { LogDebug(BCLog::NET, "Resetting socket for peer=%d%s", GetId(), LogIP(fLogIPs)); m_sock.reset(); + + TRACEPOINT(net, closed_connection, + GetId(), + m_addr_name.c_str(), + ConnectionTypeAsString().c_str(), + ConnectedThroughNetwork(), + Ticks(m_connected)); } m_i2p_sam_session.reset(); } diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py index 3cddd340c8a..beb25461530 100755 --- a/test/functional/interface_usdt_net.py +++ b/test/functional/interface_usdt_net.py @@ -146,8 +146,8 @@ int trace_outbound_connection(struct pt_regs *ctx) { BPF_PERF_OUTPUT(evicted_inbound_connections); int trace_evicted_inbound_connection(struct pt_regs *ctx) { + struct ClosedConnection evicted = {}; 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); @@ -169,6 +169,21 @@ int trace_misbehaving_connection(struct pt_regs *ctx) { misbehaving_connections.perf_submit(ctx, &misbehaving, sizeof(misbehaving)); return 0; }; + +BPF_PERF_OUTPUT(closed_connections); +int trace_closed_connection(struct pt_regs *ctx) { + struct ClosedConnection closed = {}; + void *conn_type_pointer = NULL, *address_pointer = NULL; + bpf_usdt_readarg(1, ctx, &closed.conn.id); + bpf_usdt_readarg(2, ctx, &address_pointer); + bpf_usdt_readarg(3, ctx, &conn_type_pointer); + bpf_usdt_readarg(4, ctx, &closed.conn.network); + bpf_usdt_readarg(5, ctx, &closed.time_established); + bpf_probe_read_user_str(&closed.conn.addr, sizeof(closed.conn.addr), address_pointer); + bpf_probe_read_user_str(&closed.conn.type, sizeof(closed.conn.type), conn_type_pointer); + closed_connections.perf_submit(ctx, &closed, sizeof(closed)); + return 0; +}; """ @@ -193,6 +208,7 @@ class NewConnection(ctypes.Structure): def __repr__(self): return f"NewConnection(conn={self.conn}, existing={self.existing})" + class ClosedConnection(ctypes.Structure): _fields_ = [ ("conn", Connection), @@ -230,6 +246,7 @@ class NetTracepointTest(BitcoinTestFramework): self.outbound_conn_tracepoint_test() self.evicted_inbound_conn_tracepoint_test() self.misbehaving_conn_tracepoint_test() + self.closed_conn_tracepoint_test() def p2p_message_tracepoint_test(self): # Tests the net:inbound_message and net:outbound_message tracepoints @@ -457,5 +474,43 @@ class NetTracepointTest(BitcoinTestFramework): bpf.cleanup() + def closed_conn_tracepoint_test(self): + self.log.info("hook into the net:closed_connection tracepoint") + ctx = USDT(pid=self.nodes[0].process.pid) + ctx.enable_probe(probe="net:closed_connection", + fn_name="trace_closed_connection") + bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + EXPECTED_CLOSED_CONNECTIONS = 2 + closed_connections = [] + + def handle_closed_connection(_, data, __): + event = ctypes.cast(data, ctypes.POINTER(ClosedConnection)).contents + self.log.info(f"handle_closed_connection(): {event}") + closed_connections.append(event) + + bpf["closed_connections"].open_perf_buffer(handle_closed_connection) + + self.log.info( + f"connect {EXPECTED_CLOSED_CONNECTIONS} P2P test nodes to our bitcoind node") + testnodes = list() + for p2p_idx in range(EXPECTED_CLOSED_CONNECTIONS): + testnode = P2PInterface() + self.nodes[0].add_p2p_connection(testnode) + testnodes.append(testnode) + for node in testnodes: + node.peer_disconnect() + self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 0) + bpf.perf_buffer_poll(timeout=400) + + assert_equal(EXPECTED_CLOSED_CONNECTIONS, len(closed_connections)) + for closed_connection in closed_connections: + assert closed_connection.conn.id > 0 + assert_equal("inbound", closed_connection.conn.conn_type.decode('utf-8')) + assert_equal(0, closed_connection.conn.network) + assert closed_connection.time_established > 0 + + bpf.cleanup() + if __name__ == '__main__': NetTracepointTest(__file__).main()