mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01:15 +02:00
refactor: wire unix-socket NIOs through a per-node runtime directory
uBridge cannot reach sockets bind-mounted from the node's projects directory: the path exceeds AF_UNIX's 107-byte sun_path cap. The /proc detour from the previous commit is a dead end too - the runner drops from root to the server uid, which makes the process non-dumpable and its /proc/<pid>/root unreadable for the unprivileged server. Instead, mirror how CML itself runs the image (source=<scratch>/tmp, target=/tmp in its node definition): bind a per-node directory from the runtime directory (<XDG_RUNTIME_DIR>/gns3/unixio/<node-id>, next to the uBridge control sockets) at the image's socket directory. The path is short, the directory is owned by the server user (whom the agent drops its privileges to), and it is removed with the node. A socket directory covered by a persisted volume keeps working as before. IOLDockerVM persists only /tmp/run (nested bind at /tmp/run) and cleans stale sockets/netio dirs from the socket directory on start, skipping the cleanup when the container is already running. A failed wiring now stops uBridge so the next start does not hit 'bridge already exist'.
This commit is contained in:
4 files changed
+271
-116
No files matched your search
@@ -28,17 +28,22 @@ Networking does not use the container's network namespace at all: the runner's
|
||||
netiomux exposes per-interface AF_UNIX datagram sockets in the container's
|
||||
``/tmp`` (``s%02d.sock`` receive, ``c%02d.sock`` send — raw Ethernet frames),
|
||||
wired by the generic ``GNS3_UNIX_SOCKET_NIO`` capability of VendorDockerVM
|
||||
(uBridge reaches them through the container's root in /proc). Because the
|
||||
netio bus directory is private to the container's /tmp, the application IDs
|
||||
are fixed constants with no cross-node collisions.
|
||||
(uBridge reaches them through a per-node runtime directory bound at /tmp —
|
||||
see ``VendorDockerVM._unix_socket_host_dir``). Because the netio bus
|
||||
directory is private to the node, the application IDs are fixed constants
|
||||
with no cross-node collisions.
|
||||
|
||||
This class is selected by the ``GNS3_IOL_RUNNER=1`` environment marker.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from gns3server.compute.docker.docker_error import DockerHttp404Error
|
||||
from gns3server.compute.docker.vendor_docker_vm import VendorDockerVM
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -124,22 +129,40 @@ class IOLDockerVM(VendorDockerVM):
|
||||
boot (the runner writes NETMAP there but does not create it).
|
||||
* ``<working_dir>/config/iol-config.json`` is rewritten on every
|
||||
start so adapter-count and memory changes take effect.
|
||||
* Sockets and netio bus directories left in the wiring directory by a
|
||||
previous (possibly SIGKILLed) run are removed — the runner rebinds
|
||||
them on boot and would fail on a stale file.
|
||||
|
||||
The netiomux sockets and the netio bus directory need no cleanup:
|
||||
they live in the container's own /tmp, which is fresh in every
|
||||
container GNS3 creates (containers are recreated on each start).
|
||||
``tmp/run`` (startup-config, NVRAM) is never touched. Neither is
|
||||
anything while the container is already running (idempotent start of
|
||||
a live node: the sockets belong to the running runner).
|
||||
"""
|
||||
|
||||
try:
|
||||
state = await self._get_container_state()
|
||||
except DockerHttp404Error:
|
||||
state = "stopped"
|
||||
|
||||
os.makedirs(os.path.join(self.working_dir, "tmp", "run"), exist_ok=True)
|
||||
self._write_iol_config()
|
||||
|
||||
if state == "running":
|
||||
return
|
||||
|
||||
wiring_dir = self._unix_socket_wiring_dir()
|
||||
for pattern in ("s??.sock", "c??.sock"):
|
||||
for stale in glob.glob(os.path.join(wiring_dir, pattern)):
|
||||
with contextlib.suppress(OSError):
|
||||
os.unlink(stale)
|
||||
for netio_dir in glob.glob(os.path.join(wiring_dir, "netio*")):
|
||||
shutil.rmtree(netio_dir, ignore_errors=True)
|
||||
|
||||
def _write_iol_config(self):
|
||||
"""
|
||||
Write the runner's config file on the host side of the /config volume.
|
||||
The runner drops to user-id/group-id after its setup, so everything it
|
||||
creates is owned by the server user — which is also what lets an
|
||||
unprivileged uBridge traverse /proc/<pid>/root to reach the
|
||||
container's sockets.
|
||||
creates is owned by the server user — which is also what lets the
|
||||
(unprivileged) uBridge write into the node's socket directory.
|
||||
"""
|
||||
|
||||
config = {
|
||||
|
||||
Reference in new issue
Block a user