Skip to content

Add PROFILE config key with mobile / censored / throughput / stable presets#169

Open
Isusami wants to merge 4 commits into
masterking32:mainfrom
Isusami:series-a-profile-presets
Open

Add PROFILE config key with mobile / censored / throughput / stable presets#169
Isusami wants to merge 4 commits into
masterking32:mainfrom
Isusami:series-a-profile-presets

Conversation

@Isusami
Copy link
Copy Markdown
Contributor

@Isusami Isusami commented May 11, 2026

Summary

Adds a single PROFILE config key that picks a coherent group of defaults so non-technical users do not have to tune ~50 individual knobs. Four presets ship:

  • stable — current defaults (no-op; explicit baseline).
  • mobile — slower ping cadence (2× cooldown / cold intervals + proportional thresholds), PACKET_DUPLICATION_COUNT=1, raised NACK initial delay, halved local DNS cache.
  • censored — survivability-first: PACKET_DUPLICATION_COUNT=4, wider MTU range (28–200 up / 60–900 down), retry-friendly ARQ (ARQInitialRTOSeconds=2.0, ARQMaxDataRetries=2400), BASE_ENCODE_DATA=true.
  • throughput — bandwidth-first: PACKET_DUPLICATION_COUNT=1, RESOLVER_BALANCING_STRATEGY=4 (lowest latency), ZSTD compression on both directions, narrow MTU range (120–150 up / 400–500 down).

Explicit keys in client_config.toml always win over the preset. PROFILE unset or set to custom/stable is a no-op — existing configs are unaffected.

Example usage

Step 1 — Pick the profile that matches your situation:

Your situation Use
I want what I had before, unchanged stable (or leave it out)
I'm on a phone / mobile data mobile
My network blocks or drops a lot of traffic censored
My network is clean and I want maximum speed throughput

Step 2 — Add one line to client_config.toml:

PROFILE = "censored"

That's it. The client now uses sensible settings for that situation. No other config changes needed.

Optional — Override a single knob:

PROFILE = "censored"
ARQ_MAX_DATA_RETRIES = 3000   # whatever you set wins over the preset

Anything you write explicitly in your config always wins over the preset. So you can pick a preset for everything you don't care about and tweak just the one or two knobs you do.

How it works

Preset overrides are applied after TOML decode and before finalizeClientConfig. The loader now uses toml.MetaData to know which keys were explicitly defined in the user's TOML, and the preset only writes a field if that field was not explicitly set. This makes precedence deterministic and safe.

Why

The current client_config.toml.simple lists ~50 knobs that interact in non-obvious ways (ping cadence, duplication, ARQ RTO, MTU range, compression). Most users never tune them. Presets give a one-word choice that picks consistent defaults for the four most common use cases.

Changes

  1. A1PROFILE plumbing, stable preset (no-op), toml.MetaData-driven explicit-key precedence, and the snapshot-test infrastructure (testdata/profile_*.golden.json).
  2. A2mobile preset.
  3. A3censored preset.
  4. A4throughput preset (operator-choice preset; explicitly trades duplication-for-survivability for raw bandwidth).

Backward compatibility

  • PROFILE omitted → identical to today's behavior.
  • PROFILE=stable → byte-for-byte identical to today (snapshot-asserted).
  • An explicit TOML key always overrides the preset's opinion (precedence-tested).
  • No wire-protocol change. Server-side untouched.

Tests

  • Snapshot tests per preset against internal/config/testdata/profile_*.golden.json (regenerate with MASTERDNSVPN_UPDATE_PROFILE_GOLDEN=1 go test ./internal/config/ -run TestProfile.*Snapshot).
  • Precedence test confirming explicit keys win over presets.
  • Reflection guard test catches profileOverrides field-name typos at test time so a misnamed knob in a future preset fails loudly instead of silently no-op'ing.
  • Unknown PROFILE value returns a config-load error.

go build ./..., go test ./..., go vet ./... all pass.

Risk

Low. The feature is opt-in (no PROFILE = no behavior change), all changes are in internal/config/, no wire-protocol surface is touched, and the snapshot tests guard against silent drift.

Open questions for maintainer review

A few preset values were judgment calls; happy to adjust:

  1. mobile cadence multiplier — literal 2× on PingCooldownIntervalSeconds and PingColdIntervalSeconds. Acceptable?
  2. mobile NACK delay — bumped from 0.1s0.6s (6×). If a smaller bump is preferred (e.g. 0.3s), trivial to change.
  3. censored duplication — picked 4 (max-survivability inside the 1..10 clamp). Could be 3.
  4. censored MTU envelope — picked 28..200 upload and 60..900 download. Wider than defaults; still inside finalizeClientConfig's validation.
  5. throughput MTU envelope120..150 upload, 400..500 download. Tight but leaves the prober a workable window.

Each of these is one line in internal/config/profiles.go; happy to revise based on operator experience.

Future work (out of this PR)

  • mobile and throughput will get cleaner adaptive-duplication wiring once MIN_DUP/MAX_DUP land in a follow-up PR. Until then, the presets set PACKET_DUPLICATION_COUNT directly; the source comments mark the migration point.
  • A -doctor subcommand to validate a chosen profile's resolved config is planned as a separate PR.

Isusami added 4 commits May 11, 2026 23:50
Introduces the PROFILE knob with the precondition the architect flagged:
the loader now builds a "defined" map of TOML keys explicitly set by the
operator (mirroring the existing JSON loader path) and applyProfile only
overrides knobs the operator did not set. The stable preset is an empty
override bag, intentionally identical to omitting PROFILE.

Tests cover stable=omitted equivalence, unknown-profile error, the
precedence guarantee (explicit PACKET_DUPLICATION_COUNT wins over the
preset), a snapshot infrastructure for later presets, and a reflection
guard that catches profileOverrides field-name typos at test time.
Optimizes the cadence ladder for high-latency, battery-constrained links:
doubles the cooldown/cold ping intervals and the matching warm/cool/cold
thresholds so radios get more sleep, drops PacketDuplicationCount to the
clamp floor (1) for bandwidth, lifts the data NACK initial delay to 0.6s
so a single jittery RTT does not flood resend requests, and halves the
local DNS cache to keep memory pressure down.

Coherence note: a future B-series PR will need to extend this preset to
also set MIN_DUP/MAX_DUP coherently once adaptive duplication lands; the
preset comment documents that boundary explicitly.
Tuned for hostile/lossy paths where survivability is paramount: high
duplication (4, well within the 1..10 clamp), more aggressive ping
cadence so a failing resolver is detected sooner, a wider MTU search
window in both directions so the prober can find any path that works,
more retry-friendly ARQ parameters (initial RTO 2s, max data retries
2400), and BASE_ENCODE_DATA on by default for resolvers that prefer
label-safe payloads.
Optimized for raw bandwidth on healthy paths. Drops PacketDuplicationCount
to 1, picks the BalancingLowestLatency selector (strategy 4), turns on
ZSTD compression in both directions, and narrows the MTU search range so
the prober converges on a stable working size instead of widening probes.

This preset deliberately trades duplication-for-survivability for raw
bandwidth. Operators must opt in explicitly via PROFILE = "throughput" —
never a silent default; it is not appropriate as a global default for the
project.
@Isusami Isusami marked this pull request as ready for review May 11, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant