The VPN Manager I want to build: let different apps use their own VPNs

23 min read Original article ↗

VPN MANAGER / Development notes

What I first wanted to build

The host's original network stays unchanged.
Designated apps use only their designated exits.

I first wanted to build VPN Manager because most ordinary VPN clients seem geared toward switching the whole computer together.
But that is not what I need.
I want to specify which apps use which VPN, while unmanaged programs keep using the host's original network.

For example, I may have a browser, a downloader and other apps open at the same time.
The browser goes to Instance A, the downloader goes to Instance B, and the others use the original network.
A and B each have their own WireGuard identity, DNS, Gateway, connection resources and lifecycle.

If A disconnects, I would rather the browser lose its network connection outright.
It must not automatically move to B, much less fall back to the ISP's original exit.

If A disconnects: The browser has no network.
Instance B and unmanaged programs keep working normally, without having to switch along with it.

Usage scenario | Three different paths on one host (scroll horizontally on small screens). This illustrates the intended architectural behavior, not a live connection view.

I also want to manage accounts, Profiles and Gateways from different VPN providers in the same system.
Ideally, designated apps or services should be isolated directly at the OS network layer, without Docker or changing the whole computer's default route.

Traffic information is another feature I want to build.
I want to see how much traffic each Instance uses, which Gateways are congested, how long DNS takes and which process consumes the most CPU or memory.
A single Connected indicator does not mean much to me.

People have already built similar things, and many assemble their own environments using netns, proxies and firewalls.
But I want to bring multi-VPN management, app isolation, failure recovery and monitoring together, without having to wire a pile of things together each time.

Start with the overall system architecture.

Current operating interface

VPN Manager overview: two VPN connections, their local Proxy Ports, download/upload statistics and a traffic-path diagram

Screen 01VPN Manager overview: two connections, their respective proxy ports and the traffic paths.Actual interface screenshot · 2026-11-01 (scroll horizontally)

VPN Manager connections page: two WireGuard connections in the Netherlands and Hong Kong, with connection status, proxy ports, live traffic and resource monitoring

Screen 02Connections page: each connection's status, live traffic and resource information.Actual interface screenshot · 2026-11-01 (scroll horizontally)

01The backbone of the system

Complete system architecture VPN Controller only passes intent; Instance Manager manages Instances independently. All persistent data goes through Store helper to Store; data traffic does not pass through the management plane. Figure 1 | The whole VPN Manager system: control, data, persistence and observation Web resource service Serves React static assets / same origin React user interface Core operations / providers / performance panel Static assets HTTP/SSE VPN provider manager Mullvad / AIRVPN adapters Each provider's IP egress checks Candidate profiles and status VPN controller VPN Core/Web API VPN platform control coordinator Profile validation / Store helper Only coordinates operation intent Security policy manager DNS profile validation Firewall policies / automatic blocking IP blocklist policy Profiles Policies Performance manager Analysis / benchmark sessions Comparisons / performance reports Instance manager Sole lifecycle ownership / outer monitoring Generations, resource leases and automatic recovery Platform manager Linux / macOS system adapters OS privileges / sandbox / resource control Typed lifecycle intent Explicitly authorized performance tests Test leases OS requests Metrics service Metrics collector Metrics compression module VPN Instance A … N Each owns complete internal services Independent GotaTun / DNS / Gateway App traffic bypasses Controller Store helper Shared type-safe persistence client Configure / start / activate / stop File descriptors (FDs) Monitoring metrics Bounded data queries Persisted metrics and reports always go through Store helper Store storage service Sole SQLite accessor / internal modules SQLite Control database + metrics database Managed app Gateway client VPN peer Encrypted WireGuard UDP Blue: control Green: data Yellow: OS resources Purple: observation All persistence goes through Store helper; secrets use a separate private delivery channel.

Figure 1Complete VPN Manager system architectureControl plane, data plane, observation and persistence are separate.(scroll horizontally to inspect the full diagram)

The overall control path is initially planned as follows:

React UI→ VPN Web API / VPN Core→ Instance Manager→ Instance Controller

Here I make Instance Manager a separate process.
It handles starting, stopping, generation, resource leases and recovery for all Instances.
Controller only receives operation intent from the UI/API; it does not forward apps' TCP/UDP data.

Anything that needs OS privileges goes to Platform Manager.
It handles network namespaces, process sandboxes, FDs, firewall policies and macOS capture/deny.
I do not want other components changing system network settings directly just because it is convenient.

