Skip to content

Commit 0562823

Browse files
authored
Merge pull request #62 from OpenCloudOS/dev
--trace-stack support
2 parents dba49fa + da36eea commit 0562823

21 files changed

Lines changed: 549 additions & 211 deletions

README.md

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ cd nettrace
8787
make all
8888
```
8989

90-
**注意**:对于不支持BTF的内核(内核版本低于5.3),在编译的时候需要加参数`COMPAT=1`,如下所示:
90+
**注意**:对于不支持BTF的内核(内核版本低于5.3),在编译的时候需要加参数`COMPAT=1`采用兼容模式进行编译,如下所示:
9191

9292
```shell
9393
make COMPAT=1 all
@@ -99,6 +99,8 @@ make COMPAT=1 all
9999
make KERNEL=/home/ubuntu/kernel COMPAT=1 all
100100
```
101101

102+
**注意:** 兼容模式编译出来的nettrace工具只能运行在和`KERNEL`内核版本相同的环境上。如果没有指定`KERNEL`,那采用的就是当前编译环境上的内核头文件,这就要求编译环境和运行环境所使用的内核要完全相同才能正常运行。否则,会发生意想不到的意外。
103+
102104
对于发行版版本较低,难以安装高版本clang的情况下,可以基于docker来进行代码的编译,具体可参考[2.4](#2.4-基于docker编译)章节来进行安装。
103105

104106
同时,对于`ubuntu 16.04/ubuntu 18.04`系统,其内核似乎存在BUG,即其使用的内核版本实际为4.15.18,uname看到的却是4.15.0。这导致了加载eBPF程序的时候内核版本不一致,无法加载。因此对于这种情况,可以使用KERN_VER参数来手动指定内核版本(计算方式为:`(4<<16) + (15<<8) + 18`):
@@ -125,6 +127,8 @@ docker run -it --rm --network=host --privileged -v <nettrace path>:/root/nettrac
125127
docker run -it --rm --network=host --privileged -v <nettrace path>:/root/nettrace -v /lib/modules/:/lib/modules/ -v /usr/src/:/usr/src/ imagedong/nettrace-build make -C /root/nettrace/ COMPAT=1 all
126128
```
127129
130+
**注意:** 兼容模式编译出来的nettrace工具只能运行在和`KERNEL`内核版本相同的环境上。如果没有指定`KERNEL`,那采用的就是当前编译环境上的内核头文件,这就要求编译环境和运行环境所使用的内核要完全相同才能正常运行。否则,会发生意想不到的意外。
131+
128132
**注意:** docker镜像可能会更新,为了使用最新的镜像,建议先试用命令`docker pull imagedong/nettrace-build`来获取最新的容器镜像。
129133
130134
## 三、使用方法
@@ -160,6 +164,7 @@ Usage:
160164
--monitor enable 'monitor' mode
161165
--drop-stack print the kernel function call stack of kfree_skb
162166
--min-latency the minial time to live of the skb
167+
--trace-stack print call stack for traces or group
163168
164169
-v show log information
165170
--debug show debug information
@@ -183,8 +188,9 @@ Usage:
183188
- `monitor`:启用监控模式。一种轻量化的实时监控系统中网络异常的模式(对内核版本有一定要求)。
184189
- `hooks`:结合netfilter做的适配,详见下文
185190
- `drop`:进行系统丢包监控,取代原先的`droptrace`
186-
- `drop-stack`: 打印kfree_skb内核函数的调用堆栈
191+
- `drop-stack`: 打印kfree_skb内核函数的调用堆栈,等价于`--trace-stack kfree_skb`
187192
- `min-latency`:根据报文的寿命进行过滤,仅打印处理时长超过该值的报文,单位为ms。该参数仅在默认和`diag`模式下可用。
193+
- `trace-stack`:指定需要进行堆栈打印的内核函数,可以指定多个,用“,”分隔。出于性能考虑,启用堆栈打印的内核函数不能超过16个。
188194
189195
下面我们首先来看一下默认模式下的工具使用方法。
190196
@@ -347,6 +353,76 @@ begin trace...
347353
[66.740110] [consume_skb ] ICMP: 192.168.122.8 -> 192.168.122.1 ping reply, seq: 1, id: 32535
348354
```
349355
356+
#### 3.1.6 堆栈打印
357+
358+
可以通过`--trace-stack`来指定需要进行内核堆栈打印的`traces`,使用方式与`--trace`完全一致。出于性能的考虑,目前启用堆栈打印的内核函数不能超过16个。基本用法:
359+
360+
```shell
361+
$ sudo ./nettrace -p icmp --trace-stack consume_skb,icmp_rcv
362+
begin trace...
363+
***************** ffff88882cafd200,ffff88882cafdc00 ***************
364+
[2846531.810609] [nf_hook_slow ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956 *ipv4 in chain: OUTPUT*
365+
[2846531.810612] [ip_output ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
366+
[2846531.810613] [nf_hook_slow ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956 *ipv4 in chain: POST_ROUTING*
367+
[2846531.810615] [ip_finish_output ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
368+
[2846531.810617] [ip_finish_output2 ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
369+
[2846531.810619] [__dev_queue_xmit ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
370+
[2846531.810621] [dev_hard_start_xmit ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956 *skb is successfully sent to the NIC driver*
371+
[2846531.810623] [enqueue_to_backlog ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
372+
[2846531.810630] [__netif_receive_skb_core.constprop.0] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
373+
[2846531.810632] [ip_rcv ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
374+
[2846531.810634] [ip_rcv_core ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
375+
[2846531.810635] [nf_hook_slow ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956 *ipv4 in chain: PRE_ROUTING*
376+
[2846531.810637] [ip_local_deliver ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
377+
[2846531.810639] [nf_hook_slow ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956 *ipv4 in chain: INPUT*
378+
[2846531.810640] [nft_do_chain ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956 *iptables table:filter, chain:INPUT*
379+
[2846531.810642] [ip_local_deliver_finish] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
380+
[2846531.810644] [skb_clone ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
381+
[2846531.810649] [icmp_rcv ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
382+
Call Stack:
383+
-> icmp_rcv+0x1
384+
-> ip_local_deliver_finish+0x7f
385+
-> ip_local_deliver+0xea
386+
-> ip_rcv+0x16d
387+
-> __netif_receive_skb_one_core+0x89
388+
-> process_backlog+0xa9
389+
-> __napi_poll+0x2e
390+
-> net_rx_action+0x28f
391+
-> __do_softirq+0xfb
392+
-> do_softirq+0xa7
393+
-> __local_bh_enable_ip+0x79
394+
-> ip_finish_output2+0x170
395+
-> __ip_finish_output+0xae
396+
-> ip_finish_output+0x36
397+
-> ip_output+0x73
398+
-> ip_push_pending_frames+0xab
399+
-> raw_sendmsg+0x651
400+
-> inet_sendmsg+0x6e
401+
-> sock_sendmsg+0x60
402+
-> __sys_sendto+0x10a
403+
-> __x64_sys_sendto+0x24
404+
-> do_syscall_64+0x3f
405+
-> entry_SYSCALL_64_after_hwframe+0x72
406+
407+
[2846531.810651] [ping_rcv ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
408+
[2846531.810653] [ping_lookup.isra.0 ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
409+
[2846531.810654] [kfree_skb ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
410+
[2846531.810659] [consume_skb ] ICMP: 127.0.0.1 -> 127.0.0.1 ping reply, seq: 3, id: 51956
411+
Call Stack:
412+
-> consume_skb+0xb8
413+
-> consume_skb+0xb8
414+
-> skb_free_datagram+0x11
415+
-> raw_recvmsg+0xb2
416+
-> inet_recvmsg+0x11d
417+
-> sock_recvmsg+0x6e
418+
-> ____sys_recvmsg+0x90
419+
-> ___sys_recvmsg+0x7c
420+
-> __sys_recvmsg+0x60
421+
-> __x64_sys_recvmsg+0x1d
422+
-> do_syscall_64+0x3f
423+
-> entry_SYSCALL_64_after_hwframe+0x72
424+
```
425+
350426
### 3.2 诊断模式
351427
352428
使用方式与上面的一致,加个`diag`参数即可使用诊断模式。上文的生命周期模式对于使用者的要求比较高,需要了解内核协议栈各个函数的用法、返回值的意义等,易用性较差。诊断模式是在生命周期模式的基础上,提供了更加丰富的信息,使得没有网络开发经验的人也可进行复杂网络问题的定位和分析。

component/arg_parse.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <malloc.h>
55
#include <string.h>
66
#include <stdlib.h>
7+
#include <inttypes.h>
78
#define _LINUX_IN_H
89
#include <netinet/in.h>
910
#include <arpa/inet.h>
@@ -94,15 +95,24 @@ int parse_args(int argc, char *argv[], arg_config_t *config,
9495
S_SET(bool, true);
9596
break;
9697
case OPTION_INT: {
97-
int val = atoi(optarg);
98+
char buf[32];
99+
int val;
100+
101+
if (sscanf(optarg, "%d%s", &val, buf) != 1) {
102+
printf("invalid arg value: %s\n",
103+
optarg);
104+
goto err;
105+
}
98106
S_DST(int, val);
99107
S_SET(bool, true);
100108
break;
101109
}
102110
case OPTION_U16BE:
103111
case OPTION_U16: {
104-
int val = atoi(optarg);
105-
if (val <=0 || val > 65535) {
112+
char buf[32];
113+
u16 val;
114+
115+
if (sscanf(optarg, "%hu%s", &val, buf) != 1) {
106116
printf("invalid arg value: %s\n",
107117
optarg);
108118
goto err;
@@ -114,8 +124,10 @@ int parse_args(int argc, char *argv[], arg_config_t *config,
114124
break;
115125
}
116126
case OPTION_U32: {
117-
long val = atol(optarg);
118-
if (val < 0 || val > 0xFFFFFFFF) {
127+
char buf[32];
128+
u32 val;
129+
130+
if (sscanf(optarg, "%u%s", &val, buf) != 1) {
119131
printf("invalid arg value: %s\n",
120132
optarg);
121133
goto err;

docs/develop.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,19 @@ trace.yaml -- gen_trace.py |
8181

8282
这个配置文件是项目的核心配置,里面按照`yaml`格式保存了所有的支持的内核函数,按照树状图的结构来配置的。在树状图中,所有的叶节点表示的都是trace(跟踪点,内核函数或者tracepoint),非叶节点代表的都是网络模块,也可以理解为目录。
8383

84-
网络模块(目录)的格式如下
84+
**网络模块(目录)格式**
8585

8686
- name:名称
8787
- desc:一段描述
8888
- visual:是否对用户可见,默认true
8989
- children:子目录,或者当前目录下的traces
9090

91-
trace的格式:
91+
**trace的格式**
9292

93-
- name:在未指定target的情况下,这个名称就是要跟踪的内核函数的名称。其格式为:内核函数名称:skb:sock,其中skb指的是skb参数在该内核函数参数中的位置,从0开始;sock代表sk在该内核函数中的位置(非必须)。如果只跟踪skb,那只需要写成:function:skb的格式即可。
94-
注意:function:skb是一种简写,这样写完就不需要写skb了,不然要提供skb字段才行
93+
- name:在未指定target的情况下,这个名称就是要跟踪的内核函数的名称。
9594
- target:当前trace针对的内核函数。在name和内核函数不同的情况下,可以通过target来指定需要跟踪的内核函数。
9695
- skb:skb参数在该内核函数参数中的位置,从0开始
96+
- sock:sk参数在该内核函数参数中的位置,从0开始(仅用作--sock模式)
9797
- tp:tracepoint类型需要写的,tracepoint的位置。格式:dir:tracepoint
9898
- analyzer:解析器。该参数用于指定诊断模式下分析当前函数采集到的数据的诊断器,默认不进行特殊的数据格式检查和处理。
9999
- rules:定义诊断模式下的规则。规则有三种级别,分别是:
@@ -105,6 +105,16 @@ trace的格式:
105105
- msg:当命中规则后给出的信息
106106
- adv:诊断建议,一般用于`error`级别的规则。
107107

108+
**简写方式**
109+
110+
可以在`name`中指定skb或者sk的索引,其格式为:内核函数名称:skb/sock,其中skb指的是skb参数在该内核函数参数中的位置,从0开始;sock代表sk在该内核函数中的位置(非必须,--sock模式下的跟踪点)。如果只跟踪skb,那只需要写成:function:skb的格式即可。如果当前定义的trace只包含`name`,那么可以进一步对其进行简化,只需要将trace定义为字符串即可,如下所示:
111+
112+
```yaml
113+
- name: ip_rcv:0 # 定义了一个trace对象,跟踪的是内核函数ip_rcv,其中skb在这个函数参数中的索引为0
114+
- ip_rcv:0 # 使用字符串来定义trace,作用和上面的一样
115+
- name: inet_listen/0 # 定义了一个trace对象,跟踪的是内核函数inet_listen,其中sock在这个函数参数中的索引为0,该trace仅在sock模式下有效。
116+
```
117+
108118
### 3.2 诊断器开发
109119
110120
常规场景下,如果我们有需要跟踪的内核函数或者场景,只需要在`trace.yaml`中增加对应的`trace`即可。如果需要增加额外的数据采集和分析能力,就需要增加自定义`诊断器`了。新增诊断器所要做的修改包括以下内容:

script/bash-completion.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
complete -W '-s --saddr -d --daddr --addr -p -D --dport -S --sport -P --port --pid --netns --netns-current -t --trace -v --detail --debug --bpf-debug --ret --basic --diag --diag-quiet --diag-keep --sock --min-latency --date --drop --drop-stack --hooks --monitor -h --help' nettrace
1+
complete -W '-s --saddr -d --daddr --addr -p -D --dport -S --sport -P --port --pid --netns --netns-current -t --trace -v --detail --debug --bpf-debug --ret --basic --diag --diag-quiet --diag-keep --sock --min-latency --date --drop --drop-stack --hooks --monitor --trace-stack -h --help' nettrace
22
complete -W '-h -s -d --addr -p --dport --sport --port -t -v --detail --stack --stack-tracer --timeline -c --ret --skb-mode --force-stack --tcp-flags -o --output' nettrace-legacy

script/zh_CN/nettrace.8

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,15 @@ nettrace \- Linux系统下的网络报文跟踪、网络问题诊断工具
9494
进行系统丢包监控,取代原先的\fB\fCdroptrace\fR
9595
.TP
9696
\fB\fC\-\-drop\-stack\fR
97-
打印kfree_skb内核函数的调用堆栈
97+
打印kfree\fIskb内核函数的调用堆栈,等价于`\-\-trace\-stack kfree\fPskb`
9898
.TP
99-
\fB\fC\-\-min\-latency\fR
99+
\fB\fC\-\-min\-latency\fR \fIlatency in ms\fP
100100
根据报文的寿命进行过滤,仅打印处理时长超过该值的报文,单位为ms。该参数仅在默认和\fB\fCdiag\fR模式下可用。
101101
.TP
102+
\fB\fC\-\-trace\-stack\fR \fItraces\fP
103+
指定需要进行堆栈打印的内核函数,可以指定多个,用“,”分隔。出于性能考虑,启用堆栈打印的
104+
内核函数不能超过16个。用法和格式与\fB\fC\-\-trace\fR完全一致。
105+
.TP
102106
\fB\fC\-v\fR
103107
显示程序启动的日志信息
104108
.TP
@@ -115,6 +119,9 @@ nettrace \- Linux系统下的网络报文跟踪、网络问题诊断工具
115119
.TP
116120
显示详细信息:
117121
\fInettrace \-p icmp \-s 192.168.1.8 \-\-detail\fP
122+
.TP
123+
打印堆栈:
124+
\fInettrace \-p icmp \-s 192.168.1.8 \-\-trace\-stack consume\fIskb,icmp\fPrcv\fP
118125
.SS 诊断模式
119126
.PP
120127
使用方式与上面的一致,加个\fB\fCdiag\fR参数即可使用诊断模式。上文的生命周期模式对于使用者的

script/zh_CN/nettrace.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,15 @@ nettrace - Linux系统下的网络报文跟踪、网络问题诊断工具
9898
进行系统丢包监控,取代原先的`droptrace`
9999

100100
`--drop-stack`
101-
打印kfree_skb内核函数的调用堆栈
101+
打印kfree_skb内核函数的调用堆栈,等价于`--trace-stack kfree_skb`
102102

103-
`--min-latency`
103+
`--min-latency` *latency in ms*
104104
根据报文的寿命进行过滤,仅打印处理时长超过该值的报文,单位为ms。该参数仅在默认和`diag`模式下可用。
105105

106+
`--trace-stack` *traces*
107+
指定需要进行堆栈打印的内核函数,可以指定多个,用“,”分隔。出于性能考虑,启用堆栈打印的
108+
内核函数不能超过16个。用法和格式与`--trace`完全一致。
109+
106110
`-v`
107111
显示程序启动的日志信息
108112

@@ -122,6 +126,9 @@ nettrace - Linux系统下的网络报文跟踪、网络问题诊断工具
122126
显示详细信息:
123127
*nettrace -p icmp -s 192.168.1.8 --detail*
124128

129+
打印堆栈:
130+
*nettrace -p icmp -s 192.168.1.8 --trace-stack consume_skb,icmp_rcv*
131+
125132
### 诊断模式
126133

127134
使用方式与上面的一致,加个`diag`参数即可使用诊断模式。上文的生命周期模式对于使用者的

shared/bpf/skb_macro.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,23 @@
4545
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
4646
#endif
4747

48+
#ifdef COMPAT_MODE
49+
#define bpf_core_helper_exist(name) false
50+
51+
#undef bpf_core_type_exists
52+
#define bpf_core_type_exists(type) false
53+
54+
#undef bpf_core_field_exists
55+
#define bpf_core_field_exists(field) false
56+
57+
#undef bpf_core_enum_value_exists
58+
#define bpf_core_enum_value_exists(value) false
59+
60+
#undef bpf_core_field_offset
61+
#define bpf_core_field_offset(type, field) offsetof(type, field)
62+
#else
63+
#define bpf_core_helper_exist(name) \
64+
bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_##name)
65+
#endif
66+
4867
#endif

shared/bpf/skb_parse.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@
77
#ifndef _H_BPF_SKB_UTILS
88
#define _H_BPF_SKB_UTILS
99

10+
#ifndef COMPAT_MODE
1011
#include <bpf/bpf_core_read.h>
12+
#endif
1113

1214
#include "skb_macro.h"
1315
#include "skb_shared.h"
1416

15-
#ifndef COMPAT_MODE
16-
#define bpf_core_helper_exist(name) \
17-
bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_##name)
18-
#else
19-
#define bpf_core_helper_exist(name) false
20-
#endif
2117

2218
typedef struct {
2319
pkt_args_t pkt;
@@ -405,13 +401,20 @@ static try_inline int __probe_parse_sk(parse_ctx_t *ctx)
405401
ske->l4.tcp.dport))
406402
goto err;
407403

408-
ske->rqlen = _C(&(sk->sk_receive_queue), qlen);
409-
ske->wqlen = _C(&(sk->sk_write_queue), qlen);
404+
ske->rqlen = _C(sk, sk_receive_queue.qlen);
405+
ske->wqlen = _C(sk, sk_write_queue.qlen);
410406

411407
ske->proto_l3 = l3_proto;
412408
ske->proto_l4 = l4_proto;
409+
ske->state = _C(skc, skc_state);
413410

414411
icsk = (void *)sk;
412+
bpf_probe_read_kernel(&ske->ca_state, sizeof(u8),
413+
(u8 *)icsk +
414+
bpf_core_field_offset(struct inet_connection_sock,
415+
icsk_retransmits) -
416+
1);
417+
415418
if (bpf_core_helper_exist(jiffies64))
416419
ske->timer_out = _C(icsk, icsk_timeout) - (unsigned long)bpf_jiffies64();
417420

shared/bpf/skb_shared.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ typedef struct {
117117
u16 proto_l3;
118118
u8 proto_l4;
119119
u8 timer_pending;
120+
u8 state;
121+
u8 ca_state;
120122
} sock_t;
121123

122124
#define TCP_FLAGS_ACK (1 << 4)

0 commit comments

Comments
 (0)