searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

ebpf 原理及示例分析

2023-03-30 11:48:59
237
0

一 eBPF是什么及工作流程

    eBPF 源于BPF,本质上是处于内核中的一个高效与灵活的虚类虚拟机组件,以一种安全的方式在许多内核 hook 点执行字节码。BPF 最初的目的是用于高效网络报文过滤,经过重新设计,eBPF 不再局限于网络协议栈,已经成为内核顶级的子系统,演进为一个通用执行引擎。开发者可基于 eBPF 开发性能分析工具、软件定义网络、安全等诸多场景。
eBPF 分为用户空间程序和内核程序两部分:
  • 用户空间程序负责加载BPF字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情
  • 内核中的BPF字节码负责在内核中执行特性事件,如需要也会将执行的结果通过maps或者perf-event事件发送至用户空间
  • 其中,用户空间程序和内核BPF字节码程序可以使用map结果实现双向通信,这为内核中运行的BPF字节码程序提供了更加灵活的控制
用户空间程序与内核中的BPF字节码交互的主要流程如下:
1. 使用LLVM或者GCC工具将编写的BPF代码程序编译成BPF字节码
2. 使用加载程序Loader将字节码加载至内核
3. 内核使用验证器(Verfier)组件保证执行字节码的安全性,以避免对内核造成灾难,在确认字节码安全后将其加载到对应的内核模块执行
4. 内核中运行的BPF字节码程序可以使用两种方式将数据回传至用户空间
    1) Maps方式可用于将内核中实现的统计摘要信息(比如测量延迟、堆栈信息)等回传至用户空间。
    2) perf-event用于将内核采集的事件实时发送至用户空间,用户空间程序实时读取分析
 
二 eBPF主要概念术语
1 bpf cmd
系统调用bpf()接口,通过cmd的参数通知内核将要执行的命令,如load prog、create map等。当前系统中支持的cmd列表如下。
enum bpf_cmd {
        BPF_MAP_CREATE,
        BPF_MAP_LOOKUP_ELEM,
        BPF_MAP_UPDATE_ELEM,
        BPF_MAP_DELETE_ELEM,
        BPF_MAP_GET_NEXT_KEY,
        BPF_PROG_LOAD,
        BPF_OBJ_PIN,
        BPF_OBJ_GET,
        BPF_PROG_ATTACH,
        BPF_PROG_DETACH,
        BPF_PROG_TEST_RUN,
        BPF_PROG_GET_NEXT_ID,
        BPF_MAP_GET_NEXT_ID,
        BPF_PROG_GET_FD_BY_ID,
        BPF_MAP_GET_FD_BY_ID,
        BPF_OBJ_GET_INFO_BY_FD,
        BPF_PROG_QUERY,
        BPF_RAW_TRACEPOINT_OPEN,
        BPF_BTF_LOAD, 
        BPF_BTF_GET_FD_BY_ID,
        BPF_TASK_FD_QUERY,
        BPF_MAP_LOOKUP_AND_DELETE_ELEM,
};
2.2 ebpf prog 类型
prog 类型与下面的attach type配合用于不同的场景,如socket_filter/sock_ops等用于过滤和重定向socket数据。随着内核版本更新,会逐步新增类型支持
enum bpf_prog_type {
        BPF_PROG_TYPE_UNSPEC,
        BPF_PROG_TYPE_SOCKET_FILTER,
        BPF_PROG_TYPE_KPROBE,
        BPF_PROG_TYPE_SCHED_CLS,
        BPF_PROG_TYPE_SCHED_ACT,
        BPF_PROG_TYPE_TRACEPOINT,
        BPF_PROG_TYPE_XDP,
        BPF_PROG_TYPE_PERF_EVENT,
        BPF_PROG_TYPE_CGROUP_SKB,
        BPF_PROG_TYPE_CGROUP_SOCK,
        BPF_PROG_TYPE_LWT_IN,
        BPF_PROG_TYPE_LWT_OUT,
        BPF_PROG_TYPE_LWT_XMIT,
        BPF_PROG_TYPE_SOCK_OPS,
        BPF_PROG_TYPE_SK_SKB,
        BPF_PROG_TYPE_CGROUP_DEVICE,
        BPF_PROG_TYPE_SK_MSG,
        BPF_PROG_TYPE_RAW_TRACEPOINT,
        BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
        BPF_PROG_TYPE_LWT_SEG6LOCAL,
        BPF_PROG_TYPE_LIRC_MODE2,
        BPF_PROG_TYPE_SK_REUSEPORT,
        BPF_PROG_TYPE_FLOW_DISSECTOR,
        BPF_PROG_TYPE_CGROUP_SYSCTL,
        BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
};
2.3 bpf attach 类型
通过系统调用bpf()将ebpf prog attach到具体的hook点上,这点有些类似netfilter,在内核不同流程上会有不同的hook点,ebpf程序将相关钩子函数挂载到对应hook点执行。
enum bpf_attach_type {
        BPF_CGROUP_INET_INGRESS,
        BPF_CGROUP_INET_EGRESS,
        BPF_CGROUP_INET_SOCK_CREATE,
        BPF_CGROUP_SOCK_OPS,
        BPF_SK_SKB_STREAM_PARSER,
        BPF_SK_SKB_STREAM_VERDICT,
        BPF_CGROUP_DEVICE,
        BPF_SK_MSG_VERDICT,
        BPF_CGROUP_INET4_BIND,
        BPF_CGROUP_INET6_BIND,
        BPF_CGROUP_INET4_CONNECT,
        BPF_CGROUP_INET6_CONNECT,
        BPF_CGROUP_INET4_POST_BIND,
        BPF_CGROUP_INET6_POST_BIND,
        BPF_CGROUP_UDP4_SENDMSG,
        BPF_CGROUP_UDP6_SENDMSG,
        BPF_LIRC_MODE2,
        BPF_FLOW_DISSECTOR,
        BPF_CGROUP_SYSCTL,
        BPF_CGROUP_UDP4_RECVMSG = 19,
        BPF_CGROUP_UDP6_RECVMSG,
        __MAX_BPF_ATTACH_TYPE
};
2.4 map类型
BPF Map 是驻留在内核空间中的高效Key/Value store.BPF Map 的交互场景主要有以下几种:
  1. BPF 程序和用户态程序的交互:BPF 程序运行完,得到的结果存储到 map 中,供用户态程序通过文件描述符访问
  2. BPF 程序和内核态程序的交互:和 BPF 程序以外的内核程序交互,也可以使用 map 作为中介
  3. BPF 程序间交互:如果 BPF 程序内部需要用全局变量来交互,但是由于安全原因 BPF 程序不允许访问全局变量,可以使用 map 来充当全局变量
