惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
C
Cisco Blogs
博客园 - 叶小钗
P
Privacy International News Feed
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
IT之家
IT之家
博客园 - 聂微东
Know Your Adversary
Know Your Adversary
Help Net Security
Help Net Security
罗磊的独立博客
I
Intezer
S
Schneier on Security
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
Cisco Talos Blog
Cisco Talos Blog
宝玉的分享
宝玉的分享
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Webroot Blog
Webroot Blog
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
P
Privacy & Cybersecurity Law Blog
T
The Exploit Database - CXSecurity.com
PCI Perspectives
PCI Perspectives
Security Latest
Security Latest
H
Heimdal Security Blog
S
Secure Thoughts
Hacker News: Ask HN
Hacker News: Ask HN
Y
Y Combinator Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Microsoft Security Blog
Microsoft Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
SecWiki News
SecWiki News
The GitHub Blog
The GitHub Blog
A
Arctic Wolf
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

C0reFast记事本

一个virtio-blk设备IO大小的问题 使用单台高配服务器(部署你的业务) 一个Solidigm P41 Plus的冷数据问题 测试ARP/ND双发效果的小工具 “无限”套娃,在WSL的Docker中使用YOLOv11做目标检测 在三条指令内实现闰年判断 一个中断关闭时间太长导致的网络延迟问题 一个Linux新老内核对于硬件中断的统计差异 IPIP隧道导致的DPDK收包RSS队列不均匀问题 在KVM虚拟机中开启TSC作为时钟源 x86平台的TSC(TIME-STAMP COUNTER) 一个UFO引发的惨案 在RHEL系统中快速抓取火焰图 反序列化AWS/阿里云样式的基于Query的API请求 DIY一个8盘位全闪NAS 分享一个记录LSI RAID卡芯片和各个OEM对应卡型号列表的神贴 在移动硬盘盒上启用SSD的Trim功能 SPDK的“Reduce”块压缩算法 实现一个Kubernetes的身份认证代理服务
硬件Bonding卸载场景下的ARP/ND双发
陈孚 · 2025-07-18 · via C0reFast记事本

大约在2019年的时候,公司的服务器接入网络架构开始向双上联去堆叠方向迁移,相比于之前老的接入网络而言,新的网络架构在各方面的提升都非常明显,尤其是在带宽利用率和冗余性方面,关于网络架构的部分,这里暂时就不多做介绍了,具体的可以参考京东以及H3C的相关分享和文档:异构去堆叠 | 一种完美提升网络高可用SLA的方案 H3C数据中心交换机S-MLAG最佳实践

在新的网络架构中,我们的方案是通过ARP转主机路由方式来实现网络层面的负载均衡和高可用的,这个方案有个依赖,需要主机实现ARP/ND相关协议包的双发。

为什么会有这个双发的需求呢?因为在我们选择的去堆叠方案中,主机的网卡是通过Bonding的方式来实现双上联的,要想实现对负载均衡和高可用的同时支持,Bonding的流量分载只能是基于hash的模式,这会带来一个问题:对于Host侧的ARP请求/响应报文,只会发送给某一台交换机,假设Host连接了LeafA和leafB两台交换机,当LeafA交换机的ARP表项即将过期,需要发送ARP请求探测Host状态时,Host回复的响应报文可能因为hash原因回复给LeafB了,这样一来,LeafA就无法及时获取到Host的最新状态信息,从而影响到整个网络的稳定性和可靠性。

因为上面的这个原因,Host侧内核的Bonding模块必须作出相应的调整,将所有ARP的请求和响应报文,都进行复制,并在所有的子接口上进行发送。当然实现的方法并不麻烦,具体的可以参考龙蜥社区的实现anolis: bond: broadcast ARP or ND messages to all slaves

至此就基本解决了物理服务器层面对新接入网络架构融合的问题。

虚拟化的网卡Bonding

作为第一个接入新网络架构的虚拟化平台,只解决物理服务器层面的问题还不够,因为虚拟化的核心产品:VM的网络也需要解决。对于虚拟化业务的网卡,选择的都是Mellanox(现NVIDIA)的产品,不得不说,Mellanox的网卡对于虚拟化的场景来说,还是非常友好的,相比于普通的网卡而言,支持很多虚拟化相关的Offload特性,完全可以称得上是SmartNIC。

