Skip to content

feat(log): add test route rule in log window - #143

Merged
PhoenixNil merged 1 commit into
mainfrom
feature/route-test
Sep 28, 2026
Merged

PhoenixNil merged 1 commit into
mainfrom
feature/route-test

Conversation

@PhoenixNil

@PhoenixNil PhoenixNil commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

What

A route tester in the log window: a new toolbar button (magnifier) opens a flyout where you type a domain, IP or URL. It shows whether the running core would send it direct / proxy / block, plus the outbound tag. It is only available while connected.

How

It asks the live core instead of re-implementing xray's rule matching in C#.

  • Config: XrayConfigBuilder.Build adds api: { tag: "api", listen: "127.0.0.1:<fresh port>", services: ["RoutingService"] } to every config. api.listen opens the gRPC listener directly, with no extra inbound or routing rule. A config profile that declares its own api is left untouched, and the tester reports that it is unavailable.
  • Transport: RouteTestService calls RoutingService.TestRoute using gRPC over HttpClient h2c (Version20 + RequestVersionExact). RouteTestProtocol hand-encodes the protobuf (one request, one response field), so no Grpc/Protobuf packages are added under AOT.
  • Accuracy: TestRoute runs the router's own PickRoute, the same one real connections use. Results therefore include domainStrategy DNS resolution (IPIfNonMatch) and balancer picks, and cover every rule source: built-in template, GUI rules, Advanced Edit, TUN lead rules, and config profiles.
  • Values read back from the built config instead of assumed, like the system proxy port:
    • the inbound tag the test claims to arrive on (so inboundTag rules in custom profiles match);
    • the first outbound's tag, which is where xray sends traffic when no rule matches.
  • Input: a bare domain, an IP (v4/v6, bracketed with a port), host:port, or a full URL. A URL without a port uses its scheme's default (http/ws → 80).
  • Lifecycle: XrayService.StartAsync(BuiltXrayConfig) publishes XrayService.RouteTest while the core runs and clears it on stop or exit. The log window drops a stale result whenever the core restarts (reapply picks a new port).
  • Refactor: the free-port helper moves from RealLatencyProbeService to Helpers/LoopbackPort and is shared by both.

Reviewer notes

  • Known limits:
    • It tests the rules that are live now. Unsaved edits in the custom-rules window don't count; saving reapplies, and the tester follows.
    • It simulates TCP only, so UDP-only rules such as the TUN QUIC block don't take part.
    • It shows the outbound but not which rule matched, because RoutingContext has no rule tag.
    • process rules never match: a synthetic request has no originating process (xray resolves it from the OS socket table), so results describe apps no process rule covers.
  • Security: every session now has an unauthenticated RoutingService on loopback. It includes AddRule/RemoveRule, so a local process could alter live routing. The effect is limited to routing, and only local processes can reach it.
  • Port race: as in the latency test, there is a tiny window between picking the port and xray binding it. If something grabs the port in that window, that start fails.

Testing

  • dotnet test XrayUI.Tests/XrayUI.Tests.csproj -c Release: 209 passed. New tests cover protobuf encode/decode, input parsing and scheme default ports, and FindRouteTestInboundTag.
  • Checked against the bundled xray 26.6.1 with scratch configs:
    • h2c connects to Go gRPC under both JIT and NativeAOT;
    • inboundTag and port rules match;
    • with no matching rule, the result is the default outbound.
  • In the app, connected, with Advanced Edit using IPIfNonMatch:
    • baidu.com, qq.com, 223.5.5.5 and 192.168.1.1 → direct;
    • google, github.com, 8.8.8.8 and IPv6 → proxy;
    • invalid input shows a hint;
    • switching to global routing reapplies, the port changes, the stale result is cleared, and everything shows proxy;
    • when disconnected it shows available while connected.
  • Local dotnet publish with AOT succeeds without trim warnings.
  • Not verified: TUN mode (needs elevation), and the "profile declares its own api" path in the UI.

Add a route tester to the log window's toolbar: enter a domain, IP or URL
and it shows whether the live core sends it direct, to the proxy, or blocks
it, along with the outbound tag.

Rather than re-implement rule matching in C# (geosite/geoip, the domain
matchers, domainStrategy DNS resolution, balancers), it asks the running
core. Every built config now carries an api section exposing only
RoutingService on a fresh loopback port, and the tester calls
RoutingService.TestRoute, which runs the same PickRoute real connections
use. gRPC is spoken over HttpClient h2c with the protobuf hand-encoded in
RouteTestProtocol, so no Grpc/Protobuf packages are added under AOT.

The inbound tag the test claims, and the default outbound reported when no
rule matches, are both read back from the built config, like the system
proxy port, so custom config profiles with their own tags get correct
answers. A profile that declares its own api section is left alone and the
tester reports it is unavailable. URLs without a port use their scheme's
default port so port rules see what a real connection would.

The free-port helper moves from RealLatencyProbeService to
Helpers/LoopbackPort so both users share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T03:36:55.638562Z e92e6e2 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e92e6e2d45

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +169 to +173
if (target.Ip is { } ip)
WriteBytes(context, CtxTargetIps, ip.GetAddressBytes());
WriteVarintField(context, CtxTargetPort, (ulong)target.Port);
if (!string.IsNullOrEmpty(target.Domain))
WriteString(context, CtxTargetDomain, target.Domain);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark route results as ambiguous for process-based rules

When smart routing contains a GUI or Advanced Edit process rule, an actual connection from the matching executable may be sent direct or blocked, but this synthetic context contains only network, destination, port, and inbound-tag data, so the process rule cannot match and the UI reports a later destination rule or the default outbound instead. This is a supported configuration path (XrayConfigBuilder.BuildSmartRules explicitly installs process rules), so the tester should either accept the relevant process context or warn that no definitive result is available when process-dependent rules are active.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Won't fix; documented under Known limits instead. xray resolves process rules from the OS socket table, so a synthetic TestRoute request can't carry a process. The result is exact for apps no process rule covers, and the per-connection verdicts in the same log window show the real outcome for a specific app.

@PhoenixNil PhoenixNil changed the title feat(log): test where a domain or IP is routed from the log window feat(log): add test route rule in log window Sep 28, 2026
@PhoenixNil
PhoenixNil merged commit 5fcc011 into main Sep 28, 2026
7 checks passed
@PhoenixNil
PhoenixNil deleted the feature/route-test branch September 28, 2026 04:26
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