news 2026/9/15 12:18:46

Linux内核VXLAN收发包流程详解:从FDB查表到MTU调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核VXLAN收发包流程详解:从FDB查表到MTU调优

做Linux网络内核调试的人,几乎都绕不开VXLAN。K8s里Flannel的VXLAN后端、OpenStack里的overlay网络、各种容器网络方案,喊的都是同一个东西:用UDP隧道把二层帧送到远端。很多人对“VXLAN原理”聊得头头是道,但一落到内核里就会懵——同样是vxlan0,为什么tcpdump -i vxlan0能看到包,对端VM就是不通?为什么MTU要设1450而不是1500?这些问题的答案,都藏在Linux内核VXLAN的收发包流程里。

这篇文章我以内核5.15/6.1左右的源码主线来拆,代码路径主要在drivers/net/vxlan/vxlan_core.c。适合正在看内核网络源码的人、被overlay网络问题折磨的运维、以及刚接触云网络想搞懂数据面的同学。我会把“发包查哪张表、收包从哪个回调进来、两张FDB怎么配合、MTU和offload怎么影响数据面”一次讲透。

1. VXLAN在内核里的位置:一台用软件做出来的远端二层交换机

1.1 VXLAN本质:MAC-in-UDP

VXLAN做的事情很简单,把原始的二层以太网帧当成一个整体,外面套上VXLAN头、UDP头、IP头,然后从三层网络送出去。对端收到后剥掉外层头,把原来的二层帧重新注入到本地的虚拟网络中。

这个“远程二层网络”里有两个核心概念:

  • VTEP(VXLAN Tunnel Endpoint):隧道端点,通常就是运行vxlan模块的Linux主机或交换机,负责封装和解封装。
  • VNI(VXLAN Network Identifier):24比特的网络标识,用来区分不同的二层网络。VLAN只有12比特,VXLAN的VNI能支持更多租户/网络隔离。

VXLAN头非常标准,8个字节,其中最重要的就是VNI:

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |R|R|R|R|I|R|R|R| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | VXLAN Network Identifier (VNI) | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

I标志位是1表示这是一个有效的VXLAN报文;VNI就是后面24位。Linux内核在做收包的时候,第一件事就是看这个VNI能不能和本地vxlan设备对上,对不上直接丢。

1.2 内核vxlan模块在数据面上的角色

从内核视角看,vxlan设备就是一张虚拟网卡。它注册了自己的net_device_ops,实现了ndo_start_xmit作为发包入口;收包则是通过UDP封装回调从协议栈拿到报文后,注入到这张虚拟网卡上。

但它和普通网卡最大的区别是:普通网卡发出去的包直接走物理链路,而vxlan设备发出去的包是先查一张“MAC地址到远端VTEP IP”的映射表,然后重新封装成UDP/IP包,交给本机的三层的路由栈发出去。

所以VXLAN收发包流程本质上是两级转发:

  1. 本地一级:内层MAC地址域,决定这个二层帧该进哪个本地端口或虚拟设备。
  2. 远端一级:外层IP地址域,决定这个封装后的UDP包该从哪个underlay路由送到哪个VTEP。

理解了这个两级结构,后面看代码就不会乱。

2. 发包流程:内层帧如何在vxlan_xmit里被查表、封装、甩给路由栈

2.1 入口:bridge把内层帧交给vxlan_xmit

典型场景里,vxlan0端口是挂在Linux bridge下的。VM发出来的帧先进bridge,bridge查自己的FDB,发现目的MAC对应端口是vxlan0,于是把帧从vxlan0这个端口发出去。

具体路径是这样的:

VM/veth -> br0 -> bridge FDB -> dev_queue_xmit(br0 -> vxlan0) -> __dev_queue_xmit -> dev_hard_start_xmit -> vxlan_xmit

vxlan_xmit就是vxlan设备的ndo_start_xmit。到这一步,内层以太网头仍然原封不动留在skb里,eth_hdr(skb)可以直接读到目的MAC。

如果你的环境没有bridge,直接给vxlan0配了IP,流程也一样。比如从本机ping一个远端内网IP,协议栈会先在vxlan0上发ARP请求。ARP请求是一个广播帧,照样会走vxlan_xmit被封进UDP隧道发出去。所以记住一个原则:vxlan设备只认识二层帧,所有发给它的包都是以太网帧。

