How Xray-core Router Condition System Works for Routing Decisions
The Xray-core router condition system evaluates rules sequentially using a Condition interface that returns true when a request matches, with the first matching rule determining the outbound destination.
The Xray-core router condition system is the decision engine that determines which outbound proxy or destination handles each network request. Built around a flexible interface-based design, it allows complex matching logic through composable conditions. This article explains how the condition system works by examining the source code in XTLS/Xray-core.
Core Architecture: The Condition Interface
At the heart of the router condition system lies a single Go interface defined in app/router/condition.go. Every matcher in Xray-core implements this interface:
type Condition interface {
Apply(ctx routing.Context) bool
}
The Apply method receives a routing.Context containing all request metadata—target domain, IP addresses, ports, inbound tags, user information, and more. It returns true when the request satisfies the condition, false otherwise.
How Rules Become Conditions
When Xray-core initializes, the router transforms JSON configuration rules into executable condition chains. This happens in router.Init within app/router/router.go:
// router.Init → rule.BuildCondition → Rule.Condition
The BuildCondition function in app/router/config.go constructs a ConditionChan—a slice of Condition objects that act as an AND gate. All sub-conditions must match for the rule to apply.
ConditionChan: The Composite Pattern
The ConditionChan type implements short-circuit evaluation. From app/router/condition.go:
func (v *ConditionChan) Apply(ctx routing.Context) bool {
for _, cond := range *v {
if !cond.Apply(ctx) {
return false
}
}
return true
}
If any sub-condition fails, Apply immediately returns false. Only when all conditions succeed does the rule match.
Available Condition Matchers
Xray-core provides specialized matchers for every common routing dimension. Each implements the Condition interface:
| Matcher | Purpose | Source Location |
|---|---|---|
DomainMatcher |
Domain rules (exact, subdomain, regexp, keyword) | app/router/condition.go:L47-L68 |
IPMatcher |
IP ranges, CIDR, GeoIP data | app/router/condition.go:L79-L108 |
PortMatcher |
Single ports, ranges, lists | app/router/condition.go:L110-L138 |
NetworkMatcher |
TCP, UDP, Unix domain sockets | app/router/condition.go:L39-L55 |
UserMatcher |
User emails, regexp patterns | app/router/condition.go:L56-L80 |
InboundTagMatcher |
Inbound listener identifiers | app/router/condition.go:L200-L228 |
ProtocolMatcher |
Detected protocols (HTTP, TLS, VMess, etc.) | app/router/condition.go:L31-L61 |
AttributeMatcher |
Key-value attributes from context | app/router/condition.go:L63-L89 |
ProcessNameMatcher |
Local process name, path, or folder | app/router/condition.go:L91-L139 |
Each matcher encapsulates efficient lookup structures. DomainMatcher uses a hybrid trie for subdomain matching plus Aho-Corasick for keyword detection. IPMatcher leverages CIDR trie trees for O(log n) lookups.
The Routing Execution Flow
When a connection arrives, the router executes a deterministic matching process in app/router/router.go:
- PickRoute receives the
routing.Contextfrom the inbound handler - pickRouteInternal iterates through
r.rulesin configuration order - For each rule, it calls
rule.Apply(ctx)—theConditionChan.Applychain - First matching rule wins: its outbound tag (or balancer-derived tag) is returned
- Webhook execution: if the rule specifies a post-action webhook, it fires asynchronously
The core loop from router.go:47-71:
for _, rule := range r.rules {
if rule.Apply(ctx) {
return rule, ctx, nil
}
}
If no rule matches, the request uses the default outbound or fails depending on configuration.
Domain Strategy Interaction
The router condition system interacts with domain strategies that control DNS resolution timing. Implemented in pickRouteInternal around line 52-73 of router.go:
| Strategy | Behavior |
|---|---|
AsIs |
No DNS resolution; rules match using original destination |
IPIfNonMatch |
First pass with original info; if no match, resolve domain to IPs and retry |
IPOnDemand |
Resolve domain before any rule evaluation; all rules see IP addresses |
When IPIfNonMatch or IPOnDemand triggers resolution, the DNS client attaches resolved IPs to the context, enabling IPMatcher conditions to evaluate against GeoIP databases.
Configuration Example
A practical routing rule combining multiple conditions:
{
"tag": "proxy-out",
"ruleTag": "block-youtube",
"domain": ["regexp:^.*\\.youtube\\.com$"],
"port": [443],
"network": ["tcp"],
"inboundTag": ["http-in"],
"process": ["chrome", "firefox"]
}
This rule matches when all conditions succeed:
- Target domain matches
*.youtube.comvia regex - Destination port is 443
- Network protocol is TCP
- Request entered through
http-ininbound - Originating process is Chrome or Firefox
On match, traffic routes to the proxy-out outbound.
Programmatic Condition Construction
For custom tooling, build conditions directly in Go:
import (
"github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
)
func makeDemoCondition() router.Condition {
// Domain matcher for *.example.com
dm, _ := router.NewDomainMatcher([]*geodata.DomainRule{
{Type: geodata.Domain_Substr, Value: "example.com"},
})
// Port matcher for 80 and 8080
pm := router.NewPortMatcher(&net.PortList{
Range: []net.PortRange{
{From: 80, To: 80},
{From: 8080, To: 8080},
},
}, router.MatcherAsType_Target)
// Combine with AND logic
ch := router.NewConditionChan()
ch.Add(dm)
ch.Add(pm)
return ch
}
Calling makeDemoCondition().Apply(ctx) returns true only when both domain and port conditions match.
Key Source Files
| File | Purpose |
|---|---|
app/router/router.go |
Router initialization, PickRoute, pickRouteInternal, domain strategy handling, webhook execution |
app/router/condition.go |
Condition interface, ConditionChan, all concrete matcher implementations |
app/router/config.go |
Protobuf structures, BuildCondition function that converts JSON rules to Condition objects |
app/router/rule.go |
Rule struct combining tag, RuleTag, and Condition |
infra/conf/router.go |
JSON configuration parsing into protobuf format |
features/routing/router.go |
Public routing.Router interface definition |
Summary
- The Xray-core router condition system uses a
Conditioninterface with a singleApply(routing.Context) boolmethod - Rules combine multiple conditions through
ConditionChan, which implements short-circuit AND logic - Ten concrete matchers handle domains, IPs, ports, networks, users, inbound tags, protocols, attributes, and process names
- The router evaluates rules sequentially; the first match wins and determines the outbound destination
- Domain strategies (
AsIs,IPIfNonMatch,IPOnDemand) control when DNS resolution occurs relative to condition evaluation
Frequently Asked Questions
How does Xray-core handle multiple conditions in a single routing rule?
Xray-core combines multiple conditions using AND logic through the ConditionChan type. When ConditionChan.Apply executes, it iterates through all sub-conditions and returns false immediately if any condition fails. Only when every sub-condition returns true does the entire rule match. This short-circuit evaluation minimizes CPU overhead for non-matching rules.
What happens when no routing rule matches a request?
When no rule matches, pickRouteInternal completes its loop without finding a valid rule. The behavior depends on configuration: typically the request routes to a default outbound if configured, or the connection may be refused or handled by fallback mechanisms. The routing context remains available for logging and monitoring regardless of match status.
Can I use regular expressions in domain matching rules?
Yes. The DomainMatcher supports multiple matching types defined in the geodata package, including Domain_Regex for full regular expressions, Domain_Wildcard for *.example.com patterns, Domain_Domain for exact matches, Domain_Full for complete string equality, and Domain_Substr for substring detection. The regex engine compiles patterns during initialization for efficient runtime matching.
How does the IPIfNonMatch domain strategy affect condition evaluation?
With IPIfNonMatch, the router first evaluates all rules using the original destination information without DNS resolution. If no rule matches, the router resolves the domain to IP addresses, attaches the DNS client to the routing context, and re-evaluates the entire rule list. This second pass allows IPMatcher conditions with GeoIP databases to match against the resolved addresses, enabling location-based routing for domain-only destinations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →