refactor: Outbound domain resolver

This commit is contained in:
世界 2025-01-12 12:45:27 +08:00
parent 0909369edd
commit 19d1dd09b8
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
29 changed files with 224 additions and 99 deletions

View file

@ -28,12 +28,14 @@ type NetworkManager interface {
} }
type NetworkOptions struct { type NetworkOptions struct {
BindInterface string
RoutingMark uint32
DomainResolver string
DomainResolveOptions DNSQueryOptions
NetworkStrategy *C.NetworkStrategy NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration FallbackDelay time.Duration
BindInterface string
RoutingMark uint32
} }
type InterfaceUpdateListener interface { type InterfaceUpdateListener interface {

2
box.go
View file

@ -309,7 +309,7 @@ func New(options Options) (*Box, error) {
} }
} }
if ntpOptions.Enabled { if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
if err != nil { if err != nil {
return nil, E.Cause(err, "create NTP service") return nil, E.Cause(err, "create NTP service")
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -15,7 +16,7 @@ import (
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
) )
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
if options.IsWireGuardListener { if options.IsWireGuardListener {
return NewDefault(ctx, options) return NewDefault(ctx, options)
} }
@ -23,50 +24,71 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
dialer N.Dialer dialer N.Dialer
err error err error
) )
if options.Detour == "" { if options.Detour != "" {
dialer, err = NewDefault(ctx, options)
if err != nil {
return nil, err
}
} else {
outboundManager := service.FromContext[adapter.OutboundManager](ctx) outboundManager := service.FromContext[adapter.OutboundManager](ctx)
if outboundManager == nil { if outboundManager == nil {
return nil, E.New("missing outbound manager") return nil, E.New("missing outbound manager")
} }
dialer = NewDetour(outboundManager, options.Detour) dialer = NewDetour(outboundManager, options.Detour)
} } else {
if options.Detour == "" { dialer, err = NewDefault(ctx, options)
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))
}
}
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 { if err != nil {
return nil, err return nil, err
} }
return NewResolveParallelInterfaceDialer( }
service.FromContext[adapter.DNSRouter](ctx), if remoteIsDomain && options.Detour == "" {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
dnsTransport := service.FromContext[adapter.DNSTransportManager](ctx)
var defaultOptions adapter.NetworkOptions
if networkManager != nil {
defaultOptions = networkManager.DefaultOptions()
}
var (
dnsQueryOptions adapter.DNSQueryOptions
resolveFallbackDelay time.Duration
)
if options.DomainResolver != nil && options.DomainResolver.Server != "" {
transport, loaded := dnsTransport.Transport(options.DomainResolver.Server)
if !loaded {
return nil, E.New("domain resolver not found: " + options.DomainResolver.Server)
}
var strategy C.DomainStrategy
if options.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) {
strategy = C.DomainStrategy(options.DomainResolver.Strategy)
} else if
//nolint:staticcheck
options.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
strategy = C.DomainStrategy(options.DomainStrategy)
}
dnsQueryOptions = adapter.DNSQueryOptions{
Transport: transport,
Strategy: strategy,
DisableCache: options.DomainResolver.DisableCache,
RewriteTTL: options.DomainResolver.RewriteTTL,
ClientSubnet: options.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
}
resolveFallbackDelay = time.Duration(options.FallbackDelay)
} 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(options.FallbackDelay)
} else {
deprecated.Report(ctx, deprecated.OptionMissingDomainResolver)
}
dialer = NewResolveDialer(
ctx,
dialer, dialer,
true, options.Detour == "" && !options.TCPFastOpen,
C.DomainStrategy(options.DomainStrategy), dnsQueryOptions,
time.Duration(options.FallbackDelay), resolveFallbackDelay,
), nil )
}
return dialer, nil
} }
type ParallelInterfaceDialer interface { type ParallelInterfaceDialer interface {

View file

@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
var ( var (
@ -18,20 +19,30 @@ var (
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil) _ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
) )
type ResolveDialer interface {
N.Dialer
QueryOptions() adapter.DNSQueryOptions
}
type ParallelInterfaceResolveDialer interface {
ParallelInterfaceDialer
QueryOptions() adapter.DNSQueryOptions
}
type resolveDialer struct { type resolveDialer struct {
router adapter.DNSRouter
dialer N.Dialer dialer N.Dialer
parallel bool parallel bool
router adapter.DNSRouter queryOptions adapter.DNSQueryOptions
strategy C.DomainStrategy
fallbackDelay time.Duration 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, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
return &resolveDialer{ return &resolveDialer{
service.FromContext[adapter.DNSRouter](ctx),
dialer, dialer,
parallel, parallel,
router, queryOptions,
strategy,
fallbackDelay, fallbackDelay,
} }
} }
@ -41,13 +52,13 @@ type resolveParallelNetworkDialer struct {
dialer ParallelInterfaceDialer 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, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
return &resolveParallelNetworkDialer{ return &resolveParallelNetworkDialer{
resolveDialer{ resolveDialer{
service.FromContext[adapter.DNSRouter](ctx),
dialer, dialer,
parallel, parallel,
router, queryOptions,
strategy,
fallbackDelay, fallbackDelay,
}, },
dialer, dialer,
@ -59,12 +70,12 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
return d.dialer.DialContext(ctx, network, destination) return d.dialer.DialContext(ctx, network, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) 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 { if err != nil {
return nil, err return nil, err
} }
if d.parallel { 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 { } else {
return N.DialSerial(ctx, d.dialer, network, destination, addresses) return N.DialSerial(ctx, d.dialer, network, destination, addresses)
} }
@ -75,7 +86,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) 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 { if err != nil {
return nil, err return nil, err
} }
@ -86,14 +97,20 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil 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) { 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) {
if !destination.IsFqdn() { if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination) return d.dialer.DialContext(ctx, network, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{ addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
Strategy: d.strategy,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -101,7 +118,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
fallbackDelay = d.fallbackDelay fallbackDelay = d.fallbackDelay
} }
if d.parallel { 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 { } else {
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} }
@ -112,10 +129,13 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) 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 { if err != nil {
return nil, err return nil, err
} }
if fallbackDelay == 0 {
fallbackDelay = d.fallbackDelay
}
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil { if err != nil {
return nil, err return nil, err
@ -123,6 +143,10 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil 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 return d.dialer
} }

View file

@ -101,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
tlsConfig.ShortIds[shortID] = true 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -19,12 +19,20 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
if options.LegacyDefaultDialer { if options.LegacyDefaultDialer {
return dialer.NewDefaultOutbound(ctx), nil return dialer.NewDefaultOutbound(ctx), nil
} else { } else {
return dialer.New(ctx, options.DialerOptions) return dialer.New(ctx, options.DialerOptions, false)
} }
} }
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) { func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
transportDialer, err := NewLocalDialer(ctx, options.LocalDNSServerOptions) var (
transportDialer N.Dialer
err error
)
if options.LegacyDefaultDialer {
transportDialer = dialer.NewDefaultOutbound(ctx)
} else {
transportDialer, err = dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -35,7 +43,7 @@ func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions)
return nil, E.New("address resolver not found: ", options.AddressResolver) return nil, E.New("address resolver not found: ", options.AddressResolver)
} }
transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay)) transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay))
} else if M.IsDomainName(options.Server) { } else if options.ServerIsDomain() {
return nil, E.New("missing address resolver for server: ", options.Server) return nil, E.New("missing address resolver for server: ", options.Server)
} }
return transportDialer, nil return transportDialer, nil