Store persists state, Metrics collects data, and Performance Manager handles analysis and authorized tests.
These things should not sit in the app packet path.
For example, slower Metrics or database writes must not queue packets already being transmitted; new state changes still follow the original fail-closed rules.

Table 1 | Execution boundaries and main responsibilities
Execution boundary Main responsibilities
vpn-controllerd Serves React static assets, VPN Web API and VPN Core, and coordinates Provider/Security/Metrics/Performance control.
vpn-instance-managerd Sole cross-Instance lifecycle owner; manages desired state, generation, identity/resource leases, the outer Watchdog and recovery reconciliation.
vpn-platformd Exclusively handles privileged OS operations; runs Linux/macOS Adapters, sandboxes, FD/policy creation, spawn, seal and safety evidence collection.
vpn-stored Sole SQLite owner; manages control.sqlite, metrics.sqlite, secrets, revisions, the operation journal and backups.
vpn-instance × N An independent worker for each VPN; owns its own GotaTun, DNS, Gateway, Port, Metrics and inner Watchdog.
vpn-provider-worker Runs API operations, route-bound IP preflight and restricted connectors per Provider job; does not create, start or stop Instances itself.
macOS system extension (if enabled) Handles flow capture, deny and bridging for managed apps; provides safety boundaries according to native platform capabilities.

Complete component ownership

I first classify components by who manages them and who has authority to operate them.
Drawing a pile of boxes alone is not useful. What matters is who is affected when a process dies, and who can restart it.

Complete component ownership diagram All 55 formal components are included; identically named Process Managers and Instance Watchdogs are distinguished by their parents. Indentation indicates ownership, not that every helper is a daemon. Component ownership | Grouped by execution boundary and privilege responsibility React user interface (UI - React) · Core UI · Performance Panel · Provider management panel VPN Controller · VPN Core · VPN Web API · VPN platform control coordinator · VPN profile validator · Store helper (storage adapter) Platform Manager · Linux system adapter · Process manager · Process sandbox · Network resource control · macOS system adapter · Process manager · Process sandbox · Network resource control Provider manager (VPN Provide Manager) · Mullvad provider adapter · Mullvad IP egress checker · AIRVPN provider adapter · AIRVPN IP egress checker Security Manager · DNS profile validator · Firewall profile · Automatic block detector Metrics Service · Metrics collector · Metrics compression module Performance Manager Storage service (Store) · Configuration data helper · Secret data helper · Security policy helper · DNS profile helper · Provider data helper · Mullvad data helper · AIRVPN data helper · Metrics storage helper Instance Manager · Outer Instance watchdog · VPN Instance (single Profile) · Instance controller · Instance DNS controller · Instance port controller · Port forwarding · Instance metrics · Inner Instance watchdog · Gateway manager · Gateway service · Gateway metrics · GotaTun Web static resource service Execution locations are determined by component ownership, privileges, and fault-isolation boundaries.

Component ownership diagramAll components and their responsibility ownershipGrouped by parent/owner, showing the components inside the UI, Controller, Platform, Store, and each Instance.(scroll horizontally to inspect the full diagram)

02VPN Instance architecture

The complete internals and data path of a single Instance Each Instance contains its own Controller, DNS, Port, Gateway, Metrics, Watchdog, and GotaTun. The inner Watchdog can block traffic, but cannot rebuild or unblock on its own. Figure 2|A single VPN Instance: independent services and data paths Instance manager Commands/execution generation/resource leases VPN Instance|Independent Rust Worker Instance Controller Controls only this Instance Gateway Manager Gateway Service × N Gateway Metrics Instance DNS Controller Independent cache/connection pool/resolver DNS only through the VPN tunnel Instance Port Controller Port forwarding(Port forward) Only Apps in the same Instance are allowed Designated managed App Proxy client/ Protected process Port forwarding goes only to Apps in the same Instance TCP/IP handling+GotaTun Linux kernel/macOS Rust protocol stack Independent WireGuard device and keys Instance Metrics Local counters/latency distributions Instance Watchdog Tunnel/DNS/platform policy health VPN peer Peer-restricted encrypted WireGuard UDP Platform manager Process sandbox/TUN/ Restricted UDP FD Metrics service Bounded snapshot transfer Apps cannot obtain the host Underlay, secrets, Store, or namespace descriptors.

Figure 2The services and data paths of a single VPN InstanceEach Instance owns its own runtime, controller, Gateway, DNS, Metrics, and Watchdog.(scroll horizontally to inspect the full diagram)