enum bpf_map_type {
        BPF_MAP_TYPE_UNSPEC,
        BPF_MAP_TYPE_HASH,
        BPF_MAP_TYPE_ARRAY,
        BPF_MAP_TYPE_PROG_ARRAY,
        BPF_MAP_TYPE_PERF_EVENT_ARRAY,
        BPF_MAP_TYPE_PERCPU_HASH,
        BPF_MAP_TYPE_PERCPU_ARRAY,
        BPF_MAP_TYPE_STACK_TRACE,
        BPF_MAP_TYPE_CGROUP_ARRAY,
        BPF_MAP_TYPE_LRU_HASH,
        BPF_MAP_TYPE_LRU_PERCPU_HASH,
        BPF_MAP_TYPE_LPM_TRIE,
        BPF_MAP_TYPE_ARRAY_OF_MAPS,
        BPF_MAP_TYPE_HASH_OF_MAPS,
        BPF_MAP_TYPE_DEVMAP,
        BPF_MAP_TYPE_SOCKMAP,
        BPF_MAP_TYPE_CPUMAP,
        BPF_MAP_TYPE_XSKMAP,
        BPF_MAP_TYPE_SOCKHASH,
        BPF_MAP_TYPE_CGROUP_STORAGE,
        BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
        BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
        BPF_MAP_TYPE_QUEUE,
        BPF_MAP_TYPE_STACK,
};
 
三 示例分析
    下面以一个内核源码selftest中的简单示例,来分析ebpf程序的执行流程和主要组件之间的配合。示例文件tools/testing/selftests/bpf/test_sock.c,为了流程清晰,便于分析,仅保留“bind reject all”case,来分析整个ebpf的加载流程及工作原理。
    首先,看一下ebpf具体执行指令信息,该部分大体含义为,创建ebpf钩子函数,attach到BPF_CGROUP_INET4_POST_BIND hook点,然后创建socket(AF_INET, SOCK_STREAM, 0)并bind()到指定地址。
