Scope and prerequisites#
Commands and output are generic examples. Kernel, image, package, and service versions affect the exact behavior, so check the documentation for the versions you use as you read.
Back up Android data and stop the relevant sessions before modifying containers, images, mounts, or firewall rules; examples involving deletion or forced stops may lose data. Manage NixOS system and user-environment configuration through Nix. Generic Linux sections using apt/pacman, direct writes to /etc, or pip installations do not apply to a NixOS host. Rebuild operations such as build, test, and switch, and functional verification after boot, are distinct stages.
Waydroid's network stack has three layers: an LXC bridge, NAT, and dnsmasq DHCP. Its security boundary must be checked against the actual Namespace, AppArmor, Seccomp, capabilities, and D-Bus authorization configuration. The mere existence of these mechanisms does not mean that the default environment enables the corresponding isolation.
The waydroid0 bridge architecture#
Waydroid does not create a tap/tun virtual network interface for the Android container. Instead, it creates a Linux bridge named waydroid0 and uses NAT to forward the host network into the container. The full network topology is as follows:
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:#e0e0e0Key parameters
| Parameter | Value | Description |
|---|---|---|
| Bridge name | waydroid0 |
Created automatically when LXC starts |
| Host IP | <EXAMPLE_IP>/24 |
Gateway address, also used for the dnsmasq binding |
| DHCP address pool | <EXAMPLE_IP> ~ <EXAMPLE_IP> |
The container's eth0 obtains an address from this range |
| MAC address | <EXAMPLE_MAC> |
Fixed MAC; dnsmasq can use it to assign a fixed IP |
| Lease file | /var/lib/misc/dnsmasq.waydroid0.leases (current upstream) |
Persistent dnsmasq leases |
The waydroid-net.sh script flow
Network initialization is performed by /usr/lib/waydroid/waydroid-net.sh (or triggered by the waydroid-net systemd service), in the following order:
- Create the bridge:
brctl addbr waydroid0, orip link add waydroid0 type bridge - Configure the host IP:
ip addr add <EXAMPLE_IP>/24 dev waydroid0 - Bring up the bridge:
ip link set waydroid0 up - Set NAT rules: select the iptables or nftables variant for the system environment (see the next section)
- Start dnsmasq: listen on the
waydroid0interface to provide DHCP and DNS - Start the container with LXC: LXC creates a veth pair, attaches one end to the bridge, and places the other in the container's namespace
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>Checking IPv6 compatibility with the network script
Check the IPv6 requirements of the current package and network script Even if you do not use IPv6 networking at all and waydroid0 is IPv4-only, the kernel must enable
CONFIG_IPV6=y. This is because the network script or Android networking components may require IPv6 to be available; Binder does not have such a direct dependency on IPv6. Check whether the network script fails because/proc/sys/net/ipv6/is missing, rather than interpreting a networking requirement as a Binder protocol requirement. The default NixOS kernel already meets this requirement.
NAT and DHCP configuration#
Waydroid has different network backends. In the script examined, LXC_USE_NFT controls the choice; distributions may select it through package variants. Having nftables installed does not by itself imply an automatic switch, and this script variable should not be added to the LXC config:
| Condition to check | Backend used | Configuration location |
|---|---|---|
| The selected script/package uses iptables | iptables; check the legacy/nft compatibility layer separately | The actual waydroid-net.sh and package configuration |
| The selected script/package enables the nft backend | nftables | The actual script/package; the NixOS module selects a package according to networking.nftables.enable |
The iptables variant
On traditional iptables systems, such as Ubuntu 20.04/22.04 and Debian 11, waydroid-net.sh inserts the following rules:
# 允许 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 MASQUERADERule persistence By default, iptables rules are volatile and disappear after a reboot. The waydroid-net service reinserts them each time it starts. If you use
iptables-persistentornetfilter-persistent, avoid conflicts with the rules Waydroid inserts automatically.
The nftables variant
When the nft backend is selected, the corresponding rules can be used; the distribution version alone is not evidence that the backend is enabled. The inet waydroid below is a custom illustration. The upstream script examined uses inet lxc and ip lxc; actual names depend on the packaged variant:
# 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 masqueradenftables integration on NixOS: if your NixOS configuration already declares a networking.nftables ruleset, Waydroid needs the corresponding FORWARD and NAT paths opened in it. You can explicitly add waydroid chains through networking.nftables.ruleset, or use the virtualisation.waydroid module to handle this automatically.
dnsmasq DHCP configuration
Waydroid starts a dnsmasq instance dedicated to the waydroid0 interface. Its key parameters are:
--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 and port bindings The upstream script examined runs dnsmasq on the bridge address, providing both DNS and DHCP; DNS is not disabled by default. Diagnose conflicts by listening address and interface, rather than disabling systemd-resolved simply because it is present on the host.
LXC network namespace configuration
Networking is ultimately driven by the LXC container configuration. The main configuration is in /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 配置项Port forwarding
The host can usually reach the container address directly through the bridge route; the service must also be listening, ADB must authorize access, and the firewall must allow it. Usually, start with adb connect <CONTAINER_IP>:5555, without exposing the port to an external network. The DNAT example below only explains port forwarding. Restrict the ingress interface and source; do not use it as the default ADB configuration:
# 将宿主 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 ACCEPTEquivalent nftables rules:
# 自定义 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 acceptFixing Docker network conflicts
When Docker and Waydroid run on the same system, the docker0 bridge (default <EXAMPLE_IP>/16) may conflict with waydroid0 routes or nftables rules. Typical symptoms include no network access inside the Android container or disrupted Docker container networking.
Common symptoms
- Android reports a Wi-Fi connection but cannot open web pages
ping <EXAMPLE_IP>times out inside the containerwaydroid logreports no errors, but networking fails silently- Docker container networking malfunctions after Waydroid starts
Fix: edit /var/lib/waydroid/lxc/waydroid/config and change:
# LXC_USE_NFT=true 属于网络脚本变量,不是此 LXC 配置项To:
# 使用适配的网络后端/包,不在 LXC config 加入无效 lxc_use_nftThen restart Waydroid:
waydroid session stop
sudo systemctl restart waydroid-container
waydroid session startExplanation: with # LXC_USE_NFT=true 属于网络脚本变量,不是此 LXC 配置项, LXC tries to use nftables to create container network rules, which may overwrite or be overwritten by Docker's nftables rules in chains at the same hook priority. Disabling it makes LXC fall back to iptables-legacy. The actual issue may be Docker changing the FORWARD policy, rule ordering, overlapping subnets, or mixed backends. Inspect the actual rules and routes; adding an invalid variable to the LXC config cannot solve the problem.
Firewall configuration guide#
Even when Waydroid's internal NAT works, the host firewall may still block forwarded traffic on waydroid0. The following covers four common firewall approaches.
General prerequisite: enable kernel IP forwarding
# 临时启用
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's default FORWARD policy is DROP Ubuntu's UFW sets the FORWARD chain policy to
DROPby default. This may leave Waydroid without network access; confirm it using rule counters and logs. Allow only the necessary forwarding with the narrowest scope, then verify the result. Do not treat a global ACCEPT policy as required configuration.
nftables (NixOS / manual nft)
Declarative NixOS configuration (/etc/nixos/configuration.nix or a flake module):
This illustrates how a standalone firewall works; it is not additional default configuration required after enabling Waydroid. Inspect the module-generated rules first. A policy drop on a new forward base chain affects all forwarded traffic, so do not paste it blindly into an existing Docker, VPN, or routing setup. Integrate it with the existing policy and check drops in other chains, ordering, duplicate NAT, and a rollback path.
{
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
}
}
'';
}Manual nft commands:
# 创建 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 masqueradeNative iptables (without a firewall daemon)
# 如果系统没有 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 ACCEPTCommon firewall-related failures
| Symptom | Possible cause | Diagnostic command |
|---|---|---|
| Android reports a connection but has no network access | DROP in the FORWARD chain | iptables -L FORWARD -n -v |
| Play Store cannot connect | DNS (53) or HTTPS (443) is blocked | Test nslookup google.com inside the container |
| DHCP address acquisition times out | UDP 67 is blocked | tcpdump -i waydroid0 port 67 |
| The container has no IP address at all | waydroid0 was not created, or dnsmasq did not start | ip link show waydroid0 / systemctl status waydroid-container |
| Some websites work, but Google services do not | IPv6 routing black hole | Compare ping -6 google.com with ping -4 google.com |
Security model#
Waydroid is not a complete sandbox and does not provide QEMU/KVM-level isolation. It relies on multiple layers of Linux kernel security mechanisms for defense in depth.
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:#e0e0e0Layer 1: Linux Namespace isolation
Waydroid uses LXC to create six namespaces for the Android container, giving it a separate view of the system:
| Namespace | Purpose | What is isolated |
|---|---|---|
| Mount | Separate filesystem mounts | Android system partitions (system.img / vendor.img) are visible only inside the container |
| PID | Separate process tree | Inside the container, ps sees Android init as PID 1 |
| Network | Separate network stack | The container has its own eth0, routing table, and iptables |
| IPC | Separate interprocess communication | Binder is isolated through device contexts and has a host bridge; this cannot be inferred from the IPC namespace alone |
| UTS | Separate hostname | The container's hostname is isolated from the host's |
| User | Partial UID mapping | Check the actual lxc.idmap; do not assume that root maps to an unprivileged UID by default |
Namespace isolation is not a complete sandbox All namespaces share the same Linux kernel. A single kernel privilege-escalation vulnerability (CVE) can break through all of the isolation. Waydroid's security boundary is much thinner than KVM's, which provides a separate virtual address space and vCPUs.
Layer 2: AppArmor mandatory access control
The following three profiles are custom hardening illustrations from the original notes, not verified upstream defaults. The current config_3 is unconfined; do not treat these simplified rules as a complete security policy ready for deployment.
waydroid-adbd — the ADB debugging daemon:
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 — all Android application processes (children forked by Zygote):
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 — the LXC container as a whole, before Android init starts:
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,
}Check the current AppArmor status
sudo aa-status | grep waydroidIf a profile shows
(complain)rather than(enforce), it is in learning mode and does not actually block violations.
Layer 3: Seccomp syscall filtering
LXC uses Seccomp-BPF to configure syscall allowlists/blocklists for the container. Check each item against the actual seccomp file. The bpf/io_uring restrictions in the original notes were not present in the upstream defaults examined:
| System call | Risk | Handling |
|---|---|---|
open_by_handle_at |
Bypasses filesystem permission checks to access arbitrary files through inode handles | Blocked |
bpf |
eBPF interface | Not listed in the default seccomp configuration examined; also subject to kernel permission restrictions |
io_uring_* |
Asynchronous I/O interface | Not listed in the default seccomp configuration examined; no claim that it is blocked by default |
kexec_* |
Hot-replaces the kernel; should never be available inside a container | Blocked |
init_module / finit_module |
Loads kernel modules | Blocked |
Seccomp profile location: /var/lib/waydroid/lxc/waydroid/waydroid.seccomp (generated by the LXC template).
Layer 4: PolicyKit authorization gates
In Waydroid's architecture, the user-space SessionManager, running with ordinary-user permissions, does not manipulate privileged resources directly. Operations requiring root, such as starting the container, creating binder nodes, and mounting images, send requests over the D-Bus system bus, with PolicyKit deciding whether to authorize them.
The action categories below retain a custom authorization design; these names are not presented as existing upstream PolicyKit actions:
| PolicyKit Action | Trigger | Authorization policy |
|---|---|---|
android.waydroid.container.start |
User starts the Waydroid container | Requires membership in wheel or explicitly entering a password |
android.waydroid.container.stop |
User stops the Waydroid container | Same as above |
android.waydroid.container.freeze |
Freeze the container (suspend) | Same as above |
android.waydroid.container.binder |
Register a new binder node | Only root and the waydroid user are allowed |
Inspect the installed D-Bus/authorization files and service code; do not infer that a file exists from an example action.
Linux capabilities: a minimal privilege set
Container processes receive fine-grained capabilities rather than full root privileges. The minimal set granted to the Waydroid container is:
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 监控)Check capability restrictions using config_base together with seccomp. The current config_base retains broad privileges including sys_admin, sys_ptrace, and sys_time; do not claim that sys_time is absent by default.
Security summary and limitations
Waydroid is not a complete sandbox Unlike KVM, Waydroid containers share the host's Linux kernel. This means:
- Kernel privilege-escalation vulnerabilities directly affect the container's security boundary
- Bugs in the binder driver may be exploited by malicious apps
- GPU driver vulnerabilities, exposed through
/dev/dri/renderD*, create an additional attack surface- Android applications do not undergo Google Play Protect screening (Waydroid does not come with SafetyNet certification)
Risk levels: when running APKs from untrusted sources, different host/Android/VM combinations cannot be assigned a fixed risk ranking. Consider the shared kernel, device permissions, data mappings, and application sources.
Summary of the attack surfaces covered by the four security layers:
攻击路径 阻断层
─────────────────────────────────────────
恶意 App 读取宿主文件 → AppArmor + Namespace
恶意 App 执行危险系统调用 → Seccomp
恶意 App 尝试启动/停止容器 → PolicyKit
恶意 App 网络扫描/攻击宿主 → Network Namespace + 防火墙
内核提权 (CVE) 从容器逃逸 → 无阻断 (共享内核)
GPU 驱动内存越界访问 → 部分阻断 (AppArmor 限制 /dev/dri)Verification sources#
Official Waydroid installation instructions.
Official Waydroid command reference.
Waydroid upstream source code.
Actual capability configuration.
References & further reading
- Official Waydroid installation instructions ↗
- Official Waydroid command reference ↗
- Waydroid upstream source code ↗
- Actual network script ↗
- Actual LXC configuration ↗
- Actual capability configuration ↗
- Actual seccomp configuration ↗
Adapted from technical notes. Examples use generic names.
Continue along a reading path
Waydroid
- 01Waydroid architecture: LXC, Binder, and session bridging
- 02Waydroid installation and basic setup
- 03Waydroid integration on NixOS
- 04Waydroid GPU detection and hardware acceleration
- 05Waydroid bridges, NAT, and security boundaries
- 06Waydroid properties, multi-window mode, and ARM translation
- 07Waydroid troubleshooting and FAQ
- 08Waydroid tips: windows, translation, and shared directories