Each Instance has its own GotaTun, DNS Controller, Port Controller, Gateway Manager, Metrics, and Watchdog.
WireGuard identity, DNS cache, sockets, buffers, and queues also need to be separate.
CPU, NIC, and OS can be shared, but A's execution state should not be mixed with B's.

There is one point I want to make clear first.
GotaTun is a WireGuard engine, not a complete TCP/IP socket stack in itself.
So connecting GotaTun does not mean that all TCP/UDP connection handling is already there. That part still needs to be handled separately.

On Linux, I lean toward directly using the kernel TCP/IP stack inside an isolated network namespace.
For an explicit Proxy on macOS, each Instance uses its own smoltcp along with GotaTun.
Their sockets, buffers, and queues also need to be independent. Controller SQL or Provider HTTP calls must not hold up actual network traffic.

DNS also needs to follow its Instance.
If resolution fails, report failure. Do not quietly switch to asking the host resolver.
After receiving HTTP CONNECT or SOCKS5 TCP/UDP, the Gateway must actually hand it to its own Instance to establish the destination connection.
Port Forward can only forward to a verified App endpoint within the same Instance. It must not take the shortcut of connecting directly to the host's 127.0.0.1.

The difference between managed Apps, Gateway, and Port Forward

I divide traffic entry points into three types.
Managed app must first enter an isolated environment and cannot choose the host network on its own.
Gateway only handles traffic explicitly handed to the HTTP/SOCKS Proxy. Connections the App establishes separately are not automatically protected.
Port Forward is traffic arriving from the VPN. It can only be delivered to an App within the same Instance that has a valid AppEndpointLease .

DNS and IPv6 cannot be exceptions either.
If the Instance's DNS path is unavailable, do not query.
If IPv6 has no verifiable tunnel path, it must not automatically be allowed onto the host network either.

App isolation on macOS is more troublesome

The macOS side is actually more troublesome.
An explicit Proxy is possible, but it only handles traffic actually handed to the Proxy.
QUIC, WebRTC, or the system DNS used by an App itself may create separate sockets.
So having a Proxy does not mean the entire App is isolated.

The browser toolbox's per-site proxy routing page: two proxy profiles, Hong Kong SOCKS5 127.0.0.1:1080 and Netherlands SOCKS5 127.0.0.1:1082

Screen 03The browser toolbox's per-site proxy routing: Hong Kong uses 127.0.0.1:1080, and the Netherlands uses 127.0.0.1:1082.Actual interface screenshot · 2026-11-01 (scroll horizontally)

03Linux: making sure there is “no way back” at the OS layer

Linux isolated data path The restricted UDP FD created in advance on the host is given only to GotaTun. Apps have only the lo/TUN path inside netns. Worker replacement can retain the closed sandbox without changing the host default route/DNS. Figure 3|Linux: managed-process isolation and the WireGuard UDP exit Host network namespace|Existing network settings remain unchanged Instance isolation sandbox: only lo and TUN; no veth/Host NAT Host loopback listener Gateway TCP/UDP forwarding Accepts only traffic from bound clients Process sandbox Designated managed App process tree Host IPC and inherited network FDs are prohibited Instance internal services Gateway/DNS/port connections Destination sockets are created in the isolated netns Delivery of pre-bound listening FDs Linux kernel TCP/IP+TUN Independent routes/DNS/firewall policy GotaTun Each Instance owns an independent device Restricted UDP FD Created in advance in the host netns Can send only to approved VPN peers Host NIC → VPN peer Platform management: process and network resource control When Worker/TUN fails, Apps still have no other external-network exit. The sandbox lifecycle can span Worker generations. Create or remove only namespaces, cgroups, binds, and filtering rules owned by this system.

Figure 3Linux namespace isolation and the WireGuard UDP pathThe ordinary host network remains unchanged. Apps in the isolated netns have no unauthorized plaintext host exit.(scroll horizontally to inspect the full diagram)

The Linux approach is more direct.
I do not intend to change the host's default route or move the physical NIC to another namespace.
Unmanaged Apps continue using the original network as usual.

Platform Manager first creates a UDP socket in the host network namespace that allows only the designated WireGuard peer.
It then passes the restricted FD to the VPN worker in the isolated namespace.
That namespace has only loopback and TUN. It provides no plaintext veth/NAT exit to the host.

IP packets sent by an App first enter TUN, are encrypted by GotaTun, and are then sent to the WireGuard peer through the UDP FD originally created in the host namespace.
However, creating the socket on the host is not enough.
Which IP/port it can connect to, who holds the FD, and whether Platform policy is actually effective all need to be checked.