static struct sock_test tests[] = {
        {
                "bind4 reject all",
                .insns = {
                        BPF_MOV64_IMM(BPF_REG_0, 0),
                        BPF_EXIT_INSN(),
                },
                BPF_CGROUP_INET4_POST_BIND,
                BPF_CGROUP_INET4_POST_BIND,
                AF_INET,
                SOCK_STREAM,
                "0.0.0.0",
                0,
                BIND_REJECT,
        },
};
    其中。Insns部分,BPF_MOV64_IMM(BPF_REG_0, 0)指令,将寄存器R0置为0,表示bpf函数返回值,在该示例中会导致后面的bind()函数失败。关于ebpf指令说明可参考:eBPF Instruction Set — The Linux Kernel documentation
    接下来,看下具体加载过程,该示例涉及到cgroup,需要绑定到对应承担cgroup资源上如下,不过该部分非ebpf核心部件,不重点描述,感兴趣的可以看下源码文件。
    上面为示例核心流程,首先,1处创建prog,通过系统调用bpf(),使用BPF_PROG_LOAD cmd加载内核中去,其次,2处通过系统调用bpf(),使用BPF_PROG_ATTACH cmd将prog attach到BPF_CGROUP_INET4_POST_BIND对应hook点上。最后,3处创建socket,并执行bind()操作,并执行对应hook点的ebpf prog程序,阻断bing()执行。
首先,第一步创建prog
static int load_sock_prog(const struct bpf_insn *prog,
                          enum bpf_attach_type attach_type)
{
        struct bpf_load_program_attr attr;
        
        memset(&attr, 0, sizeof(struct bpf_load_program_attr));
        attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK;
        attr.expected_attach_type = attach_type;
        attr.insns = prog;
        attr.insns_cnt = probe_prog_length(attr.insns);
        attr.license = "GPL";
        
        return bpf_load_program_xattr(&attr, bpf_log_buf, BPF_LOG_BUF_SIZE);
}
...
static inline int sys_bpf_prog_load(union bpf_attr *attr, unsigned int size)
{
        int fd;

        do {
                fd = sys_bpf(BPF_PROG_LOAD, attr, size);
        } while (fd < 0 && errno == EAGAIN);

        return fd;
}
用户空间程序,最终通过bpf()系统调用进入内核空间,并通过cmd表示将要执行的操作,使用bpf_attr参数表示prog类型,指令、map等属性信息。
下面是bpf()系统调用接口,通过cmd和bpf_attr执行不同的操作,其中加载bpf程序对应cmd为BPF_PROG_LOAD
其次,第二步最终通过bpf()系统调用,BPF_PROG_ATTACH 执行将第一步创建的prog attach到对应hook点上。
其中,BPF_CGROUP_INET4_POST_BIND会在__inet_bind()流程中被执行。
最终,在执行bind()系统调用进入内核后,在对应hook通过遍历相关ebpf钩子列表array,执行对应的过滤操作,如下
int __cgroup_bpf_run_filter_sk(struct sock *sk,
                               enum bpf_attach_type type)
{
        struct cgroup *cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);
        int ret;

        ret = BPF_PROG_RUN_ARRAY(cgrp->bpf.effective[type], sk, BPF_PROG_RUN);
        return ret == 1 ? 0 : -EPERM;
}
EXPORT_SYMBOL(__cgroup_bpf_run_filter_sk);
...
#define __BPF_PROG_RUN_ARRAY(array, ctx, func, check_non_null)  \
        ({                                              \
                struct bpf_prog_array_item *_item;      \
                struct bpf_prog *_prog;                 \
                struct bpf_prog_array *_array;          \
                u32 _ret = 1;                           \
                preempt_disable();                      \
                rcu_read_lock();                        \
                _array = rcu_dereference(array);        \
                if (unlikely(check_non_null && !_array))\
                        goto _out;                      \
                _item = &_array->items[0];              \
                while ((_prog = READ_ONCE(_item->prog))) {              \
                        bpf_cgroup_storage_set(_item->cgroup_storage);  \
                        _ret &= func(_prog, ctx);       \
                        _item++;                        \
                }                                       \
_out:                                                   \
                rcu_read_unlock();                      \
                preempt_enable();                       \
                _ret;                                   \
         }) 

#define BPF_PROG_RUN_ARRAY(array, ctx, func)            \
        __BPF_PROG_RUN_ARRAY(array, ctx, func, false)
 
 
 
