eBPF 流量监控

简介

eBPF 网络流量工具可结合使用内核与用户空间实现来监控设备自上次启动以来的网络使用情况,而且还提供了一些额外的功能,例如套接字标记、分离前台/后台流量,以及可根据手机状态阻止应用访问网络的 per-UID 防火墙。从该工具收集的统计数据存储在称为 eBPF maps 的内核数据结构中,并且相应结果可供 NetworkStatsService 等服务用于提供自设备上次启动以来的持久流量统计数据。

示例和源代码

对用户空间进行的更改主要在 system/netdframework/base 项目中。开发工作在 AOSP 中完成,因此 AOSP 代码将始终保持最新状态。源代码主要位于 system/netd/server/TrafficController*system/netd/bpfloadersystem/netd/libbpf/ 中。另外,对框架进行的一些必要更改也在 framework/base/system/core 中。

实现

从 Android 9 开始,内核版本为 4.9 或更高且最初搭载了 Android P 版本的 Android 设备必须使用基于 eBPF 的网络流量监控记帐模块,而不是 xt_qtaguid。新的基础架构更灵活且更易于维护,并且不需要任何外部内核代码。

旧版流量监控和 eBPF 流量监控之间的主要设计差异如图 1 所示。

旧版流量监控和 eBPF 流量监控的设计差异

图 1. 旧版流量监控(左)和 eBPF 流量监控(右)的设计差异

新的 trafficController 设计基于 per-cgroup eBPF 过滤器以及内核中的 xt_bpf netfilter 模块。当数据包 tx/rx 通过 eBPF 过滤器时,系统会对其应用相应过滤器。cgroup eBPF 过滤器位于传输层中,负责根据套接字 UID 和用户空间设置对正确的 UID 计算流量。xt_bpf netfilter 连接在 bw_raw_PREROUTINGbw_mangle_POSTROUTING 链上,负责对正确的接口计算流量。

在设备启动时,用户空间进程 trafficController 会创建用于收集数据的 eBPF 映射,并将所有映射作为虚拟文件固定在 sys/fs/bpf。然后,特权进程 bpfloader 将预编译的 eBPF 程序加载到内核中,并将其附加到正确的 cgroup。所有流量都对应于同一个根 cgroup,因此默认情况下,所有进程都应包含在该 cgroup 中。

在系统运行时,trafficController 可以通过写入到 traffic_cookie_tag_maptraffic_uid_counterSet_map,对套接字进行标记/取消标记。NetworkStatsService 可以从 traffic_tag_stats_maptraffic_uid_stats_maptraffic_iface_stats_map 中读取流量统计数据。除了流量统计数据收集功能外,trafficControllercgroup eBPF 过滤器还负责根据手机设置屏蔽来自特定 UID 的流量。基于 UID 的网络流量屏蔽功能取代了内核中的 xt_owner 模块,详细模式可以通过写入到 traffic_powersave_uid_maptraffic_standby_uid_maptraffic_dozable_uid_map 来进行配置。

新实现遵循旧版 xt_qtaguid 模块实现,因此 TrafficControllerNetworkStatsService 将使用旧版实现或新实现运行。如果应用使用公共 API,则无论后台使用 xt_qtaguid 还是 eBPF 工具,都不应受到任何影响。

如果设备内核基于 Android 通用内核 4.9(SHA39c856663dcc81739e52b02b77d6af259eb838f6 或以上版本),则实现新的 eBPF 工具时无需修改 HAL、驱动程序或内核代码。

要求

  1. 内核配置必须开启以下配置:

    1. CONFIG_CGROUP_BPF=y
    2. CONFIG_BPF=y
    3. CONFIG_BPF_SYSCALL=y
    4. CONFIG_NETFILTER_XT_MATCH_BPF=y

    验证是否已开启正确配置时,VTS 内核配置测试非常有用。

  2. 设备 MEM_LOCK 资源限制必须设为 8 MB 或更多。

旧版 xt_qtaguid 弃用过程

新的 eBPF 工具将取代 xt_qtaguid 模块以及它所基于的 xt_owner 模块。我们将开始从 Android 内核中移除 xt_qtaguid 模块,并停用不必要的配置。

在 Android 9 版本中,xt_qtaguid 模块在所有设备上都处于开启状态,不过,直接读取 xt_qtaguid 模块 proc 文件的所有公共 API 都将移至 NetworkManagement 服务中。根据设备内核版本和首个 API 级别,NetworkManagement 服务能够知道 eBPF 工具是否处于开启状态,并选择正确的模块来获取每个应用的网络使用情况统计数据。sepolicy 会阻止 SDK 级别为 28 及以上的应用访问 xt_qtaguid proc 文件。

从 Android 9 之后的下一个版本起,我们将完全阻止应用访问这些 xt_qtaguid proc 文件,并开始从新的 Android 通用内核中移除 xt_qtaguid 模块。移除该模块后,我们将更新相应内核版本的 Android 基础配置,以明确关闭 xt_qtaguid 模块。当 Android 版本的最低内核版本要求为 4.9 或更高时,我们将彻底弃用 xt_qtaguid 模块。

在 Android 9 版本中,只有搭载 Android 9 版本的设备才需要具备新的 eBPF 功能。如果设备搭载的内核可以支持 eBPF 工具,我们建议在升级到 Android 9 版本时,将设备更新到采用新的 eBPF 功能。没有旨在强制执行该更新的 CTS 测试。

验证

您应该定期从 Android 通用内核和 Android AOSP 主代码库获取补丁程序。请确保您的实现通过适用的 VTS 和 CTS 测试、netd_unit_test 以及 libbpf_test

测试

内核 net_tests 旨在确保您已开启必要的功能,并向后移植了必要的内核补丁程序。这些测试已集成到 Android 9 版本 VTS 测试中。system/netd/ 中有一些单元测试(netd_unit_testlibbpf_test)。netd_integration_test 中有一些旨在验证新工具整体行为的测试。

CTS 和 CTS 验证程序

由于 Android 9 版本支持这两种流量监控模块,因此没有旨在强制在所有设备上实现新模块的 CTS 测试。不过,对于内核版本高于 4.9 且最初搭载了 Android 9 版本(即首个 API 级别 >= 28)的设备,GSI 上提供了一些旨在验证新模块是否已正确配置的 CTS 测试。旧的 CTS 测试(如 TrafficStatsTestNetworkUsageStatsTestCtsNativeNetTestCases)可用于验证新模块的行为是否与旧的 UID 模块一致。

手动测试

system/netd/ 中有一些单元测试(netd_unit_testnetd_integration_testlibbpf_test)。系统支持 dumpsys,以便手动检查状态。命令 dumpsys netd 可显示 trafficController 模块的基本状态以及 eBPF 是否已正确开启。如果 eBPF 处于开启状态,命令 dumpsys netd trafficcontroller 会显示每个 eBPF 映射的详细内容,包括带标记套接字信息、每个标记的统计数据、UID 和 iface,以及所有者 UID 匹配项。

测试所在位置

CTS 测试位于以下位置:

VTS 测试位于以下位置:https://android.googlesource.com/kernel/tests/+/master/net/test/bpf_test.py

单元测试位于以下位置: