mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Add JSON schema support
This commit is contained in:
@@ -14,7 +14,7 @@ PREFIX ?= $(shell go env GOPATH)
|
|||||||
SING_FFI ?= sing-ffi
|
SING_FFI ?= sing-ffi
|
||||||
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
|
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build schema
|
||||||
|
|
||||||
build:
|
build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
@@ -32,6 +32,9 @@ ci_build:
|
|||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
||||||
|
|
||||||
|
schema:
|
||||||
|
go run -ldflags "$(LDFLAGS_SHARED)" --tags "$(TAGS)" $(MAIN) schema -o docs/schema.json
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package certificate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
func (m *Registry) CreateOptions(providerType string) (any, bool) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package endpoint
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package inbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
r.access.Lock()
|
r.access.Lock()
|
||||||
defer r.access.Unlock()
|
defer r.access.Unlock()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -94,7 +94,9 @@ func TestNewAppleSessionConfig(t *testing.T) {
|
|||||||
options: option.HTTPClientOptions{
|
options: option.HTTPClientOptions{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
DialerOptions: option.DialerOptions{
|
DialerOptions: option.DialerOptions{
|
||||||
ConnectTimeout: badoption.Duration(2 * time.Second),
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
|
ConnectTimeout: badoption.Duration(2 * time.Second),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
|
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
|
||||||
TLS: &option.OutboundTLSOptions{
|
TLS: &option.OutboundTLSOptions{
|
||||||
|
|||||||
+20
-4
@@ -1623,8 +1623,10 @@ func dnsRuleActionDisablesLegacyDNSMode(action option.DNSRuleAction) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch action.Action {
|
switch action.Action {
|
||||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
case "", C.RuleActionTypeRoute:
|
||||||
return action.RouteOptions.DisableOptimisticCache || action.RouteOptions.Speculative
|
return action.RouteOptions.DisableOptimisticCache || action.RouteOptions.Speculative
|
||||||
|
case C.RuleActionTypeEvaluate:
|
||||||
|
return action.EvaluateOptions.DisableOptimisticCache || action.EvaluateOptions.Speculative
|
||||||
case C.RuleActionTypeRouteOptions:
|
case C.RuleActionTypeRouteOptions:
|
||||||
return action.RouteOptionsOptions.DisableOptimisticCache
|
return action.RouteOptionsOptions.DisableOptimisticCache
|
||||||
default:
|
default:
|
||||||
@@ -1634,8 +1636,10 @@ func dnsRuleActionDisablesLegacyDNSMode(action option.DNSRuleAction) bool {
|
|||||||
|
|
||||||
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
||||||
switch action.Action {
|
switch action.Action {
|
||||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
case "", C.RuleActionTypeRoute:
|
||||||
return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS
|
return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS
|
||||||
|
case C.RuleActionTypeEvaluate:
|
||||||
|
return C.DomainStrategy(action.EvaluateOptions.Strategy) != C.DomainStrategyAsIS
|
||||||
case C.RuleActionTypeRouteOptions:
|
case C.RuleActionTypeRouteOptions:
|
||||||
return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS
|
return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS
|
||||||
default:
|
default:
|
||||||
@@ -1663,8 +1667,14 @@ func dnsRuleActionType(rule option.DNSRule) string {
|
|||||||
func dnsRuleActionServer(rule option.DNSRule) string {
|
func dnsRuleActionServer(rule option.DNSRule) string {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case "", C.RuleTypeDefault:
|
case "", C.RuleTypeDefault:
|
||||||
|
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
|
||||||
|
return rule.DefaultOptions.EvaluateOptions.Server
|
||||||
|
}
|
||||||
return rule.DefaultOptions.RouteOptions.Server
|
return rule.DefaultOptions.RouteOptions.Server
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
|
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
|
||||||
|
return rule.LogicalOptions.EvaluateOptions.Server
|
||||||
|
}
|
||||||
return rule.LogicalOptions.RouteOptions.Server
|
return rule.LogicalOptions.RouteOptions.Server
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -1674,9 +1684,9 @@ func dnsRuleActionServer(rule option.DNSRule) string {
|
|||||||
func dnsRuleActionEvaluateTag(rule option.DNSRule) string {
|
func dnsRuleActionEvaluateTag(rule option.DNSRule) string {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case "", C.RuleTypeDefault:
|
case "", C.RuleTypeDefault:
|
||||||
return rule.DefaultOptions.RouteOptions.Tag
|
return rule.DefaultOptions.EvaluateOptions.Tag
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
return rule.LogicalOptions.RouteOptions.Tag
|
return rule.LogicalOptions.EvaluateOptions.Tag
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -1685,8 +1695,14 @@ func dnsRuleActionEvaluateTag(rule option.DNSRule) string {
|
|||||||
func dnsRuleActionSpeculative(rule option.DNSRule) bool {
|
func dnsRuleActionSpeculative(rule option.DNSRule) bool {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case "", C.RuleTypeDefault:
|
case "", C.RuleTypeDefault:
|
||||||
|
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
|
||||||
|
return rule.DefaultOptions.EvaluateOptions.Speculative
|
||||||
|
}
|
||||||
return rule.DefaultOptions.RouteOptions.Speculative
|
return rule.DefaultOptions.RouteOptions.Speculative
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
|
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
|
||||||
|
return rule.LogicalOptions.EvaluateOptions.Speculative
|
||||||
|
}
|
||||||
return rule.LogicalOptions.RouteOptions.Speculative
|
return rule.LogicalOptions.RouteOptions.Speculative
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ func evaluateRule(server string, tag string, speculative bool) option.DNSRule {
|
|||||||
DefaultOptions: option.DefaultDNSRule{
|
DefaultOptions: option.DefaultDNSRule{
|
||||||
DNSRuleAction: option.DNSRuleAction{
|
DNSRuleAction: option.DNSRuleAction{
|
||||||
Action: C.RuleActionTypeEvaluate,
|
Action: C.RuleActionTypeEvaluate,
|
||||||
RouteOptions: option.DNSRouteActionOptions{
|
EvaluateOptions: option.DNSEvaluateActionOptions{
|
||||||
Server: server,
|
Server: server,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
Speculative: speculative,
|
Speculative: speculative,
|
||||||
|
|||||||
@@ -328,8 +328,10 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServ
|
|||||||
return nil, E.New("link has no DNS servers configured")
|
return nil, E.New("link has no DNS servers configured")
|
||||||
}
|
}
|
||||||
serverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{
|
serverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{
|
||||||
BindInterface: defaultInterface.Name,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
UDPFragmentDefault: true,
|
BindInterface: defaultInterface.Name,
|
||||||
|
UDPFragmentDefault: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
func (r *TransportRegistry) CreateOptions(transportType string) (any, bool) {
|
||||||
r.access.Lock()
|
r.access.Lock()
|
||||||
defer r.access.Unlock()
|
defer r.access.Unlock()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ sing-box uses JSON for configuration files.
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"$schema": "https://sing-box.sagernet.org/schema.json",
|
||||||
"log": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
"ntp": {},
|
||||||
@@ -25,6 +26,7 @@ sing-box uses JSON for configuration files.
|
|||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|----------------|---------------------------------|
|
|----------------|---------------------------------|
|
||||||
|
| `$schema` | [JSON Schema](./schema/) |
|
||||||
| `log` | [Log](./log/) |
|
| `log` | [Log](./log/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
| `ntp` | [NTP](./ntp/) |
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"$schema": "https://sing-box.sagernet.org/schema.json",
|
||||||
"log": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
"ntp": {},
|
||||||
@@ -25,6 +26,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|----------------|------------------------|
|
|----------------|------------------------|
|
||||||
|
| `$schema` | [JSON Schema](./schema/) |
|
||||||
| `log` | [日志](./log/) |
|
| `log` | [日志](./log/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
| `ntp` | [NTP](./ntp/) |
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
```
|
||||||
+17317
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
"github.com/sagernet/sing-box/common/networkquality"
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
"github.com/sagernet/sing-box/common/stun"
|
||||||
"github.com/sagernet/sing-box/daemon"
|
"github.com/sagernet/sing-box/daemon"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox"
|
"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"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -38,6 +42,14 @@ func (s *applicationService) FormatConfig(ctx context.Context, request *ConfigCo
|
|||||||
return &ConfigContent{Content: content}, nil
|
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) {
|
func (s *applicationService) EncodeProfile(ctx context.Context, request *ProfileContent) (*ProfileData, error) {
|
||||||
content := libbox.ProfileContent{
|
content := libbox.ProfileContent{
|
||||||
Name: request.Name,
|
Name: request.Name,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
unsafe "unsafe"
|
unsafe "unsafe"
|
||||||
|
|
||||||
daemon "github.com/sagernet/sing-box/daemon"
|
daemon "github.com/sagernet/sing-box/daemon"
|
||||||
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
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" +
|
"\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" +
|
"\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" +
|
"\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" +
|
"\x12ApplicationService\x12?\n" +
|
||||||
"\vCheckConfig\x12\x16.desktop.ConfigContent\x1a\x16.google.protobuf.Empty\"\x00\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" +
|
"\rEncodeProfile\x12\x17.desktop.ProfileContent\x1a\x14.desktop.ProfileData\"\x00\x12@\n" +
|
||||||
"\rDecodeProfile\x12\x14.desktop.ProfileData\x1a\x17.desktop.ProfileContent\"\x00\x12H\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" +
|
"\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
|
27, // 28: desktop.DesktopService.SetInsecureModeEnabled:input_type -> desktop.SetInsecureModeEnabledRequest
|
||||||
9, // 29: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent
|
9, // 29: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent
|
||||||
9, // 30: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent
|
9, // 30: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent
|
||||||
10, // 31: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent
|
30, // 31: desktop.ApplicationService.GenerateConfigSchema:input_type -> google.protobuf.Empty
|
||||||
11, // 32: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData
|
10, // 32: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent
|
||||||
3, // 33: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest
|
11, // 33: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData
|
||||||
4, // 34: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest
|
3, // 34: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest
|
||||||
5, // 35: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest
|
4, // 35: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest
|
||||||
6, // 36: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo
|
5, // 36: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest
|
||||||
30, // 37: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty
|
6, // 37: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo
|
||||||
30, // 38: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty
|
30, // 38: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty
|
||||||
30, // 39: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty
|
30, // 39: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty
|
||||||
12, // 40: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo
|
30, // 40: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty
|
||||||
30, // 41: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty
|
12, // 41: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo
|
||||||
13, // 42: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList
|
30, // 42: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty
|
||||||
17, // 43: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent
|
13, // 43: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList
|
||||||
30, // 44: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty
|
17, // 44: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent
|
||||||
19, // 45: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive
|
30, // 45: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty
|
||||||
30, // 46: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty
|
19, // 46: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive
|
||||||
30, // 47: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty
|
30, // 47: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty
|
||||||
20, // 48: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList
|
30, // 48: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty
|
||||||
24, // 49: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent
|
20, // 49: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList
|
||||||
30, // 50: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty
|
24, // 50: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent
|
||||||
19, // 51: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive
|
30, // 51: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty
|
||||||
30, // 52: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty
|
19, // 52: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive
|
||||||
30, // 53: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty
|
30, // 53: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty
|
||||||
29, // 54: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse
|
30, // 54: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty
|
||||||
26, // 55: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings
|
29, // 55: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse
|
||||||
30, // 56: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty
|
26, // 56: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings
|
||||||
30, // 57: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty
|
30, // 57: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty
|
||||||
9, // 58: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent
|
30, // 58: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty
|
||||||
11, // 59: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData
|
9, // 59: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent
|
||||||
10, // 60: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent
|
9, // 60: desktop.ApplicationService.GenerateConfigSchema:output_type -> desktop.ConfigContent
|
||||||
30, // 61: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty
|
11, // 61: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData
|
||||||
31, // 62: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
|
10, // 62: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent
|
||||||
32, // 63: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress
|
30, // 63: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty
|
||||||
36, // [36:64] is the sub-list for method output_type
|
31, // 64: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
|
||||||
8, // [8:36] is the sub-list for method input_type
|
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 type_name
|
||||||
8, // [8:8] is the sub-list for extension extendee
|
8, // [8:8] is the sub-list for extension extendee
|
||||||
0, // [0:8] is the sub-list for field type_name
|
0, // [0:8] is the sub-list for field type_name
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ service DesktopService {
|
|||||||
service ApplicationService {
|
service ApplicationService {
|
||||||
rpc CheckConfig(ConfigContent) returns (google.protobuf.Empty) {}
|
rpc CheckConfig(ConfigContent) returns (google.protobuf.Empty) {}
|
||||||
rpc FormatConfig(ConfigContent) returns (ConfigContent) {}
|
rpc FormatConfig(ConfigContent) returns (ConfigContent) {}
|
||||||
|
rpc GenerateConfigSchema(google.protobuf.Empty) returns (ConfigContent) {}
|
||||||
rpc EncodeProfile(ProfileContent) returns (ProfileData) {}
|
rpc EncodeProfile(ProfileContent) returns (ProfileData) {}
|
||||||
rpc DecodeProfile(ProfileData) returns (ProfileContent) {}
|
rpc DecodeProfile(ProfileData) returns (ProfileContent) {}
|
||||||
rpc ArchiveReport(ArchiveReportRequest) returns (google.protobuf.Empty) {}
|
rpc ArchiveReport(ArchiveReportRequest) returns (google.protobuf.Empty) {}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
|
|
||||||
daemon "github.com/sagernet/sing-box/daemon"
|
daemon "github.com/sagernet/sing-box/daemon"
|
||||||
|
|
||||||
grpc "google.golang.org/grpc"
|
grpc "google.golang.org/grpc"
|
||||||
codes "google.golang.org/grpc/codes"
|
codes "google.golang.org/grpc/codes"
|
||||||
status "google.golang.org/grpc/status"
|
status "google.golang.org/grpc/status"
|
||||||
@@ -901,6 +900,7 @@ var DesktopService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
const (
|
const (
|
||||||
ApplicationService_CheckConfig_FullMethodName = "/desktop.ApplicationService/CheckConfig"
|
ApplicationService_CheckConfig_FullMethodName = "/desktop.ApplicationService/CheckConfig"
|
||||||
ApplicationService_FormatConfig_FullMethodName = "/desktop.ApplicationService/FormatConfig"
|
ApplicationService_FormatConfig_FullMethodName = "/desktop.ApplicationService/FormatConfig"
|
||||||
|
ApplicationService_GenerateConfigSchema_FullMethodName = "/desktop.ApplicationService/GenerateConfigSchema"
|
||||||
ApplicationService_EncodeProfile_FullMethodName = "/desktop.ApplicationService/EncodeProfile"
|
ApplicationService_EncodeProfile_FullMethodName = "/desktop.ApplicationService/EncodeProfile"
|
||||||
ApplicationService_DecodeProfile_FullMethodName = "/desktop.ApplicationService/DecodeProfile"
|
ApplicationService_DecodeProfile_FullMethodName = "/desktop.ApplicationService/DecodeProfile"
|
||||||
ApplicationService_ArchiveReport_FullMethodName = "/desktop.ApplicationService/ArchiveReport"
|
ApplicationService_ArchiveReport_FullMethodName = "/desktop.ApplicationService/ArchiveReport"
|
||||||
@@ -914,6 +914,7 @@ const (
|
|||||||
type ApplicationServiceClient interface {
|
type ApplicationServiceClient interface {
|
||||||
CheckConfig(ctx context.Context, in *ConfigContent, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CheckConfig(ctx context.Context, in *ConfigContent, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
FormatConfig(ctx context.Context, in *ConfigContent, opts ...grpc.CallOption) (*ConfigContent, 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)
|
EncodeProfile(ctx context.Context, in *ProfileContent, opts ...grpc.CallOption) (*ProfileData, error)
|
||||||
DecodeProfile(ctx context.Context, in *ProfileData, opts ...grpc.CallOption) (*ProfileContent, error)
|
DecodeProfile(ctx context.Context, in *ProfileData, opts ...grpc.CallOption) (*ProfileContent, error)
|
||||||
ArchiveReport(ctx context.Context, in *ArchiveReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, 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
|
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) {
|
func (c *applicationServiceClient) EncodeProfile(ctx context.Context, in *ProfileContent, opts ...grpc.CallOption) (*ProfileData, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ProfileData)
|
out := new(ProfileData)
|
||||||
@@ -1023,6 +1034,7 @@ type ApplicationService_StartStandaloneSTUNTestClient = grpc.ServerStreamingClie
|
|||||||
type ApplicationServiceServer interface {
|
type ApplicationServiceServer interface {
|
||||||
CheckConfig(context.Context, *ConfigContent) (*emptypb.Empty, error)
|
CheckConfig(context.Context, *ConfigContent) (*emptypb.Empty, error)
|
||||||
FormatConfig(context.Context, *ConfigContent) (*ConfigContent, error)
|
FormatConfig(context.Context, *ConfigContent) (*ConfigContent, error)
|
||||||
|
GenerateConfigSchema(context.Context, *emptypb.Empty) (*ConfigContent, error)
|
||||||
EncodeProfile(context.Context, *ProfileContent) (*ProfileData, error)
|
EncodeProfile(context.Context, *ProfileContent) (*ProfileData, error)
|
||||||
DecodeProfile(context.Context, *ProfileData) (*ProfileContent, error)
|
DecodeProfile(context.Context, *ProfileData) (*ProfileContent, error)
|
||||||
ArchiveReport(context.Context, *ArchiveReportRequest) (*emptypb.Empty, 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")
|
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) {
|
func (UnimplementedApplicationServiceServer) EncodeProfile(context.Context, *ProfileContent) (*ProfileData, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method EncodeProfile not implemented")
|
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)
|
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) {
|
func _ApplicationService_EncodeProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ProfileContent)
|
in := new(ProfileContent)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -1213,6 +1247,10 @@ var ApplicationService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "FormatConfig",
|
MethodName: "FormatConfig",
|
||||||
Handler: _ApplicationService_FormatConfig_Handler,
|
Handler: _ApplicationService_FormatConfig_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GenerateConfigSchema",
|
||||||
|
Handler: _ApplicationService_GenerateConfigSchema_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "EncodeProfile",
|
MethodName: "EncodeProfile",
|
||||||
Handler: _ApplicationService_EncodeProfile_Handler,
|
Handler: _ApplicationService_EncodeProfile_Handler,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
box "github.com/sagernet/sing-box"
|
box "github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
tun "github.com/sagernet/sing-tun"
|
tun "github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -241,6 +243,14 @@ func (s *interfaceMonitorStub) MyInterfaces() []string {
|
|||||||
return nil
|
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) {
|
func FormatConfig(configContent string) (*StringBox, error) {
|
||||||
options, err := parseConfig(baseContext(nil), configContent)
|
options, err := parseConfig(baseContext(nil), configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ nav:
|
|||||||
- AnyTLS client metadata: manual/misc/anytls-client-metadata.md
|
- AnyTLS client metadata: manual/misc/anytls-client-metadata.md
|
||||||
- Configuration:
|
- Configuration:
|
||||||
- configuration/index.md
|
- configuration/index.md
|
||||||
|
- JSON Schema: configuration/schema.md
|
||||||
- Log:
|
- Log:
|
||||||
- configuration/log/index.md
|
- configuration/log/index.md
|
||||||
- DNS:
|
- DNS:
|
||||||
|
|||||||
+23
-5
@@ -1,9 +1,11 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -23,21 +25,25 @@ type ACMECertificateProviderOptions struct {
|
|||||||
AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"`
|
AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"`
|
||||||
ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"`
|
ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"`
|
||||||
DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,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"`
|
Profile string `json:"profile,omitempty"`
|
||||||
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _ACMEProviderDNS01ChallengeOptions struct {
|
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"`
|
TTL badoption.Duration `json:"ttl,omitempty"`
|
||||||
PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"`
|
PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"`
|
||||||
PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"`
|
PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"`
|
||||||
Resolvers badoption.Listable[string] `json:"resolvers,omitempty"`
|
Resolvers badoption.Listable[string] `json:"resolvers,omitempty"`
|
||||||
OverrideDomain string `json:"override_domain,omitempty"`
|
OverrideDomain string `json:"override_domain,omitempty"`
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
|
|
||||||
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
|
|
||||||
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions
|
type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions
|
||||||
@@ -80,6 +86,14 @@ func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
return badjson.UnmarshallExcluded(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o), v)
|
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
|
type ACMEKeyType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -105,3 +119,7 @@ func (t *ACMEKeyType) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t ACMEKeyType) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringEnum("", "ed25519", "p256", "p384", "rsa2048", "rsa4096"), nil
|
||||||
|
}
|
||||||
|
|||||||
+13
-1
@@ -1,6 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
@@ -8,7 +11,7 @@ import (
|
|||||||
type APIServiceOptions struct {
|
type APIServiceOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Secret string `json:"secret,omitempty"`
|
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"`
|
AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"`
|
||||||
Dashboard *APIDashboardOptions `json:"dashboard,omitempty"`
|
Dashboard *APIDashboardOptions `json:"dashboard,omitempty"`
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
@@ -48,3 +51,12 @@ func (o *APIDashboardOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
return json.UnmarshalDisallowUnknownFields(bytes, (*_APIDashboardOptions)(o))
|
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
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ type CCMServiceOptions struct {
|
|||||||
CredentialPath string `json:"credential_path,omitempty"`
|
CredentialPath string `json:"credential_path,omitempty"`
|
||||||
Users []CCMUser `json:"users,omitempty"`
|
Users []CCMUser `json:"users,omitempty"`
|
||||||
Headers badoption.HTTPHeader `json:"headers,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"`
|
UsagesPath string `json:"usages_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -1,13 +1,16 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
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"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
type _CertificateOptions struct {
|
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"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"`
|
CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"`
|
||||||
CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"`
|
CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"`
|
||||||
@@ -34,3 +37,12 @@ func (o *CertificateOptions) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CertificateProviderOptionsRegistry interface {
|
type CertificateProviderOptionsRegistry interface {
|
||||||
|
OptionTypes() []string
|
||||||
CreateOptions(providerType string) (any, bool)
|
CreateOptions(providerType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +48,16 @@ func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content
|
|||||||
return nil
|
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 {
|
type CertificateProviderOptions struct {
|
||||||
Tag string `json:"-"`
|
Tag string `json:"-"`
|
||||||
Type string `json:"-"`
|
Type string `json:"-"`
|
||||||
@@ -98,3 +110,17 @@ func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, c
|
|||||||
func (o *CertificateProviderOptions) IsShared() bool {
|
func (o *CertificateProviderOptions) IsShared() bool {
|
||||||
return o.Tag != ""
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import "github.com/sagernet/sing/common/json/badoption"
|
|||||||
type CloudflaredInboundOptions struct {
|
type CloudflaredInboundOptions struct {
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
HighAvailabilityConnections int `json:"ha_connections,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"`
|
PostQuantum bool `json:"post_quantum,omitempty"`
|
||||||
EdgeIPVersion int `json:"edge_ip_version,omitempty"`
|
EdgeIPVersion int `json:"edge_ip_version,omitempty" enum:"0,4,6"`
|
||||||
DatagramVersion string `json:"datagram_version,omitempty"`
|
DatagramVersion string `json:"datagram_version,omitempty" enum:"v2,v3"`
|
||||||
GracePeriod badoption.Duration `json:"grace_period,omitempty"`
|
GracePeriod badoption.Duration `json:"grace_period,omitempty"`
|
||||||
Region string `json:"region,omitempty"`
|
Region string `json:"region,omitempty"`
|
||||||
ControlDialer DialerOptions `json:"control_dialer,omitempty"`
|
ControlDialer DialerOptions `json:"control_dialer,omitempty"`
|
||||||
|
|||||||
+3
-3
@@ -17,11 +17,11 @@ type DirectInboundOptions struct {
|
|||||||
type _DirectOutboundOptions struct {
|
type _DirectOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
// Deprecated: Use Route Action instead
|
// Deprecated: Use Route Action instead
|
||||||
OverrideAddress string `json:"override_address,omitempty"`
|
OverrideAddress string `json:"override_address,omitempty" schema:"omit"`
|
||||||
// Deprecated: Use Route Action instead
|
// Deprecated: Use Route Action instead
|
||||||
OverridePort uint16 `json:"override_port,omitempty"`
|
OverridePort uint16 `json:"override_port,omitempty" schema:"omit"`
|
||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
ProxyProtocol uint8 `json:"proxy_protocol,omitempty"`
|
ProxyProtocol uint8 `json:"proxy_protocol,omitempty" schema:"omit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectOutboundOptions _DirectOutboundOptions
|
type DirectOutboundOptions _DirectOutboundOptions
|
||||||
|
|||||||
+37
-4
@@ -3,8 +3,10 @@ package option
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -16,7 +18,7 @@ import (
|
|||||||
type RawDNSOptions struct {
|
type RawDNSOptions struct {
|
||||||
Servers []DNSServerOptions `json:"servers,omitempty"`
|
Servers []DNSServerOptions `json:"servers,omitempty"`
|
||||||
Rules []DNSRule `json:"rules,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"`
|
ReverseMapping bool `json:"reverse_mapping,omitempty"`
|
||||||
DNSClientOptions
|
DNSClientOptions
|
||||||
}
|
}
|
||||||
@@ -46,12 +48,23 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e
|
|||||||
return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
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 {
|
type DNSClientOptions struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Timeout badoption.Duration `json:"timeout,omitempty"`
|
Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
DisableExpire bool `json:"disable_expire,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"`
|
CacheCapacity uint32 `json:"cache_capacity,omitempty"`
|
||||||
Optimistic *OptimisticDNSOptions `json:"optimistic,omitempty"`
|
Optimistic *OptimisticDNSOptions `json:"optimistic,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,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))
|
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 {
|
type DNSTransportOptionsRegistry interface {
|
||||||
|
OptionTypes() []string
|
||||||
CreateOptions(transportType string) (any, bool)
|
CreateOptions(transportType string) (any, bool)
|
||||||
}
|
}
|
||||||
type _DNSServerOptions struct {
|
type _DNSServerOptions struct {
|
||||||
@@ -122,6 +145,16 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
|
|||||||
return nil
|
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 {
|
type DNSServerAddressOptions struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
ServerPort uint16 `json:"server_port,omitempty"`
|
ServerPort uint16 `json:"server_port,omitempty"`
|
||||||
@@ -176,8 +209,8 @@ type RemoteHTTPSDNSServerOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeIPDNSServerOptions struct {
|
type FakeIPDNSServerOptions struct {
|
||||||
Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"`
|
Inet4Range *badoption.Prefix `json:"inet4_range,omitempty" examples:"198.18.0.0/15"`
|
||||||
Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"`
|
Inet6Range *badoption.Prefix `json:"inet6_range,omitempty" examples:"fc00::/18"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DHCPDNSServerOptions struct {
|
type DHCPDNSServerOptions struct {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -51,6 +54,43 @@ func (r *DNSRCode) Build() int {
|
|||||||
return int(*r)
|
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 {
|
type DNSRecordOptions struct {
|
||||||
dns.RR
|
dns.RR
|
||||||
fromBase64 bool
|
fromBase64 bool
|
||||||
@@ -123,3 +163,7 @@ func (o DNSRecordOptions) Match(record dns.RR) bool {
|
|||||||
}
|
}
|
||||||
return dns.IsDuplicate(o.RR, record)
|
return dns.IsDuplicate(o.RR, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o DNSRecordOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringNode(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import (
|
|||||||
|
|
||||||
type stubDNSTransportOptionsRegistry struct{}
|
type stubDNSTransportOptionsRegistry struct{}
|
||||||
|
|
||||||
|
func (stubDNSTransportOptionsRegistry) OptionTypes() []string {
|
||||||
|
return []string{C.DNSTypeUDP, C.DNSTypeFakeIP}
|
||||||
|
}
|
||||||
|
|
||||||
func (stubDNSTransportOptionsRegistry) CreateOptions(transportType string) (any, bool) {
|
func (stubDNSTransportOptionsRegistry) CreateOptions(transportType string) (any, bool) {
|
||||||
switch transportType {
|
switch transportType {
|
||||||
case C.DNSTypeUDP:
|
case C.DNSTypeUDP:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type EndpointOptionsRegistry interface {
|
type EndpointOptionsRegistry interface {
|
||||||
|
OptionTypes() []string
|
||||||
CreateOptions(endpointType string) (any, bool)
|
CreateOptions(endpointType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,3 +47,13 @@ func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) err
|
|||||||
h.Options = options
|
h.Options = options
|
||||||
return nil
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type CacheFileOptions struct {
|
|||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
CacheID string `json:"cache_id,omitempty"`
|
CacheID string `json:"cache_id,omitempty"`
|
||||||
StoreFakeIP bool `json:"store_fakeip,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"`
|
RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
|
||||||
StoreDNS bool `json:"store_dns,omitempty"`
|
StoreDNS bool `json:"store_dns,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ type ClashAPIOptions struct {
|
|||||||
ExternalController string `json:"external_controller,omitempty"`
|
ExternalController string `json:"external_controller,omitempty"`
|
||||||
ExternalUI string `json:"external_ui,omitempty"`
|
ExternalUI string `json:"external_ui,omitempty"`
|
||||||
ExternalUIDownloadURL string `json:"external_ui_download_url,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"`
|
Secret string `json:"secret,omitempty"`
|
||||||
DefaultMode string `json:"default_mode,omitempty"`
|
DefaultMode string `json:"default_mode,omitempty"`
|
||||||
ModeList []string `json:"-"`
|
ModeList []string `json:"-"`
|
||||||
@@ -31,15 +31,15 @@ type ClashAPIOptions struct {
|
|||||||
AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"`
|
AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"`
|
||||||
|
|
||||||
// Deprecated: migrated to global cache file
|
// 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
|
// 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
|
// 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
|
// 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
|
// Deprecated: migrated to global cache file
|
||||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
StoreFakeIP bool `json:"store_fakeip,omitempty" schema:"omit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayAPIOptions struct {
|
type V2RayAPIOptions struct {
|
||||||
|
|||||||
+3
-3
@@ -3,13 +3,13 @@ package option
|
|||||||
import "github.com/sagernet/sing/common/json/badoption"
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
type SelectorOutboundOptions struct {
|
type SelectorOutboundOptions struct {
|
||||||
Outbounds []string `json:"outbounds"`
|
Outbounds []string `json:"outbounds" reference:"outbound"`
|
||||||
Default string `json:"default,omitempty"`
|
Default string `json:"default,omitempty" reference:"outbound"`
|
||||||
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
|
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type URLTestOutboundOptions struct {
|
type URLTestOutboundOptions struct {
|
||||||
Outbounds []string `json:"outbounds"`
|
Outbounds []string `json:"outbounds" reference:"outbound"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Interval badoption.Duration `json:"interval,omitempty"`
|
Interval badoption.Duration `json:"interval,omitempty"`
|
||||||
Tolerance uint16 `json:"tolerance,omitempty"`
|
Tolerance uint16 `json:"tolerance,omitempty"`
|
||||||
|
|||||||
+33
-2
@@ -3,6 +3,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common/byteformats"
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -26,8 +27,8 @@ type QUICOptions struct {
|
|||||||
|
|
||||||
type _HTTPClientOptions struct {
|
type _HTTPClientOptions struct {
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
Engine string `json:"engine,omitempty"`
|
Engine string `json:"engine,omitempty" enum:"go,apple"`
|
||||||
Version int `json:"version,omitempty"`
|
Version int `json:"version,omitempty" enum:"0,1,2,3"`
|
||||||
DisableVersionFallback bool `json:"disable_version_fallback,omitempty"`
|
DisableVersionFallback bool `json:"disable_version_fallback,omitempty"`
|
||||||
Headers badoption.HTTPHeader `json:"headers,omitempty"`
|
Headers badoption.HTTPHeader `json:"headers,omitempty"`
|
||||||
HTTP2Options HTTP2Options `json:"-"`
|
HTTP2Options HTTP2Options `json:"-"`
|
||||||
@@ -125,3 +126,33 @@ func httpClientVariant(options _HTTPClientOptions) any {
|
|||||||
return nil
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+7
-7
@@ -14,13 +14,13 @@ type HysteriaInboundOptions struct {
|
|||||||
Obfs string `json:"obfs,omitempty"`
|
Obfs string `json:"obfs,omitempty"`
|
||||||
Users []HysteriaUser `json:"users,omitempty"`
|
Users []HysteriaUser `json:"users,omitempty"`
|
||||||
// Deprecated: use QUIC fields instead
|
// 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
|
// 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
|
// 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
|
// Deprecated: use QUIC fields instead
|
||||||
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty" schema:"omit"`
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
QUICOptions
|
QUICOptions
|
||||||
}
|
}
|
||||||
@@ -44,11 +44,11 @@ type HysteriaOutboundOptions struct {
|
|||||||
Auth []byte `json:"auth,omitempty"`
|
Auth []byte `json:"auth,omitempty"`
|
||||||
AuthString string `json:"auth_str,omitempty"`
|
AuthString string `json:"auth_str,omitempty"`
|
||||||
// Deprecated: use QUIC fields instead
|
// 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
|
// Deprecated: use QUIC fields instead
|
||||||
ReceiveWindow uint64 `json:"recv_window,omitempty"`
|
ReceiveWindow uint64 `json:"recv_window,omitempty" schema:"omit"`
|
||||||
// Deprecated: use QUIC fields instead
|
// 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"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
QUICOptions
|
QUICOptions
|
||||||
|
|||||||
+29
-5
@@ -2,8 +2,10 @@ package option
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -20,7 +22,7 @@ type Hysteria2InboundOptions struct {
|
|||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
QUICOptions
|
QUICOptions
|
||||||
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
|
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"`
|
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||||
Realm *Hysteria2InboundRealm `json:"realm,omitempty"`
|
Realm *Hysteria2InboundRealm `json:"realm,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -30,7 +32,7 @@ type Hysteria2Realm struct {
|
|||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
RealmID string `json:"realm_id"`
|
RealmID string `json:"realm_id"`
|
||||||
STUNServers badoption.Listable[string] `json:"stun_servers"`
|
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"`
|
PortMapping *Hysteria2RealmPortMapping `json:"port_mapping,omitempty"`
|
||||||
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -52,7 +54,7 @@ type Hysteria2ObfsGecko struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _Hysteria2Obfs struct {
|
type _Hysteria2Obfs struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" enum:"salamander,gecko"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
GeckoOptions Hysteria2ObfsGecko `json:"-"`
|
GeckoOptions Hysteria2ObfsGecko `json:"-"`
|
||||||
}
|
}
|
||||||
@@ -93,13 +95,23 @@ func (o *Hysteria2Obfs) UnmarshalJSON(bytes []byte) error {
|
|||||||
return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Obfs)(o), v)
|
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 {
|
type Hysteria2User struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _Hysteria2Masquerade struct {
|
type _Hysteria2Masquerade struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" enum:"file,proxy,string"`
|
||||||
FileOptions Hysteria2MasqueradeFile `json:"-"`
|
FileOptions Hysteria2MasqueradeFile `json:"-"`
|
||||||
ProxyOptions Hysteria2MasqueradeProxy `json:"-"`
|
ProxyOptions Hysteria2MasqueradeProxy `json:"-"`
|
||||||
StringOptions Hysteria2MasqueradeString `json:"-"`
|
StringOptions Hysteria2MasqueradeString `json:"-"`
|
||||||
@@ -160,6 +172,18 @@ func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error {
|
|||||||
return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v)
|
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 {
|
type Hysteria2MasqueradeFile struct {
|
||||||
Directory string `json:"directory"`
|
Directory string `json:"directory"`
|
||||||
}
|
}
|
||||||
@@ -188,7 +212,7 @@ type Hysteria2OutboundOptions struct {
|
|||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
QUICOptions
|
QUICOptions
|
||||||
BBRProfile string `json:"bbr_profile,omitempty"`
|
BBRProfile string `json:"bbr_profile,omitempty" enum:"standard,conservative,aggressive"`
|
||||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||||
Realm *Hysteria2Realm `json:"realm,omitempty"`
|
Realm *Hysteria2Realm `json:"realm,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-10
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -12,6 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type InboundOptionsRegistry interface {
|
type InboundOptionsRegistry interface {
|
||||||
|
OptionTypes() []string
|
||||||
CreateOptions(outboundType string) (any, bool)
|
CreateOptions(outboundType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +57,23 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
|||||||
return nil
|
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
|
// Deprecated: Use rule action instead
|
||||||
type InboundOptions struct {
|
type InboundOptions struct {
|
||||||
SniffEnabled bool `json:"sniff,omitempty"`
|
SniffEnabled bool `json:"sniff,omitempty" schema:"omit"`
|
||||||
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
|
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty" schema:"omit"`
|
||||||
SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty"`
|
SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty" schema:"omit"`
|
||||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty" schema:"omit"`
|
||||||
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
|
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty" schema:"omit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenOptions struct {
|
type ListenOptions struct {
|
||||||
@@ -69,7 +82,7 @@ type ListenOptions struct {
|
|||||||
BindInterface string `json:"bind_interface,omitempty"`
|
BindInterface string `json:"bind_interface,omitempty"`
|
||||||
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
||||||
ReuseAddr bool `json:"reuse_addr,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"`
|
DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"`
|
||||||
TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"`
|
TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"`
|
||||||
TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
|
TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
|
||||||
@@ -78,13 +91,15 @@ type ListenOptions struct {
|
|||||||
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||||
UDPFragmentDefault bool `json:"-"`
|
UDPFragmentDefault bool `json:"-"`
|
||||||
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty" reference:"inbound"`
|
||||||
|
|
||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
ProxyProtocol bool `json:"proxy_protocol,omitempty" schema:"omit"`
|
||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"`
|
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty" schema:"omit"`
|
||||||
InboundOptions
|
// Legacy inbound fields are rejected since sing-box 1.13.0.
|
||||||
|
//nolint:staticcheck
|
||||||
|
InboundOptions `schema:"omit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDPNATBehavior uint8
|
type UDPNATBehavior uint8
|
||||||
@@ -129,6 +144,10 @@ func (b *UDPNATBehavior) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
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
|
type UDPTimeoutCompat badoption.Duration
|
||||||
|
|
||||||
func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) {
|
func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) {
|
||||||
@@ -145,6 +164,12 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error {
|
|||||||
return json.Unmarshal(data, (*badoption.Duration)(c))
|
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 {
|
type ListenOptionsWrapper interface {
|
||||||
TakeListenOptions() ListenOptions
|
TakeListenOptions() ListenOptions
|
||||||
ReplaceListenOptions(options ListenOptions)
|
ReplaceListenOptions(options ListenOptions)
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ type InboundMultiplexOptions struct {
|
|||||||
|
|
||||||
type OutboundMultiplexOptions struct {
|
type OutboundMultiplexOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
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"`
|
MaxConnections int `json:"max_connections,omitempty"`
|
||||||
MinStreams int `json:"min_streams,omitempty"`
|
MinStreams int `json:"min_streams,omitempty"`
|
||||||
MaxStreams int `json:"max_streams,omitempty"`
|
MaxStreams int `json:"max_streams,omitempty"`
|
||||||
|
|||||||
+2
-2
@@ -20,7 +20,7 @@ type NaiveInboundOptions struct {
|
|||||||
ListenOptions
|
ListenOptions
|
||||||
Users []auth.User `json:"users,omitempty"`
|
Users []auth.User `json:"users,omitempty"`
|
||||||
Network NetworkList `json:"network,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
|
InboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ type NaiveOutboundOptions struct {
|
|||||||
ReceiveWindow *byteformats.MemoryBytes `json:"stream_receive_window,omitempty"`
|
ReceiveWindow *byteformats.MemoryBytes `json:"stream_receive_window,omitempty"`
|
||||||
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||||
QUIC bool `json:"quic,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"`
|
QUICSessionReceiveWindow *byteformats.MemoryBytes `json:"quic_session_receive_window,omitempty"`
|
||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"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)
|
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 {
|
type DefaultNetworkNamespaceOptions struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ type OCMServiceOptions struct {
|
|||||||
CredentialPath string `json:"credential_path,omitempty"`
|
CredentialPath string `json:"credential_path,omitempty"`
|
||||||
Users []OCMUser `json:"users,omitempty"`
|
Users []OCMUser `json:"users,omitempty"`
|
||||||
Headers badoption.HTTPHeader `json:"headers,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"`
|
UsagesPath string `json:"usages_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type OpenConnectEndpointOptions struct {
|
|||||||
UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"`
|
UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"`
|
||||||
UDPNATMax uint32 `json:"udp_nat_max,omitempty"`
|
UDPNATMax uint32 `json:"udp_nat_max,omitempty"`
|
||||||
Server string `json:"server"`
|
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"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
AuthGroup string `json:"auth_group,omitempty"`
|
AuthGroup string `json:"auth_group,omitempty"`
|
||||||
@@ -29,7 +29,7 @@ type OpenConnectEndpointOptions struct {
|
|||||||
NoUDP bool `json:"no_udp,omitempty"`
|
NoUDP bool `json:"no_udp,omitempty"`
|
||||||
DTLSLocalPort uint16 `json:"dtls_local_port,omitempty"`
|
DTLSLocalPort uint16 `json:"dtls_local_port,omitempty"`
|
||||||
CompressionDisabled bool `json:"compression_disabled,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"`
|
IPv6Disabled bool `json:"ipv6_disabled,omitempty"`
|
||||||
HTTPKeepAliveDisabled bool `json:"http_keepalive_disabled,omitempty"`
|
HTTPKeepAliveDisabled bool `json:"http_keepalive_disabled,omitempty"`
|
||||||
XMLPostDisabled bool `json:"xml_post_disabled,omitempty"`
|
XMLPostDisabled bool `json:"xml_post_disabled,omitempty"`
|
||||||
@@ -49,7 +49,7 @@ type OpenConnectEndpointOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OpenConnectTokenOptions struct {
|
type OpenConnectTokenOptions struct {
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty" enum:"totp,hotp,stoken,oidc"`
|
||||||
Secret string `json:"secret,omitempty"`
|
Secret string `json:"secret,omitempty"`
|
||||||
SecretPath string `json:"secret_path,omitempty"`
|
SecretPath string `json:"secret_path,omitempty"`
|
||||||
PIN string `json:"pin,omitempty"`
|
PIN string `json:"pin,omitempty"`
|
||||||
|
|||||||
+35
-35
@@ -20,22 +20,22 @@ type OpenVPNClientEndpointOptions struct {
|
|||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
OpenVPNEndpointOptions
|
OpenVPNEndpointOptions
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty" enum:"tls,static_key"`
|
||||||
Network string `json:"network,omitempty"`
|
Network string `json:"network,omitempty" enum:"udp,udp4,udp6,tcp,tcp4,tcp6"`
|
||||||
Servers []OpenVPNRemoteOptions `json:"servers,omitempty"`
|
Servers []OpenVPNRemoteOptions `json:"servers,omitempty"`
|
||||||
RemoteRandom bool `json:"remote_random,omitempty"`
|
RemoteRandom bool `json:"remote_random,omitempty"`
|
||||||
Address badoption.Listable[netip.Prefix] `json:"address,omitempty"`
|
Address badoption.Listable[netip.Prefix] `json:"address,omitempty"`
|
||||||
PeerAddress badoption.Addr `json:"peer_address,omitempty"`
|
PeerAddress badoption.Addr `json:"peer_address,omitempty"`
|
||||||
PeerAddressIPv6 badoption.Addr `json:"peer_address_ipv6,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"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,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"`
|
StaticChallenge string `json:"static_challenge,omitempty"`
|
||||||
StaticChallengeEcho bool `json:"static_challenge_echo,omitempty"`
|
StaticChallengeEcho bool `json:"static_challenge_echo,omitempty"`
|
||||||
StaticKey badoption.Listable[string] `json:"static_key,omitempty"`
|
StaticKey badoption.Listable[string] `json:"static_key,omitempty"`
|
||||||
StaticKeyPath string `json:"static_key_path,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"`
|
TLS *OpenVPNOutboundTLSOptions `json:"tls,omitempty"`
|
||||||
Cipher string `json:"cipher,omitempty"`
|
Cipher string `json:"cipher,omitempty"`
|
||||||
DataCiphers badoption.Listable[string] `json:"data_ciphers,omitempty"`
|
DataCiphers badoption.Listable[string] `json:"data_ciphers,omitempty"`
|
||||||
@@ -43,13 +43,13 @@ type OpenVPNClientEndpointOptions struct {
|
|||||||
Auth string `json:"auth,omitempty"`
|
Auth string `json:"auth,omitempty"`
|
||||||
MSSFix uint32 `json:"mss_fix,omitempty"`
|
MSSFix uint32 `json:"mss_fix,omitempty"`
|
||||||
MSSFixDisabled bool `json:"mss_fix_disabled,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"`
|
Fragment uint32 `json:"fragment,omitempty"`
|
||||||
ReplayWindow uint32 `json:"replay_window,omitempty"`
|
ReplayWindow uint32 `json:"replay_window,omitempty"`
|
||||||
ReplayWindowTime badoption.Duration `json:"replay_window_time,omitempty"`
|
ReplayWindowTime badoption.Duration `json:"replay_window_time,omitempty"`
|
||||||
Compression string `json:"compression,omitempty"`
|
Compression string `json:"compression,omitempty" enum:"none,no,lz4,lz4-v2,stub,stub-v2,disabled,off"`
|
||||||
CompressionLZO string `json:"compression_lzo,omitempty"`
|
CompressionLZO string `json:"compression_lzo,omitempty" enum:"none,no,yes,adaptive,asym,disabled,off"`
|
||||||
AllowCompression string `json:"allow_compression,omitempty"`
|
AllowCompression string `json:"allow_compression,omitempty" enum:"no,asym,yes"`
|
||||||
RouteNoPull bool `json:"route_no_pull,omitempty"`
|
RouteNoPull bool `json:"route_no_pull,omitempty"`
|
||||||
PullFilters []OpenVPNPullFilterOptions `json:"pull_filters,omitempty"`
|
PullFilters []OpenVPNPullFilterOptions `json:"pull_filters,omitempty"`
|
||||||
Routes badoption.Listable[netip.Prefix] `json:"routes,omitempty"`
|
Routes badoption.Listable[netip.Prefix] `json:"routes,omitempty"`
|
||||||
@@ -75,20 +75,20 @@ type OpenVPNClientEndpointOptions struct {
|
|||||||
type OpenVPNServerEndpointOptions struct {
|
type OpenVPNServerEndpointOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
OpenVPNEndpointOptions
|
OpenVPNEndpointOptions
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty" enum:"tls,static_key"`
|
||||||
Network string `json:"network,omitempty"`
|
Network string `json:"network,omitempty" enum:"tcp,udp"`
|
||||||
Remote string `json:"remote,omitempty"`
|
Remote string `json:"remote,omitempty"`
|
||||||
RemotePort uint16 `json:"remote_port,omitempty"`
|
RemotePort uint16 `json:"remote_port,omitempty"`
|
||||||
MaxClients int `json:"max_clients,omitempty"`
|
MaxClients int `json:"max_clients,omitempty"`
|
||||||
Address badoption.Listable[netip.Prefix] `json:"address"`
|
Address badoption.Listable[netip.Prefix] `json:"address"`
|
||||||
PeerAddress badoption.Addr `json:"peer_address,omitempty"`
|
PeerAddress badoption.Addr `json:"peer_address,omitempty"`
|
||||||
PeerAddressIPv6 badoption.Addr `json:"peer_address_ipv6,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"`
|
DuplicateCN bool `json:"duplicate_cn,omitempty"`
|
||||||
Users []auth.User `json:"users,omitempty"`
|
Users []auth.User `json:"users,omitempty"`
|
||||||
StaticKey badoption.Listable[string] `json:"static_key,omitempty"`
|
StaticKey badoption.Listable[string] `json:"static_key,omitempty"`
|
||||||
StaticKeyPath string `json:"static_key_path,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"`
|
TLS *OpenVPNInboundTLSOptions `json:"tls,omitempty"`
|
||||||
Cipher string `json:"cipher,omitempty"`
|
Cipher string `json:"cipher,omitempty"`
|
||||||
DataCiphers badoption.Listable[string] `json:"data_ciphers,omitempty"`
|
DataCiphers badoption.Listable[string] `json:"data_ciphers,omitempty"`
|
||||||
@@ -96,7 +96,7 @@ type OpenVPNServerEndpointOptions struct {
|
|||||||
Auth string `json:"auth,omitempty"`
|
Auth string `json:"auth,omitempty"`
|
||||||
MSSFix uint32 `json:"mss_fix,omitempty"`
|
MSSFix uint32 `json:"mss_fix,omitempty"`
|
||||||
MSSFixDisabled bool `json:"mss_fix_disabled,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"`
|
ReplayWindow uint32 `json:"replay_window,omitempty"`
|
||||||
ReplayWindowTime badoption.Duration `json:"replay_window_time,omitempty"`
|
ReplayWindowTime badoption.Duration `json:"replay_window_time,omitempty"`
|
||||||
Push *OpenVPNPushOptions `json:"push,omitempty"`
|
Push *OpenVPNPushOptions `json:"push,omitempty"`
|
||||||
@@ -111,17 +111,17 @@ type OpenVPNServerEndpointOptions struct {
|
|||||||
|
|
||||||
type OpenVPNRemoteOptions struct {
|
type OpenVPNRemoteOptions struct {
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Network string `json:"network,omitempty"`
|
Network string `json:"network,omitempty" enum:"udp,udp4,udp6,tcp,tcp4,tcp6"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenVPNPullFilterOptions struct {
|
type OpenVPNPullFilterOptions struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action" enum:"accept,ignore,reject"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenVPNOutboundTLSOptions struct {
|
type OpenVPNOutboundTLSOptions struct {
|
||||||
ServerName string `json:"server_name,omitempty"`
|
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"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
|
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
|
||||||
@@ -132,11 +132,11 @@ type OpenVPNOutboundTLSOptions struct {
|
|||||||
CRLPath string `json:"crl_path,omitempty"`
|
CRLPath string `json:"crl_path,omitempty"`
|
||||||
RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"`
|
RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"`
|
||||||
RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"`
|
RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"`
|
||||||
RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty"`
|
RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty" enum:"server,client,none"`
|
||||||
CertificateProfile string `json:"certificate_profile,omitempty"`
|
CertificateProfile string `json:"certificate_profile,omitempty" enum:"legacy,preferred,insecure,suiteb"`
|
||||||
NSCertificateType string `json:"ns_certificate_type,omitempty"`
|
NSCertificateType string `json:"ns_certificate_type,omitempty" enum:"server,client"`
|
||||||
VersionMin string `json:"version_min,omitempty"`
|
VersionMin string `json:"version_min,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
VersionMax string `json:"version_max,omitempty"`
|
VersionMax string `json:"version_max,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
Cipher string `json:"cipher,omitempty"`
|
Cipher string `json:"cipher,omitempty"`
|
||||||
Groups string `json:"groups,omitempty"`
|
Groups string `json:"groups,omitempty"`
|
||||||
ControlWrap *OpenVPNControlWrapOptions `json:"control_wrap,omitempty"`
|
ControlWrap *OpenVPNControlWrapOptions `json:"control_wrap,omitempty"`
|
||||||
@@ -149,35 +149,35 @@ type OpenVPNInboundTLSOptions struct {
|
|||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
|
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
|
||||||
ClientCertificatePath string `json:"client_certificate_path,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"`
|
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"`
|
PeerFingerprint badoption.Listable[string] `json:"peer_fingerprint,omitempty"`
|
||||||
CRLPath string `json:"crl_path,omitempty"`
|
CRLPath string `json:"crl_path,omitempty"`
|
||||||
RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"`
|
RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"`
|
||||||
RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"`
|
RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"`
|
||||||
RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty"`
|
RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty" enum:"server,client,none"`
|
||||||
CertificateProfile string `json:"certificate_profile,omitempty"`
|
CertificateProfile string `json:"certificate_profile,omitempty" enum:"legacy,preferred,insecure,suiteb"`
|
||||||
NSCertificateType string `json:"ns_certificate_type,omitempty"`
|
NSCertificateType string `json:"ns_certificate_type,omitempty" enum:"server,client"`
|
||||||
VersionMin string `json:"version_min,omitempty"`
|
VersionMin string `json:"version_min,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
VersionMax string `json:"version_max,omitempty"`
|
VersionMax string `json:"version_max,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
Cipher string `json:"cipher,omitempty"`
|
Cipher string `json:"cipher,omitempty"`
|
||||||
Groups string `json:"groups,omitempty"`
|
Groups string `json:"groups,omitempty"`
|
||||||
ControlWrap *OpenVPNInboundControlWrapOptions `json:"control_wrap,omitempty"`
|
ControlWrap *OpenVPNInboundControlWrapOptions `json:"control_wrap,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenVPNControlWrapOptions struct {
|
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"`
|
Key badoption.Listable[string] `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
Direction string `json:"direction,omitempty"`
|
Direction string `json:"direction,omitempty" enum:"server,client"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenVPNInboundControlWrapOptions struct {
|
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"`
|
Key badoption.Listable[string] `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,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"`
|
ForceCookie bool `json:"force_cookie,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,8 +198,8 @@ type OpenVPNPushDNSServerOptions struct {
|
|||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
Addresses badoption.Listable[string] `json:"addresses"`
|
Addresses badoption.Listable[string] `json:"addresses"`
|
||||||
ResolveDomains badoption.Listable[string] `json:"resolve_domains,omitempty"`
|
ResolveDomains badoption.Listable[string] `json:"resolve_domains,omitempty"`
|
||||||
DNSSEC string `json:"dnssec,omitempty"`
|
DNSSEC string `json:"dnssec,omitempty" enum:"yes,optional,no"`
|
||||||
Transport string `json:"transport,omitempty"`
|
Transport string `json:"transport,omitempty" enum:"plain,dot,doh"`
|
||||||
SNI string `json:"sni,omitempty"`
|
SNI string `json:"sni,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-2
@@ -3,7 +3,9 @@ package option
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -12,7 +14,7 @@ import (
|
|||||||
type _Options struct {
|
type _Options struct {
|
||||||
RawMessage json.RawMessage `json:"-"`
|
RawMessage json.RawMessage `json:"-"`
|
||||||
CommentsSet *json.CommentSet `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"`
|
Log *LogOptions `json:"log,omitempty"`
|
||||||
DNS *DNSOptions `json:"dns,omitempty"`
|
DNS *DNSOptions `json:"dns,omitempty"`
|
||||||
NTP *NTPOptions `json:"ntp,omitempty"`
|
NTP *NTPOptions `json:"ntp,omitempty"`
|
||||||
@@ -45,6 +47,17 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
|||||||
return checkOptions(o)
|
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 {
|
func (o Options) Comments() *json.CommentSet {
|
||||||
return o.CommentsSet
|
return o.CommentsSet
|
||||||
}
|
}
|
||||||
@@ -55,7 +68,7 @@ func (o *Options) SetComments(comments *json.CommentSet) {
|
|||||||
|
|
||||||
type LogOptions struct {
|
type LogOptions struct {
|
||||||
Disabled bool `json:"disabled,omitempty"`
|
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"`
|
Output string `json:"output,omitempty"`
|
||||||
Timestamp bool `json:"timestamp,omitempty"`
|
Timestamp bool `json:"timestamp,omitempty"`
|
||||||
DisableColor bool `json:"-"`
|
DisableColor bool `json:"-"`
|
||||||
|
|||||||
+11
-2
@@ -3,6 +3,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
@@ -13,8 +14,8 @@ type CloudflareOriginCACertificateProviderOptions struct {
|
|||||||
DataDirectory string `json:"data_directory,omitempty"`
|
DataDirectory string `json:"data_directory,omitempty"`
|
||||||
APIToken string `json:"api_token,omitempty"`
|
APIToken string `json:"api_token,omitempty"`
|
||||||
OriginCAKey string `json:"origin_ca_key,omitempty"`
|
OriginCAKey string `json:"origin_ca_key,omitempty"`
|
||||||
RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"`
|
RequestType CloudflareOriginCARequestType `json:"request_type,omitempty" enum:"origin-rsa,origin-ecc"`
|
||||||
RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"`
|
RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty" enum:"0,7,30,90,365,730,1095,5475"`
|
||||||
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ func (t *CloudflareOriginCARequestType) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t CloudflareOriginCARequestType) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringEnum("", "origin-rsa", "origin-ecc"), nil
|
||||||
|
}
|
||||||
|
|
||||||
type CloudflareOriginCARequestValidity uint16
|
type CloudflareOriginCARequestValidity uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -74,3 +79,7 @@ func (v *CloudflareOriginCARequestValidity) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
+33
-4
@@ -2,8 +2,10 @@ package option
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type OutboundOptionsRegistry interface {
|
type OutboundOptionsRegistry interface {
|
||||||
|
OptionTypes() []string
|
||||||
CreateOptions(outboundType string) (any, bool)
|
CreateOptions(outboundType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,13 +62,27 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err
|
|||||||
return nil
|
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 {
|
type DialerOptionsWrapper interface {
|
||||||
TakeDialerOptions() DialerOptions
|
TakeDialerOptions() DialerOptions
|
||||||
ReplaceDialerOptions(options DialerOptions)
|
ReplaceDialerOptions(options DialerOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DialerOptions struct {
|
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"`
|
BindInterface string `json:"bind_interface,omitempty"`
|
||||||
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
|
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
|
||||||
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
|
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
|
||||||
@@ -73,7 +90,7 @@ type DialerOptions struct {
|
|||||||
ProtectPath string `json:"protect_path,omitempty"`
|
ProtectPath string `json:"protect_path,omitempty"`
|
||||||
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
||||||
ReuseAddr bool `json:"reuse_addr,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"`
|
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
|
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
|
||||||
@@ -91,11 +108,11 @@ type DialerOptions struct {
|
|||||||
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
|
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
|
||||||
|
|
||||||
// Deprecated: migrated to domain resolver
|
// Deprecated: migrated to domain resolver
|
||||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty" schema:"omit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DomainResolveOptions struct {
|
type _DomainResolveOptions struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server" reference:"dns_server"`
|
||||||
Timeout badoption.Duration `json:"timeout,omitempty"`
|
Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
@@ -138,6 +155,18 @@ func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
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 {
|
func (o *DialerOptions) TakeDialerOptions() DialerOptions {
|
||||||
return *o
|
return *o
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
@@ -63,6 +64,10 @@ func (r *OnDemandRuleAction) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r OnDemandRuleAction) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringEnum("connect", "disconnect", "evaluate_connection", "ignore"), nil
|
||||||
|
}
|
||||||
|
|
||||||
type OnDemandRuleInterfaceType int
|
type OnDemandRuleInterfaceType int
|
||||||
|
|
||||||
func (r *OnDemandRuleInterfaceType) MarshalJSON() ([]byte, error) {
|
func (r *OnDemandRuleInterfaceType) MarshalJSON() ([]byte, error) {
|
||||||
@@ -103,3 +108,7 @@ func (r *OnDemandRuleInterfaceType) UnmarshalJSON(bytes []byte) error {
|
|||||||
*r = OnDemandRuleInterfaceType(interfaceTypeValue)
|
*r = OnDemandRuleInterfaceType(interfaceTypeValue)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r OnDemandRuleInterfaceType) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringEnum("any", "wifi", "cellular"), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package option
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
@@ -39,6 +41,15 @@ func (r *ResolvedServiceOptions) UnmarshalJSONContext(ctx context.Context, bytes
|
|||||||
return nil
|
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 {
|
type ResolvedDNSServerOptions struct {
|
||||||
Service string `json:"service"`
|
Service string `json:"service"`
|
||||||
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
||||||
|
|||||||
+5
-5
@@ -3,11 +3,11 @@ package option
|
|||||||
import "github.com/sagernet/sing/common/json/badoption"
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty" schema:"omit"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty" schema:"omit"`
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
RuleSet []RuleSet `json:"rule_set,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"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
FindNeighbor bool `json:"find_neighbor,omitempty"`
|
FindNeighbor bool `json:"find_neighbor,omitempty"`
|
||||||
DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"`
|
DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"`
|
||||||
@@ -26,11 +26,11 @@ type RouteOptions struct {
|
|||||||
type GeoIPOptions struct {
|
type GeoIPOptions struct {
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty" reference:"outbound"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeositeOptions struct {
|
type GeositeOptions struct {
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty" reference:"outbound"`
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-11
@@ -5,6 +5,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _Rule struct {
|
type _Rule struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" enum:"default,logical"`
|
||||||
DefaultOptions DefaultRule `json:"-"`
|
DefaultOptions DefaultRule `json:"-"`
|
||||||
LogicalOptions LogicalRule `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 {
|
type RawDefaultRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty" reference:"inbound"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty" enum:"4,6"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty" enum:"tcp,udp,icmp"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
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"`
|
Client badoption.Listable[string] `json:"client,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,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"`
|
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
||||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
||||||
PreferredBy badoption.Listable[string] `json:"preferred_by,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"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// 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 {
|
type DefaultRule struct {
|
||||||
@@ -138,7 +210,7 @@ func (r DefaultRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawLogicalRule struct {
|
type RawLogicalRule struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode" enum:"and,or"`
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-41
@@ -4,9 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -14,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _RuleAction struct {
|
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:"-"`
|
RouteOptions RouteActionOptions `json:"-"`
|
||||||
RouteOptionsOptions RouteOptionsActionOptions `json:"-"`
|
RouteOptionsOptions RouteOptionsActionOptions `json:"-"`
|
||||||
DirectOptions DirectActionOptions `json:"-"`
|
DirectOptions DirectActionOptions `json:"-"`
|
||||||
@@ -97,9 +99,10 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRuleAction struct {
|
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"`
|
Race bool `json:"race,omitempty"`
|
||||||
RouteOptions DNSRouteActionOptions `json:"-"`
|
RouteOptions DNSRouteActionOptions `json:"-"`
|
||||||
|
EvaluateOptions DNSEvaluateActionOptions `json:"-"`
|
||||||
RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"`
|
RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"`
|
||||||
RejectOptions RejectActionOptions `json:"-"`
|
RejectOptions RejectActionOptions `json:"-"`
|
||||||
PredefinedOptions DNSRouteActionPredefined `json:"-"`
|
PredefinedOptions DNSRouteActionPredefined `json:"-"`
|
||||||
@@ -117,7 +120,7 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
|
|||||||
r.Action = ""
|
r.Action = ""
|
||||||
v = r.RouteOptions
|
v = r.RouteOptions
|
||||||
case C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeEvaluate:
|
||||||
v = r.RouteOptions
|
v = r.EvaluateOptions
|
||||||
case C.RuleActionTypeRespond:
|
case C.RuleActionTypeRespond:
|
||||||
v = nil
|
v = nil
|
||||||
case C.RuleActionTypeRouteOptions:
|
case C.RuleActionTypeRouteOptions:
|
||||||
@@ -146,7 +149,7 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
|
|||||||
r.Action = C.RuleActionTypeRoute
|
r.Action = C.RuleActionTypeRoute
|
||||||
v = &r.RouteOptions
|
v = &r.RouteOptions
|
||||||
case C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeEvaluate:
|
||||||
v = &r.RouteOptions
|
v = &r.EvaluateOptions
|
||||||
case C.RuleActionTypeRespond:
|
case C.RuleActionTypeRespond:
|
||||||
v = nil
|
v = nil
|
||||||
case C.RuleActionTypeRouteOptions:
|
case C.RuleActionTypeRouteOptions:
|
||||||
@@ -165,14 +168,11 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if r.Action == C.RuleActionTypeRoute && r.RouteOptions.Tag != "" {
|
|
||||||
return E.New("`tag` is only available in the `evaluate` action")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteActionOptions struct {
|
type RouteActionOptions struct {
|
||||||
Outbound string `json:"outbound,omitempty"`
|
Outbound string `json:"outbound,omitempty" reference:"outbound"`
|
||||||
RawRouteOptionsActionOptions
|
RawRouteOptionsActionOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ type RawRouteOptionsActionOptions struct {
|
|||||||
TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"`
|
TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"`
|
||||||
TLSRecordFragment bool `json:"tls_record_fragment,omitempty"`
|
TLSRecordFragment bool `json:"tls_record_fragment,omitempty"`
|
||||||
TLSSpoof string `json:"tls_spoof,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
|
type RouteOptionsActionOptions RawRouteOptionsActionOptions
|
||||||
@@ -211,30 +211,31 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSRouteActionOptions struct {
|
type DNSRouteActionOptions struct {
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty" reference:"dns_server"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Speculative bool `json:"speculative,omitempty"`
|
||||||
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"`
|
Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty" schema:"omit"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRouteOptionsActionOptions struct {
|
type DNSRouteOptionsActionOptions AbstractDNSRouteActionOptions
|
||||||
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
|
|
||||||
|
|
||||||
func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
|
func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
|
||||||
err := json.Unmarshal(data, (*_DNSRouteOptionsActionOptions)(r))
|
err := json.Unmarshal(data, (*AbstractDNSRouteActionOptions)(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -244,9 +245,9 @@ func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DirectActionOptions DialerOptions
|
type DirectActionOptions struct {
|
||||||
|
AbstractDialerOptions
|
||||||
type DirectActionOptions _DirectActionOptions
|
}
|
||||||
|
|
||||||
func (d DirectActionOptions) Descriptions() []string {
|
func (d DirectActionOptions) Descriptions() []string {
|
||||||
var descriptions []string
|
var descriptions []string
|
||||||
@@ -286,19 +287,8 @@ func (d DirectActionOptions) Descriptions() []string {
|
|||||||
return descriptions
|
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 {
|
type _RejectActionOptions struct {
|
||||||
Method string `json:"method,omitempty"`
|
Method string `json:"method,omitempty" enum:"default,drop,reply"`
|
||||||
NoDrop bool `json:"no_drop,omitempty"`
|
NoDrop bool `json:"no_drop,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,12 +322,12 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RouteActionSniff struct {
|
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"`
|
Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteActionResolve struct {
|
type RouteActionResolve struct {
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty" reference:"dns_server"`
|
||||||
Timeout badoption.Duration `json:"timeout,omitempty"`
|
Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
@@ -352,3 +342,81 @@ type DNSRouteActionPredefined struct {
|
|||||||
Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"`
|
Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"`
|
||||||
Extra badoption.Listable[DNSRecordOptions] `json:"extra,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},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+36
-13
@@ -5,6 +5,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _DNSRule struct {
|
type _DNSRule struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" enum:"default,logical"`
|
||||||
DefaultOptions DefaultDNSRule `json:"-"`
|
DefaultOptions DefaultDNSRule `json:"-"`
|
||||||
LogicalOptions LogicalDNSRule `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 {
|
type DNSRuleMatchResponse struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Tag string
|
Tag string
|
||||||
@@ -111,13 +130,17 @@ func (m *DNSRuleMatchResponse) ResponseTag() string {
|
|||||||
return m.Tag
|
return m.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m DNSRuleMatchResponse) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.AnyOf(schema.BooleanNode(), schema.StringNode()), nil
|
||||||
|
}
|
||||||
|
|
||||||
type RawDefaultDNSRule struct {
|
type RawDefaultDNSRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty" reference:"inbound"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty" enum:"4,6"`
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
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"`
|
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"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,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"`
|
PackageNameRegex badoption.Listable[string] `json:"package_name_regex,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,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"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
@@ -148,7 +171,7 @@ type RawDefaultDNSRule struct {
|
|||||||
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
||||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
||||||
PreferredBy badoption.Listable[string] `json:"preferred_by,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"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
MatchResponse *DNSRuleMatchResponse `json:"match_response,omitempty"`
|
MatchResponse *DNSRuleMatchResponse `json:"match_response,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
@@ -161,13 +184,13 @@ type RawDefaultDNSRule struct {
|
|||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
// Deprecated: removed in sing-box 1.12.0
|
// Deprecated: removed in sing-box 1.12.0
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"`
|
||||||
// Deprecated: removed in sing-box 1.11.0
|
// 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: 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 {
|
type DefaultDNSRule struct {
|
||||||
@@ -210,7 +233,7 @@ func (r DefaultDNSRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawLogicalDNSRule struct {
|
type RawLogicalDNSRule struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode" enum:"and,or"`
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
routeRuleActionKeys = jsonFieldNames(reflect.TypeFor[_RuleAction](), reflect.TypeFor[RouteActionOptions]())
|
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 {
|
func nestedRuleChildContext(ctx context.Context) context.Context {
|
||||||
|
|||||||
+53
-7
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/domain"
|
"github.com/sagernet/sing/common/domain"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -19,9 +20,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _RuleSet struct {
|
type _RuleSet struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" enum:"inline,local,remote"`
|
||||||
Tag badoption.Listable[string] `json:"tag"`
|
Tag badoption.Listable[string] `json:"tag"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitempty" enum:"source,binary"`
|
||||||
InlineOptions PlainRuleSet `json:"-"`
|
InlineOptions PlainRuleSet `json:"-"`
|
||||||
LocalOptions LocalRuleSet `json:"-"`
|
LocalOptions LocalRuleSet `json:"-"`
|
||||||
RemoteOptions RemoteRuleSet `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 {
|
type LocalRuleSet struct {
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -140,11 +180,11 @@ type RemoteRuleSet struct {
|
|||||||
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
|
||||||
UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
|
UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
|
||||||
// Deprecated: use http_client instead
|
// 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 _HeadlessRule struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" enum:"default,logical"`
|
||||||
DefaultOptions DefaultHeadlessRule `json:"-"`
|
DefaultOptions DefaultHeadlessRule `json:"-"`
|
||||||
LogicalOptions LogicalHeadlessRule `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 {
|
type DefaultHeadlessRule struct {
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
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"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
@@ -241,7 +287,7 @@ func (r DefaultHeadlessRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogicalHeadlessRule struct {
|
type LogicalHeadlessRule struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode" enum:"and,or"`
|
||||||
Rules []HeadlessRule `json:"rules,omitempty"`
|
Rules []HeadlessRule `json:"rules,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -251,7 +297,7 @@ func (r LogicalHeadlessRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _PlainRuleSetCompat struct {
|
type _PlainRuleSetCompat struct {
|
||||||
Version uint8 `json:"version"`
|
Version uint8 `json:"version" enum:"1,2,3,4,5"`
|
||||||
Options PlainRuleSet `json:"-"`
|
Options PlainRuleSet `json:"-"`
|
||||||
RawMessage json.RawMessage `json:"-"`
|
RawMessage json.RawMessage `json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ServiceOptionsRegistry interface {
|
type ServiceOptionsRegistry interface {
|
||||||
|
OptionTypes() []string
|
||||||
CreateOptions(serviceType string) (any, bool)
|
CreateOptions(serviceType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,3 +47,13 @@ func (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
|||||||
h.Options = options
|
h.Options = options
|
||||||
return nil
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package option
|
|||||||
type ShadowsocksInboundOptions struct {
|
type ShadowsocksInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Network NetworkList `json:"network,omitempty"`
|
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"`
|
Password string `json:"password,omitempty"`
|
||||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||||
@@ -25,7 +25,7 @@ type ShadowsocksDestination struct {
|
|||||||
type ShadowsocksOutboundOptions struct {
|
type ShadowsocksOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
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"`
|
Password string `json:"password"`
|
||||||
Plugin string `json:"plugin,omitempty"`
|
Plugin string `json:"plugin,omitempty"`
|
||||||
PluginOptions string `json:"plugin_opts,omitempty"`
|
PluginOptions string `json:"plugin_opts,omitempty"`
|
||||||
|
|||||||
+7
-2
@@ -3,13 +3,14 @@ package option
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowTLSInboundOptions struct {
|
type ShadowTLSInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Version int `json:"version,omitempty"`
|
Version int `json:"version,omitempty" enum:"1,2,3"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Users []ShadowTLSUser `json:"users,omitempty"`
|
Users []ShadowTLSUser `json:"users,omitempty"`
|
||||||
Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"`
|
Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"`
|
||||||
@@ -62,6 +63,10 @@ func (w *WildcardSNI) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w WildcardSNI) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringEnum("", "off", "authed", "all"), nil
|
||||||
|
}
|
||||||
|
|
||||||
type ShadowTLSUser struct {
|
type ShadowTLSUser struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
@@ -75,7 +80,7 @@ type ShadowTLSHandshakeOptions struct {
|
|||||||
type ShadowTLSOutboundOptions struct {
|
type ShadowTLSOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Version int `json:"version,omitempty"`
|
Version int `json:"version,omitempty" enum:"1,2,3"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ type HTTPMixedInboundOptions struct {
|
|||||||
type SOCKSOutboundOptions struct {
|
type SOCKSOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty" enum:"4,4a,5"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
|||||||
+43
-14
@@ -1,20 +1,27 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type _SnellInboundOptions struct {
|
type _SnellInboundOptions struct {
|
||||||
ListenOptions
|
Version int `json:"version" enum:"5,6"`
|
||||||
Version int `json:"version"`
|
AbstractSnellInboundOptions
|
||||||
PSK string `json:"psk"`
|
|
||||||
Users []SnellUser `json:"users,omitempty"`
|
|
||||||
ObfsOptions SnellObfsServerOptions `json:"-"`
|
ObfsOptions SnellObfsServerOptions `json:"-"`
|
||||||
V6Options SnellV6Options `json:"-"`
|
V6Options SnellV6Options `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AbstractSnellInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
PSK string `json:"psk"`
|
||||||
|
Users []SnellUser `json:"users,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type SnellInboundOptions _SnellInboundOptions
|
type SnellInboundOptions _SnellInboundOptions
|
||||||
|
|
||||||
func (o *SnellInboundOptions) UnmarshalJSON(content []byte) error {
|
func (o *SnellInboundOptions) UnmarshalJSON(content []byte) error {
|
||||||
@@ -51,18 +58,31 @@ func (o SnellInboundOptions) MarshalJSON() ([]byte, error) {
|
|||||||
return badjson.MarshallObjects((_SnellInboundOptions)(o), versionOptions)
|
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 {
|
type _SnellOutboundOptions struct {
|
||||||
DialerOptions
|
Version int `json:"version" enum:"4,6"`
|
||||||
ServerOptions
|
AbstractSnellOutboundOptions
|
||||||
Version int `json:"version"`
|
|
||||||
PSK string `json:"psk"`
|
|
||||||
UserKey string `json:"userkey,omitempty"`
|
|
||||||
Reuse bool `json:"reuse,omitempty"`
|
|
||||||
Network NetworkList `json:"network,omitempty"`
|
|
||||||
ObfsOptions SnellObfsClientOptions `json:"-"`
|
ObfsOptions SnellObfsClientOptions `json:"-"`
|
||||||
V6Options SnellV6Options `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
|
type SnellOutboundOptions _SnellOutboundOptions
|
||||||
|
|
||||||
func (o *SnellOutboundOptions) UnmarshalJSON(content []byte) error {
|
func (o *SnellOutboundOptions) UnmarshalJSON(content []byte) error {
|
||||||
@@ -99,8 +119,17 @@ func (o SnellOutboundOptions) MarshalJSON() ([]byte, error) {
|
|||||||
return badjson.MarshallObjects((_SnellOutboundOptions)(o), versionOptions)
|
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 {
|
type SnellObfsServerOptions struct {
|
||||||
ObfsMode string `json:"obfs_mode,omitempty"`
|
ObfsMode string `json:"obfs_mode,omitempty" enum:"none,http,tls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnellUser struct {
|
type SnellUser struct {
|
||||||
@@ -109,10 +138,10 @@ type SnellUser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SnellObfsClientOptions 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"`
|
ObfsHost string `json:"obfs_host,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnellV6Options struct {
|
type SnellV6Options struct {
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty" enum:"default,unshaped,unsafe-raw"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package option
|
|||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
@@ -56,6 +58,15 @@ func (o *TailscaleSSHServerOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
return json.UnmarshalDisallowUnknownFields(bytes, (*_TailscaleSSHServerOptions)(o))
|
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 {
|
type TailscaleDNSServerOptions struct {
|
||||||
Endpoint string `json:"endpoint,omitempty"`
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
||||||
@@ -127,6 +138,15 @@ func (d *DERPVerifyClientURLOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
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 {
|
type DERPMeshOptions struct {
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Host string `json:"host,omitempty"`
|
Host string `json:"host,omitempty"`
|
||||||
@@ -165,3 +185,12 @@ func (d *DERPSTUNListenOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
return json.Unmarshal(bytes, (*_DERPSTUNListenOptions)(d))
|
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
|
||||||
|
}
|
||||||
|
|||||||
+23
-14
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
@@ -13,9 +14,9 @@ type InboundTLSOptions struct {
|
|||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
Insecure bool `json:"insecure,omitempty"`
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
ALPN badoption.Listable[string] `json:"alpn,omitempty" examples:"http/1.1,h2,h3"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
|
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
|
||||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
@@ -32,7 +33,7 @@ type InboundTLSOptions struct {
|
|||||||
CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"`
|
CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"`
|
||||||
|
|
||||||
// Deprecated: use certificate_provider
|
// Deprecated: use certificate_provider
|
||||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
ACME *InboundACMEOptions `json:"acme,omitempty" schema:"omit"`
|
||||||
|
|
||||||
ECH *InboundECHOptions `json:"ech,omitempty"`
|
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||||
@@ -82,6 +83,10 @@ func (t *ClientAuthType) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
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 {
|
type InboundTLSOptionsContainer struct {
|
||||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -101,13 +106,13 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
|
|||||||
|
|
||||||
type OutboundTLSOptions struct {
|
type OutboundTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
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"`
|
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
Insecure bool `json:"insecure,omitempty"`
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
ALPN badoption.Listable[string] `json:"alpn,omitempty" examples:"http/1.1,h2,h3"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty" enum:"1.0,1.1,1.2,1.3"`
|
||||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
|
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
|
||||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
@@ -121,7 +126,7 @@ type OutboundTLSOptions struct {
|
|||||||
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
||||||
RecordFragment bool `json:"record_fragment,omitempty"`
|
RecordFragment bool `json:"record_fragment,omitempty"`
|
||||||
Spoof string `json:"spoof,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"`
|
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||||
HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"`
|
HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"`
|
||||||
@@ -199,6 +204,10 @@ func (c *CurvePreference) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c CurvePreference) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.StringEnum("P256", "P384", "P521", "X25519", "X25519MLKEM768"), nil
|
||||||
|
}
|
||||||
|
|
||||||
type InboundRealityOptions struct {
|
type InboundRealityOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
|
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
|
||||||
@@ -218,9 +227,9 @@ type InboundECHOptions struct {
|
|||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
|
|
||||||
// Deprecated: not supported by stdlib
|
// 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
|
// 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 {
|
type OutboundECHOptions struct {
|
||||||
@@ -230,14 +239,14 @@ type OutboundECHOptions struct {
|
|||||||
QueryServerName string `json:"query_server_name,omitempty"`
|
QueryServerName string `json:"query_server_name,omitempty"`
|
||||||
|
|
||||||
// Deprecated: not supported by stdlib
|
// 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
|
// 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 {
|
type OutboundUTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
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 {
|
type OutboundRealityOptions struct {
|
||||||
|
|||||||
+18
-1
@@ -1,7 +1,10 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -29,7 +32,7 @@ type ACMEExternalAccountOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _ACMEDNS01ChallengeOptions struct {
|
type _ACMEDNS01ChallengeOptions struct {
|
||||||
Provider string `json:"provider,omitempty"`
|
Provider string `json:"provider,omitempty" enum:"alidns,cloudflare,acmedns"`
|
||||||
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
|
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
|
||||||
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
|
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
|
||||||
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
|
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
|
||||||
@@ -77,6 +80,20 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
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 {
|
type ACMEDNS01AliDNSOptions struct {
|
||||||
AccessKeyID string `json:"access_key_id,omitempty"`
|
AccessKeyID string `json:"access_key_id,omitempty"`
|
||||||
AccessKeySecret string `json:"access_key_secret,omitempty"`
|
AccessKeySecret string `json:"access_key_secret,omitempty"`
|
||||||
|
|||||||
+3
-3
@@ -5,7 +5,7 @@ import "github.com/sagernet/sing/common/json/badoption"
|
|||||||
type TUICInboundOptions struct {
|
type TUICInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Users []TUICUser `json:"users,omitempty"`
|
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"`
|
AuthTimeout badoption.Duration `json:"auth_timeout,omitempty"`
|
||||||
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
|
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
|
||||||
Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
|
Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
|
||||||
@@ -24,8 +24,8 @@ type TUICOutboundOptions struct {
|
|||||||
ServerOptions
|
ServerOptions
|
||||||
UUID string `json:"uuid,omitempty"`
|
UUID string `json:"uuid,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
CongestionControl string `json:"congestion_control,omitempty"`
|
CongestionControl string `json:"congestion_control,omitempty" enum:"cubic,new_reno,bbr"`
|
||||||
UDPRelayMode string `json:"udp_relay_mode,omitempty"`
|
UDPRelayMode string `json:"udp_relay_mode,omitempty" enum:"native,quic"`
|
||||||
UDPOverStream bool `json:"udp_over_stream,omitempty"`
|
UDPOverStream bool `json:"udp_over_stream,omitempty"`
|
||||||
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
|
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
|
||||||
Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
|
Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
|
||||||
|
|||||||
+17
-12
@@ -4,6 +4,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -12,10 +13,10 @@ import (
|
|||||||
|
|
||||||
type TunInboundOptions struct {
|
type TunInboundOptions struct {
|
||||||
InterfaceName string `json:"interface_name,omitempty"`
|
InterfaceName string `json:"interface_name,omitempty"`
|
||||||
NetNs string `json:"netns,omitempty"`
|
NetNs string `json:"netns,omitempty" reference:"network_namespace"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
Address badoption.Listable[netip.Prefix] `json:"address,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"`
|
DNSMode string `json:"dns_mode,omitempty" enum:"disabled,native,hijack"`
|
||||||
DNSAddress badoption.Listable[netip.Addr] `json:"dns_address,omitempty"`
|
DNSAddress badoption.Listable[netip.Addr] `json:"dns_address,omitempty"`
|
||||||
AutoRoute bool `json:"auto_route,omitempty"`
|
AutoRoute bool `json:"auto_route,omitempty"`
|
||||||
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
|
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
|
||||||
@@ -48,26 +49,26 @@ type TunInboundOptions struct {
|
|||||||
UDPMapping UDPNATBehavior `json:"udp_mapping,omitempty"`
|
UDPMapping UDPNATBehavior `json:"udp_mapping,omitempty"`
|
||||||
UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"`
|
UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"`
|
||||||
UDPNATMax uint32 `json:"udp_nat_max,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"`
|
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||||
InboundOptions
|
InboundOptions
|
||||||
|
|
||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
GSO bool `json:"gso,omitempty"`
|
GSO bool `json:"gso,omitempty" schema:"omit"`
|
||||||
// Deprecated: merged to Address
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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
|
// Deprecated: removed
|
||||||
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
|
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty" schema:"omit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FwMark uint32
|
type FwMark uint32
|
||||||
@@ -92,3 +93,7 @@ func (f *FwMark) UnmarshalJSON(bytes []byte) error {
|
|||||||
*f = FwMark(intValue)
|
*f = FwMark(intValue)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f FwMark) DescribeSchema(builder schema.Builder) (*schema.Node, error) {
|
||||||
|
return schema.AnyOf(schema.UnsignedNode(32), schema.StringNode()), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -43,6 +46,10 @@ func (v NetworkList) Build() []string {
|
|||||||
return strings.Split(string(v), "\n")
|
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
|
type DomainStrategy C.DomainStrategy
|
||||||
|
|
||||||
func (s DomainStrategy) String() string {
|
func (s DomainStrategy) String() string {
|
||||||
@@ -105,6 +112,12 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
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
|
type DNSQueryType uint16
|
||||||
|
|
||||||
func (t DNSQueryType) String() string {
|
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))
|
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 {
|
func DNSQueryTypeToString(queryType uint16) string {
|
||||||
typeName, loaded := mDNS.TypeToString[queryType]
|
typeName, loaded := mDNS.TypeToString[queryType]
|
||||||
if loaded {
|
if loaded {
|
||||||
@@ -170,6 +192,10 @@ func (n *NetworkStrategy) UnmarshalJSON(content []byte) error {
|
|||||||
return nil
|
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
|
type InterfaceType C.InterfaceType
|
||||||
|
|
||||||
func (t InterfaceType) Build() C.InterfaceType {
|
func (t InterfaceType) Build() C.InterfaceType {
|
||||||
@@ -193,3 +219,9 @@ func (t *InterfaceType) UnmarshalJSON(content []byte) error {
|
|||||||
*t = InterfaceType(interfaceType)
|
*t = InterfaceType(interfaceType)
|
||||||
return nil
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+13
-1
@@ -1,13 +1,16 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
type _UDPOverTCPOptions struct {
|
type _UDPOverTCPOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
Version uint8 `json:"version,omitempty"`
|
Version uint8 `json:"version,omitempty" enum:"1,2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDPOverTCPOptions _UDPOverTCPOptions
|
type UDPOverTCPOptions _UDPOverTCPOptions
|
||||||
@@ -28,3 +31,12 @@ func (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
return json.UnmarshalDisallowUnknownFields(bytes, (*_UDPOverTCPOptions)(o))
|
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
|
||||||
|
}
|
||||||
|
|||||||
+13
-1
@@ -1,6 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -13,7 +16,7 @@ const (
|
|||||||
|
|
||||||
type _USBIPServerServiceOptions struct {
|
type _USBIPServerServiceOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Provider string `json:"provider,omitempty"`
|
Provider string `json:"provider,omitempty" enum:"default,dynamic"`
|
||||||
Options any `json:"-"`
|
Options any `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +52,15 @@ func (o *USBIPServerServiceOptions) UnmarshalJSON(content []byte) error {
|
|||||||
return nil
|
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 {
|
type USBIPClientServiceOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/schema"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -9,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _V2RayTransportOptions struct {
|
type _V2RayTransportOptions struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type" enum:"http,ws,quic,grpc,httpupgrade"`
|
||||||
HTTPOptions V2RayHTTPOptions `json:"-"`
|
HTTPOptions V2RayHTTPOptions `json:"-"`
|
||||||
WebsocketOptions V2RayWebsocketOptions `json:"-"`
|
WebsocketOptions V2RayWebsocketOptions `json:"-"`
|
||||||
QUICOptions V2RayQUICOptions `json:"-"`
|
QUICOptions V2RayQUICOptions `json:"-"`
|
||||||
@@ -67,6 +70,18 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
return nil
|
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 {
|
type V2RayHTTPOptions struct {
|
||||||
Host badoption.Listable[string] `json:"host,omitempty"`
|
Host badoption.Listable[string] `json:"host,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
|
|||||||
+2
-2
@@ -18,13 +18,13 @@ type VMessOutboundOptions struct {
|
|||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
UUID string `json:"uuid"`
|
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"`
|
AlterId int `json:"alter_id,omitempty"`
|
||||||
GlobalPadding bool `json:"global_padding,omitempty"`
|
GlobalPadding bool `json:"global_padding,omitempty"`
|
||||||
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
PacketEncoding string `json:"packet_encoding,omitempty"`
|
PacketEncoding string `json:"packet_encoding,omitempty" enum:"packetaddr,xudp"`
|
||||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
domainStrategy: C.DomainStrategy(options.DomainStrategy),
|
domainStrategy: C.DomainStrategy(options.DomainStrategy),
|
||||||
fallbackDelay: time.Duration(options.FallbackDelay),
|
fallbackDelay: time.Duration(options.FallbackDelay),
|
||||||
dialer: outboundDialer.(dialer.ParallelInterfaceDialer),
|
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
|
//nolint:staticcheck
|
||||||
if options.ProxyProtocol != 0 {
|
if options.ProxyProtocol != 0 {
|
||||||
|
|||||||
@@ -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}))
|
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 == "" {
|
if listenAddress.IsUnspecified() && s.options.BindInterface == "" && s.options.RoutingMark == 0 && s.options.NetNs == "" {
|
||||||
udpDialer, dialerErr := dialer.NewDefault(s.ctx, option.DialerOptions{
|
udpDialer, dialerErr := dialer.NewDefault(s.ctx, option.DialerOptions{
|
||||||
ReuseAddr: s.options.ReuseAddr,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
UDPFragment: s.options.UDPFragment,
|
ReuseAddr: s.options.ReuseAddr,
|
||||||
UDPFragmentDefault: s.options.UDPFragmentDefault,
|
UDPFragment: s.options.UDPFragment,
|
||||||
|
UDPFragmentDefault: s.options.UDPFragmentDefault,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if dialerErr != nil {
|
if dialerErr != nil {
|
||||||
return dialerErr
|
return dialerErr
|
||||||
|
|||||||
@@ -320,7 +320,9 @@ func (t *Endpoint) start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
systemDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{
|
systemDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{
|
||||||
BindInterface: tunName,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
|
BindInterface: tunName,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = systemTun.Close()
|
_ = systemTun.Close()
|
||||||
|
|||||||
@@ -98,7 +98,9 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
Dialer: outboundDialer,
|
Dialer: outboundDialer,
|
||||||
CreateDialer: func(interfaceName string) N.Dialer {
|
CreateDialer: func(interfaceName string) N.Dialer {
|
||||||
return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{
|
return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{
|
||||||
BindInterface: interfaceName,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
|
BindInterface: interfaceName,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
Name: options.Name,
|
Name: options.Name,
|
||||||
|
|||||||
+12
-10
@@ -74,7 +74,9 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
|||||||
RuleActionRouteOptions: routeOptions,
|
RuleActionRouteOptions: routeOptions,
|
||||||
}, nil
|
}, nil
|
||||||
case C.RuleActionTypeDirect:
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -141,16 +143,16 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
|
|||||||
}
|
}
|
||||||
case C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeEvaluate:
|
||||||
return &RuleActionEvaluate{
|
return &RuleActionEvaluate{
|
||||||
Server: action.RouteOptions.Server,
|
Server: action.EvaluateOptions.Server,
|
||||||
Tag: action.RouteOptions.Tag,
|
Tag: action.EvaluateOptions.Tag,
|
||||||
Speculative: action.RouteOptions.Speculative,
|
Speculative: action.EvaluateOptions.Speculative,
|
||||||
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
||||||
Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
|
Strategy: C.DomainStrategy(action.EvaluateOptions.Strategy),
|
||||||
Timeout: time.Duration(action.RouteOptions.Timeout),
|
Timeout: time.Duration(action.EvaluateOptions.Timeout),
|
||||||
DisableCache: action.RouteOptions.DisableCache,
|
DisableCache: action.EvaluateOptions.DisableCache,
|
||||||
DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache,
|
DisableOptimisticCache: action.EvaluateOptions.DisableOptimisticCache,
|
||||||
RewriteTTL: action.RouteOptions.RewriteTTL,
|
RewriteTTL: action.EvaluateOptions.RewriteTTL,
|
||||||
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.EvaluateOptions.ClientSubnet)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case C.RuleActionTypeRespond:
|
case C.RuleActionTypeRespond:
|
||||||
|
|||||||
+10
-2
@@ -32,10 +32,14 @@ func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DN
|
|||||||
return nil, E.New("`race` requires `match_response`")
|
return nil, E.New("`race` requires `match_response`")
|
||||||
}
|
}
|
||||||
switch options.DefaultOptions.Action {
|
switch options.DefaultOptions.Action {
|
||||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
case "", C.RuleActionTypeRoute:
|
||||||
if options.DefaultOptions.RouteOptions.Server == "" && checkServer {
|
if options.DefaultOptions.RouteOptions.Server == "" && checkServer {
|
||||||
return nil, E.New("missing server field")
|
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)
|
return NewDefaultDNSRule(ctx, logger, options.DefaultOptions, legacyDNSMode)
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
@@ -50,10 +54,14 @@ func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DN
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch options.LogicalOptions.Action {
|
switch options.LogicalOptions.Action {
|
||||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
case "", C.RuleActionTypeRoute:
|
||||||
if options.LogicalOptions.RouteOptions.Server == "" && checkServer {
|
if options.LogicalOptions.RouteOptions.Server == "" && checkServer {
|
||||||
return nil, E.New("missing server field")
|
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)
|
return NewLogicalDNSRule(ctx, logger, options.LogicalOptions, legacyDNSMode)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)<<bits - 1
|
||||||
|
node.Maximum = &maximumValue
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationPattern = `^[-+]?(((\d+(\.\d*)?|\.\d+)(ns|us|µs|μs|ms|s|m|h|d))+|0)$`
|
||||||
|
|
||||||
|
func DurationNode() *Node {
|
||||||
|
return &Node{Type: "string", Pattern: durationPattern}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringEnum(values ...string) *Node {
|
||||||
|
anyValues := make([]any, 0, len(values))
|
||||||
|
for _, value := range values {
|
||||||
|
anyValues = append(anyValues, value)
|
||||||
|
}
|
||||||
|
return &Node{Type: "string", Enum: anyValues}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringConst(value string) *Node {
|
||||||
|
return &Node{Const: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AnyOf(nodes ...*Node) *Node {
|
||||||
|
return &Node{AnyOf: nodes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OneOf(nodes ...*Node) *Node {
|
||||||
|
return &Node{OneOf: nodes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListableOf(element *Node) *Node {
|
||||||
|
return AnyOf(element, &Node{Type: "array", Items: element})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RefNode(name string) *Node {
|
||||||
|
return &Node{Ref: "#/$defs/" + name}
|
||||||
|
}
|
||||||
@@ -131,8 +131,10 @@ func (t *Transport) updateTransports(link *TransportLink) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
||||||
BindInterface: link.iif.Name,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
UDPFragmentDefault: true,
|
BindInterface: link.iif.Name,
|
||||||
|
UDPFragmentDefault: true,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
var transports []adapter.DNSTransport
|
var transports []adapter.DNSTransport
|
||||||
for _, address := range link.address {
|
for _, address := range link.address {
|
||||||
|
|||||||
+3
-1
@@ -52,7 +52,9 @@ func TestTCPSlowOpen(t *testing.T) {
|
|||||||
ServerPort: serverPort,
|
ServerPort: serverPort,
|
||||||
},
|
},
|
||||||
DialerOptions: option.DialerOptions{
|
DialerOptions: option.DialerOptions{
|
||||||
TCPFastOpen: true,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
|
TCPFastOpen: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
Password: password,
|
Password: password,
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
|
|||||||
options.MTU = DefaultMTU
|
options.MTU = DefaultMTU
|
||||||
}
|
}
|
||||||
interfaceDialer, err := dialer.NewDefault(options.Context, option.DialerOptions{
|
interfaceDialer, err := dialer.NewDefault(options.Context, option.DialerOptions{
|
||||||
BindInterface: options.Name,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
|
BindInterface: options.Name,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
|
|||||||
options.MTU = DefaultMTU
|
options.MTU = DefaultMTU
|
||||||
}
|
}
|
||||||
interfaceDialer, err := dialer.NewDefault(options.Context, option.DialerOptions{
|
interfaceDialer, err := dialer.NewDefault(options.Context, option.DialerOptions{
|
||||||
BindInterface: options.Name,
|
AbstractDialerOptions: option.AbstractDialerOptions{
|
||||||
|
BindInterface: options.Name,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user