Skip to content

CubeVS:用 eBPF 做 TAP 与 DNAT

本文基于 CubeSandbox 开源仓库中 CubeNet/ 目录下的源码进行分析。

概述

CubeVS(Cube Virtual Switch)是 CubeSandbox 的网络数据面核心组件,使用 eBPF 技术在 Linux 内核态实现高性能的网络包处理。它主要负责:

  1. TAP 设备管理:为每个沙箱(MVM)创建和管理 TAP 虚拟网卡
  2. DNAT/SNAT 转换:在内核态完成地址转换,避免用户态拷贝开销
  3. 网络策略执行:基于 LPM Trie 实现出站流量的 allow/deny 策略
  4. 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 名称类型KeyValue用途
mvmip_to_ifindexHASHMVM IP (u32)TAP ifindex (u32)IP 到网卡映射
ifindex_to_mvmmetaHASHifindex (u32)mvm_meta 结构体网卡元数据
egress_sessionsHASHsession_keynat_session出站 NAT 会话
ingress_sessionsHASHsession_keyingress_session入站 NAT 会话
allow_outHASH_OF_MAPSifindex (u32)LPM Trie出站白名单
deny_outHASH_OF_MAPSifindex (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 实现网络数据面有以下优势:

  1. 零拷贝转发:数据包在内核态直接处理,无需拷贝到用户态
  2. 高效 NAT:使用 eBPF helper 函数更新校验和,避免全包重算
  3. 策略执行:LPM Trie 支持 O(prefix_length) 复杂度的 IP 匹配
  4. 会话跟踪:基于 Hash Map 的会话表,O(1) 查找复杂度

延伸阅读