Weave fast datapath(fastdp)技术解析:基于 OVS 内核数据路径的 VXLAN 覆盖网络实现
Weave fast datapath(fastdp)技术解析:基于 OVS 内核数据路径的 VXLAN 覆盖网络实现
导读
本文以仓库 docs/fastdp.md 为核心,系统讲解 Weave Net 中「快速数据路径」(fast datapath,简称 fastdp)的实现原理与使用方式。fastdp 是 Weave Net 默认启用的高性能 Overlay 方案:它利用 Linux 内核的 Open vSwitch 数据路径(ODP)模块,让数据包在内核态完成 VXLAN 封装与转发,与传统的用户态 Sleeve 封装相比显著降低上下文切换开销。读完本文,你将掌握:Overlay 方法的自动选择机制、fastdp 的以太网桥接与流表(flow)工作原理、基于 VXLAN 的心跳与 PMTU 探测、12 位短对等体 ID 的编码方式,以及 WEAVE_MTU、WEAVE_NO_FASTDP 等关键配置的实战用法;对于加密场景,还会深入 IPsec/XFRM 的实现细节。
Overlay 方法选择:fastdp 优先、Sleeve 兜底
Weave 在与其他节点建立连接时,会自动选择当前环境下可用的最佳 Overlay 实现。其选择过程(见 docs/fastdp.md 的 "Overlay Method Selection" 一节)为:
- 针对一个新的对等体,并行启动两种转发器(forwarder):fastdp 与 Sleeve;
- 当某个转发器率先确认可用(established)后,根据优先级选定其中最受青睐的一个,并关闭另一个转发器(若它尚未失败);
- 若之后幸存的转发器失败(例如心跳超时),控制面 TCP 连接会回收重建,从而让选择算法再次运行。
默认情况下,fast datapath 优先于 Sleeve。这与源码中的注册顺序一致:在 prog/weaver/main.go#L606-L641 的 createOverlay() 中,fastdp 被率先 overlay.Add("fastdp", ...),随后才 overlay.Add("sleeve", ...)。
OverlaySwitch 的实现机制
router/overlay_switch.go 中的 OverlaySwitch 负责具体的选择逻辑:
- 特性协商:
AddFeaturesTo将本节点支持的 Overlay 列表("Overlays"特性)随连接宣告出去;commonOverlays()通过解析对端params.Features<a href="https://link.gitcode.com/i/8ab3b8c853ba825c82f81b7629da28d3" target="_blank">"Overlays"]求交集,得到双方共同支持的 Overlay 集合,并按发起连接一方的顺序排列([router/overlay_switch.go#L87-L126)。 - 并行尝试:
PrepareConnection为每个共同支持的 Overlay 都创建一个子转发器(subForwarder),并用monitorForwarder协程监听各自的 established / error / health 事件(router/overlay_switch.go#L178-L245)。 - 择优逻辑:
chooseBest()遵循两条原则——"最受青睐的已建立转发器"优先,其次才是"最受青睐的可用转发器";健康状态(onHold)会临时把某个子转发器移出候选,心跳恢复后重新加入(router/overlay_switch.go#L361-L400)。
需要特别说明的是 SetCompatOverlay:当对端是老版本节点、未在特性中声明 Overlays 时,直接退化为兼容 Overlay(即 Sleeve)。因此 fastdp 的选择是逐连接的,一个网络中可以同时存在 fastdp 与 Sleeve 两种链路——例如跨数据中心的场景,通常数据中心内部走 fastdp,而受限链路自动回退 Sleeve。
本地桥接:内核态以太网桥与 MAC 学习
与 Sleeve 使用的 weave 网桥 netdev 不同,OVS 数据路径本身不具备桥接能力。因此 fastdp 在 VXLAN Overlay 之外,还自行实现了一个以太网桥,维护自己的转发数据库:学习 MAC 地址,并据此分发广播、单播与组播流量。
这一逻辑在 router/fastdp.go#L266-L311 的 bridge() 方法中非常直观:
- 若源 MAC 未知,则学习之——把
sendToMAC[srcMAC]指向该数据包进入的桥端口(sendToPort[ingress]),并记入seenMACs(源码注释明确指出注释:Learn the source MAC); - 若目的 MAC 已知,则直接构造
Output动作将包送向对应端口,同时生成一条匹配该以太网源/目的地址的流规则; - 若目的 MAC 未知或为真实广播,则向除入端口外的所有桥端口(包括本地 netdev/internal 端口以及通往远端 peer 的 vxlan 端口)复制一份;对非真实广播(目的 MAC 第 0 字节低位为 0 的未知单播),用
vetoFlowCreationFlowOp禁止创建流,因为一旦学到该 MAC,这条流就必须失效。
短对等体 ID:用 12 位编码塞进 24 位 VXLAN VNI
路由器在决定如何转发数据包时,必须知道源对等体与目的对等体是谁。Sleeve 通过在其自定义封装中直接携带 peer 名称(约 17 字节)传达这一信息;但 VXLAN 线格式没有这样的字段。VXLAN 头中恰好有一个 24 位 segment ID(VNI) 字段可以利用——挑战在于要用 12 位标识符唯一标识 peer,而不是 Sleeve 所用的 17 字节。
实际做法是:每个 peer 采用一个随机生成的 short ID(短 ID),通过已有的 gossip 机制解决 ID 归属冲突。代码层面:
tunnelIDFor()(router/fastdp.go#L953-L958)把源、目的 peer 的 12 位 short ID 编码进 8 字节 tunnel ID:src | dst<<12,写入 VNI;extractPeers()(router/fastdp.go#L540-L545)在 miss handler 中反向解析:vni & 0xfff取源 short ID,(vni >> 12) & 0xfff取目的 short ID,再通过peers.FetchByShortID()还原成完整的mesh.Peer对象。
由此,VXLAN 数据包在到达对端时,内核无需用户态参与即可从 VNI 中得知流量属于哪两个 peer,从而让流规则得以匹配这些信息。若两个 peer 恰好选了相同的 short ID,则会触发短 ID 冲突失效流程(见下文"流失效")。
通过 VXLAN 发送心跳(Heartbeats)
fastdp 的心跳实现很巧妙:发送一个封装好的以太网帧,其源、目的 MAC 均为 00:00:00:00:00:00,负载由 连接 UID(64 位)与整个以太网帧总长度(16 位) 组成。接收方的 vxlan vport miss handler 一旦发现全零 MAC,便识别出这是特殊心跳帧,在通过 TCP 控制通道确认连接 UID 与帧长匹配后,回送心跳 ACK。
源码佐证:
sendHeartbeat()(router/fastdp.go#L780-L801)构造EthernetOverhead + iface.MTU长度的帧,写入 UID 与帧长后走Forward()发出;- vxlan vport 的 miss handler 检测到
SrcMAC == zeroMAC && DstMAC == zeroMAC时,返回vxlanSpecialPacketFlowOp(router/fastdp.go#L493-L535); handleVxlanSpecialPacket()(router/fastdp.go#L808-L856)校验帧长与 UID,并通过sendControlMsg(FastDatapathHeartbeatAck, nil)应答;同时它还能在此过程中发现对端 IP 变化(NAT 场景),更新remoteAddr。
心跳同时充当连接健康探针:doHeartbeats() 协程(router/fastdp.go#L720-L759)按 heartbeatInterval 周期性发送;一旦心跳超时(heartbeatTimeout 触发),就把该 forwarder 标记为不健康(healthChan <- false),并切换为慢心跳频率(SlowHeartbeat),OverlaySwitch 随即将其 onHold,改用其他可用 Overlay——这也是 Sleeve 回退的触发路径之一。
PMTU 发现与 MTU 配置
心跳以太网帧的长度被设置为 datapath 接口的 MTU。若承载它的 VXLAN 包在路径上被丢弃或截断,心跳将得不到 ACK;这种 ACK 缺失会让两端 peer 回退到 Sleeve Overlay——因为 Sleeve 具备更完善的动态应对低路径 MTU 的机制。
为避免在典型部署中误触发回退,datapath 接口默认静态配置为 1376 字节 MTU,可适配大多数底层网络提供商的 MTU,包括 GCE 的 1460 字节(两者相差的 84 字节正是加密 VXLAN 的额外开销)。如需调整,可在启动时通过环境变量 WEAVE_MTU 覆盖:
# 典型 jumbo frame 配置
$ WEAVE_MTU=8916 weave launch host2 host3
该变量在 weave 启动脚本(-e WEAVE_MTU 传入容器、--mtu 参数透传)以及 prog/net-plugin/config.json(CNI 插件配置项)中均有定义。需要提醒:底层网络必须能承载"指定 MTU + 约 84–87 字节封装开销"的数据包(且最终 MTU 最好能被 4 整除),否则该连接会回退到 Sleeve;这一约束适用于任意两端之间的每一条路径。基准测试脚本 docs/benchmarks.md 中亦给出了 WEAVE_MTU=8916/8950 的调优示例。
虚拟端口(Virtual Ports):internal、netdev、vxlan
与 weave datapath 关联的虚拟端口共有三类(见 docs/fastdp.md 的 "Virtual Ports" 一节):
internal:自动创建一个,以 datapath 名命名。它对应 datapath 创建后在宿主机上出现的同名网络接口,是weave expose的进出端口;netdev:每个应用容器一个,对应 veth pair 的主机端;vxlan:监听的每个 UDP 端口对应一个,用于接收 VXLAN 封装包。通常只有一个(weave 主端口 +1,见下),但若网络中 peer 端口配置不一致,也可能存在多个。
在 router/fastdp.go#L447-L538 中可以看到 vxlan vport 的创建细节:getVxlanVportID() 以 vxlan-<udpPort> 命名创建 vport,并对内核"端口占用时仍报告创建成功"的 bug 做了 netlink.LinkByName + 接口状态检查的额外校验;mainVxlanUDPPort = port + 1(router/fastdp.go#L134)——即 VXLAN 端口固定为 weave 主端口加 1。Weave 默认控制端口为 6783,故 VXLAN 流量使用 UDP 6784(防火墙需放行,参见 site/tasks/manage/fastdp.md)。makeBridgeVport()(router/fastdp.go#L1183-L1209)则为 internal/netdev 端口登记桥接 miss handler 并注册发送函数。
Miss 与流创建:把转发决策下沉给内核
如前所述,datapath 本身没有任何转发行为——任何未命中流表(flow)的入站包都会被交给用户态 miss handler。miss handler 的职责有二:
- 指示 datapath 对引发 miss 的数据包执行动作(例如复制到一个或多个端口);
- 可选地安装流规则,让 datapath 后续直接对相似数据包自动处理,无需再回到用户态。
关键 Flow Keys
当 miss handler 被调用时,它拥有两块上下文:触发 miss 的原始包字节数组,以及内核模块已从包中提取的 flow keys 集合。weave 关心的 key 有:
| Flow Key | 含义 |
|---|---|
InPort |
入站虚拟端口标识 |
Ethernet |
以太网帧的源、目的 MAC 地址 |
Tunnel |
隧道标识(即上文短 peer ID 编码的 VNI),以及源/目的 IPv4;仅当从 vxlan vport 入站时存在 |
关键在于:路由器必须仅凭这些信息决定动作——这样才可能写出同时匹配这些 key 的流规则,让内核自动执行,彻底摆脱用户态参与。相关的两类动作:
Output:把包输出到指定 vport;SetTunnel:更新生效的 VXLAN 隧道参数,仅在从 vxlan vport 输出前需要。
一个流可以包含多个动作,因此路由器可以创建一条规则,同时把广播流量复制到所有"本地"vport(显然除去入端口),以及按路由拓扑复制到通往远端 peer 的 vxlan vport。路由器安装的每条流都具有如下特征:匹配入端口的 InPort key;若入端口是 vxlan 则带 Tunnel key;带匹配源/目的 MAC 的 Ethernet key;动作列表包含面向 internal/netdev vport 的 Output,以及面向 vxlan vport 的 SetTunnel+Output。
不创建流的情况
以下两种场景,miss handler 只执行动作、不创建对应流:
- 广播以发现未知目的 MAC 的路径时:该状态在绝大多数情况下是瞬态的,创建流的收益抵不上学到 MAC 后必须失效它的代价;
- 数据包还需通过 Sleeve 转发给一个或多个 peer 时:因为不存在不带用户态参与的 OVS 流动作可完成该任务,路由器必须持续处理所有后续匹配包(router/fastdp.go#L1237-L1307 的
send()中,遇到 Sleeve 转发等"外来 FlowOp"时会置createFlow = false并懒解码数据包走 FlowOp 接口)。
此外,router/fastdp.go#L1321-L1363 的 isHairpinFlow() 会否决掉入端口==出端口的"发夹弯"流(如网桥端口启用 hairpin 模式导致包从进入的 vxlan 隧道原路返回),防止流表污染。
流失效(Flow Invalidation)
一旦某个 key 组合的流被创建,miss handler 就再也不会因匹配包被调用。因此,检测使既有流动作失效的事件至关重要。触发全量失效的事件有三类:
- 向 datapath 添加 netdev vport(
VportCreated/makeBridgeVport中调用deleteFlows); - 路由失效(拓扑变化)——对应
InvalidateRoutes()(router/fastdp.go#L323-L328); - 短 peer ID 冲突——对应
InvalidateShortIDs()(router/fastdp.go#L330-L335)。
三者的响应动作一致:删除 datapath 中的全部流,让它们基于更新后的状态重建。
除事件驱动失效外,还有一个每 5 分钟执行一次的过期进程(run() 与 expireFlows(),见 router/fastdp.go#L1043-L1091):枚举 datapath 中所有流,删除自上次检查以来未被使用(Used == 0)的流,从而清理:
- 引用已删除 netdev vport 的流;
- 已停止 forwarder 创建的流;
- 长期未通信的 MAC 地址相关流。
值得注意的是:以隧道 IPv4 地址为 key 的流不需要在 peer 因 NAT 改变 IP 时主动清除——这会引发一次 miss 并创建新流,而旧流会通过定时器机制自然过期。顺带一提,sendToMAC 桥接学习表同样有独立的 10 分钟过期机制(expireMACs())。
fastdp 加密实现:ESP 传输模式 + AES-GCM + XFRM
当连接启用加密时(对应 docs/fastdp-crypto.md 及 router/fastdp.go#L655-L706 中 Confirm() 的 IPsec 初始化逻辑),fastdp 的 VXLAN 流量会被完整加密,带来以下安全属性:数据机密性、数据源认证、完整性、防重放,以及受限的流量流机密性(因为整个 VXLAN 包都被加密)。
协议与密钥派生
- 采用 ESP 协议(RFC 2406)传输模式,每个包使用 AES-GCM(RFC 4106) 加密,32 字节 key + 4 字节 salt;
- 每个连接方向使用不同的 AES-GCM key/salt,通过 HKDF(RFC 5869) 派生:
SAin[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceIn, info=localPeerName)
SAout[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceOut, info=remotePeerName)
其中 sessionKey 由 mesh 库在控制面连接建立时协商产生;nonceIn/nonceOut 是经加密控制面通道交换的 32 字节随机数。
SPI 与连接建立
方向性的安全连接以 SPI 标识。内核要求"SPI + 目的 IP"在安全关联中唯一,为避免冲突,出站 SPI 由远端 peer 生成。连接建立流程为:两端各自 Confirm() → 安装 iptables 阻塞规则、生成随机 nonce、派生出站 key/salt、分配 SPI、创建 SA,并通过 InitSARemote 控制消息(FastDatapathCryptoInitSARemote)交换 SPI 与 nonce;收到对端消息后补齐入站 SA 与 SP,并安装标记规则(具体消息交互见 router/fastdp.go#L858-L918 的 ControlMessage / handleCryptoInitSARemote)。
iptables 标记与 XFRM
实现基于内核 XFRM 包转换框架。由于内核 VXLAN 驱动不会在 IP flow descriptor 中设置隧道目的端口,XFRM 策略(SPout)无法按端口匹配——无法只加密 peer 间的隧道流量。解决思路是:用 iptables 给这类出站包打标记,并在策略选择器中设置相同的标记。标记值的选择有讲究:Kubernetes 默认使用 1<<14、1<<15,weave 选用 1<<17(0x20000) 以避免干扰;该方案同时要求内核至少 4.2 版本。相关规则(实现于 net/ipsec/ipsec.go):
iptables -t mangle -A OUTPUT -j WEAVE-IPSEC-OUT
iptables -t mangle -A WEAVE-IPSEC-OUT -s ${LOCAL_PEER_IP} -d ${REMOTE_PEER_IP} \
-p udp --dport ${TUNNEL_PORT} -j WEAVE-IPSEC-OUT-MARK
iptables -t mangle -A WEAVE-IPSEC-OUT-MARK --set-xmark ${MARK} -j MARK
由于 Linux 未实现 IP Security Levels,还需额外规则防止"已建立安全连接的 peer 之间意外发出未加密流量":入站方向为每个 ESP 包打标记并丢弃未标记的隧道流量;出站方向丢弃带标记但不匹配任何 SPout 的流量。
此外,为防 SeqNo 循环导致重放攻击,使用 64 位扩展序列号(ESN)。MTU 计算方面,除 VXLAN 开销外还需计入 34–37 字节的 ESP 开销(SPI 4 + Sequence Number 4 + ESP IV 8 + Pad Length 1 + NextHeader 1 + ICV 16 + 0–3 字节填充),这正对应前文 1376 字节默认 MTU 与 GCE 1460 字节之间约 84 字节加密 VXLAN 开销的由来。
运维实践:启用、禁用与查看连接模式
fastdp 的使用完全无需预先配置——使用 Weave Net 1.2 及以上版本即自动启用。以下是几个关键运维要点(详见 site/tasks/manage/fastdp.md):
禁用 fastdp:设置环境变量 WEAVE_NO_FASTDP 后 weave launch:
$ WEAVE_NO_FASTDP=true weave launch
该变量在 weave 启动脚本中会转换为 --no-fastdp 参数(prog/weaver/main.go#L206 定义的 flag),最终在 net/bridge.go#L226-L235 的 configuredBridgeType() 中使 NoFastdp 生效、改用普通 bridge。集成测试 test/190_no_fastdp_2_test.sh 与 test/191_create_bridge_2_test.sh 即用于验证该场景。
保证 fastdp 可用(避免回退 Sleeve):
- 避免 NAT 设备介入;
- 放行 UDP 6784 端口(VXLAN 隧道流量,即 weave 主端口 6783 + 1);
- 确保
WEAVE_MTU与中间网络的 MTU 匹配。
查看连接使用的 Overlay:weave status connections 会逐连接显示转发模式:
$ weave status connections
<-192.168.122.25:43889 established fastdp a6:66:4f:a5:8a:11(ubuntu1204)
$ weave status connections
<- 192.168.122.25:54782 established sleeve 8a:50:4c:23:11:ae(ubuntu1204)
显示 fastdp 表示该连接走快速数据路径;否则显示 sleeve 表示回退到用户态封装。由于选择是逐连接自动做出的,网络中两种隧道可能并存。
加密注意:fastdp 加密采用 ESP 封装,部分网络(如 GCP 默认)会拒绝 ESP 包,需在防火墙上放行 ESP 流量。关于加密更完整的原理可参考 site/concepts/encryption-implementation.md,概念级讲解见 site/concepts/fastdp-how-it-works.md。
小结
fastdp 是 Weave Net 将 Overlay 数据面"下沉内核"的代表作:用 OVS datapath 的流表机制取代用户态逐包转发,以 12 位短 peer ID 巧妙填充 VXLAN VNI,用全零 MAC 心跳帧 + TCP 控制通道完成存活探测与 PMTU 感知,再配以事件驱动与周期性的双重流失效机制保证状态一致。结合 IPsec/XFRM 加密与 WEAVE_MTU、WEAVE_NO_FASTDP 等易用配置,它在保持"零配置自动最优选择"体验的同时,为跨主机容器网络提供了更低开销的数据通路。深入上述各机制的完整实现,可继续阅读 router/fastdp.go(约 1400 行的核心实现)、router/overlay_switch.go(选择算法)与 net/ipsec/ipsec.go(加密落地)。

