mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
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:
parent
088f1da77a
commit
347537f1b3
@ -69,6 +69,17 @@ class DockerVM(BaseNode):
|
||||
:param extra_volumes: Additional directories to make persistent
|
||||
"""
|
||||
|
||||
# systemd units masked by GNS3_MASK_UDEV=1: the udev daemon, its activation
|
||||
# sockets and the coldplug/settle triggers. Masking them stops a privileged
|
||||
# systemd container from replaying device events on the host.
|
||||
_UDEV_UNITS = (
|
||||
"systemd-udevd.service",
|
||||
"systemd-udevd-control.socket",
|
||||
"systemd-udevd-kernel.socket",
|
||||
"systemd-udev-trigger.service",
|
||||
"systemd-udev-settle.service",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
@ -542,6 +553,33 @@ class DockerVM(BaseNode):
|
||||
devices = self._format_devices(line.split("=", 1)[1])
|
||||
if devices:
|
||||
params["HostConfig"]["Devices"] = devices
|
||||
elif line.startswith("GNS3_MASK_UDEV=") and \
|
||||
line.split("=", 1)[1].strip().lower() in ("1", "true", "yes"):
|
||||
# A privileged systemd-based NOS container (e.g. Cisco XRd)
|
||||
# runs systemd-udevd, which coldplugs every device it can see
|
||||
# -- and in privileged mode that includes the HOST's USB/input/
|
||||
# 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:
|
||||
params["HostConfig"]["Mounts"].append({
|
||||
"Type": "bind",
|
||||
"Source": "/dev/null",
|
||||
"Target": f"/etc/systemd/system/{unit}",
|
||||
"ReadOnly": True,
|
||||
})
|
||||
elif line.startswith("GNS3_MASK_SYSTEMD="):
|
||||
# Generic form: comma/semicolon-separated unit names to mask
|
||||
# the same way (bind /dev/null over /etc/systemd/system/<unit>).
|
||||
for unit in line.split("=", 1)[1].replace(";", ",").split(","):
|
||||
unit = unit.strip()
|
||||
if unit and "/" not in unit and ".." not in unit:
|
||||
params["HostConfig"]["Mounts"].append({
|
||||
"Type": "bind",
|
||||
"Source": "/dev/null",
|
||||
"Target": f"/etc/systemd/system/{unit}",
|
||||
"ReadOnly": True,
|
||||
})
|
||||
|
||||
if params["Entrypoint"] is None:
|
||||
params["Entrypoint"] = []
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user