Структура конфигурации
sing-box использует формат конфигурации на основе JSON с чётко определённой корневой структурой. Парсинг конфигурации использует контекстно-зависимый JSON-декодер с реестрами типов для полиморфных типов.
Исходный код: option/options.go, option/inbound.go, option/outbound.go, option/endpoint.go, option/dns.go, option/route.go, option/service.go, option/experimental.go
Корневая структура опций
type _Options struct {
RawMessage json.RawMessage `json:"-"`
Schema string `json:"$schema,omitempty"`
Log *LogOptions `json:"log,omitempty"`
DNS *DNSOptions `json:"dns,omitempty"`
NTP *NTPOptions `json:"ntp,omitempty"`
Certificate *CertificateOptions `json:"certificate,omitempty"`
Endpoints []Endpoint `json:"endpoints,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
Services []Service `json:"services,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
}
type Options _OptionsПример конфигурации
{
"$schema": "https://sing-box.sagernet.org/schema.json",
"log": {
"level": "info"
},
"dns": {
"servers": [...],
"rules": [...]
},
"inbounds": [
{"type": "tun", "tag": "tun-in", ...},
{"type": "mixed", "tag": "mixed-in", ...}
],
"outbounds": [
{"type": "direct", "tag": "direct"},
{"type": "vless", "tag": "proxy", ...},
{"type": "selector", "tag": "select", ...}
],
"endpoints": [
{"type": "wireguard", "tag": "wg", ...}
],
"route": {
"rules": [...],
"rule_set": [...],
"final": "proxy"
},
"services": [
{"type": "resolved", "tag": "resolved-dns", ...}
],
"experimental": {
"cache_file": {"enabled": true},
"clash_api": {"external_controller": "127.0.0.1:9090"}
}
}Валидация
Метод Options.UnmarshalJSONContext выполняет валидацию:
func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error {
decoder := json.NewDecoderContext(ctx, bytes.NewReader(content))
decoder.DisallowUnknownFields() // строгий парсинг
err := decoder.Decode((*_Options)(o))
o.RawMessage = content
return checkOptions(o)
}Проверки после парсинга:
- Дублирование тегов входящих: Два входящих не могут иметь одинаковый тег
- Дублирование тегов исходящих/эндпоинтов: Теги исходящих и эндпоинтов разделяют одно пространство имён; дубликаты не допускаются
func checkInbounds(inbounds []Inbound) error {
seen := make(map[string]bool)
for i, inbound := range inbounds {
tag := inbound.Tag
if tag == "" { tag = F.ToString(i) }
if seen[tag] { return E.New("duplicate inbound tag: ", tag) }
seen[tag] = true
}
return nil
}Парсинг типизированных входящих/исходящих/эндпоинтов
Входящие, исходящие, эндпоинты, DNS-серверы и сервисы используют один и тот же паттерн для полиморфного JSON-парсинга: поле type определяет, в какую структуру опций парсить оставшиеся поля.
Паттерн
Каждая типизированная структура имеет одинаковое строение:
type _Inbound struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Options any `json:"-"` // специфичные для типа опции, не в JSON напрямую
}Контекстно-зависимая десериализация
Десериализация использует context.Context Go для передачи реестров типов:
func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error {
// 1. Парсинг полей "type" и "tag"
err := json.UnmarshalContext(ctx, content, (*_Inbound)(h))
// 2. Получение реестра опций из контекста
registry := service.FromContext[InboundOptionsRegistry](ctx)
// 3. Создание типизированной структуры опций для данного типа
options, loaded := registry.CreateOptions(h.Type)
// 4. Парсинг оставшихся полей (исключая type/tag) в типизированную структуру
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options)
// 5. Сохранение распарсенных опций
h.Options = options
return nil
}Функция badjson.UnmarshallExcluded является ключевой — она парсит JSON-объект, исключая поля, которые уже были распарсены другой структурой. Это позволяет обрабатывать type и tag отдельно от специфичных для протокола опций.
Интерфейсы реестров
type InboundOptionsRegistry interface {
CreateOptions(inboundType string) (any, bool)
}
type OutboundOptionsRegistry interface {
CreateOptions(outboundType string) (any, bool)
}
type EndpointOptionsRegistry interface {
CreateOptions(endpointType string) (any, bool)
}
type DNSTransportOptionsRegistry interface {
CreateOptions(transportType string) (any, bool)
}
type ServiceOptionsRegistry interface {
CreateOptions(serviceType string) (any, bool)
}Опции DNS
Конфигурация DNS имеет двойную структуру для обратной совместимости:
type DNSOptions struct {
RawDNSOptions // текущий формат
LegacyDNSOptions // устаревший формат (автоматическое обновление)
}
type RawDNSOptions struct {
Servers []DNSServerOptions `json:"servers,omitempty"`
Rules []DNSRule `json:"rules,omitempty"`
Final string `json:"final,omitempty"`
ReverseMapping bool `json:"reverse_mapping,omitempty"`
DNSClientOptions
}DNS-серверы используют тот же типизированный паттерн:
type DNSServerOptions struct {
Type string `json:"type,omitempty"`
Tag string `json:"tag,omitempty"`
Options any `json:"-"`
}Устаревший формат DNS-сервера (на основе URL, как tls://1.1.1.1) автоматически обновляется до нового типизированного формата во время десериализации.
Опции маршрутизации
type RouteOptions struct {
GeoIP *GeoIPOptions
Geosite *GeositeOptions
Rules []Rule
RuleSet []RuleSet
Final string
FindProcess bool
FindNeighbor bool
AutoDetectInterface bool
OverrideAndroidVPN bool
DefaultInterface string
DefaultMark FwMark
DefaultDomainResolver *DomainResolveOptions
DefaultNetworkStrategy *NetworkStrategy
DefaultNetworkType badoption.Listable[InterfaceType]
DefaultFallbackNetworkType badoption.Listable[InterfaceType]
DefaultFallbackDelay badoption.Duration
}Экспериментальные опции
type ExperimentalOptions struct {
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
Debug *DebugOptions `json:"debug,omitempty"`
}Опции логирования
type LogOptions struct {
Disabled bool `json:"disabled,omitempty"`
Level string `json:"level,omitempty"`
Output string `json:"output,omitempty"`
Timestamp bool `json:"timestamp,omitempty"`
DisableColor bool `json:"-"` // внутреннее, не из JSON
}Общие типы опций
ListenOptions (входящие)
type ListenOptions struct {
Listen *badoption.Addr
ListenPort uint16
BindInterface string
RoutingMark FwMark
ReuseAddr bool
NetNs string
DisableTCPKeepAlive bool
TCPKeepAlive badoption.Duration
TCPKeepAliveInterval badoption.Duration
TCPFastOpen bool
TCPMultiPath bool
UDPFragment *bool
UDPTimeout UDPTimeoutCompat
Detour string
}DialerOptions (исходящие)
type DialerOptions struct {
Detour string
BindInterface string
Inet4BindAddress *badoption.Addr
Inet6BindAddress *badoption.Addr
ProtectPath string
RoutingMark FwMark
NetNs string
ConnectTimeout badoption.Duration
TCPFastOpen bool
TCPMultiPath bool
DomainResolver *DomainResolveOptions
NetworkStrategy *NetworkStrategy
NetworkType badoption.Listable[InterfaceType]
FallbackNetworkType badoption.Listable[InterfaceType]
FallbackDelay badoption.Duration
}ServerOptions (исходящие)
type ServerOptions struct {
Server string `json:"server"`
ServerPort uint16 `json:"server_port"`
}
func (o ServerOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
}Замечания по реализации
- Контекстно-зависимый JSON-парсинг является центральным элементом дизайна.
context.Contextнесёт реестры типов, внедрённые при запуске, обеспечивая полиморфный парсинг без рефлексии или генерации кода badjson.UnmarshallExcluded— это пользовательский JSON-парсер, который позволяет двум структурам разделять один JSON-объект, распределяя поля между ними. Такtype/tagотделяются от опций протоколаDisallowUnknownFieldsвключён, делая парсер строгим — опечатки в именах полей вызывают ошибки парсинга- Миграция устаревших форматов обрабатывается непосредственно во время десериализации (напр., устаревшие URL DNS-серверов, устаревшие поля входящих). Флаг контекста
dontUpgradeпозволяет выполнять сериализацию «туда-обратно» без запуска миграции - Валидация минимальна на этапе парсинга — проверяется только уникальность тегов. Семантическая валидация (напр., обязательные поля, валидные адреса) происходит при создании сервисов
RawMessageсохраняется в корневомOptionsдля обеспечения повторной сериализации или пересылки оригинальной конфигурации