对于Mellanox的网卡,有个比较重要的特性是OVS Offload Using ASAP² Direct,基于这个特性,可以将虚拟化网络常见的封装等等都卸载到硬件,极大的提升网络的性能,这个特性里还有一个功能SR-IOV VF LAG,可以将Bonding的功能卸载到硬件,这两个特性,就是虚拟化平台最需要的能力。

对于大部分公有云的场景,存在VPC的概念,不同用户的VM之间有租户的隔离,VM与VM之间的通信,会通过overlay网络通信,针对这种场景,ASAP²+SR-IOV VF LAG的功能可以很好的满足需求。VM的流量通过硬件卸载,Bonding通过硬件卸载,VM感知不到Bonding的存在,但是却可以享受到Bonding带来的高可用和负载均衡。与此同时,因为VM所有的流量都会额外封装一层隧道,所以对于交换机来说,只能看到宿主机的隧道端点IP地址,因此,对于类似的场景来说,只需要针对宿主机的IP进行ARP/ND双发即可。而这个需求,通过上面的patch就已经可以实现了。

Underlay网络的ARP双发

我们的场景和公有云有些不同,因为我们是一个私有云,并没有VPC的概念,所有的VM也是直接通过underlay网络通信的,这个网络模型,其实有点像早期公有云的经典网络。相比overlay网络而言,underlay网络的最大好处就是简单,所有VM的流量,只需要额外打上一个VLAN TAG即可,整个网络是扁平的,可以规避很多overlay的问题。并且作为一个私有云,所有的用户就是我们自己,也没有像公有云那样的租户隔离需求,因此使用这个模型是很自然的。

但是这也带来了一个原本不存在的问题:因为VM的流量是直接通过underlay网络通信的,所以VM的ARP/ND请求也需要“双发”了。一个可行的办法是把网络的结构暴露给VM,在VM里也打上bonding,然后就可以复用上面的patch,和物理服务器一样实现ARP/ND双发。很显然这不是想要的,既然提供了VM服务,就应该给用户更好的体验,尽可能隔离掉底层的这些细节。

这时候SR-IOV VF LAG的功能就派上用场了,SR-IOV VF LAG的功能可以将Bonding的功能卸载到硬件,这样一来,VM就感知不到Bonding的存在了。但是用上VF LAG之后,VM的流量都会被卸载到硬件里,就无法直接在网络层面实现ARP/ND双发了,因为硬件卸载的Bonding并不支持ARP/ND双发。当然修改网卡的固件也许能够解决这个问题,但定制固件的方案在时间和成本上都不太可行。

我们先看看在VF LAG的场景下,网卡硬件是如何转发流量的。在ASAP²的场景下,网卡工作在一个叫做“switchdev”的模式下。在这个模式下,网卡化身为一个交换机,这个交换机,可以根据“流表”来转发流量。这个流表是由OVS来管理的,当网卡中一条流表规则也没有的时候,网卡默认会把网络包原封不动的转发给OVS,靠OVS实现流表的学习和转发,一旦OVS学习到流表之后,再通过Linux的TC flower或者DPDK的rte_flow下发给网卡,当有后续的流量可以匹配到对应的流表规则,网卡会直接根据流表规则转发流量,不再把流量转发给OVS。

那流表长啥样?下面是一个线上实际的OVS流表例子和一些解释:

# 00:11:22:33:44:01是交换机(网关)的MAC地址,port(1)是交换机侧在OVS里的代表口
# 52:11:22:33:44:55是VM的MAC地址,port(2)是VM侧在OVS里的代表口

# 交换机发送的ARP请求,将VLAN TAG去掉,转发给VM
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=0),encap(eth_type(0x0806)), packets:7, bytes:448, used:0.067s, actions:pop_vlan,2
# VM发送的ARP请求,打上VLAN TAG,转发给交换机
recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x0806), packets:2, bytes:104, used:0.081s, actions:push_vlan(vid=1000,pcp=0),1
# 交换机发送的IPv4数据包(eth_type 0x0800是IPv4),将VLAN TAG去掉,转发给VM
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=0),encap(eth_type(0x0800)), packets:22772, bytes:4038655, used:2.350s, actions:pop_vlan,2
# VM发送的IPv4数据包,打上VLAN TAG,转发给交换机
recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x0800), packets:26714, bytes:3151911, used:0.300s, actions:push_vlan(vid=1000,pcp=0),1
# 交换机发送的IPv6数据包(eth_type 0x86dd是IPv6),将VLAN TAG去掉,转发给VM
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=0),encap(eth_type(0x86dd)), packets:111019, bytes:140906750, used:1.490s, actions:pop_vlan,2
# VM发送的IPv6数据包,打上VLAN TAG,转发给交换机
recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x86dd), packets:96546, bytes:8451184, used:1.490s, actions:push_vlan(vid=1000,pcp=0),1

