CubeSandbox 网络模型深度解析:CubeVS 三层 eBPF 架构与内核态安全策略
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
本文是 CubeSandbox 网络子系统CubeVS的架构技术指南。CubeSandbox 为每个沙箱实例提供独立虚拟网络与私网连通性,并在内核态强制执行逐沙箱安全策略——这一切都建立在由三个 eBPF 程序、一组共享 BPF Map 和一套 Go 控制面库构成的 CubeVS 之上。读完本文,你将掌握 CubeVS 的三程序挂载模型、出/入站流量路径、双表会话跟踪、SNAT/DNAT 机制、CIDR 与域名两级网络策略、端口映射与 TAP 设备全生命周期管理,并能对照仓库源码理解每条关键路径的实现细节。
1. 架构总览
1.1 设计目标
传统容器网络栈(Linux Bridge、OVS、基于 iptables 的 NAT)会在每个数据包上叠加与宿主机租户数成正比的额外开销。CubeVS 用三个小型协作的 eBPF 程序替换了这一整套栈,将它们挂载在内核数据路径的关键点上,其核心设计目标有三点:
- 无共享网桥或软件交换机:每个沙箱通过自己独立的 TAP 设备直接挂到内核数据路径上,避免了网桥方案的广播域和逐跳成本。两个沙箱之间不存在任何共享 L2 网段,天然隔离。
- 内核态策略执行:网络策略在数据包到达用户态之前就由 eBPF 完成评估,CPU 开销最小化。
- 可扩展 NAT:SNAT 端口分配使用带锁保护的端口池与抗冲突插入,避免了大规模部署中 iptables 规则爆炸的问题。
1.2 三个 BPF 程序
CubeVS 在数据包穿越宿主机的三个网络边界上各挂一个 BPF 程序:
| 程序 | 源文件 | 挂载点 | 方向 | 职责 |
|---|---|---|---|---|
from_cube | mvmtap.bpf.c | 每个 TAP 设备的 TC ingress | 沙箱 --> 宿主机 | 策略检查、DNS 检查、SNAT、会话创建、ARP 代理 |
from_world | nodenic.bpf.c | 宿主机网卡的 TC ingress | 外部 --> 宿主机 | 反向 NAT、端口映射代理 |
from_envoy | localgw.bpf.c | cube-dev 的 TC egress | 宿主机 --> 沙箱 | 处理宿主机侧发往沙箱的流量:透明代理回包(CubeEgress)与宿主机主动探测 |
从源码看,三个程序的生成声明集中在 cubevs.go 的go:generate指令中,分别通过bpf2go编译localgw.bpf.c、mvmtap.bpf.c和nodenic.bpf.c,目标架构由$GOARCH决定,并引用vmlinux/$GOARCH下的内核头文件(vmlinux 目录按 amd64/arm64 分别提供)。
1.3 Go 控制面
CubeNet/cubevs/这个 Go 包封装了 BPF 的完整生命周期,主要 API 与文档一一对应:
Init():加载并 pin 三个 BPF 对象文件,注入宿主机相关常量(IP、MAC、接口索引),并挂载共享 TC 过滤器(详见第 9 节)。AddTAPDevice()/DelTAPDevice():注册/注销沙箱 TAP 设备,包括其元数据与网络策略(CIDR 与域名两类)。注册失败时会回滚已写入的元数据(tap.go)。AttachFilter():在 TAP 设备上创建 clsact qdisc 并挂载from_cubeTC 过滤器(详见第 8 节)。SetSNATIPs():填充 SNAT IP 池(详见第 4 节)。- 端口映射 API(
AddPortMapping、DelPortMapping、ListPortMapping、GetPortMapping等):运行时更新端口映射表(详见第 7 节)。 - Reaper 后台协程:一个清扫过期的 NAT 会话,另一个清扫过期的 DNS 学习策略条目(reaper.go 中
StartSessionReaper()通过sync.Once启动doReap,每 5 秒依次执行reapSessions()与reapDNSState())。
1.4 BPF Maps
三个程序通过/sys/fs/bpf/下的 pinned BPF Map 共享状态,共分六个功能组(Map 名常量见 cubevs.go):
| 分组 | Maps | 用途 |
|---|---|---|
| 设备注册表 | mvmip_to_ifindex、ifindex_to_mvmmeta | 每个沙箱的 IP 到设备、设备到元数据的双向查找 |
| NAT 会话 | egress_sessions、ingress_sessions | 双向 5 元组会话跟踪(见第 3 节) |
| SNAT 池 | snat_iplist | SNAT IP 及每个 IP 的源端口水位线 |
| 端口映射 | remote_port_mapping、local_port_mapping | 沙箱对外暴露服务的静态 NAT 规则(见第 7 节) |
| L3/L4 策略 | allow_out_v3、deny_out | 逐沙箱 CIDR 允许/拒绝列表(见第 5 节) |
| L7/域名策略 | dns_allow_v2、dns_query_track | 逐沙箱域名规则与待处理的 DNS 查询状态(见第 6 节) |
allow_out_v3中的条目带有可选过期时间,这使得 DNS 学习规则能与静态配置规则共享同一张 Map。仓库中的用户态结构体与 BPF 侧结构体通过编译期unsafe.Sizeof静态断言保证内存布局一致(cubevs.go),例如natSession固定 64 字节、sessionKey固定 20 字节、mvmMetadata固定 128 字节。
Map 采用 pin 机制,因此不同时间加载的程序(例如Init()之后逐个挂载的 per-TAP 过滤器)可以通过文件系统共享状态。bpfFSPath默认为/sys/fs/bpf,且被声明为变量以便测试将其指向临时 bpffs 挂载点(map.go)。
2. 流量路径
2.1 出站:沙箱到外部网络
当沙箱进程向外部发起连接时,数据包路径如下:
逐步拆解:
- 沙箱发出源 IP 为
169.254.68.6(固定内部地址)、源端口由其 TCP/UDP 协议栈选择的报文。 - 报文进入 TAP 设备,命中
from_cubeTC ingress 过滤器。 from_cube检查目的地址。如果目标是沙箱网关169.254.68.5,过滤器将报文重定向进cube-dev,交给宿主协议栈处理。- 对于其他所有目标,
from_cube依次执行:- 评估网络策略(见第 5 节)。被策略标记为需要 L7 透明代理(CubeEgress)检查的流量被重定向进
cube-dev而不是直接 NAT;宿主机侧的代理从那里接管。 - 检查出站 DNS 查询,以便基于域名的规则(见第 6 节)在响应返回时学习已解析的 IP。
- 在
egress_sessions与ingress_sessions中创建或更新 NAT 会话。 - 执行 SNAT:用 SNAT 池中的 IP 与动态分配的端口替换沙箱源 IP 和端口,并更新 L3/L4 校验和。
- 将改写后的报文重定向到宿主网卡。
- 评估网络策略(见第 5 节)。被策略标记为需要 L7 透明代理(CubeEgress)检查的流量被重定向进
2.2 入站:外部网络到沙箱
回包和端口映射的入站连接到达宿主网卡后,被路由回正确的沙箱:
from_world处理两类情况:
- 基于会话的反向 NAT:过滤器在
ingress_sessions中按报文 5 元组查找。命中后重建沙箱侧的原始 5 元组,执行反向 DNAT,并将报文重定向到正确的 TAP 设备。 - 端口映射入站:若无会话命中,过滤器用目的端口查
remote_port_mapping。命中说明这是对沙箱对外暴露服务的入站连接;过滤器将报文 DNAT 到沙箱的监听端口并重定向到 TAP。
2.3 宿主机到沙箱的流量
两类宿主机侧流量通过cube-dev进入沙箱,都由from_envoy处理:
两种情况下from_envoy都会把目的地址改写为沙箱内部 IP(169.254.68.6)并重定向到沙箱的 TAP。二者的差异在于源地址的处理:
- CubeEgress 代理回包:当沙箱的出站 HTTP/HTTPS 被重定向到宿主机上的 L7 透明代理(CubeEgress)后,CubeEgress 代连真实远端服务器并收到回包,再通过
cube-dev把回包送回沙箱,并通过IP_TRANSPARENT保留真实远端 IP 作为源地址。from_envoy保留该源 IP,因此沙箱看到的回包就像直接来自远端对端。 - 宿主机主动探测:宿主机自身向沙箱服务发送就绪/存活探测。这些报文源自宿主协议栈,以 cube-dev 自身地址为源地址从
cube-dev发出。from_envoy将源改写为沙箱网关(169.254.68.5),因此沙箱看到探测来自其默认网关。
这一机制在 localgw.bpf.c 中实现,其 L7 mark 常量(cube_l7_mark_http、cube_l7_mark_https、cube_l7_mark_mask)可在Params中通过L7MarkHTTP/L7MarkHTTPS/L7MarkMask覆盖,默认值与 iptables TPROXY 初始化脚本共享(cubevs.go)。
3. 会话跟踪
CubeVS 维护有状态连接跟踪,以便正确反向 NAT 回包并清理过期连接。
3.1 双表设计
两张 Map 协同工作:
egress_sessions是主会话表。键为沙箱侧看到的原始 5 元组。值保存完整 NAT 状态:SNAT IP 与端口、TAP ifindex、时间戳、TCP 状态以及主动关闭处理标志。对应的用户态结构体natSession中还有PacketClass、L7Scheme与PolicyVersion字段——其中PolicyVersion是策略代数,数据面会将其与每个 NAT 会话缓存的副本比较,以决定既有连接是否需要重新评估策略(cubevs.go)。ingress_sessions是反向查找表。键为外部侧看到的 5 元组。值只保存重建egress_sessions键所需的最小信息(ingressSessionValue仅 16 字节,包含 Version、VMIP、VMPort),用于执行反向 NAT。
这种双表设计避免了在两个方向上重复保存完整会话状态,同时支持从连接任意一侧进行 O(1) 查找。会话键按协议区分:TCP 键带有Version字段(TAP 代次,参与会话键),而 UDP 键的 Version 固定为 0——这从 reaper.go 的sessionKey结构体可以看出。
3.2 协议跟踪
CubeVS 在 TCP 上镜像 Linux 内核nf_conntrack:跟踪标准状态(SYN_SENT、ESTABLISHED、FIN_WAIT、TIME_WAIT等),使会话超时反映其协议状态——ESTABLISHED会话可以安全存活数小时,而半开会话与已关闭会话在数秒到数分钟内被回收。UDP 与 ICMP 使用更简单的两状态模型(UNREPLIED/REPLIED),首个回包到达时延长超时。
TCP 状态枚举tcpConntrackState完整复刻了内核enum tcp_conntrack,从tcpCTNone一直到tcpCTIgnored(reaper.go),UDP/ICMP 状态枚举亦与 icmp.h 中的ICMP_CT_*常量保持一致。
3.3 会话 Reaper
Go 后台协程每 5 秒运行一次,遍历egress_sessions,对每个会话比较now - access_time与状态相关超时。仓库中的实际超时表(reaper.go)如下:
| 协议 | 状态 | 超时 |
|---|---|---|
| TCP | NONE | 0(内核为 2 分钟) |
| TCP | SYN_SENT / SYN_RECV | 1 分钟 |
| TCP | ESTABLISHED | 3 小时 |
| TCP | FIN_WAIT | 2 分钟 |
| TCP | CLOSE_WAIT | 1 分钟 |
| TCP | LAST_ACK | 30 秒 |
| TCP | TIME_WAIT | 2 分钟(若沙箱主动关闭则为 10 秒) |
| TCP | CLOSE | 10 秒 |
| UDP | UNREPLIED / REPLIED | 30 秒 / 180 秒 |
| ICMP | 任意 | 30 秒 |
其中tcpTimeout()有一个特殊分支:当会话处于TIME_WAIT且ActiveClose == 1(沙箱侧主动关闭)时,套用CLOSE的 10 秒超时而非默认的 2 分钟,加速回收沙箱主动关闭的连接(reaper.go)。
会话过期时,reaper 同时删除egress_sessions与ingress_sessions条目——删除顺序是先删 ingress 再删 egress,因为需要用 egress 会话构造 ingress 键(reaper.go)。若会话并非在正常终态过期(例如ESTABLISHED未收到 FIN 而超时),reaper 通过 1024 容量的事件通道上报ErrSessionExpiredNotClosed告警日志。它还监控会话总数,当占用率超过 Map 容量(maxSessions = 1048576)的 80% 时上报ErrSessionsTooMany事件(reaper.go)。
4. SNAT 与 DNAT
4.1 SNAT:出站地址转换
沙箱的每个出站报文在离开宿主机前都必须被改写为可路由的源地址。CubeVS 在from_cube中通过最多 4 个 SNAT IP 的池完成这一操作。
IP 选择对每个沙箱是确定性的:index = jhash(sandbox_ip) % 4。因此同一沙箱的所有连接使用同一 SNAT IP,简化了外部防火墙规则与日志分析。
端口分配按每个 SNAT IP 条目单调递增的水位线工作,起始端口为 30000。该条目由 BPF spin lock 保护,因此不同 CPU 上的并发分配不会竞争。若选中的SNAT-IP:port与ingress_sessions中已有条目冲突,分配器递增端口并重试有限次数,仍失败则丢弃报文。
源码层面,SetSNATIPs()将传入的 IP 列表按 IP 升序排序后写入snat_iplist的前 4 个槽位(不足时循环复用),并将每个条目的MaxPort初始化为maxPortStart = 30000(snat.go)。
分配成功后,from_cube就地更新 IP 与传输层头,并将报文重定向到宿主网卡。
4.2 DNAT:入站地址转换
DNAT 发生在三种场景:
- 宿主机到沙箱流量(cube-dev 上的
from_envoy)——目的 IP 被改写为沙箱内部 IP(169.254.68.6)。源地址要么保留(CubeEgress 代理回包),要么改写为沙箱网关(宿主机主动探测),见 §2.3。 - 会话回包流量(宿主网卡上的
from_world)——目的 IP 和端口从节点的 SNAT 地址改写回沙箱的原始源地址和端口,使用ingress_sessions中的反向查找。 - 端口映射流量(宿主网卡上的
from_world)——对通过端口映射暴露的服务,直接用remote_port_mapping将目的改写为沙箱的监听端口,不触碰会话表。
5. 网络策略(基于 CIDR)
CubeVS 完全在内核态执行逐沙箱出站网络策略,使用 LPM(最长前缀匹配)trie 承载 CIDR 规则。
5.1 架构
每个沙箱的 TAP 设备关联两张 LPM trie:
allow_out_v3—— 目的 CIDR 允许列表。若目的匹配,无论拒绝列表如何都放行。条目可携带过期时间,这正是 DNS 学习规则(第 6 节)与静态规则共存于同一张 Map 的方式。v3 布局(lpmKeyV3)将端口放入键中(prefixlen 48 为精确(ip, port)规则、32 为仅 IP 规则、<32 为网段规则),值中直接保存 scheme,从而支持 L7 按端口规则(cubevs.go)。deny_out—— 目的 CIDR 拒绝列表。若目的匹配(且不在允许列表中),报文被丢弃。
两者都实现为以 TAP ifindex 为键的 hash-of-maps,因此对一个沙箱的策略更新永远不会触碰其他沙箱的 Map。每张内层 LPM trie 的容量上限为maxNetPolicyEntries = 8192条,创建时使用BPF_F_NO_PREALLOC标志(netpolicy.go)。
5.2 评估顺序
对每个出站报文:
- 若目的为沙箱网关(
169.254.68.5):放行(内部流量)。 - 若
allow_out_v3有匹配条目:放行。 - 若
deny_out有匹配条目:丢弃。 - 否则:放行。
优先级为allow > deny > default-allow,因此运维人员可以安装一条宽泛的拒绝规则(0.0.0.0/0,阻断所有互联网访问),再用允许规则打洞。
无论策略如何配置,CubeVS 始终拒绝以下私网与链路本地网段,防止沙箱探测宿主内部网络或其他沙箱(netpolicy.go 中的alwaysDeniedSandboxCIDRs):
10.0.0.0/8127.0.0.0/8169.254.0.0/16172.16.0.0/12192.168.0.0/16
这些恒定拒绝规则会在 TAP 注册/替换策略时与用户规则合并写入(effectiveDenyOutEntriesForReplace,netpolicy.go),确保该不变量永远不会被策略更新覆盖掉。
5.3 策略配置
注册 TAP 设备时,调用方提供一个MVMOptions结构体(tap.go):
AllowInternetAccess—— 若为false,安装0.0.0.0/0的 blanket 拒绝规则(netpolicy.go)。AllowOut/DenyOut—— CIDR 列表(AllowOut还可填域名,见第 6 节)。AllowOut中的目标会在用户态被自动分类:IPv4/CIDR 字面量进入allow_out_v3,域名进入dns_allow_v2(splitAllowOutTargets)。L7AllowOut——L7Target列表,每个目标由Host+ 可选(Port, Scheme)组成,用于 L7 透明代理策略。Port == 0 && Scheme == 0表示未指定,会展开为默认端口集{80/http, 443/https}(expandDefaultPortSet)。
策略可以在运行时更新:通过修改内层 LPM trie 即可生效,无需摘除或重载 BPF 程序。仓库提供了三种更新模式(netpolicy.go):
applyNetPolicy(创建路径的增量添加);replaceNetPolicy(恢复路径的冲刷 + 重填);UpdateTAPDevicePolicy(运行中沙箱的差异收敛)——先计算当前安装状态与期望状态的差异,只执行必要的逐条目写入;先撤销后添加,保证任何中间状态都不会比新旧策略都更宽松;最后再递增策略代数PolicyVersion,让数据面在下一个报文上对既有连接重新评估。
6. 网络策略(基于域名)
当服务的 IP 是动态的(CDN、云 API)时,CIDR 规则就不够用了。CubeVS 因此还支持从沙箱自身 DNS 流量学习的基于域名的允许规则。
6.1 工作原理
域名规则按沙箱存储在dns_allow_v2中,这是一张以反转的小写域名为键的 LPM trie。支持两种形态(编码规则见 dnspolicy.go 的makeDNSAllowRule):
- 精确匹配,例如
qq.com—— 只匹配qq.com。编码为反转名加\0结尾(qq.com->moc.qq\0)。 - 通配前缀,例如
*.qq.com—— 匹配a.qq.com等子域,但不匹配 apex。编码为反转名加.结尾(*.qq.com->moc.qq.),前缀长度按字节计算后写入Prefixlen。
当沙箱发出 DNS 查询时,from_cube提取被查询的域名并在dns_allow中查找。若域名被允许,查询 id 与源端口被记录到dns_query_track(键包含 ifindex、服务器 IP、源端口、DNS ID 与 qname 哈希,值缓存了命中规则的端口集合,见 cubevs.go)。响应返回时,from_world用dns_query_track匹配响应,提取 A 记录,并将每个解析出的 IP 以源自 DNS TTL 的过期时间插入该沙箱的allow_out_v3。DNS reaper 以与 NAT 会话 reaper 相同的方式清扫过期条目——netPolicyValueV3Expired判定动态条目(ExpiresAtNS != 0)是否到期(dns_reaper.go)。
一个值得注意的实现细节:静态规则与 DNS 学习规则在同一键上相遇时,静态规则(过期时间为 0)胜出,使条目变成永久条目,而不是让 DNS 的 TTL 到期后把静态裁决一起删掉(netpolicy.go)。
因为 DNS 学习规则落在与静态 CIDR 相同的allow_out_v3中,第 5 节的快速路径不需要为它们做任何特殊处理。域名解析出的 IP 在dns_query_track过期前一直被跟踪,未收到响应的查询由reapDNSQueryTrack定期清理。
6.2 未启用时零开销
DNS 检查是按沙箱选择性开启的。from_cube与from_world用沙箱元数据中的一个标志(dns_policy_flags,即dnsPolicyFlagLearningEnabled)门控整条 DNS 管线,该标志只在调用方配置了域名规则时才会被设置(dnsPolicyFlagsForDomains,netpolicy.go)。未配置时,DNS 报文走普通 UDP 路径,不进行额外解析、不做dns_query_track记账、不做逐包dns_allow查找。只使用 CIDR 策略的沙箱不会为域名策略机制支付任何开销。
7. 端口映射
沙箱从外部不可直接到达。当沙箱需要暴露服务时,CubeVS 提供端口映射——一条将到达特定宿主机端口的流量转发到特定沙箱端口的静态 NAT 规则。
7.1 双向映射
两张 BPF Map 支撑端口映射:
remote_port_mapping—— 将宿主机端口映射到(TAP ifindex, 沙箱监听端口)对。from_world用它把入站连接路由到正确的沙箱。local_port_mapping—— 将(TAP ifindex, 沙箱监听端口)反向映射到宿主机端口。from_cube将其用作优化:当沙箱从已映射的监听端口发包时,过滤器可跳过完整会话创建,直接用节点 IP 和正确端口执行 SNAT。
Go 侧的MVMPort结构体(8 字节,静态断言保证)以网络字节序存储端口,与数据面的 tcphdr 字段直接可比(cubevs.go)。
7.2 入站流程
- 外部客户端发送报文到
node_ip:host_port。 from_world查找remote_port_mapping[host_port]。- 命中后,过滤器将目的改写为
169.254.68.6:sandbox_listen_port并重定向到 TAP 设备。
此路径不创建会话表条目,使长连接服务的 Map 保持精简。
7.3 管理
Go API 提供AddPortMapping()、DelPortMapping()、ListPortMapping()与GetPortMapping()用于运行时管理映射。port.go 中的addPortMapping实现了完整的冲突检测与并发安全:插入前先查双向映射,任一方向已被其他元组占用即报错;本地表插入失败时回滚已插入的远端表条目(rollbackRemotePortMapping)。DelPortMapping只删除仍与期望元组匹配的条目,避免误删并发变更(port.go)。此外DeletePortMappingsByIfindex可独立扫描两张表并清理属于指定 ifindex 的全部映射。
7.4 计算节点端口划分
为防止子系统间冲突,宿主机可用端口空间被划分为三个区间:
| 端口区间 | 用途 |
|---|---|
10000–19999 | 宿主机临时端口(ip_local_port_range) |
20000–29999 | CubeProxy 用于到达沙箱的端口 |
30000–65535 | SNAT 为沙箱发起流量使用的源端口 |
Cubelet 内嵌的网络运行时只为沙箱端口映射从20000–29999分配宿主机端口。产品创建路径通过exposedPorts暴露容器端口,并且始终请求自动宿主机端口分配(HostPort=0);用户不能选择特定宿主机端口。运行时随后从该受管区间分配空闲端口,并在创建响应中返回映射。这与 SNAT 起始端口 30000(maxPortStart)上下呼应,两个子系统各自占用互不重叠的端口段。
8. TAP 设备生命周期
每个沙箱都有一个专用 TAP 设备,作为其在宿主机侧的唯一网络接口。CubeVS 管理这些设备的完整生命周期。
注册。沙箱运行时创建新沙箱时,调用AddTAPDevice(ifindex, ip, id, version, options),该函数将沙箱元数据写入ifindex_to_mvmmeta与mvmip_to_ifindex,并根据options初始化逐沙箱策略 Map(CIDR 与域名规则)。第 5.2 节的恒定拒绝 CIDR 首先安装,然后才是调用方指定的规则(tap.go)。若策略安装失败,注册会回滚已写入的元数据。元数据中的UUID最多 64 字节(maxIDLength),超长时报ErrTooLong;Version字段记录 TAP 代次,用于沙箱回滚场景——BumpMvmVersion通过读改写递增该代次,数据面据此判定旧会话失效并重置(tap.go)。
过滤器挂载。一旦 TAP 设备在 OS 层存在,AttachFilter(ifindex)就在其上创建 clsact qdisc 并挂载from_cubeTC 过滤器。从此刻起,沙箱发出的每个报文都被拦截。tc.go 展示了底层实现:createQdisc通过tc.Open添加 clsact qdisc(已存在时报EEXIST则忽略),attachFilter以direct-action标志、handle 1、优先级 1、ETH_P_ALL协议挂载 BPF 过滤器,并使用tcnl.Filter().Replace保证幂等。
拆除。DelTAPDevice(ifindex, ip)移除逐沙箱策略 Map 与设备注册表条目。引用该 TAP 设备的活跃会话留在原处——会话 reaper 会在它们超时后清理,从而避免在拆除时做昂贵的全表扫描。实现上拆除被拆分为两个可组合步骤:CleanupTAPDevicePolicy(冲刷allow_out_v3、deny_out、dns_allow_v2内层 Map 并清零 DNS 策略标志)与DeleteTAPDeviceMetadata(删除双向注册表条目,幂等处理缺失键)。只有 TAP 网卡本身被销毁时才会删除 hash-of-maps 的外层键(DeleteTAPDevicePolicyMaps、GCStaleNetPolicyMaps),就绪池复用场景保留外层键并重装默认拒绝规则(netpolicy.go)。
9. 初始化
cubevs.Init()在 Cubelet 内嵌网络运行时启动时(网络插件 /NetworkController启动阶段)只调用一次。它执行以下步骤:
- 加载并 pin三个 BPF 程序及其 Map 到
/sys/fs/bpf/; - 注入宿主机相关值到 BPF 字节码(加载前替换):沙箱 IP 与网关 IP、cube-dev 接口索引/IP/MAC、宿主网卡接口索引/IP/MAC、下一跳网关 MAC,以及 L7 mark 常量;
- 挂载共享 TC 过滤器:
from_envoy挂到 cube-dev 的 egress,from_world挂到宿主网卡的 ingress。
per-TAP 的from_cube过滤器在稍后沙箱逐个创建时挂载(第 8 节)。
从 cubevs.go 的Params结构体可以看到初始化所需的全部宿主机参数:MVMInnerIP/MVMMacAddr(沙箱内部 IP/MAC)、MVMGatewayIP(沙箱网关)、Cubegw0Ifindex/IP/MacAddr(cube-dev,即文档所称 cube-dev 设备)、EgressSrcMacAddr/EgressDstMacAddr/EgressRedirectFlags(出站 L2 改写与重定向行为)、NodeIfindex/IP/Mask/MacAddr(节点自身)、NodeGatewayMacAddr(下一跳网关 MAC)。若CubeRouterIfindex为 0 则禁用 cube-router 钩子挂载。
10. ARP 代理
沙箱被分配链路本地 IP(169.254.68.6),默认网关为169.254.68.5。由于这些地址只存在于 TAP 设备的点对点链路内,169.254.68.5处没有真实主机应答 ARP 请求——而且与网桥方案不同,这里也没有可广播该请求的共享 L2 网段。
from_cube充当 ARP 应答者:当沙箱询问"谁是 169.254.68.5?"时,过滤器当场构造一个 ARP 回复,用 cube-dev 网关 MAC 填充发送方 MAC,并重定向回 TAP。沙箱由此获得网关的有效 ARP 条目,可以正常发送 IP 报文,所有 IP 报文随后都进入第 2.1 节的出站路径。
由于沙箱链路使用169.254.0.0/16地址,而该网段同时位于恒定拒绝 CIDR 列表中(第 5.2 节),沙箱自身永远无法直接访问宿主或其他沙箱的链路本地地址——ARP 代理只服务于网关地址本身。
11. 总结
CubeVS 通过三层 eBPF 架构实现沙箱网络隔离:
这套设计建立在两个核心理念之上:
- 数据路径逻辑全部住在内核态。策略评估、NAT、会话跟踪、DNS 学习与 ARP 解析全部在 eBPF 中完成;Go 控制面只负责生命周期管理与周期性清理。
- 每个沙箱端到端隔离。独立的 TAP 设备、独立的策略 Map、独立的会话——同一宿主机上的两个沙箱之间不存在任何共享网桥或交换机。
附:源码地图
本文全部内容均有仓库源码佐证,以下路径可供深入阅读:
| 主题 | 源码位置 |
|---|---|
BPF 程序定义与go:generate | CubeNet/cubevs/cubevs.go |
| 三个 BPF 程序源码 | CubeNet/src/mvmtap.bpf.c、CubeNet/src/nodenic.bpf.c、CubeNet/src/localgw.bpf.c |
BPF 侧结构体与常量(cubevs.h) | CubeNet/src/cubevs.h |
| 会话 Reaper 与超时表 | CubeNet/cubevs/reaper.go |
| SNAT IP 池 | CubeNet/cubevs/snat.go |
| TAP 设备生命周期 API | CubeNet/cubevs/tap.go |
| 端口映射管理 | CubeNet/cubevs/port.go |
| CIDR/L7 策略规划与安装 | CubeNet/cubevs/netpolicy.go |
| 域名策略编码 | CubeNet/cubevs/dnspolicy.go |
| DNS 学习条目回收 | CubeNet/cubevs/dns_reaper.go |
| TC qdisc / 过滤器挂载 | CubeNet/cubevs/tc.go |
| BPF Map pin 加载 | CubeNet/cubevs/map.go |
| 相关单元测试 | CubeNet/cubevs/netpolicy_test.go、CubeNet/cubevs/dns_learn_test.go、CubeNet/cubevs/tap_test.go、CubeNet/cubevs/egress_policy_test.go、CubeNet/cubevs/port_test.go、CubeNet/cubevs/reaper_test.go(若存在) |
说明:本文所述的端口区间、超时数值、Map 名称与容量上限均以当前仓库 CubeNet/cubevs/ 的实现为准。CubeVS 运行在内核 eBPF 数据面上,需要较新的内核版本支持 TC clsact、LPM trie、spin lock 与 BPF pin 等特性;沙箱侧固定内部地址
169.254.68.6与网关169.254.68.5为链路本地段约定,属于实现事实而非可配置参数。
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考