1104 Commits

Author SHA1 Message Date
YueGuobin
61020d3bbc
feat(docker): traffic-insight markers on kernel-datapath links via AF_PACKET
Markers no longer disqualify the kernel datapath. They ride the NIO like
on the relay path and attach to the veth host end through uBridge's
marker add_kernel/delete_kernel/enable_kernel commands (AF_PACKET taps,
same BPF/direction/signal semantics as the relay mark filter).

- base_node: marker anchors are now polymorphic (relay bridge name or
  kernel veth host interface); the reconcile loop is datapath-agnostic
  and DockerVM overrides the add/delete/enable primitives to translate
  the commands. Marker-name validation extracted for both add paths.
- docker_vm: kernel NIO connect/update apply markers on the veth anchor;
  link deletion tears markers down explicitly (the veth survives the
  link, unlike a relay bridge whose death drops its filters).
- controller: eligibility and start_marker no longer exclude markers or
  project-level marker definitions; the kernel NIO carries the capture
  node's marker specs.
- NIO creation accepts markers on kernel NIOs (filters still rejected).
2026-10-06 00:21:07 +08:00
YueGuobin
f6b869be63
feat(docker): packet capture on kernel-datapath links via AF_PACKET
Replace the capture rejection with wiring to uBridge's capture module:
an AF_PACKET socket bound to the veth host end writes into the same pcap
file the relay path uses (capture start_kernel <if> "<pcap>" /
capture stop_kernel), so the pcap streaming to clients is unchanged.

- capture survives node restarts: _connect_nio re-arms it when the NIO
  is already capturing, mirroring the relay path
- uBridge's kernel capture is a singleton per process: a concurrent
  second capture on the same node surfaces uBridge's EALREADY error
- update the datapath docs: filters and markers remain relay-only,
  capture is now served on both datapaths
2026-10-06 00:21:07 +08:00
YueGuobin
37d7b9121e
feat(docker): wire Docker-to-Docker links through a kernel bridge
Eligible links (both endpoints Docker, same compute, no filters/markers,
stopped nodes, no unix-socket NIO containers) are wired as veth pairs
enslaved into a per-link Linux kernel bridge (gns3 + 11 hex of the link
id, filling IFNAMSIZ) instead of the uBridge UDP relay, so frames flow
entirely in the kernel.

- NIOBridge NIO type: the controller decides the datapath in
  UDPLink._prepare and both computes enslave their deterministic gv*/gc*
  veth pair via uBridge's docker/brctl modules
- carrier/suspend = host-end admin state; both endpoints race-safely
  create/delete the bridge (EEXIST/EBUSY/ENOENT tolerated)
- veth adapters can still attach relay (UDP) links on a running node:
  lazy relay bridge over AF_PACKET on the host end (add_nio_ethernet)
- filters/markers/capture are rejected with explicit errors on this
  datapath (they live in the uBridge relay); vendor unix-socket
  containers are excluded; Server.enable_kernel_datapath is the
  kill-switch (default on)
- host-end veths survive container death and are removed on stop plus
  swept by name on the next start (crash/stale recovery)
