From e8b51aa12c3a9468ad59053f0e280a48892131f3 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Fri, 14 Aug 2026 19:09:48 +0800 Subject: [PATCH] docker: also null-bind udevadm in GNS3_MASK_UDEV Masking the udev systemd units stopped the daemon's coldplug (host audio resets), but host USB devices still reconnected on every XRd start. A/B testing with plain `docker run` isolated the trigger: XRd's own xr_startup.sh calls udevadm directly (USB license-dongle probing, e.g. `udevadm trigger --action=add --parent-match=`), which synthesizes uevents into the host kernel from the privileged container -- no udevd required. GNS3_MASK_UDEV=1 now also binds /dev/null over the udevadm binary (/bin, /sbin, /usr/bin). Verified with a plain-run experiment: with the bind, host udev monitor shows zero usb/input/hid/sound events during XRd boot (only normal docker veth traffic), and XRd itself boots to running state -- it does not need udevadm under GNS3 (interfaces are pre-created veths). --- gns3server/compute/docker/docker_vm.py | 17 +++++++++++++++-- tests/compute/docker/test_docker_vm.py | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 382db3690..8f3961a35 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -80,6 +80,19 @@ class DockerVM(BaseNode): "systemd-udev-settle.service", ) + # udevadm binary paths also null-bound by GNS3_MASK_UDEV=1. NOS startup + # scripts call udevadm directly -- Cisco XRd's xr_startup.sh runs + # `udevadm trigger --action=add --parent-match=` (USB license + # dongle probing), which synthesizes uevents into the host kernel from a + # privileged container and reconnects host USB devices. Masking the units + # alone does not stop this; the binary must be neutralized too. XRd boots + # fine without udevadm (interfaces are pre-created by GNS3). + _UDEVADM_PATHS = ( + "/bin/udevadm", + "/sbin/udevadm", + "/usr/bin/udevadm", + ) + def __init__( self, name, @@ -561,11 +574,11 @@ class DockerVM(BaseNode): # audio/disk devices, reconnecting/muting them on every start. # XRd doesn't need udev (interfaces are pre-created by GNS3), so # bind /dev/null over the udev units to keep it from running. - for unit in self._UDEV_UNITS: + for target in [f"/etc/systemd/system/{u}" for u in self._UDEV_UNITS] + list(self._UDEVADM_PATHS): params["HostConfig"]["Mounts"].append({ "Type": "bind", "Source": "/dev/null", - "Target": f"/etc/systemd/system/{unit}", + "Target": target, "ReadOnly": True, }) elif line.startswith("GNS3_MASK_SYSTEMD="): diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index b71c9e3ef..53bfb2445 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -327,6 +327,8 @@ async def test_create_masks_systemd_units(compute_project, manager): if m.get("Source") == "/dev/null"} for unit in DockerVM._UDEV_UNITS: assert f"/etc/systemd/system/{unit}" in masked + for path in DockerVM._UDEVADM_PATHS: + assert path in masked assert "/etc/systemd/system/foo.service" in masked assert "/etc/systemd/system/bar.socket" in masked