View file

@ -161,6 +161,20 @@ var OptionLegacyDNSFakeIPOptions = Note{
ScheduledVersion: "1.14.0", ScheduledVersion: "1.14.0",
} }
var OptionOutboundDNSRuleItem = Note{
Name: "outbound-dns-rule-item",
Description: "outbound DNS rule item",
DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0",
}
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",
}
var Options = []Note{ var Options = []Note{
OptionBadMatchSource, OptionBadMatchSource,
OptionGEOIP, OptionGEOIP,
@ -172,4 +186,8 @@ var Options = []Note{
OptionWireGuardOutbound, OptionWireGuardOutbound,
OptionWireGuardGSO, OptionWireGuardGSO,
OptionTUNGSO, OptionTUNGSO,
OptionLegacyDNSTransport,
OptionLegacyDNSFakeIPOptions,
OptionOutboundDNSRuleItem,
OptionMissingDomainResolver,
} }

View file

@ -77,12 +77,46 @@ type DialerOptions struct {
TCPMultiPath bool `json:"tcp_multi_path,omitempty"` TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"` UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"` 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 { func (o *DialerOptions) TakeDialerOptions() DialerOptions {
@ -107,6 +141,10 @@ func (o ServerOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
} }
func (o ServerOptions) ServerIsDomain() bool {
return M.IsDomainName(o.Server)
}
func (o *ServerOptions) TakeServerOptions() ServerOptions { func (o *ServerOptions) TakeServerOptions() ServerOptions {
return *o return *o
} }

View file

@ -13,6 +13,7 @@ type RouteOptions struct {
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"` DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark FwMark `json:"default_mark,omitempty"` DefaultMark FwMark `json:"default_mark,omitempty"`
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"` DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"`
DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"`

View file

@ -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) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) {
options.UDPFragmentDefault = true 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 { if err != nil {
return nil, err return nil, err
} }
outbound := &Outbound{ outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
logger: logger, logger: logger,
//nolint:staticcheck
domainStrategy: C.DomainStrategy(options.DomainStrategy), domainStrategy: C.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer, dialer: outboundDialer.(dialer.ParallelInterfaceDialer),
// loopBack: newLoopBackDetector(router), // loopBack: newLoopBackDetector(router),
} }
//nolint:staticcheck //nolint:staticcheck

