路由引擎
路由器根据规则列表评估传入连接,并选择哪个出站处理器应处理该流量。它支持域名/IP/端口匹配、GeoIP/GeoSite 数据库以及负载均衡。
源码:app/router/router.go、app/router/condition.go、app/router/strategy_*.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
}规则评估
规则按顺序评估 — 第一个匹配的规则生效:
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 |
func (r *Router) applyDomainStrategy(ctx routing.Context) routing.Context {
switch r.domainStrategy {
case Config_IpIfNonMatch:
// 第一轮:尝试域名匹配
// 如果未匹配:解析域名→IP,再次尝试
case Config_IpOnDemand:
// 仅在遇到基于 IP 的规则时解析
}
}规则条件
每条规则都有一个 Condition 来检查路由上下文:
type Rule struct {
Condition Condition
Tag string // 出站标签
RuleTag string // 规则标识符,用于日志
Balancer *Balancer // 或 nil
}
type Condition interface {
Apply(ctx routing.Context) bool
}条件类型
| 条件 | 字段 | 描述 |
|---|---|---|
DomainMatcher | domain | 匹配目标域名(全匹配、子串、正则、域名后缀) |
GeoIPMatcher | ip | 根据 GeoIP 数据库匹配目标 IP |
MultiGeoIPMatcher | geoip | 多个 GeoIP 匹配器(国家代码) |
PortMatcher | port | 匹配目标端口或端口范围 |
PortRangeMatcher | portList | 根据范围匹配端口 |
NetworkMatcher | network | TCP、UDP 或两者 |
ProtocolMatcher | protocol | 嗅探到的协议(http、tls、bittorrent) |
UserMatcher | user | 已认证的用户邮箱 |
InboundTagMatcher | inboundTag | 入站处理器标签 |
AttributeMatcher | attrs | HTTP 属性(嗅探获得) |
ConditionChan | (复合) | 多个条件的 AND 组合 |
路由上下文
路由上下文提供规则可以匹配的所有字段:
// 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 包,它提供了高效的匹配器:
匹配器类型
// 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 使用最小完美哈希函数:
// common/strmatcher/mph_matcher.go
type MphIndexMatcher struct {
rules []matcherGroup // 每个哈希桶的匹配器组
values []uint32 // 哈希表
level0 []uint32
level1 []uint32
// ... MPH 查找表
}这为全匹配和域名后缀匹配模式提供了 O(1) 查找性能,比线性扫描快得多。
GeoIP / GeoSite
GeoIP
基于 IP 的地理匹配使用排序的 CIDR 范围列表和二分查找:
// 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 序列化)中加载:
type GeoSiteList struct {
Entry []*GeoSite // 国家代码 → 域名列表
}
type GeoSite struct {
CountryCode string
Domain []*Domain // 带 Type(全匹配/域名后缀/子串/正则)
}负载均衡
当规则指向负载均衡器而非直接标签时:
type Balancer struct {
selectors []string // 出站标签模式
strategy BalancingStrategy // 轮询、随机、最低延迟、最小负载
ohm outbound.Manager
}策略
| 策略 | 描述 |
|---|---|
random | 在匹配的出站中随机选择 |
roundRobin | 顺序轮换 |
leastPing | 来自观测探针的最低 RTT |
leastLoad | 最少活跃连接 / 最佳健康状态 |
func (b *Balancer) PickOutbound() (string, error) {
candidates := b.getMatchingOutbounds()
return b.strategy.Pick(candidates)
}路由上下文流程
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实现说明
规则顺序很重要:第一个匹配的规则生效。用户期望规则按从上到下的顺序评估。
域名策略至关重要:
IPIfNonMatch会进行两轮评估 — 先用域名,然后用解析后的 IP。这会影响性能(未匹配时触发 DNS 查询)。GeoIP 二分查找:IP 范围在加载时预排序。使用二分查找,而非线性扫描。
域名的 MPH 优化:对于 100K+ 的域名规则(GeoSite 中很常见),MPH 提供 O(1) 复杂度,优于 O(n)。没有它,域名匹配会成为性能瓶颈。
RouteTarget 与 Target 的区别:使用
routeOnly嗅探时,路由器看到的是RouteTarget(嗅探到的域名),但出站使用的是Target(原始 IP)。负载均衡器健康状态:
leastPing和leastLoad依赖 Observatory 功能定期探测出站健康状态。