2.2 vxlan_xmit_one:查FDB、选隧道目的、封装

vxlan_xmit本身做的事情不多,核心逻辑在vxlan_xmit_one里。我按源码顺序拆一下:

  1. 取出VNI。如果设备配置了collect metadata,VNI从skb_dst(skb)->tun_info里拿;否则用创建vxlan设备时指定的id
  2. 用内层目的MAC查vxlan设备的FDB,函数是vxlan_fdb_find_rcu(vxlan, eth_hdr(skb)->h_dest, vni)
  3. FDB命中,拿到对应的struct vxlan_rdst,里面有远端IP、远端端口、远端VNI。FDB没命中,就用设备创建时的默认remote或组播地址。
  4. 确认有足够的headroom,skb_cow_head避免改写skb时复制整个包。
  5. 调用vxlan_xmit_one内部逻辑构建外层头,最终通过udp_tunnel_xmit_skb把外层IP/UDP头补上,并走ip_local_out发出。

这里面最容易踩坑的是第3步。很多人以为vxlan的FDB和bridge的FDB是一张表,不是。vxlan设备的FDB解决的是“内层MAC应该封装到哪个远端VTEP”,bridge的FDB解决的是“本地VM的帧应该从哪个本地端口出去”。两张表在一条链路里是串联关系。

udp_tunnel_xmit_skb这个函数很关键。它一次性完成:

  • 选择外层源IP和目的IP(目的IP就是你查FDB得到的远端VTEP地址)
  • 填UDP头,目的端口默认4789
  • 填外层IP头,计算校验和
  • 通过路由栈发出

如果vxlan设备有多个远端IP(比如FDB里一个MAC对应多个VTEP),内核会逐个调用发送路径,相当于把同一份内层帧复制多份,分别封装发往不同VTEP。

2.3 为什么UDP源端口要用内层五元组哈希

VXLAN用UDP端口有个好处:源端口可以随便变。Linux内核在封装时不会傻傻地用固定源端口,而是用内层报文的五元组哈希出一个源端口值,函数是vxlan_src_port

这么做的目的是为了underlay的负载均衡。物理网络上的交换机一般会基于五元组做ECMP哈希。如果所有VXLAN流都从同一个UDP源端口出去,那它们在外层看来就是同一条流,ECMP没法拆开,很容易把流量打到一个劣化链路或一个CPU队列上。源端口按内层哈希变化后,外层五元组就不同了,underlay路由器才能把不同租户的流量散到多个路径上。

在物理网卡支持RSS的情况下,这个哈希还会影响收包端的CPU分发。所以遇到VXLAN吞吐打不满、单核CPU被打爆的时候,先查一下UDP源端口分布和网卡RSS队列配置,比盲目调内核参数管用。

发包侧的调试命令也简单,抓外层包就能看到VXLAN封装长什么样:

tcpdump -ni eth0 udp port 4789 -vv

你会看到类似这样的输出:

IP 192.168.1.10.45871 > 192.168.1.2.4789: VXLAN, flags [I] (0x08), vni 42 00:ff:aa:bb:cc:01 > ff:ff:ff:ff:ff:ff, ethertype ARP

外层是192.168.1.10 -> 192.168.1.2的UDP包,里面装的是一个完整的ARP广播帧。这就是VXLAN封装最直观的样子。

3. 收包流程:UDP 4789端口上的包是怎么被认领并还原理的

3.1 UDP socket上的encap_rcv回调

VXLAN的收包并不是协议栈把UDP数据送到某个用户态进程,而是直接在内核UDP层被截获。

vxlan模块在创建UDP socket时,会调用setup_udp_tunnel_sock,把这个socket的encap_rcv回调指向vxlan_rcv。当物理网卡上收到目的端口为4789的UDP包,经过IP层进入udp_rcv,再到udp_queue_rcv_skb时,内核会检查这个UDP socket是否设置了encap_rcv。设置了就直接调它,而不是走正常的数据报socket接收逻辑。

这和其他隧道协议如GENEVE、ERSpan是同一个套路。用一句话说:VXLAN收包不是“内核把包交给了用户态应用”,而是“内核在UDP层把包截留下来,转交给vxlan驱动处理”。

3.2 vxlan_rcv逐项检查和MAC学习

