docker: mask systemd-udevd in privileged containers (GNS3_MASK_UDEV)

A privileged systemd-based NOS container (Cisco XRd boots /usr/sbin/init)
runs systemd-udevd, which on startup coldplugs every device it can reach.
In privileged mode that includes the HOST's USB/input/audio/disk devices,
so every XRd start reconnects USB, mutes audio, and disrupts the host
journal -- highly disruptive on Linux desktops (caught in the act: the
container's udevd was even rescanning the host BTRFS root device).

XRd doesn't need udev (its interfaces are pre-created by GNS3 veth and
mapped via XR_INTERFACES). Add two opt-in env vars, consumed host-side at
container create time in the inherited DockerVM.create (so VendorDockerVM
nodes get it too):

  GNS3_MASK_UDEV=1        -> bind /dev/null over the udevd unit, its two
                             activation sockets, and the coldplug/settle
                             trigger services
  GNS3_MASK_SYSTEMD=u1,u2 -> bind /dev/null over arbitrary units in
                             /etc/systemd/system/ (comma/semicolon list)

Only injected when set, so ordinary nodes are unaffected.
This commit is contained in:
YueGuobin committed 2026-08-14 01:12:21 +08:00
1 parent 088f1da77a
commit 347537f1b3
2 files changed
+61

No files matched your search

+23
View File
@@ -308,6 +308,29 @@ async def test_create_applies_env_host_config(compute_project, manager):
), "GNS3_ user vars must not leak into the container environment"
@pytest.mark.asyncio
async def test_create_masks_systemd_units(compute_project, manager):
"""
GNS3_MASK_UDEV=1 binds /dev/null over the udev units, and GNS3_MASK_SYSTEMD
does the same for arbitrary units -- stopping a privileged systemd container
from udev-coldplugging host devices.
"""
environment = "GNS3_MASK_UDEV=1\nGNS3_MASK_SYSTEMD=foo.service,bar.socket"
response = {"Id": "e90e34656806", "Warnings": []}
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", environment=environment)
await vm.create()
masked = {m["Target"] for m in mock.call_args[1]["data"]["HostConfig"]["Mounts"]
if m.get("Source") == "/dev/null"}
for unit in DockerVM._UDEV_UNITS:
assert f"/etc/systemd/system/{unit}" in masked
assert "/etc/systemd/system/foo.service" in masked
assert "/etc/systemd/system/bar.socket" in masked
@pytest.mark.asyncio
async def test_create_with_extra_configs(compute_project, manager):
"""