0条评论
0 / 1000
zhangzq
6文章数
1粉丝数
zhangzq
6 文章 | 1 粉丝
原创

ebpf 原理及示例分析

2023-03-30 11:48:59
237
0

一 eBPF是什么及工作流程

    eBPF 源于BPF,本质上是处于内核中的一个高效与灵活的虚类虚拟机组件,以一种安全的方式在许多内核 hook 点执行字节码。BPF 最初的目的是用于高效网络报文过滤,经过重新设计,eBPF 不再局限于网络协议栈,已经成为内核顶级的子系统,演进为一个通用执行引擎。开发者可基于 eBPF 开发性能分析工具、软件定义网络、安全等诸多场景。
eBPF 分为用户空间程序和内核程序两部分:
  • 用户空间程序负责加载BPF字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情
  • 内核中的BPF字节码负责在内核中执行特性事件,如需要也会将执行的结果通过maps或者perf-event事件发送至用户空间
  • 其中,用户空间程序和内核BPF字节码程序可以使用map结果实现双向通信,这为内核中运行的BPF字节码程序提供了更加灵活的控制
用户空间程序与内核中的BPF字节码交互的主要流程如下:
1. 使用LLVM或者GCC工具将编写的BPF代码程序编译成BPF字节码
2. 使用加载程序Loader将字节码加载至内核
3. 内核使用验证器(Verfier)组件保证执行字节码的安全性,以避免对内核造成灾难,在确认字节码安全后将其加载到对应的内核模块执行
4. 内核中运行的BPF字节码程序可以使用两种方式将数据回传至用户空间
    1) Maps方式可用于将内核中实现的统计摘要信息(比如测量延迟、堆栈信息)等回传至用户空间。
    2) perf-event用于将内核采集的事件实时发送至用户空间,用户空间程序实时读取分析
 
二 eBPF主要概念术语
1 bpf cmd
系统调用bpf()接口,通过cmd的参数通知内核将要执行的命令,如load prog、create map等。当前系统中支持的cmd列表如下。
enum bpf_cmd {
        BPF_MAP_CREATE,
        BPF_MAP_LOOKUP_ELEM,
        BPF_MAP_UPDATE_ELEM,
        BPF_MAP_DELETE_ELEM,
        BPF_MAP_GET_NEXT_KEY,
        BPF_PROG_LOAD,
        BPF_OBJ_PIN,
        BPF_OBJ_GET,
        BPF_PROG_ATTACH,
        BPF_PROG_DETACH,
        BPF_PROG_TEST_RUN,
        BPF_PROG_GET_NEXT_ID,
        BPF_MAP_GET_NEXT_ID,
        BPF_PROG_GET_FD_BY_ID,
        BPF_MAP_GET_FD_BY_ID,
        BPF_OBJ_GET_INFO_BY_FD,
        BPF_PROG_QUERY,
        BPF_RAW_TRACEPOINT_OPEN,
        BPF_BTF_LOAD, 
        BPF_BTF_GET_FD_BY_ID,
        BPF_TASK_FD_QUERY,
        BPF_MAP_LOOKUP_AND_DELETE_ELEM,
};
2.2 ebpf prog 类型
prog 类型与下面的attach type配合用于不同的场景,如socket_filter/sock_ops等用于过滤和重定向socket数据。随着内核版本更新,会逐步新增类型支持
enum bpf_prog_type {
        BPF_PROG_TYPE_UNSPEC,
        BPF_PROG_TYPE_SOCKET_FILTER,
        BPF_PROG_TYPE_KPROBE,
        BPF_PROG_TYPE_SCHED_CLS,
        BPF_PROG_TYPE_SCHED_ACT,
        BPF_PROG_TYPE_TRACEPOINT,
        BPF_PROG_TYPE_XDP,
        BPF_PROG_TYPE_PERF_EVENT,
        BPF_PROG_TYPE_CGROUP_SKB,
        BPF_PROG_TYPE_CGROUP_SOCK,
        BPF_PROG_TYPE_LWT_IN,
        BPF_PROG_TYPE_LWT_OUT,
        BPF_PROG_TYPE_LWT_XMIT,
        BPF_PROG_TYPE_SOCK_OPS,
        BPF_PROG_TYPE_SK_SKB,
        BPF_PROG_TYPE_CGROUP_DEVICE,
        BPF_PROG_TYPE_SK_MSG,
        BPF_PROG_TYPE_RAW_TRACEPOINT,
        BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
        BPF_PROG_TYPE_LWT_SEG6LOCAL,
        BPF_PROG_TYPE_LIRC_MODE2,
        BPF_PROG_TYPE_SK_REUSEPORT,
        BPF_PROG_TYPE_FLOW_DISSECTOR,
        BPF_PROG_TYPE_CGROUP_SYSCTL,
        BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
};
2.3 bpf attach 类型
通过系统调用bpf()将ebpf prog attach到具体的hook点上,这点有些类似netfilter,在内核不同流程上会有不同的hook点,ebpf程序将相关钩子函数挂载到对应hook点执行。
enum bpf_attach_type {
        BPF_CGROUP_INET_INGRESS,
        BPF_CGROUP_INET_EGRESS,
        BPF_CGROUP_INET_SOCK_CREATE,
        BPF_CGROUP_SOCK_OPS,
        BPF_SK_SKB_STREAM_PARSER,
        BPF_SK_SKB_STREAM_VERDICT,
        BPF_CGROUP_DEVICE,
        BPF_SK_MSG_VERDICT,
        BPF_CGROUP_INET4_BIND,
        BPF_CGROUP_INET6_BIND,
        BPF_CGROUP_INET4_CONNECT,
        BPF_CGROUP_INET6_CONNECT,
        BPF_CGROUP_INET4_POST_BIND,
        BPF_CGROUP_INET6_POST_BIND,
        BPF_CGROUP_UDP4_SENDMSG,
        BPF_CGROUP_UDP6_SENDMSG,
        BPF_LIRC_MODE2,
        BPF_FLOW_DISSECTOR,
        BPF_CGROUP_SYSCTL,
        BPF_CGROUP_UDP4_RECVMSG = 19,
        BPF_CGROUP_UDP6_RECVMSG,
        __MAX_BPF_ATTACH_TYPE
};
2.4 map类型
BPF Map 是驻留在内核空间中的高效Key/Value store.BPF Map 的交互场景主要有以下几种:
  1. BPF 程序和用户态程序的交互:BPF 程序运行完,得到的结果存储到 map 中,供用户态程序通过文件描述符访问
  2. BPF 程序和内核态程序的交互:和 BPF 程序以外的内核程序交互,也可以使用 map 作为中介
  3. BPF 程序间交互:如果 BPF 程序内部需要用全局变量来交互,但是由于安全原因 BPF 程序不允许访问全局变量,可以使用 map 来充当全局变量
