123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- package option
- import (
- "context"
- "net/netip"
- "net/url"
- C "github.com/sagernet/sing-box/constant"
- "github.com/sagernet/sing-box/experimental/deprecated"
- "github.com/sagernet/sing/common"
- 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"
- "github.com/miekg/dns"
- )
- type RawDNSOptions struct {
- Servers []DNSServerOptions `json:"servers,omitempty"`
- Rules []DNSRule `json:"rules,omitempty"`
- Final string `json:"final,omitempty"`
- ReverseMapping bool `json:"reverse_mapping,omitempty"`
- DNSClientOptions
- }
- type LegacyDNSOptions struct {
- FakeIP *LegacyDNSFakeIPOptions `json:"fakeip,omitempty"`
- }
- type DNSOptions struct {
- RawDNSOptions
- LegacyDNSOptions
- }
- type contextKeyDontUpgrade struct{}
- func ContextWithDontUpgrade(ctx context.Context) context.Context {
- return context.WithValue(ctx, (*contextKeyDontUpgrade)(nil), true)
- }
- func dontUpgradeFromContext(ctx context.Context) bool {
- return ctx.Value((*contextKeyDontUpgrade)(nil)) == true
- }
- func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
- err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)
- if err != nil {
- return err
- }
- dontUpgrade := dontUpgradeFromContext(ctx)
- legacyOptions := o.LegacyDNSOptions
- if !dontUpgrade {
- if o.FakeIP != nil && o.FakeIP.Enabled {
- deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions)
- ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP)
- }
- o.LegacyDNSOptions = LegacyDNSOptions{}
- }
- err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
- if err != nil {
- return err
- }
- if !dontUpgrade {
- rcodeMap := make(map[string]int)
- o.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool {
- if it.Type == C.DNSTypeLegacyRcode {
- rcodeMap[it.Tag] = it.Options.(int)
- return false
- }
- return true
- })
- if len(rcodeMap) > 0 {
- for i := 0; i < len(o.Rules); i++ {
- rewriteRcode(rcodeMap, &o.Rules[i])
- }
- }
- }
- return nil
- }
- func rewriteRcode(rcodeMap map[string]int, rule *DNSRule) {
- switch rule.Type {
- case C.RuleTypeDefault:
- rewriteRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction)
- case C.RuleTypeLogical:
- rewriteRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction)
- }
- }
- func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {
- if ruleAction.Action != C.RuleActionTypeRoute {
- return
- }
- rcode, loaded := rcodeMap[ruleAction.RouteOptions.Server]
- if !loaded {
- return
- }
- ruleAction.Action = C.RuleActionTypePredefined
- ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
- return
- }
- type DNSClientOptions struct {
- Strategy DomainStrategy `json:"strategy,omitempty"`
- DisableCache bool `json:"disable_cache,omitempty"`
- DisableExpire bool `json:"disable_expire,omitempty"`
- IndependentCache bool `json:"independent_cache,omitempty"`
- CacheCapacity uint32 `json:"cache_capacity,omitempty"`
- ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
- }
- type LegacyDNSFakeIPOptions struct {
- Enabled bool `json:"enabled,omitempty"`
- Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"`
- Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"`
- }
- type DNSTransportOptionsRegistry interface {
- 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) {
- switch o.Type {
- case C.DNSTypeLegacy:
- o.Type = ""
- }
- 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:
- o.Type = C.DNSTypeLegacy
- options = new(LegacyDNSServerOptions)
- deprecated.Report(ctx, deprecated.OptionLegacyDNSTransport)
- 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
- if o.Type == C.DNSTypeLegacy && !dontUpgradeFromContext(ctx) {
- err = o.Upgrade(ctx)
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
- if o.Type != C.DNSTypeLegacy {
- return nil
- }
- options := o.Options.(*LegacyDNSServerOptions)
- serverURL, _ := url.Parse(options.Address)
- var serverType string
- if serverURL.Scheme != "" {
- serverType = serverURL.Scheme
- } else {
- switch options.Address {
- case "local", "fakeip":
- serverType = options.Address
- default:
- serverType = C.DNSTypeUDP
- }
- }
- remoteOptions := RemoteDNSServerOptions{
- LocalDNSServerOptions: LocalDNSServerOptions{
- DialerOptions: DialerOptions{
- Detour: options.Detour,
- DomainResolver: &DomainResolveOptions{
- Server: options.AddressResolver,
- Strategy: options.AddressStrategy,
- },
- FallbackDelay: options.AddressFallbackDelay,
- },
- Legacy: true,
- LegacyStrategy: options.Strategy,
- LegacyDefaultDialer: options.Detour == "",
- LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
- },
- LegacyAddressResolver: options.AddressResolver,
- LegacyAddressStrategy: options.AddressStrategy,
- LegacyAddressFallbackDelay: options.AddressFallbackDelay,
- }
- switch serverType {
- case C.DNSTypeLocal:
- o.Type = C.DNSTypeLocal
- o.Options = &remoteOptions.LocalDNSServerOptions
- case C.DNSTypeUDP:
- o.Type = C.DNSTypeUDP
- o.Options = &remoteOptions
- var serverAddr M.Socksaddr
- if serverURL.Scheme == "" {
- serverAddr = M.ParseSocksaddr(options.Address)
- } else {
- serverAddr = M.ParseSocksaddr(serverURL.Host)
- }
- if !serverAddr.IsValid() {
- return E.New("invalid server address")
- }
- remoteOptions.Server = serverAddr.AddrString()
- if serverAddr.Port != 0 && serverAddr.Port != 53 {
- remoteOptions.ServerPort = serverAddr.Port
- }
- case C.DNSTypeTCP:
- o.Type = C.DNSTypeTCP
- o.Options = &remoteOptions
- serverAddr := M.ParseSocksaddr(serverURL.Host)
- if !serverAddr.IsValid() {
- return E.New("invalid server address")
- }
- remoteOptions.Server = serverAddr.AddrString()
- if serverAddr.Port != 0 && serverAddr.Port != 53 {
- remoteOptions.ServerPort = serverAddr.Port
- }
- case C.DNSTypeTLS, C.DNSTypeQUIC:
- o.Type = serverType
- serverAddr := M.ParseSocksaddr(serverURL.Host)
- if !serverAddr.IsValid() {
- return E.New("invalid server address")
- }
- remoteOptions.Server = serverAddr.AddrString()
- if serverAddr.Port != 0 && serverAddr.Port != 853 {
- remoteOptions.ServerPort = serverAddr.Port
- }
- o.Options = &RemoteTLSDNSServerOptions{
- RemoteDNSServerOptions: remoteOptions,
- }
- case C.DNSTypeHTTPS, C.DNSTypeHTTP3:
- o.Type = serverType
- httpsOptions := RemoteHTTPSDNSServerOptions{
- RemoteTLSDNSServerOptions: RemoteTLSDNSServerOptions{
- RemoteDNSServerOptions: remoteOptions,
- },
- }
- o.Options = &httpsOptions
- serverAddr := M.ParseSocksaddr(serverURL.Host)
- if !serverAddr.IsValid() {
- return E.New("invalid server address")
- }
- httpsOptions.Server = serverAddr.AddrString()
- if serverAddr.Port != 0 && serverAddr.Port != 443 {
- httpsOptions.ServerPort = serverAddr.Port
- }
- if serverURL.Path != "/dns-query" {
- httpsOptions.Path = serverURL.Path
- }
- case "rcode":
- var rcode int
- switch serverURL.Host {
- case "success":
- rcode = dns.RcodeSuccess
- case "format_error":
- rcode = dns.RcodeFormatError
- case "server_failure":
- rcode = dns.RcodeServerFailure
- case "name_error":
- rcode = dns.RcodeNameError
- case "not_implemented":
- rcode = dns.RcodeNotImplemented
- case "refused":
- rcode = dns.RcodeRefused
- default:
- return E.New("unknown rcode: ", serverURL.Host)
- }
- o.Type = C.DNSTypeLegacyRcode
- o.Options = rcode
- case C.DNSTypeDHCP:
- o.Type = C.DNSTypeDHCP
- dhcpOptions := DHCPDNSServerOptions{}
- if serverURL.Host != "" && serverURL.Host != "auto" {
- dhcpOptions.Interface = serverURL.Host
- }
- o.Options = &dhcpOptions
- case C.DNSTypeFakeIP:
- o.Type = C.DNSTypeFakeIP
- fakeipOptions := FakeIPDNSServerOptions{}
- if legacyOptions, loaded := ctx.Value((*LegacyDNSFakeIPOptions)(nil)).(*LegacyDNSFakeIPOptions); loaded {
- fakeipOptions.Inet4Range = legacyOptions.Inet4Range
- fakeipOptions.Inet6Range = legacyOptions.Inet6Range
- }
- o.Options = &fakeipOptions
- default:
- return E.New("unsupported DNS server scheme: ", serverType)
- }
- return nil
- }
- 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 M.IsDomainName(o.Server)
- }
- func (o *DNSServerAddressOptions) TakeServerOptions() ServerOptions {
- return ServerOptions(*o)
- }
- func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) {
- *o = DNSServerAddressOptions(options)
- }
- type LegacyDNSServerOptions struct {
- Address string `json:"address"`
- AddressResolver string `json:"address_resolver,omitempty"`
- AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
- AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"`
- Strategy DomainStrategy `json:"strategy,omitempty"`
- Detour string `json:"detour,omitempty"`
- ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
- }
- type HostsDNSServerOptions struct {
- Path badoption.Listable[string] `json:"path,omitempty"`
- Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
- }
- type LocalDNSServerOptions struct {
- DialerOptions
- Legacy bool `json:"-"`
- LegacyStrategy DomainStrategy `json:"-"`
- LegacyDefaultDialer bool `json:"-"`
- LegacyClientSubnet netip.Prefix `json:"-"`
- }
- type RemoteDNSServerOptions struct {
- LocalDNSServerOptions
- DNSServerAddressOptions
- LegacyAddressResolver string `json:"-"`
- LegacyAddressStrategy DomainStrategy `json:"-"`
- LegacyAddressFallbackDelay badoption.Duration `json:"-"`
- }
- 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"`
- Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"`
- }
- type DHCPDNSServerOptions struct {
- LocalDNSServerOptions
- Interface string `json:"interface,omitempty"`
- }
|