Обзор системы конфигурации
Система конфигурации Xray преобразует человекочитаемый JSON (или YAML/TOML) в protobuf-сообщения, которые потребляет ядро. Конвейер имеет чёткое разделение: infra/conf/ отвечает за парсинг и валидацию, тогда как core/ и app/ работают исключительно с protobuf-типами.
Архитектура конвейера
flowchart LR
A[JSON/YAML/TOML File] --> B[Reader Decoder]
B --> C["conf.Config (Go struct)"]
C --> D["conf.Config.Build()"]
D --> E["core.Config (protobuf)"]
E --> F["core.New(config)"]
F --> G[Xray Instance]
subgraph "infra/conf/"
B
C
D
end
subgraph "core/"
E
F
G
endСтруктура конфигурации верхнего уровня
Файл: infra/conf/xray.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
type Buildable interface {
Build() (proto.Message, error)
}Каждая структура конфигурации реализует этот интерфейс. Метод Build() валидирует конфигурацию и создаёт эквивалентное protobuf-сообщение.
Поддержка нескольких форматов
Файл: infra/conf/serial/loader.go
Xray поддерживает конфигурации в форматах JSON, YAML и TOML. Все форматы преобразуются в conf.Config через функции-декодеры:
var ReaderDecoderByFormat = map[string]readerDecoder{
"json": DecodeJSONConfig,
"yaml": DecodeYAMLConfig,
"toml": DecodeTOMLConfig,
}JSON: Парсится напрямую с ридером для удаления комментариев (json_reader.Reader), с отслеживанием позиции синтаксических ошибок.
YAML: Конвертируется в JSON через yaml.YAMLToJSON(), затем парсится как JSON.
TOML: Десериализуется в map[string]interface{}, сериализуется в JSON, затем парсится как JSON.
Объединение конфигураций
Файл: infra/conf/serial/builder.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
}Метод Override() структуры Config обрабатывает объединение:
- Простые поля (log, routing, DNS, policy и т.д.) заменяются целиком
- Inbound/outbound объединяются по тегу: совпадающие теги обновляются, новые теги добавляются
- Порядок outbound зависит от имени файла: файлы, содержащие "tail", добавляются в конец, остальные — в начало
Конвейер сборки
Файл: infra/conf/xray.go — Config.Build()
Метод Build() создаёт финальный core.Config:
func (c *Config) Build() (*core.Config, error) {
PostProcessConfigureFile(c) // линтинг и валидация
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)
}
}Порядок имеет значение:
- Конфигурация логирования всегда первая (чтобы другие модули могли логировать при инициализации)
- FakeDNS добавляется перед DNS (должен инициализироваться первым для
RequireFeatures) - Dispatcher, InboundConfig, OutboundConfig всегда присутствуют как базовые приложения
Сборка конфигурации Inbound
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():
- Создаёт
ReceiverConfig(адрес прослушивания, порт, настройки потока, сниффинг) - Загружает протокол-специфичные настройки через
inboundConfigLoader.LoadWithID() - Вызывает
Build()загруженной конфигурации для получения protobuf-настроек прокси - Оборачивает оба в
core.InboundHandlerConfigсserial.ToTypedMessage()
Сборка конфигурации Outbound
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():
- Создаёт
SenderConfig(адрес via, настройки потока, mux, цепочка прокси) - Обрабатывает
targetStrategy(стратегия домена для DNS-разрешения на outbound) - Загружает протокол-специфичные настройки через
outboundConfigLoader.LoadWithID() - Оборачивает в
core.OutboundHandlerConfig
Загрузчики конфигураций протоколов
Файл: infra/conf/xray.go
Два реестра JSONConfigLoader сопоставляют имена протоколов с конструкторами конфигураций:
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 | Верхнеуровневый Config, Build(), загрузчики протоколов |
infra/conf/buildable.go | Интерфейс Buildable |
infra/conf/serial/builder.go | BuildConfig(), mergeConfigs() |
infra/conf/serial/loader.go | Декодеры JSON/YAML/TOML |
infra/conf/dns.go | DNSConfig, NameServerConfig |
infra/conf/router.go | RouterConfig |
infra/conf/api.go | APIConfig |
infra/conf/common.go | Общие типы: Address, PortList, StringList |
core/config.go | protobuf core.Config, регистрация форматов |
Заметки по реализации
JSON-ридер (
infra/conf/json/reader.go) удаляет комментарии в стиле C (//и/* */) перед парсингом. Это позволяет использовать файлы формата «JSONC».PostProcessConfigureFile()вызывается передBuild()для предварительной обработки или валидации. Здесь выполняются проверки линтинга.Поле
Transport(глобальные настройки транспорта) устарело и возвращает ошибку при использовании, направляя пользователей кstreamSettingsдля каждого inbound/outbound.Существуют псевдонимы протоколов:
"block"соответствуетBlackholeConfig,"direct"—FreedomConfig,"tunnel"—DokodemoConfig,"mixed"—SocksServerConfig.Метод
Override()обрабатывает объединение конфигураций при использовании нескольких файлов. Когда конфигурации outbound имеют одинаковый тег, более поздний файл имеет приоритет. Новые outbound из файлов без "tail" добавляются в начало для сохранения приоритета маршрутизации.Метод
BuildMPHCache()структурыConfigгенерирует файл кэша минимального идеального хэша для матчеров доменов, который может быть загружен при запуске для пропуска парсинга файлов данных geosite.