2026-10-06 00:21:07 +08:00
Mark Paronyan
552c0ca3e0
refactor: use f-strings in remaining compute modules 2026-10-03 19:44:01 +03:00
Mark Paronyan
bbebf20825
refactor: use f-strings in dynamips manager 2026-10-03 19:44:01 +03:00
Mark Paronyan
c458459626
refactor: use f-strings in dynamips NIO modules 2026-10-03 19:44:01 +03:00
Mark Paronyan
fba3ecc5fc
refactor: use f-strings in dynamips nodes 2026-10-03 19:44:00 +03:00
Mark Paronyan
a5d2bca921
refactor: use f-strings in dynamips router node 2026-10-03 19:44:00 +03:00
Mark Paronyan
97f6805d0a
refactor: use f-strings in iou compute modules 2026-10-03 19:44:00 +03:00
Mark Paronyan
d5ad4c67c6
refactor: use f-strings in qemu compute modules 2026-10-03 19:44:00 +03:00
Mark Paronyan
7f2c0c7e9c
refactor: use f-strings in docker compute modules 2026-10-03 19:44:00 +03:00
Mark Paronyan
b622ccae7b
refactor: declare explicit public API in package __init__ modules 2026-10-03 19:43:59 +03:00
Mark Paronyan
306d8fbde4
refactor: remove unused imports 2026-10-03 19:43:58 +03:00
Mark Paronyan
1ed8320e31
Merge branch '3.1' into ruff/pr01-imports 2026-10-03 14:21:22 +03:00
grossmj
64e2a62a04
fix: improve warning message for missing mtools in config disk creation. Ref #2778 2026-10-02 16:08:38 +02:00
Mark Paronyan
61caa20524 style: sort imports in agent and remaining compute modules 2026-10-02 12:43:26 +03:00
Mark Paronyan
b9cdac487c style: sort imports in compute, db and services modules 2026-10-02 12:43:26 +03:00
Mark Paronyan
38e9ddca09 style: sort imports in compute node modules 2026-10-02 12:43:26 +03:00
Mark Paronyan
95ada23395
refactor: replace percent formatting with format specifiers 2026-10-01 19:57:47 +03:00
Mark Paronyan
06899c92fc
refactor: use iterable unpacking instead of concatenation 2026-10-01 19:57:46 +03:00
Mark Paronyan
2dc7479d11
style: remove f-strings without placeholders 2026-10-01 19:57:46 +03:00
Mark Paronyan
396407827e
style: drop redundant open mode arguments 2026-10-01 19:57:44 +03:00
Mark Paronyan
06ef803ee9
style: use not-in membership tests 2026-10-01 19:57:43 +03:00
Mark Paronyan
1a495ab318
refactor: replace getattr with attribute access 2026-10-01 19:57:42 +03:00
Mark Paronyan
c5b5091f5e
refactor: use explicit conversion in f-strings 2026-10-01 19:57:41 +03:00
Mark Paronyan
eae69bd5c5
fix(typing): resolve mypy errors in compute.virtualbox.virtualbox_vm 2026-09-29 22:02:23 +03:00
Mark Paronyan
2542ad4647
fix(typing): resolve mypy errors in compute.vpcs.vpcs_vm 2026-09-29 09:38:24 +03:00
Mark Paronyan
a7350ae0c8
fix(typing): resolve mypy errors in compute.iou.utils 2026-09-29 09:38:24 +03:00
Mark Paronyan
1babf7bcce
fix(typing): resolve mypy errors in compute.dynamips.nodes.router 2026-09-29 09:38:24 +03:00
Jeremy Grossmann
884526c3be
Merge pull request #2900 from markparonyan/mypy-compute-iou-vm
fix(typing): resolve mypy errors in compute.iou.iou_vm
2026-09-28 23:33:41 +02:00
Mark Paronyan
4802c865fc
fix(typing): resolve mypy errors in compute.iou.iou_vm 2026-09-28 21:48:18 +03:00
Mark Paronyan
8c0a5404ac
Merge branch '3.1' into mypy-compute-qemu-vm 2026-09-28 12:39:38 +03:00
Mark Paronyan
525ef8ca57
fix(typing): resolve mypy errors in compute.qemu.qemu_vm 2026-09-28 12:37:28 +03:00
Jeremy Grossmann
79ecb15164
Merge pull request #2888 from markparonyan/mypy-compute-vmware-vm
fix(typing): resolve mypy errors in compute.vmware.vmware_vm
2026-09-28 11:06:12 +02:00
Jeremy Grossmann
310bb193a5
Merge pull request #2887 from markparonyan/mypy-compute-docker-vm
fix(typing): resolve mypy errors in compute.docker vm modules
2026-09-28 11:03:44 +02:00
Mark Paronyan
fdfb2d5591
fix(typing): resolve mypy errors in compute.vmware.vmware_vm 2026-09-28 04:55:44 +00:00
Mark Paronyan
667b0531f6
fix(typing): resolve mypy errors in compute.project 2026-09-28 04:55:38 +00:00
Mark Paronyan
b8ae84031e
fix(typing): resolve mypy errors in compute.docker vm modules 2026-09-28 04:55:32 +00:00
Mark Paronyan
a0450b6bce
refactor: ruff autofixes 2026-09-27 15:55:10 +03:00
Cristi
da16dc9874 fix(qemu): Fixed missing image replacement for linked-clone nodes 2026-09-18 23:58:49 +03:00
YueGuobin
84e7ead465
fix: make node working directory deletion robust
The rmtree error handler in BaseNode.delete() was chmod'ing the failed
path to S_IWRITE (0o200). On POSIX this strips the search permission
from directories, turning a transient deletion failure into a directory
that can no longer be traversed or deleted. The node deletion itself
silently "succeeds" (rmtree gives up once its error handler returns)
and a later project deletion then fails with EACCES. The handler also
never retried the failed operation, so it did not help on Windows
either (the platform it was written for).