enum bpf_map_type {
        BPF_MAP_TYPE_UNSPEC,
        BPF_MAP_TYPE_HASH,
        BPF_MAP_TYPE_ARRAY,
        BPF_MAP_TYPE_PROG_ARRAY,
        BPF_MAP_TYPE_PERF_EVENT_ARRAY,
        BPF_MAP_TYPE_PERCPU_HASH,
        BPF_MAP_TYPE_PERCPU_ARRAY,
        BPF_MAP_TYPE_STACK_TRACE,
        BPF_MAP_TYPE_CGROUP_ARRAY,
        BPF_MAP_TYPE_LRU_HASH,
        BPF_MAP_TYPE_LRU_PERCPU_HASH,
        BPF_MAP_TYPE_LPM_TRIE,
        BPF_MAP_TYPE_ARRAY_OF_MAPS,
        BPF_MAP_TYPE_HASH_OF_MAPS,
        BPF_MAP_TYPE_DEVMAP,
        BPF_MAP_TYPE_SOCKMAP,
        BPF_MAP_TYPE_CPUMAP,
        BPF_MAP_TYPE_XSKMAP,
        BPF_MAP_TYPE_SOCKHASH,
        BPF_MAP_TYPE_CGROUP_STORAGE,
        BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
        BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
        BPF_MAP_TYPE_QUEUE,
        BPF_MAP_TYPE_STACK,
};
 
三 示例分析
    下面以一个内核源码selftest中的简单示例,来分析ebpf程序的执行流程和主要组件之间的配合。示例文件tools/testing/selftests/bpf/test_sock.c,为了流程清晰,便于分析,仅保留“bind reject all”case,来分析整个ebpf的加载流程及工作原理。
    首先,看一下ebpf具体执行指令信息,该部分大体含义为,创建ebpf钩子函数,attach到BPF_CGROUP_INET4_POST_BIND hook点,然后创建socket(AF_INET, SOCK_STREAM, 0)并bind()到指定地址。
