问题
虚拟机发送大 UDP 数据报时,接收端无法正常处理。
结论
在 MTU 为 1500 的 IPv4 链路中,UDP 负载超过 1472 字节后通常会触发 IP 分片。分片并不一定表示宿主机或网卡故障,但部分应用、隧道、VPN、负载均衡设备和防火墙不会正确重组分片,因此发送端看似成功,接收端却可能收不到完整数据报。
网卡还可能启用 UDP Segmentation Offload(USO)。USO 会把较大的 UDP 数据交给网卡或虚拟化后端分段;如果虚拟网卡、TAP、网桥和物理网卡的卸载能力不一致,可能出现与另一台虚拟机不同的行为。排查时应先确认真实 MTU、卸载状态和抓包结果,再决定是否关闭 USO。
推荐处理顺序
- 优先让应用把 UDP 负载控制在路径 MTU 以内。
- 对比正常和异常虚拟机的网卡型号、MTU 和卸载状态。
- 只在确认 USO 参与问题后,关闭目标虚拟机的 IPv4/IPv6 USO。
- 用宿主机和实际接收端分别抓包验证。
- 记录 CPU 变化;大 UDP 发送关闭 USO 后可能增加 CPU 开销。
操作步骤
1. 确认宿主机网卡和链路 MTU
将下列占位符替换为实际值:
<目标虚拟机ID>:出现问题的虚拟机编号。<对照虚拟机ID>:表现正常的虚拟机编号,可选。<网桥名>:通常是vmbr0,以当前配置为准。<TAP接口>:通常形如tap<目标虚拟机ID>i0。
在 PVE 宿主机执行:
bash
qm config <目标虚拟机ID> | grep '^net0'
qm config <对照虚拟机ID> | grep '^net0'
ip -d link show <网桥名>
ip -br link
ip link show <TAP接口>
重点记录:
- 虚拟机、TAP、网桥和物理出口的 MTU 是否一致。
- 网卡是否启用了
firewall=1、VLAN 或其他会改变路径的选项。
2. 在 Windows 中查看网卡卸载状态
确认已安装 QEMU Guest Agent 后,在目标 Windows 虚拟机的管理员 PowerShell 中执行:
powershell
$nic = Get-NetAdapter |
Where-Object InterfaceDescription -like 'Red Hat VirtIO Ethernet Adapter*' |
Where-Object Status -eq 'Up' |
Select-Object -First 1
if (-not $nic) {
throw '没有找到正在使用的 VirtIO 网卡,请先确认网卡型号和驱动。'
}
Get-NetIPInterface -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 |
Select-Object InterfaceAlias,NlMtu,ConnectionState
Get-NetAdapterUso -Name $nic.Name
Get-NetAdapterLso -Name $nic.Name
Get-NetAdapterChecksumOffload -Name $nic.Name
Get-NetAdapterAdvancedProperty -Name $nic.Name |
Where-Object DisplayName -match 'UDP|LSO|RSS|Checksum'
确认以下项目:
- IPv4 和 IPv6 的 UDP Segmentation Offload 是否为 Enabled。
- MTU 是否为 1500,或是否符合实际网络路径。
- RSS、TCP LSO 和校验和卸载状态。
- 目标虚拟机与正常虚拟机之间是否只有 USO 状态不同。
如果不是 VirtIO 网卡,以上 Red Hat VirtIO Ethernet Adapter 查询条件需要按实际驱动名称调整。
3. 只关闭 IPv4/IPv6 USO
确认 USO 是主要差异后,只关闭 USO,不要一次性关闭全部卸载功能:
powershell
Disable-NetAdapterUso -Name $nic.Name -IPv4 -IPv6 -Confirm:$false
网卡可能短暂重置,RDP 或其他网络连接会断开数秒。重新连接后检查:
powershell
Get-NetAdapterUso -Name $nic.Name
Get-NetIPInterface -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 |
Select-Object InterfaceAlias,NlMtu,ConnectionState
预期结果:IPv4/IPv6 USO 为关闭,MTU 没有被改变。不要同时修改网卡型号、IP 地址、网桥和其他卸载选项,否则无法判断实际原因。
4. 验证应用层是否仍发送超大 UDP 数据报
在 MTU 1500 的 IPv4 网络中:
text
最大 UDP 负载 ≈ 1500 - 20(IPv4 头)- 8(UDP 头)= 1472 字节
如果数据还要经过 VPN、隧道、VXLAN、容器网络或其他封装,实际安全值应更小。推荐应用层自行分片、重组,或把单个 UDP 负载控制在路径 MTU 以内。
5. 在宿主机抓包确认分片情况
把 <VM_IP>、<接收端IP> 和 <TAP接口> 替换为实际值:
bash
tcpdump -ni <TAP接口> 'src host <VM_IP> and dst host <接收端IP> and ip' -vv -c 20
如果要检查物理出口,可在物理网卡上重复抓包:
bash
tcpdump -ni <物理网卡> 'src host <VM_IP> and dst host <接收端IP> and ip' -vv -c 20
同时查看宿主机重组和 UDP 错误计数:
bash
nstat -az | grep -Ei 'IpReasm(Reqds|OKs|Fails)|Udp(InErrors|RcvbufErrors|SndbufErrors)'
测试大于 MTU 的 UDP 数据报时,抓包中可能出现多个 IPv4 分片。正常情况下,重组成功计数会增加,IpReasmFails 不应持续增加,UDP 错误计数也不应异常增长。
注意:关闭 USO 不会消除超过 MTU 的 IP 分片,它只是避免 VirtIO USO 参与这条发送路径。如果实际接收端不支持 IPv4 分片,最终仍需从发送应用、隧道 MTU 或接收端处理能力上解决。
6. 重启后复查
Windows 重启后再次执行:
powershell
Get-NetAdapterUso -Name $nic.Name
确认设置仍然保持。如果设置没有持久化,应检查 VirtIO 驱动版本、设备高级属性和 Windows 组策略,而不是继续重复修改 PVE 网卡配置。
7. 回滚
如果关闭 USO 后 CPU 占用明显增加、吞吐下降或业务出现新问题,可恢复原设置:
powershell
Enable-NetAdapterUso -Name $nic.Name -IPv4 -IPv6 -Confirm:$false
Get-NetAdapterUso -Name $nic.Name