导读:本期聚焦于上海GEO公司创作的《如何使用bpf_get_current_uid_gid获取用户ID实现网络访问权限控制?》,敬请观看详情。在eBPF程序中,如何知道触发某个网络事件的是哪个用户?bpf_get_current_uid_gid这个辅助函数正是为此而生。它能在内核态直接取出当前进程的UID和GID,配合tc或XDP类型的程序,可以针对不同用户做流量过滤、限速或审计。比如禁止某个低权限用户对外发起SSH连接,或只允许特定用户组的进程访问某个端口,都可以借助这个函数轻松实现。本文将详细介绍该函数的返回值结构、高低位拆分方法、典型使用场景,并给出可编译运行的完整代码示例,同时分析使用过程中的常见坑点与权限要求。

bpf_get_current_uid_gid是eBPF提供的一个内核辅助函数,作用是获取当前触发事件的进程的用户ID和组ID。与常规的内核态取值方式不同,它把两个32位的值打包进一个64位的返回值里,低32位是UID,高32位是GID。在网络安全过滤、按用户限速、操作审计这类场景下,这个函数几乎是必选项,因为仅凭IP和端口无法区分流量究竟来自系统中的哪个用户。

如何使用bpf_get_current_uid_gid获取用户ID实现网络访问权限控制?

一、函数原型与返回值的解析方法

bpf_get_current_uid_gid的函数原型非常简单,它不接受任何参数,直接返回一个u64类型的结果。内核头文件中的定义大致如下:

u64 bpf_get_current_uid_gid(void);
// 返回值:低32位为UID,高32位为GID

拿到返回值之后,需要手动做位运算拆分。常见的写法是先取出低32位作为UID,再右移32位得到GID。很多初学者在这里容易犯错:直接把返回值当成UID来比较,结果过滤规则完全失效,因为返回值的高位混入了GID的信息,数值会变得非常大。

正确的拆分方式如下:

u64 uid_gid = bpf_get_current_uid_gid();
u32 uid = uid_gid;          // 低32位:UID
u32 gid = uid_gid >> 32;   // 高32位:GID

另外要注意大小端问题。这个函数的返回值布局与CPU字节序有关,在x86和ARM等小端架构上,低32位对应UID。如果你在奇异架构上开发,建议先用bpf_printk打印验证一下拆分结果是否符合预期,再写过滤逻辑。

二、在tc程序中实现按用户放行或拦截流量

tc(Traffic Control)类型的eBPF程序挂载在网卡队列上,能对所有出入流量做分类处理,是做用户级访问控制的理想挂载点。下面这段示例代码演示了一个典型需求:禁止UID为1001的用户对外发起TCP连接,其余流量正常放行。

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

SEC("classifier")
int tc_filter(struct __sk_buff *skb)
{
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK;

    if (eth->h_proto != __bpf_htons(ETH_P_IP))
        return TC_ACT_OK;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;

    if (ip->protocol != IPPROTO_TCP)
        return TC_ACT_OK;

    // 关键一步:获取当前发包进程的UID
    u64 uid_gid = bpf_get_current_uid_gid();
    u32 uid = (u32)uid_gid;

    if (uid == 1001) {
        bpf_printk("blocked packet from uid %u\n", uid);
        return TC_ACT_SHOT;  // 直接丢弃
    }

    return TC_ACT_OK;
}

char LICENSE[] SEC("license") = "GPL";

这段代码有几个细节值得说明。第一,所有的数据包访问都必须做边界检查,也就是data与data_end的比较,否则验证器会直接拒绝加载程序。第二,拦截动作用TC_ACT_SHOT表示丢包,放行则返回TC_ACT_OK。第三,bpf_get_current_uid_gid取到的是当前正在执行发送操作的进程上下文,因此在egress方向挂载时语义最准确。

挂载时可以借助tc命令完成。先给网卡添加一个clsact队列规则,再把编译出的目标文件挂上去:

clang -O2 -target bpf -c tc_uid.c -o tc_uid.o
tc qdisc add dev eth0 clsact
tc filter add dev eth0 egress bpf da obj tc_uid.o sec classifier

卸载也很简单,执行tc qdisc del dev eth0 clsact即可整体移除。如果只想更新程序而不影响其他过滤器,可以用tc filter replace命令。

三、使用场景、限制与常见坑点

这个函数最典型的应用有三类。一是容器安全,通过UID区分容器内进程,对越权访问做实时阻断;二是多用户服务器的资源治理,比如限制某些用户只能访问指定端口;三是审计需求,把UID记录到map中,由用户态程序汇总生成按用户的流量报表,配合定时任务就能发现异常账户行为。

不过它也有明确的限制,需要在使用前想清楚。最关键的一点是:该函数依赖当前任务的上下文,只有当eBPF程序是在进程系统调用的路径上执行时,取到的UID才有意义。XDP程序挂载在驱动收包的最前端,此时报文处理往往已经脱离了原始进程上下文(尤其是接收方向,对端可能根本不在本机),因此bpf_get_current_uid_gid在XDP的ingress场景基本拿不到有效值。这也是为什么上面的例子选择了tc的egress路径——本机进程发包时,内核正处于该进程的上下文中,UID取值才可靠。

另一个常见的坑是权限要求。调用该函数的程序通常需要以root身份加载,或者获得CAP_BPF与CAP_PERFMON能力。如果你的程序加载时报操作不允许的错误,首先检查当前的执行身份。此外,该辅助函数在较老的内核版本上不可用,建议在4.2以上内核使用,并且在代码中通过SEC("license") = "GPL"声明GPL许可,因为它是GPL限制的辅助函数,缺少许可声明会导致加载失败。

最后提一个实践建议:不要在过滤逻辑里硬编码UID,更优雅的做法是把允许或禁止的UID列表放进一个BPF map,由用户态管理工具动态增删。这样用户ID发生变化时不需要重新编译和加载eBPF程序,运维起来灵活得多。对于GID的判断也同理,取高32位后与map中的组ID比对,就能实现按用户组的批量管控。掌握这些要点之后,基于UID的网络访问控制就可以稳定地跑在生产环境上了。

eBPFbpf_get_current_uid_gid网络访问控制修改时间:2026-09-15 07:20:30

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260915/57120.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。