Skip to content

配置系统概述

Xray 的配置系统将人类可读的 JSON(或 YAML/TOML)转换为核心引擎使用的 Protobuf 消息。该流程有明确的分层:infra/conf/ 负责解析和验证,而 core/app/ 仅使用 Protobuf 类型。

流水线架构

mermaid
flowchart LR
    A[JSON/YAML/TOML 文件] --> B[Reader 解码器]
    B --> C["conf.Config(Go 结构体)"]
    C --> D["conf.Config.Build()"]
    D --> E["core.Config(Protobuf)"]
    E --> F["core.New(config)"]
    F --> G[Xray 实例]

    subgraph "infra/conf/"
        B
        C
        D
    end

    subgraph "core/"
        E
        F
        G
    end

顶层 Config 结构体

文件: infra/conf/xray.go

go
type Config struct {
    Transport       map[string]json.RawMessage `json:"transport"`  // 已弃用
    LogConfig       *LogConfig                 `json:"log"`
    RouterConfig    *RouterConfig              `json:"routing"`
    DNSConfig       *DNSConfig                 `json:"dns"`
    InboundConfigs  []InboundDetourConfig      `json:"inbounds"`
    OutboundConfigs []OutboundDetourConfig     `json:"outbounds"`
    Policy          *PolicyConfig              `json:"policy"`
    API             *APIConfig                 `json:"api"`
    Metrics         *MetricsConfig             `json:"metrics"`
    Stats           *StatsConfig               `json:"stats"`
    Reverse         *ReverseConfig             `json:"reverse"`
    FakeDNS         *FakeDNSConfig             `json:"fakeDns"`
    Observatory     *ObservatoryConfig         `json:"observatory"`
    BurstObservatory *BurstObservatoryConfig   `json:"burstObservatory"`
    Version         *VersionConfig             `json:"version"`
}

每个字段对应一个顶层 JSON 键。结构体内嵌的子配置类型知道如何构建对应的 Protobuf 消息。

Buildable 接口

文件: infra/conf/buildable.go

go
type Buildable interface {
    Build() (proto.Message, error)
}

每个配置结构体都实现此接口。Build() 方法验证配置并生成等效的 Protobuf 消息。

多格式支持

文件: infra/conf/serial/loader.go

Xray 支持 JSON、YAML 和 TOML 配置。所有格式都通过解码器函数转换为 conf.Config

go
var ReaderDecoderByFormat = map[string]readerDecoder{
    "json": DecodeJSONConfig,
    "yaml": DecodeYAMLConfig,
    "toml": DecodeTOMLConfig,
}

JSON: 通过去注释读取器(json_reader.Reader)直接解析,并带有语法错误位置追踪。

YAML: 通过 yaml.YAMLToJSON() 转换为 JSON,然后按 JSON 解析。

TOML: 先反序列化为 map[string]interface{},再序列化为 JSON,最后按 JSON 解析。

配置合并

文件: infra/conf/serial/builder.go

支持合并多个配置文件:

go
func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
    cf := &conf.Config{}
    for i, file := range files {
        c, _ := ReaderDecoderByFormat[file.Format](r)
        if i == 0 {
            *cf = *c
            continue
        }
        cf.Override(c, file.Name)
    }
    return cf, nil
}

Config 上的 Override() 方法处理合并逻辑:

  • 简单字段(log、routing、DNS、policy 等)整体替换
  • Inbound/outbound 按标签合并:匹配的标签被更新,新标签被添加
  • Outbound 的顺序取决于文件名:文件名包含 "tail" 的追加到末尾,其他的前置

Build 流水线

文件: infra/conf/xray.go -- Config.Build()

Build() 方法构造最终的 core.Config

go
func (c *Config) Build() (*core.Config, error) {
    PostProcessConfigureFile(c)  // lint 和验证

    config := &core.Config{
        App: []*serial.TypedMessage{
            serial.ToTypedMessage(&dispatcher.Config{}),     // 始终存在
            serial.ToTypedMessage(&proxyman.InboundConfig{}), // 始终存在
            serial.ToTypedMessage(&proxyman.OutboundConfig{}),// 始终存在
        },
    }

    // 构建并追加各配置段
    if c.API != nil       { config.App = append(config.App, serial.ToTypedMessage(apiConf)) }
    if c.Stats != nil     { config.App = append(config.App, serial.ToTypedMessage(statsConf)) }
    // Log 前置(最先初始化)
    config.App = append([]*serial.TypedMessage{logConfMsg}, config.App...)
    if c.RouterConfig != nil { config.App = append(config.App, serial.ToTypedMessage(routerConfig)) }
    if c.DNSConfig != nil    { config.App = append(config.App, serial.ToTypedMessage(dnsApp)) }
    if c.FakeDNS != nil      { config.App = append([]*serial.TypedMessage{...}, config.App...) } // 前置!
    // ... observatory、reverse、policy、version

    // 构建 inbound 和 outbound
    for _, rawInboundConfig := range inbounds {
        ic, _ := rawInboundConfig.Build()
        config.Inbound = append(config.Inbound, ic)
    }
    for _, rawOutboundConfig := range outbounds {
        oc, _ := rawOutboundConfig.Build()
        config.Outbound = append(config.Outbound, oc)
    }
}

