tracing: Tracepoints for in- and outbound P2P msgs
Can be used to monitor in- and outbound node traffic. Based on ealier work by jb55. Co-authored-by: William Casarin <jb55@jb55.com>
This commit is contained in:
7 files changed
+639
-1
No files matched your search
@@ -43,3 +43,123 @@ example, to point to release builds if needed. See the
|
|||||||
kernel VM. This means the bpftrace and BCC examples must be executed with root
|
kernel VM. This means the bpftrace and BCC examples must be executed with root
|
||||||
privileges. Make sure to carefully review any scripts that you run with root
|
privileges. Make sure to carefully review any scripts that you run with root
|
||||||
privileges first!**
|
privileges first!**
|
||||||
|
|
||||||
|
### log_p2p_traffic.bt
|
||||||
|
|
||||||
|
A bpftrace script logging information about inbound and outbound P2P network
|
||||||
|
messages. Based on the `net:inbound_message` and `net:outbound_message`
|
||||||
|
tracepoints.
|
||||||
|
|
||||||
|
By default, `bpftrace` limits strings to 64 bytes due to the limited stack size
|
||||||
|
in the eBPF VM. For example, Tor v3 addresses exceed the string size limit which
|
||||||
|
results in the port being cut off during logging. The string size limit can be
|
||||||
|
increased with the `BPFTRACE_STRLEN` environment variable (`BPFTRACE_STRLEN=70`
|
||||||
|
works fine).
|
||||||
|
|
||||||
|
```
|
||||||
|
$ bpftrace contrib/tracing/log_p2p_traffic.bt
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
```
|
||||||
|
outbound 'ping' msg to peer 11 (outbound-full-relay, [2a02:b10c:f747:1:ef:fake:ipv6:addr]:8333) with 8 bytes
|
||||||
|
inbound 'pong' msg from peer 11 (outbound-full-relay, [2a02:b10c:f747:1:ef:fake:ipv6:addr]:8333) with 8 bytes
|
||||||
|
inbound 'inv' msg from peer 16 (outbound-full-relay, XX.XX.XXX.121:8333) with 37 bytes
|
||||||
|
outbound 'getdata' msg to peer 16 (outbound-full-relay, XX.XX.XXX.121:8333) with 37 bytes
|
||||||
|
inbound 'tx' msg from peer 16 (outbound-full-relay, XX.XX.XXX.121:8333) with 222 bytes
|
||||||
|
outbound 'inv' msg to peer 9 (outbound-full-relay, faketorv3addressa2ufa6odvoi3s77j4uegey0xb10csyfyve2t33curbyd.onion:8333) with 37 bytes
|
||||||
|
outbound 'inv' msg to peer 7 (outbound-full-relay, XX.XX.XXX.242:8333) with 37 bytes
|
||||||
|
…
|
||||||
|
```
|
||||||
|
|
||||||
|
### p2p_monitor.py
|
||||||
|
|
||||||
|
A BCC Python script using curses for an interactive P2P message monitor. Based
|
||||||
|
on the `net:inbound_message` and `net:outbound_message` tracepoints.
|
||||||
|
|
||||||
|
Inbound and outbound traffic is listed for each peer together with information
|
||||||
|
about the connection. Peers can be selected individually to view recent P2P
|
||||||
|
messages.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 contrib/tracing/p2p_monitor.py ./src/bitcoind
|
||||||
|
```
|
||||||
|
|
||||||
|
Lists selectable peers and traffic and connection information.
|
||||||
|
```
|
||||||
|
P2P Message Monitor
|
||||||
|
Navigate with UP/DOWN or J/K and select a peer with ENTER or SPACE to see individual P2P messages
|
||||||
|
|
||||||
|
PEER OUTBOUND INBOUND TYPE ADDR
|
||||||
|
0 46 398 byte 61 1407590 byte block-relay-only XX.XX.XXX.196:8333
|
||||||
|
11 1156 253570 byte 3431 2394924 byte outbound-full-relay XXX.X.XX.179:8333
|
||||||
|
13 3425 1809620 byte 1236 305458 byte inbound XXX.X.X.X:60380
|
||||||
|
16 1046 241633 byte 1589 1199220 byte outbound-full-relay 4faketorv2pbfu7x.onion:8333
|
||||||
|
19 577 181679 byte 390 148951 byte outbound-full-relay kfake4vctorjv2o2.onion:8333
|
||||||
|
20 11 1248 byte 13 1283 byte block-relay-only [2600:fake:64d9:b10c:4436:aaaa:fe:bb]:8333
|
||||||
|
21 11 1248 byte 13 1299 byte block-relay-only XX.XXX.X.155:8333
|
||||||
|
22 5 103 byte 1 102 byte feeler XX.XX.XXX.173:8333
|
||||||
|
23 11 1248 byte 12 1255 byte block-relay-only XX.XXX.XXX.220:8333
|
||||||
|
24 3 103 byte 1 102 byte feeler XXX.XXX.XXX.64:8333
|
||||||
|
…
|
||||||
|
```
|
||||||
|
|
||||||
|
Showing recent P2P messages between our node and a selected peer.
|
||||||
|
|
||||||
|
```
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
| PEER 16 (4faketorv2pbfu7x.onion:8333) |
|
||||||
|
| OUR NODE outbound-full-relay PEER |
|
||||||
|
| <--- sendcmpct (9 bytes) |
|
||||||
|
| inv (37 byte) ---> |
|
||||||
|
| <--- ping (8 bytes) |
|
||||||
|
| pong (8 byte) ---> |
|
||||||
|
| inv (37 byte) ---> |
|
||||||
|
| <--- addr (31 bytes) |
|
||||||
|
| inv (37 byte) ---> |
|
||||||
|
| <--- getheaders (1029 bytes) |
|
||||||
|
| headers (1 byte) ---> |
|
||||||
|
| <--- feefilter (8 bytes) |
|
||||||
|
| <--- pong (8 bytes) |
|
||||||
|
| <--- headers (82 bytes) |
|
||||||
|
| <--- addr (30003 bytes) |
|
||||||
|
| inv (1261 byte) ---> |
|
||||||
|
| … |
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### log_raw_p2p_msgs.py
|
||||||
|
|
||||||
|
A BCC Python script showcasing eBPF and USDT limitations when passing data
|
||||||
|
larger than about 32kb. Based on the `net:inbound_message` and
|
||||||
|
`net:outbound_message` tracepoints.
|
||||||
|
|
||||||
|
Bitcoin P2P messages can be larger than 32kb (e.g. `tx`, `block`, ...). The
|
||||||
|
eBPF VM's stack is limited to 512 bytes, and we can't allocate more than about
|
||||||
|
32kb for a P2P message in the eBPF VM. The **message data is cut off** when the
|
||||||
|
message is larger than MAX_MSG_DATA_LENGTH (see script). This can be detected
|
||||||
|
in user-space by comparing the data length to the message length variable. The
|
||||||
|
message is cut off when the data length is smaller than the message length.
|
||||||
|
A warning is included with the printed message data.
|
||||||
|
|
||||||
|
Data is submitted to user-space (i.e. to this script) via a ring buffer. The
|
||||||
|
throughput of the ring buffer is limited. Each p2p_message is about 32kb in
|
||||||
|
size. In- or outbound messages submitted to the ring buffer in rapid
|
||||||
|
succession fill the ring buffer faster than it can be read. Some messages are
|
||||||
|
lost. BCC prints: `Possibly lost 2 samples` on lost messages.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 contrib/tracing/log_raw_p2p_msgs.py ./src/bitcoind
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Logging raw P2P messages.
|
||||||
|
Messages larger that about 32kb will be cut off!
|
||||||
|
Some messages might be lost!
|
||||||
|
outbound msg 'inv' from peer 4 (outbound-full-relay, XX.XXX.XX.4:8333) with 253 bytes: 0705000000be2245c8f844c9f763748e1a7…
|
||||||
|
…
|
||||||
|
Warning: incomplete message (only 32568 out of 53552 bytes)! inbound msg 'tx' from peer 32 (outbound-full-relay, XX.XXX.XXX.43:8333) with 53552 bytes: 020000000001fd3c01939c85ad6756ed9fc…
|
||||||
|
…
|
||||||
|
Possibly lost 2 samples
|
||||||
|
```
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bpftrace
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
{
|
||||||
|
printf("Logging P2P traffic\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
usdt:./src/bitcoind:net:inbound_message
|
||||||
|
{
|
||||||
|
$peer_id = (int64) arg0;
|
||||||
|
$peer_addr = str(arg1);
|
||||||
|
$peer_type = str(arg2);
|
||||||
|
$msg_type = str(arg3);
|
||||||
|
$msg_len = arg4;
|
||||||
|
printf("inbound '%s' msg from peer %d (%s, %s) with %d bytes\n", $msg_type, $peer_id, $peer_type, $peer_addr, $msg_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
usdt:./src/bitcoind:net:outbound_message
|
||||||
|
{
|
||||||
|
$peer_id = (int64) arg0;
|
||||||
|
$peer_addr = str(arg1);
|
||||||
|
$peer_type = str(arg2);
|
||||||
|
$msg_type = str(arg3);
|
||||||
|
$msg_len = arg4;
|
||||||
|
|
||||||
|
printf("outbound '%s' msg to peer %d (%s, %s) with %d bytes\n", $msg_type, $peer_id, $peer_type, $peer_addr, $msg_len);
|
||||||
|
}
|
||||||
|
|
||||||
Executable
+180
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
""" Demonstration of eBPF limitations and the effect on USDT with the
|
||||||
|
net:inbound_message and net:outbound_message tracepoints. """
|
||||||
|
|
||||||
|
# This script shows a limitation of eBPF when data larger than 32kb is passed to
|
||||||
|
# user-space. It uses BCC (https://github.com/iovisor/bcc) to load a sandboxed
|
||||||
|
# eBPF program into the Linux kernel (root privileges are required). The eBPF
|
||||||
|
# program attaches to two statically defined tracepoints. The tracepoint
|
||||||
|
# 'net:inbound_message' is called when a new P2P message is received, and
|
||||||
|
# 'net:outbound_message' is called on outbound P2P messages. The eBPF program
|
||||||
|
# submits the P2P messages to this script via a BPF ring buffer. The submitted
|
||||||
|
# messages are printed.
|
||||||
|
|
||||||
|
# eBPF Limitations:
|
||||||
|
#
|
||||||
|
# Bitcoin P2P messages can be larger than 32kb (e.g. tx, block, ...). The eBPF
|
||||||
|
# VM's stack is limited to 512 bytes, and we can't allocate more than about 32kb
|
||||||
|
# for a P2P message in the eBPF VM. The message data is cut off when the message
|
||||||
|
# is larger than MAX_MSG_DATA_LENGTH (see definition below). This can be detected
|
||||||
|
# in user-space by comparing the data length to the message length variable. The
|
||||||
|
# message is cut off when the data length is smaller than the message length.
|
||||||
|
# A warning is included with the printed message data.
|
||||||
|
#
|
||||||
|
# Data is submitted to user-space (i.e. to this script) via a ring buffer. The
|
||||||
|
# throughput of the ring buffer is limited. Each p2p_message is about 32kb in
|
||||||
|
# size. In- or outbound messages submitted to the ring buffer in rapid
|
||||||
|
# succession fill the ring buffer faster than it can be read. Some messages are
|
||||||
|
# lost.
|
||||||
|
#
|
||||||
|
# BCC prints: "Possibly lost 2 samples" on lost messages.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from bcc import BPF, USDT
|
||||||
|
|
||||||
|
# BCC: The C program to be compiled to an eBPF program (by BCC) and loaded into
|
||||||
|
# a sandboxed Linux kernel VM.
|
||||||
|
program = """
|
||||||
|
#include <uapi/linux/ptrace.h>
|
||||||
|
|
||||||
|
#define MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
|
||||||
|
|
||||||
|
// Maximum possible allocation size
|
||||||
|
// from include/linux/percpu.h in the Linux kernel
|
||||||
|
#define PCPU_MIN_UNIT_SIZE (32 << 10)
|
||||||
|
|
||||||
|
// Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
|
||||||
|
#define MAX_PEER_ADDR_LENGTH 62 + 6
|
||||||
|
#define MAX_PEER_CONN_TYPE_LENGTH 20
|
||||||
|
#define MAX_MSG_TYPE_LENGTH 20
|
||||||
|
#define MAX_MSG_DATA_LENGTH PCPU_MIN_UNIT_SIZE - 200
|
||||||
|
|
||||||
|
struct p2p_message
|
||||||
|
{
|
||||||
|
u64 peer_id;
|
||||||
|
char peer_addr[MAX_PEER_ADDR_LENGTH];
|
||||||
|
char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
|
||||||
|
char msg_type[MAX_MSG_TYPE_LENGTH];
|
||||||
|
u64 msg_size;
|
||||||
|
u8 msg[MAX_MSG_DATA_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
// We can't store the p2p_message struct on the eBPF stack as it is limited to
|
||||||
|
// 512 bytes and P2P message can be bigger than 512 bytes. However, we can use
|
||||||
|
// an BPF-array with a length of 1 to allocate up to 32768 bytes (this is
|
||||||
|
// defined by PCPU_MIN_UNIT_SIZE in include/linux/percpu.h in the Linux kernel).
|
||||||
|
// Also see https://github.com/iovisor/bcc/issues/2306
|
||||||
|
BPF_ARRAY(msg_arr, struct p2p_message, 1);
|
||||||
|
|
||||||
|
// Two BPF perf buffers for pushing data (here P2P messages) to user-space.
|
||||||
|
BPF_PERF_OUTPUT(inbound_messages);
|
||||||
|
BPF_PERF_OUTPUT(outbound_messages);
|
||||||
|
|
||||||
|
int trace_inbound_message(struct pt_regs *ctx) {
|
||||||
|
int idx = 0;
|
||||||
|
struct p2p_message *msg = msg_arr.lookup(&idx);
|
||||||
|
|
||||||
|
// lookup() does not return a NULL pointer. However, the BPF verifier
|
||||||
|
// requires an explicit check that that the `msg` pointer isn't a NULL
|
||||||
|
// pointer. See https://github.com/iovisor/bcc/issues/2595
|
||||||
|
if (msg == NULL) return 1;
|
||||||
|
|
||||||
|
bpf_usdt_readarg(1, ctx, &msg->peer_id);
|
||||||
|
bpf_usdt_readarg_p(2, ctx, &msg->peer_addr, MAX_PEER_ADDR_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(3, ctx, &msg->peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(4, ctx, &msg->msg_type, MAX_MSG_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg(5, ctx, &msg->msg_size);
|
||||||
|
bpf_usdt_readarg_p(6, ctx, &msg->msg, MIN(msg->msg_size, MAX_MSG_DATA_LENGTH));
|
||||||
|
|
||||||
|
inbound_messages.perf_submit(ctx, msg, sizeof(*msg));
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int trace_outbound_message(struct pt_regs *ctx) {
|
||||||
|
int idx = 0;
|
||||||
|
struct p2p_message *msg = msg_arr.lookup(&idx);
|
||||||
|
|
||||||
|
// lookup() does not return a NULL pointer. However, the BPF verifier
|
||||||
|
// requires an explicit check that that the `msg` pointer isn't a NULL
|
||||||
|
// pointer. See https://github.com/iovisor/bcc/issues/2595
|
||||||
|
if (msg == NULL) return 1;
|
||||||
|
|
||||||
|
bpf_usdt_readarg(1, ctx, &msg->peer_id);
|
||||||
|
bpf_usdt_readarg_p(2, ctx, &msg->peer_addr, MAX_PEER_ADDR_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(3, ctx, &msg->peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(4, ctx, &msg->msg_type, MAX_MSG_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg(5, ctx, &msg->msg_size);
|
||||||
|
bpf_usdt_readarg_p(6, ctx, &msg->msg, MIN(msg->msg_size, MAX_MSG_DATA_LENGTH));
|
||||||
|
|
||||||
|
outbound_messages.perf_submit(ctx, msg, sizeof(*msg));
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def print_message(event, inbound):
|
||||||
|
print(f"%s %s msg '%s' from peer %d (%s, %s) with %d bytes: %s" %
|
||||||
|
(
|
||||||
|
f"Warning: incomplete message (only %d out of %d bytes)!" % (
|
||||||
|
len(event.msg), event.msg_size) if len(event.msg) < event.msg_size else "",
|
||||||
|
"inbound" if inbound else "outbound",
|
||||||
|
event.msg_type.decode("utf-8"),
|
||||||
|
event.peer_id,
|
||||||
|
event.peer_conn_type.decode("utf-8"),
|
||||||
|
event.peer_addr.decode("utf-8"),
|
||||||
|
event.msg_size,
|
||||||
|
bytes(event.msg[:event.msg_size]).hex(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main(bitcoind_path):
|
||||||
|
bitcoind_with_usdts = USDT(path=str(bitcoind_path))
|
||||||
|
|
||||||
|
# attaching the trace functions defined in the BPF program to the tracepoints
|
||||||
|
bitcoind_with_usdts.enable_probe(
|
||||||
|
probe="inbound_message", fn_name="trace_inbound_message")
|
||||||
|
bitcoind_with_usdts.enable_probe(
|
||||||
|
probe="outbound_message", fn_name="trace_outbound_message")
|
||||||
|
bpf = BPF(text=program, usdt_contexts=[bitcoind_with_usdts])
|
||||||
|
|
||||||
|
# BCC: perf buffer handle function for inbound_messages
|
||||||
|
def handle_inbound(_, data, size):
|
||||||
|
""" Inbound message handler.
|
||||||
|
|
||||||
|
Called each time a message is submitted to the inbound_messages BPF table."""
|
||||||
|
|
||||||
|
event = bpf["inbound_messages"].event(data)
|
||||||
|
print_message(event, True)
|
||||||
|
|
||||||
|
# BCC: perf buffer handle function for outbound_messages
|
||||||
|
|
||||||
|
def handle_outbound(_, data, size):
|
||||||
|
""" Outbound message handler.
|
||||||
|
|
||||||
|
Called each time a message is submitted to the outbound_messages BPF table."""
|
||||||
|
|
||||||
|
event = bpf["outbound_messages"].event(data)
|
||||||
|
print_message(event, False)
|
||||||
|
|
||||||
|
# BCC: add handlers to the inbound and outbound perf buffers
|
||||||
|
bpf["inbound_messages"].open_perf_buffer(handle_inbound)
|
||||||
|
bpf["outbound_messages"].open_perf_buffer(handle_outbound)
|
||||||
|
|
||||||
|
print("Logging raw P2P messages.")
|
||||||
|
print("Messages larger that about 32kb will be cut off!")
|
||||||
|
print("Some messages might be lost!")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
bpf.perf_buffer_poll()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("USAGE:", sys.argv[0], "path/to/bitcoind")
|
||||||
|
exit()
|
||||||
|
path = sys.argv[1]
|
||||||
|
main(path)
|
||||||
Executable
+250
@@ -0,0 +1,250 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
""" Interactive bitcoind P2P network traffic monitor utilizing USDT and the
|
||||||
|
net:inbound_message and net:outbound_message tracepoints. """
|
||||||
|
|
||||||
|
# This script demonstrates what USDT for Bitcoin Core can enable. It uses BCC
|
||||||
|
# (https://github.com/iovisor/bcc) to load a sandboxed eBPF program into the
|
||||||
|
# Linux kernel (root privileges are required). The eBPF program attaches to two
|
||||||
|
# statically defined tracepoints. The tracepoint 'net:inbound_message' is called
|
||||||
|
# when a new P2P message is received, and 'net:outbound_message' is called on
|
||||||
|
# outbound P2P messages. The eBPF program submits the P2P messages to
|
||||||
|
# this script via a BPF ring buffer.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import curses
|
||||||
|
from curses import wrapper, panel
|
||||||
|
from bcc import BPF, USDT
|
||||||
|
|
||||||
|
# BCC: The C program to be compiled to an eBPF program (by BCC) and loaded into
|
||||||
|
# a sandboxed Linux kernel VM.
|
||||||
|
program = """
|
||||||
|
#include <uapi/linux/ptrace.h>
|
||||||
|
|
||||||
|
// Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
|
||||||
|
// I2P addresses are 60 chars + 6 chars for the port (':12345').
|
||||||
|
#define MAX_PEER_ADDR_LENGTH 62 + 6
|
||||||
|
#define MAX_PEER_CONN_TYPE_LENGTH 20
|
||||||
|
#define MAX_MSG_TYPE_LENGTH 20
|
||||||
|
|
||||||
|
struct p2p_message
|
||||||
|
{
|
||||||
|
u64 peer_id;
|
||||||
|
char peer_addr[MAX_PEER_ADDR_LENGTH];
|
||||||
|
char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
|
||||||
|
char msg_type[MAX_MSG_TYPE_LENGTH];
|
||||||
|
u64 msg_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Two BPF perf buffers for pushing data (here P2P messages) to user space.
|
||||||
|
BPF_PERF_OUTPUT(inbound_messages);
|
||||||
|
BPF_PERF_OUTPUT(outbound_messages);
|
||||||
|
|
||||||
|
int trace_inbound_message(struct pt_regs *ctx) {
|
||||||
|
struct p2p_message msg = {};
|
||||||
|
|
||||||
|
bpf_usdt_readarg(1, ctx, &msg.peer_id);
|
||||||
|
bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg(5, ctx, &msg.msg_size);
|
||||||
|
|
||||||
|
inbound_messages.perf_submit(ctx, &msg, sizeof(msg));
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int trace_outbound_message(struct pt_regs *ctx) {
|
||||||
|
struct p2p_message msg = {};
|
||||||
|
|
||||||
|
bpf_usdt_readarg(1, ctx, &msg.peer_id);
|
||||||
|
bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
|
||||||
|
bpf_usdt_readarg(5, ctx, &msg.msg_size);
|
||||||
|
|
||||||
|
outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Message:
|
||||||
|
""" A P2P network message. """
|
||||||
|
msg_type = ""
|
||||||
|
size = 0
|
||||||
|
data = bytes()
|
||||||
|
inbound = False
|
||||||
|
|
||||||
|
def __init__(self, msg_type, size, inbound):
|
||||||
|
self.msg_type = msg_type
|
||||||
|
self.size = size
|
||||||
|
self.inbound = inbound
|
||||||
|
|
||||||
|
|
||||||
|
class Peer:
|
||||||
|
""" A P2P network peer. """
|
||||||
|
id = 0
|
||||||
|
address = ""
|
||||||
|
connection_type = ""
|
||||||
|
last_messages = list()
|
||||||
|
|
||||||
|
total_inbound_msgs = 0
|
||||||
|
total_inbound_bytes = 0
|
||||||
|
total_outbound_msgs = 0
|
||||||
|
total_outbound_bytes = 0
|
||||||
|
|
||||||
|
def __init__(self, id, address, connection_type):
|
||||||
|
self.id = id
|
||||||
|
self.address = address
|
||||||
|
self.connection_type = connection_type
|
||||||
|
self.last_messages = list()
|
||||||
|
|
||||||
|
def add_message(self, message):
|
||||||
|
self.last_messages.append(message)
|
||||||
|
if len(self.last_messages) > 25:
|
||||||
|
self.last_messages.pop(0)
|
||||||
|
if message.inbound:
|
||||||
|
self.total_inbound_bytes += message.size
|
||||||
|
self.total_inbound_msgs += 1
|
||||||
|
else:
|
||||||
|
self.total_outbound_bytes += message.size
|
||||||
|
self.total_outbound_msgs += 1
|
||||||
|
|
||||||
|
|
||||||
|
def main(bitcoind_path):
|
||||||
|
peers = dict()
|
||||||
|
|
||||||
|
bitcoind_with_usdts = USDT(path=str(bitcoind_path))
|
||||||
|
|
||||||
|
# attaching the trace functions defined in the BPF program to the tracepoints
|
||||||
|
bitcoind_with_usdts.enable_probe(
|
||||||
|
probe="inbound_message", fn_name="trace_inbound_message")
|
||||||
|
bitcoind_with_usdts.enable_probe(
|
||||||
|
probe="outbound_message", fn_name="trace_outbound_message")
|
||||||
|
bpf = BPF(text=program, usdt_contexts=[bitcoind_with_usdts])
|
||||||
|
|
||||||
|
# BCC: perf buffer handle function for inbound_messages
|
||||||
|
def handle_inbound(_, data, size):
|
||||||
|
""" Inbound message handler.
|
||||||
|
|
||||||
|
Called each time a message is submitted to the inbound_messages BPF table."""
|
||||||
|
event = bpf["inbound_messages"].event(data)
|
||||||
|
if event.peer_id not in peers:
|
||||||
|
peer = Peer(event.peer_id, event.peer_addr.decode(
|
||||||
|
"utf-8"), event.peer_conn_type.decode("utf-8"))
|
||||||
|
peers[peer.id] = peer
|
||||||
|
peers[event.peer_id].add_message(
|
||||||
|
Message(event.msg_type.decode("utf-8"), event.msg_size, True))
|
||||||
|
|
||||||
|
# BCC: perf buffer handle function for outbound_messages
|
||||||
|
def handle_outbound(_, data, size):
|
||||||
|
""" Outbound message handler.
|
||||||
|
|
||||||
|
Called each time a message is submitted to the outbound_messages BPF table."""
|
||||||
|
event = bpf["outbound_messages"].event(data)
|
||||||
|
if event.peer_id not in peers:
|
||||||
|
peer = Peer(event.peer_id, event.peer_addr.decode(
|
||||||
|
"utf-8"), event.peer_conn_type.decode("utf-8"))
|
||||||
|
peers[peer.id] = peer
|
||||||
|
peers[event.peer_id].add_message(
|
||||||
|
Message(event.msg_type.decode("utf-8"), event.msg_size, False))
|
||||||
|
|
||||||
|
# BCC: add handlers to the inbound and outbound perf buffers
|
||||||
|
bpf["inbound_messages"].open_perf_buffer(handle_inbound)
|
||||||
|
bpf["outbound_messages"].open_perf_buffer(handle_outbound)
|
||||||
|
|
||||||
|
wrapper(loop, bpf, peers)
|
||||||
|
|
||||||
|
|
||||||
|
def loop(screen, bpf, peers):
|
||||||
|
screen.nodelay(1)
|
||||||
|
cur_list_pos = 0
|
||||||
|
win = curses.newwin(30, 70, 2, 7)
|
||||||
|
win.erase()
|
||||||
|
win.border(ord("|"), ord("|"), ord("-"), ord("-"),
|
||||||
|
ord("-"), ord("-"), ord("-"), ord("-"))
|
||||||
|
info_panel = panel.new_panel(win)
|
||||||
|
info_panel.hide()
|
||||||
|
|
||||||
|
ROWS_AVALIABLE_FOR_LIST = curses.LINES - 5
|
||||||
|
scroll = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# BCC: poll the perf buffers for new events or timeout after 50ms
|
||||||
|
bpf.perf_buffer_poll(timeout=50)
|
||||||
|
|
||||||
|
ch = screen.getch()
|
||||||
|
if (ch == curses.KEY_DOWN or ch == ord("j")) and cur_list_pos < len(
|
||||||
|
peers.keys()) -1 and info_panel.hidden():
|
||||||
|
cur_list_pos += 1
|
||||||
|
if cur_list_pos >= ROWS_AVALIABLE_FOR_LIST:
|
||||||
|
scroll += 1
|
||||||
|
if (ch == curses.KEY_UP or ch == ord("k")) and cur_list_pos > 0 and info_panel.hidden():
|
||||||
|
cur_list_pos -= 1
|
||||||
|
if scroll > 0:
|
||||||
|
scroll -= 1
|
||||||
|
if ch == ord('\n') or ch == ord(' '):
|
||||||
|
if info_panel.hidden():
|
||||||
|
info_panel.show()
|
||||||
|
else:
|
||||||
|
info_panel.hide()
|
||||||
|
screen.erase()
|
||||||
|
render(screen, peers, cur_list_pos, scroll, ROWS_AVALIABLE_FOR_LIST, info_panel)
|
||||||
|
curses.panel.update_panels()
|
||||||
|
screen.refresh()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
def render(screen, peers, cur_list_pos, scroll, ROWS_AVALIABLE_FOR_LIST, info_panel):
|
||||||
|
""" renders the list of peers and details panel
|
||||||
|
|
||||||
|
This code is unrelated to USDT, BCC and BPF.
|
||||||
|
"""
|
||||||
|
header_format = "%6s %-20s %-20s %-22s %-67s"
|
||||||
|
row_format = "%6s %-5d %9d byte %-5d %9d byte %-22s %-67s"
|
||||||
|
|
||||||
|
screen.addstr(0, 1, (" P2P Message Monitor "), curses.A_REVERSE)
|
||||||
|
screen.addstr(
|
||||||
|
1, 0, (" Navigate with UP/DOWN or J/K and select a peer with ENTER or SPACE to see individual P2P messages"), curses.A_NORMAL)
|
||||||
|
screen.addstr(3, 0,
|
||||||
|
header_format % ("PEER", "OUTBOUND", "INBOUND", "TYPE", "ADDR"), curses.A_BOLD | curses.A_UNDERLINE)
|
||||||
|
peer_list = sorted(peers.keys())[scroll:ROWS_AVALIABLE_FOR_LIST+scroll]
|
||||||
|
for i, peer_id in enumerate(peer_list):
|
||||||
|
peer = peers[peer_id]
|
||||||
|
screen.addstr(i + 4, 0,
|
||||||
|
row_format % (peer.id, peer.total_outbound_msgs, peer.total_outbound_bytes,
|
||||||
|
peer.total_inbound_msgs, peer.total_inbound_bytes,
|
||||||
|
peer.connection_type, peer.address),
|
||||||
|
curses.A_REVERSE if i + scroll == cur_list_pos else curses.A_NORMAL)
|
||||||
|
if i + scroll == cur_list_pos:
|
||||||
|
info_window = info_panel.window()
|
||||||
|
info_window.erase()
|
||||||
|
info_window.border(
|
||||||
|
ord("|"), ord("|"), ord("-"), ord("-"),
|
||||||
|
ord("-"), ord("-"), ord("-"), ord("-"))
|
||||||
|
|
||||||
|
info_window.addstr(
|
||||||
|
1, 1, f"PEER {peer.id} ({peer.address})".center(68), curses.A_REVERSE | curses.A_BOLD)
|
||||||
|
info_window.addstr(
|
||||||
|
2, 1, f" OUR NODE{peer.connection_type:^54}PEER ",
|
||||||
|
curses.A_BOLD)
|
||||||
|
for i, msg in enumerate(peer.last_messages):
|
||||||
|
if msg.inbound:
|
||||||
|
info_window.addstr(
|
||||||
|
i + 3, 1, "%68s" %
|
||||||
|
(f"<--- {msg.msg_type} ({msg.size} bytes) "), curses.A_NORMAL)
|
||||||
|
else:
|
||||||
|
info_window.addstr(
|
||||||
|
i + 3, 1, " %s (%d byte) --->" %
|
||||||
|
(msg.msg_type, msg.size), curses.A_NORMAL)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("USAGE:", sys.argv[0], "path/to/bitcoind")
|
||||||
|
exit()
|
||||||
|
path = sys.argv[1]
|
||||||
|
main(path)
|
||||||
@@ -53,6 +53,46 @@ be found in [contrib/tracing].
|
|||||||
|
|
||||||
The currently available tracepoints are listed here.
|
The currently available tracepoints are listed here.
|
||||||
|
|
||||||
|
### Context `net`
|
||||||
|
|
||||||
|
#### Tracepoint `net:inbound_message`
|
||||||
|
|
||||||
|
Is called when a message is received from a peer over the P2P network. Passes
|
||||||
|
information about our peer, the connection and the message as arguments.
|
||||||
|
|
||||||
|
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. Message Type (inv, ping, getdata, addrv2, ...) as `pointer to C-style String` (max. length 20 characters)
|
||||||
|
5. Message Size in bytes as `uint64`
|
||||||
|
6. Message Bytes as `pointer to unsigned chars` (i.e. bytes)
|
||||||
|
|
||||||
|
Note: The message is passed to the tracepoint in full, however, due to space
|
||||||
|
limitations in the eBPF kernel VM it might not be possible to pass the message
|
||||||
|
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:outbound_message`
|
||||||
|
|
||||||
|
Is called when a message is send to a peer over the P2P network. Passes
|
||||||
|
information about our peer, the connection and the message as arguments.
|
||||||
|
|
||||||
|
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. Message Type (inv, ping, getdata, addrv2, ...) as `pointer to C-style String` (max. length 20 characters)
|
||||||
|
5. Message Size in bytes as `uint64`
|
||||||
|
6. Message Bytes as `pointer to unsigned chars` (i.e. bytes)
|
||||||
|
|
||||||
|
Note: The message is passed to the tracepoint in full, however, due to space
|
||||||
|
limitations in the eBPF kernel VM it might not be possible to pass the message
|
||||||
|
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.
|
||||||
|
|
||||||
## Adding tracepoints to Bitcoin Core
|
## Adding tracepoints to Bitcoin Core
|
||||||
|
|
||||||
To add a new tracepoint, `#include <util/trace.h>` in the compilation unit where
|
To add a new tracepoint, `#include <util/trace.h>` in the compilation unit where
|
||||||
|
|||||||
+11
-1
@@ -25,6 +25,7 @@
|
|||||||
#include <util/sock.h>
|
#include <util/sock.h>
|
||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
#include <util/thread.h>
|
#include <util/thread.h>
|
||||||
|
#include <util/trace.h>
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
@@ -3017,11 +3018,20 @@ bool CConnman::NodeFullyConnected(const CNode* pnode)
|
|||||||
void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
|
void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
|
||||||
{
|
{
|
||||||
size_t nMessageSize = msg.data.size();
|
size_t nMessageSize = msg.data.size();
|
||||||
LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", SanitizeString(msg.m_type), nMessageSize, pnode->GetId());
|
LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", msg.m_type, nMessageSize, pnode->GetId());
|
||||||
if (gArgs.GetBoolArg("-capturemessages", false)) {
|
if (gArgs.GetBoolArg("-capturemessages", false)) {
|
||||||
CaptureMessage(pnode->addr, msg.m_type, msg.data, /* incoming */ false);
|
CaptureMessage(pnode->addr, msg.m_type, msg.data, /* incoming */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TRACE6(net, outbound_message,
|
||||||
|
pnode->GetId(),
|
||||||
|
pnode->GetAddrName().c_str(),
|
||||||
|
pnode->ConnectionTypeAsString().c_str(),
|
||||||
|
msg.m_type.c_str(),
|
||||||
|
msg.data.size(),
|
||||||
|
msg.data.data()
|
||||||
|
);
|
||||||
|
|
||||||
// make sure we use the appropriate network transport format
|
// make sure we use the appropriate network transport format
|
||||||
std::vector<unsigned char> serializedHeader;
|
std::vector<unsigned char> serializedHeader;
|
||||||
pnode->m_serializer->prepareForTransport(msg, serializedHeader);
|
pnode->m_serializer->prepareForTransport(msg, serializedHeader);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <util/check.h> // For NDEBUG compile time check
|
#include <util/check.h> // For NDEBUG compile time check
|
||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
#include <util/system.h>
|
#include <util/system.h>
|
||||||
|
#include <util/trace.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -4052,6 +4053,15 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
|
|||||||
}
|
}
|
||||||
CNetMessage& msg(msgs.front());
|
CNetMessage& msg(msgs.front());
|
||||||
|
|
||||||
|
TRACE6(net, inbound_message,
|
||||||
|
pfrom->GetId(),
|
||||||
|
pfrom->GetAddrName().c_str(),
|
||||||
|
pfrom->ConnectionTypeAsString().c_str(),
|
||||||
|
msg.m_command.c_str(),
|
||||||
|
msg.m_recv.size(),
|
||||||
|
msg.m_recv.data()
|
||||||
|
);
|
||||||
|
|
||||||
if (gArgs.GetBoolArg("-capturemessages", false)) {
|
if (gArgs.GetBoolArg("-capturemessages", false)) {
|
||||||
CaptureMessage(pfrom->addr, msg.m_command, MakeUCharSpan(msg.m_recv), /* incoming */ true);
|
CaptureMessage(pfrom->addr, msg.m_command, MakeUCharSpan(msg.m_recv), /* incoming */ true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user