static struct sock_test tests[] = {
        {
                "bind4 reject all",
                .insns = {
                        BPF_MOV64_IMM(BPF_REG_0, 0),
                        BPF_EXIT_INSN(),
                },
                BPF_CGROUP_INET4_POST_BIND,
                BPF_CGROUP_INET4_POST_BIND,
                AF_INET,
                SOCK_STREAM,
                "0.0.0.0",
                0,
                BIND_REJECT,
        },
};
    其中。Insns部分,BPF_MOV64_IMM(BPF_REG_0, 0)指令,将寄存器R0置为0,表示bpf函数返回值,在该示例中会导致后面的bind()函数失败。关于ebpf指令说明可参考:eBPF Instruction Set — The Linux Kernel documentation
    接下来,看下具体加载过程,该示例涉及到cgroup,需要绑定到对应承担cgroup资源上如下,不过该部分非ebpf核心部件,不重点描述,感兴趣的可以看下源码文件。
    上面为示例核心流程,首先,1处创建prog,通过系统调用bpf(),使用BPF_PROG_LOAD cmd加载内核中去,其次,2处通过系统调用bpf(),使用BPF_PROG_ATTACH cmd将prog attach到BPF_CGROUP_INET4_POST_BIND对应hook点上。最后,3处创建socket,并执行bind()操作,并执行对应hook点的ebpf prog程序,阻断bing()执行。
首先,第一步创建prog
static int load_sock_prog(const struct bpf_insn *prog,
                          enum bpf_attach_type attach_type)
{
        struct bpf_load_program_attr attr;
        
        memset(&attr, 0, sizeof(struct bpf_load_program_attr));
        attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK;
        attr.expected_attach_type = attach_type;
        attr.insns = prog;
        attr.insns_cnt = probe_prog_length(attr.insns);
        attr.license = "GPL";
        
        return bpf_load_program_xattr(&attr, bpf_log_buf, BPF_LOG_BUF_SIZE);
}
...
static inline int sys_bpf_prog_load(union bpf_attr *attr, unsigned int size)
{
        int fd;

        do {
                fd = sys_bpf(BPF_PROG_LOAD, attr, size);
        } while (fd < 0 && errno == EAGAIN);

        return fd;
}
用户空间程序,最终通过bpf()系统调用进入内核空间,并通过cmd表示将要执行的操作,使用bpf_attr参数表示prog类型,指令、map等属性信息。
下面是bpf()系统调用接口,通过cmd和bpf_attr执行不同的操作,其中加载bpf程序对应cmd为BPF_PROG_LOAD
其次,第二步最终通过bpf()系统调用,BPF_PROG_ATTACH 执行将第一步创建的prog attach到对应hook点上。
其中,BPF_CGROUP_INET4_POST_BIND会在__inet_bind()流程中被执行。
最终,在执行bind()系统调用进入内核后,在对应hook通过遍历相关ebpf钩子列表array,执行对应的过滤操作,如下
int __cgroup_bpf_run_filter_sk(struct sock *sk,
                               enum bpf_attach_type type)
{
        struct cgroup *cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);
        int ret;

        ret = BPF_PROG_RUN_ARRAY(cgrp->bpf.effective[type], sk, BPF_PROG_RUN);
        return ret == 1 ? 0 : -EPERM;
}
EXPORT_SYMBOL(__cgroup_bpf_run_filter_sk);
...
#define __BPF_PROG_RUN_ARRAY(array, ctx, func, check_non_null)  \
        ({                                              \
                struct bpf_prog_array_item *_item;      \
                struct bpf_prog *_prog;                 \
                struct bpf_prog_array *_array;          \
                u32 _ret = 1;                           \
                preempt_disable();                      \
                rcu_read_lock();                        \
                _array = rcu_dereference(array);        \
                if (unlikely(check_non_null && !_array))\
                        goto _out;                      \
                _item = &_array->items[0];              \
                while ((_prog = READ_ONCE(_item->prog))) {              \
                        bpf_cgroup_storage_set(_item->cgroup_storage);  \
                        _ret &= func(_prog, ctx);       \
                        _item++;                        \
                }                                       \
_out:                                                   \
                rcu_read_unlock();                      \
                preempt_enable();                       \
                _ret;                                   \
         }) 

#define BPF_PROG_RUN_ARRAY(array, ctx, func)            \
        __BPF_PROG_RUN_ARRAY(array, ctx, func, false)
 
 
 
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0