Files
tsunagi/docs/vip.md
T
Aleksandr Bogomiakov 2e7e238459 Improve tray VIP monitoring and table layout
The VIP editor put routing and traffic observations in a dense text block,
while nested layouts in the devices grid could let text escape its row.
Separate the VIP overview from configuration and draw both live tables with
explicit cell bounds, font-aware row heights, clipping and horizontal overflow.
Measure columns before painting and keep widths stable as live values change.

The overview reuses the existing traffic sparklines for service totals and
individual listener backends, with TX/RX rates, health, RTT, open flows and
counters. Retain bounded histories by service/listener/backend identity and
release them when their resources disappear. Recreated listeners start fresh.

Use egui 0.31 Scene and Painter for a pannable, zoomable routing diagram with
Fit, clickable backend details and linked table selection. Highlight recent
counter growth separately from the backend selected for new connections;
idle bindings alone do not imply traffic, and stale activity expires. Draw
legend markers directly rather than depending on optional font glyphs.
The overview only reads saved observations; edits and deletion still require
Save. No dependencies, wire formats, IPC or persistent schemas change.

Validation on Linux through nix develop:
- cargo fmt --all -- --check
- cargo clippy --locked --workspace --all-targets -- -D warnings
- cargo test --locked --workspace --all-targets: 566 passed, 3 ignored
- cargo build --locked -p tsunagi-tray
The inherited TSUNAGI_CONTROL_SOCKET was unset for Clippy and tests.
Seven new GUI tests cover enlarged fonts, mixed online/offline rows, long
names, large counters, narrow/wide layouts, scene clicks, traffic selection,
stale samples, counter resets, listener recreation and history cleanup.
The first enlarged-font regression exposed an override-font height mismatch;
row sizing now resolves the actual selected font and the regression passes.
A disposable fixture preview rendered successfully with software OpenGL on
an isolated Xvfb display. Initial Xvfb attempts needed explicit xkbcommon and
OpenGL runtime paths in the Nix environment. The preview never contacted an
agent; its temporary source was removed. macOS, Windows, Wayland and live host
TUN traffic were not exercised by this UI change.
2026-10-21 16:57:27 +01:00

11 KiB

Named virtual services

A VIP is a shared name such as api.home, with independent TCP/UDP listeners. Each listener maps its port to a set of node identities and application ports. The application uses the name; the automatically allocated IPv4 address is an internal reservation. The current overlay carries IPv4 only.

CLI and tray

tsng vip list home
tsng vip create home api
tsng vip port add home api tcp/443 --backend server1:6443 --backend server2:7443
tsng vip port add home api udp/53 --backend server1:5353
tsng vip backend add home api tcp/443 server3:6443
tsng vip backend remove home api tcp/443 server2:7443
tsng vip port set home api tcp/443 --port-check off
tsng vip port set home api tcp/443 --check-interval-ms 3000 --check-timeout-ms 2000 --failures 2 --successes 1 --idle-timeout 7200
tsng vip show home api
tsng vip status home api --watch
tsng vip edit home api
tsng vip port remove home api udp/53
tsng vip remove home api

list without a network lists all configured networks. Network and VIP selectors accept exact names, full ids and unique id prefixes; backend node selectors also accept stored node names. Ambiguous selections change nothing. Names resolve to identities when saved, so a renamed node remains a backend. --json provides configuration, revision, counters and observations; list/watch emit one JSON object per service/sample. edit opens JSON in VISUAL/EDITOR and submits one complete version-checked draft. An invalid or rejected edited file is retained with its path reported.

Each mutating command is atomic. A listener can be added with its full initial backend set, and edit can replace the entire configuration at once. Empty VIPs/listeners are valid and resolve, but cannot accept traffic. Configuration updates currently require the network to be active locally. Saved configuration remains visible while a network is stopped.

The tray's network tile has a VIP button. Its separate window edits names, listeners, backend nodes/ports, check settings and flow timeouts. All edits, including deletion, remain drafts until Save. Status updates do not replace a dirty draft. A save conflict retains the draft; Discard / reload loads the latest configuration. Closing a dirty window offers explicit discard or continued editing.

The Overview tab shows a total VIP traffic plot and one backend table per listener, with health, RTT, open flows, totals, TX/RX rates and individual traffic plots. Plots switch between bytes and packets. Observations describe traffic seen by this device, not a network-wide aggregate.

An interactive diagram connects the service name through each listener to its node and target port. Drag to pan, Ctrl+scroll/pinch to zoom, and use Fit to restore the view. Click a backend in the diagram or table to highlight it and see its identity, address and measurement age. Green branches with moving dots indicate counter growth during the latest status sample; a blue ring marks the backend selected for new connections. Existing flows can therefore keep a different branch active. Idle open flows alone do not light up a branch; activity expires after five seconds without a fresh sample. Graph interaction never edits or saves configuration. The Configuration tab retains the explicit Save workflow. Addresses and ids are available in a collapsed detail.

Delivery and recovery

The client agent selects the eligible backend with the lowest smoothed full round-trip latency through the actual encrypted data plane, including relays. Unknown or stale RTT is not treated as zero. Local backends have zero path RTT. A new flow is pinned to its selected backend; a faster RTT does not move it. This can concentrate new traffic on the closest node.

At local ingress, NAT replaces destination address/port and reserves an OS source port for an unambiguous reverse mapping. Return traffic passes normal network/peer source and destination admission before reverse NAT restores the virtual source address/port. Plugins and encrypted transit stay unchanged. A peer never gains permission to claim a VIP source. Fragment assembly is bounded, checksums are updated, packets are fragmented back to the overlay MTU, and ICMP errors have their quoted NAT headers restored. Fragment caches are scoped to network and authenticated sender, with idle expiry independent of further traffic.

