Merge pull request #3029 from tikD1337/fix/qemu-tap-partial-csum

fix(qemu): open the adapter TAP with vnet_hdr=off so relay links carry finished checksums
This commit is contained in:
Jeremy Grossmann 2026-10-24 09:20:16 +02:00 committed by GitHub
commit dd19d96fbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 34 additions and 3 deletions

View File

@ -2715,8 +2715,14 @@ class QemuVM(KernelDatapathMixin, BaseNode):
tap = self._kernel_host_ifc(adapter_number) or self._tap_name(adapter_number) tap = self._kernel_host_ifc(adapter_number) or self._tap_name(adapter_number)
network_options.extend(["-device", f"{device_string},netdev=gns3-{adapter_number}"]) network_options.extend(["-device", f"{device_string},netdev=gns3-{adapter_number}"])
if self._tap_datapath or isinstance(nio, NIOTAP): if self._tap_datapath or isinstance(nio, NIOTAP):
# vnet_hdr=off: with QEMU's default virtio-net header the
# guest NIC offloads TCP/UDP checksums and TSO to the TAP.
# A kernel bridge finishes such frames, but the uBridge relay
# (cloud, NAT, ...) copies them off the TAP as they are, so
# receivers drop them. Without the header QEMU and the guest
# send finished frames, as with the 2.x socket netdev.
network_options.extend( network_options.extend(
["-netdev", f"tap,id=gns3-{adapter_number},ifname={tap},script=no,downscript=no"] ["-netdev", f"tap,id=gns3-{adapter_number},ifname={tap},script=no,downscript=no,vnet_hdr=off"]
) )
else: else:
# Legacy datapath (uBridge without the tap module): QEMU talks # Legacy datapath (uBridge without the tap module): QEMU talks

View File

@ -29,6 +29,7 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from gns3server.compute.nios.nio_tap import NIOTAP
from gns3server.compute.qemu import Qemu from gns3server.compute.qemu import Qemu
from gns3server.compute.qemu.qemu_error import QemuError from gns3server.compute.qemu.qemu_error import QemuError
from gns3server.compute.qemu.qemu_vm import QemuVM from gns3server.compute.qemu.qemu_vm import QemuVM
@ -143,11 +144,35 @@ async def test_network_options_use_the_tap_anchor(vm):
options = await vm._network_options() options = await vm._network_options()
assert f"tap,id=gns3-0,ifname={TAP0},script=no,downscript=no" in options assert f"tap,id=gns3-0,ifname={TAP0},script=no,downscript=no,vnet_hdr=off" in options
assert not any(option.startswith("socket,") for option in options) assert not any(option.startswith("socket,") for option in options)
assert not vm._local_udp_tunnels assert not vm._local_udp_tunnels
@pytest.mark.asyncio
async def test_network_options_turn_off_the_tap_vnet_header(vm):
"""
Every TAP netdev turns the virtio-net header off, the legacy NIOTAP one
included. With QEMU's default header the guest NIC hands its TCP/UDP
checksums (and TSO) to the TAP, and the uBridge relay copies those
frames off the TAP unfinished: receivers behind a cloud drop them.
"""
vm._tap_datapath = True
vm.adapters = 2
vm.adapter_type = "virtio-net-pci"
vm._kernel_taps[(0, 0)] = TAP0
vm._ethernet_adapters[1].add_nio(0, NIOTAP("tap-legacy"))
options = await vm._network_options()
netdevs = [options[i + 1] for i, option in enumerate(options) if option == "-netdev"]
assert netdevs == [
f"tap,id=gns3-0,ifname={TAP0},script=no,downscript=no,vnet_hdr=off",
"tap,id=gns3-1,ifname=tap-legacy,script=no,downscript=no,vnet_hdr=off",
]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_network_options_keep_the_legacy_socket_netdev(vm): async def test_network_options_keep_the_legacy_socket_netdev(vm):
""" """

View File

@ -11,7 +11,7 @@ binary, the real IOS CLI behind the server's telnet console), driven through
the controller REST API. QEMU is the node type whose guest leg *is* the the controller REST API. QEMU is the node type whose guest leg *is* the
anchor: uBridge creates one persistent TAP per adapter before launch and anchor: uBridge creates one persistent TAP per adapter before launch and
hands it to this user, and QEMU opens it by ifname as its netdev hands it to this user, and QEMU opens it by ifname as its netdev
(``-netdev tap,ifname=...,script=no,downscript=no``) — no userspace hop, no (``-netdev tap,ifname=...,script=no,downscript=no,vnet_hdr=off``) — no userspace hop, no
port bridge, the anchor is the NIC's host side. port bridge, the anchor is the NIC's host side.
* two QEMU nodes on one compute report ``kernel_datapath`` when the * two QEMU nodes on one compute report ``kernel_datapath`` when the