diff --git a/docs/superpowers/specs/2026-05-01-custom-best-exit-probe-target-design.md b/docs/superpowers/specs/2026-05-01-custom-best-exit-probe-target-design.md new file mode 100644 index 0000000..4e723fe --- /dev/null +++ b/docs/superpowers/specs/2026-05-01-custom-best-exit-probe-target-design.md @@ -0,0 +1,180 @@ +# Custom Best-Exit Probe Target Design + +Date: 2026-05-01 +Status: Approved design + +## Goal + +Allow each tunnel to define the TCP target used for exit-side quality probing instead of always probing `www.bing.com:443`. + +The custom target must be used consistently by: + +- `best` exit scoring: each exit probes the configured target to measure exit-to-public quality. +- Tunnel quality monitoring: the existing exit-side quality check probes the same configured target. + +If a tunnel does not configure a target, behavior remains compatible with today: `www.bing.com:443`. + +## Non-Goals + +- Do not add HTTP/HTTPS request probing in this phase. The probe remains TCP host/port measurement. +- Do not add a global default target setting in this phase. +- Do not require existing tunnels to be edited or migrated manually. +- Do not change the `best` switching thresholds, confirmation rounds, cooldowns, or runtime chain ordering semantics. +- Do not add frontend test infrastructure. + +## User-Facing Behavior + +Each tunnel form gets a compact quality target section: + +- Host input, placeholder `www.bing.com`. +- Port input, placeholder `443`. +- Helper text: this target is used for tunnel quality detection and `best` optimal-exit scoring; leaving it empty uses `www.bing.com:443`. + +Tunnel list/get responses include the configured target so edit forms can round-trip it. The UI displays the effective target near quality/best-exit information as `测试目标:host:port`. + +## Data Model + +Add nullable/default-compatible fields to `model.Tunnel`: + +- `ProbeTargetHost string` mapped to `probe_target_host`, `type:text`, default `''`. +- `ProbeTargetPort int` mapped to `probe_target_port`, default `0`. + +Effective target resolution: + +- If `ProbeTargetHost` is non-empty and `ProbeTargetPort` is valid, use it. +- Otherwise use `www.bing.com:443`. + +The existing `TunnelQuality` persisted fields `exit_to_bing_latency` and `exit_to_bing_loss` remain unchanged for compatibility. They will semantically mean exit-to-configured-test-target after this change. API/UI labels should avoid saying `Bing` for new displays. + +## Validation + +On create/update: + +- Empty host and empty/zero port are allowed and mean default target. +- If either host or port is set, validate both as a pair. +- Host is trimmed and must not contain URL scheme, path, query, or whitespace. +- Host can be a domain, IPv4, or IPv6 literal. Bracketed IPv6 input should be normalized by removing surrounding brackets. +- Port must be an integer from `1` to `65535`. +- Do not perform network probing during save; external network failures must not block configuration changes. + +Errors should be specific, for example: + +- `测试目标 Host 不能为空` +- `测试目标端口必须是 1-65535` +- `测试目标 Host 不能包含协议或路径` + +## Backend Flow + +Introduce a small value/helper near the tunnel quality and best-exit code: + +```go +type tunnelProbeTarget struct { + Host string + Port int +} +``` + +Helpers: + +- `defaultTunnelProbeTarget() tunnelProbeTarget` returns `www.bing.com:443`. +- `normalizeTunnelProbeTarget(host string, port int) (tunnelProbeTarget, bool, error)` validates user input; the boolean indicates whether the user explicitly configured a target. +- `effectiveTunnelProbeTarget(tunnel *model.Tunnel) tunnelProbeTarget` returns configured target or default. + +Use the effective target in `tunnelQualityProber.probeTunnel`: + +- Type 1 and unknown tunnel fallback probes entry node to effective target instead of hardcoded Bing. +- Type 2 probes the selected/current exit node to effective target instead of hardcoded Bing. +- `probeBestExitOwners` receives the effective target and passes it into best-exit owner scoring. + +Use the effective target in `evaluateBestExitOwner`: + +- Owner-to-exit measurement stays unchanged. +- Exit-to-public measurement probes `target.Host:target.Port` instead of `bestExitPublicTargetHost:bestExitPublicTargetPort`. +- The per-round public probe cache key must include node ID plus target host and port so future extensions cannot reuse measurements across different targets. + +## API Shape + +Tunnel list/get data includes: + +```json +{ + "probeTargetHost": "example.com", + "probeTargetPort": 443 +} +``` + +For old/default tunnels, return empty host and `0` to represent `use default`. The edit form must preserve default-as-empty unless the user explicitly saves a custom target. + +Quality monitoring response includes effective target display metadata: + +```json +{ + "probeTargetHost": "www.bing.com", + "probeTargetPort": 443 +} +``` + +Existing `exitToBingLatency` and `exitToBingLoss` keys stay to avoid breaking frontend and external consumers. + +## Frontend Flow + +Extend `ChainTunnel` only if needed for node-level data; the target belongs to the tunnel, so `Tunnel` and `TunnelForm` get: + +- `probeTargetHost?: string` +- `probeTargetPort?: number` + +On edit: + +- Populate form fields from tunnel response. +- Empty or zero means default target. + +On submit: + +- Trim host. +- Convert blank port to `0`. +- Send `probeTargetHost` and `probeTargetPort` with create/update payload. + +Display: + +- In the form helper, show default target behavior. +- In quality/best-exit display areas, avoid `Bing` wording; prefer `测试目标` or the concrete `host:port`. + +## Error Handling + +- Invalid target input returns a normal API error envelope with a specific message. +- Probe failures use existing quality error paths and best-exit scoring failure entries. +- If all exit-to-target probes fail, best-exit behavior remains the same as today when all Bing probes fail: no valid best decision is applied from that round. + +## Testing + +Backend tests: + +- Normalize default target when host/port are empty. +- Reject partial host/port configuration and invalid port ranges. +- Reject host values with URL scheme/path/whitespace. +- Create/update tunnel persists `probeTargetHost` and `probeTargetPort`. +- `ListTunnels` returns target fields. +- `tunnelQualityProber` uses configured target instead of `www.bing.com:443`. +- `best` scoring uses configured target for exit-to-target probes. +- Empty target preserves old default `www.bing.com:443` behavior. + +Frontend verification: + +- `pnpm run build` passes. +- Manual UI check: create/edit tunnel with blank target and custom target, confirm payload and round-trip display. + +## Rollout And Compatibility + +- Existing tunnels continue using `www.bing.com:443` because empty target resolves to default. +- SQLite/PostgreSQL schema changes are handled by existing auto-migration. +- Historical `TunnelQuality` rows keep existing columns and are not rewritten. +- No runtime agent change is required; the panel already performs these quality probes through existing node ping APIs. + +## Open Decisions + +None. User-approved decisions: + +- Per-tunnel fields are `host + port`. +- The target applies to both `best` scoring and tunnel quality monitoring. +- Probe type remains TCP host/port. +- Empty target defaults to `www.bing.com:443`.