From f5554d816a002fa28958a21448b3ed0a2ce83dbd Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 12 Aug 2026 23:41:45 +0800 Subject: [PATCH] vendor: drop the hardcoded /etc/network mount for SKIP_INIT containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Override _mount_binds in VendorDockerVM: for GNS3_SKIP_INIT containers the /etc/network volume (GNS3's own network config consumed by init.sh's ifup) is dead weight — init.sh never runs and the NOS manages its own interfaces. The override removes the bind, filters /etc/network out of self._volumes (keeping GNS3_VOLUMES and the bridge/fix passes consistent) and deletes the host-side skeleton directory created by the base class. Without GNS3_SKIP_INIT the mount is kept, matching base behaviour. _persistent_volumes() is removed — the mount override is now the single filter point. --- docs/features/docker-exec-console.md | 14 ++++--- gns3server/compute/docker/vendor_docker_vm.py | 39 ++++++++++++------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/docs/features/docker-exec-console.md b/docs/features/docker-exec-console.md index b61a67188..dd741ff87 100644 --- a/docs/features/docker-exec-console.md +++ b/docs/features/docker-exec-console.md @@ -241,12 +241,14 @@ host ──Docker bind mount──▶ /gns3volumes/etc/opt/srlinux (always m > host-side root-owned, and an unprivileged GNS3 process cannot chown them > from the host. Container-side root (with GNS3's `UsernsMode: host`) can. -With `GNS3_SKIP_INIT`, `/etc/network` is skipped by both vendor passes -(`VendorDockerVM._persistent_volumes()`): it holds GNS3's own network config -for init.sh's `ifup`, which never runs for SKIP_INIT containers — the NOS -manages its own interfaces. The Docker mount itself is left alone (shared -`_mount_binds`, hardcoded at `docker_vm.py` `_mount_binds()`); only the -vendor-side bridge/fix passes skip it. +With `GNS3_SKIP_INIT`, GNS3's hardcoded `/etc/network` volume (see +`docker_vm.py` `_mount_binds()`) is dropped entirely by +`VendorDockerVM._mount_binds()`: it holds GNS3's own network config for +init.sh's `ifup`, which never runs for SKIP_INIT containers — the NOS +manages its own interfaces. The override removes the bind, filters the +volume out of `self._volumes`, and deletes the host-side skeleton directory +the base class just created. Without `GNS3_SKIP_INIT` the mount is kept +(behaviour matches the base class). ### Lifecycle summary diff --git a/gns3server/compute/docker/vendor_docker_vm.py b/gns3server/compute/docker/vendor_docker_vm.py index 1c80eac7c..9d0c683f6 100644 --- a/gns3server/compute/docker/vendor_docker_vm.py +++ b/gns3server/compute/docker/vendor_docker_vm.py @@ -27,9 +27,11 @@ container behaves identically to DockerVM. """ import asyncio +import contextlib import json import logging import os +import shutil from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer from gns3server.compute.docker.docker_vm import DockerVM @@ -79,6 +81,26 @@ class VendorDockerVM(DockerVM): # ---- hook overrides --------------------------------------------------- + def _mount_binds(self, image_info): + """ + Override: for SKIP_INIT containers, drop GNS3's hardcoded + /etc/network volume. It holds GNS3's own network config consumed by + init.sh's `ifup`; init.sh never runs for SKIP_INIT containers (the + NOS manages its own interfaces), so the mount would be dead weight. + Removes the bind, drops the volume from self._volumes (so + GNS3_VOLUMES and the vendor passes stay consistent) and deletes the + host-side skeleton directory the base class just created. + """ + binds = super()._mount_binds(image_info) + if self._gns3_init: + return binds + binds = [b for b in binds if b.get("Target") != "/gns3volumes/etc/network"] + self._volumes = [v for v in self._volumes if v != "/etc/network"] + shutil.rmtree(os.path.join(self.working_dir, "etc", "network"), ignore_errors=True) + with contextlib.suppress(OSError): + os.rmdir(os.path.join(self.working_dir, "etc")) + return binds + def _prepare_init_and_interface_env(self, params): """ Override: conditionally prepend init.sh, and honour @@ -95,19 +117,6 @@ class VendorDockerVM(DockerVM): last_ifname = f"eth{self.adapters - 1}" params["Env"].append(f"GNS3_MAX_ETHERNET={last_ifname}") - def _persistent_volumes(self): - """ - Volumes relevant to vendor persistence. With GNS3_SKIP_INIT, drop - /etc/network — GNS3's own network config for init.sh's ifup, unused - when init.sh is skipped (the NOS manages its own interfaces). The - Docker mount itself is left alone (shared _mount_binds); only the - vendor-side bridge/fix passes skip it. Without SKIP_INIT the full - list is returned so behaviour matches the base class. - """ - if self._gns3_init: - return self._volumes - return [v for v in self._volumes if v != "/etc/network"] - def _get_container_ifname(self, adapter_number): """ Override: honour GNS3_INTERFACE_NAMES (e.g. mgmt0, e1-1) in adapter @@ -170,7 +179,7 @@ class VendorDockerVM(DockerVM): return uid, gid = os.getuid(), os.getgid() - for volume in self._persistent_volumes(): + for volume in self._volumes: target = f"/gns3volumes{volume}" log.debug("Docker container '%s' fix ownership on %s", self._name, target) try: @@ -212,7 +221,7 @@ class VendorDockerVM(DockerVM): Permission-changes recorded by _fix_permissions at the previous stop are restored (best-effort). """ - for volume in self._persistent_volumes(): + for volume in self._volumes: vol_target = f"/gns3volumes{volume}" # fmt: off script = (