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
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).
Docker ids are not comparable across image stores: for the same content a
classic store reports the config digest while a containerd store (or an
image loaded from an OCI archive) reports the manifest digest. The
controller pins the id of its own daemon, so a compute whose store reports
another id for that content answered ImageMissingError forever: every node
creation streamed the image again (save/load round trip) without ever
converging, then failed with "The image 'x' is missing" after the six
retries. Docker 29 makes this easy to hit, since the id a daemon reports
now depends on how the image entered it.
The load endpoint now takes the tag and the digest pinned by the
controller, and the compute records how its own daemon identifies the
image it just received. Node creation accepts the pin through that pair,
and only while the tag still resolves to the synced content: a tag moved
to some other image, or an image the controller never pushed here, still
counts as missing. The pair lives in the compute process, so a restarted
compute syncs once more.
Computes that predate the new endpoint parameters keep the previous
behaviour, as do controllers that do not send them.
The ordered startup (uBridge creates the persistent TAP and hands it to
the server's uid before vpcs is launched, vpcs attaches with -e -d), why
the attach needs no privileges while creating a TAP does, that uBridge
stays off the data path, and that the setcap in the vpcs install target
is for standalone use only.
A VPCS node's single adapter anchors on a persistent TAP the server
creates (tap create + set_owner via uBridge) and the vpcs process
attaches to by name: -e -d <gp{id8}e0p0> -n, the same
server-creates/guest-attaches shape as QEMU's netdev. -n (a vpcs fork
feature) keeps the guest's ip command from ifconfig'ing the anchor on
the host; the vpcs build is probed once per binary identity
(vpcs_tap capability, usage scan) and gates kernel links together
with ubridge_tap — anything else keeps the node on the relay (the
legacy local UDP tunnel on a stock binary, an AF_PACKET endpoint on
the anchor with a capable one).
VPCSVM takes KernelDatapathMixin: _connect_nio dispatches kernel
(enslave into the per-link bridge) / relay-on-anchor / legacy,
captures pick capture start_kernel vs the bridge engine, stop runs
under an execute lock and terminates the process before deleting the
anchor TAPs (a held device refuses deletion), and the duplicate-port
guard backstops the controller's. The NIO routes accept nio_bridge
and write suspend back on update.
_delete_anchor_taps verifies each tap is gone from sysfs and retries
a bounded three times: a holder can release its fd a moment after its
process was reaped (vpcs's daemon parent kills its console child and
exits first), so the unconditional delete raced EBUSY and leaked the
anchor intermittently.
The vpcs side needs IFNAMSIZ (16) instead of its own IFNAMESIZ (12)
for the -d name buffer — anchor names are 14 chars and truncated to a
nonexistent device, which TUNSETIFF then tries to create without
CAP_NET_ADMIN (fixed in the vpcs fork).
e2e (tests/e2e/test_vpcs_kernel_datapath.py): kernel and relay
scenarios green against the real binary — real ICMP both ways, §E.1/§
E.2 on the anchors, delay/suspend/capture, link delete/re-create,
node stop/restart self-heal, zero leftovers; the relay control passes
with both the fork binary and a stock one.
controller_host is recorded on the mounted compute sub-application
when the controller queries the compute capabilities; the test relied
on another test having made that call first and failed when run on
its own. Set the state directly in the test.
Every node lifecycle call (start/stop) emitted a "starting"/"stopping"
node.updated notification on entry, and for the whole duration of the
call asdict() masked the real status with the transitional one — every
compute event forwarded in that window was re-emitted as a duplicate
transitional status.
Stop-all/start-all on a 13-node project pushed two transitional events
per node before the final state. Clients that do not render these
transitional statuses (the Web UI) showed nodes flickering through an
unexpected suspended-looking state before reaching started/stopped.
Remove the transitional notification and the status masking: clients
see the real statuses again (started/stopped), from the forwarded
compute events plus the controller-side confirmation. The wait option
on the lifecycle routes, the strict stop error handling and
wait_for_status() are unchanged.