Skip to content

路由引擎

路由器根据规则列表评估传入连接,并选择哪个出站处理器应处理该流量。它支持域名/IP/端口匹配、GeoIP/GeoSite 数据库以及负载均衡。

源码app/router/router.goapp/router/condition.goapp/router/strategy_*.go

路由器结构

go
// app/router/router.go
type Router struct {
    domainStrategy Config_DomainStrategy
    rules          []*Rule
    balancers      map[string]*Balancer
    dns            dns.Client
    ctx            context.Context
    ohm            outbound.Manager
    dispatcher     routing.Dispatcher
}

规则评估

规则按顺序评估 — 第一个匹配的规则生效:

go
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
    // 应用域名策略(可能解析 DNS)
    ctx = r.applyDomainStrategy(ctx)

    for _, rule := range r.rules {
        if rule.Apply(ctx) {
            return rule, ctx, nil
        }
    }
    return nil, ctx, common.ErrNoClue
}

域名策略

域名策略控制路由器在评估规则之前是否解析域名:

策略行为
AsIs直接使用域名;不解析 DNS
IPIfNonMatch先尝试域名;如果没有规则匹配,则解析为 IP 后重试
IPOnDemand仅在规则需要 IP 匹配时才将域名解析为 IP
go
func (r *Router) applyDomainStrategy(ctx routing.Context) routing.Context {
    switch r.domainStrategy {
    case Config_IpIfNonMatch:
        // 第一轮:尝试域名匹配
        // 如果未匹配:解析域名→IP,再次尝试
    case Config_IpOnDemand:
        // 仅在遇到基于 IP 的规则时解析
    }
}

规则条件

每条规则都有一个 Condition 来检查路由上下文:

go
type Rule struct {
    Condition Condition
    Tag       string     // 出站标签
    RuleTag   string     // 规则标识符,用于日志
    Balancer  *Balancer  // 或 nil
}

type Condition interface {
    Apply(ctx routing.Context) bool
}

条件类型

条件字段描述
DomainMatcherdomain匹配目标域名(全匹配、子串、正则、域名后缀)
GeoIPMatcherip根据 GeoIP 数据库匹配目标 IP
MultiGeoIPMatchergeoip多个 GeoIP 匹配器(国家代码)
PortMatcherport匹配目标端口或端口范围
PortRangeMatcherportList根据范围匹配端口
NetworkMatchernetworkTCP、UDP 或两者
ProtocolMatcherprotocol嗅探到的协议(http、tls、bittorrent)
UserMatcheruser已认证的用户邮箱
InboundTagMatcherinboundTag入站处理器标签
AttributeMatcherattrsHTTP 属性(嗅探获得)
ConditionChan(复合)多个条件的 AND 组合

路由上下文

路由上下文提供规则可以匹配的所有字段:

go
// features/routing/session/context.go
type Context struct {
    Inbound  *session.Inbound   // 来源、标签、用户
    Outbound *session.Outbound  // 目标、routeTarget
    Content  *session.Content   // 嗅探到的协议、属性
}

func (ctx *Context) GetTargetDomain() string
func (ctx *Context) GetTargetIPs() []net.IP
func (ctx *Context) GetSourceIPs() []net.IP
func (ctx *Context) GetInboundTag() string
func (ctx *Context) GetUser() string
func (ctx *Context) GetProtocol() string
func (ctx *Context) GetAttributes() map[string]string

域名匹配

域名匹配使用 strmatcher 包,它提供了高效的匹配器:

匹配器类型

go
// common/strmatcher/strmatcher.go
const (
    Full    Type = 0  // 精确匹配:"example.com"
    Substr  Type = 1  // 包含匹配:"example" 匹配 "test.example.com"
    Domain  Type = 2  // 域名后缀匹配:"example.com" 匹配 "a.b.example.com"
    Regex   Type = 3  // 正则表达式
)

MPH(最小完美哈希)优化

对于大型域名列表(GeoSite),Xray-core 使用最小完美哈希函数:

