为什么我们给 AI 沙箱手写了一套跨平台用户态 TCP/IP 栈
目录
AI sandbox 的网络问题,表面上只是"让 guest 能访问外网"。但我担心的不是能不能 ping 通,而是:未知代码会在高频网络路径里做什么,以及我们能不能在事后解释它为什么能做。
AI agent 跑的是用户给的仓库、脚本、二进制和临时命令。它可能 curl | sh,可能跑包管理器,也可能启动一个开发服务器。网络是高频路径:DNS、TLS、包管理器、Git、模型下载、webhook、local service probe,全部都会从这里经过。如果这些流量直接进宿主内核网络栈,我们很难在跨 Linux / macOS / Windows 的语义上稳定地做审计、DLP、egress allowlist,甚至很难解释一次连接到底从哪里来、为什么被允许。
所以 Tokimo 最后选择了一条看起来更重、但边界更清楚的路:guest 里只放 tokimo-tun-pump 搬运 Ethernet frame,host 侧自己跑基于 smoltcp 的 userspace netstack。guest 可以像平常一样发包,但真正决定 flow 怎么建、怎么路由、怎么记账、怎么被策略拦截的地方,回到了 Tokimo 自己的 Rust 代码里。
问题为什么难
AI sandbox 的网络需求和传统 VM 网络不同,面临三个同时成立的约束:
┌──────────────────────────────────────────────────────────┐
│ 约束 1: 未知代码 │
│ Guest 运行任意用户仓库 / 脚本 / 二进制 │
│ 网络行为不可预测,必须从外部可解释 │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ 约束 2: 三平台一致性 │
│ Linux Auto (CH → bwrap) / macOS (VZ) / Windows (Hyper-V) │
│ 策略语义必须统一,不能每 backend 各写一套 │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ 约束 3: 可审计 / 可解释 │
│ 每次连接需能追溯到 session、task、进程 │
│ flow log 要产品侧可控,而非依赖宿主平台状态 │
└──────────────────────────────────────────────────────────┘
这三个约束加在一起,就排除了大部分"接通即可"的常规方案。传统的平台原生网络(nftables / pf / WFP)能解决单平台的某个约束,但无法同时满足三个。
AI sandbox 网络的真正难题不是吞吐,而是同时满足跨平台一致性和完整的流量可解释性。
为什么不是容器 / userns
容器路线我们不是没想过。userns、Landlock、seccomp、cgroup 全部拼起来,看起来像一个便宜的 sandbox,而且启动快、镜像生态成熟、调试体验也好。问题在于它们共享宿主内核,爆炸半径不够小。CVE-2022-0185、CVE-2024-1086 这种例子不需要过度展开;结论就是,只要未知代码还能打到同一个内核,AI shell 的风险就没有被切开。
对传统 CI 来说,容器可能是足够好的折中;对一个会让用户把任意仓库、任意命令、任意 agent loop 丢进去的产品来说,我们不想把"请相信 namespace 组合得足够好"当成核心安全边界。尤其网络审计还要和进程、文件、会话身份绑定时,共享内核会让很多边界变成策略约定,而不是故障域隔离。
于是主路线切到 VM:Linux 优先 Cloud Hypervisor,macOS 用 Virtualization.framework,Windows 用 Hyper-V。VM 至少给了一个硬边界:guest 用户态和宿主内核不是同一个故障域。但 Linux 现在还有一个必须诚实写出的可用性兜底:当 CH 不可用时,Auto backend 会退到 bubblewrap。bwrap 是 namespaces-based 容器,不提供 VM 级 kernel boundary;它的价值是让无 KVM / 无 vhost-vsock 环境仍能跑同一套 sandbox API、FUSE 和用户态 netstack,而不是把它包装成等价强隔离。
flowchart LR
A[容器 / userns] --> A1[共享宿主内核]
A1 --> A2[syscall / namespace / eBPF 风险面]
B[强隔离主路线] --> B1[Linux: Cloud Hypervisor]
B --> B2[macOS: Virtualization.framework]
B --> B3[Windows: Hyper-V]
F[Linux fallback] --> F1[bubblewrap namespaces]
B1 --> C[统一 sandbox API]
B2 --> C
B3 --> C
F1 --> C
C --> D[用户态网络栈]
共享内核让"合规的设计"和"不出问题的实现"永远不在同一个故障域里。
业界正在做什么
用户态 TCP/IP 并不是 Tokimo 独自发明的方向,业界已有多个先行者验证了可行性:
- passt(Linux 近年推出的用户态网络方案):在无特权 namespace 下提供完整 IPv4/IPv6 转发,不依赖 tun 设备,专为 VM 和容器的 rootless 网络设计,代表了 Linux 内核社区对"用户态网络可以够好"的判断。
- cowork(逆向分析结论):其 Windows 后端跳过了 Hyper-V vSwitch,而是走用户态网络栈。这个选择几乎坐实了一个判断:想在跨平台 AI sandbox 里保持策略一致性,回避 vSwitch 是务实选择,不是偏执。
- gVisor netstack:Google 的 gVisor 把完整的 TCP/IP 实现搬进用户态(Go),用于 runsc 容器的网络层。其 netstack 模块已被多个项目独立复用,说明用户态 L3/L4 在生产级沙箱里是被验证过的架构。
这条路不是孤注一掷,而是多个团队在不同场景里独立走到相同结论的收敛点。
网络方案怎么选
VM 以后,网络仍然有很多选择。我们试过或评估过 vSwitch / kernel stack 方向,最后丢掉了。原因不是它不能工作,而是策略落点太散:Linux、macOS、Windows 的网络对象、权限模型、packet hook 能力都不一样。你可以在某个平台上写 nftables,在另一个平台上写 pf 或系统过滤接口,再在 Windows 上补一套 Hyper-V / WFP 逻辑;但这样得到的不是一个产品级网络策略模型,而是三套相似但不等价的实现。
后来逆向看 cowork 的实现,发现他们也没有走 Hyper-V network,而是用了用户态网络栈,这个判断基本坐实:如果目标是跨平台 AI sandbox,真正难的不是"把网接通",而是把每个 flow 的语义收束到同一个地方。
vhost-user-net 很快,尤其 Linux + DPDK 场景很好看。但 Tokimo 的目标不是 10G+ NFV,也不是只跑 Linux。我们要的是一个三平台一致、可审计、可演进的接口。所以选择了 smoltcp:性能上限不如 vhost,CPU 也会更贵,但它把 L3/L4 放回了我们能读、能改、能插 hook 的 Rust 代码里。
flowchart LR
A[Tokimo AI sandbox 网络选择] --> B[vSwitch / kernel stack]
A --> C[vhost-user-net / DPDK]
A --> D[smoltcp userspace netstack]
B --> B1[平台原生但策略分裂]
C --> C1[吞吐最高但偏 Linux / 特权部署]
D --> D1[跨平台一致 / Rust hook 可控]
| 方案 | 吞吐 | CPU | 平台覆盖 | 可审计 | 维护成本 | 我们选不选 |
|---|---|---|---|---|---|---|
| vSwitch / kernel stack | 高 | 低 | 部分采用 | 否(三平台语义不一致) | 中 | 否 |
| vhost-user-net + DPDK | 极高 | 低 | 部分采用(偏 Linux) | 部分 | 高(特权 / DPDK 运维) | 否 |
| passt | 高 | 低 | 部分采用(Linux/CH 阶段资产) | 部分 | 中 | 保留历史验证价值,但不作为统一策略落点 |
| bubblewrap native netns only | 中 | 低 | Linux fallback | 否(只能说明隔离,不提供三平台 flow 模型) | 低 | 否,bwrap 仍接入 Tokimo netstack |
| gVisor netstack | 中 | 中 | 部分采用 | 是 | 高(Go 依赖) | 否 |
| wireguard-only relay | 低 | 低 | 是 | 否(relay 语义弱) | 低 | 否 |
| smoltcp userspace netstack | 中(目标/内部口径,正式 bench 待补) | 高 | 是(CH、bwrap、VZ、Hyper-V 统一) | 是 | 中(需维护 fork/patch) | 是 |
跨平台策略一致性是选型的第一约束,吞吐是选型的最后约束。
现在的架构
flowchart TD
subgraph Guest[Guest sandbox]
App[AI shell / unknown code]
Tk0[tk0 network interface]
Pump[tokimo-tun-pump]
App --> Tk0
Tk0 <-->|Ethernet frames| Pump
end
Pump <-->|CH: vsock| CH[Linux Cloud Hypervisor transport]
Pump <-->|bwrap: socketpair| BW[Linux bubblewrap transport]
Pump <-->|VZ: virtio-vsock| VZ[macOS transport]
Pump <-->|Hyper-V: HvSocket| HV[Windows transport]
subgraph Host[Host]
Netstack[smoltcp netstack]
Route[flow route / NAT / proxy]
Hooks[packet hooks / flow hooks]
Policy[egress allowlist / audit log / DLP]
end
CH --> Netstack
BW --> Netstack
VZ --> Netstack
HV --> Netstack
Netstack --> Route --> Hooks --> Policy --> Internet[(Internet / local services)]
数据在各层之间的流动:
┌──────────────────────────────────────────┐
│ 应用层 (AI shell / unknown code) │
│ POSIX socket API │
└──────────────────────────────────────────┘
│
v TCP/UDP segments
┌──────────────────────────────────────────┐
│ Guest socket / tk0 虚拟网卡 │
│ Ethernet frames (ARP + IP) │
└──────────────────────────────────────────┘
│
v raw frames over vsock / HvSocket
┌──────────────────────────────────────────┐
│ tokimo-tun-pump (guest 侧) │
│ 无策略,只负责帧的搬运 │
└──────────────────────────────────────────┘
│
v byte stream
┌──────────────────────────────────────────┐
│ smoltcp netstack (host 侧) │
│ L3/L4 解析 -> flow 创建 -> hook 拦截 │
│ egress allowlist / audit / DLP │
└──────────────────────────────────────────┘
│
v upstream TCP/UDP socket
┌──────────────────────────────────────────┐
│ 宿主 OS 网络栈 -> Internet │
└──────────────────────────────────────────┘
guest 侧的 tokimo-tun-pump 很克制:它不做策略,只负责把 tk0 上的 frame 搬出来。host 侧的 netstack 才是核心:smoltcp 处理 L3/L4,Tokimo 在 flow 创建、路由、关闭,以及 packet 进出点上预留审计位置。transport 可以是 Linux CH 的 vsock、Linux bwrap 的 socketpair、macOS virtio-vsock、Windows HvSocket,但上层看到的是同一类 frame / flow。
guest 不需要知道策略,它只需要相信自己有一张网卡,像普通 Linux 一样发 ARP、IP、TCP、UDP。host 侧则把 frame 还原成可理解的连接,把"这个 TCP flow 属于哪个 sandbox session、目标是什么、是否命中 allowlist、是否需要 DLP、关闭时传了多少数据"记录下来。策略不再散在 guest 里,也不依赖某个宿主平台的私有网络对象。
flowchart LR
P1[packet in: guest -> host] --> H1[packet hook]
H1 --> F1[smoltcp parse]
F1 --> F2[flow create / route]
F2 --> H2[flow hook: allow / deny / log]
H2 --> U[upstream socket]
U --> H3[flow hook: response accounting]
H3 --> F3[smoltcp emit]
F3 --> H4[packet hook]
H4 --> P2[packet out: host -> guest]
hook 分成 packet 和 flow 两层,因为它们回答的问题不同。packet hook 适合底层观测、调试、计数;flow hook 才适合做产品策略——egress allowlist、按任务记录审计日志、在连接建立时拒绝某个目的地、在响应阶段做流量记账。这样避免了把高层策略塞进每个 packet,也避免在 flow 层丢掉必要的底层可见性。
策略的位置决定策略的可维护性;把 flow hook 放在 host userspace,是让网络审计成为产品能力而非平台副作用的关键一步。
性能目标不是越高越好
目前保守口径约 ~1 Gbps,会议目标是 4.5 Gbps(该数字为内部目标,正式 bench 数据待补充)。这个目标对 AI agent 已经足够:拉依赖、访问 GitHub、跑包管理器、下载模型分片,瓶颈通常不在本机 guest↔host 网络。我们没有必要为了 10G+ 把 DPDK、vhost、平台特权和复杂运维全部拖进来。
更重要的是,网络栈可以独立升级。guest 发行版换不换、kernel config 怎么裁剪、Windows 后端怎么包装,都不应该影响 egress allowlist 和审计语义。只要 tokimo-tun-pump 和 host netstack 的协议稳定,网络策略就是 Tokimo 自己的产品能力,而不是某个平台的副作用。
高性能网络方案往往会把一部分控制权交回内核、驱动、vSwitch 或平台服务;而 AI sandbox 的第一优先级不是跑满网卡,而是让未知代码的网络行为可解释、可限制、可复盘。4.5 Gbps 目标足够支撑当前 agent 工作负载,约 ~1 Gbps 的保守口径已能覆盖大量开发场景。剩下的优化可以在同一架构里迭代,而不是为了一个极限吞吐数字推翻安全边界。
吞吐是可以迭代的工程数字;安全边界一旦妥协,架构级别的代价需要重写才能偿还。
我们诚实承认的取舍
得到的是三个东西:
- 跨 backend 接口一致:Linux Auto backend 下的 CH / bwrap、macOS VZ、Windows Hyper-V 可以用不同 transport,但 flow 语义、日志字段、allowlist 判断不需要写四遍。平台和 Linux fallback 差异被压在 transport 层,上层策略保持同一套模型。
- 安全产品能力落点明确:DLP、审计日志、egress allowlist、按会话网络开关,都可以落在 host netstack,而不是散在 nftables、pf、Windows Filtering Platform 或 Hyper-V vSwitch 里。以后要解释一次连接,看的是 Tokimo 的 flow log,而不是三个平台各自的系统状态。
- 网络升级可以独立于 distro / kernel:CH initrd 可以从 0.1.0 升到 0.1.1,rootfs 可以换,guest kernel 可以裁剪;只要 pump 还在,host 策略就不跟着漂。
代价:
- 吞吐上限低于 vhost-user-net
- CPU 成本更高
- smoltcp fork / patch 需要长期维护
- 调试路径更长:包从 guest -> host -> userspace stack -> upstream socket,中间任何环节出问题都需要额外工具
对 AI sandbox 来说,可解释、可限制、可复盘是第一阶段的核心产品指标;网络栈必须是我们能装刹车和行车记录仪的那个位置。
用户态 TCP/IP 是把网络治理能力从"依赖三个平台各自的私有对象"变成"Tokimo 代码库里一个可读、可改、可审计的模块"的必要条件——不是工程上的偏执,而是架构层面的职责划分。
版权属于:一名宅。
本文链接:https://zhaiyiming.com/archives/88.html
转载时须注明出处及本声明