mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-02 11:27:35 +03:00
389 lines
12 KiB
Go
389 lines
12 KiB
Go
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"`
|
|
}
|