The transient failure exists in practice: a concurrent MD5 checksum
computation caching its result in the node directory (e.g. a properties
request racing the deletion) can recreate a file after rmtree has
listed the directory, making the final rmdir fail with ENOTEMPTY.

- add the missing user permissions instead of replacing the whole mode,
  and retry the failed unlink/rmdir
- retry the whole deletion a few times to absorb files recreated while
  the directory is being deleted
- raise a ComputeError when the directory cannot be fully deleted
  instead of failing silently
2026-09-15 01:23:31 +08:00
YueGuobin
be62e8c022
feat: keep Docker images on computes consistent with the controller host
Only relying on the image name lets a moved tag (e.g. a newer :latest)
silently serve stale content from a compute that already has an image
under the same name. When creating a Docker node, the controller now
pins the image id (Id from the Docker daemon on the controller host)
into the create payload. A compute holding a different image under the
same tag reports the image as missing, which routes it through the
image sync added by the previous commit and re-aligns the tag.

No new template fields or database changes: the controller host daemon
remains the source of truth and the pin is resolved per creation. When
the image is not available on the controller host the pin is omitted
and behavior is unchanged (the compute pulls from the repository).
2026-09-14 00:55:18 +08:00
YueGuobin
c477812332
feat: sync Docker images from the controller to remote computes
When a Docker node is created on a remote compute whose Docker daemon
does not have the image, the compute now raises ImageMissingError
instead of blindly pulling from the Docker repository. The controller
exports the image from the Docker daemon on its host (docker save
stream) and streams it to the compute which loads it, so locally built
or docker-loaded images work across computes. When the image is not
available on the controller host either, the compute is asked to pull
it from the Docker repository as a fallback.

- add a POST /docker/images/load compute endpoint that streams a
  docker save tar into the Docker daemon
- let Docker.http_query pass raw (non-dict) request bodies through so
  the tar can be streamed to the daemon
- drop the inline pull from DockerVM.create() and the now unused
  DockerVM.pull_image wrapper
2026-09-14 00:36:54 +08:00
YueGuobin
4b239cc11a
fix: run the reclaim helper as root regardless of the image's default USER
The one-shot reclaim container inherited the image's baked-in USER:
ghcr.io/nokia/srlinux runs as "user:user", so the "privileged" helper
was exactly as unprivileged as the server itself — chmod/chown on files
written by other uids (srlinux writes as a large internal uid) failed
with EPERM and node/project deletion still broke, just with a different
error. Pass --user 0:0 explicitly so the helper is root no matter what
the image declares, and fix the manual reclaim hint the same way.

Validated live on two stuck srlinux node directories (257/258
foreign-owned entries reclaimed to 0 in ~0.5 s each).
2026-09-12 23:15:52 +08:00
YueGuobin
54d9d7c08f
fix: reclaim root-owned container files so docker nodes and projects can be deleted
The stop-time permission pass necessarily runs before the container's
processes exit, so files written during the shutdown window (syslog
archives, trace flushes) and after any SIGKILL path stay owned by root
on the host. An unprivileged server can neither chown nor delete them,
which broke node deletion and project deletion.

