跳转至

Android 完整部署

WutherCore 在 Android 上提供四条实际可用的接管路径:

  1. root TUN,由 native 进程直接打开 /dev/net/tun
  2. root TPROXY,由 native 进程建立 TCP 和 UDP 透明监听并安装策略路由。
  3. root REDIRECT,由 native 进程建立 TCP 透明监听并原子安装 nftables NAT 规则。
  4. 非 root VpnService,由宿主应用建立 TUN,再把文件描述符交给 native。

method: auto 在 Android 上选择 TUN。TUN 打开时先尝试 root /dev/net/tun,失败后 才消费宿主注入的 VpnService 文件描述符。因此同一份 virtual_nic 配置可以同时 服务 root daemon 和普通 Android 应用,但两条路径的启动方式、路由所有权和应用 过滤边界不同。

仓库提供三份 root 配置:

数据面 配置文件
root TUN android-root-tun.yaml
root TPROXY android-root-tproxy.yaml
root REDIRECT android-root-redirect.yaml

非 root 宿主配置见 android.yaml

模式选择

模式 capture.method TCP UDP 设备或规则 最低权限
root TUN virtual_nicauto 支持 支持 /dev/net/tun,接口地址,策略路由 UID 0 或足够的有效 capability
root TPROXY tproxy 支持 支持 透明 socket,iptables,策略路由 有效 CAP_NET_ADMIN
root REDIRECT redirect 支持 不支持 透明 TCP socket,nftables NAT 有效 CAP_NET_ADMIN
VpnService virtual_nicauto 支持 支持 宿主提供的预配置 TUN fd Android VPN 授权

优先按下面的条件选择:

需求 推荐
普通应用分发,不要求 root VpnService
root 设备,需要完整 TCP 和 UDP,要求按应用过滤 root TUN
root 设备,不希望创建 TUN,内核具备 TPROXY root TPROXY
旧内核或只需要 TCP 接管 root REDIRECT
同时兼容 root 和非 root 宿主 method: auto,由启动方式决定 TUN 来源

不要把 tun.auto_redirect 当作 Android root 开关。Android 支持显式 method: redirect,但不支持 Linux 专用的 tun.auto_redirect 混合数据面。

源码中的实际调用链

Android 平台入口位于 platform/android.rs。 生产 Android 构建会把 TUN、TPROXY 和 REDIRECT 都交给 Linux 系数据面实现:

CapturePlan
  -> platform::android::build_engine
  -> platform::linux::build_engine
  -> LinuxTun | LinuxTproxy | LinuxRedirect

三条路径分别继续执行:

root TUN
  -> android_tun_io::open
  -> LinuxTunIo::open("/dev/net/tun")
  -> 配置接口和策略路由
  -> 启动 TUN dispatcher

root TPROXY
  -> require_net_admin
  -> 绑定 7894 TCP 和 UDP 透明监听
  -> 安装 iptables 和 ip6tables 规则
  -> 安装 fwmark 策略路由

root REDIRECT
  -> require_net_admin
  -> 先绑定临时 TCP 监听
  -> 检查 nftables 批处理
  -> 原子发布 NAT 规则

VpnService
  -> android_tun_io::open
  -> root TUN 打开失败
  -> 消费 JNI 注入的 fd 和实际 MTU
  -> 跳过 native 接口与路由配置

关键实现文件:

行为 源码
Android 数据面选择 platform/android.rs
root TUN 优先和 VpnService fallback platform/android_tun_io.rs
TUN、TPROXY 和 REDIRECT 生命周期 platform/linux.rs
TPROXY 固定 mark、端口和规则 tproxy_rules.rs
REDIRECT 原子 nftables 规则 platform/linux_auto_redirect.rs
JNI 和 VpnService.protect platform/android_jni.rs
权限检测 core-inbound/privilege.rs

四层 capability 探测与生产数据面的关系

源码中还保留了 AndroidCapability::select_tier,它可以描述四层旧式 root 能力:

tier 探测条件 能力描述
nftables-full nft、双栈 TPROXY、IPv6 NAT、owner match 完整双栈透明代理
iptables-v4v6-tproxy iptables、ip6tables、双栈 TPROXY 双栈 TCP 和 UDP
iptables-v4v6-redirect iptables、ip6tables、IPv6 NAT 双栈 TCP,UDP 受限
iptables-v4-only 只有 IPv4 iptables IPv4 TCP

