一、eBPF相关概述
随着android的版本不断升级,android 9之后,内核版本均为4.X以及更高的5.X,linux的eBPF设计在android系统中应用也越来越多。对于BPF以及eBPF(extended BPF)网上已经有很多的文章介绍,推荐大家先通过“android平台eBPF初探”等文章先对eBPF的概念、框架和功能有个大概的了解。下面从eBPF设计中网络相关部分大概梳理下eBPF的演变过程。
BPF(Berkeley PacketFilter)最早出现的目的是为了提供一种高性能的网络包过滤方法,所谓的包过滤就是利用一些预定条件,对网络传输的数据包进行筛选、统计等。在BPF出现之前,iptable一直作为linux内核中防火墙和网络数据包过滤的主要方法。
相比之前的iptables过滤方法,BPF存在如下2个优点:
1.BPF过滤的编码实现通过字节码的形式,直接运行在内核态,避免了在包过滤的过程中与用户态的数据交互,而过滤的结果以MAP形式提供给用户态获取。
2.BPF的过滤在代码中判断执行,相比较iptable的每个chain上的table中多条规则顺序匹配,效率更高。
在BPF的基础上,linux进行了对应的扩展,除了网络相关的内核hook,在文件系统、CPU调度、系统调用等内核很多模块也同步增加了hook点,丰富了eBPF的检测内容。eBPF 同时从新封装了一套完整的系统调用,用于装载BPF的代码段、创建和读取BPF map,使得eBPF编程更加通用、易懂。
二、eBPF的编程的API封装
eBPF编程中,开发人员主要实现的是eBPF的program代码和MAP存储块信息的定义。linux现有版本中的system/bpf/progs/include/bpf_helpers.h中已经提供了封装好的接口,如下所示:
1.DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog)
用于定义eBPF的program的代码段,在启动后通过libbpf_android.so调用bpf系统调用,将其加载到内核中。系统运行的过程中,kernel对应模块的hook会调用这些eBPF的program代码,执行的结果如果需要反馈到用户态就更新到MAP中。
2. #define DEFINE_BPF_PROG(SECTION_NAME,prog_uid, prog_gid, the_prog)
用于创建eBPF的MAP存储数据,同时增加了3个接口定义,用于对该MAP的查询、更新、删除操作。
三、Android的eBPF的网络统计和网络管控实现
Andoird 9以后的网络统计和网络收发数据管控(过滤)功能实现主要是使用了eBPF中skfilter和cgroupskb的program type。这部分代码都是在system/netd/bpf_progs/netd.c 中实现的。
1. Netd模块中eBPF program以及eBPF MAP定义
2. eBPF网络统计和管控的流程
1)系统开机后,netd.c运行,在sys/fs/bpf中生成对应的prog和map文件。
文件与代码定义的对应关系(下面看到的都是FILENAME 为netd.c 定义的):
/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME /sys/fs/bpf/map_FILENAME_MAPNAME。
2)在收发数据时,linux协议栈在的cgroup bpf hook 函数__cgroup_bpf_run_filter_skb会调用cgroupskb tpye类型的prog,区分接收、发送做不同的处理。
3)netd.c中定义的cgroupskb prog在内核态以字节码的形式运行,bpf_traffic_account函数开始对数据包就行统计。如果是发送的数据包,并且根据过滤规则为drop的数据包,直接标记为丢弃并不做统计;如果是UID为DNS进程,则根据框架的配置,选择是否drop;其他情况,根据进程的UID更新map_netd_app_uid_stats_map对应的统计值,供上层框架查询。
4)同时系统启动时hal层的BandwidthController.cpp模块会通过iptables的方式在kernel Netfilter 的bw_raw_PREROUTING和bw_mangle_POSTROUTING chain上创建xt_bpf过滤器。所以在数据收发的过程中,所有数据包都会经过xb_bpf过滤。
5)Kernel的xb_bpf驱动中,bpf_mt_check_v1, 通过XT_BPF_MODE_PATH_PINNED的方式调用之前在netd中定义的xt_bpf_egress_prog 和 xt_bpf_ingress_prog的eBPF program。该eBPF program负责对正确的接口计算流量并更新到map_netd_iface_stats_map的MAP中。
3. Deviceidle策略生效时eBPF的验证
在Android M中,Google就引入了Doze模式,目的是降低待机过程中的功耗,其中对应用的管控就包括网络限制,一旦进入Doze idle mode,非白名单应用将无法连接网络。在Android 9之后,这种网络限制管控就是通过上面讲到的eBPF实现的。下面我们通过实际测试来验证下,这个过程中eBPF是如何生效的。
1)通过下面adb 命令,可以强制系统进入doze idle mode
adb shell dumpsys battery unplug
adb shell dumpsys deviceidle force-idle
2)进入idle状态后,主要执行两个操作:将白名单的应用过滤策略设置为DOZABLE_MATCH;打开DOZE idle 模式的策略管控。在eBPF中,uid的过滤策略在uid_owner_map中存储,而DOZE idle 模式是通过ownerMatch configuration来控制的。通过下面的命令,我们可以看到force-idle以后的uid_owner_map信息。
adb shell dumpsys netd trafficcontroller
3)上面提到的eBPF的bpf_traffic_account中,调用bpf_owner_match接口,其中判断如果当前config配置为DOZABLE_MATCH并且Uid规则不是DOZABLE_MATCH时将该包选择丢弃。
通过这种方式,进入idle以后,所有的非白名单应用发送的数据都被丢弃,即完成网络管控。
四、小结
通过以上介绍,相信大家应该对linux eBPF中cgroupskb以及skfilter在android的网络统计以及数据包过滤的功能实现有了基本了解。除了doze idle的功能验证,大家也可以通过App Standby网络限制功能、用户流量告警等功能按照上面的流程来跟踪整个功能是如何实现的,这样更能加深对linux eBPF的框架理解。目前android版本中,虽然还没有彻底废除iptables模块,但是如果为了降低手机的待机、后台运行等场景的数据收发功耗,我们可以参考上面流量统计功能自定义一些自己的匹配规则,使得这种eBPF字节码的编程方式运用更为广泛。
参考文献:
1. Google原生流量统计介绍
https://source.android.google.cn/devices/tech/datausage/ebpf-traffic-monitor
2. Linux BPF doc
https://www.kernel.org/doc/Documentation/networking/filter.txt
3. Google原生代码的相关提交记录
https://android.googlesource.com/platform/system/netd/
https://android-review.googlesource.com/c/platform/system/netd/+/711290