# How Xray-core Router Condition System Works for Routing Decisions

> Understand how the Xray-core router condition system makes routing decisions. Learn how sequential rule evaluation determines outbound destinations based on request matching.

- Repository: [Project X Community, Not Porn-jet X Hub/Xray-core](https://github.com/XTLS/Xray-core)
- Tags: internals
- Published: 2026-04-21

---

**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`](https://github.com/XTLS/Xray-core/blob/main/app/router/condition.go). Every matcher in Xray-core implements this interface:

```go
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`](https://github.com/XTLS/Xray-core/blob/main/app/router/router.go):

```go
// router.Init → rule.BuildCondition → Rule.Condition

```

The `BuildCondition` function in [`app/router/config.go`](https://github.com/XTLS/Xray-core/blob/main/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`](https://github.com/XTLS/Xray-core/blob/main/app/router/condition.go):

```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`](https://github.com/XTLS/Xray-core/blob/main/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`:

```go
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`](https://github.com/XTLS/Xray-core/blob/main/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:

```json
{
  "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:

```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`](https://github.com/XTLS/Xray-core/blob/main/app/router/router.go) | Router initialization, `PickRoute`, `pickRouteInternal`, domain strategy handling, webhook execution |
| [`app/router/condition.go`](https://github.com/XTLS/Xray-core/blob/main/app/router/condition.go) | `Condition` interface, `ConditionChan`, all concrete matcher implementations |
| [`app/router/config.go`](https://github.com/XTLS/Xray-core/blob/main/app/router/config.go) | Protobuf structures, `BuildCondition` function that converts JSON rules to `Condition` objects |
| [`app/router/rule.go`](https://github.com/XTLS/Xray-core/blob/main/app/router/rule.go) | `Rule` struct combining tag, `RuleTag`, and `Condition` |
| [`infra/conf/router.go`](https://github.com/XTLS/Xray-core/blob/main/infra/conf/router.go) | JSON configuration parsing into protobuf format |
| [`features/routing/router.go`](https://github.com/XTLS/Xray-core/blob/main/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.