这段代码当前只用于能力描述、测试和非 Android 调试 stub。生产 Android 的 build_engine 不会调用这个 tier 自动降级器,而是直接按配置选择 Linux 系 TUN、TPROXY 或 REDIRECT。

因此当前生产行为是:

  1. method: tproxy 缺少 TPROXY 能力时启动失败,不会自动退到 REDIRECT。
  2. method: redirect 走当前 nftables 原子后端,不会自动退到旧 iptables v4。
  3. method: virtual_nic 只在 root TUN 和 VpnService fd 之间 fallback。

部署脚本应在启动前探测设备能力,再选择对应配置文件。不能只看到 select_tier 的四个枚举,就假定生产 engine 已经自动执行四级降级。

root 权限的真实含义

启动时执行的 su -c id 只是探测 root 管理器是否允许 root 子命令。它不会改变当前 native 进程的 UID,也不会给当前进程补上 CAP_NET_ADMIN

这意味着下面的启动方式不成立:

普通应用进程启动 native
  -> native 内部探测到 su
  -> 误以为当前进程已经成为 root

root 数据面要求下面两种方式之一:

  1. 直接由 root shell、Magisk service 或 KernelSU service 启动整个 wuther-core 进程。
  2. 让服务进程真正持有所需的有效 capability。只有可以执行 su -c 但当前进程 仍是普通应用 UID,不足以创建 root TUN 或透明 socket。

通过 adb 首次验证:

adb push wuther-core /data/local/tmp/wuther-core
adb push config.yaml /data/local/tmp/wuther-core.yaml
adb shell su -c 'chmod 0755 /data/local/tmp/wuther-core'
adb shell su -c '/data/local/tmp/wuther-core components'
adb shell su -c '/data/local/tmp/wuther-core check /data/local/tmp/wuther-core.yaml'
adb shell su -c '/data/local/tmp/wuther-core explain /data/local/tmp/wuther-core.yaml'
adb shell su -c '/data/local/tmp/wuther-core run --config /data/local/tmp/wuther-core.yaml'

确认实际身份:

adb shell su -c 'id'
adb shell su -c 'grep CapEff /proc/self/status'
adb shell su -c 'test -c /dev/net/tun'

使用 Magisk 或 KernelSU 模块时,可由 service.sh 启动:

#!/system/bin/sh

