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

一、函数原型与返回值的解析方法
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