Reclaim them through the only privilege door a non-root server has:
a one-shot throwaway container of the node's own image, entrypoint
overridden to the GNS3 busybox (nothing of the guest boots), chowning
the node directory back to the server user. It runs at the end of
close() — project deletion rmtrees the directory right after the nodes
close, so close must leave a clean tree — and as a retry fallback in
delete(). The helper resolves the image by its create-time ID with
--pull=never, so a stale or retagged image name cannot turn into a
registry pull attempt.
2026-09-12 23:04:28 +08:00
YueGuobin
27b55b72f9
fix: keep upstream link-carrier commands off unix-socket NIO bridges
Upstream #2870 added TAP carrier control (set_nio_tap_carrier) and a
busybox interface-status monitor to the Docker base class. Both assume
TAP wiring: unix-socket NIO bridges carry no TAP NIO, so uBridge rejects
the carrier command ('bridge has no TAP NIO') and every link create,
update or delete on a running iol-runner node would fail; the monitor
polls eth{adapter} interfaces that do not exist in the container's
network namespace (or misreports the Docker default eth0 as adapter 0).

VendorDockerVM now short-circuits _set_adapter_carrier and the interface
monitor under GNS3_UNIX_SOCKET_NIO; TAP-wired vendor nodes keep the base
behavior.
2026-09-05 22:21:18 +08:00
YueGuobin
5685f40dd6
fix: keep payload-delivered state across container (re)creation
create() re-runs _parse_vendor_environment on every (re)create — and a
stop removes the container, so every start recreates it. Resetting the
controller-allocated application id there silently flipped nodes to the
hash fallback after their first stop/start (MAC change, plus collision
risk with the allocation pool), and dropped any pending startup-config
delivered by a PUT.

The application id and startup-config state now default via class
attributes instead of being re-initialized by the parser, and the hash
fallback is removed outright: starting an IOL node without a coordinated
allocation raises an actionable error — an uncoordinated id could
collide with the pool and blackhole traffic as a MAC loop.

Found in the E2E run: node booted app id 512, came back as 566 (hash
fallback) after one stop/start; the PUT-ed config edit vanished the same
way.
2026-09-05 21:54:52 +08:00
YueGuobin
8b3aafbbdc
feat: IOU-style startup-config for IOL Docker nodes
Templates reference a config file with the GNS3_IOL_STARTUP_CONFIG
environment knob; the controller materializes the file content into
startup_config_content on node creation (sent once, knob consumed —
the same pattern as the IOU startup_config mapping). The compute builds
the content into the node's nvram_<app id> at the next start using the
IOU nvram_import utility (IOL and IOU share the nvram container format,
verified against iol-xe 17.18.02): valid config at boot, no setup
dialog, %h hostname substitution, hostname rewrite on rename.

Semantics verified against the runner: IOL boots from NVRAM whenever it
holds a config, so a plain stop/start never re-applies the startup
config and 'write memory' survives restarts; an explicit content edit
(PUT) is re-applied on the next start and wins over the saved config,
like IOU.
2026-09-05 21:54:52 +08:00
YueGuobin
5c285f631b
fix: normalize marker.match event tag to int to match the REST schema
The WS event carried the tag as str (verbatim from the MARK signal) when
present and as int when falling back to the registry, so one tag reached
consumers as two different values. Parse the signal's decimal (the exact
value we installed via 'mark <bpf> tag <id>') and keep the registered int
on malformed input; None only when neither side carries one.
2026-09-05 21:54:52 +08:00
YueGuobin
0119373690
feat: allocate IOL Docker application IDs from a pool disjoint from IOU
IOL interface MACs derive from the application ID (aabb.cc{app}{iface}),
so ids must be unique across opened projects sharing computes — the same
reason IOU has its allocator. IOL Docker nodes draw from the upper half
(512-1022, netiomux's fixed peer is 1023) so the two node types can
neither collide nor starve each other; IOU behavior is unchanged.

The controller sniffs the same GNS3_IOL_RUNNER environment marker the
compute uses to select IOLDockerVM (both the nested-properties and
template/top-level-kwarg shapes), stores the id in node properties like
IOU does, and passes it through the Docker create payload. Without an
allocation the compute falls back to a stable node-derived id in the
same upper range.
2026-09-05 21:54:52 +08:00