bpftrace是基于bpf实现的追踪工具,之前的文章的分享过bpftool,bpftrace的功能更加强大。
在内核中tcp发送数据是通过tcp_sendmsg 方法实现的
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);
如果我们想看看tcp发送数据的大小,我们需要获取第三个(arg2)参数size的值。我们尝试写一个bpftrace的小脚本看一下size的值。
执行效果如下:
# bpftrace -e 'k:tcp_sendmsg { @size
Attaching 2 probes...
@size:
[1] 6 | |
[2, 4) 0 | |
[4, 8) 0 | |
[8, 16) 0 | |
[16, 32) 110 |@@@@@@@@ |
[32, 64) 687 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[64, 128) 429 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[128, 256) 60 |@@@@ |
[256, 512) 96 |@@@@@@@ |
[512, 1K) 46 |@@@ |
[1K, 2K) 28 |@@ |
[2K, 4K) 8 | |
[4K, 8K) 10 | |
可以看到size的直方图,可以看到类似正态分布。
如果你想看看都是哪些进程大量发送数据,可以添加大于 1024 字节过滤器,如下:
# bpftrace -e 'k:tcp_sendmsg /arg2 > 1024/ { printf("PID %d,COMM: %s: %d bytes\n", pid,comm, arg2); }'
Attaching 1 probe...
PID 63700,COMM: kube-apiserver: 1581 bytes
PID 68206,COMM: etcd: 1079 bytes
PID 1784,COMM: kube-scheduler: 2249 bytes
PID 63700,COMM: kube-apiserver: 1581 bytes
PID 63700,COMM: kube-apiserver: 5811 bytes
PID 63700,COMM: kube-apiserver: 1581 byt
前面都是基于 tcp_sendmsg 的size参数去玩的,我们更进一步,看看发送数据包的内容,也就tcp_sendmsg的第一个参数。我们写这样一段脚本
#!/usr/local/bin/bpftrace
#include <net/sock.h>
k:tcp_sendmsg
{
@sk[tid] = arg0;
@size[tid] = arg2;
}
kr:tcp_sendmsg
/@sk[tid]/
{
$sk = (struct sock *)@sk[tid];
$size = @size[tid];
$af = $sk->__sk_common.skc_family;
if ($af == AF_INET) {
$daddr = ntop($af, $sk->__sk_common.skc_daddr);
$saddr = ntop($af, $sk->__sk_common.skc_rcv_saddr);
$lport = $sk->__sk_common.skc_num;
$dport = $sk->__sk_common.skc_dport;
$dport = ($dport >> 8) | (($dport << 8) & 0xff00);
printf("%-15s %-5d -> %-15s %-5d: %d bytes, retval %d\n",
$saddr, $lport, $daddr, $dport, $size, retval);
} else {
printf("IPv6...\n");
}
delete(@sk[tid]);
delete(@size[tid]);
}
这段脚本有点多,初学者可能有点懵,我稍微解释一下,脚本有两个挂载点,k代表kprobe,kr代表kretprobe。脚本通过解析 sk 这个结构体,去获取tcp 包源IP和端口、目的IP和端口,这里稍微需要了解一下内核的基础数据结构体。retval 代表tcp_sendmsg 函数返回值,这个bpftrace原生支持的。
执行这个脚本
127.0.0.1 2379 -> 127.0.0.1 1236 : 64 bytes, retval 64
127.0.0.1 1236 -> 127.0.0.1 2379 : 51 bytes, retval 51
127.0.0.1 2379 -> 127.0.0.1 1236 : 38 bytes, retval 38
10.133.18.121 65286 -> 10.133.18.121 6443 : 257 bytes, retval 257
是不是非常有意思,我们基于这些数据生成每个节点之间的网络关系。