Skip to content

Commit 0dc16c1

Browse files
authored
Merge pull request #54 from OpenCloudOS/dev
Dev
2 parents dc113ab + eb7f91b commit 0dc16c1

23 files changed

Lines changed: 1115 additions & 287 deletions

common.mk

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ COMMON_SHARED := $(ROOT)/shared/pkt_utils.c $(COMPONENT)/net_utils.c \
44
$(ROOT)/shared/bpf_utils.c
55

66
CFLAGS += -I./ -I$(ROOT)/shared/bpf/
7-
BPF_CFLAGS = $(CFLAGS) -Wno-unused-function
7+
BPF_CFLAGS = $(CFLAGS) -Wno-unused-function \
8+
-Wno-compare-distinct-pointer-types -Wuninitialized \
9+
-D__TARGET_ARCH_$(SRCARCH)
810
HOST_CFLAGS = \
9-
-lbpf -lelf -lz -g -O2 -static $(CFLAGS) \
11+
-lbpf -lelf -lz -O2 -static $(CFLAGS) \
1012
-Wno-deprecated-declarations -DVERSION=$(VERSION) \
1113
-DRELEASE=$(RELEASE) \
1214
-I$(ROOT)/shared/ -I$(ROOT)/component
@@ -53,9 +55,11 @@ $(error kernel headers not exist in COMPAT mdoe, please install it)
5355
endif
5456
kheaders_cmd := ln -s vmlinux_header.h kheaders.h
5557
CFLAGS += -DCOMPAT_MODE
56-
BPF_CFLAGS += $(KERNEL_CFLAGS) -DBPF_NO_GLOBAL_DATA
58+
BPF_CFLAGS += $(KERNEL_CFLAGS) -DBPF_NO_GLOBAL_DATA \
59+
-DBPF_NO_PRESERVE_ACCESS_INDEX -g
5760
else
5861
kheaders_cmd := ln -s ../shared/bpf/vmlinux.h kheaders.h
62+
BPF_CFLAGS += -target bpf -g
5963
endif
6064

6165
ifndef BPFTOOL
@@ -78,14 +82,14 @@ kheaders.h:
7882
$(call kheaders_cmd)
7983

8084
progs/%.o: progs/%.c $(BPF_EXTRA_DEP)
81-
clang -O2 -c -g -S -Wall -fno-asynchronous-unwind-tables \
85+
clang -O2 -c -S -Wall -fno-asynchronous-unwind-tables \
8286
-Wno-incompatible-pointer-types-discards-qualifiers \
8387
$< -emit-llvm -Wno-unknown-attributes $(BPF_CFLAGS) -Xclang \
8488
-disable-llvm-passes -o - | \
8589
opt -O2 -mtriple=bpf-pc-linux | \
8690
llvm-dis | \
8791
llc -march=bpf -filetype=obj -o $@
88-
@file $@ | grep debug_info > /dev/null || (rm $@ && exit 1)
92+
@file $@ | grep eBPF > /dev/null || (rm $@ && exit 1)
8993

9094
%.skel.h: %.o
9195
$(BPFTOOL) gen skeleton $< > $@

component/list.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,43 @@ static inline int list_empty(const struct list_head *head)
9999
return head->next == head;
100100
}
101101

