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:

  1. PickRoute receives the routing.Context from the inbound handler
  2. pickRouteInternal iterates through r.rules in configuration order
  3. For each rule, it calls rule.Apply(ctx)—the ConditionChan.Apply chain
  4. First matching rule wins: its outbound tag (or balancer-derived tag) is returned
  5. 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.com via regex
  • Destination port is 443
  • Network protocol is TCP
  • Request entered through http-in inbound
  • 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 Condition interface with a single Apply(routing.Context) bool method
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →