diff --git a/adapter/network.go b/adapter/network.go index 3adfaaeb..50670831 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -29,12 +29,14 @@ type NetworkManager interface { } type NetworkOptions struct { - NetworkStrategy *C.NetworkStrategy - NetworkType []C.InterfaceType - FallbackNetworkType []C.InterfaceType - FallbackDelay time.Duration - BindInterface string - RoutingMark uint32 + BindInterface string + RoutingMark uint32 + DomainResolver string + DomainResolveOptions DNSQueryOptions + NetworkStrategy *C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration } type InterfaceUpdateListener interface { diff --git a/box.go b/box.go index 05431663..25a84742 100644 --- a/box.go +++ b/box.go @@ -187,7 +187,7 @@ func New(options Options) (*Box, error) { transportOptions.Options, ) if err != nil { - return nil, E.Cause(err, "initialize inbound[", i, "]") + return nil, E.Cause(err, "initialize DNS server[", i, "]") } } err = dnsRouter.Initialize(dnsOptions.Rules) @@ -217,7 +217,7 @@ func New(options Options) (*Box, error) { endpointOptions.Options, ) if err != nil { - return nil, E.Cause(err, "initialize inbound[", i, "]") + return nil, E.Cause(err, "initialize endpoint[", i, "]") } } for i, inboundOptions := range options.Inbounds { @@ -316,7 +316,7 @@ func New(options Options) (*Box, error) { } } if ntpOptions.Enabled { - ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain()) if err != nil { return nil, E.Cause(err, "create NTP service") } diff --git a/common/dialer/default.go b/common/dialer/default.go index 77536c43..50244ac5 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -35,7 +35,6 @@ type DefaultDialer struct { udpListener net.ListenConfig udpAddr4 string udpAddr6 string - isWireGuardListener bool networkManager adapter.NetworkManager networkStrategy *C.NetworkStrategy defaultNetworkStrategy bool @@ -183,11 +182,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial } setMultiPathTCP(&dialer4) } - if options.IsWireGuardListener { - for _, controlFn := range WgControlFns { - listener.Control = control.Append(listener.Control, controlFn) - } - } tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) if err != nil { return nil, err @@ -204,7 +198,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial udpListener: listener, udpAddr4: udpAddr4, udpAddr6: udpAddr6, - isWireGuardListener: options.IsWireGuardListener, networkManager: networkManager, networkStrategy: networkStrategy, defaultNetworkStrategy: defaultNetworkStrategy, diff --git a/common/dialer/detour.go b/common/dialer/detour.go index c1d40faa..e4a46049 100644 --- a/common/dialer/detour.go +++ b/common/dialer/detour.go @@ -29,16 +29,18 @@ func (d *DetourDialer) Start() error { } func (d *DetourDialer) Dialer() (N.Dialer, error) { - d.initOnce.Do(func() { - var loaded bool - d.dialer, loaded = d.outboundManager.Outbound(d.detour) - if !loaded { - d.initErr = E.New("outbound detour not found: ", d.detour) - } - }) + d.initOnce.Do(d.init) return d.dialer, d.initErr } +func (d *DetourDialer) init() { + var loaded bool + d.dialer, loaded = d.outboundManager.Outbound(d.detour) + if !loaded { + d.initErr = E.New("outbound detour not found: ", d.detour) + } +} + func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { dialer, err := d.Dialer() if err != nil { diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index f63e3864..812e5575 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -8,6 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -15,60 +16,105 @@ import ( "github.com/sagernet/sing/service" ) -func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { - if options.IsWireGuardListener { - return NewDefault(ctx, options) - } +type Options struct { + Context context.Context + Options option.DialerOptions + RemoteIsDomain bool + DirectResolver bool + ResolverOnDetour bool +} + +// TODO: merge with NewWithOptions +func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) { + return NewWithOptions(Options{ + Context: ctx, + Options: options, + RemoteIsDomain: remoteIsDomain, + }) +} + +func NewWithOptions(options Options) (N.Dialer, error) { + dialOptions := options.Options var ( dialer N.Dialer err error ) - if options.Detour == "" { - dialer, err = NewDefault(ctx, options) - if err != nil { - return nil, err - } - } else { - outboundManager := service.FromContext[adapter.OutboundManager](ctx) + if dialOptions.Detour != "" { + outboundManager := service.FromContext[adapter.OutboundManager](options.Context) if outboundManager == nil { return nil, E.New("missing outbound manager") } - dialer = NewDetour(outboundManager, options.Detour) - } - if options.Detour == "" { - router := service.FromContext[adapter.DNSRouter](ctx) - if router != nil { - dialer = NewResolveDialer( - router, - dialer, - options.Detour == "" && !options.TCPFastOpen, - C.DomainStrategy(options.DomainStrategy), - time.Duration(options.FallbackDelay)) + dialer = NewDetour(outboundManager, dialOptions.Detour) + } else { + dialer, err = NewDefault(options.Context, dialOptions) + if err != nil { + return nil, err } } + if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) { + networkManager := service.FromContext[adapter.NetworkManager](options.Context) + dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context) + var defaultOptions adapter.NetworkOptions + if networkManager != nil { + defaultOptions = networkManager.DefaultOptions() + } + var ( + server string + dnsQueryOptions adapter.DNSQueryOptions + resolveFallbackDelay time.Duration + ) + if dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "" { + var transport adapter.DNSTransport + if !options.DirectResolver { + var loaded bool + transport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server) + if !loaded { + return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server) + } + } + var strategy C.DomainStrategy + if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) { + strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy) + } else if + //nolint:staticcheck + dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { + //nolint:staticcheck + strategy = C.DomainStrategy(dialOptions.DomainStrategy) + } + server = dialOptions.DomainResolver.Server + dnsQueryOptions = adapter.DNSQueryOptions{ + Transport: transport, + Strategy: strategy, + DisableCache: dialOptions.DomainResolver.DisableCache, + RewriteTTL: dialOptions.DomainResolver.RewriteTTL, + ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}), + } + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else if options.DirectResolver { + return nil, E.New("missing domain resolver for domain server address") + } else if defaultOptions.DomainResolver != "" { + dnsQueryOptions = defaultOptions.DomainResolveOptions + transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver) + if !loaded { + return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver) + } + dnsQueryOptions.Transport = transport + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else { + deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + } + dialer = NewResolveDialer( + options.Context, + dialer, + dialOptions.Detour == "" && !dialOptions.TCPFastOpen, + server, + dnsQueryOptions, + resolveFallbackDelay, + ) + } return dialer, nil } -func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) { - if options.Detour != "" { - return nil, E.New("`detour` is not supported in direct context") - } - if options.IsWireGuardListener { - return NewDefault(ctx, options) - } - dialer, err := NewDefault(ctx, options) - if err != nil { - return nil, err - } - return NewResolveParallelInterfaceDialer( - service.FromContext[adapter.DNSRouter](ctx), - dialer, - true, - C.DomainStrategy(options.DomainStrategy), - time.Duration(options.FallbackDelay), - ), nil -} - type ParallelInterfaceDialer interface { N.Dialer DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index 3d667a6c..66b74e3c 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -3,14 +3,17 @@ package dialer import ( "context" "net" + "sync" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) var ( @@ -18,21 +21,37 @@ var ( _ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil) ) +type ResolveDialer interface { + N.Dialer + QueryOptions() adapter.DNSQueryOptions +} + +type ParallelInterfaceResolveDialer interface { + ParallelInterfaceDialer + QueryOptions() adapter.DNSQueryOptions +} + type resolveDialer struct { + transport adapter.DNSTransportManager + router adapter.DNSRouter dialer N.Dialer parallel bool - router adapter.DNSRouter - strategy C.DomainStrategy + server string + initOnce sync.Once + initErr error + queryOptions adapter.DNSQueryOptions fallbackDelay time.Duration } -func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer { +func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer { return &resolveDialer{ - dialer, - parallel, - router, - strategy, - fallbackDelay, + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, } } @@ -41,41 +60,68 @@ type resolveParallelNetworkDialer struct { dialer ParallelInterfaceDialer } -func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer { +func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer { return &resolveParallelNetworkDialer{ resolveDialer{ - dialer, - parallel, - router, - strategy, - fallbackDelay, + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, }, dialer, } } +func (d *resolveDialer) initialize() error { + d.initOnce.Do(d.initServer) + return d.initErr +} + +func (d *resolveDialer) initServer() { + if d.server == "" { + return + } + transport, loaded := d.transport.Transport(d.server) + if !loaded { + d.initErr = E.New("domain resolver not found: " + d.server) + return + } + d.queryOptions.Transport = transport +} + func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } if d.parallel { - return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) + return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) } else { return N.DialSerial(ctx, d.dialer, network, destination, addresses) } } func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } @@ -86,14 +132,24 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } +func (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions { + return d.queryOptions +} + +func (d *resolveDialer) Upstream() any { + return d.dialer +} + func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{ - Strategy: d.strategy, - }) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } @@ -101,21 +157,28 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context fallbackDelay = d.fallbackDelay } if d.parallel { - return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } } func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } + if fallbackDelay == 0 { + fallbackDelay = d.fallbackDelay + } conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err != nil { return nil, err @@ -123,6 +186,10 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *resolveDialer) Upstream() any { +func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions { + return d.queryOptions +} + +func (d *resolveParallelNetworkDialer) Upstream() any { return d.dialer } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index cf429815..912d13dd 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -101,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb tlsConfig.ShortIds[shortID] = true } - handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain()) if err != nil { return nil, err } diff --git a/dns/transport_adapter.go b/dns/transport_adapter.go index 02c84621..47345709 100644 --- a/dns/transport_adapter.go +++ b/dns/transport_adapter.go @@ -27,9 +27,14 @@ func NewTransportAdapter(transportType string, transportTag string, dependencies } func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter { + var dependencies []string + if localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != "" { + dependencies = append(dependencies, localOptions.DomainResolver.Server) + } return TransportAdapter{ transportType: transportType, transportTag: transportTag, + dependencies: dependencies, strategy: C.DomainStrategy(localOptions.LegacyStrategy), clientSubnet: localOptions.LegacyClientSubnet, } @@ -37,8 +42,11 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter { var dependencies []string - if remoteOptions.AddressResolver != "" { - dependencies = []string{remoteOptions.AddressResolver} + if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" { + dependencies = append(dependencies, remoteOptions.DomainResolver.Server) + } + if remoteOptions.LegacyAddressResolver != "" { + dependencies = append(dependencies, remoteOptions.LegacyAddressResolver) } return TransportAdapter{ transportType: transportType, diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go index 14e1188d..0b15c7ea 100644 --- a/dns/transport_dialer.go +++ b/dns/transport_dialer.go @@ -19,29 +19,39 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) ( if options.LegacyDefaultDialer { return dialer.NewDefaultOutbound(ctx), nil } else { - return dialer.New(ctx, options.DialerOptions) + return dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + DirectResolver: true, + }) } } func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) { - transportDialer, err := NewLocalDialer(ctx, options.LocalDNSServerOptions) - if err != nil { - return nil, err - } - if options.AddressResolver != "" { - transport := service.FromContext[adapter.DNSTransportManager](ctx) - resolverTransport, loaded := transport.Transport(options.AddressResolver) - if !loaded { - return nil, E.New("address resolver not found: ", options.AddressResolver) + if options.LegacyDefaultDialer { + transportDialer := dialer.NewDefaultOutbound(ctx) + if options.LegacyAddressResolver != "" { + transport := service.FromContext[adapter.DNSTransportManager](ctx) + resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver) + if !loaded { + return nil, E.New("address resolver not found: ", options.LegacyAddressResolver) + } + transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay)) + } else if options.ServerIsDomain() { + return nil, E.New("missing address resolver for server: ", options.Server) } - transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay)) - } else if M.IsDomainName(options.Server) { - return nil, E.New("missing address resolver for server: ", options.Server) + return transportDialer, nil + } else { + return dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + DirectResolver: true, + }) } - return transportDialer, nil } -type TransportDialer struct { +type legacyTransportDialer struct { dialer N.Dialer dnsRouter adapter.DNSRouter transport adapter.DNSTransport @@ -49,8 +59,8 @@ type TransportDialer struct { fallbackDelay time.Duration } -func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer { - return &TransportDialer{ +func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer { + return &legacyTransportDialer{ dialer, dnsRouter, transport, @@ -59,7 +69,7 @@ func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport } } -func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if destination.IsIP() { return d.dialer.DialContext(ctx, network, destination) } @@ -73,7 +83,7 @@ func (d *TransportDialer) DialContext(ctx context.Context, network string, desti return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) } -func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if destination.IsIP() { return d.dialer.ListenPacket(ctx, destination) } @@ -88,6 +98,6 @@ func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksa return conn, err } -func (d *TransportDialer) Upstream() any { +func (d *legacyTransportDialer) Upstream() any { return d.dialer } diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index bf648365..d5c3ca48 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -148,10 +148,11 @@ var OptionTUNGSO = Note{ var OptionLegacyDNSTransport = Note{ Name: "legacy-dns-transport", - Description: "legacy DNS transport", + Description: "legacy DNS servers", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", - EnvName: "LEGACY_DNS_TRANSPORT", + EnvName: "LEGACY_DNS_SERVERS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats", } var OptionLegacyDNSFakeIPOptions = Note{ @@ -159,6 +160,23 @@ var OptionLegacyDNSFakeIPOptions = Note{ Description: "legacy DNS fakeip options", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats", +} + +var OptionOutboundDNSRuleItem = Note{ + Name: "outbound-dns-rule-item", + Description: "outbound DNS rule item", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", +} + +var OptionMissingDomainResolver = Note{ + Name: "missing-domain-resolver", + Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", } var Options = []Note{ @@ -172,4 +190,8 @@ var Options = []Note{ OptionWireGuardOutbound, OptionWireGuardGSO, OptionTUNGSO, + OptionLegacyDNSTransport, + OptionLegacyDNSFakeIPOptions, + OptionOutboundDNSRuleItem, + OptionMissingDomainResolver, } diff --git a/option/dns.go b/option/dns.go index 8c9b8bda..f4418468 100644 --- a/option/dns.go +++ b/option/dns.go @@ -4,7 +4,6 @@ import ( "context" "net/netip" "net/url" - "os" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" @@ -121,11 +120,6 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { if o.Type != C.DNSTypeLegacy { return nil } - defer func() { - encoder := json.NewEncoder(os.Stderr) - encoder.SetIndent("", " ") - encoder.Encode(o) - }() options := o.Options.(*LegacyDNSServerOptions) serverURL, _ := url.Parse(options.Address) var serverType string @@ -139,18 +133,34 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { serverType = C.DNSTypeUDP } } - remoteOptions := RemoteDNSServerOptions{ - LocalDNSServerOptions: LocalDNSServerOptions{ - DialerOptions: DialerOptions{ - Detour: options.Detour, + var remoteOptions RemoteDNSServerOptions + if options.Detour == "" { + remoteOptions = RemoteDNSServerOptions{ + LocalDNSServerOptions: LocalDNSServerOptions{ + LegacyStrategy: options.Strategy, + LegacyDefaultDialer: options.Detour == "", + LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), }, - LegacyStrategy: options.Strategy, - LegacyDefaultDialer: options.Detour == "", - LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), - }, - AddressResolver: options.AddressResolver, - AddressStrategy: options.AddressStrategy, - AddressFallbackDelay: options.AddressFallbackDelay, + LegacyAddressResolver: options.AddressResolver, + LegacyAddressStrategy: options.AddressStrategy, + LegacyAddressFallbackDelay: options.AddressFallbackDelay, + } + } else { + remoteOptions = RemoteDNSServerOptions{ + LocalDNSServerOptions: LocalDNSServerOptions{ + DialerOptions: DialerOptions{ + Detour: options.Detour, + DomainResolver: &DomainResolveOptions{ + Server: options.AddressResolver, + Strategy: options.AddressStrategy, + }, + FallbackDelay: options.AddressFallbackDelay, + }, + LegacyStrategy: options.Strategy, + LegacyDefaultDialer: options.Detour == "", + LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + }, + } } switch serverType { case C.DNSTypeLocal: @@ -291,9 +301,9 @@ type LocalDNSServerOptions struct { type RemoteDNSServerOptions struct { LocalDNSServerOptions ServerOptions - AddressResolver string `json:"address_resolver,omitempty"` - AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` - AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` + LegacyAddressResolver string `json:"-"` + LegacyAddressStrategy DomainStrategy `json:"-"` + LegacyAddressFallbackDelay badoption.Duration `json:"-"` } type RemoteTLSDNSServerOptions struct { diff --git a/option/outbound.go b/option/outbound.go index 5cadd3e2..99e3361a 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -77,12 +77,46 @@ type DialerOptions struct { TCPMultiPath bool `json:"tcp_multi_path,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` IsWireGuardListener bool `json:"-"` + + // Deprecated: migrated to domain resolver + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` +} + +type _DomainResolveOptions struct { + Server string `json:"server"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type DomainResolveOptions _DomainResolveOptions + +func (o DomainResolveOptions) MarshalJSON() ([]byte, error) { + if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && + !o.DisableCache && + o.RewriteTTL == nil && + o.ClientSubnet == nil { + return json.Marshal(o.Server) + } else { + return json.Marshal((_DomainResolveOptions)(o)) + } +} + +func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err == nil { + o.Server = stringValue + return nil + } + return json.Unmarshal(bytes, (*_DomainResolveOptions)(o)) } func (o *DialerOptions) TakeDialerOptions() DialerOptions { @@ -107,6 +141,10 @@ func (o ServerOptions) Build() M.Socksaddr { return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) } +func (o ServerOptions) ServerIsDomain() bool { + return M.IsDomainName(o.Server) +} + func (o *ServerOptions) TakeServerOptions() ServerOptions { return *o } diff --git a/option/route.go b/option/route.go index 1eb2294b..f4b65391 100644 --- a/option/route.go +++ b/option/route.go @@ -13,6 +13,7 @@ type RouteOptions struct { OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` DefaultInterface string `json:"default_interface,omitempty"` DefaultMark FwMark `json:"default_mark,omitempty"` + DefaultDomainResolver *DomainResolveOptions `json:"default_domain_resolver,omitempty"` DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"` DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index d173ec53..9b454f58 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -42,16 +42,20 @@ type Outbound struct { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true - outboundDialer, err := dialer.NewDirect(ctx, options.DialerOptions) + if options.Detour != "" { + return nil, E.New("`detour` is not supported in direct context") + } + outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) if err != nil { return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), - logger: logger, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + logger: logger, + //nolint:staticcheck domainStrategy: C.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), - dialer: outboundDialer, + dialer: outboundDialer.(dialer.ParallelInterfaceDialer), // loopBack: newLoopBackDetector(router), } //nolint:staticcheck diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index c58f3071..0570dde5 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -30,7 +30,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index e1d8716c..7746df13 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -47,7 +47,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 74e87b37..c805f07e 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, E.New("unknown obfs type: ", options.Obfs.Type) } } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 7e7277ef..875c9e69 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -44,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 5ae5656f..1db191d8 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -47,7 +47,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if options.Version > 1 { handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) for serverName, serverOptions := range options.HandshakeForServerName { - handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions) + handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions, serverOptions.ServerIsDomain()) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } } } - handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, options.Handshake.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/shadowtls/outbound.go b/protocol/shadowtls/outbound.go index 2b480729..0731b033 100644 --- a/protocol/shadowtls/outbound.go +++ b/protocol/shadowtls/outbound.go @@ -68,7 +68,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) } } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index 323149e2..851412ff 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index eb9970b5..304ea389 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -49,7 +49,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 58824b53..9a0e2d65 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -75,7 +75,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } startConf.TorrcFile = torrcFile } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) if err != nil { return nil, err } diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index 82889bc1..37a6933c 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -38,7 +38,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index 49b01f96..a31d4850 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL case "quic": tuicUDPStream = true } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index 1d832a65..e0208be9 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index d41b30d9..be05990e 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 300701a9..e167bec1 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -53,7 +53,14 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL if options.Detour == "" { options.IsWireGuardListener = true } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: common.Any(options.Peers, func(it option.WireGuardPeer) bool { + return !M.ParseAddr(it.Address).IsValid() + }), + ResolverOnDetour: true, + }) if err != nil { return nil, err } @@ -81,9 +88,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL PrivateKey: options.PrivateKey, ListenPort: options.ListenPort, ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ - Strategy: C.DomainStrategy(options.DomainStrategy), - }) + endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) if lookupErr != nil { return netip.Addr{}, lookupErr } diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 4aa49a8d..edd8184c 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -56,7 +56,14 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } else if options.GSO { return nil, E.New("gso is conflict with detour") } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain() || common.Any(options.Peers, func(it option.LegacyWireGuardPeer) bool { + return it.ServerIsDomain() + }), + ResolverOnDetour: true, + }) if err != nil { return nil, err } @@ -94,9 +101,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Address: options.LocalAddress, PrivateKey: options.PrivateKey, ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ - Strategy: C.DomainStrategy(options.DomainStrategy), - }) + endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) if lookupErr != nil { return netip.Addr{}, lookupErr } diff --git a/route/network.go b/route/network.go index ab1be76c..a494ce09 100644 --- a/route/network.go +++ b/route/network.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net" + "net/netip" "os" "runtime" "strings" @@ -55,13 +56,21 @@ type NetworkManager struct { } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { + defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver) nm := &NetworkManager{ logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: routeOptions.AutoDetectInterface, defaultOptions: adapter.NetworkOptions{ - BindInterface: routeOptions.DefaultInterface, - RoutingMark: uint32(routeOptions.DefaultMark), + BindInterface: routeOptions.DefaultInterface, + RoutingMark: uint32(routeOptions.DefaultMark), + DomainResolver: defaultDomainResolver.Server, + DomainResolveOptions: adapter.DNSQueryOptions{ + Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), + DisableCache: defaultDomainResolver.DisableCache, + RewriteTTL: defaultDomainResolver.RewriteTTL, + ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}), + }, NetworkStrategy: (*C.NetworkStrategy)(routeOptions.DefaultNetworkStrategy), NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build), FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build), diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 7287ad86..88149aff 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -49,7 +49,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), }, nil case C.RuleActionTypeDirect: - directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) + directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) if err != nil { return nil, err } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 9d1c69b8..087fb7b2 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -210,7 +210,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.Outbound) > 0 { - item := NewOutboundRule(options.Outbound) + item := NewOutboundRule(ctx, options.Outbound) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_item_outbound.go b/route/rule/rule_item_outbound.go index 3f37dee7..a13d0597 100644 --- a/route/rule/rule_item_outbound.go +++ b/route/rule/rule_item_outbound.go @@ -1,9 +1,11 @@ package rule import ( + "context" "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/deprecated" F "github.com/sagernet/sing/common/format" ) @@ -15,7 +17,8 @@ type OutboundItem struct { matchAny bool } -func NewOutboundRule(outbounds []string) *OutboundItem { +func NewOutboundRule(ctx context.Context, outbounds []string) *OutboundItem { + deprecated.Report(ctx, deprecated.OptionOutboundDNSRuleItem) rule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)} for _, outbound := range outbounds { if outbound == "any" { @@ -28,8 +31,8 @@ func NewOutboundRule(outbounds []string) *OutboundItem { } func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool { - if r.matchAny && metadata.Outbound != "" { - return true + if r.matchAny { + return metadata.Outbound != "" } return r.outboundMap[metadata.Outbound] }