可以看到,因为这几条流表的存在,交换机和VM之间的流量几乎都可以被匹配上,直接在硬件中进行转发。

由于ARP是作为独立的一个EtherType 0x0806进行传输的,仔细观察流表就不难发现ARP的流量有两条单独的流表规则,默认情况下,ARP的流表也会被写入到硬件中,从而实现硬件的offload加速,而我们需要进行ARP的双发,那第一步肯定是不希望ARP的流表被写入到硬件中,这样一来,ARP的流量会被硬件上送到OVS,由OVS来处理,刚好一个关键点是在流表的卸载方式选择上,我们使用了OVS-Kernel的方式,OVS处理的流量最终会通过内核进行转发,这样一来,ARP相关的报文自然而然的进入到了内核Bonding模块处理,而双发的逻辑也就起了作用。

那理论上分析可行,剩下的就是需要修改一下OVS的代码了,实现起来倒是也不困难,OVS针对硬件流表的下发,都放在lib/netdev-offload-tc.c这个文件中。所有的流表下发逻辑在netdev_tc_flow_put函数中,因此只需要在这个函数中添加一个判断,如果是ARP的流表,直接返回EOPNOTSUPP即可。

Underlay网络的ND双发

相比IPv4的ARP来说,IPv6的ND双发就有些麻烦了,一方面,用于IPv6邻居发现的Neighbor Discovery(ND)协议是通过ICMPv6来实现的,另一方面,ICMPv6的报文并没有单独的EtherType,而是和IPv6数据包一起共用了0x86DD这个EtherType,从上面的流表也可以看出来,对于IPv6的流量,默认情况下只会有一组流表规则,根本区分不出业务的TCP/UDP流量和ICMPv6的数据报文,更区分不出ICMPv6中更细的ND相关的报文。

但是理论上,只要类似ARP那样,让硬件匹配不到,或者让硬件匹配到,然后上送给OVS处理,就可以类似ARP那样实现ND的双发了。

通过NVIDIA提供的文档Classification Fields (Matches),可以看到网卡是支持匹配IPv4/IPv6的TCP/UDP/ICMP/ICMPv6这几个Protocol的,既然可以匹配ICMPv6,那就尝试手动添加一条流表,看看是否可以匹配上。

# 添加一条ICMPv6的流表规则,匹配ICMPv6
# ovs-ofctl add-flow ovs-sriov "table=0,priority=200,icmp6 actions=normal"
# 再尝试dump当前学习到的流表
# ovs-appctl dpctl/dump-flows|grep 0x86dd
recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x86dd),ipv6(proto=58), packets:1, bytes:1, used:0s, actions:push_vlan(vid=1000,pcp=0),1
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=0),encap(eth_type(0x86dd),ipv6(proto=58)), packets:1, bytes:1, used:0s, actions:pop_vlan,2

recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x86dd),ipv6(proto=17), packets:3, bytes:210, used:1.560s, actions:push_vlan(vid=1000,pcp=0),1
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=0),encap(eth_type(0x86dd),ipv6(proto=17)), packets:0, bytes:0, used:1.560s, actions:pop_vlan,2

recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x86dd),ipv6(proto=6), packets:49, bytes:3923, used:2.850s, actions:push_vlan(vid=1000,pcp=0),1
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=0),encap(eth_type(0x86dd),ipv6(proto=6)), packets:411, bytes:605503, used:2.850s, actions:pop_vlan,2

可以看到,IPv6的TCP(proto=6)、UDP(proto=17)和ICMPv6(proto=58)都被单独匹配出来了,现在的流表规则,已经类似ARP的流表规则了,那接下来理论上参考ARP的修改,继续让ICMPv6的流表不卸载到硬件,就可以实现双发了。

