feat: add Device::suggest_exit_node mirroring Go SuggestExitNode#267
Conversation
|
Warning Review limit reached
More reviews will be available in 5 minutes and 2 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Port Go v1.100.0 suggestExitNodeUsingDERP (the classic DERP-region-latency exit-node suggestion path) as a pure, unit-testable function with the region and node selectors injected as closures, exactly as Go does, so the algorithm is deterministic under test while production uses uniform-random selection. The candidate predicate requires a peer be online, carry the suggest-exit-node node-capability, and advertise an exit route. Per the IPv4-only fork posture it accepts a peer advertising 0.0.0.0/0 rather than Go's strict both-v4-and-v6 requirement, matching the existing family-agnostic exit-node check. Among 2+ candidates it partitions by home DERP region, picks the lowest-latency region with a lowest-region-id tiebreak, and applies prev-suggestion stickiness within the winning region. Region-less peers fall back without geo weighting; the Mullvad geo path, traffic-steering path, and Location field are deferred. Runtime::suggest_exit_node gathers the latest netcheck report and peer set, runs the pure function, and remembers the result for stickiness. Device exposes it for the daemon's exit-node suggest command; a no-preferred-DERP precondition surfaces as a typed error, an empty candidate set as Ok(None). Signed-off-by: Sergio <sergio1993_1@hotmail.com>
dd42e1f to
bcc2780
Compare
What
Engine ask #24 —
Device::suggest_exit_node(), reproducing Go v1.100.0'ssuggestExitNodeUsingDERP(the classic DERP-region-latency path) behaviorally. The daemon consumes it astnet exit-node suggest(bead tsd-jz2).Algorithm (faithful to Go
ipn/ipnlocal/local.go)A pure
suggest_exit_node(report, candidates, prev_suggestion, select_region, select_node)function, with theselect_region/select_nodeclosures injected exactly as Go does (so tests pass deterministic stubs; production uses uniform RNG):ErrNoPreferredDerp("try again later").suggest-exit-nodenode-cap + advertises an exit route. (Self excluded — not a peer.)Ok(None)(no suggestion, not an error). 1 candidate → returned directly.minLatencyDERPRegion); no usable latency →select_region(uniform). Within the region,select_nodeapplies prev-suggestion stickiness (return the prior pick if still a candidate) else uniform random.Determinism (matches Go exactly)
There is no seed/hash tiebreak. Determinism comes only from (a) lowest-latency-region + lowest-region-ID tiebreak and (b) prev-suggestion stickiness (persisted in a
Runtimecell, mirroring Go'slastSuggestedExitNode); the pick among equal candidates is uniform-random, as in Go.Parity decision (IPv4-only fork)
Go's
ContainsExitRoutesrequires both0.0.0.0/0and::/0. This fork is IPv4-only, so the candidate predicate accepts a peer advertising0.0.0.0/0(family-agnosticprefix_len == 0, matching the existingis_exit_nodecheck) — documented as a deliberate deviation, recorded in DEFERRED-QUESTIONS. Without it the feature would be inert on a v4-only tailnet.Deferred (Phase 2, documented)
The Mullvad location/geo-distance path (only reached when no candidate has a home DERP) and the
traffic-steeringalgorithm (off unless the tailnet enables it).ExitNodeSuggestionships{id, name};Locationis deferred with the geo path.Tests
17 tests ported from Go's
local_test.gotable cases via the pure fn + deterministic stubs: no-report error, 0/1/2+ candidates, lower-latency-region wins, region-ID tiebreak, no-latency→select_region fallback, prev-suggestion stickiness, and the predicate exclusions (no cap / no exit route / offline).Gates (local, all green — re-verified after rebasing onto current main)
cargo test -p geiserx_ts_control -p geiserx_ts_runtime -p geiserx_tailscale(248 + 371 + facade) · clippy-D warnings(3 crates +tunlane) ·cargo fmt --check·cargo doc(broken_intra_doc_links=deny) ·cargo run -p checks.Part of the daemon engine-asks batch; lands in the next release. After merge the daemon bumps + adds
tnet exit-node suggest.Signed-off-by: Sergio sergio@geiser.cloud
Created using Claude Code (Opus 4.8)