这篇文章是监控QEMU
的网络流量分布。
QEMU
网络使用的技术是virtio
,在QEMU
端是virtio-net
, 在内核端是vhost-net
。由于这里是使用uprobes
来跟踪,所以,是对virtio-net
来挂钩。
在这里对virtio-net
的接收队列挂uprobes
和uretprobes
,对发送队列挂uretprobes
。同时对结果建立直方图。
QEMU
对发送和接收的主要处理函数如下:
// RX
static ssize_t virtio_net_receive_rcu(NetClientState *nc,
const uint8_t *buf,
size_t size) {
...
}
// TX
static int32_t virtio_net_flush_tx(VirtIONetQueue *q) {
...
}
为了检测virtio_net_receive_rcu
,将启用uprobe
和uretprobe
并创建大小为size_t
的单独直方图。为了检测virtio_net_flush_tx
,需启用一个uretprobe
。
在虚拟机里使用virtio-net
, 需要QEMU
以下列参数启动
-device virtio-net-pci,netdev=tapnet
-netdev tap,id=tapnet,vhost=off,ifname=tap0,script=/etc/qemu-ifup,downscript=no
由于使用直方图,就不需要使用BPF_PERF_OUTPUT
。
#!/usr/bin/pythonfrom bcc import BPF
from time import sleep
bpf_txt = """
#include <uapi/linux/ptrace.h>BPF_HISTOGRAM(rx_entry_hist);
BPF_HISTOGRAM(rx_retval_hist);
BPF_HISTOGRAM(tx_retval_hist);
int start_vnet_rec_rcu(struct pt_regs *ctx) {
u64 size;
size = PT_REGS_PARM3(ctx);
rx_entry_hist.increment(bpf_log2l(size));
return 0;
}
int end_vnet_rec_rcu(struct pt_regs *ctx) {
int retval;
retval = PT_REGS_RC(ctx);
rx_retval_hist.increment(bpf_log2l(retval));
return 0;
}
int end_vnet_tx(struct pt_regs *ctx) {
int retval;
retval = PT_REGS_RC(ctx);
tx_retval_hist.increment(bpf_log2l(retval));
return 0;
}
"""
# Load BPF program
dev_qemu_path = \
"/mnt/repos/oracle/qemu/build/x86_64-softmmu/qemu-system-x86_64"
bpf_ctx = BPF(text=bpf_txt)
bpf_ctx.attach_uprobe(name=dev_qemu_path,
sym="virtio_net_receive_rcu",
fn_name="start_vnet_rec_rcu")
bpf_ctx.attach_uretprobe(name=dev_qemu_path,
sym="virtio_net_receive_rcu",
fn_name="end_vnet_rec_rcu")
bpf_ctx.attach_uretprobe(name=dev_qemu_path,
sym="virtio_net_flush_tx",
fn_name="end_vnet_tx")
# Print header
print("Aggregating data from virtio-net RX & TX virtqueues... "
"Hit Ctrl-C to end.")
# Process arguments
while 1:
try:
sleep(1)
except KeyboardInterrupt:
print("\n")
break
# Output
print("Virtio-net RX (entry) log2 packet size histogram")
print("---------------------------------------------")
bpf_ctx["rx_entry_hist"].print_log2_hist("size")
print("\n")
print("Virtio-net RX (return) log2 packet size histogram")
print("----------------------------------------------")
bpf_ctx["rx_retval_hist"].print_log2_hist("retSize")
print("\n")
print("virtio-net TX (return) log2 number of packets transmitted")
bpf_ctx["tx_retval_hist"].print_log2_hist("num packets")
print("\n")
/mnt/repos/oracle/qemu/build/x86_64-softmmu/qemu-system-x86_64
请改成自己的qemu
的二进制文件
打开另一个窗口来运行这个脚本,然后在虚拟机产生一些网络流量。在这个例子和下一个例子中,只是简单地 wget 一个文件来生成网络流量。片刻之后,按 Ctrl-C 脚本,您应该会看到如下内容:
Aggregating data from virtio-net RX & TX virtqueues... Hit Ctrl-C to end.
^CVirtio-net RX (entry) log2 packet size histogram
---------------------------------------------
size : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 7 | |
64 -> 127 : 17 | |
128 -> 255 : 1 | |
256 -> 511 : 1 | |
512 -> 1023 : 5 | |
1024 -> 2047 : 47906 |**** |
2048 -> 4095 : 30912 |*** |
4096 -> 8191 : 100188 |********* |
8192 -> 16383 : 411462 |****************************************|
16384 -> 32767 : 278 | |
Virtio-net RX (return) log2 packet size histogram
----------------------------------------------
retSize : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 7 | |
64 -> 127 : 17 | |
128 -> 255 : 1 | |
256 -> 511 : 1 | |
512 -> 1023 : 5 | |
1024 -> 2047 : 47906 |**** |
2048 -> 4095 : 30912 |*** |
4096 -> 8191 : 100188 |********* |
8192 -> 16383 : 411462 |****************************************|
16384 -> 32767 : 278 | |
virtio-net TX (return) log2 number of packets transmitted
num packets : count distribution
0 -> 1 : 782827 |****************************************|
2 -> 3 : 78190 |*** |
4 -> 7 : 740 | |
8 -> 15 : 24 | |
16 -> 31 : 1 | |
如果脚本运行没有任何结果,请确保虚拟机启动参数
查看此脚本的输出,发现接收队列的参数size
和返回直方图与预期的一样。发送数据包的大部分操作通常一次只发送 1 个数据包。
BPF_HISTOGRAM(name)
:创建一个直方图表,原型BPF_HISTOGRAM(name, key_type=int, size=64)
PT_REGS_PARMn(ctx)
:获得函数第n个参数
hist.increment(bpf_log2l(val))
:增加2为底对数的值
attach_uprobe(name='...', sym='...', fn_name='...')
:对名称里name
的二进制文件的符号sym
进行挂uprobes
,挂的钩子在fn_name
指定
attach_uretprobe(name='...', sym='...', fn_name='...')
:对名称里name
的二进制文件的符号sym
进行挂uretprobes
,挂的钩子在fn_name
指定
bpf_ctx["hist_name"].print_log2_hist("label")
:将名为hist_name
的BPF
直方图表打印为ASCII
格式的直方图, 第一列的名称由label
指定
暗号:de340