go
// common/strmatcher/mph_matcher.go
type MphIndexMatcher struct {
    rules   []matcherGroup  // 每个哈希桶的匹配器组
    values  []uint32        // 哈希表
    level0  []uint32
    level1  []uint32
    // ... MPH 查找表
}

这为全匹配和域名后缀匹配模式提供了 O(1) 查找性能,比线性扫描快得多。

GeoIP / GeoSite

GeoIP

基于 IP 的地理匹配使用排序的 CIDR 范围列表和二分查找:

go
// app/router/condition_geoip.go
type GeoIPMatcher struct {
    ip4 []ipv6   // 排序的 IPv4 范围(映射为 IPv6)
    ip6 []ipv6   // 排序的 IPv6 范围
}

func (m *GeoIPMatcher) Match(ip net.IP) bool {
    // 在排序的范围列表中进行二分查找
}

GeoSite

基于域名的地理匹配从 .dat 文件(protobuf 序列化)中加载:

go
type GeoSiteList struct {
    Entry []*GeoSite  // 国家代码 → 域名列表
}
type GeoSite struct {
    CountryCode string
    Domain      []*Domain  // 带 Type(全匹配/域名后缀/子串/正则)
}

负载均衡

当规则指向负载均衡器而非直接标签时:

go
type Balancer struct {
    selectors []string          // 出站标签模式
    strategy  BalancingStrategy // 轮询、随机、最低延迟、最小负载
    ohm       outbound.Manager
}

策略

策略描述
random在匹配的出站中随机选择
roundRobin顺序轮换
leastPing来自观测探针的最低 RTT
leastLoad最少活跃连接 / 最佳健康状态
go
func (b *Balancer) PickOutbound() (string, error) {
    candidates := b.getMatchingOutbounds()
    return b.strategy.Pick(candidates)
}

路由上下文流程

mermaid
flowchart TB
    Dispatch["分发器接收<br/>(ctx, destination)"]
    Sniff["嗅探:从 TLS SNI /<br/>HTTP Host 检测域名"]

    Dispatch --> Sniff
    Sniff --> SetCtx["设置路由上下文:<br/>域名、IP、端口、协议、<br/>入站标签、用户"]

    SetCtx --> Strategy{域名策略?}

    Strategy -->|AsIs| Eval["按顺序评估规则"]
    Strategy -->|IPIfNonMatch| Eval
    Strategy -->|IPOnDemand| Eval

    Eval --> Match{规则匹配?}
    Match -->|是| Check{有负载均衡器?}
    Check -->|是| Balance["Balancer.Pick()"]
    Check -->|否| Tag["使用 rule.Tag"]
    Balance --> Handler["获取出站处理器"]
    Tag --> Handler

    Match -->|否,还有更多规则| Eval
    Match -->|无规则匹配 + IPIfNonMatch| Resolve["解析域名→IP"]
    Resolve --> Eval2["使用 IP 重新评估"]
    Match -->|无规则匹配| Default["使用默认出站"]

    Eval2 --> Match2{规则匹配?}
    Match2 -->|是| Check
    Match2 -->|否| Default

实现说明

  1. 规则顺序很重要:第一个匹配的规则生效。用户期望规则按从上到下的顺序评估。

  2. 域名策略至关重要IPIfNonMatch 会进行两轮评估 — 先用域名,然后用解析后的 IP。这会影响性能(未匹配时触发 DNS 查询)。

  3. GeoIP 二分查找:IP 范围在加载时预排序。使用二分查找,而非线性扫描。

  4. 域名的 MPH 优化:对于 100K+ 的域名规则(GeoSite 中很常见),MPH 提供 O(1) 复杂度,优于 O(n)。没有它,域名匹配会成为性能瓶颈。

  5. RouteTarget 与 Target 的区别:使用 routeOnly 嗅探时,路由器看到的是 RouteTarget(嗅探到的域名),但出站使用的是 Target(原始 IP)。

  6. 负载均衡器健康状态leastPingleastLoad 依赖 Observatory 功能定期探测出站健康状态。

用于重新实现目的的技术分析。