但相比于ARP来说,ICMPv6本身包含的功能还是比较多的,这样修改的话,除了ND相关的报文,IPv6 Ping相关的报文也会被上送OVS处理了,总体来说还是会影响一些效率,是否可以继续拆分呢?比如只把ND相关的报文拆成单独流表不下发,剩下的Ping报文依然走硬件转发。OVS是支持这样的配置的,可以尝试一下,稍微修改一下下发的流表:

# 添加一条ICMPv6的流表规则,匹配ICMPv6的ND报文,icmp_type=135是ND协议中的NS包,也是我们需要双发的包
# ovs-ofctl add-flow ovs-sriov "table=0,priority=200,icmp6,icmp_type=135 actions=normal"
# ovs-appctl dpctl/dump-flows|grep 0x86dd
recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x86dd),ipv6(proto=58),icmpv6(type=135), packets:0, bytes:0, used:0s, actions:push_vlan(vid=1000,pcp=0),1
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=6),encap(eth_type(0x86dd),ipv6(proto=58),icmpv6(type=136/0xfc)), packets:0, bytes:0, used:0s, actions:pop_vlan,2

recirc_id(0),in_port(2),eth(src=52:11:22:33:44:55,dst=00:11:22:33:44:01),eth_type(0x86dd),ipv6(proto=58),icmpv6(type=type=128/0xfc), packets:0, bytes:0, used:0s, actions:push_vlan(vid=1000,pcp=0),1
recirc_id(0),in_port(1),eth(src=00:11:22:33:44:01,dst=52:11:22:33:44:55),eth_type(0x8100),vlan(vid=1000,pcp=6),encap(eth_type(0x86dd),ipv6(proto=58),icmpv6(type=type=129/0xfc)), packets:0, bytes:0, used:0s, actions:pop_vlan,2

可以看到,NS(type=135)和NA(type=136)的流表规则已经被拆分出来了,同样的Ping的Echo Request(type=128)和Echo Reply(type=129)的流表规则也被拆分出来了,理论上也可以如法炮制,只让NS报文相关的流表不下发到硬件,这样一来,ICMPv6的双发就可以实现了。但这里有一个问题,就是网卡其实不支持匹配更细致的报文了,直接看一下下发到硬件中的流表和对应的状态:

# 由于OVS-Kernel使用TC下发流表规则到网卡驱动,所以可以通过tc命令查询流表和对应的状态
# tc filter show dev vf0 ingress
filter protocol ipv6 pref 3 flower chain 0
filter protocol ipv6 pref 3 flower chain 0 handle 0x1
  dst_mac 00:11:22:33:44:01
  src_mac 52:11:22:33:44:55
  eth_type ipv6
  ip_proto icmpv6
  icmp_type 135
  not_in_hw
        action order 1: vlan  push id 1000 protocol 802.1Q priority 0 pipe
         index 8 ref 1 bind 1
        no_percpu

        action order 2: mirred (Egress Redirect to device bond0) stolen
        index 11 ref 1 bind 1
        cookie b332255e9d440e02192dbb9b04d20332
        no_percpu

可以发现,虽然OVS生成了NS报文的流表,并且也成功的转成TC规则并下发,但dump出来的规则里not_in_hw表示这条规则并没有下发到硬件中,网卡在这种场景下不支持匹配ICMPv6的type字段,所以即使像之前一样修改OVS不下发该流表,最终网卡的行为都是一致的。

虽然没办法精确的将NS报文从硬件中拾取出来,但将ICMPv6报文全部上送软件处理,也算是可以接受的方案了,即使有些不完美,考虑到大部分业务的流量还是走的TCP/UDP,对业务的影响应该微乎其微。

总结

虽然最终没有达到最完美的状态,但最终还是实现了在硬件Bonding卸载的场景下,ARP和ND的双发。经过这几年的实践,这个网络方案在我们的私有云上运行的非常稳定,一方面,因为场景和公有云的差异,选择了一个比较简单的Underlay+OVS-Kernel卸载的方案,而不是基于DPDK的方案,另一方面,正是基于这样的选择,带来了额外的VM双发需求,只能说环环相扣,处处充满妥协了😀。

那么未来呢,未来的虚拟化网络是属于DPU(或者说IPU)的,借助DPU的帮助,终于可以在固件层面做一些想做的事了,类似的问题也就不是问题了,未来值得期待。