顺序很重要:

  1. Log 配置始终排在最前(使其他模块在初始化时即可记录日志)
  2. FakeDNS 前置于 DNS 之前(必须先初始化以满足 RequireFeatures
  3. Dispatcher、InboundConfig、OutboundConfig 作为基础应用始终存在

Inbound 配置构建

go
type InboundDetourConfig struct {
    Protocol       string           `json:"protocol"`
    PortList       *PortList        `json:"port"`
    ListenOn       *Address         `json:"listen"`
    Settings       *json.RawMessage `json:"settings"`
    Tag            string           `json:"tag"`
    StreamSetting  *StreamConfig    `json:"streamSettings"`
    SniffingConfig *SniffingConfig  `json:"sniffing"`
}

Build() 方法:

  1. 构建 ReceiverConfig(监听地址、端口、流设置、嗅探)
  2. 通过 inboundConfigLoader.LoadWithID() 加载协议特定设置
  3. 调用加载的配置的 Build() 获取 Protobuf 代理设置
  4. 使用 serial.ToTypedMessage() 将两者包装在 core.InboundHandlerConfig

Outbound 配置构建

go
type OutboundDetourConfig struct {
    Protocol       string           `json:"protocol"`
    SendThrough    *string          `json:"sendThrough"`
    Tag            string           `json:"tag"`
    Settings       *json.RawMessage `json:"settings"`
    StreamSetting  *StreamConfig    `json:"streamSettings"`
    ProxySettings  *ProxyConfig     `json:"proxySettings"`
    MuxSettings    *MuxConfig       `json:"mux"`
    TargetStrategy string           `json:"targetStrategy"`
}

Build() 方法:

  1. 构建 SenderConfig(出口地址、流设置、mux、代理链)
  2. 处理 targetStrategy(outbound DNS 解析的域名策略)
  3. 通过 outboundConfigLoader.LoadWithID() 加载协议特定设置
  4. 包装在 core.OutboundHandlerConfig

协议配置加载器

文件: infra/conf/xray.go

两个 JSONConfigLoader 注册表将协议名称映射到配置构造器:

go
var inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
    "dokodemo-door": func() interface{} { return new(DokodemoConfig) },
    "http":          func() interface{} { return new(HTTPServerConfig) },
    "shadowsocks":   func() interface{} { return new(ShadowsocksServerConfig) },
    "socks":         func() interface{} { return new(SocksServerConfig) },
    "vless":         func() interface{} { return new(VLessInboundConfig) },
    "vmess":         func() interface{} { return new(VMessInboundConfig) },
    "trojan":        func() interface{} { return new(TrojanServerConfig) },
    // ...
}, "protocol", "settings")

var outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
    "freedom":       func() interface{} { return new(FreedomConfig) },
    "blackhole":     func() interface{} { return new(BlackholeConfig) },
    "vless":         func() interface{} { return new(VLessOutboundConfig) },
    "vmess":         func() interface{} { return new(VMessOutboundConfig) },
    "dns":           func() interface{} { return new(DNSOutboundConfig) },
    // ...
}, "protocol", "settings")

LoadWithID() 将原始 JSON settings 反序列化到对应的配置结构体中。

关键源文件

文件用途
infra/conf/xray.go顶层 ConfigBuild()、协议加载器
infra/conf/buildable.goBuildable 接口
infra/conf/serial/builder.goBuildConfig()mergeConfigs()
infra/conf/serial/loader.goJSON/YAML/TOML 解码器
infra/conf/dns.goDNSConfigNameServerConfig
infra/conf/router.goRouterConfig
infra/conf/api.goAPIConfig
infra/conf/common.go共享类型:AddressPortListStringList
core/config.gocore.Config Protobuf、格式注册

实现要点

  • JSON 读取器(infra/conf/json/reader.go)在解析前去除 C 风格注释(///* */),支持 "JSONC" 文件。

  • PostProcessConfigureFile()Build() 之前调用,处理预处理和验证,这也是 lint 检查运行的位置。

  • Transport 字段(全局传输设置)已弃用,使用时会返回错误,引导用户使用按 inbound/outbound 配置的 streamSettings

  • 协议别名:"block" 映射到 BlackholeConfig"direct" 映射到 FreedomConfig"tunnel" 映射到 DokodemoConfig"mixed" 映射到 SocksServerConfig

  • Override() 方法处理多文件配置合并。当 outbound 配置具有相同标签时,后面的文件优先。来自非 "tail" 文件的新 outbound 会被前置以维持路由优先级。

  • Config 上的 BuildMPHCache() 方法为域名匹配器生成最小完美哈希缓存文件,可在启动时加载以跳过解析 geosite 数据文件。

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