102+
static inline void __list_splice(const struct list_head *list,
103+
struct list_head *prev,
104+
struct list_head *next)
105+
{
106+
struct list_head *first = list->next;
107+
struct list_head *last = list->prev;
108+
109+
first->prev = prev;
110+
prev->next = first;
111+
112+
last->next = next;
113+
next->prev = last;
114+
}
115+
116+
static inline void list_splice(const struct list_head *list,
117+
struct list_head *head)
118+
{
119+
if (!list_empty(list))
120+
__list_splice(list, head, head->next);
121+
}
122+
123+
static inline void list_splice_tail(struct list_head *list,
124+
struct list_head *head)
125+
{
126+
if (!list_empty(list))
127+
__list_splice(list, head->prev, head);
128+
}
129+
130+
static inline void list_splice_init(struct list_head *list,
131+
struct list_head *head)
132+
{
133+
if (!list_empty(list)) {
134+
__list_splice(list, head, head->next);
135+
INIT_LIST_HEAD(list);
136+
}
137+
}
138+
102139
static inline int list_is_singular(const struct list_head *head)
103140
{
104141
return !list_empty(head) && (head->next == head->prev);

docs/develop.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# 开发手册
2+
3+
## 一、项目结构介绍
4+
5+
### 1.1 文件夹
6+
7+
- component:组件模块,封装了一些通用的C函数
8+
9+
- docs:项目的文档目录
10+
11+
- legacy:老的基于BCC的nettrace,已遗弃
12+
13+
- nodetrace:节点报文跟踪模块
14+
15+
- script:项目构建过程中需要用到的一些脚本等
16+
17+
- shared:网络报文处理(用户态和BPF)用到的一些封装的相对较为通用的函数
18+
19+
- bpf/sk_parse.h:封装的BPF程序相关的网络报文处理函数
20+
21+
- bpf/vmlinux*:经过BTF生成的内核头文件,包含了内核中所使用到的所有的结构体
22+
23+
- pkg_utils.c:封装的用户态使用的网络报文处理(解析、打印)函数
24+
25+
- bpf_utils.c:封装的一些用户简化BPF程序处理的函数
26+
27+
- src:nettrace的核心代码
28+
29+
- progs:BPF代码目录,其中kprobe.c是基于kprobe-BPF实现的BPF程序,tracing是基于tracing-BPF技术(还未实现,等待开发中)。除了该目录下,其他的代码都是用户态的代码。
30+
31+
- analysis.c:解析器代码,用来处理BPF程序采集到的数据
32+
33+
- dropreason.c:用于支持内核特性`skb drop reason`的用户态代码
34+
35+
- gen_trace.py:根据`trace.yaml`里定义的内核函数和tracepoint来生成`trace_group.c``kprobe_trace.h`
36+
37+
- nettrace.c:nettrace主程序的入口函数,定义了命令行参数等
38+
39+
- trace_probe.c:用于处理基于kprobe-BPF类型的BPF程序的加载和数据处理
40+
41+
- trace_tracing.c:用于处理基于tracing-BPF类型的BPF程序的加载和数据处理(暂未实现)
42+
43+
- trace.c:BPF程序的检查、加载等部分的功能函数
44+
45+
- trace.yaml:定义了nettrace所有的支持跟踪的内核函数和tracepoint。同时,诊断模式的规则也定义在了这里面
46+
47+
- vmlinux_header.h:对于不支持BTF(COMPAT模式)的情况,会使用这里的头文件
48+
49+
### 1.2 项目编译过程
50+
51+
项目的部分编译过程如下图所示,其中kprobe.o是经过CLANG编译出来的BPF的ELF文件,经过bpftool生成skel头文件。
52+
53+
```
54+
nettrace.c ----------------- nettrace
55+
trace.c |
56+
xxxxx |
57+
|
58+
|
59+
trace_group.c |
60+
╱ |
61+
trace.yaml -- gen_trace.py |
62+
╲ |
63+
kprobe_trace.h |
64+
╲ |
65+
kprobe.o → kprobe.skel.h
66+
67+
kprobe.c
68+
```
69+
70+
## 二、项目加载及运行
71+
72+
整个nettrace在运行过程中的代码执行逻辑如下图所示:
73+
74+
![](images/nettrace-start.svg)
75+
76+
这里没有列出`poll`(BPF事件处理)的逻辑,这块比较复杂,后面再补上。
77+
78+
## 三、开发介绍
79+
80+
### 3.1 trace.yaml格式
81+
82+
这个配置文件是项目的核心配置,里面按照`yaml`格式保存了所有的支持的内核函数,按照树状图的结构来配置的。在树状图中,所有的叶节点表示的都是trace(跟踪点,内核函数或者tracepoint),非叶节点代表的都是网络模块,也可以理解为目录。
83+
84+
网络模块(目录)的格式如下:
85+
86+
- name:名称
87+
- desc:一段描述
88+
- visual:是否对用户可见,默认true
89+
- children:子目录,或者当前目录下的traces
90+
91+
trace的格式:
92+
93+
- name:在未指定target的情况下,这个名称就是要跟踪的内核函数的名称。其格式为:内核函数名称:skb:sock,其中skb指的是skb参数在该内核函数参数中的位置,从0开始;sock代表sk在该内核函数中的位置(非必须)。如果只跟踪skb,那只需要写成:function:skb的格式即可。
94+
注意:function:skb是一种简写,这样写完就不需要写skb了,不然要提供skb字段才行
95+
- target:当前trace针对的内核函数。在name和内核函数不同的情况下,可以通过target来指定需要跟踪的内核函数。
96+
- skb:skb参数在该内核函数参数中的位置,从0开始
97+
- tp:tracepoint类型需要写的,tracepoint的位置。格式:dir:tracepoint
98+
- analyzer:解析器。该参数用于指定诊断模式下分析当前函数采集到的数据的诊断器,默认不进行特殊的数据格式检查和处理。
99+
- rules:定义诊断模式下的规则。规则有三种级别,分别是:
100+
- `info`:提示性规则,只是给个信息提示
101+
- `warn`:警告性规则,命中当前规则可能意味着网络可能会发生问题
102+
- `error`:出错性规则,命中当前规则意味着发生了网络丢包、网络异常
103+
rules是一个数组,可以为每个trace指定多条规则。rules的格式如下:
104+
- exp:命中当前规则的表达式。目前支持的表达式包括any(一定会命中)和返回值表达式。返回值表达式支持的语句包括:eq(等于)、ne(不等于)、lt(小于)、gt(大于)、range(指定一个范围)。例如:`eq 0`代表返回值等于0就命中规则。
105+
- msg:当命中规则后给出的信息
106+
- adv:诊断建议,一般用于`error`级别的规则。
107+
108+
### 3.2 诊断器开发
109+
110+
常规场景下,如果我们有需要跟踪的内核函数或者场景,只需要在`trace.yaml`中增加对应的`trace`即可。如果需要增加额外的数据采集和分析能力,就需要增加自定义`诊断器`了。新增诊断器所要做的修改包括以下内容:
111+
112+
**BPF代码编写**
113+
114+
`progs/kprobe.c`中使用`DEFINE_KPROBE_SKB`来定义一个用来跟踪内核函数的trace,这里我们假设要跟踪的内核函数为`sch_direct_xmit`
115+
116+
```c
117+
118+
DEFINE_KPROBE_SKB(sch_direct_xmit, 1) {
119+
struct Qdisc *q = nt_regs_ctx(ctx, 2);
120+
struct netdev_queue *txq;
121+
DECLARE_EVENT(qdisc_event_t, e)
122+
123+
txq = _C(q, dev_queue);
124+
e->state = _C(txq, state);
125+
xxxxxx
126+
return handle_entry(ctx);
127+
```
128+
129+
`DEFINE_KPROBE_SKB`第一个参数是内核函数名称,第二个是skb的索引。*注意*:这里的索引是从1开始的,和yaml里的不一样。`nt_regs_ctx`用于获取内核函数的参数,第一个参数是固定的,第二个参数代表要获取内核函数参数的索引,也是从1开始的。在这个函数里面,我们就可以编写自己的BPF代码来获取数据。
130+
131+
如果当前已经定义好的事件的结构体没有能满足要求的,那还需要定义自己的用于传递给用户态的结构体。其定义在`progs/shared.h`中,定义的方式可参考其中的`qdisc_event_t`:
132+
133+
```c
134+
DEFINE_EVENT(qdisc_event_t,
135+
event_field(u64, last_update)
136+
event_field(u32, state)
137+
event_field(u32, qlen)
138+
event_field(u32, flags)
139+
)
140+
```
141+
142+
**诊断器定义**
143+
144+
在BPF代码中我们定义了自己的结构体,并采集了一些自定义的信息。这些信息目前`analysis.c`里的代码是不能处理的,会被忽略,因此我们需要定义特殊的诊断器用于处理这些信息。自定义的诊断器都定义在analysis.c中,可以采用两个宏定义:
145+
146+
- DEFINE_ANALYZER_ENTRY:采用这个宏定义的诊断器会在函数入口(函数被执行的时候)触发的事件中被调用,针对的是kprobe阶段
147+
- DEFINE_ANALYZER_EXIT:采用这个宏定义的诊断器会在函数执行结束触发的事件中被调用,针对的是kretprobe阶段。如果要分析函数的返回值,需要使用这个宏。
148+
149+
宏定义的第一个参数为诊断器的名称,这里假设我们定义了`drop`诊断器。第二个是诊断器针对的模式,当前nettrace支持`basic/timeline/diag/drop/sock`五种模式。
150+
151+
```c
152+
DEFINE_ANALYZER_EXIT(qdisc, TRACE_MODE_DIAG_MASK)
153+
{
154+
/* e->event是基础类型的结构体,这里我们将其转为我们定义的结构体。这里的event
155+
* 变量就是qdisc_event_t类型的,我们就能获取到BPF中采集到的数据,并按照一定的
156+
* 格式显示出来。
157+
*/
158+
define_pure_event(qdisc_event_t, event, e->entry->event);
159+
char *msg = malloc(1024);
160+
int hz;
161+
162+
msg[0] = '\0';
163+
hz = kernel_hz();
164+
hz = hz > 0 ? hz : 1;
165+
sprintf(msg, PFMT_EMPH_STR(" *queue state: %x, flags: %x, "
166+
"last update: %lums, len: %lu*"), event->state,
167+
event->flags, (1000 * event->last_update) / hz,
168+
event->qlen);
169+
entry_set_msg(e->entry, msg);
170+
171+
rule_run(e->entry, trace, e->event.val);
172+
173+
return RESULT_CONT;
174+
}
175+
```
176+
177+
除了定义诊断器,我们还需要在analysis.h中声明这个诊断器,格式为:
178+
179+
```c
180+
DECLARE_ANALYZER(qdisc);
181+
```
182+
183+
**trace修改**
184+
185+
`trace.yaml`中将我们要跟踪的内核函数(这里为`sch_direct_xmit`)加进来,这里我们需要将其`analyzer`字段指定为我们刚才创建的诊断器qdisc。*需要注意的是*:由于这是一个自定义的trace(不是自动生成的,是我们在kprobe.c中手动定义的),因此这里不能给其指定skb或者sk:
186+
187+
```yaml
188+
- name: sch_direct_xmit
189+
analyzer: qdisc
190+
```
191+
192+

0 commit comments

Comments
 (0)