适用范围与操作前提#
命令和输出使用通用示例。内核、镜像、包与服务版本会影响具体行为,阅读时应结合所用版本的文档核对。
修改容器、镜像、挂载和防火墙前先备份 Android 数据并停止相关会话;带删除或强制停止的示例可能丢失数据。NixOS 的系统与用户环境配置应通过 Nix 管理,通用 Linux 的 apt/pacman、直接写 /etc 和 pip 安装段落不用于 NixOS 宿主。重建的 build、test、switch 与启动后的功能验证是不同阶段。
Waydroid 的网络栈由 LXC 网桥 + NAT + dnsmasq DHCP 三层构成;安全边界需按实际 Namespace、AppArmor、Seccomp、capabilities 与 D-Bus 授权配置逐项检查,不能仅凭这些机制存在,就认为默认环境已经启用了相应隔离。
waydroid0 网桥架构#
Waydroid 不为 Android 容器创建 tap/tun 虚拟网卡,而是建立一个名为 waydroid0 的 Linux bridge,将宿主网络通过 NAT 转发到容器内部。整个网络拓扑如下:
graph TB
subgraph Host["Linux 宿主系统"]
ETH["物理网卡 (eth0/wlan0)"]
DNSMASQ["dnsmasq<br/>DNS + DHCP<br/>:53 / :67"]
BRIDGE["waydroid0<br/>Bridge Interface<br/><EXAMPLE_IP>/24"]
NAT["NAT 表 (iptables/nftables)<br/>POSTROUTING MASQUERADE<br/>FORWARD ACCEPT"]
end
subgraph Container["Android 容器 (LXC)"]
VETH_HOST["veth 对端 (host side)"]
VETH_CT["eth0 (container side)<br/>DHCP 分配 <CONTAINER_SUBNET_HOST><br/>MAC <EXAMPLE_MAC>"]
APPS["Android Apps"]
end
INTERNET["Internet"] --> ETH
ETH --> NAT
NAT --> BRIDGE
DNSMASQ -->|"IP 分配 <EXAMPLE_IP>-254"| BRIDGE
BRIDGE --> VETH_HOST
VETH_HOST -.->|"veth pair"| VETH_CT
VETH_CT --> APPS
style Host fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
style Container fill:#533483,stroke:#3b2665,color:#e0e0e0核心参数
| 参数 | 值 | 说明 |
|---|---|---|
| 网桥名 | waydroid0 |
LXC 启动时自动创建 |
| 宿主 IP | <EXAMPLE_IP>/24 |
网关地址,同时也是 dnsmasq 绑定接口 |
| DHCP 地址池 | <EXAMPLE_IP> ~ <EXAMPLE_IP> |
容器 eth0 从此范围获取地址 |
| MAC 地址 | <EXAMPLE_MAC> |
固定 MAC,dnsmasq 可按此分配固定 IP |
| 租约文件 | /var/lib/misc/dnsmasq.waydroid0.leases(当前上游) |
dnsmasq 租约持久化 |
waydroid-net.sh 脚本流程
网络初始化由 /usr/lib/waydroid/waydroid-net.sh(或 waydroid-net systemd 服务触发)完成,执行顺序如下:
- 创建 bridge:
brctl addbr waydroid0或通过ip link add waydroid0 type bridge - 配置宿主 IP:
ip addr add <EXAMPLE_IP>/24 dev waydroid0 - 启用 bridge:
ip link set waydroid0 up - 设置 NAT 规则:根据系统环境选择 iptables 或 nftables 变体(详见下一节)
- 启动 dnsmasq:监听
waydroid0接口,提供 DHCP 和 DNS 服务 - LXC 启动容器:LXC 创建 veth pair,将一端挂入 bridge,另一端放入容器命名空间
waydroid-net.service
└─ waydroid-net.sh
├─ ip link add waydroid0 type bridge
├─ ip addr add <EXAMPLE_IP>/24 dev waydroid0
├─ ip link set waydroid0 up
├─ iptables 规则 / nftables 规则
└─ dnsmasq --interface=waydroid0 --dhcp-range=<EXAMPLE_IP>,<EXAMPLE_IP>IPv6 与网络脚本的兼容性检查
检查当前包与网络脚本对 IPv6 的要求 即使你完全不使用 IPv6 网络(waydroid0 仅为 IPv4),内核也必须启用
CONFIG_IPV6=y。原因是 网络脚本或 Android 网络组件可能需要 IPv6 可用,但 Binder 与 IPv6 没有这种直接依赖。检查网络脚本是否因/proc/sys/net/ipv6/缺失失败,不把网络条件解释成 Binder 协议条件。NixOS 默认内核已满足此要求。
NAT 与 DHCP 配置#
Waydroid 存在不同网络后端,所查脚本由 LXC_USE_NFT 控制;发行版可能通过包变体选择它。不能仅凭系统装了 nftables 就推断自动切换,也不在 LXC config 加入这个脚本变量:
| 检测条件 | 使用后端 | 配置文件路径 |
|---|---|---|
| 所用脚本/包选择 iptables | iptables;legacy/nft 兼容层另检查 | 实际 waydroid-net.sh 与包配置 |
| 所用脚本/包启用 nft 后端 | nftables | 实际脚本/包;NixOS 模块按 networking.nftables.enable 选包 |
iptables 变体
在传统 iptables 系统(Ubuntu 20.04/22.04、Debian 11 等)上,waydroid-net.sh 插入以下规则:
# 允许 waydroid0 桥上的流量转发
iptables -I FORWARD -i waydroid0 -j ACCEPT
iptables -I FORWARD -o waydroid0 -j ACCEPT
# SNAT:将容器发出的包的源地址伪装为宿主 IP
iptables -t nat -I POSTROUTING -s <EXAMPLE_IP>/24 -j MASQUERADE规则持久化 iptables 规则默认为易失性,重启后消失。waydroid-net 服务每次启动时重新注入规则。如果你使用
iptables-persistent或netfilter-persistent,注意不要与 Waydroid 自动注入的规则冲突。
nftables 变体
选择 nft 后端时可以使用相应规则;发行版版本本身不是后端已启用的证据。以下 inet waydroid 是自定义示意;核对的上游脚本使用 inet lxc 与 ip lxc,实际名称依打包变体:
# nftables 等价规则(nft 语法)
nft add table inet waydroid
nft add chain inet waydroid forward { type filter hook forward priority 0\; policy accept\; }
nft add rule inet waydroid forward iifname "waydroid0" accept
nft add rule inet waydroid forward oifname "waydroid0" accept
nft add chain inet waydroid nat { type nat hook postrouting priority 100\; policy accept\; }
nft add rule inet waydroid nat ip saddr <EXAMPLE_IP>/24 masqueradeNixOS 环境下的 nftables 集成:如果你的 NixOS 配置已经声明了 networking.nftables 规则集,Waydroid 需要在你的规则集中打开对应的 FORWARD 和 NAT 通路。可通过 networking.nftables.ruleset 显式加入 waydroid 链,或使用 virtualisation.waydroid 模块自动处理。
dnsmasq DHCP 配置
Waydroid 启动一个专用于 waydroid0 接口的 dnsmasq 实例,核心参数:
--interface=waydroid0 # 仅监听 waydroid0
--bind-interfaces # 不绑定其他接口
--dhcp-range=<EXAMPLE_IP>,<EXAMPLE_IP>,12h # DHCP 池 + 租约时间
--dhcp-leasefile=/var/lib/misc/dnsmasq.waydroid0.leases
# 当前上游实例提供 DHCP 与 DNS,不默认使用 --port=0DNS 与端口绑定 核对的上游脚本在网桥地址运行 dnsmasq,提供 DNS 与 DHCP;它不是默认 DNS-disabled 模式。冲突应按监听地址与接口判断,不因宿主存在 systemd-resolved 就直接关闭它。
LXC 网络命名空间配置
网络配置最终由 LXC 容器配置驱动。核心配置位于 /var/lib/waydroid/lxc/waydroid/config:
# 网络类型:veth(虚拟以太网对)
lxc.net.0.type = veth
# 连接宿主侧 veth 到 waydroid0 bridge
lxc.net.0.link = waydroid0
# 容器内接口名
lxc.net.0.name = eth0
# 固定 MAC
lxc.net.0.hwaddr = <EXAMPLE_MAC>
# 网络命名空间隔离
lxc.net.0.flags = up
# 使用 nftables 还是 iptables 生成规则
# LXC_USE_NFT=true 属于网络脚本变量,不是此 LXC 配置项端口转发
宿主通常可以经网桥路由直接访问容器地址;还需服务监听、ADB 授权和防火墙允许。通常先使用 adb connect <CONTAINER_IP>:5555,无需把端口暴露到外部网络。以下 DNAT 仅解释端口转发机制,必须限制入口接口和来源,不作为默认 ADB 配置:
# 将宿主 5555 端口转发到容器 <EXAMPLE_IP>:5555
iptables -t nat -I PREROUTING -i <TRUSTED_INTERFACE> -s <TRUSTED_SOURCE_IP> -p tcp --dport 5555 -j DNAT --to-destination <CONTAINER_IP>:5555
iptables -I FORWARD -i <TRUSTED_INTERFACE> -s <TRUSTED_SOURCE_IP> -p tcp -d <CONTAINER_IP> --dport 5555 -j ACCEPTnftables 等价规则:
# 自定义 DNAT 示例先建立 prerouting base chain,并限制来源
nft add chain inet waydroid prerouting '{ type nat hook prerouting priority dstnat; }'
nft add rule inet waydroid prerouting iifname "<TRUSTED_INTERFACE>" ip saddr <TRUSTED_SOURCE_IP> tcp dport 5555 dnat to <CONTAINER_IP>:5555
nft add rule inet waydroid forward iifname "<TRUSTED_INTERFACE>" ip saddr <TRUSTED_SOURCE_IP> ip daddr <CONTAINER_IP> tcp dport 5555 acceptDocker 网络冲突修复
当系统同时运行 Docker 和 Waydroid 时,docker0 bridge(默认 <EXAMPLE_IP>/16)可能与 waydroid0 产生路由或 nftables 规则冲突。典型症状:Android 容器内无法访问网络,或 Docker 容器网络中断。
常见症状
- Android 显示已连接 Wi-Fi 但无法打开网页
ping <EXAMPLE_IP>在容器内超时waydroid log无报错,但网络静默失败- Docker 容器在 Waydroid 启动后网络异常
修复方法:编辑 /var/lib/waydroid/lxc/waydroid/config,将:
# LXC_USE_NFT=true 属于网络脚本变量,不是此 LXC 配置项改为:
# 使用适配的网络后端/包,不在 LXC config 加入无效 lxc_use_nft然后重启 Waydroid:
waydroid session stop
sudo systemctl restart waydroid-container
waydroid session start原理:当 # LXC_USE_NFT=true 属于网络脚本变量,不是此 LXC 配置项 时,LXC 会尝试使用 nftables 创建容器网络规则,可能与 Docker 的 nftables 规则在同一个 hook 优先级链上互相覆盖。关闭后 LXC 回退到 iptables-legacy,真正问题可能是 Docker 修改 FORWARD 策略、规则顺序、子网重叠或后端混用。检查实际规则与路由;不能向 LXC config 加入无效变量来解决。
防火墙配置指南#
即使 Waydroid 内部 NAT 已工作,宿主防火墙仍可能拦截 waydroid0 上的转发流量。以下覆盖四种主流防火墙方案。
通用前置条件:开启内核 IP 转发
# 临时启用
echo 1 > /proc/sys/net/ipv4/ip_forward
# 永久启用(/etc/sysctl.d/99-waydroid.conf)
net.ipv4.ip_forward = 1firewalld(Fedora / RHEL / openSUSE)
# 将 waydroid0 加入 trusted zone
sudo firewall-cmd --zone=trusted --add-interface=waydroid0 --permanent
# 开放 DHCP 和 DNS 端口
sudo firewall-cmd --zone=trusted --add-port=67/udp --permanent
sudo firewall-cmd --zone=trusted --add-port=53/udp --permanent
# 允许 trusted zone 内的流量转发
sudo firewall-cmd --zone=trusted --add-forward --permanent
# 重载规则
sudo firewall-cmd --reload
# 验证
sudo firewall-cmd --zone=trusted --list-allUFW(Ubuntu / Debian)
# 开放 DHCP 和 DNS
sudo ufw allow in on waydroid0 to any port 67 proto udp
sudo ufw allow in on waydroid0 to any port 53 proto udp
# 允许 FORWARD 链(关键!)
# 编辑 /etc/default/ufw,确保:
# 优先增加针对 waydroid0 的 route allow 规则,不全局放开 FORWARD
sudo ufw reload
# 如果仍不生效,手动添加 iptables FORWARD 规则
sudo iptables -I FORWARD -i waydroid0 -j ACCEPT
sudo iptables -I FORWARD -o waydroid0 -j ACCEPTUFW 默认 FORWARD 策略为 DROP Ubuntu 的 UFW 默认将 FORWARD 链策略设为
DROP。这可能导致 Waydroid 无网络,应以规则计数与日志确认。应按最小范围放行所需转发,再验证,不把全局 ACCEPT 当作必需配置。
nftables(NixOS / 手动 nft)
NixOS 声明式配置(/etc/nixos/configuration.nix 或 flake 模块):
这是独立防火墙的机制示意,不是启用 Waydroid 后还必须添加的默认配置。先看模块生成规则;新的 forward base chain 的 policy drop 会影响所有转发流量,不能盲贴到已有 Docker/VPN/路由系统。整合已有策略,确认其他链的 drop、顺序、重复 NAT 与回退入口。
{
networking.nftables.enable = true;
networking.nftables.ruleset = ''
table inet filter {
chain forward {
type filter hook forward priority filter; policy drop;
iifname "waydroid0" accept
oifname "waydroid0" accept
ct state established,related accept
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr <EXAMPLE_IP>/24 masquerade
}
}
'';
}手动 nft 命令:
# 创建 waydroid 专用表
nft add table inet waydroid-fw
# FORWARD链:允许 waydroid0 双向流量
nft add chain inet waydroid-fw forward { type filter hook forward priority 0\; policy drop\; }
nft add rule inet waydroid-fw forward iifname "waydroid0" accept
nft add rule inet waydroid-fw forward oifname "waydroid0" accept
nft add rule inet waydroid-fw forward ct state established,related accept
# NAT:出站伪装
nft add chain inet waydroid-fw nat { type nat hook postrouting priority 100\; policy accept\; }
nft add rule inet waydroid-fw nat ip saddr <EXAMPLE_IP>/24 masquerade原生 iptables(无防火墙守护进程)
# 如果系统没有 firewalld / UFW / nftables,直接操作 iptables
iptables -I FORWARD -i waydroid0 -j ACCEPT
iptables -I FORWARD -o waydroid0 -j ACCEPT
iptables -t nat -I POSTROUTING -s <EXAMPLE_IP>/24 -j MASQUERADE
# 允许 DHCP 请求
iptables -I INPUT -i waydroid0 -p udp --dport 67 -j ACCEPT常见防火墙相关故障
| 症状 | 可能原因 | 排查命令 |
|---|---|---|
| Android 显示已连接但无网络 | FORWARD 链 DROP | iptables -L FORWARD -n -v |
| Play Store 无法连接 | DNS(53) 或 HTTPS(443) 被拦截 | nslookup google.com 在容器内测试 |
| DHCP 获取 IP 超时 | UDP 67 被拦截 | tcpdump -i waydroid0 port 67 |
| 容器完全无 IP | waydroid0 未创建或 dnsmasq 未启动 | ip link show waydroid0 / systemctl status waydroid-container |
| 部分网站可访问但 Google 服务不行 | IPv6 路由黑洞 | ping -6 google.com 对比 ping -4 google.com |
安全模型#
Waydroid 并非完整的沙箱(与 QEMU/KVM 级别的隔离不同),它依赖 Linux 内核的多层安全机制叠加形成纵深防御。
graph TB
subgraph Outer["外部攻击面"]
APP["恶意 Android App"]
NET["网络攻击"]
end
subgraph Layer1["第1层: Namespace 隔离"]
NS["Process / PID / Mount<br/>Network / IPC / UTS<br/>独立命名空间"]
end
subgraph Layer2["第2层: 可选 AppArmor 加固示意"]
AA1["waydroid-adbd<br/>ADB 守护进程"]
AA2["waydroid-android_app<br/>Android 应用进程"]
AA3["lxc-waydroid<br/>LXC 容器整体"]
end
subgraph Layer3["第3层: Seccomp-BPF"]
SEC["系统调用过滤<br/>open_by_handle_at (blocked)<br/>bpf/io_uring 未列默认过滤"]
end
subgraph Layer4["第4层: 实际 D-Bus/授权策略检查"]
PK["D-Bus 操作门控<br/>container.start<br/>container.stop<br/>binder node create"]
end
subgraph Inner["内核"]
KERNEL["Linux Kernel"]
end
APP --> NS
NET --> NS
NS --> AA1
NS --> AA2
NS --> AA3
AA3 --> SEC
SEC --> PK
PK --> KERNEL
style Layer1 fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
style Layer2 fill:#0f3460,stroke:#16213e,color:#e0e0e0
style Layer3 fill:#533483,stroke:#3b2665,color:#e0e0e0
style Layer4 fill:#e94560,stroke:#c23152,color:#e0e0e0第1层:Linux Namespace 隔离
Waydroid 通过 LXC 为 Android 容器创建六个命名空间,使其拥有独立的系统视图:
| Namespace | 作用 | 隔离内容 |
|---|---|---|
| Mount | 独立文件系统挂载 | Android 系统分区(system.img / vendor.img)仅容器可见 |
| PID | 独立进程树 | 容器内 ps 只看到 Android init 为 PID 1 |
| Network | 独立网络栈 | 容器拥有自己的 eth0、路由表和 iptables |
| IPC | 独立进程间通信 | Binder 通过设备上下文隔离并有宿主桥接,不能仅由 IPC namespace 推断 |
| UTS | 独立主机名 | 容器 hostname 与宿主隔离 |
| User | 部分 UID 映射 | 核对实际 lxc.idmap,不能假定默认 root 映射到非特权 UID |
Namespace 隔离不等于完全沙箱 所有 Namespace 共享同一个 Linux 内核。一个内核提权漏洞(CVE)即可打破所有隔离。Waydroid 的安全边界远薄于 KVM(后者有独立的虚拟地址空间和 vCPU)。
第2层:AppArmor Mandatory Access Control
以下三个 profile 是原笔记的自定义加固示意,并非核实的上游默认。当前 config_3 是 unconfined;不要把这些简化规则当作可直接部署且完整的安全策略。
waydroid-adbd — ADB 调试守护进程:
profile waydroid-adbd flags=(attach_disconnected) {
# 允许网络和 binder
network inet stream,
capability net_bind_service,
# 仅允许访问 ADB 密钥和 data 目录
/var/lib/waydroid/rootfs/** r,
/var/lib/waydroid/data/** rw,
# 禁止访问宿主敏感路径
deny /etc/shadow r,
deny /root/** rw,
}waydroid-android_app — 所有 Android 应用进程(Zygote fork 的子进程):
profile waydroid-android_app flags=(attach_disconnected) {
# 应用仅可读取 APK 和 system 分区
/var/lib/waydroid/rootfs/** r,
/var/lib/waydroid/data/app/** r,
# 通过 gbinder 与宿主 Waydroid 服务通信
unix (send, receive) type=stream peer=(label=waydroid-session),
# 禁止直接访问 GPU 设备节点(通过 HAL 间接使用)
deny /dev/dri/** rw,
}lxc-waydroid — LXC 容器整体(Android init 启动前):
profile lxc-waydroid flags=(attach_disconnected) {
# 允许挂载 Android 镜像
mount options=(ro, bind) -> /var/lib/waydroid/images/,
mount options=(rw, bind) -> /var/lib/waydroid/data/,
# 允许 binder 设备
/dev/binder rw,
/dev/binderfs/** rw,
# 允许 GPU 渲染
/dev/dri/renderD* rw,
}查看当前 AppArmor 状态
sudo aa-status | grep waydroid如果某个 profile 显示
(complain)而非(enforce),说明处于学习模式,不会实际阻止违规操作。
第3层:Seccomp 系统调用过滤
LXC 通过 Seccomp-BPF 为容器配置系统调用白名单/黑名单。以实际 seccomp 文件核对每项;原笔记的 bpf/io_uring 限制未见于所查上游默认:
| 系统调用 | 风险 | 处理方式 |
|---|---|---|
open_by_handle_at |
绕过文件系统权限检查,通过 inode handle 访问任意文件 | 阻塞 |
bpf |
eBPF 接口 | 所查默认 seccomp 未列出;另受内核权限限制 |
io_uring_* |
异步 I/O 接口 | 所查默认 seccomp 未列出,不宣称默认被阻塞 |
kexec_* |
热替换内核,容器内绝不应有 | 阻塞 |
init_module / finit_module |
加载内核模块 | 阻塞 |
Seccomp profile 位置:/var/lib/waydroid/lxc/waydroid/waydroid.seccomp(由 LXC 模板生成)。
第4层:PolicyKit 权限门控
Waydroid 的架构中,用户态(SessionManager, 普通用户权限)不直接操作特权资源。任何需要 root 的操作(启动容器、创建 binder 节点、挂载镜像)都通过 D-Bus 系统总线发起请求,由 PolicyKit 决定是否授权。
下方保留自定义授权设计的动作分类;这些名字不作为上游已经存在的 PolicyKit action:
| PolicyKit Action | 触发场景 | 授权策略 |
|---|---|---|
android.waydroid.container.start |
用户启动 Waydroid 容器 | 要求 wheel 组或主动输入密码 |
android.waydroid.container.stop |
用户停止 Waydroid 容器 | 同上 |
android.waydroid.container.freeze |
冻结容器(休眠) | 同上 |
android.waydroid.container.binder |
注册新的 binder 节点 | 仅允许 root 和 waydroid 用户 |
实际检查已安装 D-Bus/授权文件与服务代码,不根据示例 action 推断文件存在。
Linux Capabilities:最小权限集
容器进程被赋予的是细粒度 capabilities,而非完整的 root 权限。Waydroid 容器获得的最小集合:
cap_sys_admin # mount 操作、namespace 创建
cap_sys_nice # 调整进程优先级(Android 调度需要)
cap_net_admin # 配置网络接口
cap_net_bind_service # 绑定低端口(DHCP、VPN 服务)
cap_net_raw # 原始套接字(ping 等)
cap_ipc_lock # 锁定内存(binder 共享内存)
cap_sys_ptrace # 调试子进程(Zygote fork 监控)能力限制须核对 config_base 与 seccomp 的组合。当前 config_base 保留 sys_admin、sys_ptrace 和 sys_time 等较宽权限,不能宣称 sys_time 默认不存在。
安全性总结与局限
Waydroid 不是完整沙箱 与 KVM 不同,Waydroid 容器与宿主共享同一个 Linux 内核。这意味着:
- 内核提权漏洞直接影响容器安全边界
- binder 驱动的 bug可能被恶意 App 利用
- GPU 驱动的漏洞(通过
/dev/dri/renderD*暴露)是额外的攻击面- Android 应用不经过 Google Play Protect 审核(Waydroid 不自带 SafetyNet 认证)
风险等级:运行来自不可信来源的 APK 时,不能给不同宿主/Android/VM 组合排一个固定风险序列;应看共享内核、设备授权、数据映射和应用来源。
四层安全机制覆盖的攻击面总结:
攻击路径 阻断层
─────────────────────────────────────────
恶意 App 读取宿主文件 → AppArmor + Namespace
恶意 App 执行危险系统调用 → Seccomp
恶意 App 尝试启动/停止容器 → PolicyKit
恶意 App 网络扫描/攻击宿主 → Network Namespace + 防火墙
内核提权 (CVE) 从容器逃逸 → 无阻断 (共享内核)
GPU 驱动内存越界访问 → 部分阻断 (AppArmor 限制 /dev/dri)核对依据#
参考与延伸
根据技术笔记重新整理,示例使用通用名称。