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