For a backend on the client itself, the request is injected into the same TUN with the nonlocal VIP as source, and the host reply is reverse-translated at local ingress. The local backend therefore sees the VIP as source; remote backends see the client's ordinary overlay address. Firewalls and application bind addresses remain the administrator's responsibility.

Removing a backend/listener/VIP prevents new bindings while existing bindings continue until idle expiry. Established TCP connections are not migrated. A retransmitted empty SYN may choose another backend before a SYN-ACK has arrived; SYNs carrying application data are not retargeted. A new SYN sequence creates a new binding even if the client reuses its address/port tuple. TCP RST shortens binding retention. An unavailable UDP backend is replaced for subsequent datagrams; already sent datagrams are never replayed. Stateful UDP applications may need to re-establish their application session. Protocols that embed IP addresses or negotiate additional ports require their own configuration; no application payloads are parsed or rewritten.

Idle expiry defaults to 7200 seconds for TCP and 120 seconds for UDP. Shadow ports are quarantined for 120 seconds after retirement to reject late replies and avoid immediate reuse. Active and quarantined bindings share a bounded capacity. State and sockets are released on network stop or agent shutdown. A restart drops bindings; shared service configuration remains intact.

Health

One probe exchange per second measures each used peer through the encrypted IP tunnel. The EWMA uses 7/8 of the previous sample and 1/8 of the new one. A probe expires after three seconds; two consecutive missing responses or a missing data path exclude the peer. Measurements expire after ten seconds. Probes continue during failure so recovery is automatic.

TCP connect checks default to on. Defaults are a three-second interval, two-second timeout, two failures to exclude and one success to recover. Checks are shared for the same network/node/address/TCP-port, run off the packet path with at most 32 concurrent connects, and are scheduled fairly. A 250ms worker tick services timers. Connect checks send no application data; they confirm port acceptance and may appear in server logs. They cannot prove application readiness after a successful handshake. Disabling port checks retains data-plane probes.

UDP uses encrypted path checks and explicit ICMP port errors. No arbitrary probe datagrams are injected into an unknown application protocol. Silence cannot prove failure of an arbitrary UDP application. An explicit unreachable port is excluded for ten seconds and then becomes eligible to retry.

Shared state and addresses

Any authenticated member can create, edit or remove any VIP. Each field is a self-contained signed register: network, service id, field key, optional encoded value, Lamport clock, author, durable author counter and signature. Signing uses the separate tsunagi-vip-field/v1\0 domain followed by postcard encoding of those fields with the signature vector empty. The encoding/order is a wire contract; the identity scheme and handshake transcript are unchanged.

Concurrent edits to different backend fields merge. A conflict on one field uses (Lamport clock, author id, canonical bytes) ordering, independent of wall time and arrival order. Listener generations prevent backend fields from an old removed listener reappearing when the port is recreated. Service deletion is a permanent signed tombstone for its id; recreation gets a fresh id. Missing snapshot fields are never deletion.

Saving validates the entire draft and checks the revision shown by the editor. The merged snapshot and author's counter commit together in SQLite before local routing changes or publication. Receivers verify and persist complete service snapshots before exposing them. Periodic rotating snapshots repair queue loss and returning replicas without requiring the creator online. Concurrent edits made before participants have seen each other can still merge after a save. A merged configuration exceeding listener/backend limits is reported as an error and excluded from routing rather than selected according to arrival order.

VIP allocation walks the agreed overlay range from a service-id-derived seed, excluding known node claims and other allocated VIPs. Nodes avoid known VIP reservations when choosing a new address. Names colliding with node names or another service are reported; node names take precedence and service ties use id order. A partition can temporarily have incomplete state; after merge an address or conflicting name's effective holder can change. Use DNS names. DNS TTL is 30 seconds; DNS does not migrate established connections or force applications to discard cached addresses. Existing NAT bindings retain their original VIP address while draining.

Limits bounds active services (64 by default), permanent deletion markers (1024), signed fields per active service (192), listeners (16), backends per listener (32), active TCP checks (32), flow/port reservations (4096) and incomplete IPv4 datagrams (64). A deleted service retains only its signed permanent marker; obsolete fields can be dropped because replay cannot override deletion. A full deletion history is an explicit error; protection is never silently evicted. Deleted services do not consume the active-service limit. Whole service snapshots must also fit the configured control frame limit. Corrupt mandatory VIP storage is a hard error on activation, never reset as a cache.

Observations and verification

Status is this client's observation. Per VIP/listener/backend it reports the next selected backend, health reason, RTT and sample age, active bindings, user IP packet/byte counters, drops and UDP failovers. Probe bytes do not enter user traffic counters. The GUI and CLI watch derive rates from cumulative counters; counters reset on restart. These are IP forwarding counters, not application acknowledgements or guaranteed delivered-byte totals.

Automated tests cover field merging, stale drafts, signed deletion, tampering, network isolation, SQLite migration and transactional rollback, real port connect failure/recovery, RTT selection, UDP failover, local hairpin NAT, fragmentation/checksums, real encrypted TCP/UDP packet delivery and source admission, real CLI/DNS wiring and draft form rendering. In-memory TUN tests exercise packets, not an operating system TCP stack. Native desktop behavior, physical interfaces and cross-platform kernel/firewall behavior need live-host validation; automated tests do not establish those.