CubeVS:用 eBPF 做 TAP 与 DNAT
本文基于 CubeSandbox 开源仓库中
CubeNet/目录下的源码进行分析。
概述
CubeVS(Cube Virtual Switch)是 CubeSandbox 的网络数据面核心组件,使用 eBPF 技术在 Linux 内核态实现高性能的网络包处理。它主要负责:
- TAP 设备管理:为每个沙箱(MVM)创建和管理 TAP 虚拟网卡
- DNAT/SNAT 转换:在内核态完成地址转换,避免用户态拷贝开销
- 网络策略执行:基于 LPM Trie 实现出站流量的 allow/deny 策略
- ARP 代理:响应沙箱内的 ARP 请求,简化网络配置
架构总览
eBPF 程序详解
1. mvmtap.bpf.c — TAP 设备主处理程序
这是 CubeVS 的核心 eBPF 程序,挂载在每个 TAP 设备的 TC(Traffic Control)钩子上,处理沙箱的进出流量。
挂载点:每个 TAP 设备的 ingress 和 egress
主要功能:
c
// 处理 ARP 请求 - ARP 代理
static __always_inline int handle_arp(struct __sk_buff *skb, __u32 ifindex)
{
// 1. 解析 ARP 请求
// 2. 使用网关 MAC 地址构建 ARP 回复
// 3. 将回复重定向回同一接口
return bpf_redirect(ifindex, 0);
}
// 检查是否需要做 NAT
static __always_inline bool should_do_nat(const struct iphdr *l3)
{
// 支持 TCP、UDP、ICMP
// 不处理分片包
if (l3->protocol != IPPROTO_TCP &&
l3->protocol != IPPROTO_UDP &&
l3->protocol != IPPROTO_ICMP)
return false;
return true;
}
// 网络策略检查
static __always_inline bool check_net_policy(__u32 ifindex, __u32 daddr)
{
// 优先级: allow_out > deny_out > 默认允许
// 1. 检查 allow_out 白名单
// 2. 检查 deny_out 黑名单
// 3. 默认允许
}关键 eBPF Map:
| Map 名称 | 类型 | Key | Value | 用途 |
|---|---|---|---|---|
mvmip_to_ifindex | HASH | MVM IP (u32) | TAP ifindex (u32) | IP 到网卡映射 |
ifindex_to_mvmmeta | HASH | ifindex (u32) | mvm_meta 结构体 | 网卡元数据 |
egress_sessions | HASH | session_key | nat_session | 出站 NAT 会话 |
ingress_sessions | HASH | session_key | ingress_session | 入站 NAT 会话 |
allow_out | HASH_OF_MAPS | ifindex (u32) | LPM Trie | 出站白名单 |
deny_out | HASH_OF_MAPS | ifindex (u32) | LPM Trie | 出站黑名单 |
2. localgw.bpf.c — Envoy 网关处理
挂载在 cube-dev 设备的 egress 路径上,处理从 Envoy 代理发往沙箱的流量。
c
SEC("tc")
int from_envoy(struct __sk_buff *skb)
{
// 1. 提取目标 IP (daddr)
// 2. 执行 DNAT: 将目标地址改为 MVM 内部 IP
// 3. 执行 SNAT: 将源地址改为网关 IP
// 4. 根据目标 IP 查找对应的 TAP 设备 ifindex
// 5. 重定向到 TAP 设备
dnat(skb, l3, mvm_inner_ip);
snat(skb, l3, mvm_gateway_ip);
ifindex = bpf_map_lookup_elem(&mvmip_to_ifindex, &daddr);
return bpf_redirect(*ifindex, 0);
}3. nodenic.bpf.c — 外部网络入站处理
处理从外部网络(如 CubeProxy)进入沙箱的流量,实现端口映射和 NAT 会话跟踪。
端口映射处理:
c
static int tcp_nat_proxy(struct ethhdr *l2, struct iphdr *l3,
struct tcphdr *l4, struct mvm_port *mvm_port)
{
// 1. 更新目标端口为 MVM 监听端口
// 2. 更新目标 IP 为 MVM 内部 IP
// 3. 更新 L3/L4 校验和
// 4. 更新 L2 MAC 地址
// 5. 重定向到 TAP 设备
l4->dest = mvm_port->listen_port;
rewrite_l3_addr(l3, &l3->daddr, mvm_inner_ip);
return bpf_redirect(mvm_port->ifindex, 0);
}NAT 实现
CubeVS 的 NAT 实现在 nat.h 中,使用 eBPF 的 helper 函数高效完成地址转换:
c
static __always_inline long nat_rewrite(struct __sk_buff *skb, struct iphdr *l3,
__u32 new_ip, enum nat_dir dir)
{
__u32 old_ip = (dir == NAT_DIR_SRC) ? l3->saddr : l3->daddr;
// 更新 L4 校验和 (TCP/UDP)
bpf_l4_csum_replace(skb, offset, old_ip, new_ip, flags);
// 更新 L3 校验和
bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(old_ip));
// 写入新 IP 地址
bpf_skb_store_bytes(skb,
(dir == NAT_DIR_SRC) ? IP_SADDR_OFF : IP_DADDR_OFF,
&new_ip, sizeof(new_ip), 0);
}
// 便捷宏
#define snat(skb, l3, src_ip) nat_rewrite(skb, l3, src_ip, NAT_DIR_SRC)
#define dnat(skb, l3, dst_ip) nat_rewrite(skb, l3, dst_ip, NAT_DIR_DST)数据包流向
出站流量(沙箱 → 外部)
入站流量(外部 → 沙箱,通过端口映射)
Go 用户态库 (cubevs)
CubeNet/cubevs/ 目录下是 eBPF 程序的 Go 用户态管理库,提供以下核心功能:
TAP 设备管理
go
// 添加 TAP 设备到 CubeVS
func AddTAPDevice(ifindex uint32, ip net.IP, id string, version uint32, opts MVMOptions) error {
// 1. 更新 ifindex_to_mvmmeta map
// 2. 更新 mvmip_to_ifindex map
// 3. 应用网络策略 (allow_out/deny_out)
}
// 删除 TAP 设备
func DelTAPDevice(ifindex uint32, ip net.IP) error {
// 1. 清理网络策略
// 2. 从 maps 中删除记录
}
// 列出所有 TAP 设备
func ListTAPDevices() ([]TAPDevice, error) {
// 遍历 ifindex_to_mvmmeta map
}端口映射
go
// 添加端口映射
func AddPortMapping(ifindex uint32, containerPort, hostPort uint16) error {
// 更新 remote_port_mapping 和 local_port_mapping
}
// 删除端口映射
func DelPortMapping(ifindex uint32, containerPort, hostPort uint16) error {
// 从 maps 中删除映射
}网络策略
网络策略使用 LPM(Longest Prefix Match)Trie 实现,支持 CIDR 格式的 IP 范围匹配:
go
type MVMOptions struct {
AllowInternetAccess *bool
AllowOut *[]string // CIDR or IP
DenyOut *[]string // CIDR or IP
}关键数据结构
mvm_meta(TAP 设备元数据)
c
struct mvm_meta {
__u32 version; // 版本号
__u32 ip; // MVM IP 地址
__u8 uuid[64]; // 沙箱 ID
__u8 reserved[56]; // 保留字段
};session_key(NAT 会话五元组)
c
struct session_key {
__u32 src_ip;
__u32 dst_ip;
__u16 src_port;
__u16 dst_port;
__u8 protocol;
__u8 version;
};mvm_port(端口映射)
c
struct mvm_port {
__u32 ifindex; // TAP 设备 ifindex
__u16 listen_port; // MVM 监听端口
__u16 reserved;
};性能优势
CubeVS 使用 eBPF 实现网络数据面有以下优势:
- 零拷贝转发:数据包在内核态直接处理,无需拷贝到用户态
- 高效 NAT:使用 eBPF helper 函数更新校验和,避免全包重算
- 策略执行:LPM Trie 支持 O(prefix_length) 复杂度的 IP 匹配
- 会话跟踪:基于 Hash Map 的会话表,O(1) 查找复杂度
延伸阅读
- network-agent:节点网络配置谁在管 — 了解 network-agent 如何管理 TAP 设备生命周期
- CubeProxy:OpenResty 里的路由魔法 — 了解请求如何路由到正确的沙箱