BPF_PROG_TYPE_SOCKET_FILTER,从宏字面意思比较容易想到实现的是socket filter功能,它区别于sockops和tracepoint等功能,需要额外借助setsockopt能力将功能函数和socket绑定,功能才能真正生效。
如何定该类型
在内核态功能函数中定义SEC("socketxxxx"),则会被解析为BPF_PROG_TYPE_SOCKET_FILTER类型功能。
比如内核中实现的三个example程序:
samples/bpf/sockex1_kern.c --->SEC("socket1")
samples/bpf/sockex1_user.c
samples/bpf/sockex2_kern.c --->SEC("socket2")
samples/bpf/sockex2_user.c
samples/bpf/sockex3_kern.c --->SEC("socket3")
samples/bpf/sockex3_user.c
功能程序加载
这个没什么好讲的,程序肯定是装载到了内核,内核定义了一个数据结构
478 struct bpf_prog {
479 u16 pages; /* Number of allocated pages */
480 u16 jited:1, /* Is our filter JIT'ed? */
481 jit_requested:1,/* archs need to JIT the prog */
482 undo_set_mem:1, /* Passed set_memory_ro() checkpoint */
483 gpl_compatible:1, /* Is filter GPL compatible? */
484 cb_access:1, /* Is control block accessed? */
485 dst_needed:1, /* Do we need dst entry? */
486 blinded:1, /* Was blinded */
487 is_func:1, /* program is a bpf function */
488 kprobe_override:1, /* Do we override a kprobe? */
489 has_callchain_buf:1; /* callchain buffer allocated? */
490 enum bpf_prog_type type; /* Type of BPF program */
491 enum bpf_attach_type expected_attach_type; /* For some prog types */
492 u32 len; /* Number of filter blocks */
493 u32 jited_len; /* Size of jited insns in bytes */
494 u8 tag[BPF_TAG_SIZE];
495 struct bpf_prog_aux *aux; /* Auxiliary fields */
496 struct sock_fprog_kern *orig_prog; /* Original BPF program */
497 unsigned int (*bpf_func)(const void *ctx,
498 const struct bpf_insn *insn);
499 /* Instructions for interpreter */
500 union {
501 struct sock_filter insns[0];
502 struct bpf_insn insnsi[0];
503 };
504 };
该数据解决在内核态功能函数被解析后逐渐初始化,并且最终初始化完整。
那么struct bpf_prog对象是如何被外部引用的呢 ? 通过文件的方式实现。
在linux内核中万物都可以定位为文件,通过文件的方式让隐晦的内容呈现给用户,用户通过文件fd的方式就可以快速的获取struct bpf_prog对象。片段代码如下:
1366 err = bpf_prog_new_fd(prog);
1367 if (err < 0) {
1368 /* failed to allocate fd.
1369 * bpf_prog_put() is needed because the above
1370 * bpf_prog_alloc_id() has published the prog
1371 * to the userspace and the userspace may
1372 * have refcnt-ed it through BPF_PROG_GET_FD_BY_ID.
1373 */
1374 bpf_prog_put(prog);
1375 return err;
1376 }
prog就是功能型函数的存储对象,通过bpf_prog_new_fd最终实现了和文件关联,并且后续可以通过文件方式关联找到struct bpf_prog对象,事实上setsockopt就是这么实现将struct bpf_prog对象和sock关联的。
功能程序关联struct bpf_prog对象肯定要和sock关联,然后才能在sock关键路径上被调用执行。
11 int main(int ac, char **argv)
12 {
13 char filename[256];
14 FILE *f;
15 int i, sock;
16
17 snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
18
19 if (load_bpf_file(filename)) {
20 printf("%s", bpf_log_buf);
21 return 1;
22 }
23
24 sock = open_raw_sock("lo");
25
26 assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, prog_fd,
27 sizeof(prog_fd[0])) == 0);
26行代码正式将prog对象和sock关联。
setsockopt 的关键代码片段:
1906 if (level == SOL_SOCKET)
1907 err =
1908 sock_setsockopt(sock, level, optname, optval,
1909 optlen);
1910 else
939 case SO_ATTACH_BPF:
940 ret = -EINVAL;
941 if (optlen == sizeof(u32)) {
942 u32 ufd;
943
944 ret = -EFAULT;
945 if (copy_from_user(&ufd, optval, sizeof(ufd)))
946 break;
947
948 ret = sk_attach_bpf(ufd, sk);
949 }
950 break;
1570 int sk_attach_bpf(u32 ufd, struct sock *sk)
1571 {
1572 struct bpf_prog *prog = __get_bpf(ufd, sk);
1573 int err;
1574
1575 if (IS_ERR(prog))
1576 return PTR_ERR(prog);
1577
1578 err = __sk_attach_prog(prog, sk);
1579 if (err < 0) {
1580 bpf_prog_put(prog);
1581 return err;
1582 }
1583
1584 return 0;
1585 }
1176 static struct bpf_prog *__bpf_prog_get(u32 ufd, enum bpf_prog_type *attach_type,
1177 bool attach_drv)
1178 {
1179 struct fd f = fdget(ufd);
1180 struct bpf_prog *prog;
1181
1182 prog = ____bpf_prog_get(f);
1183 if (IS_ERR(prog))
1184 return prog;
1185 if (!bpf_prog_get_ok(prog, attach_type, attach_drv)) {
1186 prog = ERR_PTR(-EINVAL);
1187 goto out;
1188 }
1189
1190 prog = bpf_prog_inc(prog);
1191 out:
1192 fdput(f);
1193 return prog;
1194 }
功能挂载点
已经将功能函数需要通过setsockopt实现和sock的关联,具体存储在sock什么对象上 ?
1430 static int __sk_attach_prog(struct bpf_prog *prog, struct sock *sk)
1431 {
1432 struct sk_filter *fp, *old_fp;
1433
1434 fp = kmalloc(sizeof(*fp), GFP_KERNEL);
1435 if (!fp)
1436 return -ENOMEM;
1437
1438 fp->prog = prog;
1439
1440 if (!__sk_filter_charge(sk, fp)) {
1441 kfree(fp);
1442 return -ENOMEM;
1443 }
1444 refcount_set(&fp->refcnt, 1);
1445
1446 old_fp = rcu_dereference_protected(sk->sk_filter,
1447 lockdep_sock_is_held(sk));
1448 rcu_assign_pointer(sk->sk_filter, fp);
1449
1450 if (old_fp)
1451 sk_filter_uncharge(sk, old_fp);
1452
1453 return 0;
1454 }
最终prog对象指向了sk->sk_filter->prog。
功能函数执行到这里已经很明显了,直接在内核过滤sk_filter就能找到相关的代码了。
481 int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
482 {
483 int err;
484
485 err = sk_filter(sk, skb);
486 if (err)
487 return err;
488
489 return __sock_queue_rcv_skb(sk, skb);
490 }
sk_filter就是BPF_PROG_TYPE_SOCKET_FILTER埋点函数。
raw_rcv -> raw_rcv_skb -> sock_queue_rcv_skb。
需要注意的是sk_filter看到的skb为拷贝后的副本。
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved