package option import ( "context" "net/netip" "reflect" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/service" ) type RawDNSOptions struct { Servers []DNSServerOptions `json:"servers,omitempty"` Rules []DNSRule `json:"rules,omitempty"` Final string `json:"final,omitempty" reference:"dns_server"` ReverseMapping bool `json:"reverse_mapping,omitempty"` DNSClientOptions } type DNSOptions struct { RawDNSOptions } const ( legacyDNSFakeIPRemovedMessage = "legacy DNS fakeip options are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats" legacyDNSServerRemovedMessage = "legacy DNS server formats are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats" ) type removedLegacyDNSOptions struct { FakeIP json.RawMessage `json:"fakeip,omitempty"` } func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { var legacyOptions removedLegacyDNSOptions err := json.UnmarshalContext(ctx, content, &legacyOptions) if err != nil { return err } if len(legacyOptions.FakeIP) != 0 { return E.New(legacyDNSFakeIPRemovedMessage) } return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) } func (o DNSOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { return builder.Define("DNS", func() (*schema.Node, error) { node := schema.StrictObject() err := builder.FlattenStruct(node, reflect.TypeFor[RawDNSOptions]()) if err != nil { return nil, err } return node, nil }) } type DNSClientOptions struct { Strategy DomainStrategy `json:"strategy,omitempty"` Timeout badoption.Duration `json:"timeout,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` IndependentCache bool `json:"independent_cache,omitempty" schema:"omit"` CacheCapacity uint32 `json:"cache_capacity,omitempty"` Optimistic *OptimisticDNSOptions `json:"optimistic,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type _OptimisticDNSOptions struct { Enabled bool `json:"enabled,omitempty"` Timeout badoption.Duration `json:"timeout,omitempty"` } type OptimisticDNSOptions _OptimisticDNSOptions func (o OptimisticDNSOptions) MarshalJSON() ([]byte, error) { if o.Timeout == 0 { return json.Marshal(o.Enabled) } return json.Marshal((_OptimisticDNSOptions)(o)) } func (o *OptimisticDNSOptions) UnmarshalJSON(bytes []byte) error { err := json.Unmarshal(bytes, &o.Enabled) if err == nil { return nil } return json.UnmarshalDisallowUnknownFields(bytes, (*_OptimisticDNSOptions)(o)) } func (o OptimisticDNSOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { objectForm := schema.StrictObject() err := builder.FlattenStruct(objectForm, reflect.TypeFor[OptimisticDNSOptions]()) if err != nil { return nil, err } return schema.AnyOf(schema.BooleanNode(), objectForm), nil } type DNSTransportOptionsRegistry interface { OptionTypes() []string CreateOptions(transportType string) (any, bool) } type _DNSServerOptions struct { Type string `json:"type,omitempty"` Tag string `json:"tag,omitempty"` Options any `json:"-"` } type DNSServerOptions _DNSServerOptions func (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { return badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options) } func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { err := json.UnmarshalContext(ctx, content, (*_DNSServerOptions)(o)) if err != nil { return err } registry := service.FromContext[DNSTransportOptionsRegistry](ctx) if registry == nil { return E.New("missing DNS transport options registry in context") } var options any switch o.Type { case "", C.DNSTypeLegacy: return E.New(legacyDNSServerRemovedMessage) default: var loaded bool options, loaded = registry.CreateOptions(o.Type) if !loaded { return E.New("unknown transport type: ", o.Type) } } err = badjson.UnmarshallExcludedContext(ctx, content, (*_DNSServerOptions)(o), options) if err != nil { return err } o.Options = options return nil } func (o DNSServerOptions) DescribeSchema(builder schema.Builder) (*schema.Node, error) { return builder.Define("DNSServer", func() (*schema.Node, error) { registry := service.FromContext[DNSTransportOptionsRegistry](builder.Context()) if registry == nil { return nil, E.New("missing DNS transport options registry in context") } return registryUnion(builder, registry, nil, true) }) } type DNSServerAddressOptions struct { Server string `json:"server"` ServerPort uint16 `json:"server_port,omitempty"` } func (o DNSServerAddressOptions) Build() M.Socksaddr { return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) } func (o DNSServerAddressOptions) ServerIsDomain() bool { return o.Build().IsDomain() } func (o *DNSServerAddressOptions) TakeServerOptions() ServerOptions { return ServerOptions(*o) } func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) { *o = DNSServerAddressOptions(options) } type HostsDNSServerOptions struct { Path badoption.Listable[string] `json:"path,omitempty"` Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` } type RawLocalDNSServerOptions struct { DialerOptions } type LocalDNSServerOptions struct { RawLocalDNSServerOptions PreferGo bool `json:"prefer_go,omitempty"` NeighborDomain badoption.Listable[string] `json:"neighbor_domain,omitempty"` } type RemoteDNSServerOptions struct { RawLocalDNSServerOptions DNSServerAddressOptions } type RemoteTLSDNSServerOptions struct { RemoteDNSServerOptions OutboundTLSOptionsContainer } type RemoteHTTPSDNSServerOptions struct { RemoteTLSDNSServerOptions Path string `json:"path,omitempty"` Method string `json:"method,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` } type FakeIPDNSServerOptions struct { Inet4Range *badoption.Prefix `json:"inet4_range,omitempty" examples:"198.18.0.0/15"` Inet6Range *badoption.Prefix `json:"inet6_range,omitempty" examples:"fc00::/18"` } type DHCPDNSServerOptions struct { LocalDNSServerOptions Interface string `json:"interface,omitempty"` } type MDNSDNSServerOptions struct { LocalDNSServerOptions Interface badoption.Listable[string] `json:"interface,omitempty"` }