What I care more about is that the App has no ordinary host internet exit at all.
If the worker crashes, disconnect first. There is no need to find it a backup route.
I cannot accept an App finding its own way back to the ISP exit after the VPN disconnects.

But netns is not a complete sandbox either.
An App may inherit another process's FD, or find a bypass through Unix sockets, D-Bus, or host IPC.

04Instance startup and fault-recovery flow

Lifecycle, automatic recovery, and stop priority Manual Stop and emergency stop take priority over recovery. CommitResume is allowed only after the complete Manager/intent/generation/sandbox tuple matches. Manager replacement replaces workers and adopts only closed sandboxes. Figure 5|Safe startup, automatic recovery, and manual-stop priority Persist intent and reserve resources Desired state/manual stop/emergency stop Manager term/intent revision Worker generation/identity lease Close the environment first, then start the process Sandbox generation is independent of Worker generation Unconfigured Worker/verified identity Store privately delivers a one-time secret Allow only health probes Verify VPN tunnel/DNS/policy Close App/Gateway/port forwarding Independently verify health Commit opening(CommitResume) Manager rechecks the latest intent Platform consumes the single-use authorization Only this step formally opens the traffic gates Ready/VPN tunnel only Enable configured App/Gateway Allow only the current Worker generation Controller failure does not affect the manager Failure → closed/sealed Immediately close related data entry points No direct exit when failure is detected Sealed generations cannot be reopened Automatic recovery Only when persisted intent is desired=running Manual stop/emergency stop must be cleared Old Worker/FD must be conclusively invalidated Create a new generation; retain the Instance ID Manual stop/emergency stop Advance intent revision and revoke authorization Stale recovery processes cannot reopen traffic Clearing the emergency state still leaves it stopped The user must explicitly start it afterward Manager or Platform restart Seal old generations Retain only sandbox leases proven closed Replace running Workers Stop intent cannot be proven → closed Only Instance Manager can approve CommitResume. ApplyPolicy alone cannot reopen traffic. A CPU/Store/Metrics pause must not create an unauthorized direct-host fallback.

Figure 5Instance startup and fault-recovery flowResume in the diagram requires current intent, worker generation, and Platform enforcement evidence.(scroll horizontally to inspect the full diagram)

A successful WireGuard handshake does not mean I can let Apps start accessing the internet.

When starting an Instance, first build the isolated environment and keep it closed by default.
After the worker starts, allow only the WireGuard handshake, DNS, and health probes.
Open App and Gateway traffic only after confirming that the current configuration, Platform policy, and worker generation are all correct.

If any intermediate step fails, remain blocked.
Rebuilding a worker must always use a new generation. An old generation that has been Seal cannot be reopened.
If the Manager is replaced, the Platform epoch changes, or the user presses Start/Stop during this period, previously issued commands must no longer take effect.

I split the Watchdog into two layers.
When the Watchdog inside an Instance detects a problem, it closes its own ingress first and then reports the fault.
The outer Instance Manager is responsible for recording intent, reclaiming old resources, and rebuilding.
I do not want both sides competing to restart the same Instance at the same time.

Manual Stop and EmergencyStop must have the highest priority.
Suppose the database fails just when Stop is pressed, and it is unclear whether the latest stop intent was saved successfully. The system must not automatically reopen using the old desired=running .
At that point, I would rather enter recovery_required and wait for a person to confirm.

As for whether the original TCP session can reconnect after a crash, I am not trying to guarantee that.
Losing a connection is better than using the wrong exit.

Connection state cannot be only Connected/Disconnected

I do not want the UI to have only Connected/Disconnected states.
For example, I set A to desired_state=running, but it is currently reconnecting, and enforcement=blocked.
These three things should be displayed separately.
Wanting it to keep running and whether it can currently send traffic out are different things.

Startup, fail-closed behavior, and automatic recovery: START/ACTIVATE sequence diagram

The flow first writes desired=running to Store. Manager then reserves the generation and identity lease and asks Platform to execute PrepareClosed.
The worker initially starts in probe_only mode.
Only after confirming that tunnel, DNS, policy, and current authorization information all agree does it execute CommitResume and Activate.