MODDIR=${0%/*}
RUNDIR="$MODDIR/run"
LOGDIR="$MODDIR/log"

mkdir -p "$RUNDIR" "$LOGDIR"
chmod 0700 "$RUNDIR" "$LOGDIR"

"$MODDIR/bin/wuther-core" check "$MODDIR/config.yaml" || exit 1

"$MODDIR/bin/wuther-core" run \
  --config "$MODDIR/config.yaml" \
  >>"$LOGDIR/wuther-core.log" 2>&1 &

echo $! >"$RUNDIR/wuther-core.pid"

配置、日志和状态目录不要放在普通应用可以修改的位置。root daemon 读取的订阅、 规则集和证书也应限制文件权限。

root TUN

完整配置

capture:
  on: true
  method: virtual_nic
  traffic: system
  resolver: hijack
  stack: mixed
  mtu: 1400
  offload: true
  exclude:
    cidr:
      - 127.0.0.0/8
      - ::1/128
  tun:
    interface_name: rpktun0
    address:
      - 172.19.0.1/30
      - fdfe:dcba:9876::1/126
    inet6: true
    auto_route: true
    auto_redirect: false
    strict_route: false
    iproute2_table_index: 2024
    iproute2_rule_index: 9100
    route_exclude_address:
      - 127.0.0.0/8
      - ::1/128
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 192.168.0.0/16
      - 223.5.5.5/32
      - 1.1.1.1/32
    endpoint_independent_nat: true
    udp_timeout: 5m
    include_android_user: [0]
    exclude_package:
      - com.android.captiveportallogin

完整文件见 android-root-tun.yaml

启动顺序

root TUN 启动不是简单打开一个文件描述符。当前实现按下面的顺序建立所有权:

  1. 打开 /dev/net/tun
  2. 使用 TUNSETIFF 创建或绑定 interface_name
  3. 记录崩溃恢复账本。
  4. 设置接口 MTU、地址和启用状态。
  5. 根据 auto_route 安装策略路由。
  6. 安装出站 fwmark 绕行,避免代理出站再次进入 TUN。
  7. 安装 UID、GID 和 Android 包名的内核级绕行规则。
  8. 启动 TUN dispatcher,接管设备读写。

如果 root TUN 成功打开,即使宿主已经注入 VpnService fd,也优先使用 root TUN。 此时 native 自己管理接口和路由,不需要 VpnService.protect

字段行为

字段 root TUN 行为
interface_name 请求的 TUN 名称,最终名称以内核返回值为准
address 第一条 IPv4 和第一条 IPv6 地址进入执行计划
inet6 控制 IPv6 地址、路由和数据面
auto_route 安装接管路由和出站绕行规则
strict_route 安装更严格的防泄漏路由,首次部署建议关闭
iproute2_table_index root TUN 私有路由表,默认回填为 2022
iproute2_rule_index root TUN 规则优先级,默认回填为 9000
route_address 只把指定目的 CIDR 导入 TUN
route_exclude_address 明确绕过 TUN 的静态 CIDR
route_address_set 使用规则集生成的动态接管集合
route_exclude_address_set 使用规则集生成的动态绕行集合
endpoint_independent_nat 控制 UDP NAT 映射复用
udp_timeout 控制 UDP 会话老化
exclude_mptcp 尝试让 MPTCP 流量绕行

auto_redirect 必须保持 false。Android root TUN 本身已经是完整接管路径, Linux auto_redirect 的 TCP NAT 和 UDP TUN 混合逻辑在 Android 上会被明确拒绝。

UID、GID、Android user 和包名

root TUN 可以使用:

capture:
  tun:
    include_uid: []
    include_uid_range:
      - "10000:99999"
    exclude_uid:
      - 1000
    exclude_uid_range: []
    include_gid: []
    include_gid_range: []
    exclude_gid: []
    exclude_gid_range: []
    include_android_user:
      - 0
      - 10
    include_package: []
    exclude_package:
      - com.android.captiveportallogin

语义如下:

字段 语义
include_uid 只接管列出的 UID
include_uid_range 只接管闭区间中的 UID
exclude_uid 让列出的 UID 绕过
exclude_uid_range 让闭区间中的 UID 绕过
GID 对应字段 与 UID 规则相同,但匹配 socket GID
include_android_user Android user N 映射为 N * 100000N * 100000 + 99999
include_package 包名白名单
exclude_package 包名黑名单

内核级包名绕行会读取 /data/system/packages.list 把包名解析为 app UID。多用户设备 再结合 include_android_user 添加 user 偏移。包名无法解析时,对应内核规则会跳过 并记录日志,因此生产环境应检查启动日志中的解析结果。

显式 include_uidinclude_uid_range 存在时,它们优先定义 UID 白名单。 include_android_user 不会再替代这组显式 UID 条件。

内核级 identity bypass 只在 auto_route: true 时安装,因为它依赖同一套 fwmark 策略路由。关闭 auto_route 后,宿主必须自己建立等价的绕行规则。

root TPROXY

完整配置

capture:
  on: true
  method: tproxy
  traffic: system
  resolver: hijack
  stack: mixed
  exclude:
    cidr:
      - 127.0.0.0/8
      - ::1/128
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 192.168.0.0/16
  tun:
    inet6: true
    auto_redirect: false
    route_exclude_address:
      - 223.5.5.5/32
      - 1.1.1.1/32
    auto_redirect_output_mark: "0x2024"

完整文件见 android-root-tproxy.yaml

TPROXY 不创建 TUN,因此不要配置 capture.mtutun 子块仍承载透明接管共用的 IPv6、绕行 CIDR 和出站 mark 字段。

当前固定资源

资源 当前值
TCP 透明监听 0.0.0.0:7894,IPv6 开启时同时监听 [::]:7894
UDP 透明监听 与 TCP 使用同一端口
TPROXY mark 0x2d0
策略路由表 0x2d0
本地路由设备 lo
IPv4 防火墙 iptables -t mangle
IPv6 防火墙 ip6tables -t mangle

启动时先验证有效 CAP_NET_ADMIN,再绑定 TCP 和 UDP listener,随后安装策略路由和 防火墙链。规则安装失败时会删除已安装的链和策略路由。停止时先终止 listener,再 撤销规则。

TPROXY 规则会绕过:

  1. capture.exclude.cidr
  2. tun.route_exclude_address
  3. IPv4 和 IPv6 的内置本地、私网、组播、文档地址与 Tailnet 网段。
  4. 带出站 mark 的 WutherCore socket。

当前显式 TPROXY 规则生成器同时安装 PREROUTING 和 OUTPUT 链,不使用 traffic 选择单一 hook。不要依赖 traffic: system 把规则缩小到只有本机进程。需要限制 入口接口或转发来源时,应在设备自己的上游防火墙链中完成。

当前 TPROXY 规则生成器不把这些字段翻译成内核规则:

不应依赖的字段 原因
include_packageexclude_package TPROXY 规则没有包名到 UID 的翻译阶段
UID 和 GID 过滤 当前 TPROXY 链只生成 CIDR 绕行
interface 和 MAC 过滤 当前 TPROXY 链没有生成对应匹配
route_address 当前 TPROXY 防火墙入口不使用接管白名单
动态 route set 当前 TPROXY 入口没有同步动态集合

需要按应用接管时优先使用 root TUN。需要限制 TPROXY 范围时,用静态 exclude.cidrroute_exclude_address 明确绕行,并通过设备防火墙控制哪些链 进入 WutherCore。

检查内核能力:

adb shell su -c 'iptables -t mangle -j TPROXY -h'
adb shell su -c 'ip6tables -t mangle -j TPROXY -h'
adb shell su -c 'grep -i TPROXY /proc/net/ip_tables_matches'

运行后检查:

adb shell su -c 'ip rule show'
adb shell su -c 'ip -6 rule show'
adb shell su -c 'iptables -t mangle -S WUTHERCORE_PREROUTING'
adb shell su -c 'iptables -t mangle -S WUTHERCORE_OUTPUT'
adb shell su -c 'ss -lntup | grep 7894'

root REDIRECT

完整配置

capture:
  on: true
  method: redirect
  traffic: system
  resolver: off
  stack: system
  exclude:
    cidr:
      - 127.0.0.0/8
      - ::1/128
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 192.168.0.0/16
  tun:
    inet6: true
    auto_redirect: false
    route_exclude_address:
      - 223.5.5.5/32
      - 1.1.1.1/32
    include_android_user:
      - 0
    exclude_uid:
      - 1000

完整文件见 android-root-redirect.yaml

REDIRECT 只接管 TCP。UDP 和普通 UDP DNS 不会进入 REDIRECT listener,因此该模式 不适合要求完整 UDP 代理的设备。需要 UDP 时使用 root TUN 或 root TPROXY。

traffic 与过滤器

traffic nftables hook 可用过滤
system output UID、UID 范围、GID、GID 范围、Android user
apps output system 相同
lan prerouting interface、MAC

本机 OUTPUT 没有可靠的入口接口和源 MAC,所以 systemapps 配置 interface 或 MAC 过滤会失败。转发流量的 PREROUTING 没有可靠的 socket owner,所以 traffic: lan 配置 UID、GID 或 Android user 过滤会失败。

当前 REDIRECT 原子规则后端明确拒绝:

  1. exclude.process
  2. include_packageexclude_package
  3. route_address_setroute_exclude_address_set

包名需要先解析成 UID,再写入 include_uidexclude_uid。不能把包名字段写进 配置后假定它已经生效。

REDIRECT 会先绑定实际 TCP listener,获得端口后生成 nftables 批处理,先执行 nft --check,再原子提交。规则发布失败时 listener 会被关闭,不留下半套 NAT 状态。停止时保留清理账本,只有 nftables 撤销成功后才报告完成。

检查规则:

adb shell su -c 'nft list table inet wuthercore_redirect'
adb shell su -c 'ss -lntp'

VpnService

VpnService 是非 root 路径,也可作为 root TUN 打开失败时的 fallback。宿主必须按 完整顺序执行:

  1. 调用 VpnBridge.vpnServiceConfigJson(configPath)
  2. 把返回的 addresses、routes、dns_servers 写入 VpnService.Builder
  3. 根据返回值设置 allowed 或 disallowed applications。
  4. 把返回的 MTU 传给 Builder.setMtu
  5. 调用 establish()
  6. ParcelFileDescriptor 调用 detachFd()
  7. 调用 VpnBridge.setVpnService(service) 注册 socket protect。
  8. 调用 VpnBridge.setVpnFdWithMtu(fd, mtu) 注入 fd 和真实 MTU。
  9. 启动 native 核心。

native 会逐值比较注入的 MTU 与 capture.mtu。旧的 setVpnFd(fd) 只为 ABI 兼容 保留,新宿主必须使用 setVpnFdWithMtu

VpnService fd 已由 Android framework 配置接口、地址和路由。native 检测到它是 预配置设备后,会跳过 Linux 接口、路由和防火墙管理。所有代理出站 socket 必须先 执行 VpnService.protect(fd),否则会再次进入自己的 VPN。

应用过滤在 VpnService 路径分为两层:

行为
Android Builder 使用 allowed 或 disallowed applications 决定哪些应用进入 TUN
native 数据面 使用 UID、GID、包名和 Android user 信息执行流级策略

Builder 不允许同时使用 allowed 和 disallowed 列表。配置同时出现 include_packageexclude_package 时,导出逻辑优先使用 allowed 模式并报告 被忽略的排除列表。

method: auto

Android 上的 method: auto 固定解析为 TUN,不会自动改成 TPROXY 或 REDIRECT。 随后由 TUN opener 决定设备来源:

尝试 root /dev/net/tun
  -> 成功,使用 root TUN
  -> 失败,检查是否有注入的 VpnService fd
  -> 有 fd,使用 VpnService
  -> 无 fd,启动失败

如果要使用 TPROXY 或 REDIRECT,必须显式写:

capture:
  on: true
  method: tproxy

或:

capture:
  on: true
  method: redirect

构建

三种 root 模式和 VpnService 都需要 with_tun

pwsh -File scripts/build-all.ps1 `
  -Tags "standard" `
  -Targets "aarch64-linux-android"

只构建 TUN、API、VLESS、REALITY、gRPC 和 uTLS:

pwsh -File scripts/build-all.ps1 `
  -Tags "with_api,with_tun,with_vless,with_reality,with_grpc,with_utls" `
  -Targets "aarch64-linux-android"

构建后检查组件:

wuther-core components
wuther-core components --json

如果输出没有 with_tun,任何 Android capture 配置都会在启动前被组件门禁拒绝。

启动前检查

通用

wuther-core check config.yaml
wuther-core explain config.yaml
wuther-core components

root TUN

test -c /dev/net/tun
ip link show
ip rule show

root TPROXY

iptables --version
ip6tables --version
iptables -t mangle -j TPROXY -h

root REDIRECT

nft --version
nft list ruleset

运行状态和回滚

root TUN、TPROXY 和 REDIRECT 都使用资源所有权账本。启动过程中任何一步失败,已经 取得所有权的接口、listener、路由和防火墙规则会按逆序清理。清理失败会保留账本, 后续 stop 可以继续重试,不会把残留规则当作已成功撤销。

运行后至少核对:

ip link show rpktun0
ip rule show
ip route show table all
ip -6 route show table all
iptables-save
nft list ruleset

只检查进程存在不能证明接管已经成功。应同时确认接口或透明 listener 存在,策略 路由已安装,出站节点可达,DNS 没有循环,停止后规则能够恢复。

常见错误

su 可用但 root TUN 仍失败

原因是当前 native 进程仍是普通应用 UID。把整个 daemon 放进 root service 启动, 不要依赖进程内部的一次 su -c id 探测。

method: auto 没有进入 TPROXY

这是设计行为。Android 的 auto 只选择 TUN。TPROXY 和 REDIRECT 必须显式配置。

root TUN 意外使用了 VpnService

检查 /dev/net/tun 权限、有效 capability 和 TUNSETIFF 错误。root open 失败后 才会消费已注入的 VpnService fd。

TPROXY 启动时缺少 CAP_NET_ADMIN

给当前 daemon 真正授予有效 capability,或直接由 root service 启动。只允许 su 子命令不足以创建透明 socket和策略路由。

REDIRECT 下 UDP 不工作

REDIRECT 的能力声明就是 TCP only。改用 root TUN 或 root TPROXY。

包名过滤没有生效

root TUN 需要能够读取并解析 Android 包到 UID 的映射。REDIRECT 要求先把包名转换 成 UID。TPROXY 当前不生成包名或 UID 过滤规则。

开启 tun.auto_redirect 被拒绝

它是 Linux root managed TUN 的专用混合数据面,不是 Android root 模式开关。 Android 请使用 root TUN,显式 TPROXY,显式 REDIRECT 或 VpnService。