diff --git a/Makefile b/Makefile index b8ed34b96..1dc0a7f03 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ PREFIX ?= $(shell go env GOPATH) SING_FFI ?= sing-ffi LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json -.PHONY: test release docs build +.PHONY: test release docs build schema build: export GOTOOLCHAIN=local && \ @@ -32,6 +32,9 @@ ci_build: generate_completions: go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN) +schema: + go run -ldflags "$(LDFLAGS_SHARED)" --tags "$(TAGS)" $(MAIN) schema -o docs/schema.json + install: go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) diff --git a/adapter/certificate/registry.go b/adapter/certificate/registry.go index 5a080f2cc..da48560ac 100644 --- a/adapter/certificate/registry.go +++ b/adapter/certificate/registry.go @@ -2,6 +2,8 @@ package certificate import ( "context" + "maps" + "slices" "sync" "github.com/sagernet/sing-box/adapter" @@ -44,6 +46,12 @@ func NewRegistry() *Registry { } } +func (m *Registry) OptionTypes() []string { + m.access.Lock() + defer m.access.Unlock() + return slices.Sorted(maps.Keys(m.optionsType)) +} + func (m *Registry) CreateOptions(providerType string) (any, bool) { m.access.Lock() defer m.access.Unlock() diff --git a/adapter/endpoint/registry.go b/adapter/endpoint/registry.go index 92cb9025d..9cce9e800 100644 --- a/adapter/endpoint/registry.go +++ b/adapter/endpoint/registry.go @@ -2,6 +2,8 @@ package endpoint import ( "context" + "maps" + "slices" "sync" "github.com/sagernet/sing-box/adapter" @@ -44,6 +46,12 @@ func NewRegistry() *Registry { } } +func (m *Registry) OptionTypes() []string { + m.access.Lock() + defer m.access.Unlock() + return slices.Sorted(maps.Keys(m.optionsType)) +} + func (m *Registry) CreateOptions(outboundType string) (any, bool) { m.access.Lock() defer m.access.Unlock() diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go index 01e367d8b..1ce889c93 100644 --- a/adapter/inbound/registry.go +++ b/adapter/inbound/registry.go @@ -2,6 +2,8 @@ package inbound import ( "context" + "maps" + "slices" "sync" "github.com/sagernet/sing-box/adapter" @@ -44,6 +46,12 @@ func NewRegistry() *Registry { } } +func (m *Registry) OptionTypes() []string { + m.access.Lock() + defer m.access.Unlock() + return slices.Sorted(maps.Keys(m.optionsType)) +} + func (m *Registry) CreateOptions(outboundType string) (any, bool) { m.access.Lock() defer m.access.Unlock() diff --git a/adapter/outbound/registry.go b/adapter/outbound/registry.go index 8743ba103..81fc06298 100644 --- a/adapter/outbound/registry.go +++ b/adapter/outbound/registry.go @@ -2,6 +2,8 @@ package outbound import ( "context" + "maps" + "slices" "sync" "github.com/sagernet/sing-box/adapter" @@ -44,6 +46,12 @@ func NewRegistry() *Registry { } } +func (r *Registry) OptionTypes() []string { + r.access.Lock() + defer r.access.Unlock() + return slices.Sorted(maps.Keys(r.optionsType)) +} + func (r *Registry) CreateOptions(outboundType string) (any, bool) { r.access.Lock() defer r.access.Unlock() diff --git a/adapter/service/registry.go b/adapter/service/registry.go index 42fec82fc..d0b01da51 100644 --- a/adapter/service/registry.go +++ b/adapter/service/registry.go @@ -2,6 +2,8 @@ package service import ( "context" + "maps" + "slices" "sync" "github.com/sagernet/sing-box/adapter" @@ -44,6 +46,12 @@ func NewRegistry() *Registry { } } +func (m *Registry) OptionTypes() []string { + m.access.Lock() + defer m.access.Unlock() + return slices.Sorted(maps.Keys(m.optionsType)) +} + func (m *Registry) CreateOptions(outboundType string) (any, bool) { m.access.Lock() defer m.access.Unlock() diff --git a/cmd/sing-box/cmd_schema.go b/cmd/sing-box/cmd_schema.go new file mode 100644 index 000000000..73538125b --- /dev/null +++ b/cmd/sing-box/cmd_schema.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "os" + "reflect" + + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/schema" + + "github.com/spf13/cobra" +) + +var commandSchemaFlagOutput string + +var commandSchema = &cobra.Command{ + Use: "schema", + Short: "Generate configuration JSON schema", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + err := generateSchema() + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandSchema.Flags().StringVarP(&commandSchemaFlagOutput, "output", "o", "", "write schema to file instead of stdout") + mainCommand.AddCommand(commandSchema) +} + +func generateSchema() error { + content, err := schema.Generate(include.Context(context.Background()), reflect.TypeFor[option.Options]()) + if err != nil { + return err + } + if commandSchemaFlagOutput != "" { + return os.WriteFile(commandSchemaFlagOutput, content, 0o644) + } + _, err = os.Stdout.Write(content) + return err +} diff --git a/common/httpclient/apple_transport_darwin_test.go b/common/httpclient/apple_transport_darwin_test.go index 3055c25f2..272e8f1ba 100644 --- a/common/httpclient/apple_transport_darwin_test.go +++ b/common/httpclient/apple_transport_darwin_test.go @@ -94,7 +94,9 @@ func TestNewAppleSessionConfig(t *testing.T) { options: option.HTTPClientOptions{ Version: 2, DialerOptions: option.DialerOptions{ - ConnectTimeout: badoption.Duration(2 * time.Second), + AbstractDialerOptions: option.AbstractDialerOptions{ + ConnectTimeout: badoption.Duration(2 * time.Second), + }, }, OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ TLS: &option.OutboundTLSOptions{ diff --git a/dns/router.go b/dns/router.go index bd86da93c..43088881b 100644 --- a/dns/router.go +++ b/dns/router.go @@ -1623,8 +1623,10 @@ func dnsRuleActionDisablesLegacyDNSMode(action option.DNSRuleAction) bool { return true } switch action.Action { - case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + case "", C.RuleActionTypeRoute: return action.RouteOptions.DisableOptimisticCache || action.RouteOptions.Speculative + case C.RuleActionTypeEvaluate: + return action.EvaluateOptions.DisableOptimisticCache || action.EvaluateOptions.Speculative case C.RuleActionTypeRouteOptions: return action.RouteOptionsOptions.DisableOptimisticCache default: @@ -1634,8 +1636,10 @@ func dnsRuleActionDisablesLegacyDNSMode(action option.DNSRuleAction) bool { func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool { switch action.Action { - case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + case "", C.RuleActionTypeRoute: return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS + case C.RuleActionTypeEvaluate: + return C.DomainStrategy(action.EvaluateOptions.Strategy) != C.DomainStrategyAsIS case C.RuleActionTypeRouteOptions: return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS default: @@ -1663,8 +1667,14 @@ func dnsRuleActionType(rule option.DNSRule) string { func dnsRuleActionServer(rule option.DNSRule) string { switch rule.Type { case "", C.RuleTypeDefault: + if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate { + return rule.DefaultOptions.EvaluateOptions.Server + } return rule.DefaultOptions.RouteOptions.Server case C.RuleTypeLogical: + if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate { + return rule.LogicalOptions.EvaluateOptions.Server + } return rule.LogicalOptions.RouteOptions.Server default: return "" @@ -1674,9 +1684,9 @@ func dnsRuleActionServer(rule option.DNSRule) string { func dnsRuleActionEvaluateTag(rule option.DNSRule) string { switch rule.Type { case "", C.RuleTypeDefault: - return rule.DefaultOptions.RouteOptions.Tag + return rule.DefaultOptions.EvaluateOptions.Tag case C.RuleTypeLogical: - return rule.LogicalOptions.RouteOptions.Tag + return rule.LogicalOptions.EvaluateOptions.Tag default: return "" } @@ -1685,8 +1695,14 @@ func dnsRuleActionEvaluateTag(rule option.DNSRule) string { func dnsRuleActionSpeculative(rule option.DNSRule) bool { switch rule.Type { case "", C.RuleTypeDefault: + if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate { + return rule.DefaultOptions.EvaluateOptions.Speculative + } return rule.DefaultOptions.RouteOptions.Speculative case C.RuleTypeLogical: + if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate { + return rule.LogicalOptions.EvaluateOptions.Speculative + } return rule.LogicalOptions.RouteOptions.Speculative default: return false diff --git a/dns/router_race_test.go b/dns/router_race_test.go index 22e72ff7b..285554c31 100644 --- a/dns/router_race_test.go +++ b/dns/router_race_test.go @@ -174,7 +174,7 @@ func evaluateRule(server string, tag string, speculative bool) option.DNSRule { DefaultOptions: option.DefaultDNSRule{ DNSRuleAction: option.DNSRuleAction{ Action: C.RuleActionTypeEvaluate, - RouteOptions: option.DNSRouteActionOptions{ + EvaluateOptions: option.DNSEvaluateActionOptions{ Server: server, Tag: tag, Speculative: speculative, diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go index 93f24c617..7f8359463 100644 --- a/dns/transport/local/local_resolved_linux.go +++ b/dns/transport/local/local_resolved_linux.go @@ -328,8 +328,10 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServ return nil, E.New("link has no DNS servers configured") } serverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{ - BindInterface: defaultInterface.Name, - UDPFragmentDefault: true, + AbstractDialerOptions: option.AbstractDialerOptions{ + BindInterface: defaultInterface.Name, + UDPFragmentDefault: true, + }, }) if err != nil { return nil, err diff --git a/dns/transport_registry.go b/dns/transport_registry.go index d838158b2..0e98b1907 100644 --- a/dns/transport_registry.go +++ b/dns/transport_registry.go @@ -2,6 +2,8 @@ package dns import ( "context" + "maps" + "slices" "sync" "github.com/sagernet/sing-box/adapter" @@ -44,6 +46,12 @@ func NewTransportRegistry() *TransportRegistry { } } +func (r *TransportRegistry) OptionTypes() []string { + r.access.Lock() + defer r.access.Unlock() + return slices.Sorted(maps.Keys(r.optionsType)) +} + func (r *TransportRegistry) CreateOptions(transportType string) (any, bool) { r.access.Lock() defer r.access.Unlock() diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 9bc9ad1bf..67c1da414 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -5,6 +5,7 @@ sing-box uses JSON for configuration files. ```json { + "$schema": "https://sing-box.sagernet.org/schema.json", "log": {}, "dns": {}, "ntp": {}, @@ -25,6 +26,7 @@ sing-box uses JSON for configuration files. | Key | Format | |----------------|---------------------------------| +| `$schema` | [JSON Schema](./schema/) | | `log` | [Log](./log/) | | `dns` | [DNS](./dns/) | | `ntp` | [NTP](./ntp/) | diff --git a/docs/configuration/index.zh.md b/docs/configuration/index.zh.md index f1e76ca9a..d0698961c 100644 --- a/docs/configuration/index.zh.md +++ b/docs/configuration/index.zh.md @@ -5,6 +5,7 @@ sing-box 使用 JSON 作为配置文件格式。 ```json { + "$schema": "https://sing-box.sagernet.org/schema.json", "log": {}, "dns": {}, "ntp": {}, @@ -25,6 +26,7 @@ sing-box 使用 JSON 作为配置文件格式。 | Key | Format | |----------------|------------------------| +| `$schema` | [JSON Schema](./schema/) | | `log` | [日志](./log/) | | `dns` | [DNS](./dns/) | | `ntp` | [NTP](./ntp/) | diff --git a/docs/configuration/schema.md b/docs/configuration/schema.md new file mode 100644 index 000000000..210e2bf52 --- /dev/null +++ b/docs/configuration/schema.md @@ -0,0 +1,47 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +# JSON Schema + +sing-box provides a JSON Schema Draft 2020-12 for configuration files. +Compatible editors can use it for completion and validation. + +### Structure + +```json +{ + "$schema": "https://sing-box.sagernet.org/schema.json" +} +``` + +### Fields + +#### $schema + +The schema URI used by compatible editors. +This field does not affect sing-box runtime behavior. + +The schema published with this documentation is available at +[sing-box.sagernet.org/schema.json](https://sing-box.sagernet.org/schema.json). + +### Generate + +Use the following command to generate a schema matching the installed binary: + +```bash +sing-box schema -o schema.json +``` + +Without `--output`, the schema is written to standard output. +The generated schema reflects the features included in the current build. + +You can then reference the local schema from a configuration file: + +```json +{ + "$schema": "./schema.json" +} +``` diff --git a/docs/configuration/schema.zh.md b/docs/configuration/schema.zh.md new file mode 100644 index 000000000..26c85264f --- /dev/null +++ b/docs/configuration/schema.zh.md @@ -0,0 +1,47 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +# JSON Schema + +sing-box 为配置文件提供 JSON Schema Draft 2020-12。 +兼容的编辑器可使用它提供补全和校验。 + +### 结构 + +```json +{ + "$schema": "https://sing-box.sagernet.org/schema.json" +} +``` + +### 字段 + +#### $schema + +兼容编辑器使用的 Schema URI。 +该字段不影响 sing-box 的运行行为。 + +随本文档发布的 Schema 位于 +[sing-box.sagernet.org/schema.json](https://sing-box.sagernet.org/schema.json)。 + +### 生成 + +使用以下命令生成与已安装的二进制文件匹配的 Schema: + +```bash +sing-box schema -o schema.json +``` + +未指定 `--output` 时,Schema 将写入标准输出。 +生成的 Schema 会反映当前构建中包含的功能。 + +之后可从配置文件中引用本地 Schema: + +```json +{ + "$schema": "./schema.json" +} +``` diff --git a/docs/schema.json b/docs/schema.json new file mode 100644 index 000000000..ce381e595 --- /dev/null +++ b/docs/schema.json @@ -0,0 +1,17317 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sing-box.sagernet.org/schema.json", + "type": "object", + "properties": { + "$schema": { + "type": "string", + "examples": [ + "https://sing-box.sagernet.org/schema.json" + ] + }, + "log": { + "$ref": "#/$defs/LogOptions" + }, + "dns": { + "$ref": "#/$defs/DNS" + }, + "ntp": { + "$ref": "#/$defs/NTPOptions" + }, + "certificate": { + "type": "object", + "properties": { + "store": { + "type": "string", + "enum": [ + "system", + "mozilla", + "chrome", + "none" + ] + }, + "certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_directory_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "additionalProperties": false + }, + "certificate_providers": { + "type": "array", + "items": { + "$ref": "#/$defs/CertificateProvider" + } + }, + "http_clients": { + "type": "array", + "items": { + "$ref": "#/$defs/HTTPClient" + } + }, + "network_namespaces": { + "type": "array", + "items": { + "$ref": "#/$defs/NetworkNamespace" + } + }, + "endpoints": { + "type": "array", + "items": { + "$ref": "#/$defs/Endpoint" + } + }, + "inbounds": { + "type": "array", + "items": { + "$ref": "#/$defs/Inbound" + } + }, + "outbounds": { + "type": "array", + "items": { + "$ref": "#/$defs/Outbound" + } + }, + "route": { + "$ref": "#/$defs/RouteOptions" + }, + "services": { + "type": "array", + "items": { + "$ref": "#/$defs/Service" + } + }, + "experimental": { + "$ref": "#/$defs/ExperimentalOptions" + } + }, + "additionalProperties": false, + "$defs": { + "ACMEExternalAccountOptions": { + "type": "object", + "properties": { + "key_id": { + "type": "string" + }, + "mac_key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ACMEProviderDNS01Challenge": { + "oneOf": [ + { + "type": "object", + "properties": { + "provider": { + "const": "alidns" + }, + "ttl": { + "$ref": "#/$defs/Duration" + }, + "propagation_delay": { + "$ref": "#/$defs/Duration" + }, + "propagation_timeout": { + "$ref": "#/$defs/Duration" + }, + "resolvers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "override_domain": { + "type": "string" + }, + "access_key_id": { + "type": "string" + }, + "access_key_secret": { + "type": "string" + }, + "region_id": { + "type": "string" + }, + "security_token": { + "type": "string" + } + }, + "required": [ + "provider" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "cloudflare" + }, + "ttl": { + "$ref": "#/$defs/Duration" + }, + "propagation_delay": { + "$ref": "#/$defs/Duration" + }, + "propagation_timeout": { + "$ref": "#/$defs/Duration" + }, + "resolvers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "override_domain": { + "type": "string" + }, + "api_token": { + "type": "string" + }, + "zone_token": { + "type": "string" + } + }, + "required": [ + "provider" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "provider": { + "const": "acmedns" + }, + "ttl": { + "$ref": "#/$defs/Duration" + }, + "propagation_delay": { + "$ref": "#/$defs/Duration" + }, + "propagation_timeout": { + "$ref": "#/$defs/Duration" + }, + "resolvers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "override_domain": { + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "subdomain": { + "type": "string" + }, + "server_url": { + "type": "string" + } + }, + "required": [ + "provider" + ], + "additionalProperties": false + } + ] + }, + "AnyTLSUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "BrutalOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "up_mbps": { + "type": "integer" + }, + "down_mbps": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "CCMUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "additionalProperties": false + }, + "CacheFileOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "cache_id": { + "type": "string" + }, + "store_fakeip": { + "type": "boolean" + }, + "rdrc_timeout": { + "$ref": "#/$defs/Duration" + }, + "store_dns": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "CertificateProvider": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "acme" + }, + "tag": { + "type": "string" + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "data_directory": { + "type": "string" + }, + "default_server_name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "account_key": { + "type": "string" + }, + "disable_http_challenge": { + "type": "boolean" + }, + "disable_tls_alpn_challenge": { + "type": "boolean" + }, + "alternative_http_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "alternative_tls_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "external_account": { + "$ref": "#/$defs/ACMEExternalAccountOptions" + }, + "dns01_challenge": { + "$ref": "#/$defs/ACMEProviderDNS01Challenge" + }, + "key_type": { + "type": "string", + "enum": [ + "ed25519", + "p256", + "p384", + "rsa2048", + "rsa4096" + ] + }, + "profile": { + "type": "string" + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "cloudflare-origin-ca" + }, + "tag": { + "type": "string" + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "data_directory": { + "type": "string" + }, + "api_token": { + "type": "string" + }, + "origin_ca_key": { + "type": "string" + }, + "request_type": { + "type": "string", + "enum": [ + "origin-rsa", + "origin-ecc" + ] + }, + "requested_validity": { + "type": "integer", + "enum": [ + 0, + 7, + 30, + 90, + 365, + 730, + 1095, + 5475 + ], + "minimum": 0, + "maximum": 65535 + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tailscale" + }, + "tag": { + "type": "string" + }, + "endpoint": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "CertificateProviderReference": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "certificate_provider" + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "acme" + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "data_directory": { + "type": "string" + }, + "default_server_name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "account_key": { + "type": "string" + }, + "disable_http_challenge": { + "type": "boolean" + }, + "disable_tls_alpn_challenge": { + "type": "boolean" + }, + "alternative_http_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "alternative_tls_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "external_account": { + "$ref": "#/$defs/ACMEExternalAccountOptions" + }, + "dns01_challenge": { + "$ref": "#/$defs/ACMEProviderDNS01Challenge" + }, + "key_type": { + "type": "string", + "enum": [ + "ed25519", + "p256", + "p384", + "rsa2048", + "rsa4096" + ] + }, + "profile": { + "type": "string" + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "cloudflare-origin-ca" + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "data_directory": { + "type": "string" + }, + "api_token": { + "type": "string" + }, + "origin_ca_key": { + "type": "string" + }, + "request_type": { + "type": "string", + "enum": [ + "origin-rsa", + "origin-ecc" + ] + }, + "requested_validity": { + "type": "integer", + "enum": [ + 0, + 7, + 30, + 90, + 365, + 730, + 1095, + 5475 + ], + "minimum": 0, + "maximum": 65535 + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tailscale" + }, + "endpoint": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + } + ] + }, + "ClashAPIOptions": { + "type": "object", + "properties": { + "external_controller": { + "type": "string" + }, + "external_ui": { + "type": "string" + }, + "external_ui_download_url": { + "type": "string" + }, + "external_ui_download_detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "secret": { + "type": "string" + }, + "default_mode": { + "type": "string" + }, + "access_control_allow_origin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "access_control_allow_private_network": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DERPMeshOptions": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "host": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "DNS": { + "type": "object", + "properties": { + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/DNSServer" + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/DNSRule" + } + }, + "final": { + "type": "string", + "x-tag-reference": "dns_server" + }, + "reverse_mapping": { + "type": "boolean" + }, + "strategy": { + "$ref": "#/$defs/DomainStrategy" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "disable_cache": { + "type": "boolean" + }, + "disable_expire": { + "type": "boolean" + }, + "cache_capacity": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "optimistic": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "timeout": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + } + ] + }, + "client_subnet": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DNSQueryType": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "string", + "enum": [ + "A", + "AAAA", + "AFSDB", + "AMTRELAY", + "ANY", + "APL", + "ATMA", + "AVC", + "AXFR", + "CAA", + "CDNSKEY", + "CDS", + "CERT", + "CNAME", + "CSYNC", + "DHCID", + "DLV", + "DNAME", + "DNSKEY", + "DS", + "EID", + "EUI48", + "EUI64", + "GID", + "GPOS", + "HINFO", + "HIP", + "HTTPS", + "IPSECKEY", + "ISDN", + "IXFR", + "KEY", + "KX", + "L32", + "L64", + "LOC", + "LP", + "MAILA", + "MAILB", + "MB", + "MD", + "MF", + "MG", + "MINFO", + "MR", + "MX", + "NAPTR", + "NID", + "NIMLOC", + "NINFO", + "NS", + "NSAP-PTR", + "NSEC", + "NSEC3", + "NSEC3PARAM", + "NULL", + "NXNAME", + "NXT", + "None", + "OPENPGPKEY", + "OPT", + "PTR", + "PX", + "RESINFO", + "RKEY", + "RP", + "RRSIG", + "RT", + "Reserved", + "SIG", + "SMIMEA", + "SOA", + "SPF", + "SRV", + "SSHFP", + "SVCB", + "TA", + "TALINK", + "TKEY", + "TLSA", + "TSIG", + "TXT", + "UID", + "UINFO", + "UNSPEC", + "URI", + "X25", + "ZONEMD" + ] + } + ] + }, + "DNSRCode": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string", + "enum": [ + "NOERROR", + "FORMERR", + "SERVFAIL", + "NXDOMAIN", + "NOTIMP", + "NOTIMPL", + "REFUSED", + "YXDOMAIN", + "YXRRSET", + "NXRRSET", + "NOTAUTH", + "NOTZONE", + "DSOTYPENI", + "BADSIG", + "BADKEY", + "BADTIME", + "BADMODE", + "BADNAME", + "BADALG", + "BADTRUNC", + "BADCOOKIE" + ] + } + ] + }, + "DNSRule": { + "oneOf": [ + { + "type": "object", + "unevaluatedProperties": false, + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "inbound": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "inbound" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "inbound" + } + } + ] + }, + "ip_version": { + "type": "integer", + "enum": [ + 4, + 6 + ] + }, + "query_type": { + "anyOf": [ + { + "$ref": "#/$defs/DNSQueryType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/DNSQueryType" + } + } + ] + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "auth_user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "protocol": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + } + } + ] + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_keyword": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_is_private": { + "type": "boolean" + }, + "source_port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "source_port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + "clash_mode": { + "type": "string" + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "network_is_expensive": { + "type": "boolean" + }, + "network_is_constrained": { + "type": "boolean" + }, + "wifi_ssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "wifi_bssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "interface_address": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "network_interface_address": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/InterfaceType" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "default_interface_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_mac_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_hostname": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "preferred_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "rule_set": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "rule_set" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "rule_set" + } + } + ] + }, + "rule_set_ip_cidr_match_source": { + "type": "boolean" + }, + "match_response": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_is_private": { + "type": "boolean" + }, + "ip_accept_any": { + "type": "boolean" + }, + "response_rcode": { + "$ref": "#/$defs/DNSRCode" + }, + "response_answer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "response_ns": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "response_extra": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "invert": { + "type": "boolean" + } + } + }, + { + "$ref": "#/$defs/DNSRuleAction" + } + ] + }, + { + "type": "object", + "unevaluatedProperties": false, + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "logical" + }, + "mode": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/NestedDNSRule" + } + }, + "invert": { + "type": "boolean" + } + }, + "required": [ + "type", + "mode", + "rules" + ] + }, + { + "$ref": "#/$defs/DNSRuleAction" + } + ] + } + ] + }, + "DNSRuleAction": { + "oneOf": [ + { + "type": "object", + "properties": { + "action": { + "const": "route" + }, + "race": { + "type": "boolean" + }, + "server": { + "type": "string", + "x-tag-reference": "dns_server" + }, + "speculative": { + "type": "boolean" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "disable_cache": { + "type": "boolean" + }, + "disable_optimistic_cache": { + "type": "boolean" + }, + "rewrite_ttl": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "client_subnet": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "action": { + "const": "evaluate" + }, + "race": { + "type": "boolean" + }, + "server": { + "type": "string", + "x-tag-reference": "dns_server" + }, + "tag": { + "type": "string" + }, + "speculative": { + "type": "boolean" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "disable_cache": { + "type": "boolean" + }, + "disable_optimistic_cache": { + "type": "boolean" + }, + "rewrite_ttl": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "client_subnet": { + "type": "string" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "respond" + }, + "race": { + "type": "boolean" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "route-options" + }, + "race": { + "type": "boolean" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "disable_cache": { + "type": "boolean" + }, + "disable_optimistic_cache": { + "type": "boolean" + }, + "rewrite_ttl": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "client_subnet": { + "type": "string" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "reject" + }, + "race": { + "type": "boolean" + }, + "method": { + "type": "string", + "enum": [ + "", + "default", + "drop", + "reply" + ] + }, + "no_drop": { + "type": "boolean" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "predefined" + }, + "race": { + "type": "boolean" + }, + "rcode": { + "$ref": "#/$defs/DNSRCode" + }, + "answer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ns": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "extra": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "DNSServer": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "dhcp" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "prefer_go": { + "type": "boolean" + }, + "neighbor_domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "interface": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "fakeip" + }, + "tag": { + "type": "string" + }, + "inet4_range": { + "type": "string", + "examples": [ + "198.18.0.0/15" + ] + }, + "inet6_range": { + "type": "string", + "examples": [ + "fc00::/18" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "h3" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "hosts" + }, + "tag": { + "type": "string" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "predefined": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "https" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "local" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "prefer_go": { + "type": "boolean" + }, + "neighbor_domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "mdns" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "prefer_go": { + "type": "boolean" + }, + "neighbor_domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "interface": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "openconnect" + }, + "tag": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "accept_default_resolvers": { + "type": "boolean" + }, + "accept_search_domain": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "openvpn" + }, + "tag": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "accept_default_resolvers": { + "type": "boolean" + }, + "accept_search_domain": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "quic" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "resolved" + }, + "tag": { + "type": "string" + }, + "service": { + "type": "string" + }, + "accept_default_resolvers": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tailscale" + }, + "tag": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "accept_default_resolvers": { + "type": "boolean" + }, + "accept_search_domain": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tcp" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tls" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "udp" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "DebugOptions": { + "type": "object", + "properties": { + "listen": { + "type": "string" + }, + "gc_percent": { + "type": "integer" + }, + "max_stack": { + "type": "integer" + }, + "max_threads": { + "type": "integer" + }, + "panic_on_fault": { + "type": "boolean" + }, + "trace_back": { + "type": "string" + }, + "memory_limit": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "oom_killer": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DialerOptions": { + "type": "object", + "properties": { + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "DomainResolver": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "dns_server" + }, + { + "type": "object", + "properties": { + "server": { + "type": "string", + "x-tag-reference": "dns_server" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "strategy": { + "$ref": "#/$defs/DomainStrategy" + }, + "disable_cache": { + "type": "boolean" + }, + "disable_optimistic_cache": { + "type": "boolean" + }, + "rewrite_ttl": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "client_subnet": { + "type": "string" + } + }, + "required": [ + "server" + ], + "additionalProperties": false + } + ] + }, + "DomainStrategy": { + "type": "string", + "enum": [ + "", + "as_is", + "prefer_ipv4", + "prefer_ipv6", + "ipv4_only", + "ipv6_only" + ] + }, + "Duration": { + "type": "string", + "pattern": "^[-+]?(((\\d+(\\.\\d*)?|\\.\\d+)(ns|us|µs|μs|ms|s|m|h|d))+|0)$" + }, + "Endpoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "openconnect" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "system": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "udp_timeout": { + "$ref": "#/$defs/Duration" + }, + "udp_mapping": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_filtering": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_nat_max": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "server": { + "type": "string" + }, + "flavor": { + "type": "string", + "enum": [ + "anyconnect", + "gp", + "fortinet", + "f5", + "pulse", + "nc" + ] + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "auth_group": { + "type": "string" + }, + "cookie": { + "type": "string" + }, + "token": { + "$ref": "#/$defs/OpenConnectTokenOptions" + }, + "reported_os": { + "type": "string" + }, + "user_agent": { + "type": "string" + }, + "version": { + "type": "string" + }, + "local_hostname": { + "type": "string" + }, + "mobile": { + "$ref": "#/$defs/OpenConnectMobileOptions" + }, + "csd": { + "$ref": "#/$defs/OpenConnectCSDOptions" + }, + "hip": { + "$ref": "#/$defs/OpenConnectHIPOptions" + }, + "tncc": { + "$ref": "#/$defs/OpenConnectTNCCOptions" + }, + "fortinet_host_check": { + "$ref": "#/$defs/OpenConnectFortinetHostCheckOptions" + }, + "no_udp": { + "type": "boolean" + }, + "dtls_local_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "compression_disabled": { + "type": "boolean" + }, + "compression_mode": { + "type": "string", + "enum": [ + "stateless", + "all" + ] + }, + "ipv6_disabled": { + "type": "boolean" + }, + "http_keepalive_disabled": { + "type": "boolean" + }, + "xml_post_disabled": { + "type": "boolean" + }, + "external_auth_disabled": { + "type": "boolean" + }, + "password_authentication_disabled": { + "type": "boolean" + }, + "tcp_keep_alive_enabled": { + "type": "boolean" + }, + "pfs": { + "type": "boolean" + }, + "mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "base_mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "dpd_interval": { + "$ref": "#/$defs/Duration" + }, + "reconnect_timeout": { + "$ref": "#/$defs/Duration" + }, + "trojan_interval": { + "$ref": "#/$defs/Duration" + }, + "queue_length": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "allow_insecure_crypto": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/OpenConnectTLSOptions" + }, + "form_entries": { + "type": "array", + "items": { + "$ref": "#/$defs/OpenConnectFormEntryOptions" + } + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "openvpn-client" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "system": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_mapping": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_filtering": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_nat_max": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "mode": { + "type": "string", + "enum": [ + "tls", + "static_key" + ] + }, + "network": { + "type": "string", + "enum": [ + "udp", + "udp4", + "udp6", + "tcp", + "tcp4", + "tcp6" + ] + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/OpenVPNRemoteOptions" + } + }, + "remote_random": { + "type": "boolean" + }, + "address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "peer_address": { + "type": "string" + }, + "peer_address_ipv6": { + "type": "string" + }, + "topology": { + "type": "string", + "enum": [ + "net30", + "p2p", + "subnet" + ] + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "auth_retry": { + "type": "string", + "enum": [ + "none", + "nointeract", + "interact" + ] + }, + "static_challenge": { + "type": "string" + }, + "static_challenge_echo": { + "type": "boolean" + }, + "static_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "static_key_path": { + "type": "string" + }, + "key_direction": { + "type": "string", + "enum": [ + "server", + "client" + ] + }, + "tls": { + "$ref": "#/$defs/OpenVPNOutboundTLSOptions" + }, + "cipher": { + "type": "string" + }, + "data_ciphers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "data_ciphers_fallback": { + "type": "string" + }, + "auth": { + "type": "string" + }, + "mss_fix": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "mss_fix_disabled": { + "type": "boolean" + }, + "mss_fix_mode": { + "type": "string", + "enum": [ + "mtu", + "fixed" + ] + }, + "fragment": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "replay_window": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "replay_window_time": { + "$ref": "#/$defs/Duration" + }, + "compression": { + "type": "string", + "enum": [ + "none", + "no", + "lz4", + "lz4-v2", + "stub", + "stub-v2", + "disabled", + "off" + ] + }, + "compression_lzo": { + "type": "string", + "enum": [ + "none", + "no", + "yes", + "adaptive", + "asym", + "disabled", + "off" + ] + }, + "allow_compression": { + "type": "string", + "enum": [ + "no", + "asym", + "yes" + ] + }, + "route_no_pull": { + "type": "boolean" + }, + "pull_filters": { + "type": "array", + "items": { + "$ref": "#/$defs/OpenVPNPullFilterOptions" + } + }, + "routes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "route_gateway": { + "type": "string" + }, + "route_metric": { + "type": "integer" + }, + "redirect_gateway": { + "type": "boolean" + }, + "redirect_gateway_flags": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "redirect_private": { + "type": "boolean" + }, + "block_ipv6": { + "type": "boolean" + }, + "ping_interval": { + "$ref": "#/$defs/Duration" + }, + "ping_restart": { + "$ref": "#/$defs/Duration" + }, + "ping_restart_disabled": { + "type": "boolean" + }, + "renegotiate_interval": { + "$ref": "#/$defs/Duration" + }, + "renegotiate_disabled": { + "type": "boolean" + }, + "renegotiate_bytes": { + "type": "integer", + "minimum": 0 + }, + "renegotiate_packets": { + "type": "integer", + "minimum": 0 + }, + "tls_timeout": { + "$ref": "#/$defs/Duration" + }, + "handshake_window": { + "$ref": "#/$defs/Duration" + }, + "explicit_exit_notify": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "openvpn-server" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "system": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_mapping": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_filtering": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_nat_max": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "mode": { + "type": "string", + "enum": [ + "tls", + "static_key" + ] + }, + "network": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + "remote": { + "type": "string" + }, + "remote_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "max_clients": { + "type": "integer" + }, + "address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "peer_address": { + "type": "string" + }, + "peer_address_ipv6": { + "type": "string" + }, + "topology": { + "type": "string", + "enum": [ + "net30", + "p2p", + "subnet" + ] + }, + "duplicate_cn": { + "type": "boolean" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/User" + } + }, + "static_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "static_key_path": { + "type": "string" + }, + "key_direction": { + "type": "string", + "enum": [ + "server", + "client" + ] + }, + "tls": { + "$ref": "#/$defs/OpenVPNInboundTLSOptions" + }, + "cipher": { + "type": "string" + }, + "data_ciphers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "data_ciphers_fallback": { + "type": "string" + }, + "auth": { + "type": "string" + }, + "mss_fix": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "mss_fix_disabled": { + "type": "boolean" + }, + "mss_fix_mode": { + "type": "string", + "enum": [ + "mtu", + "fixed" + ] + }, + "replay_window": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "replay_window_time": { + "$ref": "#/$defs/Duration" + }, + "push": { + "$ref": "#/$defs/OpenVPNPushOptions" + }, + "ping_interval": { + "$ref": "#/$defs/Duration" + }, + "ping_restart": { + "$ref": "#/$defs/Duration" + }, + "renegotiate_interval": { + "$ref": "#/$defs/Duration" + }, + "renegotiate_disabled": { + "type": "boolean" + }, + "renegotiate_bytes": { + "type": "integer", + "minimum": 0 + }, + "renegotiate_packets": { + "type": "integer", + "minimum": 0 + }, + "handshake_window": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tailscale" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "state_directory": { + "type": "string" + }, + "auth_key": { + "type": "string" + }, + "control_url": { + "type": "string" + }, + "ephemeral": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "accept_routes": { + "type": "boolean" + }, + "exit_node": { + "type": "string" + }, + "exit_node_allow_lan_access": { + "type": "boolean" + }, + "advertise_routes": { + "type": "array", + "items": { + "type": "string" + } + }, + "advertise_exit_node": { + "type": "boolean" + }, + "advertise_tags": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "relay_server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "relay_server_static_endpoints": { + "type": "array", + "items": { + "type": "string" + } + }, + "system_interface": { + "type": "boolean" + }, + "system_interface_name": { + "type": "string" + }, + "system_interface_mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "ssh_server": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "disable_pty": { + "type": "boolean" + }, + "disable_sftp": { + "type": "boolean" + }, + "disable_forwarding": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "wireguard" + }, + "tag": { + "type": "string" + }, + "system": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "private_key": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "peers": { + "type": "array", + "items": { + "$ref": "#/$defs/WireGuardPeer" + } + }, + "udp_timeout": { + "$ref": "#/$defs/Duration" + }, + "udp_mapping": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_filtering": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_nat_max": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "workers": { + "type": "integer" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "ExperimentalOptions": { + "type": "object", + "properties": { + "cache_file": { + "$ref": "#/$defs/CacheFileOptions" + }, + "clash_api": { + "$ref": "#/$defs/ClashAPIOptions" + }, + "v2ray_api": { + "$ref": "#/$defs/V2RayAPIOptions" + }, + "debug": { + "$ref": "#/$defs/DebugOptions" + } + }, + "additionalProperties": false + }, + "HTTPClient": { + "type": "object", + "properties": { + "tag": { + "type": "string" + }, + "engine": { + "type": "string", + "enum": [ + "go", + "apple" + ] + }, + "version": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ] + }, + "disable_version_fallback": { + "type": "boolean" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "HTTPClientReference": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "http_client" + }, + { + "type": "object", + "properties": { + "engine": { + "type": "string", + "enum": [ + "go", + "apple" + ] + }, + "version": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ] + }, + "disable_version_fallback": { + "type": "boolean" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, + "HTTPHeader": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "HTTPProxyOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bypass_domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "match_domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "additionalProperties": false + }, + "HeadlessRule": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "query_type": { + "anyOf": [ + { + "$ref": "#/$defs/DNSQueryType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/DNSQueryType" + } + } + ] + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp", + "icmp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp", + "icmp" + ] + } + } + ] + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_keyword": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "source_port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "network_is_expensive": { + "type": "boolean" + }, + "network_is_constrained": { + "type": "boolean" + }, + "wifi_ssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "wifi_bssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "network_interface_address": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/InterfaceType" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "default_interface_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "invert": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "logical" + }, + "mode": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/HeadlessRule" + } + }, + "invert": { + "type": "boolean" + } + }, + "required": [ + "type", + "mode", + "rules" + ], + "additionalProperties": false + } + ] + }, + "Hysteria2InboundRealm": { + "type": "object", + "properties": { + "server_url": { + "type": "string" + }, + "token": { + "type": "string" + }, + "realm_id": { + "type": "string" + }, + "stun_servers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_version": { + "type": "integer", + "enum": [ + 0, + 4, + 6 + ] + }, + "port_mapping": { + "$ref": "#/$defs/Hysteria2RealmPortMapping" + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + }, + "stun_domain_resolver": { + "$ref": "#/$defs/DomainResolver" + } + }, + "additionalProperties": false + }, + "Hysteria2Realm": { + "type": "object", + "properties": { + "server_url": { + "type": "string" + }, + "token": { + "type": "string" + }, + "realm_id": { + "type": "string" + }, + "stun_servers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_version": { + "type": "integer", + "enum": [ + 0, + 4, + 6 + ] + }, + "port_mapping": { + "$ref": "#/$defs/Hysteria2RealmPortMapping" + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + } + }, + "additionalProperties": false + }, + "Hysteria2RealmPortMapping": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "lifetime": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "Hysteria2User": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "HysteriaRealmUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "token": { + "type": "string" + }, + "max_realms": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "HysteriaUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + }, + "auth_str": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Inbound": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "anytls" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/AnyTLSUser" + } + }, + "padding_scheme": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "cloudflared" + }, + "tag": { + "type": "string" + }, + "token": { + "type": "string" + }, + "ha_connections": { + "type": "integer" + }, + "protocol": { + "type": "string", + "enum": [ + "auto", + "quic", + "http2", + "h2mux" + ] + }, + "post_quantum": { + "type": "boolean" + }, + "edge_ip_version": { + "type": "integer", + "enum": [ + 0, + 4, + 6 + ] + }, + "datagram_version": { + "type": "string", + "enum": [ + "v2", + "v3" + ] + }, + "grace_period": { + "$ref": "#/$defs/Duration" + }, + "region": { + "type": "string" + }, + "control_dialer": { + "$ref": "#/$defs/DialerOptions" + }, + "tunnel_dialer": { + "$ref": "#/$defs/DialerOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "direct" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "override_address": { + "type": "string" + }, + "override_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "http" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/User" + } + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "set_system_proxy": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "hysteria" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "up": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "up_mbps": { + "type": "integer" + }, + "down": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "down_mbps": { + "type": "integer" + }, + "obfs": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/HysteriaUser" + } + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "hysteria2" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "up_mbps": { + "type": "integer" + }, + "down_mbps": { + "type": "integer" + }, + "obfs": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "salamander" + }, + "password": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "gecko" + }, + "password": { + "type": "string" + }, + "min_packet_size": { + "type": "integer" + }, + "max_packet_size": { + "type": "integer" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Hysteria2User" + } + }, + "ignore_client_bandwidth": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + }, + "masquerade": { + "anyOf": [ + { + "type": "string" + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "file" + }, + "directory": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "proxy" + }, + "url": { + "type": "string" + }, + "rewrite_host": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "string" + }, + "status_code": { + "type": "integer" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "content": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + } + ] + }, + "bbr_profile": { + "type": "string", + "enum": [ + "standard", + "conservative", + "aggressive" + ] + }, + "brutal_debug": { + "type": "boolean" + }, + "realm": { + "$ref": "#/$defs/Hysteria2InboundRealm" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "mixed" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/User" + } + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "set_system_proxy": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "naive" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/User" + } + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "quic_congestion_control": { + "type": "string", + "enum": [ + "bbr", + "bbr_standard", + "bbr2", + "bbr2_variant", + "cubic", + "reno" + ] + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "redirect" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "shadowsocks" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "method": { + "type": "string", + "enum": [ + "none", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", + "2022-blake3-aes-256-gcm", + "2022-blake3-chacha20-poly1305" + ] + }, + "password": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/ShadowsocksUser" + } + }, + "destinations": { + "type": "array", + "items": { + "$ref": "#/$defs/ShadowsocksDestination" + } + }, + "multiplex": { + "$ref": "#/$defs/InboundMultiplexOptions" + }, + "managed": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "shadowtls" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "version": { + "type": "integer", + "enum": [ + 1, + 2, + 3 + ] + }, + "password": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/ShadowTLSUser" + } + }, + "handshake": { + "$ref": "#/$defs/ShadowTLSHandshakeOptions" + }, + "handshake_for_server_name": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/ShadowTLSHandshakeOptions" + } + }, + "strict_mode": { + "type": "boolean" + }, + "wildcard_sni": { + "type": "string", + "enum": [ + "", + "off", + "authed", + "all" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "snell" + }, + "tag": { + "type": "string" + }, + "version": { + "const": 5 + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "psk": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/SnellUser" + } + }, + "obfs_mode": { + "type": "string", + "enum": [ + "none", + "http", + "tls" + ] + } + }, + "required": [ + "type", + "version" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "snell" + }, + "tag": { + "type": "string" + }, + "version": { + "const": 6 + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "psk": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/SnellUser" + } + }, + "mode": { + "type": "string", + "enum": [ + "default", + "unshaped", + "unsafe-raw" + ] + } + }, + "required": [ + "type", + "version" + ], + "additionalProperties": false + } + ] + }, + { + "type": "object", + "properties": { + "type": { + "const": "socks" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/User" + } + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tproxy" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "udp_mapping": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_filtering": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_nat_max": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "trojan" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/TrojanUser" + } + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "fallback": { + "$ref": "#/$defs/ServerOptions" + }, + "fallback_for_alpn": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/ServerOptions" + } + }, + "multiplex": { + "$ref": "#/$defs/InboundMultiplexOptions" + }, + "transport": { + "$ref": "#/$defs/V2RayTransport" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tuic" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/TUICUser" + } + }, + "congestion_control": { + "type": "string", + "enum": [ + "cubic", + "new_reno", + "bbr" + ] + }, + "auth_timeout": { + "$ref": "#/$defs/Duration" + }, + "zero_rtt_handshake": { + "type": "boolean" + }, + "heartbeat": { + "$ref": "#/$defs/Duration" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tun" + }, + "tag": { + "type": "string" + }, + "interface_name": { + "type": "string" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "mtu": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "address": { + "anyOf": [ + { + "type": "string", + "examples": [ + "172.19.0.1/30", + "fdfe:dcba:9876::1/126" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "examples": [ + "172.19.0.1/30", + "fdfe:dcba:9876::1/126" + ] + } + } + ] + }, + "dns_mode": { + "type": "string", + "enum": [ + "disabled", + "native", + "hijack" + ] + }, + "dns_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "auto_route": { + "type": "boolean" + }, + "iproute2_table_index": { + "type": "integer" + }, + "iproute2_rule_index": { + "type": "integer" + }, + "auto_redirect": { + "type": "boolean" + }, + "auto_redirect_input_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "auto_redirect_output_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "auto_redirect_reset_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "auto_redirect_nfqueue": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "auto_redirect_iproute2_fallback_rule_index": { + "type": "integer" + }, + "exclude_mptcp": { + "type": "boolean" + }, + "loopback_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "strict_route": { + "type": "boolean" + }, + "route_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "route_address_set": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "route_exclude_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "route_exclude_address_set": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "include_interface": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "exclude_interface": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "include_uid": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + } + ] + }, + "include_uid_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "exclude_uid": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + } + ] + }, + "exclude_uid_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "include_android_user": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + "include_package": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "exclude_package": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "include_mac_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "exclude_mac_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "udp_mapping": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_filtering": { + "type": "string", + "enum": [ + "", + "endpoint_independent", + "address_dependent", + "address_and_port_dependent" + ] + }, + "udp_nat_max": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "stack": { + "type": "string", + "enum": [ + "system", + "gvisor", + "mixed" + ] + }, + "platform": { + "$ref": "#/$defs/TunPlatformOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "vless" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/VLESSUser" + } + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "multiplex": { + "$ref": "#/$defs/InboundMultiplexOptions" + }, + "transport": { + "$ref": "#/$defs/V2RayTransport" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "vmess" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/VMessUser" + } + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "multiplex": { + "$ref": "#/$defs/InboundMultiplexOptions" + }, + "transport": { + "$ref": "#/$defs/V2RayTransport" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "InboundECHOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "key_path": { + "type": "string" + } + }, + "additionalProperties": false + }, + "InboundMultiplexOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "padding": { + "type": "boolean" + }, + "brutal": { + "$ref": "#/$defs/BrutalOptions" + } + }, + "additionalProperties": false + }, + "InboundRealityHandshakeOptions": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "InboundRealityOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "handshake": { + "$ref": "#/$defs/InboundRealityHandshakeOptions" + }, + "private_key": { + "type": "string" + }, + "short_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "max_time_difference": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "InboundTLSOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "server_name": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "alpn": { + "anyOf": [ + { + "type": "string", + "examples": [ + "http/1.1", + "h2", + "h3" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "examples": [ + "http/1.1", + "h2", + "h3" + ] + } + } + ] + }, + "min_version": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "max_version": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "cipher_suites": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "curve_preferences": { + "anyOf": [ + { + "type": "string", + "enum": [ + "P256", + "P384", + "P521", + "X25519", + "X25519MLKEM768" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "P256", + "P384", + "P521", + "X25519", + "X25519MLKEM768" + ] + } + } + ] + }, + "certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_path": { + "type": "string" + }, + "client_authentication": { + "type": "string", + "enum": [ + "no", + "request", + "require-any", + "verify-if-given", + "require-and-verify" + ] + }, + "client_certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_certificate_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_certificate_public_key_sha256": { + "anyOf": [ + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + } + } + ] + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "key_path": { + "type": "string" + }, + "kernel_tx": { + "type": "boolean" + }, + "kernel_rx": { + "type": "boolean" + }, + "handshake_timeout": { + "$ref": "#/$defs/Duration" + }, + "certificate_provider": { + "$ref": "#/$defs/CertificateProviderReference" + }, + "ech": { + "$ref": "#/$defs/InboundECHOptions" + }, + "reality": { + "$ref": "#/$defs/InboundRealityOptions" + } + }, + "additionalProperties": false + }, + "InterfaceType": { + "type": "string", + "enum": [ + "cellular", + "ethernet", + "other", + "wifi" + ] + }, + "LogOptions": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "level": { + "type": "string", + "enum": [ + "trace", + "debug", + "info", + "warn", + "warning", + "error", + "fatal", + "panic" + ] + }, + "output": { + "type": "string" + }, + "timestamp": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "NTPOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "interval": { + "$ref": "#/$defs/Duration" + }, + "write_to_system": { + "type": "boolean" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "NestedDNSRule": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "inbound": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "inbound" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "inbound" + } + } + ] + }, + "ip_version": { + "type": "integer", + "enum": [ + 4, + 6 + ] + }, + "query_type": { + "anyOf": [ + { + "$ref": "#/$defs/DNSQueryType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/DNSQueryType" + } + } + ] + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "auth_user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "protocol": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + } + } + ] + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_keyword": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_is_private": { + "type": "boolean" + }, + "source_port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "source_port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + "clash_mode": { + "type": "string" + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "network_is_expensive": { + "type": "boolean" + }, + "network_is_constrained": { + "type": "boolean" + }, + "wifi_ssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "wifi_bssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "interface_address": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "network_interface_address": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/InterfaceType" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "default_interface_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_mac_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_hostname": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "preferred_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "rule_set": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "rule_set" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "rule_set" + } + } + ] + }, + "rule_set_ip_cidr_match_source": { + "type": "boolean" + }, + "match_response": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_is_private": { + "type": "boolean" + }, + "ip_accept_any": { + "type": "boolean" + }, + "response_rcode": { + "$ref": "#/$defs/DNSRCode" + }, + "response_answer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "response_ns": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "response_extra": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "invert": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "logical" + }, + "mode": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/NestedDNSRule" + } + }, + "invert": { + "type": "boolean" + } + }, + "required": [ + "type", + "mode", + "rules" + ], + "additionalProperties": false + } + ] + }, + "NestedRule": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "inbound": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "inbound" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "inbound" + } + } + ] + }, + "ip_version": { + "type": "integer", + "enum": [ + 4, + 6 + ] + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp", + "icmp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp", + "icmp" + ] + } + } + ] + }, + "auth_user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "protocol": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + } + } + ] + }, + "client": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_keyword": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_is_private": { + "type": "boolean" + }, + "ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_is_private": { + "type": "boolean" + }, + "source_port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "source_port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + "clash_mode": { + "type": "string" + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "network_is_expensive": { + "type": "boolean" + }, + "network_is_constrained": { + "type": "boolean" + }, + "wifi_ssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "wifi_bssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "interface_address": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "network_interface_address": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/InterfaceType" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "default_interface_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_mac_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_hostname": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "preferred_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "rule_set": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "rule_set" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "rule_set" + } + } + ] + }, + "rule_set_ip_cidr_match_source": { + "type": "boolean" + }, + "invert": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "logical" + }, + "mode": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/NestedRule" + } + }, + "invert": { + "type": "boolean" + } + }, + "required": [ + "type", + "mode", + "rules" + ], + "additionalProperties": false + } + ] + }, + "NetworkNamespace": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "tag": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "tag" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "unshare" + }, + "tag": { + "type": "string" + }, + "pid_file": { + "type": "string" + } + }, + "required": [ + "type", + "tag" + ], + "additionalProperties": false + } + ] + }, + "OCMUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectCSDOptions": { + "type": "object", + "properties": { + "wrapper_path": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectFormEntryOptions": { + "type": "object", + "properties": { + "form_id": { + "type": "string" + }, + "submission_key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "promote": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "OpenConnectFortinetHostCheckOptions": { + "type": "object", + "properties": { + "hostcheck": { + "type": "string" + }, + "check_virtual_desktop": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectHIPOptions": { + "type": "object", + "properties": { + "wrapper_path": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectMobileOptions": { + "type": "object", + "properties": { + "platform_version": { + "type": "string" + }, + "device_type": { + "type": "string" + }, + "device_unique_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectTLSOptions": { + "type": "object", + "properties": { + "insecure": { + "type": "boolean" + }, + "server_name": { + "type": "string" + }, + "peer_fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "system_trust_disabled": { + "type": "boolean" + }, + "certificate_authority": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_authority_path": { + "type": "string" + }, + "client_certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_certificate_path": { + "type": "string" + }, + "client_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_key_path": { + "type": "string" + }, + "client_key_password": { + "type": "string" + }, + "mca_certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "mca_certificate_path": { + "type": "string" + }, + "mca_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "mca_key_path": { + "type": "string" + }, + "mca_key_password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectTNCCCertificateOptions": { + "type": "object", + "properties": { + "certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_path": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenConnectTNCCOptions": { + "type": "object", + "properties": { + "wrapper_path": { + "type": "string" + }, + "device_id": { + "type": "string" + }, + "user_agent": { + "type": "string" + }, + "machine_identification_enabled": { + "type": "boolean" + }, + "certificates": { + "type": "array", + "items": { + "$ref": "#/$defs/OpenConnectTNCCCertificateOptions" + } + } + }, + "additionalProperties": false + }, + "OpenConnectTokenOptions": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "totp", + "hotp", + "stoken", + "oidc" + ] + }, + "secret": { + "type": "string" + }, + "secret_path": { + "type": "string" + }, + "pin": { + "type": "string" + }, + "password": { + "type": "string" + }, + "device_id": { + "type": "string" + }, + "counter": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "OpenVPNControlWrapOptions": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "tls_auth", + "tls_crypt", + "tls_crypt_v2" + ] + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "key_path": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "server", + "client" + ] + } + }, + "additionalProperties": false + }, + "OpenVPNInboundControlWrapOptions": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "tls_auth", + "tls_crypt", + "tls_crypt_v2" + ] + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "key_path": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "server", + "client" + ] + }, + "force_cookie": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "OpenVPNInboundTLSOptions": { + "type": "object", + "properties": { + "certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_path": { + "type": "string" + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "key_path": { + "type": "string" + }, + "client_certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_certificate_path": { + "type": "string" + }, + "verify_client_certificate": { + "type": "string", + "enum": [ + "require", + "optional", + "none" + ] + }, + "client_name": { + "type": "string" + }, + "client_name_type": { + "type": "string", + "enum": [ + "subject", + "name", + "name-prefix" + ] + }, + "peer_fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "crl_path": { + "type": "string" + }, + "remote_certificate_ku": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "remote_certificate_eku": { + "type": "string" + }, + "remote_certificate_tls": { + "type": "string", + "enum": [ + "server", + "client", + "none" + ] + }, + "certificate_profile": { + "type": "string", + "enum": [ + "legacy", + "preferred", + "insecure", + "suiteb" + ] + }, + "ns_certificate_type": { + "type": "string", + "enum": [ + "server", + "client" + ] + }, + "version_min": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "version_max": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "cipher": { + "type": "string" + }, + "groups": { + "type": "string" + }, + "control_wrap": { + "$ref": "#/$defs/OpenVPNInboundControlWrapOptions" + } + }, + "additionalProperties": false + }, + "OpenVPNOutboundTLSOptions": { + "type": "object", + "properties": { + "server_name": { + "type": "string" + }, + "server_name_type": { + "type": "string", + "enum": [ + "subject", + "name", + "name-prefix" + ] + }, + "certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_path": { + "type": "string" + }, + "client_certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_certificate_path": { + "type": "string" + }, + "client_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_key_path": { + "type": "string" + }, + "peer_fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "crl_path": { + "type": "string" + }, + "remote_certificate_ku": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "remote_certificate_eku": { + "type": "string" + }, + "remote_certificate_tls": { + "type": "string", + "enum": [ + "server", + "client", + "none" + ] + }, + "certificate_profile": { + "type": "string", + "enum": [ + "legacy", + "preferred", + "insecure", + "suiteb" + ] + }, + "ns_certificate_type": { + "type": "string", + "enum": [ + "server", + "client" + ] + }, + "version_min": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "version_max": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "cipher": { + "type": "string" + }, + "groups": { + "type": "string" + }, + "control_wrap": { + "$ref": "#/$defs/OpenVPNControlWrapOptions" + } + }, + "additionalProperties": false + }, + "OpenVPNPullFilterOptions": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "accept", + "ignore", + "reject" + ] + }, + "text": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenVPNPushDNSServerOptions": { + "type": "object", + "properties": { + "priority": { + "type": "integer" + }, + "addresses": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "resolve_domains": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dnssec": { + "type": "string", + "enum": [ + "yes", + "optional", + "no" + ] + }, + "transport": { + "type": "string", + "enum": [ + "plain", + "dot", + "doh" + ] + }, + "sni": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OpenVPNPushOptions": { + "type": "object", + "properties": { + "routes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dns": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dns_servers": { + "type": "array", + "items": { + "$ref": "#/$defs/OpenVPNPushDNSServerOptions" + } + }, + "search_domains": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dhcp_options": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "redirect_gateway": { + "type": "boolean" + }, + "redirect_gateway_flags": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "block_outside_dns": { + "type": "boolean" + }, + "ping_interval": { + "$ref": "#/$defs/Duration" + }, + "ping_restart": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "OpenVPNRemoteOptions": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "network": { + "type": "string", + "enum": [ + "udp", + "udp4", + "udp6", + "tcp", + "tcp4", + "tcp6" + ] + } + }, + "additionalProperties": false + }, + "Outbound": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "anytls" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "password": { + "type": "string" + }, + "idle_session_check_interval": { + "$ref": "#/$defs/Duration" + }, + "idle_session_timeout": { + "$ref": "#/$defs/Duration" + }, + "min_idle_session": { + "type": "integer" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "block" + }, + "tag": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "bridge" + }, + "tag": { + "type": "string" + }, + "interface": { + "type": "string" + }, + "bridge_name": { + "type": "string" + }, + "iproute2_table_index": { + "type": "integer" + }, + "iproute2_rule_index": { + "type": "integer" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "direct" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "http" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "path": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "hysteria" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "server_ports": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "hop_interval": { + "$ref": "#/$defs/Duration" + }, + "up": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "up_mbps": { + "type": "integer" + }, + "down": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "down_mbps": { + "type": "integer" + }, + "obfs": { + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + }, + "auth_str": { + "type": "string" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "hysteria2" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "server_ports": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "hop_interval": { + "$ref": "#/$defs/Duration" + }, + "hop_interval_max": { + "$ref": "#/$defs/Duration" + }, + "up_mbps": { + "type": "integer" + }, + "down_mbps": { + "type": "integer" + }, + "obfs": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "salamander" + }, + "password": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "gecko" + }, + "password": { + "type": "string" + }, + "min_packet_size": { + "type": "integer" + }, + "max_packet_size": { + "type": "integer" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "password": { + "type": "string" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + }, + "bbr_profile": { + "type": "string", + "enum": [ + "standard", + "conservative", + "aggressive" + ] + }, + "brutal_debug": { + "type": "boolean" + }, + "realm": { + "$ref": "#/$defs/Hysteria2Realm" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "naive" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "insecure_concurrency": { + "type": "integer" + }, + "extra_headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "udp_over_tcp": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "version": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "minimum": 0, + "maximum": 255 + } + }, + "additionalProperties": false + } + ] + }, + "quic": { + "type": "boolean" + }, + "quic_congestion_control": { + "type": "string", + "enum": [ + "bbr", + "bbr2", + "cubic", + "reno" + ] + }, + "quic_session_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "selector" + }, + "tag": { + "type": "string" + }, + "outbounds": { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "outbound" + } + }, + "default": { + "type": "string", + "x-tag-reference": "outbound" + }, + "interrupt_exist_connections": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "shadowsocks" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "method": { + "type": "string", + "enum": [ + "none", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", + "2022-blake3-aes-256-gcm", + "2022-blake3-chacha20-poly1305", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "rc4-md5", + "chacha20-ietf", + "xchacha20" + ] + }, + "password": { + "type": "string" + }, + "plugin": { + "type": "string" + }, + "plugin_opts": { + "type": "string" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "udp_over_tcp": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "version": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "minimum": 0, + "maximum": 255 + } + }, + "additionalProperties": false + } + ] + }, + "multiplex": { + "$ref": "#/$defs/OutboundMultiplexOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "shadowtls" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "version": { + "type": "integer", + "enum": [ + 1, + 2, + 3 + ] + }, + "password": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "snell" + }, + "tag": { + "type": "string" + }, + "version": { + "const": 4 + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "psk": { + "type": "string" + }, + "userkey": { + "type": "string" + }, + "reuse": { + "type": "boolean" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "obfs_mode": { + "type": "string", + "enum": [ + "none", + "http", + "tls" + ] + }, + "obfs_host": { + "type": "string" + } + }, + "required": [ + "type", + "version" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "snell" + }, + "tag": { + "type": "string" + }, + "version": { + "const": 6 + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "psk": { + "type": "string" + }, + "userkey": { + "type": "string" + }, + "reuse": { + "type": "boolean" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "mode": { + "type": "string", + "enum": [ + "default", + "unshaped", + "unsafe-raw" + ] + } + }, + "required": [ + "type", + "version" + ], + "additionalProperties": false + } + ] + }, + { + "type": "object", + "properties": { + "type": { + "const": "socks" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "version": { + "type": "string", + "enum": [ + "4", + "4a", + "5" + ] + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "udp_over_tcp": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "version": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "minimum": 0, + "maximum": 255 + } + }, + "additionalProperties": false + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "ssh" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "user": { + "type": "string" + }, + "password": { + "type": "string" + }, + "private_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "private_key_path": { + "type": "string" + }, + "private_key_passphrase": { + "type": "string" + }, + "host_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "host_key_algorithms": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_version": { + "type": "string" + }, + "cipher": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "mac": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "kex_algorithm": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tor" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "executable_path": { + "type": "string" + }, + "extra_args": { + "type": "array", + "items": { + "type": "string" + } + }, + "data_directory": { + "type": "string" + }, + "torrc": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "trojan" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "password": { + "type": "string" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "multiplex": { + "$ref": "#/$defs/OutboundMultiplexOptions" + }, + "transport": { + "$ref": "#/$defs/V2RayTransport" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "tuic" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "uuid": { + "type": "string" + }, + "password": { + "type": "string" + }, + "congestion_control": { + "type": "string", + "enum": [ + "cubic", + "new_reno", + "bbr" + ] + }, + "udp_relay_mode": { + "type": "string", + "enum": [ + "native", + "quic" + ] + }, + "udp_over_stream": { + "type": "boolean" + }, + "zero_rtt_handshake": { + "type": "boolean" + }, + "heartbeat": { + "$ref": "#/$defs/Duration" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "urltest" + }, + "tag": { + "type": "string" + }, + "outbounds": { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "outbound" + } + }, + "url": { + "type": "string" + }, + "interval": { + "$ref": "#/$defs/Duration" + }, + "tolerance": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "interrupt_exist_connections": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "vless" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "uuid": { + "type": "string" + }, + "flow": { + "type": "string" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "multiplex": { + "$ref": "#/$defs/OutboundMultiplexOptions" + }, + "transport": { + "$ref": "#/$defs/V2RayTransport" + }, + "packet_encoding": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "vmess" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "uuid": { + "type": "string" + }, + "security": { + "type": "string", + "enum": [ + "auto", + "none", + "zero", + "aes-128-cfb", + "aes-128-gcm", + "chacha20-poly1305" + ] + }, + "alter_id": { + "type": "integer" + }, + "global_padding": { + "type": "boolean" + }, + "authenticated_length": { + "type": "boolean" + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp" + ] + } + } + ] + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "packet_encoding": { + "type": "string", + "enum": [ + "packetaddr", + "xudp" + ] + }, + "multiplex": { + "$ref": "#/$defs/OutboundMultiplexOptions" + }, + "transport": { + "$ref": "#/$defs/V2RayTransport" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "OutboundECHOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "config": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "config_path": { + "type": "string" + }, + "query_server_name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OutboundMultiplexOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "protocol": { + "type": "string", + "enum": [ + "h2mux", + "smux", + "yamux" + ] + }, + "max_connections": { + "type": "integer" + }, + "min_streams": { + "type": "integer" + }, + "max_streams": { + "type": "integer" + }, + "padding": { + "type": "boolean" + }, + "brutal": { + "$ref": "#/$defs/BrutalOptions" + } + }, + "additionalProperties": false + }, + "OutboundRealityOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "public_key": { + "type": "string" + }, + "short_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OutboundTLSOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "engine": { + "type": "string", + "enum": [ + "go", + "apple", + "windows" + ] + }, + "disable_sni": { + "type": "boolean" + }, + "server_name": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "alpn": { + "anyOf": [ + { + "type": "string", + "examples": [ + "http/1.1", + "h2", + "h3" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "examples": [ + "http/1.1", + "h2", + "h3" + ] + } + } + ] + }, + "min_version": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "max_version": { + "type": "string", + "enum": [ + "1.0", + "1.1", + "1.2", + "1.3" + ] + }, + "cipher_suites": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "curve_preferences": { + "anyOf": [ + { + "type": "string", + "enum": [ + "P256", + "P384", + "P521", + "X25519", + "X25519MLKEM768" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "P256", + "P384", + "P521", + "X25519", + "X25519MLKEM768" + ] + } + } + ] + }, + "certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "certificate_path": { + "type": "string" + }, + "certificate_public_key_sha256": { + "anyOf": [ + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + } + } + ] + }, + "client_certificate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_certificate_path": { + "type": "string" + }, + "client_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "client_key_path": { + "type": "string" + }, + "fragment": { + "type": "boolean" + }, + "fragment_fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "record_fragment": { + "type": "boolean" + }, + "spoof": { + "type": "string" + }, + "spoof_method": { + "type": "string", + "enum": [ + "wrong-sequence", + "wrong-checksum", + "wrong-ack", + "wrong-md5", + "wrong-timestamp" + ] + }, + "kernel_tx": { + "type": "boolean" + }, + "kernel_rx": { + "type": "boolean" + }, + "handshake_timeout": { + "$ref": "#/$defs/Duration" + }, + "ech": { + "$ref": "#/$defs/OutboundECHOptions" + }, + "utls": { + "$ref": "#/$defs/OutboundUTLSOptions" + }, + "reality": { + "$ref": "#/$defs/OutboundRealityOptions" + } + }, + "additionalProperties": false + }, + "OutboundUTLSOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fingerprint": { + "type": "string", + "enum": [ + "chrome_psk", + "chrome_psk_shuffle", + "chrome_padding_psk_shuffle", + "chrome_pq", + "chrome_pq_psk", + "chrome", + "firefox", + "edge", + "safari", + "360", + "qq", + "ios", + "android", + "random", + "randomized" + ] + } + }, + "additionalProperties": false + }, + "RouteOptions": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/Rule" + } + }, + "rule_set": { + "type": "array", + "items": { + "$ref": "#/$defs/RuleSet" + } + }, + "final": { + "type": "string", + "x-tag-reference": "outbound" + }, + "find_process": { + "type": "boolean" + }, + "find_neighbor": { + "type": "boolean" + }, + "dhcp_lease_files": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "auto_detect_interface": { + "type": "boolean" + }, + "override_android_vpn": { + "type": "boolean" + }, + "default_interface": { + "type": "string" + }, + "default_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "default_domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "default_network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "default_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "default_fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "default_fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "default_http_client": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Rule": { + "oneOf": [ + { + "type": "object", + "unevaluatedProperties": false, + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "inbound": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "inbound" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "inbound" + } + } + ] + }, + "ip_version": { + "type": "integer", + "enum": [ + 4, + 6 + ] + }, + "network": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tcp", + "udp", + "icmp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tcp", + "udp", + "icmp" + ] + } + } + ] + }, + "auth_user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "protocol": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + } + } + ] + }, + "client": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_keyword": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "domain_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_ip_is_private": { + "type": "boolean" + }, + "ip_cidr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ip_is_private": { + "type": "boolean" + }, + "source_port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "source_port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "port": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + } + ] + }, + "port_range": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "process_path_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "package_name_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "user_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + }, + "clash_mode": { + "type": "string" + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "network_is_expensive": { + "type": "boolean" + }, + "network_is_constrained": { + "type": "boolean" + }, + "wifi_ssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "wifi_bssid": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "interface_address": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "network_interface_address": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/InterfaceType" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "default_interface_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_mac_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "source_hostname": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "preferred_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "rule_set": { + "anyOf": [ + { + "type": "string", + "x-tag-reference": "rule_set" + }, + { + "type": "array", + "items": { + "type": "string", + "x-tag-reference": "rule_set" + } + } + ] + }, + "rule_set_ip_cidr_match_source": { + "type": "boolean" + }, + "invert": { + "type": "boolean" + } + } + }, + { + "$ref": "#/$defs/RuleAction" + } + ] + }, + { + "type": "object", + "unevaluatedProperties": false, + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "logical" + }, + "mode": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/NestedRule" + } + }, + "invert": { + "type": "boolean" + } + }, + "required": [ + "type", + "mode", + "rules" + ] + }, + { + "$ref": "#/$defs/RuleAction" + } + ] + } + ] + }, + "RuleAction": { + "oneOf": [ + { + "type": "object", + "properties": { + "action": { + "const": "route" + }, + "outbound": { + "type": "string", + "x-tag-reference": "outbound" + }, + "override_address": { + "type": "string" + }, + "override_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "fallback_delay": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_disable_domain_unmapping": { + "type": "boolean" + }, + "udp_connect": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/Duration" + }, + "tls_fragment": { + "type": "boolean" + }, + "tls_fragment_fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "tls_record_fragment": { + "type": "boolean" + }, + "tls_spoof": { + "type": "string" + }, + "tls_spoof_method": { + "type": "string", + "enum": [ + "wrong-sequence", + "wrong-checksum", + "wrong-ack", + "wrong-md5", + "wrong-timestamp" + ] + } + } + }, + { + "type": "object", + "properties": { + "action": { + "const": "route-options" + }, + "override_address": { + "type": "string" + }, + "override_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "fallback_delay": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_disable_domain_unmapping": { + "type": "boolean" + }, + "udp_connect": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/Duration" + }, + "tls_fragment": { + "type": "boolean" + }, + "tls_fragment_fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "tls_record_fragment": { + "type": "boolean" + }, + "tls_spoof": { + "type": "string" + }, + "tls_spoof_method": { + "type": "string", + "enum": [ + "wrong-sequence", + "wrong-checksum", + "wrong-ack", + "wrong-md5", + "wrong-timestamp" + ] + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "direct" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "bypass" + }, + "outbound": { + "type": "string", + "x-tag-reference": "outbound" + }, + "override_address": { + "type": "string" + }, + "override_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "fallback_delay": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "udp_disable_domain_unmapping": { + "type": "boolean" + }, + "udp_connect": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/Duration" + }, + "tls_fragment": { + "type": "boolean" + }, + "tls_fragment_fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "tls_record_fragment": { + "type": "boolean" + }, + "tls_spoof": { + "type": "string" + }, + "tls_spoof_method": { + "type": "string", + "enum": [ + "wrong-sequence", + "wrong-checksum", + "wrong-ack", + "wrong-md5", + "wrong-timestamp" + ] + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "reject" + }, + "method": { + "type": "string", + "enum": [ + "", + "default", + "drop", + "reply" + ] + }, + "no_drop": { + "type": "boolean" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "hijack-dns" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "sniff" + }, + "sniffer": { + "anyOf": [ + { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tls", + "http", + "quic", + "dns", + "stun", + "bittorrent", + "dtls", + "ssh", + "rdp", + "ntp" + ] + } + } + ] + }, + "timeout": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "action" + ] + }, + { + "type": "object", + "properties": { + "action": { + "const": "resolve" + }, + "server": { + "type": "string", + "x-tag-reference": "dns_server" + }, + "timeout": { + "$ref": "#/$defs/Duration" + }, + "strategy": { + "$ref": "#/$defs/DomainStrategy" + }, + "disable_cache": { + "type": "boolean" + }, + "disable_optimistic_cache": { + "type": "boolean" + }, + "rewrite_ttl": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "client_subnet": { + "type": "string" + } + }, + "required": [ + "action" + ] + } + ] + }, + "RuleSet": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline", + "" + ] + }, + "tag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/$defs/HeadlessRule" + } + } + }, + "required": [ + "tag" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "local" + }, + "tag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "format": { + "type": "string", + "enum": [ + "source", + "binary" + ] + }, + "path": { + "type": "string" + } + }, + "required": [ + "type", + "tag" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "remote" + }, + "tag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "format": { + "type": "string", + "enum": [ + "source", + "binary" + ] + }, + "url": { + "type": "string" + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + }, + "update_interval": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "type", + "tag" + ], + "additionalProperties": false + } + ] + }, + "ServerOptions": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + "additionalProperties": false + }, + "Service": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "api" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "secret": { + "type": "string" + }, + "access_control_allow_origin": { + "anyOf": [ + { + "type": "string", + "examples": [ + "http://sing-box-dashboard.sagernet.org/", + "https://sing-box-dashboard.sagernet.org/" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "examples": [ + "http://sing-box-dashboard.sagernet.org/", + "https://sing-box-dashboard.sagernet.org/" + ] + } + } + ] + }, + "access_control_allow_private_network": { + "type": "boolean" + }, + "dashboard": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "download_url": { + "type": "string" + }, + "http_client": { + "$ref": "#/$defs/HTTPClientReference" + }, + "update_interval": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + } + ] + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "ccm" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "credential_path": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/CCMUser" + } + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "usages_path": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "derp" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "config_path": { + "type": "string" + }, + "verify_client_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "verify_client_url": { + "anyOf": [ + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "tag": { + "type": "string" + }, + "engine": { + "type": "string", + "enum": [ + "go", + "apple" + ] + }, + "version": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ] + }, + "disable_version_fallback": { + "type": "boolean" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "tag": { + "type": "string" + }, + "engine": { + "type": "string", + "enum": [ + "go", + "apple" + ] + }, + "version": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ] + }, + "disable_version_fallback": { + "type": "boolean" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "tls": { + "$ref": "#/$defs/OutboundTLSOptions" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "initial_packet_size": { + "type": "integer" + }, + "disable_path_mtu_discovery": { + "type": "boolean" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } + ] + }, + "home": { + "type": "string" + }, + "mesh_with": { + "anyOf": [ + { + "$ref": "#/$defs/DERPMeshOptions" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/DERPMeshOptions" + } + } + ] + }, + "mesh_psk": { + "type": "string" + }, + "mesh_psk_file": { + "type": "string" + }, + "stun": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + } + }, + "additionalProperties": false + } + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "hysteria-realm" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "keep_alive_period": { + "$ref": "#/$defs/Duration" + }, + "stream_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "connection_receive_window": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "max_concurrent_streams": { + "type": "integer" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/HysteriaRealmUser" + } + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "ocm" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + }, + "credential_path": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/OCMUser" + } + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "usages_path": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "oom-killer" + }, + "tag": { + "type": "string" + }, + "memory_limit": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "safety_margin": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string" + } + ] + }, + "min_interval": { + "$ref": "#/$defs/Duration" + }, + "max_interval": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "resolved" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "ssm-api" + }, + "tag": { + "type": "string" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "servers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "cache_path": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/InboundTLSOptions" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "usbip-client" + }, + "tag": { + "type": "string" + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "devices": { + "type": "array", + "items": { + "$ref": "#/$defs/USBIPDeviceMatch" + } + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "usbip-server" + }, + "tag": { + "type": "string" + }, + "provider": { + "type": "string", + "enum": [ + "default", + "" + ] + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + }, + "devices": { + "type": "array", + "items": { + "$ref": "#/$defs/USBIPDeviceMatch" + } + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "usbip-server" + }, + "tag": { + "type": "string" + }, + "provider": { + "const": "dynamic" + }, + "listen": { + "type": "string" + }, + "listen_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "bind_interface": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "udp_fragment": { + "type": "boolean" + }, + "udp_timeout": { + "$ref": "#/$defs/UDPTimeout" + }, + "detour": { + "type": "string", + "x-tag-reference": "inbound" + } + }, + "required": [ + "type", + "provider" + ], + "additionalProperties": false + } + ] + } + ] + }, + "ShadowTLSHandshakeOptions": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "detour": { + "type": "string", + "x-tag-reference": "outbound" + }, + "bind_interface": { + "type": "string" + }, + "inet4_bind_address": { + "type": "string" + }, + "inet6_bind_address": { + "type": "string" + }, + "bind_address_no_port": { + "type": "boolean" + }, + "protect_path": { + "type": "string" + }, + "routing_mark": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string" + } + ] + }, + "reuse_addr": { + "type": "boolean" + }, + "netns": { + "type": "string", + "x-tag-reference": "network_namespace" + }, + "connect_timeout": { + "$ref": "#/$defs/Duration" + }, + "tcp_fast_open": { + "type": "boolean" + }, + "tcp_multi_path": { + "type": "boolean" + }, + "disable_tcp_keep_alive": { + "type": "boolean" + }, + "tcp_keep_alive": { + "$ref": "#/$defs/Duration" + }, + "tcp_keep_alive_interval": { + "$ref": "#/$defs/Duration" + }, + "udp_fragment": { + "type": "boolean" + }, + "domain_resolver": { + "$ref": "#/$defs/DomainResolver" + }, + "network_strategy": { + "type": "string", + "enum": [ + "default", + "fallback", + "hybrid" + ] + }, + "network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_network_type": { + "anyOf": [ + { + "$ref": "#/$defs/InterfaceType" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/InterfaceType" + } + } + ] + }, + "fallback_delay": { + "$ref": "#/$defs/Duration" + } + }, + "additionalProperties": false + }, + "ShadowTLSUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ShadowsocksDestination": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "server": { + "type": "string" + }, + "server_port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + "additionalProperties": false + }, + "ShadowsocksUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SnellUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "userkey": { + "type": "string" + } + }, + "additionalProperties": false + }, + "TUICUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "TrojanUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "TunPlatformOptions": { + "type": "object", + "properties": { + "http_proxy": { + "$ref": "#/$defs/HTTPProxyOptions" + } + }, + "additionalProperties": false + }, + "UDPTimeout": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "string", + "pattern": "^[-+]?(((\\d+(\\.\\d*)?|\\.\\d+)(ns|us|µs|μs|ms|s|m|h|d))+|0)$" + } + ] + }, + "USBIPDeviceMatch": { + "type": "object", + "properties": { + "bus_id": { + "type": "string" + }, + "vendor_id": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "product_id": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "serial": { + "type": "string" + } + }, + "additionalProperties": false + }, + "User": { + "type": "object", + "properties": { + "Username": { + "type": "string" + }, + "Password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "V2RayAPIOptions": { + "type": "object", + "properties": { + "listen": { + "type": "string" + }, + "stats": { + "$ref": "#/$defs/V2RayStatsServiceOptions" + } + }, + "additionalProperties": false + }, + "V2RayStatsServiceOptions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "inbounds": { + "type": "array", + "items": { + "type": "string" + } + }, + "outbounds": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "V2RayTransport": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "http" + }, + "host": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "ping_timeout": { + "$ref": "#/$defs/Duration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "ws" + }, + "path": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + }, + "max_early_data": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "early_data_header_name": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "quic" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "grpc" + }, + "service_name": { + "type": "string" + }, + "idle_timeout": { + "$ref": "#/$defs/Duration" + }, + "ping_timeout": { + "$ref": "#/$defs/Duration" + }, + "permit_without_stream": { + "type": "boolean" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "httpupgrade" + }, + "host": { + "type": "string" + }, + "path": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/HTTPHeader" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + }, + "VLESSUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "flow": { + "type": "string" + } + }, + "additionalProperties": false + }, + "VMessUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "alterId": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "WireGuardPeer": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "port": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "public_key": { + "type": "string" + }, + "pre_shared_key": { + "type": "string" + }, + "allowed_ips": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "persistent_keepalive_interval": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "reserved": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/experimental/boxdd/application_service.go b/experimental/boxdd/application_service.go index 7ea7878a5..ed986a307 100644 --- a/experimental/boxdd/application_service.go +++ b/experimental/boxdd/application_service.go @@ -2,12 +2,16 @@ package main import ( "context" + "reflect" "time" "github.com/sagernet/sing-box/common/networkquality" "github.com/sagernet/sing-box/common/stun" "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing-box/experimental/libbox" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/schema" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -38,6 +42,14 @@ func (s *applicationService) FormatConfig(ctx context.Context, request *ConfigCo return &ConfigContent{Content: content}, nil } +func (s *applicationService) GenerateConfigSchema(ctx context.Context, request *emptypb.Empty) (*ConfigContent, error) { + content, err := schema.Generate(include.Context(context.Background()), reflect.TypeFor[option.Options]()) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &ConfigContent{Content: string(content)}, nil +} + func (s *applicationService) EncodeProfile(ctx context.Context, request *ProfileContent) (*ProfileData, error) { content := libbox.ProfileContent{ Name: request.Name, diff --git a/experimental/boxdd/desktop_service.pb.go b/experimental/boxdd/desktop_service.pb.go index 94bbeb0fc..c9eaf014a 100644 --- a/experimental/boxdd/desktop_service.pb.go +++ b/experimental/boxdd/desktop_service.pb.go @@ -6,7 +6,6 @@ import ( unsafe "unsafe" daemon "github.com/sagernet/sing-box/daemon" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" @@ -1743,10 +1742,11 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" + "\x13DeleteAllOOMReports\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12P\n" + "\rInstallUpdate\x12\x1d.desktop.InstallUpdateRequest\x1a\x1e.desktop.InstallUpdateResponse\"\x00\x12J\n" + "\x13GetSecuritySettings\x12\x16.google.protobuf.Empty\x1a\x19.desktop.SecuritySettings\"\x00\x12Z\n" + - "\x16SetInsecureModeEnabled\x12&.desktop.SetInsecureModeEnabledRequest\x1a\x16.google.protobuf.Empty\"\x002\xbd\x04\n" + + "\x16SetInsecureModeEnabled\x12&.desktop.SetInsecureModeEnabledRequest\x1a\x16.google.protobuf.Empty\"\x002\x87\x05\n" + "\x12ApplicationService\x12?\n" + "\vCheckConfig\x12\x16.desktop.ConfigContent\x1a\x16.google.protobuf.Empty\"\x00\x12@\n" + - "\fFormatConfig\x12\x16.desktop.ConfigContent\x1a\x16.desktop.ConfigContent\"\x00\x12@\n" + + "\fFormatConfig\x12\x16.desktop.ConfigContent\x1a\x16.desktop.ConfigContent\"\x00\x12H\n" + + "\x14GenerateConfigSchema\x12\x16.google.protobuf.Empty\x1a\x16.desktop.ConfigContent\"\x00\x12@\n" + "\rEncodeProfile\x12\x17.desktop.ProfileContent\x1a\x14.desktop.ProfileData\"\x00\x12@\n" + "\rDecodeProfile\x12\x14.desktop.ProfileData\x1a\x17.desktop.ProfileContent\"\x00\x12H\n" + "\rArchiveReport\x12\x1d.desktop.ArchiveReportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12y\n" + @@ -1837,41 +1837,43 @@ var file_experimental_boxdd_desktop_service_proto_depIdxs = []int32{ 27, // 28: desktop.DesktopService.SetInsecureModeEnabled:input_type -> desktop.SetInsecureModeEnabledRequest 9, // 29: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent 9, // 30: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent - 10, // 31: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent - 11, // 32: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData - 3, // 33: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest - 4, // 34: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest - 5, // 35: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest - 6, // 36: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo - 30, // 37: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty - 30, // 38: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty - 30, // 39: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty - 12, // 40: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo - 30, // 41: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty - 13, // 42: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList - 17, // 43: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent - 30, // 44: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty - 19, // 45: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive - 30, // 46: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty - 30, // 47: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty - 20, // 48: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList - 24, // 49: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent - 30, // 50: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty - 19, // 51: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive - 30, // 52: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty - 30, // 53: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty - 29, // 54: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse - 26, // 55: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings - 30, // 56: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty - 30, // 57: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty - 9, // 58: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent - 11, // 59: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData - 10, // 60: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent - 30, // 61: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty - 31, // 62: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress - 32, // 63: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress - 36, // [36:64] is the sub-list for method output_type - 8, // [8:36] is the sub-list for method input_type + 30, // 31: desktop.ApplicationService.GenerateConfigSchema:input_type -> google.protobuf.Empty + 10, // 32: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent + 11, // 33: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData + 3, // 34: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest + 4, // 35: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest + 5, // 36: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest + 6, // 37: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo + 30, // 38: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty + 30, // 39: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty + 30, // 40: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty + 12, // 41: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo + 30, // 42: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty + 13, // 43: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList + 17, // 44: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent + 30, // 45: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty + 19, // 46: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive + 30, // 47: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty + 30, // 48: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty + 20, // 49: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList + 24, // 50: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent + 30, // 51: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty + 19, // 52: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive + 30, // 53: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty + 30, // 54: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty + 29, // 55: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse + 26, // 56: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings + 30, // 57: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty + 30, // 58: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty + 9, // 59: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent + 9, // 60: desktop.ApplicationService.GenerateConfigSchema:output_type -> desktop.ConfigContent + 11, // 61: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData + 10, // 62: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent + 30, // 63: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty + 31, // 64: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress + 32, // 65: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress + 37, // [37:66] is the sub-list for method output_type + 8, // [8:37] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name diff --git a/experimental/boxdd/desktop_service.proto b/experimental/boxdd/desktop_service.proto index 22d212c0a..4365c625c 100644 --- a/experimental/boxdd/desktop_service.proto +++ b/experimental/boxdd/desktop_service.proto @@ -33,6 +33,7 @@ service DesktopService { service ApplicationService { rpc CheckConfig(ConfigContent) returns (google.protobuf.Empty) {} rpc FormatConfig(ConfigContent) returns (ConfigContent) {} + rpc GenerateConfigSchema(google.protobuf.Empty) returns (ConfigContent) {} rpc EncodeProfile(ProfileContent) returns (ProfileData) {} rpc DecodeProfile(ProfileData) returns (ProfileContent) {} rpc ArchiveReport(ArchiveReportRequest) returns (google.protobuf.Empty) {} diff --git a/experimental/boxdd/desktop_service_grpc.pb.go b/experimental/boxdd/desktop_service_grpc.pb.go index 034f5f109..5ad6af211 100644 --- a/experimental/boxdd/desktop_service_grpc.pb.go +++ b/experimental/boxdd/desktop_service_grpc.pb.go @@ -4,7 +4,6 @@ import ( context "context" daemon "github.com/sagernet/sing-box/daemon" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -901,6 +900,7 @@ var DesktopService_ServiceDesc = grpc.ServiceDesc{ const ( ApplicationService_CheckConfig_FullMethodName = "/desktop.ApplicationService/CheckConfig" ApplicationService_FormatConfig_FullMethodName = "/desktop.ApplicationService/FormatConfig" + ApplicationService_GenerateConfigSchema_FullMethodName = "/desktop.ApplicationService/GenerateConfigSchema" ApplicationService_EncodeProfile_FullMethodName = "/desktop.ApplicationService/EncodeProfile" ApplicationService_DecodeProfile_FullMethodName = "/desktop.ApplicationService/DecodeProfile" ApplicationService_ArchiveReport_FullMethodName = "/desktop.ApplicationService/ArchiveReport" @@ -914,6 +914,7 @@ const ( type ApplicationServiceClient interface { CheckConfig(ctx context.Context, in *ConfigContent, opts ...grpc.CallOption) (*emptypb.Empty, error) FormatConfig(ctx context.Context, in *ConfigContent, opts ...grpc.CallOption) (*ConfigContent, error) + GenerateConfigSchema(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfigContent, error) EncodeProfile(ctx context.Context, in *ProfileContent, opts ...grpc.CallOption) (*ProfileData, error) DecodeProfile(ctx context.Context, in *ProfileData, opts ...grpc.CallOption) (*ProfileContent, error) ArchiveReport(ctx context.Context, in *ArchiveReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -949,6 +950,16 @@ func (c *applicationServiceClient) FormatConfig(ctx context.Context, in *ConfigC return out, nil } +func (c *applicationServiceClient) GenerateConfigSchema(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfigContent, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConfigContent) + err := c.cc.Invoke(ctx, ApplicationService_GenerateConfigSchema_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *applicationServiceClient) EncodeProfile(ctx context.Context, in *ProfileContent, opts ...grpc.CallOption) (*ProfileData, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ProfileData) @@ -1023,6 +1034,7 @@ type ApplicationService_StartStandaloneSTUNTestClient = grpc.ServerStreamingClie type ApplicationServiceServer interface { CheckConfig(context.Context, *ConfigContent) (*emptypb.Empty, error) FormatConfig(context.Context, *ConfigContent) (*ConfigContent, error) + GenerateConfigSchema(context.Context, *emptypb.Empty) (*ConfigContent, error) EncodeProfile(context.Context, *ProfileContent) (*ProfileData, error) DecodeProfile(context.Context, *ProfileData) (*ProfileContent, error) ArchiveReport(context.Context, *ArchiveReportRequest) (*emptypb.Empty, error) @@ -1046,6 +1058,10 @@ func (UnimplementedApplicationServiceServer) FormatConfig(context.Context, *Conf return nil, status.Error(codes.Unimplemented, "method FormatConfig not implemented") } +func (UnimplementedApplicationServiceServer) GenerateConfigSchema(context.Context, *emptypb.Empty) (*ConfigContent, error) { + return nil, status.Error(codes.Unimplemented, "method GenerateConfigSchema not implemented") +} + func (UnimplementedApplicationServiceServer) EncodeProfile(context.Context, *ProfileContent) (*ProfileData, error) { return nil, status.Error(codes.Unimplemented, "method EncodeProfile not implemented") } @@ -1122,6 +1138,24 @@ func _ApplicationService_FormatConfig_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _ApplicationService_GenerateConfigSchema_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationServiceServer).GenerateConfigSchema(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ApplicationService_GenerateConfigSchema_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationServiceServer).GenerateConfigSchema(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _ApplicationService_EncodeProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ProfileContent) if err := dec(in); err != nil { @@ -1213,6 +1247,10 @@ var ApplicationService_ServiceDesc = grpc.ServiceDesc{ MethodName: "FormatConfig", Handler: _ApplicationService_FormatConfig_Handler, }, + { + MethodName: "GenerateConfigSchema", + Handler: _ApplicationService_GenerateConfigSchema_Handler, + }, { MethodName: "EncodeProfile", Handler: _ApplicationService_EncodeProfile_Handler, diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 8d5a7d6b1..41793b929 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -5,6 +5,7 @@ import ( "context" "net/netip" "os" + "reflect" box "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" @@ -13,6 +14,7 @@ import ( "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/schema" tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" @@ -241,6 +243,14 @@ func (s *interfaceMonitorStub) MyInterfaces() []string { return nil } +func GenerateConfigSchema() (*StringBox, error) { + content, err := schema.Generate(baseContext(nil), reflect.TypeFor[option.Options]()) + if err != nil { + return nil, err + } + return wrapString(string(content)), nil +} + func FormatConfig(configContent string) (*StringBox, error) { options, err := parseConfig(baseContext(nil), configContent) if err != nil { diff --git a/mkdocs.yml b/mkdocs.yml index f8cb94669..f732ea6b7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,6 +80,7 @@ nav: - TunnelVision: manual/misc/tunnelvision.md - Configuration: - configuration/index.md + - JSON Schema: configuration/schema.md - Log: - configuration/log/index.md - DNS: diff --git a/option/acme.go b/option/acme.go index 31efffce1..df9daad7c 100644 --- a/option/acme.go +++ b/option/acme.go @@ -1,9 +1,11 @@ package option import ( + "reflect" "strings" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -23,21 +25,25 @@ type ACMECertificateProviderOptions struct { AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"` ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"` DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"` - KeyType ACMEKeyType `json:"key_type,omitempty"` + KeyType ACMEKeyType `json:"key_type,omitempty" enum:"ed25519,p256,p384,rsa2048,rsa4096"` Profile string `json:"profile,omitempty"` HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` } type _ACMEProviderDNS01ChallengeOptions struct { + AbstractACMEProviderDNS01ChallengeOptions + Provider string `json:"provider,omitempty" enum:"alidns,cloudflare,acmedns"` + AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` + CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` + ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` +} + +type AbstractACMEProviderDNS01ChallengeOptions struct { TTL badoption.Duration `json:"ttl,omitempty"` PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"` PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"` Resolvers badoption.Listable[string] `json:"resolvers,omitempty"` OverrideDomain string `json:"override_domain,omitempty"` - Provider string `json:"provider,omitempty"` - AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` - CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` - ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` } type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions @@ -80,6 +86,14 @@ func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { return badjson.UnmarshallExcluded(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o), v) } +func (o ACMEProviderDNS01ChallengeOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("ACMEProviderDNS01Challenge", func() (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "provider", true, acmeDNS01Variants(), func(variant *schema.Node) error { + return builder.FlattenStruct(variant, reflect.TypeFor[AbstractACMEProviderDNS01ChallengeOptions]()) + }) + }) +} + type ACMEKeyType string const ( @@ -105,3 +119,7 @@ func (t *ACMEKeyType) UnmarshalJSON(data []byte) error { } return nil } + +func (t ACMEKeyType) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("", "ed25519", "p256", "p384", "rsa2048", "rsa4096"), nil +} diff --git a/option/api.go b/option/api.go index 6a8766678..bb8564a1d 100644 --- a/option/api.go +++ b/option/api.go @@ -1,6 +1,9 @@ package option import ( + "reflect" + + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badoption" ) @@ -8,7 +11,7 @@ import ( type APIServiceOptions struct { ListenOptions Secret string `json:"secret,omitempty"` - AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty"` + AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty" examples:"http://sing-box-dashboard.sagernet.org/,https://sing-box-dashboard.sagernet.org/"` AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` Dashboard *APIDashboardOptions `json:"dashboard,omitempty"` InboundTLSOptionsContainer @@ -48,3 +51,12 @@ func (o *APIDashboardOptions) UnmarshalJSON(bytes []byte) error { } return json.UnmarshalDisallowUnknownFields(bytes, (*_APIDashboardOptions)(o)) } + +func (o APIDashboardOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + objectForm := schema.StrictObject() + err := builder.FlattenStruct(objectForm, reflect.TypeFor[APIDashboardOptions]()) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.BooleanNode(), schema.StringNode(), objectForm), nil +} diff --git a/option/ccm.go b/option/ccm.go index c916aaf22..d36ab4232 100644 --- a/option/ccm.go +++ b/option/ccm.go @@ -10,7 +10,7 @@ type CCMServiceOptions struct { CredentialPath string `json:"credential_path,omitempty"` Users []CCMUser `json:"users,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` - Detour string `json:"detour,omitempty"` + Detour string `json:"detour,omitempty" reference:"outbound"` UsagesPath string `json:"usages_path,omitempty"` } diff --git a/option/certificate.go b/option/certificate.go index ab524b992..38318698e 100644 --- a/option/certificate.go +++ b/option/certificate.go @@ -1,13 +1,16 @@ package option import ( + "reflect" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badoption" ) type _CertificateOptions struct { - Store string `json:"store,omitempty"` + Store string `json:"store,omitempty" enum:"system,mozilla,chrome,none"` Certificate badoption.Listable[string] `json:"certificate,omitempty"` CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"` CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"` @@ -34,3 +37,12 @@ func (o *CertificateOptions) UnmarshalJSON(data []byte) error { } return nil } + +func (o CertificateOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + node := schema.StrictObject() + err := builder.FlattenStruct(node, reflect.TypeFor[CertificateOptions]()) + if err != nil { + return nil, err + } + return node, nil +} diff --git a/option/certificate_provider.go b/option/certificate_provider.go index a24abdc57..277596c98 100644 --- a/option/certificate_provider.go +++ b/option/certificate_provider.go @@ -3,6 +3,7 @@ package option import ( "context" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -10,6 +11,7 @@ import ( ) type CertificateProviderOptionsRegistry interface { + OptionTypes() []string CreateOptions(providerType string) (any, bool) } @@ -46,6 +48,16 @@ func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content return nil } +func (h CertificateProvider) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("CertificateProvider", func() (*schema.Node, error) { + registry := service.FromContext[CertificateProviderOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing certificate provider options registry in context") + } + return registryUnion(builder, registry, nil, true) + }) +} + type CertificateProviderOptions struct { Tag string `json:"-"` Type string `json:"-"` @@ -98,3 +110,17 @@ func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, c func (o *CertificateProviderOptions) IsShared() bool { return o.Tag != "" } + +func (o CertificateProviderOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("CertificateProviderReference", func() (*schema.Node, error) { + registry := service.FromContext[CertificateProviderOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing certificate provider options registry in context") + } + union, err := registryUnion(builder, registry, nil, false) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.TagReferenceNode("certificate_provider"), union), nil + }) +} diff --git a/option/cloudflared.go b/option/cloudflared.go index e94a20fef..0eba05ac5 100644 --- a/option/cloudflared.go +++ b/option/cloudflared.go @@ -5,10 +5,10 @@ import "github.com/sagernet/sing/common/json/badoption" type CloudflaredInboundOptions struct { Token string `json:"token,omitempty"` HighAvailabilityConnections int `json:"ha_connections,omitempty"` - Protocol string `json:"protocol,omitempty"` + Protocol string `json:"protocol,omitempty" enum:"auto,quic,http2,h2mux"` PostQuantum bool `json:"post_quantum,omitempty"` - EdgeIPVersion int `json:"edge_ip_version,omitempty"` - DatagramVersion string `json:"datagram_version,omitempty"` + EdgeIPVersion int `json:"edge_ip_version,omitempty" enum:"0,4,6"` + DatagramVersion string `json:"datagram_version,omitempty" enum:"v2,v3"` GracePeriod badoption.Duration `json:"grace_period,omitempty"` Region string `json:"region,omitempty"` ControlDialer DialerOptions `json:"control_dialer,omitempty"` diff --git a/option/direct.go b/option/direct.go index a03f98d41..a11c45e98 100644 --- a/option/direct.go +++ b/option/direct.go @@ -17,11 +17,11 @@ type DirectInboundOptions struct { type _DirectOutboundOptions struct { DialerOptions // Deprecated: Use Route Action instead - OverrideAddress string `json:"override_address,omitempty"` + OverrideAddress string `json:"override_address,omitempty" schema:"omit"` // Deprecated: Use Route Action instead - OverridePort uint16 `json:"override_port,omitempty"` + OverridePort uint16 `json:"override_port,omitempty" schema:"omit"` // Deprecated: removed - ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` + ProxyProtocol uint8 `json:"proxy_protocol,omitempty" schema:"omit"` } type DirectOutboundOptions _DirectOutboundOptions diff --git a/option/dns.go b/option/dns.go index 6d3970d88..519058548 100644 --- a/option/dns.go +++ b/option/dns.go @@ -3,8 +3,10 @@ package option import ( "context" "net/netip" + "reflect" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -16,7 +18,7 @@ import ( type RawDNSOptions struct { Servers []DNSServerOptions `json:"servers,omitempty"` Rules []DNSRule `json:"rules,omitempty"` - Final string `json:"final,omitempty"` + Final string `json:"final,omitempty" reference:"dns_server"` ReverseMapping bool `json:"reverse_mapping,omitempty"` DNSClientOptions } @@ -46,12 +48,23 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) } +func (o DNSOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DNS", func() (*schema.Node, error) { + node := schema.StrictObject() + err := builder.FlattenStruct(node, reflect.TypeFor[RawDNSOptions]()) + if err != nil { + return nil, err + } + return node, nil + }) +} + type DNSClientOptions struct { Strategy DomainStrategy `json:"strategy,omitempty"` Timeout badoption.Duration `json:"timeout,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` - IndependentCache bool `json:"independent_cache,omitempty"` + IndependentCache bool `json:"independent_cache,omitempty" schema:"omit"` CacheCapacity uint32 `json:"cache_capacity,omitempty"` Optimistic *OptimisticDNSOptions `json:"optimistic,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` @@ -79,7 +92,17 @@ func (o *OptimisticDNSOptions) UnmarshalJSON(bytes []byte) error { return json.UnmarshalDisallowUnknownFields(bytes, (*_OptimisticDNSOptions)(o)) } +func (o OptimisticDNSOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + objectForm := schema.StrictObject() + err := builder.FlattenStruct(objectForm, reflect.TypeFor[OptimisticDNSOptions]()) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.BooleanNode(), objectForm), nil +} + type DNSTransportOptionsRegistry interface { + OptionTypes() []string CreateOptions(transportType string) (any, bool) } type _DNSServerOptions struct { @@ -122,6 +145,16 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b return nil } +func (o DNSServerOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DNSServer", func() (*schema.Node, error) { + registry := service.FromContext[DNSTransportOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing DNS transport options registry in context") + } + return registryUnion(builder, registry, nil, true) + }) +} + type DNSServerAddressOptions struct { Server string `json:"server"` ServerPort uint16 `json:"server_port,omitempty"` @@ -176,8 +209,8 @@ type RemoteHTTPSDNSServerOptions struct { } type FakeIPDNSServerOptions struct { - Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"` - Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"` + Inet4Range *badoption.Prefix `json:"inet4_range,omitempty" examples:"198.18.0.0/15"` + Inet6Range *badoption.Prefix `json:"inet6_range,omitempty" examples:"fc00::/18"` } type DHCPDNSServerOptions struct { diff --git a/option/dns_record.go b/option/dns_record.go index f10e03d9b..fe93dabc4 100644 --- a/option/dns_record.go +++ b/option/dns_record.go @@ -1,9 +1,12 @@ package option import ( + "cmp" "encoding/base64" + "slices" "strings" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -51,6 +54,43 @@ func (r *DNSRCode) Build() int { return int(*r) } +func (r DNSRCode) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DNSRCode", func() (*schema.Node, error) { + type rCodeName struct { + name string + value int + canonical bool + } + rCodeNames := make([]rCodeName, 0, len(dns.StringToRcode)) + for name, value := range dns.StringToRcode { + canonicalName, canonical := dns.RcodeToString[value] + rCodeNames = append(rCodeNames, rCodeName{ + name: name, + value: value, + canonical: canonical && canonicalName == name, + }) + } + slices.SortFunc(rCodeNames, func(left rCodeName, right rCodeName) int { + comparison := cmp.Compare(left.value, right.value) + if comparison != 0 { + return comparison + } + if left.canonical != right.canonical { + if left.canonical { + return -1 + } + return 1 + } + return cmp.Compare(left.name, right.name) + }) + values := make([]string, 0, len(rCodeNames)) + for _, entry := range rCodeNames { + values = append(values, entry.name) + } + return schema.AnyOf(schema.IntegerNode(), schema.StringEnum(values...)), nil + }) +} + type DNSRecordOptions struct { dns.RR fromBase64 bool @@ -123,3 +163,7 @@ func (o DNSRecordOptions) Match(record dns.RR) bool { } return dns.IsDuplicate(o.RR, record) } + +func (o DNSRecordOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringNode(), nil +} diff --git a/option/dns_test.go b/option/dns_test.go index 4e7bf9a92..312ae4719 100644 --- a/option/dns_test.go +++ b/option/dns_test.go @@ -13,6 +13,10 @@ import ( type stubDNSTransportOptionsRegistry struct{} +func (stubDNSTransportOptionsRegistry) OptionTypes() []string { + return []string{C.DNSTypeUDP, C.DNSTypeFakeIP} +} + func (stubDNSTransportOptionsRegistry) CreateOptions(transportType string) (any, bool) { switch transportType { case C.DNSTypeUDP: diff --git a/option/endpoint.go b/option/endpoint.go index 45c4f8311..3645a0413 100644 --- a/option/endpoint.go +++ b/option/endpoint.go @@ -3,6 +3,7 @@ package option import ( "context" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -10,6 +11,7 @@ import ( ) type EndpointOptionsRegistry interface { + OptionTypes() []string CreateOptions(endpointType string) (any, bool) } @@ -45,3 +47,13 @@ func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) err h.Options = options return nil } + +func (h Endpoint) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("Endpoint", func() (*schema.Node, error) { + registry := service.FromContext[EndpointOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing endpoint options registry in context") + } + return registryUnion(builder, registry, nil, true) + }) +} diff --git a/option/experimental.go b/option/experimental.go index 2f00decfe..3259e6a88 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -14,7 +14,7 @@ type CacheFileOptions struct { Path string `json:"path,omitempty"` CacheID string `json:"cache_id,omitempty"` StoreFakeIP bool `json:"store_fakeip,omitempty"` - StoreRDRC bool `json:"store_rdrc,omitempty"` + StoreRDRC bool `json:"store_rdrc,omitempty" schema:"omit"` RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"` StoreDNS bool `json:"store_dns,omitempty"` } @@ -23,7 +23,7 @@ type ClashAPIOptions struct { ExternalController string `json:"external_controller,omitempty"` ExternalUI string `json:"external_ui,omitempty"` ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` - ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` + ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty" reference:"outbound"` Secret string `json:"secret,omitempty"` DefaultMode string `json:"default_mode,omitempty"` ModeList []string `json:"-"` @@ -31,15 +31,15 @@ type ClashAPIOptions struct { AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` // Deprecated: migrated to global cache file - CacheFile string `json:"cache_file,omitempty"` + CacheFile string `json:"cache_file,omitempty" schema:"omit"` // Deprecated: migrated to global cache file - CacheID string `json:"cache_id,omitempty"` + CacheID string `json:"cache_id,omitempty" schema:"omit"` // Deprecated: migrated to global cache file - StoreMode bool `json:"store_mode,omitempty"` + StoreMode bool `json:"store_mode,omitempty" schema:"omit"` // Deprecated: migrated to global cache file - StoreSelected bool `json:"store_selected,omitempty"` + StoreSelected bool `json:"store_selected,omitempty" schema:"omit"` // Deprecated: migrated to global cache file - StoreFakeIP bool `json:"store_fakeip,omitempty"` + StoreFakeIP bool `json:"store_fakeip,omitempty" schema:"omit"` } type V2RayAPIOptions struct { diff --git a/option/group.go b/option/group.go index 02b3a5ecb..bc0c07e47 100644 --- a/option/group.go +++ b/option/group.go @@ -3,13 +3,13 @@ package option import "github.com/sagernet/sing/common/json/badoption" type SelectorOutboundOptions struct { - Outbounds []string `json:"outbounds"` - Default string `json:"default,omitempty"` + Outbounds []string `json:"outbounds" reference:"outbound"` + Default string `json:"default,omitempty" reference:"outbound"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` } type URLTestOutboundOptions struct { - Outbounds []string `json:"outbounds"` + Outbounds []string `json:"outbounds" reference:"outbound"` URL string `json:"url,omitempty"` Interval badoption.Duration `json:"interval,omitempty"` Tolerance uint16 `json:"tolerance,omitempty"` diff --git a/option/http.go b/option/http.go index 1a9727044..4c6da9deb 100644 --- a/option/http.go +++ b/option/http.go @@ -3,6 +3,7 @@ package option import ( "reflect" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common/byteformats" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -26,8 +27,8 @@ type QUICOptions struct { type _HTTPClientOptions struct { Tag string `json:"tag,omitempty"` - Engine string `json:"engine,omitempty"` - Version int `json:"version,omitempty"` + Engine string `json:"engine,omitempty" enum:"go,apple"` + Version int `json:"version,omitempty" enum:"0,1,2,3"` DisableVersionFallback bool `json:"disable_version_fallback,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` HTTP2Options HTTP2Options `json:"-"` @@ -125,3 +126,33 @@ func httpClientVariant(options _HTTPClientOptions) any { return nil } } + +func describeHTTPClientObject(builder schema.Builder) (*schema.Node, error) { + node := schema.StrictObject() + err := builder.FlattenStruct(node, reflect.TypeFor[HTTPClient]()) + if err != nil { + return nil, err + } + err = builder.FlattenStruct(node, reflect.TypeFor[QUICOptions]()) + if err != nil { + return nil, err + } + return node, nil +} + +func (h HTTPClient) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("HTTPClient", func() (*schema.Node, error) { + return describeHTTPClientObject(builder) + }) +} + +func (o HTTPClientOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("HTTPClientReference", func() (*schema.Node, error) { + clientObject, err := describeHTTPClientObject(builder) + if err != nil { + return nil, err + } + clientObject.Properties.Remove("tag") + return schema.AnyOf(schema.TagReferenceNode("http_client"), clientObject), nil + }) +} diff --git a/option/hysteria.go b/option/hysteria.go index f5ab87cec..d1b9ad30a 100644 --- a/option/hysteria.go +++ b/option/hysteria.go @@ -14,13 +14,13 @@ type HysteriaInboundOptions struct { Obfs string `json:"obfs,omitempty"` Users []HysteriaUser `json:"users,omitempty"` // Deprecated: use QUIC fields instead - ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` + ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty" schema:"omit"` // Deprecated: use QUIC fields instead - ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"` + ReceiveWindowClient uint64 `json:"recv_window_client,omitempty" schema:"omit"` // Deprecated: use QUIC fields instead - MaxConnClient int `json:"max_conn_client,omitempty"` + MaxConnClient int `json:"max_conn_client,omitempty" schema:"omit"` // Deprecated: use QUIC fields instead - DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` + DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty" schema:"omit"` InboundTLSOptionsContainer QUICOptions } @@ -44,11 +44,11 @@ type HysteriaOutboundOptions struct { Auth []byte `json:"auth,omitempty"` AuthString string `json:"auth_str,omitempty"` // Deprecated: use QUIC fields instead - ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` + ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty" schema:"omit"` // Deprecated: use QUIC fields instead - ReceiveWindow uint64 `json:"recv_window,omitempty"` + ReceiveWindow uint64 `json:"recv_window,omitempty" schema:"omit"` // Deprecated: use QUIC fields instead - DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` + DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty" schema:"omit"` Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer QUICOptions diff --git a/option/hysteria2.go b/option/hysteria2.go index fdf0d2cd8..3598c555e 100644 --- a/option/hysteria2.go +++ b/option/hysteria2.go @@ -2,8 +2,10 @@ package option import ( "net/url" + "reflect" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -20,7 +22,7 @@ type Hysteria2InboundOptions struct { InboundTLSOptionsContainer QUICOptions Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"` - BBRProfile string `json:"bbr_profile,omitempty"` + BBRProfile string `json:"bbr_profile,omitempty" enum:"standard,conservative,aggressive"` BrutalDebug bool `json:"brutal_debug,omitempty"` Realm *Hysteria2InboundRealm `json:"realm,omitempty"` } @@ -30,7 +32,7 @@ type Hysteria2Realm struct { Token string `json:"token,omitempty"` RealmID string `json:"realm_id"` STUNServers badoption.Listable[string] `json:"stun_servers"` - IPVersion int `json:"ip_version,omitempty"` + IPVersion int `json:"ip_version,omitempty" enum:"0,4,6"` PortMapping *Hysteria2RealmPortMapping `json:"port_mapping,omitempty"` HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` } @@ -52,7 +54,7 @@ type Hysteria2ObfsGecko struct { } type _Hysteria2Obfs struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"salamander,gecko"` Password string `json:"password,omitempty"` GeckoOptions Hysteria2ObfsGecko `json:"-"` } @@ -93,13 +95,23 @@ func (o *Hysteria2Obfs) UnmarshalJSON(bytes []byte) error { return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Obfs)(o), v) } +func (o Hysteria2Obfs) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "type", true, []schema.UnionVariant{ + {Value: C.Hysteria2ObfsTypeSalamander}, + {Value: C.Hysteria2ObfsTypeGecko, StructType: reflect.TypeFor[Hysteria2ObfsGecko]()}, + }, func(variant *schema.Node) error { + variant.Properties.Put("password", schema.StringNode()) + return nil + }) +} + type Hysteria2User struct { Name string `json:"name,omitempty"` Password string `json:"password,omitempty"` } type _Hysteria2Masquerade struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"file,proxy,string"` FileOptions Hysteria2MasqueradeFile `json:"-"` ProxyOptions Hysteria2MasqueradeProxy `json:"-"` StringOptions Hysteria2MasqueradeString `json:"-"` @@ -160,6 +172,18 @@ func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error { return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v) } +func (m Hysteria2Masquerade) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + union, err := schema.DiscriminatedUnion(builder, "type", true, []schema.UnionVariant{ + {Value: C.Hysterai2MasqueradeTypeFile, StructType: reflect.TypeFor[Hysteria2MasqueradeFile]()}, + {Value: C.Hysterai2MasqueradeTypeProxy, StructType: reflect.TypeFor[Hysteria2MasqueradeProxy]()}, + {Value: C.Hysterai2MasqueradeTypeString, StructType: reflect.TypeFor[Hysteria2MasqueradeString]()}, + }, nil) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.StringNode(), union), nil +} + type Hysteria2MasqueradeFile struct { Directory string `json:"directory"` } @@ -188,7 +212,7 @@ type Hysteria2OutboundOptions struct { Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer QUICOptions - BBRProfile string `json:"bbr_profile,omitempty"` + BBRProfile string `json:"bbr_profile,omitempty" enum:"standard,conservative,aggressive"` BrutalDebug bool `json:"brutal_debug,omitempty"` Realm *Hysteria2Realm `json:"realm,omitempty"` } diff --git a/option/inbound.go b/option/inbound.go index 0fdf3e644..337a33c91 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -4,6 +4,8 @@ import ( "context" "time" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -12,6 +14,7 @@ import ( ) type InboundOptionsRegistry interface { + OptionTypes() []string CreateOptions(outboundType string) (any, bool) } @@ -54,13 +57,23 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro return nil } +func (h Inbound) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("Inbound", func() (*schema.Node, error) { + registry := service.FromContext[InboundOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing inbound options registry in context") + } + return registryUnion(builder, registry, []string{C.TypeShadowsocksR}, true) + }) +} + // Deprecated: Use rule action instead type InboundOptions struct { - SniffEnabled bool `json:"sniff,omitempty"` - SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` - SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + SniffEnabled bool `json:"sniff,omitempty" schema:"omit"` + SniffOverrideDestination bool `json:"sniff_override_destination,omitempty" schema:"omit"` + SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty" schema:"omit"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty" schema:"omit"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty" schema:"omit"` } type ListenOptions struct { @@ -69,7 +82,7 @@ type ListenOptions struct { BindInterface string `json:"bind_interface,omitempty"` RoutingMark FwMark `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` - NetNs string `json:"netns,omitempty"` + NetNs string `json:"netns,omitempty" reference:"network_namespace"` DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"` TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` @@ -78,13 +91,15 @@ type ListenOptions struct { UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - Detour string `json:"detour,omitempty"` + Detour string `json:"detour,omitempty" reference:"inbound"` // Deprecated: removed - ProxyProtocol bool `json:"proxy_protocol,omitempty"` + ProxyProtocol bool `json:"proxy_protocol,omitempty" schema:"omit"` // Deprecated: removed - ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` - InboundOptions + ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty" schema:"omit"` + // Legacy inbound fields are rejected since sing-box 1.13.0. + //nolint:staticcheck + InboundOptions `schema:"omit"` } type UDPNATBehavior uint8 @@ -129,6 +144,10 @@ func (b *UDPNATBehavior) UnmarshalJSON(data []byte) error { return nil } +func (b UDPNATBehavior) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("", "endpoint_independent", "address_dependent", "address_and_port_dependent"), nil +} + type UDPTimeoutCompat badoption.Duration func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) { @@ -145,6 +164,12 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, (*badoption.Duration)(c)) } +func (c UDPTimeoutCompat) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("UDPTimeout", func() (*schema.Node, error) { + return schema.AnyOf(schema.UnsignedNode(32), schema.DurationNode()), nil + }) +} + type ListenOptionsWrapper interface { TakeListenOptions() ListenOptions ReplaceListenOptions(options ListenOptions) diff --git a/option/multiplex.go b/option/multiplex.go index 309d8bdc3..0f5b89cba 100644 --- a/option/multiplex.go +++ b/option/multiplex.go @@ -8,7 +8,7 @@ type InboundMultiplexOptions struct { type OutboundMultiplexOptions struct { Enabled bool `json:"enabled,omitempty"` - Protocol string `json:"protocol,omitempty"` + Protocol string `json:"protocol,omitempty" enum:"h2mux,smux,yamux"` MaxConnections int `json:"max_connections,omitempty"` MinStreams int `json:"min_streams,omitempty"` MaxStreams int `json:"max_streams,omitempty"` diff --git a/option/naive.go b/option/naive.go index da3a88db9..a76f39d1c 100644 --- a/option/naive.go +++ b/option/naive.go @@ -20,7 +20,7 @@ type NaiveInboundOptions struct { ListenOptions Users []auth.User `json:"users,omitempty"` Network NetworkList `json:"network,omitempty"` - QUICCongestionControl string `json:"quic_congestion_control,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty" enum:"bbr,bbr_standard,bbr2,bbr2_variant,cubic,reno"` InboundTLSOptionsContainer } @@ -34,7 +34,7 @@ type NaiveOutboundOptions struct { ReceiveWindow *byteformats.MemoryBytes `json:"stream_receive_window,omitempty"` UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` QUIC bool `json:"quic,omitempty"` - QUICCongestionControl string `json:"quic_congestion_control,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty" enum:"bbr,bbr2,cubic,reno"` QUICSessionReceiveWindow *byteformats.MemoryBytes `json:"quic_session_receive_window,omitempty"` OutboundTLSOptionsContainer } diff --git a/option/netns.go b/option/netns.go index 9516b660b..b206b0183 100644 --- a/option/netns.go +++ b/option/netns.go @@ -1,7 +1,10 @@ package option import ( + "reflect" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -48,6 +51,19 @@ func (o *NetworkNamespace) UnmarshalJSON(content []byte) error { return badjson.UnmarshallExcluded(content, (*_NetworkNamespace)(o), v) } +func (o NetworkNamespace) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("NetworkNamespace", func() (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "type", false, []schema.UnionVariant{ + {Value: C.NetNsTypeDefault, StructType: reflect.TypeFor[DefaultNetworkNamespaceOptions](), TypeOptional: true}, + {Value: C.NetNsTypeUnshare, StructType: reflect.TypeFor[UnshareNetworkNamespaceOptions]()}, + }, func(variant *schema.Node) error { + variant.Properties.Put("tag", schema.StringNode()) + variant.Required = append(variant.Required, "tag") + return nil + }) + }) +} + type DefaultNetworkNamespaceOptions struct { Path string `json:"path"` } diff --git a/option/ocm.go b/option/ocm.go index c13a1c1f5..091aedde1 100644 --- a/option/ocm.go +++ b/option/ocm.go @@ -10,7 +10,7 @@ type OCMServiceOptions struct { CredentialPath string `json:"credential_path,omitempty"` Users []OCMUser `json:"users,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` - Detour string `json:"detour,omitempty"` + Detour string `json:"detour,omitempty" reference:"outbound"` UsagesPath string `json:"usages_path,omitempty"` } diff --git a/option/openconnect.go b/option/openconnect.go index d3ff01ab4..bc597b5ef 100644 --- a/option/openconnect.go +++ b/option/openconnect.go @@ -11,7 +11,7 @@ type OpenConnectEndpointOptions struct { UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"` UDPNATMax uint32 `json:"udp_nat_max,omitempty"` Server string `json:"server"` - Flavor string `json:"flavor,omitempty"` + Flavor string `json:"flavor,omitempty" enum:"anyconnect,gp,fortinet,f5,pulse,nc"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` AuthGroup string `json:"auth_group,omitempty"` @@ -29,7 +29,7 @@ type OpenConnectEndpointOptions struct { NoUDP bool `json:"no_udp,omitempty"` DTLSLocalPort uint16 `json:"dtls_local_port,omitempty"` CompressionDisabled bool `json:"compression_disabled,omitempty"` - CompressionMode string `json:"compression_mode,omitempty"` + CompressionMode string `json:"compression_mode,omitempty" enum:"stateless,all"` IPv6Disabled bool `json:"ipv6_disabled,omitempty"` HTTPKeepAliveDisabled bool `json:"http_keepalive_disabled,omitempty"` XMLPostDisabled bool `json:"xml_post_disabled,omitempty"` @@ -49,7 +49,7 @@ type OpenConnectEndpointOptions struct { } type OpenConnectTokenOptions struct { - Mode string `json:"mode,omitempty"` + Mode string `json:"mode,omitempty" enum:"totp,hotp,stoken,oidc"` Secret string `json:"secret,omitempty"` SecretPath string `json:"secret_path,omitempty"` PIN string `json:"pin,omitempty"` diff --git a/option/openvpn.go b/option/openvpn.go index 642287283..7a12a4f04 100644 --- a/option/openvpn.go +++ b/option/openvpn.go @@ -20,22 +20,22 @@ type OpenVPNClientEndpointOptions struct { DialerOptions ServerOptions OpenVPNEndpointOptions - Mode string `json:"mode,omitempty"` - Network string `json:"network,omitempty"` + Mode string `json:"mode,omitempty" enum:"tls,static_key"` + Network string `json:"network,omitempty" enum:"udp,udp4,udp6,tcp,tcp4,tcp6"` Servers []OpenVPNRemoteOptions `json:"servers,omitempty"` RemoteRandom bool `json:"remote_random,omitempty"` Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` PeerAddress badoption.Addr `json:"peer_address,omitempty"` PeerAddressIPv6 badoption.Addr `json:"peer_address_ipv6,omitempty"` - Topology string `json:"topology,omitempty"` + Topology string `json:"topology,omitempty" enum:"net30,p2p,subnet"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` - AuthRetry string `json:"auth_retry,omitempty"` + AuthRetry string `json:"auth_retry,omitempty" enum:"none,nointeract,interact"` StaticChallenge string `json:"static_challenge,omitempty"` StaticChallengeEcho bool `json:"static_challenge_echo,omitempty"` StaticKey badoption.Listable[string] `json:"static_key,omitempty"` StaticKeyPath string `json:"static_key_path,omitempty"` - KeyDirection string `json:"key_direction,omitempty"` + KeyDirection string `json:"key_direction,omitempty" enum:"server,client"` TLS *OpenVPNOutboundTLSOptions `json:"tls,omitempty"` Cipher string `json:"cipher,omitempty"` DataCiphers badoption.Listable[string] `json:"data_ciphers,omitempty"` @@ -43,13 +43,13 @@ type OpenVPNClientEndpointOptions struct { Auth string `json:"auth,omitempty"` MSSFix uint32 `json:"mss_fix,omitempty"` MSSFixDisabled bool `json:"mss_fix_disabled,omitempty"` - MSSFixMode string `json:"mss_fix_mode,omitempty"` + MSSFixMode string `json:"mss_fix_mode,omitempty" enum:"mtu,fixed"` Fragment uint32 `json:"fragment,omitempty"` ReplayWindow uint32 `json:"replay_window,omitempty"` ReplayWindowTime badoption.Duration `json:"replay_window_time,omitempty"` - Compression string `json:"compression,omitempty"` - CompressionLZO string `json:"compression_lzo,omitempty"` - AllowCompression string `json:"allow_compression,omitempty"` + Compression string `json:"compression,omitempty" enum:"none,no,lz4,lz4-v2,stub,stub-v2,disabled,off"` + CompressionLZO string `json:"compression_lzo,omitempty" enum:"none,no,yes,adaptive,asym,disabled,off"` + AllowCompression string `json:"allow_compression,omitempty" enum:"no,asym,yes"` RouteNoPull bool `json:"route_no_pull,omitempty"` PullFilters []OpenVPNPullFilterOptions `json:"pull_filters,omitempty"` Routes badoption.Listable[netip.Prefix] `json:"routes,omitempty"` @@ -75,20 +75,20 @@ type OpenVPNClientEndpointOptions struct { type OpenVPNServerEndpointOptions struct { ListenOptions OpenVPNEndpointOptions - Mode string `json:"mode,omitempty"` - Network string `json:"network,omitempty"` + Mode string `json:"mode,omitempty" enum:"tls,static_key"` + Network string `json:"network,omitempty" enum:"tcp,udp"` Remote string `json:"remote,omitempty"` RemotePort uint16 `json:"remote_port,omitempty"` MaxClients int `json:"max_clients,omitempty"` Address badoption.Listable[netip.Prefix] `json:"address"` PeerAddress badoption.Addr `json:"peer_address,omitempty"` PeerAddressIPv6 badoption.Addr `json:"peer_address_ipv6,omitempty"` - Topology string `json:"topology,omitempty"` + Topology string `json:"topology,omitempty" enum:"net30,p2p,subnet"` DuplicateCN bool `json:"duplicate_cn,omitempty"` Users []auth.User `json:"users,omitempty"` StaticKey badoption.Listable[string] `json:"static_key,omitempty"` StaticKeyPath string `json:"static_key_path,omitempty"` - KeyDirection string `json:"key_direction,omitempty"` + KeyDirection string `json:"key_direction,omitempty" enum:"server,client"` TLS *OpenVPNInboundTLSOptions `json:"tls,omitempty"` Cipher string `json:"cipher,omitempty"` DataCiphers badoption.Listable[string] `json:"data_ciphers,omitempty"` @@ -96,7 +96,7 @@ type OpenVPNServerEndpointOptions struct { Auth string `json:"auth,omitempty"` MSSFix uint32 `json:"mss_fix,omitempty"` MSSFixDisabled bool `json:"mss_fix_disabled,omitempty"` - MSSFixMode string `json:"mss_fix_mode,omitempty"` + MSSFixMode string `json:"mss_fix_mode,omitempty" enum:"mtu,fixed"` ReplayWindow uint32 `json:"replay_window,omitempty"` ReplayWindowTime badoption.Duration `json:"replay_window_time,omitempty"` Push *OpenVPNPushOptions `json:"push,omitempty"` @@ -111,17 +111,17 @@ type OpenVPNServerEndpointOptions struct { type OpenVPNRemoteOptions struct { ServerOptions - Network string `json:"network,omitempty"` + Network string `json:"network,omitempty" enum:"udp,udp4,udp6,tcp,tcp4,tcp6"` } type OpenVPNPullFilterOptions struct { - Action string `json:"action"` + Action string `json:"action" enum:"accept,ignore,reject"` Text string `json:"text"` } type OpenVPNOutboundTLSOptions struct { ServerName string `json:"server_name,omitempty"` - ServerNameType string `json:"server_name_type,omitempty"` + ServerNameType string `json:"server_name_type,omitempty" enum:"subject,name,name-prefix"` Certificate badoption.Listable[string] `json:"certificate,omitempty"` CertificatePath string `json:"certificate_path,omitempty"` ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` @@ -132,11 +132,11 @@ type OpenVPNOutboundTLSOptions struct { CRLPath string `json:"crl_path,omitempty"` RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"` RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"` - RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty"` - CertificateProfile string `json:"certificate_profile,omitempty"` - NSCertificateType string `json:"ns_certificate_type,omitempty"` - VersionMin string `json:"version_min,omitempty"` - VersionMax string `json:"version_max,omitempty"` + RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty" enum:"server,client,none"` + CertificateProfile string `json:"certificate_profile,omitempty" enum:"legacy,preferred,insecure,suiteb"` + NSCertificateType string `json:"ns_certificate_type,omitempty" enum:"server,client"` + VersionMin string `json:"version_min,omitempty" enum:"1.0,1.1,1.2,1.3"` + VersionMax string `json:"version_max,omitempty" enum:"1.0,1.1,1.2,1.3"` Cipher string `json:"cipher,omitempty"` Groups string `json:"groups,omitempty"` ControlWrap *OpenVPNControlWrapOptions `json:"control_wrap,omitempty"` @@ -149,35 +149,35 @@ type OpenVPNInboundTLSOptions struct { KeyPath string `json:"key_path,omitempty"` ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` ClientCertificatePath string `json:"client_certificate_path,omitempty"` - VerifyClientCertificate string `json:"verify_client_certificate,omitempty"` + VerifyClientCertificate string `json:"verify_client_certificate,omitempty" enum:"require,optional,none"` ClientName string `json:"client_name,omitempty"` - ClientNameType string `json:"client_name_type,omitempty"` + ClientNameType string `json:"client_name_type,omitempty" enum:"subject,name,name-prefix"` PeerFingerprint badoption.Listable[string] `json:"peer_fingerprint,omitempty"` CRLPath string `json:"crl_path,omitempty"` RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"` RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"` - RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty"` - CertificateProfile string `json:"certificate_profile,omitempty"` - NSCertificateType string `json:"ns_certificate_type,omitempty"` - VersionMin string `json:"version_min,omitempty"` - VersionMax string `json:"version_max,omitempty"` + RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty" enum:"server,client,none"` + CertificateProfile string `json:"certificate_profile,omitempty" enum:"legacy,preferred,insecure,suiteb"` + NSCertificateType string `json:"ns_certificate_type,omitempty" enum:"server,client"` + VersionMin string `json:"version_min,omitempty" enum:"1.0,1.1,1.2,1.3"` + VersionMax string `json:"version_max,omitempty" enum:"1.0,1.1,1.2,1.3"` Cipher string `json:"cipher,omitempty"` Groups string `json:"groups,omitempty"` ControlWrap *OpenVPNInboundControlWrapOptions `json:"control_wrap,omitempty"` } type OpenVPNControlWrapOptions struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"tls_auth,tls_crypt,tls_crypt_v2"` Key badoption.Listable[string] `json:"key,omitempty"` KeyPath string `json:"key_path,omitempty"` - Direction string `json:"direction,omitempty"` + Direction string `json:"direction,omitempty" enum:"server,client"` } type OpenVPNInboundControlWrapOptions struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"tls_auth,tls_crypt,tls_crypt_v2"` Key badoption.Listable[string] `json:"key,omitempty"` KeyPath string `json:"key_path,omitempty"` - Direction string `json:"direction,omitempty"` + Direction string `json:"direction,omitempty" enum:"server,client"` ForceCookie bool `json:"force_cookie,omitempty"` } @@ -198,8 +198,8 @@ type OpenVPNPushDNSServerOptions struct { Priority int `json:"priority"` Addresses badoption.Listable[string] `json:"addresses"` ResolveDomains badoption.Listable[string] `json:"resolve_domains,omitempty"` - DNSSEC string `json:"dnssec,omitempty"` - Transport string `json:"transport,omitempty"` + DNSSEC string `json:"dnssec,omitempty" enum:"yes,optional,no"` + Transport string `json:"transport,omitempty" enum:"plain,dot,doh"` SNI string `json:"sni,omitempty"` } diff --git a/option/options.go b/option/options.go index e28ccef85..b70ec5aaf 100644 --- a/option/options.go +++ b/option/options.go @@ -3,7 +3,9 @@ package option import ( "bytes" "context" + "reflect" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -12,7 +14,7 @@ import ( type _Options struct { RawMessage json.RawMessage `json:"-"` CommentsSet *json.CommentSet `json:"-"` - Schema string `json:"$schema,omitempty"` + Schema string `json:"$schema,omitempty" examples:"https://sing-box.sagernet.org/schema.json"` Log *LogOptions `json:"log,omitempty"` DNS *DNSOptions `json:"dns,omitempty"` NTP *NTPOptions `json:"ntp,omitempty"` @@ -45,6 +47,17 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro return checkOptions(o) } +func (o Options) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + node := schema.StrictObject() + node.SchemaURI = "https://json-schema.org/draft/2020-12/schema" + node.ID = "https://sing-box.sagernet.org/schema.json" + err := builder.FlattenStruct(node, reflect.TypeFor[Options]()) + if err != nil { + return nil, err + } + return node, nil +} + func (o Options) Comments() *json.CommentSet { return o.CommentsSet } @@ -55,7 +68,7 @@ func (o *Options) SetComments(comments *json.CommentSet) { type LogOptions struct { Disabled bool `json:"disabled,omitempty"` - Level string `json:"level,omitempty"` + Level string `json:"level,omitempty" enum:"trace,debug,info,warn,warning,error,fatal,panic"` Output string `json:"output,omitempty"` Timestamp bool `json:"timestamp,omitempty"` DisableColor bool `json:"-"` diff --git a/option/origin_ca.go b/option/origin_ca.go index 5a9f956af..1a9498220 100644 --- a/option/origin_ca.go +++ b/option/origin_ca.go @@ -3,6 +3,7 @@ package option import ( "strings" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badoption" @@ -13,8 +14,8 @@ type CloudflareOriginCACertificateProviderOptions struct { DataDirectory string `json:"data_directory,omitempty"` APIToken string `json:"api_token,omitempty"` OriginCAKey string `json:"origin_ca_key,omitempty"` - RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"` - RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"` + RequestType CloudflareOriginCARequestType `json:"request_type,omitempty" enum:"origin-rsa,origin-ecc"` + RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty" enum:"0,7,30,90,365,730,1095,5475"` HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` } @@ -41,6 +42,10 @@ func (t *CloudflareOriginCARequestType) UnmarshalJSON(data []byte) error { return nil } +func (t CloudflareOriginCARequestType) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("", "origin-rsa", "origin-ecc"), nil +} + type CloudflareOriginCARequestValidity uint16 const ( @@ -74,3 +79,7 @@ func (v *CloudflareOriginCARequestValidity) UnmarshalJSON(data []byte) error { } return nil } + +func (v CloudflareOriginCARequestValidity) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return &schema.Node{Type: "integer", Enum: []any{0, 7, 30, 90, 365, 730, 1095, 5475}}, nil +} diff --git a/option/outbound.go b/option/outbound.go index f357bd449..49ea9f625 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -2,8 +2,10 @@ package option import ( "context" + "reflect" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -13,6 +15,7 @@ import ( ) type OutboundOptionsRegistry interface { + OptionTypes() []string CreateOptions(outboundType string) (any, bool) } @@ -59,13 +62,27 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err return nil } +func (h Outbound) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("Outbound", func() (*schema.Node, error) { + registry := service.FromContext[OutboundOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing outbound options registry in context") + } + return registryUnion(builder, registry, []string{C.TypeShadowsocksR, C.TypeWireGuard}, true) + }) +} + type DialerOptionsWrapper interface { TakeDialerOptions() DialerOptions ReplaceDialerOptions(options DialerOptions) } type DialerOptions struct { - Detour string `json:"detour,omitempty"` + Detour string `json:"detour,omitempty" reference:"outbound"` + AbstractDialerOptions +} + +type AbstractDialerOptions struct { BindInterface string `json:"bind_interface,omitempty"` Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` @@ -73,7 +90,7 @@ type DialerOptions struct { ProtectPath string `json:"protect_path,omitempty"` RoutingMark FwMark `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` - NetNs string `json:"netns,omitempty"` + NetNs string `json:"netns,omitempty" reference:"network_namespace"` ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` TCPMultiPath bool `json:"tcp_multi_path,omitempty"` @@ -91,11 +108,11 @@ type DialerOptions struct { FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` // Deprecated: migrated to domain resolver - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty" schema:"omit"` } type _DomainResolveOptions struct { - Server string `json:"server"` + Server string `json:"server" reference:"dns_server"` Timeout badoption.Duration `json:"timeout,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` @@ -138,6 +155,18 @@ func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error { return nil } +func (o DomainResolveOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DomainResolver", func() (*schema.Node, error) { + objectForm := schema.StrictObject() + err := builder.FlattenStruct(objectForm, reflect.TypeFor[DomainResolveOptions]()) + if err != nil { + return nil, err + } + objectForm.Required = []string{"server"} + return schema.AnyOf(schema.TagReferenceNode("dns_server"), objectForm), nil + }) +} + func (o *DialerOptions) TakeDialerOptions() DialerOptions { return *o } diff --git a/option/platform.go b/option/platform.go index e4ecd6fa1..7044245ad 100644 --- a/option/platform.go +++ b/option/platform.go @@ -1,6 +1,7 @@ package option import ( + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badoption" @@ -63,6 +64,10 @@ func (r *OnDemandRuleAction) UnmarshalJSON(bytes []byte) error { return nil } +func (r OnDemandRuleAction) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("connect", "disconnect", "evaluate_connection", "ignore"), nil +} + type OnDemandRuleInterfaceType int func (r *OnDemandRuleInterfaceType) MarshalJSON() ([]byte, error) { @@ -103,3 +108,7 @@ func (r *OnDemandRuleInterfaceType) UnmarshalJSON(bytes []byte) error { *r = OnDemandRuleInterfaceType(interfaceTypeValue) return nil } + +func (r OnDemandRuleInterfaceType) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("any", "wifi", "cellular"), nil +} diff --git a/option/resolved.go b/option/resolved.go index cb9f579d1..f17809364 100644 --- a/option/resolved.go +++ b/option/resolved.go @@ -3,7 +3,9 @@ package option import ( "context" "net/netip" + "reflect" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badoption" @@ -39,6 +41,15 @@ func (r *ResolvedServiceOptions) UnmarshalJSONContext(ctx context.Context, bytes return nil } +func (r ResolvedServiceOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + node := schema.StrictObject() + err := builder.FlattenStruct(node, reflect.TypeFor[ResolvedServiceOptions]()) + if err != nil { + return nil, err + } + return node, nil +} + type ResolvedDNSServerOptions struct { Service string `json:"service"` AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"` diff --git a/option/route.go b/option/route.go index 893be8ece..39f3cfb9d 100644 --- a/option/route.go +++ b/option/route.go @@ -3,11 +3,11 @@ package option import "github.com/sagernet/sing/common/json/badoption" type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Geosite *GeositeOptions `json:"geosite,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty" schema:"omit"` + Geosite *GeositeOptions `json:"geosite,omitempty" schema:"omit"` Rules []Rule `json:"rules,omitempty"` RuleSet []RuleSet `json:"rule_set,omitempty"` - Final string `json:"final,omitempty"` + Final string `json:"final,omitempty" reference:"outbound"` FindProcess bool `json:"find_process,omitempty"` FindNeighbor bool `json:"find_neighbor,omitempty"` DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"` @@ -26,11 +26,11 @@ type RouteOptions struct { type GeoIPOptions struct { Path string `json:"path,omitempty"` DownloadURL string `json:"download_url,omitempty"` - DownloadDetour string `json:"download_detour,omitempty"` + DownloadDetour string `json:"download_detour,omitempty" reference:"outbound"` } type GeositeOptions struct { Path string `json:"path,omitempty"` DownloadURL string `json:"download_url,omitempty"` - DownloadDetour string `json:"download_detour,omitempty"` + DownloadDetour string `json:"download_detour,omitempty" reference:"outbound"` } diff --git a/option/rule.go b/option/rule.go index 5759cf56e..05dcb44c5 100644 --- a/option/rule.go +++ b/option/rule.go @@ -5,6 +5,7 @@ import ( "reflect" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -13,7 +14,7 @@ import ( ) type _Rule struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"default,logical"` DefaultOptions DefaultRule `json:"-"` LogicalOptions LogicalRule `json:"-"` } @@ -65,20 +66,91 @@ func (r Rule) IsValid() bool { } } +func (r Rule) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("Rule", func() (*schema.Node, error) { + actionRef, err := builder.Define("RuleAction", func() (*schema.Node, error) { + return routeActionUnion(builder) + }) + if err != nil { + return nil, err + } + nestedRef, err := builder.Define("NestedRule", func() (*schema.Node, error) { + return nestedRuleUnion(builder, reflect.TypeFor[RawDefaultRule](), "NestedRule") + }) + if err != nil { + return nil, err + } + return ruleUnion(builder, reflect.TypeFor[RawDefaultRule](), nestedRef, actionRef) + }) +} + +// ruleUnion builds the top-level rule schema: match fields composed with rule +// actions via unevaluatedProperties, mirroring the badjson.UnmarshallExcluded +// composition in DefaultRule / LogicalRule. +func ruleUnion(builder schema.Builder, matchType reflect.Type, nestedRef *schema.Node, actionRef *schema.Node) (*schema.Node, error) { + defaultMatch := schema.LooseObject() + defaultMatch.Properties.Put("type", schema.StringEnum(C.RuleTypeDefault, "")) + err := builder.FlattenStruct(defaultMatch, matchType) + if err != nil { + return nil, err + } + defaultVariant := &schema.Node{ + Type: "object", + AllOf: []*schema.Node{defaultMatch, actionRef}, + UnevaluatedProperties: false, + } + + logicalMatch := schema.LooseObject() + logicalMatch.Properties.Put("type", schema.StringConst(C.RuleTypeLogical)) + logicalProperties(logicalMatch, nestedRef) + logicalMatch.Required = []string{"type", "mode", "rules"} + logicalVariant := &schema.Node{ + Type: "object", + AllOf: []*schema.Node{logicalMatch, actionRef}, + UnevaluatedProperties: false, + } + + return schema.OneOf(defaultVariant, logicalVariant), nil +} + +// nestedRuleUnion builds a match-only rule schema: nested rules reject rule +// actions, and headless rules never carry them. +func nestedRuleUnion(builder schema.Builder, matchType reflect.Type, selfName string) (*schema.Node, error) { + defaultVariant := schema.StrictObject() + defaultVariant.Properties.Put("type", schema.StringEnum(C.RuleTypeDefault, "")) + err := builder.FlattenStruct(defaultVariant, matchType) + if err != nil { + return nil, err + } + + logicalVariant := schema.StrictObject() + logicalVariant.Properties.Put("type", schema.StringConst(C.RuleTypeLogical)) + logicalProperties(logicalVariant, schema.RefNode(selfName)) + logicalVariant.Required = []string{"type", "mode", "rules"} + + return schema.OneOf(defaultVariant, logicalVariant), nil +} + +func logicalProperties(node *schema.Node, nestedRef *schema.Node) { + node.Properties.Put("mode", schema.StringEnum(C.LogicalTypeAnd, C.LogicalTypeOr)) + node.Properties.Put("rules", &schema.Node{Type: "array", Items: nestedRef}) + node.Properties.Put("invert", schema.BooleanNode()) +} + type RawDefaultRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty" reference:"inbound"` + IPVersion int `json:"ip_version,omitempty" enum:"4,6"` + Network badoption.Listable[string] `json:"network,omitempty" enum:"tcp,udp,icmp"` AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty" enum:"tls,http,quic,dns,stun,bittorrent,dtls,ssh,rdp,ntp"` Client badoption.Listable[string] `json:"client,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` @@ -106,12 +178,12 @@ type RawDefaultRule struct { SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"` SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"` PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty" reference:"rule_set"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source - Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` + Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty" schema:"omit"` } type DefaultRule struct { @@ -138,7 +210,7 @@ func (r DefaultRule) IsValid() bool { } type RawLogicalRule struct { - Mode string `json:"mode"` + Mode string `json:"mode" enum:"and,or"` Rules []Rule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } diff --git a/option/rule_action.go b/option/rule_action.go index 75ea3910e..df0077731 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -4,9 +4,11 @@ import ( "context" "fmt" "net/netip" + "reflect" "time" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -14,7 +16,7 @@ import ( ) type _RuleAction struct { - Action string `json:"action,omitempty"` + Action string `json:"action,omitempty" enum:"route,route-options,direct,bypass,reject,hijack-dns,sniff,resolve"` RouteOptions RouteActionOptions `json:"-"` RouteOptionsOptions RouteOptionsActionOptions `json:"-"` DirectOptions DirectActionOptions `json:"-"` @@ -97,9 +99,10 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { } type _DNSRuleAction struct { - Action string `json:"action,omitempty"` + Action string `json:"action,omitempty" enum:"route,evaluate,respond,route-options,reject,predefined"` Race bool `json:"race,omitempty"` RouteOptions DNSRouteActionOptions `json:"-"` + EvaluateOptions DNSEvaluateActionOptions `json:"-"` RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"` RejectOptions RejectActionOptions `json:"-"` PredefinedOptions DNSRouteActionPredefined `json:"-"` @@ -117,7 +120,7 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { r.Action = "" v = r.RouteOptions case C.RuleActionTypeEvaluate: - v = r.RouteOptions + v = r.EvaluateOptions case C.RuleActionTypeRespond: v = nil case C.RuleActionTypeRouteOptions: @@ -146,7 +149,7 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e r.Action = C.RuleActionTypeRoute v = &r.RouteOptions case C.RuleActionTypeEvaluate: - v = &r.RouteOptions + v = &r.EvaluateOptions case C.RuleActionTypeRespond: v = nil case C.RuleActionTypeRouteOptions: @@ -165,14 +168,11 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e if err != nil { return err } - if r.Action == C.RuleActionTypeRoute && r.RouteOptions.Tag != "" { - return E.New("`tag` is only available in the `evaluate` action") - } return nil } type RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` + Outbound string `json:"outbound,omitempty" reference:"outbound"` RawRouteOptionsActionOptions } @@ -191,7 +191,7 @@ type RawRouteOptionsActionOptions struct { TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"` TLSRecordFragment bool `json:"tls_record_fragment,omitempty"` TLSSpoof string `json:"tls_spoof,omitempty"` - TLSSpoofMethod string `json:"tls_spoof_method,omitempty"` + TLSSpoofMethod string `json:"tls_spoof_method,omitempty" enum:"wrong-sequence,wrong-checksum,wrong-ack,wrong-md5,wrong-timestamp"` } type RouteOptionsActionOptions RawRouteOptionsActionOptions @@ -211,30 +211,31 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { } type DNSRouteActionOptions struct { - Server string `json:"server,omitempty"` - Tag string `json:"tag,omitempty"` - Speculative bool `json:"speculative,omitempty"` + Server string `json:"server,omitempty" reference:"dns_server"` + Speculative bool `json:"speculative,omitempty"` + AbstractDNSRouteActionOptions +} + +type DNSEvaluateActionOptions struct { + Server string `json:"server,omitempty" reference:"dns_server"` + Tag string `json:"tag,omitempty"` + Speculative bool `json:"speculative,omitempty"` + AbstractDNSRouteActionOptions +} + +type AbstractDNSRouteActionOptions struct { Timeout badoption.Duration `json:"timeout,omitempty"` - Strategy DomainStrategy `json:"strategy,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty" schema:"omit"` DisableCache bool `json:"disable_cache,omitempty"` DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } -type _DNSRouteOptionsActionOptions struct { - Strategy DomainStrategy `json:"strategy,omitempty"` - Timeout badoption.Duration `json:"timeout,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` -} - -type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions +type DNSRouteOptionsActionOptions AbstractDNSRouteActionOptions func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, (*_DNSRouteOptionsActionOptions)(r)) + err := json.Unmarshal(data, (*AbstractDNSRouteActionOptions)(r)) if err != nil { return err } @@ -244,9 +245,9 @@ func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error { return nil } -type _DirectActionOptions DialerOptions - -type DirectActionOptions _DirectActionOptions +type DirectActionOptions struct { + AbstractDialerOptions +} func (d DirectActionOptions) Descriptions() []string { var descriptions []string @@ -286,19 +287,8 @@ func (d DirectActionOptions) Descriptions() []string { return descriptions } -func (d *DirectActionOptions) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, (*_DirectActionOptions)(d)) - if err != nil { - return err - } - if d.Detour != "" { - return E.New("detour is not available in the current context") - } - return nil -} - type _RejectActionOptions struct { - Method string `json:"method,omitempty"` + Method string `json:"method,omitempty" enum:"default,drop,reply"` NoDrop bool `json:"no_drop,omitempty"` } @@ -332,12 +322,12 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { } type RouteActionSniff struct { - Sniffer badoption.Listable[string] `json:"sniffer,omitempty"` + Sniffer badoption.Listable[string] `json:"sniffer,omitempty" enum:"tls,http,quic,dns,stun,bittorrent,dtls,ssh,rdp,ntp"` Timeout badoption.Duration `json:"timeout,omitempty"` } type RouteActionResolve struct { - Server string `json:"server,omitempty"` + Server string `json:"server,omitempty" reference:"dns_server"` Timeout badoption.Duration `json:"timeout,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` @@ -352,3 +342,81 @@ type DNSRouteActionPredefined struct { Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"` Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"` } + +type actionVariant struct { + action string + actionOptional bool + structType reflect.Type + build func(variant *schema.Node) error +} + +func actionUnion(builder schema.Builder, variants []actionVariant) (*schema.Node, error) { + variantNodes := make([]*schema.Node, 0, len(variants)) + for _, variant := range variants { + variantNode := schema.LooseObject() + variantNode.Properties.Put("action", schema.StringConst(variant.action)) + if !variant.actionOptional { + variantNode.Required = []string{"action"} + } + if variant.build != nil { + err := variant.build(variantNode) + if err != nil { + return nil, err + } + } + if variant.structType != nil { + err := builder.FlattenStruct(variantNode, variant.structType) + if err != nil { + return nil, err + } + } + variantNodes = append(variantNodes, variantNode) + } + return schema.OneOf(variantNodes...), nil +} + +func rejectProperties(variant *schema.Node) error { + variant.Properties.Put("method", schema.StringEnum( + "", + C.RuleActionRejectMethodDefault, + C.RuleActionRejectMethodDrop, + C.RuleActionRejectMethodReply, + )) + variant.Properties.Put("no_drop", schema.BooleanNode()) + return nil +} + +func routeActionUnion(builder schema.Builder) (*schema.Node, error) { + return actionUnion(builder, []actionVariant{ + {action: C.RuleActionTypeRoute, actionOptional: true, structType: reflect.TypeFor[RouteActionOptions]()}, + {action: C.RuleActionTypeRouteOptions, structType: reflect.TypeFor[RawRouteOptionsActionOptions]()}, + {action: C.RuleActionTypeDirect, structType: reflect.TypeFor[DirectActionOptions]()}, + {action: C.RuleActionTypeBypass, structType: reflect.TypeFor[RouteActionOptions]()}, + {action: C.RuleActionTypeReject, build: rejectProperties}, + {action: C.RuleActionTypeHijackDNS}, + {action: C.RuleActionTypeSniff, structType: reflect.TypeFor[RouteActionSniff]()}, + {action: C.RuleActionTypeResolve, structType: reflect.TypeFor[RouteActionResolve]()}, + }) +} + +func dnsActionUnion(builder schema.Builder) (*schema.Node, error) { + raceProperty := func(variant *schema.Node) error { + variant.Properties.Put("race", schema.BooleanNode()) + return nil + } + rejectWithRace := func(variant *schema.Node) error { + err := raceProperty(variant) + if err != nil { + return err + } + return rejectProperties(variant) + } + return actionUnion(builder, []actionVariant{ + {action: C.RuleActionTypeRoute, actionOptional: true, structType: reflect.TypeFor[DNSRouteActionOptions](), build: raceProperty}, + {action: C.RuleActionTypeEvaluate, structType: reflect.TypeFor[DNSEvaluateActionOptions](), build: raceProperty}, + {action: C.RuleActionTypeRespond, build: raceProperty}, + {action: C.RuleActionTypeRouteOptions, structType: reflect.TypeFor[DNSRouteOptionsActionOptions](), build: raceProperty}, + {action: C.RuleActionTypeReject, build: rejectWithRace}, + {action: C.RuleActionTypePredefined, structType: reflect.TypeFor[DNSRouteActionPredefined](), build: raceProperty}, + }) +} diff --git a/option/rule_dns.go b/option/rule_dns.go index dc27bd6c2..0225bc968 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -5,6 +5,7 @@ import ( "reflect" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -13,7 +14,7 @@ import ( ) type _DNSRule struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"default,logical"` DefaultOptions DefaultDNSRule `json:"-"` LogicalOptions LogicalDNSRule `json:"-"` } @@ -67,6 +68,24 @@ func (r DNSRule) IsValid() bool { } } +func (r DNSRule) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DNSRule", func() (*schema.Node, error) { + actionRef, err := builder.Define("DNSRuleAction", func() (*schema.Node, error) { + return dnsActionUnion(builder) + }) + if err != nil { + return nil, err + } + nestedRef, err := builder.Define("NestedDNSRule", func() (*schema.Node, error) { + return nestedRuleUnion(builder, reflect.TypeFor[RawDefaultDNSRule](), "NestedDNSRule") + }) + if err != nil { + return nil, err + } + return ruleUnion(builder, reflect.TypeFor[RawDefaultDNSRule](), nestedRef, actionRef) + }) +} + type DNSRuleMatchResponse struct { Enabled bool Tag string @@ -111,13 +130,17 @@ func (m *DNSRuleMatchResponse) ResponseTag() string { return m.Tag } +func (m DNSRuleMatchResponse) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.AnyOf(schema.BooleanNode(), schema.StringNode()), nil +} + type RawDefaultDNSRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty" reference:"inbound"` + IPVersion int `json:"ip_version,omitempty" enum:"4,6"` QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty" enum:"tcp,udp"` AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty" enum:"tls,http,quic,dns,stun,bittorrent,dtls,ssh,rdp,ntp"` Domain badoption.Listable[string] `json:"domain,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` @@ -135,7 +158,7 @@ type RawDefaultDNSRule struct { PackageNameRegex badoption.Listable[string] `json:"package_name_regex,omitempty"` User badoption.Listable[string] `json:"user,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"` - Outbound badoption.Listable[string] `json:"outbound,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty" reference:"outbound" schema:"omit"` ClashMode string `json:"clash_mode,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` @@ -148,7 +171,7 @@ type RawDefaultDNSRule struct { SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"` SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"` PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty" reference:"rule_set"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` MatchResponse *DNSRuleMatchResponse `json:"match_response,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` @@ -161,13 +184,13 @@ type RawDefaultDNSRule struct { Invert bool `json:"invert,omitempty"` // Deprecated: removed in sing-box 1.12.0 - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"` // Deprecated: removed in sing-box 1.11.0 - RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty" schema:"omit"` // Deprecated: renamed to rule_set_ip_cidr_match_source - Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` + Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty" schema:"omit"` } type DefaultDNSRule struct { @@ -210,7 +233,7 @@ func (r DefaultDNSRule) IsValid() bool { } type RawLogicalDNSRule struct { - Mode string `json:"mode"` + Mode string `json:"mode" enum:"and,or"` Rules []DNSRule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } diff --git a/option/rule_nested.go b/option/rule_nested.go index a48c17ea3..173e5e3e1 100644 --- a/option/rule_nested.go +++ b/option/rule_nested.go @@ -20,7 +20,7 @@ const ( var ( routeRuleActionKeys = jsonFieldNames(reflect.TypeFor[_RuleAction](), reflect.TypeFor[RouteActionOptions]()) - dnsRuleActionKeys = jsonFieldNames(reflect.TypeFor[_DNSRuleAction](), reflect.TypeFor[DNSRouteActionOptions]()) + dnsRuleActionKeys = jsonFieldNames(reflect.TypeFor[_DNSRuleAction](), reflect.TypeFor[DNSRouteActionOptions](), reflect.TypeFor[DNSEvaluateActionOptions]()) ) func nestedRuleChildContext(ctx context.Context) context.Context { diff --git a/option/rule_set.go b/option/rule_set.go index 7389abfba..7f75cdd64 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -7,6 +7,7 @@ import ( "strings" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/domain" E "github.com/sagernet/sing/common/exceptions" @@ -19,9 +20,9 @@ import ( ) type _RuleSet struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"inline,local,remote"` Tag badoption.Listable[string] `json:"tag"` - Format string `json:"format,omitempty"` + Format string `json:"format,omitempty" enum:"source,binary"` InlineOptions PlainRuleSet `json:"-"` LocalOptions LocalRuleSet `json:"-"` RemoteOptions RemoteRuleSet `json:"-"` @@ -131,6 +132,45 @@ func ruleSetDefaultFormat(path string) string { } } +func (r RuleSet) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("RuleSet", func() (*schema.Node, error) { + headlessRef, err := builder.Describe(reflect.TypeFor[HeadlessRule]()) + if err != nil { + return nil, err + } + tagNode := schema.ListableOf(schema.StringNode()) + formatNode := schema.StringEnum(C.RuleSetFormatSource, C.RuleSetFormatBinary) + + inlineVariant := schema.StrictObject() + inlineVariant.Properties.Put("type", schema.StringEnum(C.RuleSetTypeInline, "")) + inlineVariant.Properties.Put("tag", tagNode) + inlineVariant.Properties.Put("rules", &schema.Node{Type: "array", Items: headlessRef}) + inlineVariant.Required = []string{"tag"} + + localVariant := schema.StrictObject() + localVariant.Properties.Put("type", schema.StringConst(C.RuleSetTypeLocal)) + localVariant.Properties.Put("tag", tagNode) + localVariant.Properties.Put("format", formatNode) + err = builder.FlattenStruct(localVariant, reflect.TypeFor[LocalRuleSet]()) + if err != nil { + return nil, err + } + localVariant.Required = []string{"type", "tag"} + + remoteVariant := schema.StrictObject() + remoteVariant.Properties.Put("type", schema.StringConst(C.RuleSetTypeRemote)) + remoteVariant.Properties.Put("tag", tagNode) + remoteVariant.Properties.Put("format", formatNode) + err = builder.FlattenStruct(remoteVariant, reflect.TypeFor[RemoteRuleSet]()) + if err != nil { + return nil, err + } + remoteVariant.Required = []string{"type", "tag"} + + return schema.OneOf(inlineVariant, localVariant, remoteVariant), nil + }) +} + type LocalRuleSet struct { Path string `json:"path,omitempty"` } @@ -140,11 +180,11 @@ type RemoteRuleSet struct { HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` UpdateInterval badoption.Duration `json:"update_interval,omitempty"` // Deprecated: use http_client instead - DownloadDetour string `json:"download_detour,omitempty"` + DownloadDetour string `json:"download_detour,omitempty" reference:"outbound" schema:"omit"` } type _HeadlessRule struct { - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty" enum:"default,logical"` DefaultOptions DefaultHeadlessRule `json:"-"` LogicalOptions LogicalHeadlessRule `json:"-"` } @@ -198,9 +238,15 @@ func (r HeadlessRule) IsValid() bool { } } +func (r HeadlessRule) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("HeadlessRule", func() (*schema.Node, error) { + return nestedRuleUnion(builder, reflect.TypeFor[DefaultHeadlessRule](), "HeadlessRule") + }) +} + type DefaultHeadlessRule struct { QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty" enum:"tcp,udp,icmp"` Domain badoption.Listable[string] `json:"domain,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` @@ -241,7 +287,7 @@ func (r DefaultHeadlessRule) IsValid() bool { } type LogicalHeadlessRule struct { - Mode string `json:"mode"` + Mode string `json:"mode" enum:"and,or"` Rules []HeadlessRule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } @@ -251,7 +297,7 @@ func (r LogicalHeadlessRule) IsValid() bool { } type _PlainRuleSetCompat struct { - Version uint8 `json:"version"` + Version uint8 `json:"version" enum:"1,2,3,4,5"` Options PlainRuleSet `json:"-"` RawMessage json.RawMessage `json:"-"` } diff --git a/option/schema.go b/option/schema.go new file mode 100644 index 000000000..de9a6c442 --- /dev/null +++ b/option/schema.go @@ -0,0 +1,71 @@ +package option + +import ( + "reflect" + "slices" + + "github.com/sagernet/sing-box/schema" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badjson" +) + +type schemaTypeRegistry interface { + OptionTypes() []string + CreateOptions(itemType string) (any, bool) +} + +func registryUnion(builder schema.Builder, registry schemaTypeRegistry, excludeTypes []string, withTag bool) (*schema.Node, error) { + var variants []*schema.Node + for _, itemType := range registry.OptionTypes() { + if slices.Contains(excludeTypes, itemType) { + continue + } + optionsValue, _ := registry.CreateOptions(itemType) + describer, isDescriber := optionsValue.(schema.Describer) + var variant *schema.Node + var err error + if isDescriber { + variant, err = describer.DescribeSchema(builder) + } else { + variant = schema.StrictObject() + err = builder.FlattenStruct(variant, reflect.TypeOf(optionsValue).Elem()) + } + if err != nil { + return nil, E.Cause(err, itemType) + } + err = prependTypeTag(variant, itemType, withTag) + if err != nil { + return nil, E.Cause(err, itemType) + } + variants = append(variants, variant) + } + return schema.OneOf(variants...), nil +} + +// prependTypeTag merges the polymorphic base fields into a variant produced +// from registry options, matching badjson.UnmarshallExcluded composition. +func prependTypeTag(variant *schema.Node, typeName string, withTag bool) error { + if variant.OneOf != nil { + for _, branch := range variant.OneOf { + err := prependTypeTag(branch, typeName, withTag) + if err != nil { + return err + } + } + return nil + } + if variant.Properties == nil { + return E.New("cannot merge type into non-object variant") + } + newProperties := new(badjson.TypedMap[string, *schema.Node]) + newProperties.Put("type", schema.StringConst(typeName)) + if withTag { + newProperties.Put("tag", schema.StringNode()) + } + for _, entry := range variant.Properties.Entries() { + newProperties.Put(entry.Key, entry.Value) + } + variant.Properties = newProperties + variant.Required = append([]string{"type"}, variant.Required...) + return nil +} diff --git a/option/service.go b/option/service.go index 7d45bc143..35e3faca5 100644 --- a/option/service.go +++ b/option/service.go @@ -3,6 +3,7 @@ package option import ( "context" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -10,6 +11,7 @@ import ( ) type ServiceOptionsRegistry interface { + OptionTypes() []string CreateOptions(serviceType string) (any, bool) } @@ -45,3 +47,13 @@ func (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) erro h.Options = options return nil } + +func (h Service) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("Service", func() (*schema.Node, error) { + registry := service.FromContext[ServiceOptionsRegistry](builder.Context()) + if registry == nil { + return nil, E.New("missing service options registry in context") + } + return registryUnion(builder, registry, nil, true) + }) +} diff --git a/option/shadowsocks.go b/option/shadowsocks.go index 7cb656f36..484aafec5 100644 --- a/option/shadowsocks.go +++ b/option/shadowsocks.go @@ -3,7 +3,7 @@ package option type ShadowsocksInboundOptions struct { ListenOptions Network NetworkList `json:"network,omitempty"` - Method string `json:"method"` + Method string `json:"method" enum:"none,aes-128-gcm,aes-192-gcm,aes-256-gcm,chacha20-ietf-poly1305,xchacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm,2022-blake3-chacha20-poly1305"` Password string `json:"password,omitempty"` Users []ShadowsocksUser `json:"users,omitempty"` Destinations []ShadowsocksDestination `json:"destinations,omitempty"` @@ -25,7 +25,7 @@ type ShadowsocksDestination struct { type ShadowsocksOutboundOptions struct { DialerOptions ServerOptions - Method string `json:"method"` + Method string `json:"method" enum:"none,aes-128-gcm,aes-192-gcm,aes-256-gcm,chacha20-ietf-poly1305,xchacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm,2022-blake3-chacha20-poly1305,aes-128-ctr,aes-192-ctr,aes-256-ctr,aes-128-cfb,aes-192-cfb,aes-256-cfb,rc4-md5,chacha20-ietf,xchacha20"` Password string `json:"password"` Plugin string `json:"plugin,omitempty"` PluginOptions string `json:"plugin_opts,omitempty"` diff --git a/option/shadowtls.go b/option/shadowtls.go index 81ef9a437..726326183 100644 --- a/option/shadowtls.go +++ b/option/shadowtls.go @@ -3,13 +3,14 @@ package option import ( "encoding/json" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json/badjson" ) type ShadowTLSInboundOptions struct { ListenOptions - Version int `json:"version,omitempty"` + Version int `json:"version,omitempty" enum:"1,2,3"` Password string `json:"password,omitempty"` Users []ShadowTLSUser `json:"users,omitempty"` Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` @@ -62,6 +63,10 @@ func (w *WildcardSNI) UnmarshalJSON(bytes []byte) error { return nil } +func (w WildcardSNI) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("", "off", "authed", "all"), nil +} + type ShadowTLSUser struct { Name string `json:"name,omitempty"` Password string `json:"password,omitempty"` @@ -75,7 +80,7 @@ type ShadowTLSHandshakeOptions struct { type ShadowTLSOutboundOptions struct { DialerOptions ServerOptions - Version int `json:"version,omitempty"` + Version int `json:"version,omitempty" enum:"1,2,3"` Password string `json:"password,omitempty"` OutboundTLSOptionsContainer } diff --git a/option/simple.go b/option/simple.go index f244ba18b..43cbb2c6c 100644 --- a/option/simple.go +++ b/option/simple.go @@ -22,7 +22,7 @@ type HTTPMixedInboundOptions struct { type SOCKSOutboundOptions struct { DialerOptions ServerOptions - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty" enum:"4,4a,5"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Network NetworkList `json:"network,omitempty"` diff --git a/option/snell.go b/option/snell.go index 53c8117d8..6ca0b2e3f 100644 --- a/option/snell.go +++ b/option/snell.go @@ -1,20 +1,27 @@ package option import ( + "reflect" + + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" ) type _SnellInboundOptions struct { - ListenOptions - Version int `json:"version"` - PSK string `json:"psk"` - Users []SnellUser `json:"users,omitempty"` + Version int `json:"version" enum:"5,6"` + AbstractSnellInboundOptions ObfsOptions SnellObfsServerOptions `json:"-"` V6Options SnellV6Options `json:"-"` } +type AbstractSnellInboundOptions struct { + ListenOptions + PSK string `json:"psk"` + Users []SnellUser `json:"users,omitempty"` +} + type SnellInboundOptions _SnellInboundOptions func (o *SnellInboundOptions) UnmarshalJSON(content []byte) error { @@ -51,18 +58,31 @@ func (o SnellInboundOptions) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects((_SnellInboundOptions)(o), versionOptions) } +func (o SnellInboundOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "version", true, []schema.UnionVariant{ + {Value: 5, StructType: reflect.TypeFor[SnellObfsServerOptions]()}, + {Value: 6, StructType: reflect.TypeFor[SnellV6Options]()}, + }, func(variant *schema.Node) error { + return builder.FlattenStruct(variant, reflect.TypeFor[AbstractSnellInboundOptions]()) + }) +} + type _SnellOutboundOptions struct { - DialerOptions - ServerOptions - Version int `json:"version"` - PSK string `json:"psk"` - UserKey string `json:"userkey,omitempty"` - Reuse bool `json:"reuse,omitempty"` - Network NetworkList `json:"network,omitempty"` + Version int `json:"version" enum:"4,6"` + AbstractSnellOutboundOptions ObfsOptions SnellObfsClientOptions `json:"-"` V6Options SnellV6Options `json:"-"` } +type AbstractSnellOutboundOptions struct { + DialerOptions + ServerOptions + PSK string `json:"psk"` + UserKey string `json:"userkey,omitempty"` + Reuse bool `json:"reuse,omitempty"` + Network NetworkList `json:"network,omitempty"` +} + type SnellOutboundOptions _SnellOutboundOptions func (o *SnellOutboundOptions) UnmarshalJSON(content []byte) error { @@ -99,8 +119,17 @@ func (o SnellOutboundOptions) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects((_SnellOutboundOptions)(o), versionOptions) } +func (o SnellOutboundOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "version", true, []schema.UnionVariant{ + {Value: 4, StructType: reflect.TypeFor[SnellObfsClientOptions]()}, + {Value: 6, StructType: reflect.TypeFor[SnellV6Options]()}, + }, func(variant *schema.Node) error { + return builder.FlattenStruct(variant, reflect.TypeFor[AbstractSnellOutboundOptions]()) + }) +} + type SnellObfsServerOptions struct { - ObfsMode string `json:"obfs_mode,omitempty"` + ObfsMode string `json:"obfs_mode,omitempty" enum:"none,http,tls"` } type SnellUser struct { @@ -109,10 +138,10 @@ type SnellUser struct { } type SnellObfsClientOptions struct { - ObfsMode string `json:"obfs_mode,omitempty"` + ObfsMode string `json:"obfs_mode,omitempty" enum:"none,http,tls"` ObfsHost string `json:"obfs_host,omitempty"` } type SnellV6Options struct { - Mode string `json:"mode,omitempty"` + Mode string `json:"mode,omitempty" enum:"default,unshaped,unsafe-raw"` } diff --git a/option/tailscale.go b/option/tailscale.go index 59d7101b9..a470ee300 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -3,7 +3,9 @@ package option import ( "net/netip" "net/url" + "reflect" + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badoption" @@ -56,6 +58,15 @@ func (o *TailscaleSSHServerOptions) UnmarshalJSON(bytes []byte) error { return json.UnmarshalDisallowUnknownFields(bytes, (*_TailscaleSSHServerOptions)(o)) } +func (o TailscaleSSHServerOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + objectForm := schema.StrictObject() + err := builder.FlattenStruct(objectForm, reflect.TypeFor[TailscaleSSHServerOptions]()) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.BooleanNode(), objectForm), nil +} + type TailscaleDNSServerOptions struct { Endpoint string `json:"endpoint,omitempty"` AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"` @@ -127,6 +138,15 @@ func (d *DERPVerifyClientURLOptions) UnmarshalJSON(bytes []byte) error { return nil } +func (d DERPVerifyClientURLOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + objectForm, err := describeHTTPClientObject(builder) + if err != nil { + return nil, err + } + objectForm.Properties.Put("url", schema.StringNode()) + return schema.AnyOf(schema.StringNode(), objectForm), nil +} + type DERPMeshOptions struct { ServerOptions Host string `json:"host,omitempty"` @@ -165,3 +185,12 @@ func (d *DERPSTUNListenOptions) UnmarshalJSON(bytes []byte) error { } return json.Unmarshal(bytes, (*_DERPSTUNListenOptions)(d)) } + +func (d DERPSTUNListenOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + objectForm := schema.StrictObject() + err := builder.FlattenStruct(objectForm, reflect.TypeFor[DERPSTUNListenOptions]()) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.UnsignedNode(16), objectForm), nil +} diff --git a/option/tls.go b/option/tls.go index cf0db4243..5c807d676 100644 --- a/option/tls.go +++ b/option/tls.go @@ -5,6 +5,7 @@ import ( "encoding/json" "strings" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json/badoption" ) @@ -13,9 +14,9 @@ type InboundTLSOptions struct { Enabled bool `json:"enabled,omitempty"` ServerName string `json:"server_name,omitempty"` Insecure bool `json:"insecure,omitempty"` - ALPN badoption.Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty" examples:"http/1.1,h2,h3"` + MinVersion string `json:"min_version,omitempty" enum:"1.0,1.1,1.2,1.3"` + MaxVersion string `json:"max_version,omitempty" enum:"1.0,1.1,1.2,1.3"` CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` Certificate badoption.Listable[string] `json:"certificate,omitempty"` @@ -32,7 +33,7 @@ type InboundTLSOptions struct { CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"` // Deprecated: use certificate_provider - ACME *InboundACMEOptions `json:"acme,omitempty"` + ACME *InboundACMEOptions `json:"acme,omitempty" schema:"omit"` ECH *InboundECHOptions `json:"ech,omitempty"` Reality *InboundRealityOptions `json:"reality,omitempty"` @@ -82,6 +83,10 @@ func (t *ClientAuthType) UnmarshalJSON(data []byte) error { return nil } +func (t ClientAuthType) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("no", "request", "require-any", "verify-if-given", "require-and-verify"), nil +} + type InboundTLSOptionsContainer struct { TLS *InboundTLSOptions `json:"tls,omitempty"` } @@ -101,13 +106,13 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL type OutboundTLSOptions struct { Enabled bool `json:"enabled,omitempty"` - Engine string `json:"engine,omitempty"` + Engine string `json:"engine,omitempty" enum:"go,apple,windows"` DisableSNI bool `json:"disable_sni,omitempty"` ServerName string `json:"server_name,omitempty"` Insecure bool `json:"insecure,omitempty"` - ALPN badoption.Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty" examples:"http/1.1,h2,h3"` + MinVersion string `json:"min_version,omitempty" enum:"1.0,1.1,1.2,1.3"` + MaxVersion string `json:"max_version,omitempty" enum:"1.0,1.1,1.2,1.3"` CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` Certificate badoption.Listable[string] `json:"certificate,omitempty"` @@ -121,7 +126,7 @@ type OutboundTLSOptions struct { FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` RecordFragment bool `json:"record_fragment,omitempty"` Spoof string `json:"spoof,omitempty"` - SpoofMethod string `json:"spoof_method,omitempty"` + SpoofMethod string `json:"spoof_method,omitempty" enum:"wrong-sequence,wrong-checksum,wrong-ack,wrong-md5,wrong-timestamp"` KernelTx bool `json:"kernel_tx,omitempty"` KernelRx bool `json:"kernel_rx,omitempty"` HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"` @@ -199,6 +204,10 @@ func (c *CurvePreference) UnmarshalJSON(data []byte) error { return nil } +func (c CurvePreference) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum("P256", "P384", "P521", "X25519", "X25519MLKEM768"), nil +} + type InboundRealityOptions struct { Enabled bool `json:"enabled,omitempty"` Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"` @@ -218,9 +227,9 @@ type InboundECHOptions struct { KeyPath string `json:"key_path,omitempty"` // Deprecated: not supported by stdlib - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty" schema:"omit"` // Deprecated: added by fault - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty" schema:"omit"` } type OutboundECHOptions struct { @@ -230,14 +239,14 @@ type OutboundECHOptions struct { QueryServerName string `json:"query_server_name,omitempty"` // Deprecated: not supported by stdlib - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty" schema:"omit"` // Deprecated: added by fault - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty" schema:"omit"` } type OutboundUTLSOptions struct { Enabled bool `json:"enabled,omitempty"` - Fingerprint string `json:"fingerprint,omitempty"` + Fingerprint string `json:"fingerprint,omitempty" enum:"chrome_psk,chrome_psk_shuffle,chrome_padding_psk_shuffle,chrome_pq,chrome_pq_psk,chrome,firefox,edge,safari,360,qq,ios,android,random,randomized"` } type OutboundRealityOptions struct { diff --git a/option/tls_acme.go b/option/tls_acme.go index 636abc6ec..35e26779e 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -1,7 +1,10 @@ package option import ( + "reflect" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -29,7 +32,7 @@ type ACMEExternalAccountOptions struct { } type _ACMEDNS01ChallengeOptions struct { - Provider string `json:"provider,omitempty"` + Provider string `json:"provider,omitempty" enum:"alidns,cloudflare,acmedns"` AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` @@ -77,6 +80,20 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { return nil } +func acmeDNS01Variants() []schema.UnionVariant { + return []schema.UnionVariant{ + {Value: C.DNSProviderAliDNS, StructType: reflect.TypeFor[ACMEDNS01AliDNSOptions]()}, + {Value: C.DNSProviderCloudflare, StructType: reflect.TypeFor[ACMEDNS01CloudflareOptions]()}, + {Value: C.DNSProviderACMEDNS, StructType: reflect.TypeFor[ACMEDNS01ACMEDNSOptions]()}, + } +} + +func (o ACMEDNS01ChallengeOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("ACMEDNS01Challenge", func() (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "provider", true, acmeDNS01Variants(), nil) + }) +} + type ACMEDNS01AliDNSOptions struct { AccessKeyID string `json:"access_key_id,omitempty"` AccessKeySecret string `json:"access_key_secret,omitempty"` diff --git a/option/tuic.go b/option/tuic.go index 51cc0a5b9..e90417e39 100644 --- a/option/tuic.go +++ b/option/tuic.go @@ -5,7 +5,7 @@ import "github.com/sagernet/sing/common/json/badoption" type TUICInboundOptions struct { ListenOptions Users []TUICUser `json:"users,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` + CongestionControl string `json:"congestion_control,omitempty" enum:"cubic,new_reno,bbr"` AuthTimeout badoption.Duration `json:"auth_timeout,omitempty"` ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` Heartbeat badoption.Duration `json:"heartbeat,omitempty"` @@ -24,8 +24,8 @@ type TUICOutboundOptions struct { ServerOptions UUID string `json:"uuid,omitempty"` Password string `json:"password,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - UDPRelayMode string `json:"udp_relay_mode,omitempty"` + CongestionControl string `json:"congestion_control,omitempty" enum:"cubic,new_reno,bbr"` + UDPRelayMode string `json:"udp_relay_mode,omitempty" enum:"native,quic"` UDPOverStream bool `json:"udp_over_stream,omitempty"` ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` Heartbeat badoption.Duration `json:"heartbeat,omitempty"` diff --git a/option/tun.go b/option/tun.go index 554af76c3..aa959c5b1 100644 --- a/option/tun.go +++ b/option/tun.go @@ -4,6 +4,7 @@ import ( "net/netip" "strconv" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -12,10 +13,10 @@ import ( type TunInboundOptions struct { InterfaceName string `json:"interface_name,omitempty"` - NetNs string `json:"netns,omitempty"` + NetNs string `json:"netns,omitempty" reference:"network_namespace"` MTU uint32 `json:"mtu,omitempty"` - Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` - DNSMode string `json:"dns_mode,omitempty"` + Address badoption.Listable[netip.Prefix] `json:"address,omitempty" examples:"172.19.0.1/30,fdfe:dcba:9876::1/126"` + DNSMode string `json:"dns_mode,omitempty" enum:"disabled,native,hijack"` DNSAddress badoption.Listable[netip.Addr] `json:"dns_address,omitempty"` AutoRoute bool `json:"auto_route,omitempty"` IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` @@ -48,26 +49,26 @@ type TunInboundOptions struct { UDPMapping UDPNATBehavior `json:"udp_mapping,omitempty"` UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"` UDPNATMax uint32 `json:"udp_nat_max,omitempty"` - Stack string `json:"stack,omitempty"` + Stack string `json:"stack,omitempty" enum:"system,gvisor,mixed"` Platform *TunPlatformOptions `json:"platform,omitempty"` InboundOptions // Deprecated: removed - GSO bool `json:"gso,omitempty"` + GSO bool `json:"gso,omitempty" schema:"omit"` // Deprecated: merged to Address - Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty"` + Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty" schema:"omit"` // Deprecated: merged to Address - Inet6Address badoption.Listable[netip.Prefix] `json:"inet6_address,omitempty"` + Inet6Address badoption.Listable[netip.Prefix] `json:"inet6_address,omitempty" schema:"omit"` // Deprecated: merged to RouteAddress - Inet4RouteAddress badoption.Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` + Inet4RouteAddress badoption.Listable[netip.Prefix] `json:"inet4_route_address,omitempty" schema:"omit"` // Deprecated: merged to RouteAddress - Inet6RouteAddress badoption.Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` + Inet6RouteAddress badoption.Listable[netip.Prefix] `json:"inet6_route_address,omitempty" schema:"omit"` // Deprecated: merged to RouteExcludeAddress - Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` + Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty" schema:"omit"` // Deprecated: merged to RouteExcludeAddress - Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` + Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty" schema:"omit"` // Deprecated: removed - EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty" schema:"omit"` } type FwMark uint32 @@ -92,3 +93,7 @@ func (f *FwMark) UnmarshalJSON(bytes []byte) error { *f = FwMark(intValue) return nil } + +func (f FwMark) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.AnyOf(schema.UnsignedNode(32), schema.StringNode()), nil +} diff --git a/option/types.go b/option/types.go index 87cf382c6..32ac6ab06 100644 --- a/option/types.go +++ b/option/types.go @@ -1,9 +1,12 @@ package option import ( + "maps" + "slices" "strings" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -43,6 +46,10 @@ func (v NetworkList) Build() []string { return strings.Split(string(v), "\n") } +func (v NetworkList) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.ListableOf(schema.StringEnum(N.NetworkTCP, N.NetworkUDP)), nil +} + type DomainStrategy C.DomainStrategy func (s DomainStrategy) String() string { @@ -105,6 +112,12 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error { return nil } +func (s DomainStrategy) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DomainStrategy", func() (*schema.Node, error) { + return schema.StringEnum("", "as_is", "prefer_ipv4", "prefer_ipv6", "ipv4_only", "ipv6_only"), nil + }) +} + type DNSQueryType uint16 func (t DNSQueryType) String() string { @@ -142,6 +155,15 @@ func (t *DNSQueryType) UnmarshalJSON(bytes []byte) error { return E.New("unknown DNS query type: ", string(bytes)) } +func (t DNSQueryType) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("DNSQueryType", func() (*schema.Node, error) { + return schema.AnyOf( + schema.UnsignedNode(16), + schema.StringEnum(slices.Sorted(maps.Keys(mDNS.StringToType))...), + ), nil + }) +} + func DNSQueryTypeToString(queryType uint16) string { typeName, loaded := mDNS.TypeToString[queryType] if loaded { @@ -170,6 +192,10 @@ func (n *NetworkStrategy) UnmarshalJSON(content []byte) error { return nil } +func (n NetworkStrategy) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.StringEnum(slices.Sorted(maps.Keys(C.StringToNetworkStrategy))...), nil +} + type InterfaceType C.InterfaceType func (t InterfaceType) Build() C.InterfaceType { @@ -193,3 +219,9 @@ func (t *InterfaceType) UnmarshalJSON(content []byte) error { *t = InterfaceType(interfaceType) return nil } + +func (t InterfaceType) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("InterfaceType", func() (*schema.Node, error) { + return schema.StringEnum(slices.Sorted(maps.Keys(C.StringToInterfaceType))...), nil + }) +} diff --git a/option/udp_over_tcp.go b/option/udp_over_tcp.go index b496017a4..014efc8c4 100644 --- a/option/udp_over_tcp.go +++ b/option/udp_over_tcp.go @@ -1,13 +1,16 @@ package option import ( + "reflect" + + "github.com/sagernet/sing-box/schema" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/uot" ) type _UDPOverTCPOptions struct { Enabled bool `json:"enabled,omitempty"` - Version uint8 `json:"version,omitempty"` + Version uint8 `json:"version,omitempty" enum:"1,2"` } type UDPOverTCPOptions _UDPOverTCPOptions @@ -28,3 +31,12 @@ func (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error { } return json.UnmarshalDisallowUnknownFields(bytes, (*_UDPOverTCPOptions)(o)) } + +func (o UDPOverTCPOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + objectForm := schema.StrictObject() + err := builder.FlattenStruct(objectForm, reflect.TypeFor[UDPOverTCPOptions]()) + if err != nil { + return nil, err + } + return schema.AnyOf(schema.BooleanNode(), objectForm), nil +} diff --git a/option/usbip.go b/option/usbip.go index 32136e9e7..8ce912f17 100644 --- a/option/usbip.go +++ b/option/usbip.go @@ -1,6 +1,9 @@ package option import ( + "reflect" + + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -13,7 +16,7 @@ const ( type _USBIPServerServiceOptions struct { ListenOptions - Provider string `json:"provider,omitempty"` + Provider string `json:"provider,omitempty" enum:"default,dynamic"` Options any `json:"-"` } @@ -49,6 +52,15 @@ func (o *USBIPServerServiceOptions) UnmarshalJSON(content []byte) error { return nil } +func (o USBIPServerServiceOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "provider", false, []schema.UnionVariant{ + {Value: USBIPProviderDefault, StructType: reflect.TypeFor[USBIPDefaultProviderOptions](), TypeOptional: true}, + {Value: USBIPProviderDynamic, StructType: reflect.TypeFor[USBIPDynamicProviderOptions]()}, + }, func(variant *schema.Node) error { + return builder.FlattenStruct(variant, reflect.TypeFor[ListenOptions]()) + }) +} + type USBIPClientServiceOptions struct { DialerOptions ServerOptions diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index 68c238581..1c58b9749 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -1,7 +1,10 @@ package option import ( + "reflect" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -9,7 +12,7 @@ import ( ) type _V2RayTransportOptions struct { - Type string `json:"type"` + Type string `json:"type" enum:"http,ws,quic,grpc,httpupgrade"` HTTPOptions V2RayHTTPOptions `json:"-"` WebsocketOptions V2RayWebsocketOptions `json:"-"` QUICOptions V2RayQUICOptions `json:"-"` @@ -67,6 +70,18 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { return nil } +func (o V2RayTransportOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { + return builder.Define("V2RayTransport", func() (*schema.Node, error) { + return schema.DiscriminatedUnion(builder, "type", true, []schema.UnionVariant{ + {Value: C.V2RayTransportTypeHTTP, StructType: reflect.TypeFor[V2RayHTTPOptions]()}, + {Value: C.V2RayTransportTypeWebsocket, StructType: reflect.TypeFor[V2RayWebsocketOptions]()}, + {Value: C.V2RayTransportTypeQUIC, StructType: reflect.TypeFor[V2RayQUICOptions]()}, + {Value: C.V2RayTransportTypeGRPC, StructType: reflect.TypeFor[V2RayGRPCOptions]()}, + {Value: C.V2RayTransportTypeHTTPUpgrade, StructType: reflect.TypeFor[V2RayHTTPUpgradeOptions]()}, + }, nil) + }) +} + type V2RayHTTPOptions struct { Host badoption.Listable[string] `json:"host,omitempty"` Path string `json:"path,omitempty"` diff --git a/option/vmess.go b/option/vmess.go index 8a38fb70c..f4dd28284 100644 --- a/option/vmess.go +++ b/option/vmess.go @@ -18,13 +18,13 @@ type VMessOutboundOptions struct { DialerOptions ServerOptions UUID string `json:"uuid"` - Security string `json:"security"` + Security string `json:"security" enum:"auto,none,zero,aes-128-cfb,aes-128-gcm,chacha20-poly1305"` AlterId int `json:"alter_id,omitempty"` GlobalPadding bool `json:"global_padding,omitempty"` AuthenticatedLength bool `json:"authenticated_length,omitempty"` Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer - PacketEncoding string `json:"packet_encoding,omitempty"` + PacketEncoding string `json:"packet_encoding,omitempty" enum:"packetaddr,xudp"` Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"` } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 9edf38323..1480fac93 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -72,7 +72,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL domainStrategy: C.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer.(dialer.ParallelInterfaceDialer), - isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}), + isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{ + AbstractDialerOptions: option.AbstractDialerOptions{UDPFragmentDefault: true}, + }), } //nolint:staticcheck if options.ProxyProtocol != 0 { diff --git a/protocol/openvpn/server.go b/protocol/openvpn/server.go index ae5542ec5..24e036989 100644 --- a/protocol/openvpn/server.go +++ b/protocol/openvpn/server.go @@ -184,9 +184,11 @@ func (s *ServerEndpoint) Start(stage adapter.StartStage) error { listenAddress := s.options.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})) if listenAddress.IsUnspecified() && s.options.BindInterface == "" && s.options.RoutingMark == 0 && s.options.NetNs == "" { udpDialer, dialerErr := dialer.NewDefault(s.ctx, option.DialerOptions{ - ReuseAddr: s.options.ReuseAddr, - UDPFragment: s.options.UDPFragment, - UDPFragmentDefault: s.options.UDPFragmentDefault, + AbstractDialerOptions: option.AbstractDialerOptions{ + ReuseAddr: s.options.ReuseAddr, + UDPFragment: s.options.UDPFragment, + UDPFragmentDefault: s.options.UDPFragmentDefault, + }, }) if dialerErr != nil { return dialerErr diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 500bacea9..c2d5ea564 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -329,7 +329,9 @@ func (t *Endpoint) start() error { return err } systemDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{ - BindInterface: tunName, + AbstractDialerOptions: option.AbstractDialerOptions{ + BindInterface: tunName, + }, }) if err != nil { _ = systemTun.Close() diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 6412a4862..a3eeac270 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -98,7 +98,9 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL Dialer: outboundDialer, CreateDialer: func(interfaceName string) N.Dialer { return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ - BindInterface: interfaceName, + AbstractDialerOptions: option.AbstractDialerOptions{ + BindInterface: interfaceName, + }, })) }, Name: options.Name, diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 7e38639e6..499473439 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -74,7 +74,9 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti RuleActionRouteOptions: routeOptions, }, nil case C.RuleActionTypeDirect: - directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) + directDialer, err := dialer.New(ctx, option.DialerOptions{ + AbstractDialerOptions: action.DirectOptions.AbstractDialerOptions, + }, false) if err != nil { return nil, err } @@ -141,16 +143,16 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) } case C.RuleActionTypeEvaluate: return &RuleActionEvaluate{ - Server: action.RouteOptions.Server, - Tag: action.RouteOptions.Tag, - Speculative: action.RouteOptions.Speculative, + Server: action.EvaluateOptions.Server, + Tag: action.EvaluateOptions.Tag, + Speculative: action.EvaluateOptions.Speculative, RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ - Strategy: C.DomainStrategy(action.RouteOptions.Strategy), - Timeout: time.Duration(action.RouteOptions.Timeout), - DisableCache: action.RouteOptions.DisableCache, - DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache, - RewriteTTL: action.RouteOptions.RewriteTTL, - ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + Strategy: C.DomainStrategy(action.EvaluateOptions.Strategy), + Timeout: time.Duration(action.EvaluateOptions.Timeout), + DisableCache: action.EvaluateOptions.DisableCache, + DisableOptimisticCache: action.EvaluateOptions.DisableOptimisticCache, + RewriteTTL: action.EvaluateOptions.RewriteTTL, + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.EvaluateOptions.ClientSubnet)), }, } case C.RuleActionTypeRespond: diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index facefa252..66ad779b5 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -32,10 +32,14 @@ func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DN return nil, E.New("`race` requires `match_response`") } switch options.DefaultOptions.Action { - case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + case "", C.RuleActionTypeRoute: if options.DefaultOptions.RouteOptions.Server == "" && checkServer { return nil, E.New("missing server field") } + case C.RuleActionTypeEvaluate: + if options.DefaultOptions.EvaluateOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } } return NewDefaultDNSRule(ctx, logger, options.DefaultOptions, legacyDNSMode) case C.RuleTypeLogical: @@ -50,10 +54,14 @@ func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DN return nil, err } switch options.LogicalOptions.Action { - case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + case "", C.RuleActionTypeRoute: if options.LogicalOptions.RouteOptions.Server == "" && checkServer { return nil, E.New("missing server field") } + case C.RuleActionTypeEvaluate: + if options.LogicalOptions.EvaluateOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } } return NewLogicalDNSRule(ctx, logger, options.LogicalOptions, legacyDNSMode) default: diff --git a/schema/builder.go b/schema/builder.go new file mode 100644 index 000000000..45e6de72f --- /dev/null +++ b/schema/builder.go @@ -0,0 +1,58 @@ +package schema + +import ( + "context" + "reflect" + + E "github.com/sagernet/sing/common/exceptions" +) + +type Builder interface { + Context() context.Context + Describe(valueType reflect.Type) (*Node, error) + FlattenStruct(node *Node, structType reflect.Type) error + Define(name string, build func() (*Node, error)) (*Node, error) +} + +type Describer interface { + DescribeSchema(builder Builder) (*Node, error) +} + +type UnionVariant struct { + Value any + StructType reflect.Type + TypeOptional bool +} + +func DiscriminatedUnion(builder Builder, discriminatorKey string, discriminatorRequired bool, variants []UnionVariant, buildBase func(variant *Node) error) (*Node, error) { + variantNodes := make([]*Node, 0, len(variants)) + for _, variant := range variants { + variantNode := StrictObject() + if variant.TypeOptional { + stringValue, isString := variant.Value.(string) + if !isString { + return nil, E.New("optional discriminator requires a string value") + } + variantNode.Properties.Put(discriminatorKey, StringEnum(stringValue, "")) + } else { + variantNode.Properties.Put(discriminatorKey, &Node{Const: variant.Value}) + } + if discriminatorRequired || !variant.TypeOptional { + variantNode.Required = append(variantNode.Required, discriminatorKey) + } + if buildBase != nil { + err := buildBase(variantNode) + if err != nil { + return nil, err + } + } + if variant.StructType != nil { + err := builder.FlattenStruct(variantNode, variant.StructType) + if err != nil { + return nil, err + } + } + variantNodes = append(variantNodes, variantNode) + } + return OneOf(variantNodes...), nil +} diff --git a/schema/generate.go b/schema/generate.go new file mode 100644 index 000000000..4353e30dd --- /dev/null +++ b/schema/generate.go @@ -0,0 +1,25 @@ +package schema + +import ( + "context" + stdjson "encoding/json" + "reflect" +) + +func Generate(ctx context.Context, rootType reflect.Type) ([]byte, error) { + g := &generator{ + ctx: ctx, + defs: make(map[string]*Node), + defTypes: make(map[reflect.Type]string), + } + root, err := g.Describe(rootType) + if err != nil { + return nil, err + } + root.Defs = g.sortedDefs() + content, err := stdjson.MarshalIndent(root, "", " ") + if err != nil { + return nil, err + } + return append(content, '\n'), nil +} diff --git a/schema/generator.go b/schema/generator.go new file mode 100644 index 000000000..357392e47 --- /dev/null +++ b/schema/generator.go @@ -0,0 +1,421 @@ +package schema + +import ( + "context" + "encoding" + stdjson "encoding/json" + "reflect" + "slices" + "strconv" + "strings" + + "github.com/sagernet/sing/common/byteformats" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +var ( + jsonUnmarshalerType = reflect.TypeFor[stdjson.Unmarshaler]() + contextUnmarshalerType = reflect.TypeFor[json.ContextUnmarshaler]() + textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() + + durationType = reflect.TypeFor[badoption.Duration]() + addrType = reflect.TypeFor[badoption.Addr]() + prefixType = reflect.TypeFor[badoption.Prefix]() + prefixableType = reflect.TypeFor[badoption.Prefixable]() + httpHeaderType = reflect.TypeFor[badoption.HTTPHeader]() + memoryBytesType = reflect.TypeFor[byteformats.MemoryBytes]() + networkBytesCompatType = reflect.TypeFor[byteformats.NetworkBytesCompat]() +) + +type generator struct { + ctx context.Context + defs map[string]*Node + defTypes map[reflect.Type]string + path []string +} + +func (g *generator) Context() context.Context { + return g.ctx +} + +func (g *generator) Define(name string, build func() (*Node, error)) (*Node, error) { + _, exists := g.defs[name] + if exists { + return RefNode(name), nil + } + g.defs[name] = nil + node, err := build() + if err != nil { + return nil, err + } + g.defs[name] = node + return RefNode(name), nil +} + +func implementationOf[T any](valueType reflect.Type) (T, bool) { + interfaceType := reflect.TypeFor[T]() + if !valueType.Implements(interfaceType) && !reflect.PointerTo(valueType).Implements(interfaceType) { + var zeroValue T + return zeroValue, false + } + return reflect.New(valueType).Interface().(T), true +} + +func (g *generator) Describe(valueType reflect.Type) (*Node, error) { + for valueType.Kind() == reflect.Pointer { + valueType = valueType.Elem() + } + describer, described := implementationOf[Describer](valueType) + if described { + return describer.DescribeSchema(g) + } + switch valueType { + case durationType: + return g.Define("Duration", func() (*Node, error) { + return DurationNode(), nil + }) + case addrType, prefixType, prefixableType: + return StringNode(), nil + case httpHeaderType: + return g.Define("HTTPHeader", func() (*Node, error) { + return &Node{Type: "object", AdditionalProperties: ListableOf(StringNode())}, nil + }) + case memoryBytesType, networkBytesCompatType: + return AnyOf(UnsignedNode(64), StringNode()), nil + } + if isListable(valueType) { + elementNode, err := g.Describe(valueType.Elem()) + if err != nil { + return nil, err + } + return ListableOf(elementNode), nil + } + if isTypedMap(valueType) { + return g.typedMapNode(valueType) + } + pointerType := reflect.PointerTo(valueType) + if pointerType.Implements(jsonUnmarshalerType) || pointerType.Implements(contextUnmarshalerType) { + return nil, E.New("unmapped custom JSON type ", valueType.String(), " at ", strings.Join(g.path, ".")) + } + if pointerType.Implements(textUnmarshalerType) { + return StringNode(), nil + } + switch valueType.Kind() { + case reflect.Struct: + if valueType.Name() == "" { + node := StrictObject() + err := g.FlattenStruct(node, valueType) + if err != nil { + return nil, err + } + return node, nil + } + return g.Define(g.defNameFor(valueType), func() (*Node, error) { + node := StrictObject() + err := g.FlattenStruct(node, valueType) + if err != nil { + return nil, err + } + return node, nil + }) + case reflect.Slice, reflect.Array: + if valueType.Kind() == reflect.Slice && valueType.Elem().Kind() == reflect.Uint8 { + // encoding/json accepts both base64 strings and number arrays. + return AnyOf(StringNode(), &Node{Type: "array", Items: UnsignedNode(8)}), nil + } + elementNode, err := g.Describe(valueType.Elem()) + if err != nil { + return nil, err + } + return &Node{Type: "array", Items: elementNode}, nil + case reflect.Map: + if valueType.Key().Kind() != reflect.String { + return nil, E.New("unsupported map key type ", valueType.String(), " at ", strings.Join(g.path, ".")) + } + valueNode, err := g.Describe(valueType.Elem()) + if err != nil { + return nil, err + } + return &Node{Type: "object", AdditionalProperties: valueNode}, nil + case reflect.Bool: + return BooleanNode(), nil + case reflect.String: + return StringNode(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return IntegerNode(), nil + case reflect.Uint, reflect.Uint64: + return UnsignedNode(64), nil + case reflect.Uint8: + return UnsignedNode(8), nil + case reflect.Uint16: + return UnsignedNode(16), nil + case reflect.Uint32: + return UnsignedNode(32), nil + case reflect.Float32, reflect.Float64: + return &Node{Type: "number"}, nil + default: + return nil, E.New("unsupported kind ", valueType.Kind().String(), " for ", valueType.String(), " at ", strings.Join(g.path, ".")) + } +} + +func (g *generator) defNameFor(fieldType reflect.Type) string { + existingName, loaded := g.defTypes[fieldType] + if loaded { + return existingName + } + name := fieldType.Name() + for otherType, otherName := range g.defTypes { + if otherName == name && otherType != fieldType { + name = pathBase(fieldType.PkgPath()) + "." + name + break + } + } + g.defTypes[fieldType] = name + return name +} + +func pathBase(packagePath string) string { + index := strings.LastIndexByte(packagePath, '/') + if index < 0 { + return packagePath + } + return packagePath[index+1:] +} + +// FlattenStruct merges the JSON fields of structType into node, following the +// same flattening semantics as badjson.MarshallObjects / anonymous embedding. +func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { + for structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return E.New("cannot flatten non-struct type ", structType.String(), " at ", strings.Join(g.path, ".")) + } + for i := range structType.NumField() { + field := structType.Field(i) + if !field.IsExported() && !field.Anonymous { + continue + } + tagValue := field.Tag.Get("json") + tagName, _, _ := strings.Cut(tagValue, ",") + if tagName == "-" { + continue + } + fieldType := field.Type + for fieldType.Kind() == reflect.Pointer { + fieldType = fieldType.Elem() + } + if field.Tag.Get("schema") == "omit" { + continue + } + if field.Anonymous && tagName == "" { + err := g.FlattenStruct(node, fieldType) + if err != nil { + return err + } + continue + } + if tagName == "" { + tagName = field.Name + } + enumTag := field.Tag.Get("enum") + examplesTag := field.Tag.Get("examples") + referenceTag := field.Tag.Get("reference") + g.path = append(g.path, structType.Name()+"."+tagName) + var fieldNode *Node + var err error + if enumTag != "" || examplesTag != "" || referenceTag != "" { + fieldNode, err = taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag) + } else { + fieldNode, err = g.Describe(fieldType) + } + g.path = g.path[:len(g.path)-1] + if err != nil { + return err + } + node.Properties.Put(tagName, fieldNode) + } + return nil +} + +func taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string) (*Node, error) { + elementType := fieldType + for elementType.Kind() == reflect.Pointer { + elementType = elementType.Elem() + } + listable := isListable(fieldType) + plainSlice := !listable && elementType.Kind() == reflect.Slice && elementType.Elem().Kind() == reflect.String + if listable { + elementType = fieldType.Elem() + } else if plainSlice { + elementType = elementType.Elem() + } + var element *Node + var err error + if enumTag != "" { + element, err = taggedValueNode(elementType, strings.Split(enumTag, ",")) + if err != nil { + return nil, err + } + } else { + element, err = taggedValueNode(elementType, nil) + if err != nil { + return nil, err + } + } + if examplesTag != "" { + examples, parseErr := taggedValues(elementType, strings.Split(examplesTag, ",")) + if parseErr != nil { + return nil, parseErr + } + element.Examples = examples + } + if referenceTag != "" { + if elementType.Kind() != reflect.String { + return nil, E.New("reference tags require a string field, got ", fieldType.String()) + } + element.TagReference = referenceTag + } + if listable { + return ListableOf(element), nil + } + if plainSlice { + return &Node{Type: "array", Items: element}, nil + } + return element, nil +} + +func taggedValueNode(fieldType reflect.Type, values []string) (*Node, error) { + switch fieldType.Kind() { + case reflect.String: + node := StringNode() + for _, value := range values { + node.Enum = append(node.Enum, value) + } + return node, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + node := IntegerNode() + enumValues, err := taggedValues(fieldType, values) + if err != nil { + return nil, err + } + node.Enum = enumValues + return node, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + node := UnsignedNode(fieldType.Bits()) + enumValues, err := taggedValues(fieldType, values) + if err != nil { + return nil, err + } + node.Enum = enumValues + return node, nil + default: + node := StringNode() + for _, value := range values { + err := unmarshalTaggedValue(fieldType, value) + if err != nil { + return nil, err + } + node.Enum = append(node.Enum, value) + } + return node, nil + } +} + +func unmarshalTaggedValue(fieldType reflect.Type, value string) error { + err := json.Unmarshal([]byte(strconv.Quote(value)), reflect.New(fieldType).Interface()) + if err != nil { + return E.Cause(err, "unmarshal tagged value ", value, " as ", fieldType.String()) + } + return nil +} + +func taggedValues(fieldType reflect.Type, values []string) ([]any, error) { + result := make([]any, 0, len(values)) + for _, value := range values { + switch fieldType.Kind() { + case reflect.String: + result = append(result, value) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + integerValue, err := strconv.ParseInt(value, 10, fieldType.Bits()) + if err != nil { + return nil, E.Cause(err, "parse enum value ", value, " for ", fieldType.String()) + } + result = append(result, integerValue) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + unsignedValue, err := strconv.ParseUint(value, 10, fieldType.Bits()) + if err != nil { + return nil, E.Cause(err, "parse enum value ", value, " for ", fieldType.String()) + } + result = append(result, unsignedValue) + default: + err := unmarshalTaggedValue(fieldType, value) + if err != nil { + return nil, err + } + result = append(result, value) + } + } + return result, nil +} + +func isListable(fieldType reflect.Type) bool { + return fieldType.Kind() == reflect.Slice && + fieldType.PkgPath() == "github.com/sagernet/sing/common/json/badoption" && + strings.HasPrefix(fieldType.Name(), "Listable[") +} + +func isTypedMap(fieldType reflect.Type) bool { + return fieldType.Kind() == reflect.Struct && + fieldType.PkgPath() == "github.com/sagernet/sing/common/json/badjson" && + strings.HasPrefix(fieldType.Name(), "TypedMap[") +} + +func (g *generator) typedMapNode(fieldType reflect.Type) (*Node, error) { + mapField, found := fieldType.FieldByName("Map") + if !found { + return nil, E.New("unexpected TypedMap layout: missing Map in ", fieldType.String()) + } + rawMapField, found := mapField.Type.FieldByName("rawMap") + if !found { + return nil, E.New("unexpected TypedMap layout: missing rawMap in ", fieldType.String()) + } + keyType := rawMapField.Type.Key() + elementValueField, found := rawMapField.Type.Elem().Elem().FieldByName("Value") + if !found { + return nil, E.New("unexpected TypedMap layout: missing element value in ", fieldType.String()) + } + entryValueField, found := elementValueField.Type.FieldByName("Value") + if !found { + return nil, E.New("unexpected TypedMap layout: missing entry value in ", fieldType.String()) + } + valueNode, err := g.Describe(entryValueField.Type) + if err != nil { + return nil, err + } + node := &Node{Type: "object", AdditionalProperties: valueNode} + if keyType.Kind() != reflect.String || keyType.PkgPath() != "" { + keyNode, keyErr := g.Describe(keyType) + if keyErr != nil { + return nil, keyErr + } + node.PropertyNames = keyNode + } + return node, nil +} + +func (g *generator) sortedDefs() *badjson.TypedMap[string, *Node] { + names := make([]string, 0, len(g.defs)) + for name := range g.defs { + names = append(names, name) + } + slices.Sort(names) + result := new(badjson.TypedMap[string, *Node]) + for _, name := range names { + result.Put(name, g.defs[name]) + } + return result +} diff --git a/schema/schema.go b/schema/schema.go new file mode 100644 index 000000000..9c879d49d --- /dev/null +++ b/schema/schema.go @@ -0,0 +1,105 @@ +package schema + +import ( + "github.com/sagernet/sing/common/json/badjson" +) + +type Node struct { + SchemaURI string `json:"$schema,omitempty"` + ID string `json:"$id,omitempty"` + Ref string `json:"$ref,omitempty"` + Type any `json:"type,omitempty"` + Const any `json:"const,omitempty"` + Enum []any `json:"enum,omitempty"` + Pattern string `json:"pattern,omitempty"` + Minimum *int64 `json:"minimum,omitempty"` + Maximum *uint64 `json:"maximum,omitempty"` + Items *Node `json:"items,omitempty"` + Properties *badjson.TypedMap[string, *Node] `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` + PropertyNames *Node `json:"propertyNames,omitempty"` + AdditionalProperties any `json:"additionalProperties,omitempty"` + UnevaluatedProperties any `json:"unevaluatedProperties,omitempty"` + AllOf []*Node `json:"allOf,omitempty"` + AnyOf []*Node `json:"anyOf,omitempty"` + OneOf []*Node `json:"oneOf,omitempty"` + Deprecated bool `json:"deprecated,omitempty"` + Examples []any `json:"examples,omitempty"` + TagReference string `json:"x-tag-reference,omitempty"` + Defs *badjson.TypedMap[string, *Node] `json:"$defs,omitempty"` +} + +func StrictObject() *Node { + return &Node{ + Type: "object", + Properties: new(badjson.TypedMap[string, *Node]), + AdditionalProperties: false, + } +} + +func LooseObject() *Node { + return &Node{ + Type: "object", + Properties: new(badjson.TypedMap[string, *Node]), + } +} + +func StringNode() *Node { + return &Node{Type: "string"} +} + +func TagReferenceNode(kind string) *Node { + return &Node{Type: "string", TagReference: kind} +} + +func BooleanNode() *Node { + return &Node{Type: "boolean"} +} + +func IntegerNode() *Node { + return &Node{Type: "integer"} +} + +func UnsignedNode(bits int) *Node { + minimumValue := int64(0) + node := &Node{Type: "integer", Minimum: &minimumValue} + if bits < 64 { + maximumValue := uint64(1)<