The custom 14789 was picked to keep our demux socket off the ports other
VXLAN software listens on, but a private default surprises operators and
blocks interop with gear that pins the standard port. Default to the
IANA-assigned 4789 instead; the gns3_server.conf override is unchanged
for computes where swarm overlay/flannel-style VXLAN already owns 4789.
Mixed-version deployments must pin the port on every compute until they
all run the new default, or the two tunnel ends will disagree.
Per tunnel, not a host setting: a connected UDP socket runs the
kernel's route lookup for the peer, the chosen source address is
matched back to its owning interface, and that interface becomes
the dev= (and with it the derived MTU and the outer source
address). Different peers may ride different lower devices with
independently derived MTUs; an unresolvable peer is a wiring error
that degrades the link to the relay. Re-creation re-resolves too,
so route changes ride the same node-start / link-recreation paths
as MTU changes.
Verified against the running kernel's sources (7.2.6): the 50-byte
headroom is 20 outer IPv4 + 8 UDP + 8 VXLAN + 14 inner Ethernet
header (vxlan_headroom(), include/net/vxlan.h); the vxlan driver has
no NETDEV_CHANGEMTU handler so a live tunnel never follows its lower
device, while the bridge does follow its ports at runtime
(NETDEV_CHANGEMTU -> br_mtu_auto_adjust). Document the operational
consequence: the wiring path re-creates the device on every node
start and link (re)creation, so raising the underlay MTU on both
computes and stop/start-ing the nodes is the fix — no manual
ip link needed.
The scenario needed root for its network namespace and its fabric subnet
turned out host-specific: policy routing (a proxy's table consulted
before main) sends any non-RFC1918 destination out the default route, so
the peer compute was unreachable behind the veth — unfixable by picking
another subnet, and the failure paths leaked namespaces and processes.
Too environment-coupled to keep in the suite.
Unit coverage is unchanged (selection matrix, tunnel spec derivation,
VNI retry/degrade/self-heal, device lifecycle, capability probe); the
feature doc's Verification section now carries the manual two-compute
checklist instead of the e2e reference.
Cross-compute links can ride kernel VXLAN tunnels instead of the uBridge
UDP relay: each endpoint creates one VXLAN device (uBridge's rtnetlink
vxlan module) enslaved into the same per-link Linux bridge the same-host
datapath uses, so filters, markers, capture and suspend keep running on
the anchor unchanged. Opt-in (Server.enable_vxlan_cross_compute, default
off) because the tunnel caps the usable MTU at the underlay minus the
outer headers; the relay stays the default and the fallback.
The controller derives what the pair must agree on: one VNI per link
(upper VNI space, per-link random salt) and deterministic device names
(gvl{link}{node}) that make crashed-run leftovers claimable
(show -> delete -> create). Same-host compute pairs, switch endpoints
and serial/unix-socket containers stay on the relay.
A VNI the kernel refuses self-heals on every path it can surface:
interactive create retries a fresh salt then degrades to the relay;
project open re-dispatches refused batch entries the same way; node
start recognises the refused device, stops the half-started node,
re-wires both ends on a fresh VNI (or degrades the link) and retries
the start. The device MTU is derived from the lower device (dev=) and
the per-link bridge inherits it - no configuration. The capability
probe (ubridge_vxlan in /capabilities) runs one scratch create/delete
on a scratch port that never equals the configured dstport.
The e2e scenario runs two computes on one machine (a network namespace
joined by a veth pair as the fabric) and needs root.
mypy 2.4 (native parser by default) flags _jwt_username_var.set(user.username)
in agent/mcp/__init__.py: with the legacy `username = Column(String)`
declaration, instance access is typed Column[str], not str. Migrate the
column to the Mapped/mapped_column style already used by token_version and
last_login in the same table, with an explicit nullable=True so the schema
stays identical to the former Column(String) declaration.
Updating any Docker node property destroys and recreates the container
(the route calls DockerVM.update() as soon as the property diff is
non-empty), so it discards everything written inside it. On a running
node that happened silently, as a side effect of an unrelated edit: the
user only saw the node restart.
Refuse the update while the container is up and say what stopping it is
for, like QEMU, IOU, Dynamips, VPCS, VirtualBox and VMware already do
for their own restart-requiring settings. The check runs before anything
is applied — a refused update leaves the node untouched — and only when
the PUT really changes a property, since Web clients send the whole node
back on every edit.
is_running() is not reusable for the question: it reports a paused
container as not running and stops crashed containers as a side effect,
so DockerVM gains is_up() for "running or paused".
A compute 409 (for instance "must be stopped to change the console type
to vnc") left every value that had already been applied locally on the
controller node, because the update loop sets them before the PUT.
Nothing was dumped at that point, but project.dump() serializes the
whole topology: the next successful change anywhere in the project wrote
the refused values to disk, and the next project open recreated the node
with them — this time accepted by a stopped node. A refused change
silently landed later.
Restore the applied properties, the node properties, custom adapters,
missing images and the derived ports before re-raising, the way the
degraded-image create() path already cleans up after its own failure.
When a project is opened, Docker nodes are created in parallel. The free
display was found by checking whether /tmp/.X11-unix/X<n> exists, but that
socket only appears once Xvnc has started, so several nodes got the same
display. Only the first Xvnc started; the others exited with "Server is
already active for display N" ("The vnc process has stopped with return
code 1"), and their containers drew on the screen of another node.
- reserve the displays given to the nodes of this server, and also skip a
display whose lock file belongs to a live process
- restart a crashed Xvnc on the display the container was created with,
after removing its stale socket, instead of picking a new display the
container cannot reach (black screen)
- on close, only remove the X socket of our own Xvnc, never the socket of
the node that owns the display
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The device tools (device_config_send / device_show_run / vpcs_config_set)
built their Nornir inventory with only the console port and inherited the
group hostname, which is always the controller host. Console ports of
nodes on remote computes are bound on the compute's own host, so those
nodes were unreachable from the MCP tools - only local-compute nodes
worked.
Pass the per-node console_host through the project inventory and inject
it as a host-level Nornir hostname (which overrides the group's
controller-wide default). Missing or wildcard (0.0.0.0/::) values keep
the group inheritance, so single-machine setups behave exactly as
before. VPCS builds its own host entries, so it carries the hostname
over explicitly.
The round-trip test builds its catalog from the models (which already
carry the head schema), then stamps the revision that was newest when the
test was written and replays everything above it. Any migration landing
on top therefore ran its DDL against a schema it had not rolled back -
the new computes.auto_connect migration failed with "duplicate column
name: auto_connect".
Stamp the current head instead and let the downgrade roll back every
revision above the inventory one before the upgrade replays them.
Computes can now be flagged auto_connect in the database. The controller
connects to flagged computes in the background at startup (reusing the
one-second delayed fire-and-forget mechanism), and enabling the flag via
PUT /computes/{id} triggers a background connection without the request
ever waiting for it. Disabling the flag does not drop an existing
connection.
Add POST /computes/{id}/disconnect to tear a compute down manually: the
notification stream and HTTP session are closed and every reconnection
path (retry backoff, notification stream reconnect, lazy connection in
http_query) is blocked until connect() is called again or the controller
restarts with auto_connect enabled. Disconnecting a compute used by an
opened project is rejected with 409 unless force=true; forcing only
drops the controller-side control channel, nodes keep running unmanaged.
The local compute cannot be disconnected.
Also make test_compute_list create its own compute (it relied on rows
from earlier tests and failed under reordering), and stop scheduling the
delayed add_compute connection during tests: the timer leaked real
connection attempts into other tests' event loops.
Mirrors the fast static checks from CI (justfile lint + type-check):
ruff check, ruff format --check and mypy (gns3server package only,
matching the files = ["gns3server"] scope in pyproject.toml). Only
staged Python files are checked; pytest and pip check are left to CI.
Enable with: just install-hooks
Follow-up enrichments to the Per-filter guide, one per filter, each
answering the question a reader asks right after "what does it do":
- window_drop: explicit recurrence formula ([Start + k*Period, +Outage),
duty cycle) and ASCII timelines of the three forms (one-shot /
recurring / jittered); Chance spelled out as an independent per-packet
draw gated to the window; jitter explained as per-cycle re-draws that
break phase-locking with protocol timers
- quota: transparent until the budget is consumed; Chance equals
packet_loss in draw mathematics but runs in the eBPF classifier stage
(no correlation parameter, outside seed, invisible to captures)
- limit: the phantom-loss symptom (throughput below the configured rate
with rate+delay set = the 1000 default overflowing)
- seed: reproduction also requires the same traffic pattern (draws are
consumed per arriving packet); one pinned realization is not the
distribution — statistical conclusions need several seeds
- gemodel: scattered-vs-bursty timeline at the same 5% mean (fast
retransmit vs RTO), and a parameter recipe (M and L -> r, p)
- reorder: the mechanism is queue-jumping on the netem delay queue —
hence the delay requirement, the limit overflow interaction and the
delay-bounded reorder depth
- rate: per-direction independent shaping (symmetric full duplex, no
shared budget); asymmetric rates need in-guest netem
- duplicate: real-world prototype (802.11 ARQ retransmissions), example
values with the correlation-collapse anchors (10/25 -> ~1.8%, 25/25
-> ~16.7%)
Make the feature doc usable as a standalone reference:
- Parameter reference: all 13 filters' parameters, ranges and units, the
cross-filter rules (reorder+delay, gemodel/packet_loss exclusivity,
period>=outage, distribution needs jitter), the zero-means-disabled
convention, and the UI display names served by available_filters
(the Web UI labels gemodel as "Gilbert-Elliot loss", never by type)
- Per-filter guide: purpose, per-parameter meaning, caveats and a copyable
example for each of the 13 types, including the four jitter
distributions compared (uniform/normal/pareto/paretonormal)
- REST surface: endpoint table with privileges, a filters PUT example,
and common recipes (WAN latency, bursty loss via gemodel, rate cap,
ICMP blackhole, scheduled outage)
- Clarify gemodel's "one-way" mean: per direction, independent instances
per endpoint, round trip compounds to 1-(1-mean)^2; one-directional
loss is not expressible through the link filters dict
- Fix an inaccuracy: available_filters is a dedicated endpoint's response,
not a field of the link payload
uBridge's capture module now serves one AF_PACKET capture per interface,
any number per process, and `capture stop_kernel` takes the interface it
stops — this rides that (breaking, paired-binary) protocol change.
The server-side single-slot tracker (_kernel_capture_ifc with its
reserve/release/owned_by helpers) is gone: same-port duplicates were
already refused by the NIO capturing flag, uBridge answers EALREADY on a
same-interface duplicate, and per-interface stops removed the process-
wide stop the ownership gating guarded against.
The docker e2e drives ten concurrent captures on one node's uBridge and
asserts a per-interface stop freezes exactly the stopped half while the
live half keeps recording.
The cloud node offered the kernel datapath's host-side interfaces —
node anchors (gv/gq/gi/gd/gx/gp), switch cascade veth ends (gs) and
cross-compute VXLAN devices (gvl) — in its interface list and even
pre-filled them as ports on a freshly created cloud: none of them is
flagged special and only "gns3*" names were filtered. These interfaces
belong to the link or node that created them for the project's whole
lifetime, so bridging a cloud to one would race its owner.
Add is_kernel_datapath_interface() next to the naming contract in
utils/kernel_anchor.py: exact-shape matching (prefix plus its
fixed-length id-derived tail), so a user interface that merely starts
with "gv" is never swallowed. The cloud keeps managed names out of its
host-interface list and default ports mapping, while saved mappings
keep working (the existence checks still see the full interface list).