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)
network_options.extend(["-device", f"{device_string},netdev=gns3-{adapter_number}"])
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(
["-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:
# 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_asyncio
from gns3server.compute.nios.nio_tap import NIOTAP
from gns3server.compute.qemu import Qemu
from gns3server.compute.qemu.qemu_error import QemuError
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()
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 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
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
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
(``-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.
* two QEMU nodes on one compute report ``kernel_datapath`` when the