mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-02 00:30:25 +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
|
: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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
@ -542,6 +553,33 @@ class DockerVM(BaseNode):
|
|||||||
devices = self._format_devices(line.split("=", 1)[1])
|
devices = self._format_devices(line.split("=", 1)[1])
|
||||||
if devices:
|
if devices:
|
||||||
params["HostConfig"]["Devices"] = 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:
|
if params["Entrypoint"] is None:
|
||||||
params["Entrypoint"] = []
|
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"
|
), "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
|
@pytest.mark.asyncio
|
||||||
async def test_create_with_extra_configs(compute_project, manager):
|
async def test_create_with_extra_configs(compute_project, manager):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user