vxlan_rcv拿到skb后,做几件很机械的事:

  1. 检查外层UDP长度、VXLAN头长度。
  2. 检查VXLAN头的flags和VNI,VNI是从vxlan_hdr(skb)->vx_vni里取的。
  3. vxlan_lookup按socket和VNI找到本机对应的struct vxlan_dev。找不到就丢包,因为本地没有租户报这个VNI。
  4. 如果可以学习,调用vxlan_snoop做源MAC学习:记录“内层源MAC是从哪个远端IP学习到的”,更新vxlan FDB。
  5. 把skb的接收设备改成vxlan设备,skb->dev = vxlan->dev,然后调用eth_type_trans设置好协议类型。
  6. 交给gro_cells_receivenetif_receive_skb,让报文进入本机网络栈,或者被bridge收走。

第4步的vxlan_snoop是VXLAN能实现“MAC自动学习”的核心。对端主机上VM A发出的帧到了你这台VTEP后,你的vxlan设备会把“VM A的MAC地址对应远端VTEP IP 192.168.1.10”这个信息记进自己的FDB。以后再往VM A发帧,就不需要广播泛洪了,直接封装发给192.168.1.10即可。

如果关闭了学习功能(创建vxlan时加了nolearning标志),那vxlan FDB基本靠人工维护或全部泛洪,多节点场景很容易出现“查不到FDB所以封不到正确VTEP”的问题。

3.3 从gro_cells到bridge/协议栈

gro_cells_receive是我很想强调的一点。早期vxlan收包是直接netif_rx上交的,性能一般。现在驱动用每个vxlan设备自己的gro_cells结构,把从隧道解出来的内层帧先交给本设备的NAPI调度,通过napi_gro_receive做一次GRO合并,再送到netif_receive_skb

这带来的好处是:如果一条TCP流拆成了很多个小VXLAN包到达,内核可以在隧道层就把它们合并成大skb再往上走,减少bridge和协议栈的处理次数。很多人在同一套underlay下,发现老内核的VXLAN性能差得离谱,部分原因就是缺少GRO路径。

报文到达netif_receive_skb之后,如果vxlan0是bridge的端口,那这个内层帧就会被bridge接收,bridge会学源MAC在vxlan0上,再查目的MAC决定送到哪个本地端口。如果目的MAC不在本地,bridge又会把帧丢回vxlan0,vxlan驱动再按自己的FDB封装发往下一个VTEP。这就是“VXLAN交换机”的完整行为。

4. 翻车点:很多人把bridge的FDB和vxlan设备的FDB混成一张表

4.1 两张表的职责对比

我用一张表把这几年被问得最多的问题说清楚。

表名属于谁查询键查到的内容出现环节
bridge FDBLinux bridge内层目的MAC本地端口(veth/vxlan0)VM帧第一次到达br0时
vxlan FDBvxlan设备内层目的MAC远端VTEP IP/UDP端口vxlan_xmit内层封装的查表

注意看,两张表的key都是内层目的MAC,但value完全不同。一个告诉你在本地该走哪个网卡端口,另一个告诉你在隧道里该封装给哪个远端IP。

4.2 一次完整的东西向流量是怎么串起两张表的

假设两套宿主机,每套都有br0、VM、vxlan0:

VM A(00:00:00:00:00:01) -> br0

VM A发往VM B(MAC为00:00:00:00:00:02)时:

  1. br0查bridge FDB,发现00:00:00:00:00:02对应端口是vxlan0,帧被送到vxlan设备的vxlan_xmit
  2. vxlan_xmit查vxlan设备自己的FDB,发现00:00:00:00:00:02对应的远端IP是192.168.1.2,于是按这个IP封装成UDP包发出去。
  3. 对端vxlan_rcv解封装,把内层帧交给vxlan0。
  4. 对端br0收到从vxlan0上来的帧,查bridge FDB,发现00:00:00:00:00:02对应veth端口,于是把帧送到VM B。

中间任何一张表缺了对应entry,流量就会降级成泛洪。在没有组播的云环境里,某些平台还会借助控制面下发FDB,避免未知单播全部打到默认remote。

实际操作中,你可以用bridge命令同时看到这两张表:

# 查看vxlan设备的FDB(MAC -> 远端VTEP) bridge fdb show dev vxlan0 # 查看bridge的FDB(MAC -> bridge端口) bridge fdb show