START/ACTIVATE sequence diagram All payload gates are closed by default. Only current Manager and Platform evidence can authorize opening. START/ACTIVATE startup and opening sequence diagram Data gates are closed by default. Only verified evidence from the current Manager and Platform can approve opening. User interface VPN Core Store service Instance manager Platform service Instance Worker 01 · Start+version comparison 02 · Persist desired=running with CAS 03 · Issue start intent 04 · Reserve Worker generation and key lease 05 · PrepareClosed: close first 06 · Return sandbox/policy evidence 07 · Create Worker and restricted FDs 08 · Deliver restricted resources and start 09 · Deliver single-use secret lease 10 · Configure/Start: probes only 11 · VPN tunnel and DNS verification complete 12 · CommitResume: approve opening 13 · Activate: formally ready 14 · Operation successful 15 · Return latest state through SSE

Sequence diagram ASTART/ACTIVATE sequence diagramFrom persisted intent and a closed environment to probe-only, with Activate authorized only at the end.(scroll horizontally to inspect the full diagram)

05How is the first Provider created? This is a bootstrap problem

Provider initial connection and per-access checks The first Profile automatically creates an Instance. The latch is sealed after revoking the original network. All Providers share a manually selected exit, but each Adapter checks independently and repeats preflight for every HTTP request. Figure 6|Provider first bootstrap, exit switching, and per-request verification Initial Provider creation job One account/only restricted host networking authorized 10-minute authorization/one 60-second session Checks are still required for every API request Obtain the first valid Profile Validator and persisted unique child operation Controller → Instance Manager Automatically create and start an Instance New Instance passes preliminary probes Independent tunnel/DNS/system restrictions Candidate connection is for health probes only Close the original direct exit 1.Persist the closing intent 2.Revoke all direct sockets 3.Record permanent latch and exit binding Globally selected Instance All Providers share the same exit Switch or disable only manually No automatic fallback to direct host access Blocked/disabled Still display the originally selected Instance Stale monitoring information must not count as healthy Recovery must not override a manual stop Every actual Provider API request: including retries, pagination, token refresh, and reconciliation 1.Plan and reserve quota Fix the IP family and immutable request Reserve IP-probe and API quotas Read the current connection identity 2.Provider exit IP check Use the same connection/IP family as the API Check IP, location, and provider ownership separately Probe requests carry no account credentials 3.Issue a single-use request permit Bind request, route, and connection generation Nonce+2-second dispatch deadline The checking flow does not call itself recursively 4.Atomic verification before sending Recheck generation, IP family, and deadline Consume permit before the first API byte No automatic redirects or hidden retries 5.Execute Provider API Send only through the selected VPN tunnel Remote mutation result unclear → unknown Reconcile first, then decide whether to retry Provider management panel Keep selection even when Instance is offline Exit IP/country/city/time Provider ownership may be unconfirmed IP observations cannot prove DNS, IPv6, QUIC, or all App paths. Platform restrictions must enforce protection. After switching: no host DNS, host HTTP, or automatic use of another exit.

Figure 6Provider first bootstrap, exit switching, and preflight checks for every requestThe restricted direct-access authorization for the first bootstrap is revoked after switching to VPN. Automatic fallback is not allowed.(scroll horizontally to inspect the full diagram)

There is also a problem with Providers.
When VPN Manager is first installed, there is no VPN Profile at all. I have to first obtain an account, device, and WireGuard configuration from Mullvad or AIRVPN.
So where should the first management request go?

My current idea is to allow conditional direct access only for the first time.
The Provider worker receives a time-, account-, HTTPS-origin-, DNS-, and traffic-budget-limited BootstrapGrant.
Once the first valid Profile is obtained, Controller asks Instance Manager to create and start the first VPN.

After confirming that the tunnel can handle management requests, first write the intent to close direct access into Store, then revoke the original grant and sockets, and only finally mark bootstrap_consumed=true and configure the global Provider exit.
This order must not be mixed up. In particular, switching must not be declared complete while direct access is still alive.

After that, all Provider APIs go through the VPN Instance selected by the user.
If that Instance fails, display blocked. Switching back to the host or automatically switching to another Instance is prohibited.
If it really cannot be handled, the user can manually select a new exit or explicitly authorize a one-time rebootstrap.

I also want an IP preflight before every actual Provider HTTP request is sent.
Including retry, pagination, and token refresh, the same route tuple, IP family, and connector must be confirmed, and a ProviderRequestPermit valid only for this request must be obtained before sending.
For now, the tentative preflight deadline is 3 seconds and the dispatch window is 2 seconds.
This will be slower and use more checker quota, but at least it will not treat an IP shown in the UI several minutes ago as evidence for current routing.

