feat: multi-forwarder with SRTT-based failover #77

Merged
razvandimescu merged 4 commits from feat/multi-forwarder-failover into main 2026-04-11 05:26:59 +08:00
razvandimescu commented 2026-04-11 04:17:28 +08:00 (Migrated from github.com)

Summary

  • Multiple upstreams: address accepts a string or array with optional per-server port ("1.2.3.4:5353", "[::1]:5553")
  • Fallback pool: new fallback config field, tried only when all primaries fail
  • SRTT-based selection: UDP primaries sorted by smoothed round-trip time; fastest tried first
  • Sequential failover: on failure, records SRTT penalty and tries next candidate
  • Backwards compatible: address = "1.2.3.4" still works, existing configs unchanged
  • 14 new tests (195 total): config parsing, address formats, pool labels, failover behavior, upstream re-detection
[upstream]
address = ["192.168.1.1", "192.168.1.2:5353"]
fallback = ["8.8.8.8", "1.1.1.1"]

Closes #34 (items 1, 2, 3)

Test plan

  • make all passes (195 tests, clippy, fmt, audit)
  • Single address = "..." string config still works (backwards compat)
  • Array config with mixed ports resolves correctly
  • Fallback kicks in when all primaries are unreachable
  • /stats shows preferred upstream via pool.label()
  • Network watcher re-detects upstream when auto-detected

🤖 Generated with Claude Code

## Summary - **Multiple upstreams**: `address` accepts a string or array with optional per-server port (`"1.2.3.4:5353"`, `"[::1]:5553"`) - **Fallback pool**: new `fallback` config field, tried only when all primaries fail - **SRTT-based selection**: UDP primaries sorted by smoothed round-trip time; fastest tried first - **Sequential failover**: on failure, records SRTT penalty and tries next candidate - **Backwards compatible**: `address = "1.2.3.4"` still works, existing configs unchanged - **14 new tests** (195 total): config parsing, address formats, pool labels, failover behavior, upstream re-detection ```toml [upstream] address = ["192.168.1.1", "192.168.1.2:5353"] fallback = ["8.8.8.8", "1.1.1.1"] ``` Closes #34 (items 1, 2, 3) ## Test plan - [x] `make all` passes (195 tests, clippy, fmt, audit) - [x] Single `address = "..."` string config still works (backwards compat) - [x] Array config with mixed ports resolves correctly - [x] Fallback kicks in when all primaries are unreachable - [x] `/stats` shows preferred upstream via `pool.label()` - [x] Network watcher re-detects upstream when auto-detected 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.