如果你发现bridge fdb show dev vxlan0里有远端MAC,但bridge fdb show里查不到对应端口,那问题多半出在bridge这一层;反过来也一样。这个排查方向很管用。

5. 收发包流程里最容易出问题的三个隐藏环节:MTU、GRO/GSO、校验和

5.1 MTU计算:别把vxlan的50字节额外开销忘了

VXLAN封装会额外加上外层以太网头(14字节)、外层IP头(20字节)、UDP头(8字节)、VXLAN头(8字节),合计50字节。超过underlay MTU的包会被分片,而很多网络中间设备会直接丢弃分片包,表现就是“小包通、大包不通”。

如果underlay物理链路MTU是1500,vxlan0的MTU一般建议设成1450,给整条overlay路径留出余量。对于企业内部网络能开9000巨型帧的,vxlan0可以设成8950左右。

实际遇到问题时别光看配置,要实测:

# 从VM或宿主机上测 ping -M do -s 1450 <对端内网IP>

如果-s 1450通而-s 1472不通,基本就是MTU问题。TCP场景下还可能表现为“连接建立成功但传输卡死”,因为大的TCP段在隧道里被MTU卡住,又拿不到ICMP提示,导致MSS协商异常。VXLAN下建议把内层的MSS显式调小。

5.2 GSO/GRO与硬件offload:决定VXLAN跑得快不快

发送路径上的GSO(Generic Segmentation Offload),允许协议栈把很大的TCP数据块作为一个大skb交给vxlan驱动,vxlan驱动在封装完之后,再让网卡或内核拆成普通MTU大小的报文发出去。网卡支持tx-udp_tnl-segmentation时,大包分割可以下推到物理网卡,CPU占用会明显下降。

接收路径上的GRO在前面说过,是把多个小的隧道包合并成大skb。这两项开没开,对VXLAN转发性能影响极大。

排查时可以用:

ethtool -k eth0 | grep tunnel

重点关注tx-udp_tnl-segmentationrx-gro-list这类能力。如果网卡不支持,内核会在软件层面用skb_segment完成分割,CPU会上升,但功能正常。真正恐怖的是网卡宣称支持但驱动bug导致丢包,这种情况只能靠计数器对比来发现。

5.3 校验和:tcpdump里看到bad checksum不一定是坏包

VXLAN的外层UDP校验和,在IPv4下不是强制要求,但很多网卡驱动默认开着计算。由于硬件校验和offload的存在,tcpdump在网卡驱动把校验和字段填好之前抓包,经常显示bad udp checksum,这不一定代表网络上有坏包,可能只是抓包点早于硬件填充。

判断方法很简单:在收包端看ethtool -S里有没有rx_csum_offload_errors这类计数器,或者直接关掉网卡校验和卸载再抓一次。如果你在内层也开了类似vxlan硬件校验,记得确认driver版本是否匹配,否则会出现间歇性丢包。

开启和关闭vxlan设备本身的一些选项也有影响,比如:

ip link set vxlan0 type vxlan udpcsum ip link set vxlan0 type vxlan noudpcsum

IPv6 underlay场景,UDP校验和是强制的,别为了省CPU关掉。

6. 一次“对端vxlan0能收到包,VM还是不通”的完整排查思路

6.1 三个阶段抓包定位

我遇到最多的VXLAN问题不是“包完全不来”,而是“包到了对端vxlan0,但VM不通”。这种问题讲真比纯断网难搞,因为它涉及的是解封装之后的行为。

我的习惯是把抓包点分成三段:

  • underlay入口:tcpdump -ni eth0 udp port 4789
  • 解封装之后:tcpdump -ni vxlan0
  • 虚拟机内部:tcpdump -ni eth0

如果eth0上能看到VXLAN包,但对端vxlan0上没包,说明问题出在vxlan收包侧。这时候优先查:

  1. 两端VNI是否一致:ip -d link show vxlan0里的idremote
  2. 两端UDP端口是否一致:默认4789,自定义别对不上。
  3. vxlan设备是否处于UP状态、是否被拉进了正确的network namespace。
  4. 对端的VNI过滤是否因为collect metadata等原因导致查找失败。

如果vxlan0上能看到包,但VM还是不通,那问题已经在bridge/路由这一层了。