The UI needs to display several things separately: which Instance is selected, whether this request actually passed through a controlled tunnel, and whether the external IP is that Provider's exit.
For example, selecting a Mullvad Instance to call AIRVPN's management API can fully comply with my configuration.
An external IP that is not AIRVPN does not imply a traffic leak.

What should the Provider Adapter be responsible for?

I want provider integrations to follow the same flow as much as possible.
Profile validation, Instance creation, and exit binding are handled by shared control logic. Only Mullvad's and AIRVPN's respective device, server, and port-forward APIs are assigned to their Adapters.
When adding a Provider, I do not want every Adapter to have permission to directly start or stop Instances or open host sockets.

Provider APIs have another problem: a timeout after sending a request does not mean the other side did not execute it.
For example, the device may actually have been created successfully, but the response was not received.
In that case, first record it as unknown and use the operation journal to query the remote state. It should not simply be sent again.

PROVIDER first-bootstrap sequence diagram

To count the entire bootstrap as complete, we must confirm that the new Instance can send management requests and that the original direct authorization has actually been revoked. Only then do we CommitEgress.
If a result during switching cannot be confirmed, I would rather first stay blocked and establish the state clearly.

PROVIDER first-bootstrap sequence diagram Initial restricted direct access → first Profile → automatic Instance startup → seal/revoke → CommitEgress. Provider first VPN creation sequence diagram Initial restricted direct access → first Profile → automatic Instance creation → close direct access → bind VPN exit. UI/VPN Core Store service Platform service Provider worker process Provider API Instance Manager VPN Instance 01 · Create bootstrap record 02 · Prepare time-limited direct authorization 03 · Deliver only the approved direct channel 04 · Exit IP preflight+obtain Profile 05 · Return Profile 06 · Save Profile and unique child operation 07 · Automatically create and start Instance 08 · Close environment and create Worker 09 · Deliver FDs and start Worker 10 · VPN/DNS/management channel ready 11 · Persist closing intent 12 · Revoke direct authorization and sockets 13 · Return evidence verifying revocation 14 · Confirm exit and permanent latch 15 · Use selected Instance thereafter

Sequence diagram BPROVIDER first-bootstrap sequence diagramBootstrapGrant → first Profile → Instance → Seal/Revocation → CommitEgress.(scroll horizontally to inspect the full diagram)

06I want to see every VPN's traffic and resource use clearly

I do not want VPN Manager to end up with just a few green lights telling me the VPN is connected.
I want to know which Instance is consuming traffic, how many active connections a Gateway has, whether DNS latency has risen, and which process is occupying CPU, memory, and queues.

I will separate Metrics Service and Performance Manager.
Metrics Service collects statistics such as Instance counters, Gateway payload bytes, WireGuard UDP bytes, DNS successes and failures, active flows, CPU, and RSS.
Performance Manager is responsible for analyzing throughput, latency, and resource efficiency, or running benchmarks I explicitly start.
It cannot change Instance state on its own just to measure speed.

Traffic-observation and performance-analysis architecture Instance local counters are passed to Metrics Collector without blocking, then compressed and stored. UI and Performance Manager query aggregated data. App payload does not pass through this chain. Figure 7|Traffic-metrics collection and the non-blocking observation chain Instance Metrics Metrics collector Compression and aggregation Store/metrics database Performance manager React interface Cumulative counters every second Does not forward packet contents Sequence validation/deduplication/gap marking Samples may be dropped under overload CBOR+Zstd Time aggregation/retention period Separate control and metrics databases Bounded data queries Analysis/performance benchmarks Rate/latency/data gaps The observation chain carries only statistics. Actual App/Gateway traffic still passes through its own Instance.

Figure 7An independent observation chain for traffic data from Instance to UISaturated Metrics may reduce sampling completeness, but must not make the data plane wait. Charts must mark gaps.

Metrics should ideally not exert backpressure on the data plane.
Each Instance sends one bounded snapshot per second. Collector validates sequence and deduplicates, followed by CBOR/Zstd compression and aggregation.
If the collector is congested, just drop a few samples and mark a gap . Do not make packets wait for the database.

Traffic figures must not be calculated carelessly either.
Gateway payload bytes and WireGuard UDP bytes are statistics from different layers. They cannot be added together as total traffic.
On counter reset or a data interruption, charts must show a gap or unknown, not simply draw 0.
Goodput also requires receiver-side confirmation. It cannot be calculated from sender counters alone.

Traffic monitoring does not need to record everything either.
Collecting counters, histograms, and necessary error data by default is enough.
I do not want to put every DNS query name, full URL, or packet payload into Metrics.