diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 214238408..1f142f158 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -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 diff --git a/tests/compute/qemu/test_qemu_kernel_datapath.py b/tests/compute/qemu/test_qemu_kernel_datapath.py index a2d57592a..4c0cb1943 100644 --- a/tests/compute/qemu/test_qemu_kernel_datapath.py +++ b/tests/compute/qemu/test_qemu_kernel_datapath.py @@ -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): """ diff --git a/tests/e2e/test_qemu_kernel_datapath.py b/tests/e2e/test_qemu_kernel_datapath.py index b3c18296b..7134349f3 100644 --- a/tests/e2e/test_qemu_kernel_datapath.py +++ b/tests/e2e/test_qemu_kernel_datapath.py @@ -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