6.2 VNI、端口、FDB和rp_filter的排查顺序

当然,有些问题在FP层看不出来,我在实际排障时通常会按这个顺序往下走。

第一看FDB。在发送端执行:

bridge fdb show dev vxlan0

如果要到VM B的MAC没有对应远端IP,并且你处于多节点环境,而创建vxlan时用的是remote单播模式,那未知单播只会发往这一个默认remote。一旦流量目的不在这个VTEP后面,包就送不到正确宿主机。这种环境应该用组播、或者依赖控制面下发FDB。

第二看bridge的FDB:

bridge fdb show | grep vxlan0

如果目的MAC在发送端bridge里都缺,那说明bridge不知道这个MAC该从vxlan0走,可能会把帧继续从其他端口广播,甚至丢掉。

第三看rp_filter。多网卡、多VTEP环境下,VXLAN解封装后的内层源地址和物理入接口可能不一致,触发反向路径过滤,导致回包被丢。需要确认:

sysctl net.ipv4.conf.all.rp_filter sysctl net.ipv4.conf.<iface>.rp_filter

如果值不是0,且你的overlay流量路径确实是非对称的,可以针对overlay接口单独调成0。真遇到这个问题时,外面抓包看得到请求,但回包出不去,很容易被误判成对端丢包。

6.3 一些小经验

调VXLAN问题,我自己的习惯是把tcpdump落点分成underlay入口、vxlan0、VM侧三段,先定位到段,再回头看VNI、FDB、MTU、rp_filter。多数的“对端vxlan0能看到包但VM不通”,最后都是MTU或bridge FDB过期导致的。另外,看内核源码时不要一头扎进vxlan_xmit里不出来,先把收发包主链路跑通,再去追vxlan_rcv里的学习逻辑和vxlan_fdb_update,会比逐行啃源码高效得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 12:18:26

透明变电站数字孪生建设指南:六类公司技术路线与选型避坑全解读

这两年&#xff0c;只要和电力运维沾边的项目&#xff0c;讨论到最后基本都能落到一个词上&#xff1a;透明变电站。我自己的直观感受是&#xff0c;这个词已经从概念PPT里走了出来&#xff0c;变成越来越多电网单位、工业用户、EPC总包方真正立项掏钱的方向。可我接触过不少业…

作者头像 李华
网站建设 2026/9/15 12:18:00

Java+Python混合架构:企业级模型评估平台后端设计实践

简介&#xff1a;面向AI模型评估场景的后端设计源码&#xff0c;主体采用Java构建服务端核心框架&#xff0c;并加入Python脚本处理与模型评估相关的数据处理逻辑&#xff0c;适合后端开发工程师、AI平台研发人员以及高校实验平台建设者阅读与二次开发。压缩包共76个文件&#…

作者头像 李华
网站建设 2026/9/15 12:17:27

ROS-I simple_message协议深度解析:工业机器人实时通信核心

1. 项目概述&#xff1a;从一条“简单消息”看工业机器人通信的底层逻辑你有没有在调试ABB或KUKA机器人时&#xff0c;突然发现ROS节点发出去的指令像石沉大海&#xff1f;明明topic名称对得上&#xff0c;rostopic echo也显示数据在流动&#xff0c;但机械臂就是纹丝不动——最…

作者头像 李华
网站建设 2026/9/15 12:16:53

OpenHarmony高性能图像列表渲染优化实践

1. 项目背景与核心挑战在OpenHarmony生态中实现高性能图像列表渲染一直是个棘手问题。传统方案在加载网络图片时往往需要完整下载后才能获取尺寸信息&#xff0c;导致列表布局频繁重排&#xff0c;严重影响滚动流畅度。image_size_getter_http_input组件正是为解决这一痛点而生…

作者头像 李华
网站建设 2026/9/15 12:16:49

ARIA属性实战指南:从语义契约到键盘导航与状态同步

1. 这不是“加个标签”就完事的适配——无障碍到底在适配什么&#xff1f;无障碍适配这个词&#xff0c;最近在开发圈、设计圈甚至产品会上被提得越来越频繁。但说实话&#xff0c;我见过太多团队把“做了无障碍”当成一个交付 checklist 上的勾选项&#xff1a;加几个aria-lab…

作者头像 李华