View file

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -47,7 +47,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -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) 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -44,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -47,7 +47,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if options.Version > 1 { if options.Version > 1 {
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
for serverName, serverOptions := range options.HandshakeForServerName { 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -68,7 +68,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -75,7 +75,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
} }
startConf.TorrcFile = torrcFile startConf.TorrcFile = torrcFile
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
case "quic": case "quic":
tuicUDPStream = true tuicUDPStream = true
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -53,7 +53,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
if options.Detour == "" { if options.Detour == "" {
options.IsWireGuardListener = true options.IsWireGuardListener = true
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -81,9 +81,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
ListenPort: options.ListenPort, ListenPort: options.ListenPort,
ResolvePeer: func(domain string) (netip.Addr, error) { ResolvePeer: func(domain string) (netip.Addr, error) {
endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions())
Strategy: C.DomainStrategy(options.DomainStrategy),
})
if lookupErr != nil { if lookupErr != nil {
return netip.Addr{}, lookupErr return netip.Addr{}, lookupErr
} }

View file

@ -56,7 +56,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
} else if options.GSO { } else if options.GSO {
return nil, E.New("gso is conflict with detour") return nil, E.New("gso is conflict with detour")
} }
outboundDialer, err := dialer.New(ctx, options.DialerOptions) outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,9 +94,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
Address: options.LocalAddress, Address: options.LocalAddress,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
ResolvePeer: func(domain string) (netip.Addr, error) { ResolvePeer: func(domain string) (netip.Addr, error) {
endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions())
Strategy: C.DomainStrategy(options.DomainStrategy),
})
if lookupErr != nil { if lookupErr != nil {
return netip.Addr{}, lookupErr return netip.Addr{}, lookupErr
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"net/netip"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@ -55,6 +56,7 @@ type NetworkManager struct {
} }
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) {
defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DomainResolver)
nm := &NetworkManager{ nm := &NetworkManager{
logger: logger, logger: logger,
interfaceFinder: control.NewDefaultInterfaceFinder(), interfaceFinder: control.NewDefaultInterfaceFinder(),
@ -62,6 +64,13 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
defaultOptions: adapter.NetworkOptions{ defaultOptions: adapter.NetworkOptions{
BindInterface: routeOptions.DefaultInterface, BindInterface: routeOptions.DefaultInterface,
RoutingMark: uint32(routeOptions.DefaultMark), 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), NetworkStrategy: (*C.NetworkStrategy)(routeOptions.DefaultNetworkStrategy),
NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build), NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build),
FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build), FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build),

View file

@ -49,7 +49,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout),
}, nil }, nil
case C.RuleActionTypeDirect: 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -210,7 +210,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.Outbound) > 0 { if len(options.Outbound) > 0 {
item := NewOutboundRule(options.Outbound) item := NewOutboundRule(ctx, options.Outbound)
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }

View file

@ -1,9 +1,11 @@
package rule package rule
import ( import (
"context"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/deprecated"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
) )
@ -15,7 +17,8 @@ type OutboundItem struct {
matchAny bool 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)} rule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)}
for _, outbound := range outbounds { for _, outbound := range outbounds {
if outbound == "any" { if outbound == "any" {
@ -28,8 +31,8 @@ func NewOutboundRule(outbounds []string) *OutboundItem {
} }
func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool { func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool {
if r.matchAny && metadata.Outbound != "" { if r.matchAny {
return true return metadata.Outbound != ""
} }
return r.outboundMap[metadata.Outbound] return r.outboundMap[metadata.Outbound]
} }