feat: add rematch outbound type and REMATCH-NAME rule type#2862
Conversation
|
@wwqgtxx Hello. |
|
We neither have the time nor the inclination to expend the effort to review PRs/issues primarily generated by AI. The reason we haven't closed the PR is that the idea itself is quite interesting. However, the code implementation is terrible. You can simply leave once the code generated by AI is merged, while we have to continuously pay the maintenance costs for these inelegant implementations. Therefore, unless we immediately deem the AI-generated code "elegant and maintainable enough," it will most likely remain shelved. |
I didn't try to hide the fact that it was written using AI; I was just hoping for some feedback—maybe some comments on what to fix, or perhaps you could make the corrections yourselves. Thanks for the feedback. I'm glad you liked the idea, and of course I'm looking forward to seeing it implemented. |
I tried to simplify the implementation and remove the parts that looked like a proof-of-concept workaround. The latest commit is:
The old implementation used a global callback bridge through Current implementation overview: 1. Loopback outboundAdded:
The core method is: func (l *Loopback) ApplyLoopback(metadata *C.Metadata)It only updates routing metadata: metadata.InName = l.inName
metadata.SpecialRules = l.subRuleSo the actual rematching still uses the existing rule matching flow. For direct proxy, _, err := l.tunnel.ResolveProxy(metadata)Then it dials through the resolved proxy. There is also a self-resolution guard to avoid url-test recursion if the resolved chain still points back to the same loopback proxy. 2. Tunnel resolverChanged:
Added one method to the existing ResolveProxy(metadata *Metadata) (Proxy, Rule, error)Implemented in:
func (t tunnel) ResolveProxy(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
return resolveMetadata(metadata)
}This is used by loopback only for direct dial paths such as health checks. It replaces the previous global callback approach. 3. Rule matcher integrationChanged:
The normal traffic path is handled directly inside When a rule or proxy group resolves to a chain containing
So the second pass still uses the existing logic: getRules(metadata)
rule.Match(metadata, helper)This means 4. Loop protectionLoop protection is now based on visited loopback names, not a numeric hop limit. This allows a chain like: but rejects: The repeated loopback is dropped with 5. Config validationChanged:
Loopback validation is strict now. A loopback proxy must define at least one of:
If There is no longer any runtime fallback, disabled state, or mutation of the loopback config after parsing. 6. Connection chainsChanged:
A small internal field was added: LoopbackChain []string `json:"-"`This is only used to append the virtual loopback hop to connection chains before logging/statistics, so the API/UI can show that the connection passed through the loopback step. It does not affect routing or matching. 7. Parser changesChanged:
Added parsing for: type: loopbackAlso, Reason: loopback is virtual and does not own network resources. The tunnel matcher also needs access to its 8. TestsI ran: go test ./...and it passes locally. Current trade-offs / possible review pointsThe implementation still changes If this direction is still not acceptable, I would appreciate guidance on where you would prefer the metadata-to-proxy resolving hook to live. |
f513c49 to
d67572b
Compare
|
This implementation is still too complex. First, Second, we don't need a separate Third, since re-matching is initiated within the |
Thanks, I simplified the implementation according to your suggestion. Latest commit:
Current design:
Routing flow: Loop protection:
One UX question: I removed URL-test support for loopback as suggested, because loopback is not a real outbound. The original reason I tried to support it was UX: users often expect proxy-group buttons to show latency numbers. The intended use case is to have fewer buttons on clients, especially mobile clients, where one loopback button can represent a routing profile handled by rules. Without URL-test numbers, users may think that the button is broken even if routing works correctly. Do you prefer loopback to remain untestable because it is virtual, or would you consider a separate lightweight way to expose the health of the resolved outbound later? |
8ba1fa0 to
9183e38
Compare
rematch proxy to rematch rules
rematch proxy to rematch rulesrematch outbound type to rematch rules
rematch outbound type to rematch rulesrematch outbound type and REMATCH-NAME rule type
|
We ultimately determined that directly modifying the IN-NAME field was not optimal, as it would overwrite the original inbound information. Instead, we provided a new REMATCH-NAME rule for matching. |
Thanks, this makes sense. I updated my test config to use One small note: the debug log still says log.Debugln("[Rule] rematch proxy %s update metadata to in-name=%q sub-rule=%q", rematchProxy.Name(), metadata.InName, metadata.SpecialRules)Should it now use rematch-name / metadata.RematchName instead? |
|
I tested it, and everything works as you intended. And yes, with REMATCH-NAME, my original logic for building routing rules has changed slightly. But I like everything—thanks. |
Summary
This PR introduces a new outbound type:
rematch.The design is conceptually similar to Xray's outbound loopback routing, allowing a connection to re-enter the routing engine instead of immediately establishing an outbound connection.
The implementation was developed with the help of Codex based on the routing behavior described in the issue and refined through local testing.
The primary goal is to reduce proxy group complexity while preserving routing flexibility.
Without loopback, users often need large proxy groups containing many routing-specific choices. With rematch, a user can expose only a small number of high-level selections while still applying arbitrary routing logic afterwards. This makes it possible to build a single convenient proxy group with minimal UI buttons while retaining full rule-based routing capabilities.
loopbackis a virtual outbound that does not establish a network connection. Instead, when selected, it sends the connection back for another rule matching pass.During rematching, it can optionally:
REMATCH-NAME;sub-ruleset.As a result, selecting an item inside a proxy group no longer has to determine the final outbound. It can instead switch routing context and let rules decide the final path.
Motivation
Currently, when traffic is routed to a proxy group and the user selects an item inside that group, the selected proxy becomes the final outbound.
This limits the ability to create compact user-facing proxy groups while keeping sophisticated routing policies underneath.
loopbackallows a proxy group selection to act as a routing context switch rather than a final outbound, enabling:Example
Flow:
mixed-in.GLOBAL.RM.RMrematches the connection withREMATCH-NAME=RM_TEST.Configuration
target-rematch-nameOverrides
metadata.RematchNamebefore rematching.target-sub-ruleUses the specified sub-rule set for the second routing pass.
target-rematch-name+target-sub-ruleBoth options can be used together.
Validation
A loopback outbound must define at least one of:
target-rematch-nametarget-sub-ruleOtherwise it is considered invalid.
Loop Protection
Only one loopback rematch is allowed per connection.
If rematching selects another loopback outbound, the connection is rejected and a warning is logged, preventing infinite routing loops.