Skip to content

vpn: treat the server-driven proxyless rule-set detour as infrastructure#549

Merged
myleshorton merged 16 commits into
mainfrom
fisk/mirror-rulesets-detour
Jul 6, 2026
Merged

vpn: treat the server-driven proxyless rule-set detour as infrastructure#549
myleshorton merged 16 commits into
mainfrom
fisk/mirror-rulesets-detour

Conversation

@myleshorton

@myleshorton myleshorton commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

Let the server introduce optional/infrastructure outbounds (like a proxyless rule-set-fetch detour) without a client release. The client merges any outbound the server sends into its box config so references resolve, but excludes the ones the server marks non-selectable from the auto/manual proxy groups.

Why

The companion server change (getlantern/lantern-cloud#2936) sends a proxyless outbound — the Outline smart dialer (DoH + TLS-record fragmentation) — and points remote rule-set fetches at it via download_detour, so a cold-start fetch survives SNI-based DPI throttling (the CN cold-start failure, FD-179514).

But a server-sent outbound would otherwise become a user-selectable proxy — and worse, auto (urltest) could pick the low-latency direct+frag dialer and route traffic directly, exposing the user's IP. So the client must treat such outbounds as infrastructure. Rather than hardcode the proxyless tag, the client honors a generic server-declared list, so future optional outbounds need no client change.

Change

  • vpn/boxoptions.gomergeAndCollectTags now excludes the base reserved tags (auto/manual/direct/block) and any tag in BoxOptions.NonSelectableOutbounds from the returned selectable set (they're still merged into the config).
  • backend/radiance.go — populates BoxOptions.NonSelectableOutbounds from ConfigResponse.NonSelectableOutbounds.
  • Deletes vpn/proxyless.go — the client no longer knows any specific infra tag.
  • Keeps cmd/geosite-throttle-test (CN-residential validation harness).

Dependencies / rollout

  • feat: add NonSelectableOutbounds to ConfigResponse common#27 (adds NonSelectableOutbounds to ConfigResponse) is merged; go.mod points at the merged common main.
  • Server half: getlantern/lantern-cloud#2936. Its feature flag's Unleash config must constrain config.client_version to the release carrying this change — older clients (without this filter) would surface the proxyless outbound as selectable. Context: getlantern/engineering#3657.

Testing

  • go test ./vpn/TestMergeAndCollectTags_ExcludesNonSelectable (server-declared + reserved tags excluded, but still merged into the config), plus the existing build tests.
  • go build ./vpn/... ./backend/... ./cmd/geosite-throttle-test/, go vet, gofmt clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR

Summary by CodeRabbit

  • New Features
    • Added support for server-provided non-selectable outbounds/endpoints, keeping them in the final configuration while excluding them from auto/manual selector choices.
    • Introduced a new CLI diagnostic tool to test download behavior under different TLS/TCP connection strategies.
  • Bug Fixes
    • Ensured non-selectable outbounds are correctly carried into generated box options.
  • Tests
    • Added coverage to confirm non-selectable and reserved tags are excluded from selector sets while still being merged into configuration.
  • Chores
    • Updated a Go dependency.

Remote rule-sets (geosite-cn, geosite-ir, geoip-ru, ...) are fetched at
cold start over download_detour:direct, which censors throttle at the SNI
layer. Repoint every direct-fetched remote rule-set to a proxyless "mirror"
outbound — the outline-sdk smart dialer with DoH resolution + TLS-record
fragmentation — so the fetch survives the throttle without a proxy server.
Detour-only (not a selectable proxy); the outbound is injected only when a
rule-set was actually repointed, and its first strategy is plain direct so
uncensored fetches are unaffected.

Region-agnostic: replaces the earlier geosite-cn-only scoping, since IR/RU
rule-sets fetch from the same throttled CDNs (jsDelivr, raw.githubusercontent).

Includes cmd/geosite-throttle-test, the CN-residential harness used to
validate that fragmentation defeats the SNI-keyed throttle (fastly.jsdelivr
+ tlsfrag: full .srs in 3-5s; s3 excluded — its throttle is rate-based).

engineering#3657

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
Copilot AI review requested due to automatic review settings July 2, 2026 12:55
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds server-declared non-selectable outbound tags to VPN box options, wires them from backend config and tests the filtering, updates one dependency, and introduces a CLI that measures HTTPS fetch behavior through an Oxylabs CONNECT tunnel under multiple dialing strategies.

Changes

Non-selectable outbound tags

Layer / File(s) Summary
Field and tag filtering
vpn/boxoptions.go
BoxOptions gains NonSelectableOutbounds, buildOptions passes it into mergeAndCollectTags, and tag collection excludes reserved and configured non-selectable outbound or endpoint tags while still merging them into config.
Config wiring and test coverage
backend/radiance.go, vpn/nonselectable_test.go
getBoxOptions() copies NonSelectableOutbounds from fetched config, and the new test verifies excluded tags stay out of the selectable set while remaining present in the merged outbounds and endpoints.
Dependency version bump
go.mod
Updates the required github.com/getlantern/common pseudo-version in go.mod.

Geosite throttle test CLI

Layer / File(s) Summary
Command setup and tunnel dialer
cmd/geosite-throttle-test/main.go
Adds the command documentation, Oxylabs constants, CONNECT tunnel setup, buffered stream wrapper, and tls.Conn adapter.
Fetch helper
cmd/geosite-throttle-test/main.go
Adds the HTTPS fetch helper that uses a transport.StreamDialer, forces HTTP/1.1, disables compression and redirects, drains the body, counts bytes, and returns duration and status-based errors.
Strategy loop and output
cmd/geosite-throttle-test/main.go
Adds startup validation, strategy construction, session-user reuse, repeated target fetches, and per-run result printing.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and matches the main change: making the server-driven proxyless detour infrastructure-only.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fisk/mirror-rulesets-detour

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR routes sing-box remote rule-set downloads through a new proxyless “mirror” detour outbound (outline smart dialer with DoH + TLS record fragmentation) to make cold-start rule-set fetches more resilient to SNI-based DPI throttling, and adds a validation harness for CN-residential testing.

Changes:

  • Add a mirror outbound (Outline smart dialer) and rewrite remote rule-sets with download_detour: direct/empty to use it.
  • Inject the outbound into built options only when a rule-set was repointed.
  • Add unit tests for the repointing logic and a standalone throttle-test command.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
vpn/mirror.go Introduces the mirror outbound definition and rule-set repointing logic.
vpn/mirror_test.go Adds tests covering rule-set repointing behavior.
vpn/boxoptions.go Applies repointing post-merge and conditionally injects the mirror outbound.
cmd/geosite-throttle-test/main.go Adds a CLI harness to validate fragmentation effectiveness via a CN residential CONNECT proxy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread vpn/mirror.go Outdated
Comment thread vpn/boxoptions.go Outdated
Comment thread cmd/geosite-throttle-test/main.go Outdated
Comment thread cmd/geosite-throttle-test/main.go Outdated
Comment thread cmd/geosite-throttle-test/main.go Outdated
Comment thread cmd/geosite-throttle-test/main.go
buildOptions now repoints direct-fetched remote rule-sets to the mirror
download_detour, so the golden assertions must expect that detour. Add an
expectMirrorDetour helper mirroring repointRuleSetsToMirror and apply it to
the smart-routing and ad-block expected rule-sets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 2, 2026 13:01
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Comment thread vpn/mirror.go Outdated
Comment thread vpn/boxoptions.go Outdated
Comment thread vpn/boxoptions.go Outdated
Comment thread cmd/geosite-throttle-test/main.go Outdated
Comment thread cmd/geosite-throttle-test/main.go Outdated
Comment thread cmd/geosite-throttle-test/main.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
cmd/geosite-throttle-test/main.go (3)

53-53: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Unchecked type assertion.

c.(*net.TCPConn) panics if the dial ever returns a different net.Conn implementation. Given net.Dialer.DialContext with network "tcp", this is currently safe, but a comma-ok assertion avoids a hard panic if this is ever refactored.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` at line 53, The type assertion in the
geosite throttle test is unchecked and can panic if DialContext ever returns a
non-TCPConn implementation. Update the connection handling in main.go around the
tcp variable assignment to use a comma-ok assertion and handle the failure path
gracefully instead of assuming c is always a *net.TCPConn.

82-92: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Missing MinVersion on the tls.Config.

Static analysis flags the absence of tls.MinVersion here. For this diagnostic tool the risk is minimal (fetches a public rule-set file, no sensitive data), but setting a floor is a trivial hardening.

🔧 Proposed fix
-		TLSClientConfig:     &tls.Config{NextProtos: []string{"http/1.1"}},
+		TLSClientConfig:     &tls.Config{NextProtos: []string{"http/1.1"}, MinVersion: tls.VersionTLS12},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` around lines 82 - 92, The fetch helper’s
TLS setup in fetch uses a tls.Config without an explicit minimum version, which
triggers the static analysis warning. Update the tls.Config passed to
http.Transport so it sets a MinVersion floor alongside NextProtos, keeping the
existing client behavior unchanged while hardening the TLS handshake.

Source: Linters/SAST tools


43-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicates the CONNECT-tunnel pattern from bypass.go.

connectDialer/bufConn reimplement the handshake and buffered-read wrapping already present as httpConnect/bufferedConn in bypass/bypass.go. Since this is a throwaway diagnostic command kept independent of the main module's internal packages, this is optional rather than essential.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` around lines 43 - 77, The CONNECT tunnel
logic in connectDialer.DialStream and bufConn duplicates the existing
httpConnect/bufferedConn implementation from bypass.go. Replace the ad hoc
handshake and buffered-read wrapper with the shared helper pattern if it can be
reused cleanly, or otherwise keep the diagnostic copy intentionally isolated and
minimal so it does not drift from the main implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/geosite-throttle-test/main.go`:
- Around line 48-68: DialStream is missing a read deadline for the CONNECT
handshake, so the http.ReadResponse call can block forever after the TCP
connection is established. Update connectDialer.DialStream to mirror the timeout
handling used by bypass.go’s httpConnect helper: set a deadline on the TCPConn
before reading the CONNECT response, then clear it after the response is read
successfully or on error. Keep the existing error handling around fmt.Errorf,
tcp.Close, and resp.Body.Close intact.

In `@vpn/boxoptions.go`:
- Line 448: Reserve the mirror tag before calling mergeAndCollectTags for the
injected outbound in boxoptions.go, since bOptions.Options is merged first and a
user-defined entry tagged mirror can collide with the appended outbound. Update
the tag collection logic around mergeAndCollectTags/opts/bOptions.Options so
mirror is pre-reserved or checked for duplicates before appending the outbound,
preventing sing-box startup rejection.

---

Nitpick comments:
In `@cmd/geosite-throttle-test/main.go`:
- Line 53: The type assertion in the geosite throttle test is unchecked and can
panic if DialContext ever returns a non-TCPConn implementation. Update the
connection handling in main.go around the tcp variable assignment to use a
comma-ok assertion and handle the failure path gracefully instead of assuming c
is always a *net.TCPConn.
- Around line 82-92: The fetch helper’s TLS setup in fetch uses a tls.Config
without an explicit minimum version, which triggers the static analysis warning.
Update the tls.Config passed to http.Transport so it sets a MinVersion floor
alongside NextProtos, keeping the existing client behavior unchanged while
hardening the TLS handshake.
- Around line 43-77: The CONNECT tunnel logic in connectDialer.DialStream and
bufConn duplicates the existing httpConnect/bufferedConn implementation from
bypass.go. Replace the ad hoc handshake and buffered-read wrapper with the
shared helper pattern if it can be reused cleanly, or otherwise keep the
diagnostic copy intentionally isolated and minimal so it does not drift from the
main implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 13b20c9b-2f56-40c0-8aa9-1e11b70f842c

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and 28a79df.

📒 Files selected for processing (4)
  • cmd/geosite-throttle-test/main.go
  • vpn/boxoptions.go
  • vpn/mirror.go
  • vpn/mirror_test.go

Comment thread cmd/geosite-throttle-test/main.go
Comment thread vpn/boxoptions.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
vpn/boxoptions.go (1)

448-456: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reserve the mirror tag before injecting the outbound.

mergeAndCollectTags (Line 448) merges user-supplied outbounds/endpoints into opts.Outbounds before mirrorOutbound() is appended (Lines 454-456). If a user config already defines an outbound or endpoint tagged "mirror", sing-box will reject the whole config at startup due to a duplicate outbound tag. This was already flagged in a prior review round on this line and remains unfixed.

🛡️ Proposed fix: guard against tag collision
 	tags := mergeAndCollectTags(&opts, &bOptions.Options)
 
 	// Route remote rule-set fetches through the proxyless "mirror" outbound so a
 	// cold-start fetch survives DPI throttling of the CDNs they're served from.
 	// Detour-only — deliberately not added to the selectable `tags`. Injected
 	// only when a rule-set was actually repointed (engineering#3657).
 	if repointRuleSetsToMirror(&opts) {
+		if slices.Contains(tags, mirrorOutboundTag) {
+			return O.Options{}, fmt.Errorf("config uses reserved outbound/endpoint tag %q", mirrorOutboundTag)
+		}
 		opts.Outbounds = append(opts.Outbounds, mirrorOutbound())
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vpn/boxoptions.go` around lines 448 - 456, The `mergeAndCollectTags` plus
`mirrorOutbound` flow can still collide with a user-defined `"mirror"` tag and
make sing-box fail at startup. Reserve or guard the `"mirror"` tag before
appending the outbound in this block so `opts.Outbounds` never contains a
duplicate tag, and keep the fix centered around `mergeAndCollectTags`,
`repointRuleSetsToMirror`, and `mirrorOutbound`.
🧹 Nitpick comments (1)
cmd/geosite-throttle-test/main.go (1)

83-92: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Missing MinVersion on TLS client config.

Static analysis flags the tls.Config used for fetches as missing an explicit minimum TLS version. Low risk here since this is a throwaway diagnostic tool and Go's client default is already TLS 1.2, but worth setting explicitly for hygiene.

🔧 Proposed fix
-		TLSClientConfig:     &tls.Config{NextProtos: []string{"http/1.1"}},
+		TLSClientConfig:     &tls.Config{NextProtos: []string{"http/1.1"}, MinVersion: tls.VersionTLS12},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` around lines 83 - 92, The TLS client setup
in main uses a tls.Config with only NextProtos, so add an explicit MinVersion
there to make the intended TLS floor clear. Update the http.Transport
construction where tr is built so the tls.Config includes a minimum version (for
example TLS 1.2) alongside the existing TLS settings, keeping the change
localized to the client used by the geosite throttle test.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@vpn/boxoptions.go`:
- Around line 448-456: The `mergeAndCollectTags` plus `mirrorOutbound` flow can
still collide with a user-defined `"mirror"` tag and make sing-box fail at
startup. Reserve or guard the `"mirror"` tag before appending the outbound in
this block so `opts.Outbounds` never contains a duplicate tag, and keep the fix
centered around `mergeAndCollectTags`, `repointRuleSetsToMirror`, and
`mirrorOutbound`.

---

Nitpick comments:
In `@cmd/geosite-throttle-test/main.go`:
- Around line 83-92: The TLS client setup in main uses a tls.Config with only
NextProtos, so add an explicit MinVersion there to make the intended TLS floor
clear. Update the http.Transport construction where tr is built so the
tls.Config includes a minimum version (for example TLS 1.2) alongside the
existing TLS settings, keeping the change localized to the client used by the
geosite throttle test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9e35ced-eb04-4253-8ab2-16b0e3ec34eb

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and 7ec341e.

📒 Files selected for processing (5)
  • cmd/geosite-throttle-test/main.go
  • vpn/boxoptions.go
  • vpn/boxoptions_test.go
  • vpn/mirror.go
  • vpn/mirror_test.go

…rness

- Remove engineering#3657 references from code comments (AGENTS.md: keep
  rationale in git history, not in-code).
- Guard the mirror-outbound append: skip when the merged config already
  defines the "mirror" tag, so it can't produce a duplicate outbound tag.
- Replace the throttle-test harness with its reviewed-final version (TLS-
  wrapped proxy hop so creds aren't sent in cleartext, checked CONNECT write,
  bounded read deadline, non-2xx treated as an error, no external script ref).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 2, 2026 13:19
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread vpn/boxoptions.go Outdated
Comment thread vpn/mirror_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/geosite-throttle-test/main.go (1)

49-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Go doc comments for the interface methods.

DialStream and the intentionally no-op CloseRead should document their contracts where declared.

Proposed doc comments
+// DialStream establishes a TLS-wrapped CONNECT tunnel through the Oxylabs gateway.
 func (d connectDialer) DialStream(ctx context.Context, addr string) (transport.StreamConn, error) {
+// CloseRead is a no-op because tls.Conn does not expose read-side half-close.
 func (tlsStreamConn) CloseRead() error { return nil }

As per coding guidelines, **/*.go: Use Go doc comments (// Foo ...) for exported identifiers and any unexported ones with non-obvious contracts.

Also applies to: 90-90

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` at line 49, The `connectDialer` methods
need Go doc comments where they are declared: add a comment on `DialStream`
describing that it returns a `transport.StreamConn` for the given address and
context, and document `CloseRead` as an intentional no-op so its contract is
clear. Keep the comments in the `connectDialer` method block and use standard
`// Name ...` doc style for these non-obvious method behaviors.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/geosite-throttle-test/main.go`:
- Line 49: The `connectDialer` methods need Go doc comments where they are
declared: add a comment on `DialStream` describing that it returns a
`transport.StreamConn` for the given address and context, and document
`CloseRead` as an intentional no-op so its contract is clear. Keep the comments
in the `connectDialer` method block and use standard `// Name ...` doc style for
these non-obvious method behaviors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cebe6ef5-51f5-4455-8f0f-49b4b1c102f6

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec341e and 880a3dd.

📒 Files selected for processing (3)
  • cmd/geosite-throttle-test/main.go
  • vpn/boxoptions.go
  • vpn/mirror.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • vpn/mirror.go
  • vpn/boxoptions.go

…repoint

- tagInUse checks both outbounds and endpoints before injecting the mirror
  outbound, since sing-box tags must be unique across both.
- expectMirrorDetour now calls repointRuleSetsToMirror instead of duplicating
  the rewrite predicate, so the test can't drift from production logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 2, 2026 13:27
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread vpn/boxoptions.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/geosite-throttle-test/main.go`:
- Around line 16-17: The command comment is misleading about the expected proxy
username format, since sessUser builds the auth username from a bare customer ID
by prepending customer-. Update the documentation near main and sessUser to
explicitly state that OXY_USER must be the customer ID only, not a full
username, and that sessUser will add the customer- prefix automatically.
- Around line 86-90: The tlsStreamConn.CloseRead method currently returns nil
without changing the connection state, which incorrectly reports a successful
read-half close for *tls.Conn. Update tlsStreamConn so that CloseRead falls back
to closing the full connection when half-close is not supported, and keep the
adaptation logic consistent with transport.StreamConn expectations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 01da301d-02e2-4880-b528-05690bc93168

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and bcc8f2b.

📒 Files selected for processing (5)
  • cmd/geosite-throttle-test/main.go
  • vpn/boxoptions.go
  • vpn/boxoptions_test.go
  • vpn/mirror.go
  • vpn/mirror_test.go

Comment thread cmd/geosite-throttle-test/main.go Outdated
Comment thread cmd/geosite-throttle-test/main.go Outdated
Previously repointRuleSetsToMirror ran unconditionally and only the outbound
injection was guarded, so a config already using the "mirror" tag (e.g. on an
endpoint) would leave rule-sets pointing at a foreign tag. Check tagInUse
before repointing (short-circuit), so rule-sets are only rewritten when our
mirror outbound will back the tag. Adds TestTagInUse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 2, 2026 13:43
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

vpn/boxoptions.go:561

  • mergeAndCollectTags() filters reserved/non-selectable tags only for src.Outbounds, but not for src.Endpoints. Since the returned tag list is used to populate the auto/manual selector groups, an endpoint tagged "direct"/"block"/"auto"/"manual" (or tagged with a server-declared non-selectable tag) would still become user-selectable, which contradicts the function comment and the PR intent of excluding these tags from the selectable set.
	for _, ep := range src.Endpoints {
		tags = append(tags, ep.Tag)
	}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/geosite-throttle-test/main.go (1)

54-55: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set an explicit TLS MinVersion.

Both tls.Config instances (proxy handshake at Line 55, and the target-fetch transport at Line 120) omit MinVersion, defaulting to TLS 1.2. Since this dials a real external endpoint over the public internet, pin a modern floor to avoid silent downgrade.

🔧 Proposed fix
-	conn := tls.Client(raw, &tls.Config{ServerName: oxyHost})
+	conn := tls.Client(raw, &tls.Config{ServerName: oxyHost, MinVersion: tls.VersionTLS12})
-		TLSClientConfig:    &tls.Config{NextProtos: []string{"http/1.1"}},
+		TLSClientConfig:    &tls.Config{NextProtos: []string{"http/1.1"}, MinVersion: tls.VersionTLS12},

Also applies to: 119-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` around lines 54 - 55, Set an explicit TLS
minimum version for all outbound TLS connections in this test utility, since
both the proxy handshake in tls.Client and the target-fetch transport in
http.Transport currently rely on the default. Update the tls.Config used in the
connection setup around tls.Client and the tls.Config inside the transport setup
in the target-fetch path to include a modern MinVersion (for example TLS 1.2 or
higher) so both the initial proxy handshake and the external endpoint fetch are
pinned to a secure floor.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/geosite-throttle-test/main.go`:
- Around line 54-55: Set an explicit TLS minimum version for all outbound TLS
connections in this test utility, since both the proxy handshake in tls.Client
and the target-fetch transport in http.Transport currently rely on the default.
Update the tls.Config used in the connection setup around tls.Client and the
tls.Config inside the transport setup in the target-fetch path to include a
modern MinVersion (for example TLS 1.2 or higher) so both the initial proxy
handshake and the external endpoint fetch are pinned to a secure floor.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ecbb6b3-fcaf-4ea2-968d-7d7ae8d43b70

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and a032292.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • backend/radiance.go
  • cmd/geosite-throttle-test/main.go
  • go.mod
  • vpn/boxoptions.go
  • vpn/nonselectable_test.go

…bounds doc

Spell out that excluded tags are kept out of BOTH the auto (URLTest) group
(auto-selection won't route traffic through them) and the manual selector,
matching the getlantern/common#27 field doc. Comment-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 5, 2026 17:16
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

vpn/boxoptions.go:564

  • mergeAndCollectTags filters reserved/non-selectable tags only for outbounds; endpoint tags are appended unconditionally. Because endpoints share the same tag namespace (and these tags feed the auto/manual selectors), a server-provided endpoint tagged "auto"/"manual"/"direct"/"block" (or a tag listed in NonSelectableOutbounds) would still become selectable, contradicting the function’s doc comment and potentially reintroducing unsafe selection (e.g., selectable "direct"). Filter endpoints the same way as outbounds.
	for _, ep := range src.Endpoints {
		tags = append(tags, ep.Tag)
	}

Comment thread vpn/nonselectable_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/geosite-throttle-test/main.go`:
- Line 50: Add a Go doc comment for the exported connectDialer.DialStream
method. The method currently lacks its own comment even though it is exported;
add a concise // DialStream ... comment immediately above the method declaration
that describes its tunnel dialing behavior and keep it consistent with the
existing connectDialer type documentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 003e02bf-d317-49a7-bfcd-a02b409ac8bc

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and fd8d1ad.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • backend/radiance.go
  • cmd/geosite-throttle-test/main.go
  • go.mod
  • vpn/boxoptions.go
  • vpn/nonselectable_test.go

Comment thread cmd/geosite-throttle-test/main.go
…ctable set

mergeAndCollectTags filtered outbound tags but returned endpoint tags unfiltered,
so an infra-tagged endpoint (reserved or server-declared non-selectable) would
have been offered as a selectable proxy. Apply the same filter to endpoints via a
shared skip closure; cover it in the test. Also add a doc comment on the
throttle-test DialStream method.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 5, 2026 17:25
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Comment thread vpn/boxoptions.go Outdated
Comment thread vpn/boxoptions.go
Comment thread vpn/boxoptions.go Outdated
Comment thread vpn/nonselectable_test.go Outdated
common#27 merged; re-point from the branch pseudo-version to merged main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/geosite-throttle-test/main.go (1)

112-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a Go doc comment for fetch.

fetch has a non-obvious measurement contract: it forces HTTP/1.1, disables redirects/compression, drains the body, and returns measured bytes plus duration. Put that contract immediately above the declaration.

📝 Suggested doc comment
+// fetch downloads url through dialer, measuring the original HTTP/1.1 response
+// body without redirects or transparent gzip decoding.
 func fetch(dialer transport.StreamDialer, url string) (int64, time.Duration, error) {

As per coding guidelines, **/*.go: Use Go doc comments (// Foo ...) for exported identifiers and any unexported ones with non-obvious contracts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/geosite-throttle-test/main.go` around lines 112 - 144, Add a Go doc
comment immediately above fetch that describes its non-obvious measurement
contract: it uses HTTP/1.1 only, disables redirects and compression, drains the
response body, and returns the byte count plus elapsed duration. Keep the
comment tied to the fetch function so readers understand the behavior without
inspecting the implementation, and make sure it follows the Go doc comment style
used elsewhere in the package.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/geosite-throttle-test/main.go`:
- Around line 112-144: Add a Go doc comment immediately above fetch that
describes its non-obvious measurement contract: it uses HTTP/1.1 only, disables
redirects and compression, drains the response body, and returns the byte count
plus elapsed duration. Keep the comment tied to the fetch function so readers
understand the behavior without inspecting the implementation, and make sure it
follows the Go doc comment style used elsewhere in the package.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e3f32cf7-2b6c-4f7c-8ca7-19a3dbbb7047

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and 7029aa6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • backend/radiance.go
  • cmd/geosite-throttle-test/main.go
  • go.mod
  • vpn/boxoptions.go
  • vpn/nonselectable_test.go

…reserved set

Follow-up to filtering endpoint tags: the BoxOptions field, mergeAndCollectTags
doc, and test header said "outbounds" though the filter applies to endpoints too;
say "tags". Also list the full reserved set (auto/manual/direct/block) in the
skip comment. Comment-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGpKfgkbfdJwwsTaouMDoR
@myleshorton
myleshorton requested a review from Copilot July 5, 2026 17:33
@myleshorton

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
vpn/boxoptions.go (1)

527-569: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Exclude reserved tags before merging src outbounds/endpoints

mergeAndCollectTags still appends every src.Outbounds/src.Endpoints entry into dst, while buildOptions later injects auto and manual outbounds. sing-box requires outbound tags to be unique, so a src entry using auto, manual, direct, or block can make the final config invalid at startup. Filter reserved tags out of the merge, not just out of the selectable tag list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vpn/boxoptions.go` around lines 527 - 569, mergeAndCollectTags still copies
every src.Outbounds and src.Endpoints entry into dst, so reserved tags like
auto, manual, direct, and block can collide with the later buildOptions
injection and break sing-box startup. Update mergeAndCollectTags to filter these
reserved tags out before appending to dst, while keeping the existing
selectable-tag collection logic for non-reserved entries. Use the existing
reservedTags helper and the mergeAndCollectTags/buildOptions flow to keep both
the merged config and the returned tag list consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@vpn/boxoptions.go`:
- Around line 527-569: mergeAndCollectTags still copies every src.Outbounds and
src.Endpoints entry into dst, so reserved tags like auto, manual, direct, and
block can collide with the later buildOptions injection and break sing-box
startup. Update mergeAndCollectTags to filter these reserved tags out before
appending to dst, while keeping the existing selectable-tag collection logic for
non-reserved entries. Use the existing reservedTags helper and the
mergeAndCollectTags/buildOptions flow to keep both the merged config and the
returned tag list consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 39bbdf46-1a5c-4f1b-baf6-4973bc8b0c1d

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b9fb and 8e7ce27.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • backend/radiance.go
  • cmd/geosite-throttle-test/main.go
  • go.mod
  • vpn/boxoptions.go
  • vpn/nonselectable_test.go

@myleshorton

Copy link
Copy Markdown
Contributor Author

Pulling this in. Actually a small change that allows us to specify the proxyless outbound for rule-sets.

@myleshorton
myleshorton merged commit 4356bc0 into main Jul 6, 2026
4 checks passed
@myleshorton
myleshorton deleted the fisk/mirror-rulesets-detour branch July 6, 2026 17:47
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.

2 participants