Fix hysteria2 realm STUN resolution ignoring default domain resolver

This commit is contained in:
世界
2026-09-24 17:23:36 +08:00
parent 111ecb3483
commit 17c52d03b3
5 changed files with 90 additions and 97 deletions
-22
View File
@@ -8,9 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
)
@@ -45,26 +43,6 @@ type DNSQueryOptions struct {
RemoveClientSubnet bool
}
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (DNSQueryOptions, error) {
if options == nil || options.Server == "" {
return DNSQueryOptions{}, nil
}
transportManager := service.FromContext[DNSTransportManager](ctx)
transport, loaded := transportManager.Transport(options.Server)
if !loaded {
return DNSQueryOptions{}, E.New("domain resolver not found: " + options.Server)
}
return DNSQueryOptions{
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
DisableOptimisticCache: options.DisableOptimisticCache,
RewriteTTL: options.RewriteTTL,
Timeout: time.Duration(options.Timeout),
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
}, nil
}
type RDRCStore interface {
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
SaveRDRC(transportName string, qName string, qType uint16) error
+62 -64
View File
@@ -63,75 +63,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
}
}
if options.RemoteIsDomain && (!hasDetour || options.ResolverOnDetour || dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "") {
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
server string
dnsQueryOptions adapter.DNSQueryOptions
)
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)
}
hasDomainResolver := dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != ""
if options.DirectResolver {
if !hasDomainResolver {
return nil, E.New("missing domain resolver for domain server address")
}
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)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
}
server = dialOptions.DomainResolver.Server
dnsQueryOptions = adapter.DNSQueryOptions{
Transport: transport,
Strategy: strategy,
Timeout: time.Duration(dialOptions.DomainResolver.Timeout),
DisableCache: dialOptions.DomainResolver.DisableCache,
DisableOptimisticCache: dialOptions.DomainResolver.DisableOptimisticCache,
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")
dnsQueryOptions = domainResolveQueryOptions(dialOptions.DomainResolver)
} 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 {
transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
dnsQueryOptions, err = NewDNSQueryOptions(options.Context, dialOptions.DomainResolver, options.NewDialer)
if err != nil {
return nil, err
}
if
}
if hasDomainResolver {
server = dialOptions.DomainResolver.Server
}
if
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) && (!hasDomainResolver || dialOptions.DomainResolver.Strategy == option.DomainStrategy(C.DomainStrategyAsIS)) {
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
}
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
}
dialer = NewResolveDialer(
options.Context,
@@ -139,12 +95,54 @@ func NewWithOptions(options Options) (N.Dialer, error) {
dialOptions.Detour == "" && !dialOptions.TCPFastOpen,
server,
dnsQueryOptions,
resolveFallbackDelay,
time.Duration(dialOptions.FallbackDelay),
)
}
return dialer, nil
}
func NewDNSQueryOptions(ctx context.Context, domainResolver *option.DomainResolveOptions, newDialer bool) (adapter.DNSQueryOptions, error) {
dnsTransport := service.FromContext[adapter.DNSTransportManager](ctx)
if domainResolver != nil && domainResolver.Server != "" {
transport, loaded := dnsTransport.Transport(domainResolver.Server)
if !loaded {
return adapter.DNSQueryOptions{}, E.New("domain resolver not found: ", domainResolver.Server)
}
dnsQueryOptions := domainResolveQueryOptions(domainResolver)
dnsQueryOptions.Transport = transport
return dnsQueryOptions, nil
}
defaultOptions := service.FromContext[adapter.NetworkManager](ctx).DefaultOptions()
if defaultOptions.DomainResolver != "" {
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return adapter.DNSQueryOptions{}, E.New("default domain resolver not found: ", defaultOptions.DomainResolver)
}
dnsQueryOptions := defaultOptions.DomainResolveOptions
dnsQueryOptions.Transport = transport
return dnsQueryOptions, nil
}
if len(dnsTransport.Transports()) < 2 {
return adapter.DNSQueryOptions{Transport: dnsTransport.Default()}, nil
}
if newDialer {
return adapter.DNSQueryOptions{}, E.New("missing domain resolver for domain server address")
}
deprecated.Report(ctx, deprecated.OptionMissingDomainResolver)
return adapter.DNSQueryOptions{}, nil
}
func domainResolveQueryOptions(domainResolver *option.DomainResolveOptions) adapter.DNSQueryOptions {
return adapter.DNSQueryOptions{
Strategy: C.DomainStrategy(domainResolver.Strategy),
Timeout: time.Duration(domainResolver.Timeout),
DisableCache: domainResolver.DisableCache,
DisableOptimisticCache: domainResolver.DisableOptimisticCache,
RewriteTTL: domainResolver.RewriteTTL,
ClientSubnet: domainResolver.ClientSubnet.Build(netip.Prefix{}),
}
}
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)
+8
View File
@@ -3,6 +3,7 @@ package option
import (
"net/url"
"reflect"
"slices"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/schema"
@@ -10,6 +11,7 @@ import (
"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"
)
type Hysteria2InboundOptions struct {
@@ -37,6 +39,12 @@ type Hysteria2Realm struct {
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
}
func (r Hysteria2Realm) STUNServersIsDomain() bool {
return slices.ContainsFunc(r.STUNServers, func(server string) bool {
return M.ParseSocksaddr(server).IsDomain()
})
}
type Hysteria2RealmPortMapping struct {
Enabled bool `json:"enabled,omitempty"`
Timeout badoption.Duration `json:"timeout,omitempty"`
+9 -4
View File
@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
@@ -141,11 +142,15 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
return nil, E.New("realm.ip_version 4 conflicts with listen address ", listenAddr)
}
}
queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.Realm.STUNDomainResolver)
if err != nil {
return nil, err
var queryOptions adapter.DNSQueryOptions
if options.Realm.STUNServersIsDomain() {
queryOptions, err = dialer.NewDNSQueryOptions(ctx, options.Realm.STUNDomainResolver, true)
if err != nil {
return nil, E.Cause(err, "create realm STUN domain resolver")
}
}
httpClientTransport, err := service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient))
var httpClientTransport adapter.HTTPTransport
httpClientTransport, err = service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient))
if err != nil {
return nil, E.Cause(err, "create realm http client")
}
+11 -7
View File
@@ -76,21 +76,25 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
}
}
realmSTUNServersIsDomain := options.Realm != nil && options.Realm.STUNServersIsDomain()
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(),
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain() || realmSTUNServersIsDomain,
ResolverOnDetour: realmSTUNServersIsDomain,
NewDialer: realmSTUNServersIsDomain,
})
if err != nil {
return nil, err
}
var realmOptions *realm.Options
if options.Realm != nil {
queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.DialerOptions.DomainResolver)
if err != nil {
return nil, err
var queryOptions adapter.DNSQueryOptions
if realmSTUNServersIsDomain {
queryOptions = outboundDialer.(dialer.ResolveDialer).QueryOptions()
}
httpClientTransport, err := service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient))
var httpClientTransport adapter.HTTPTransport
httpClientTransport, err = service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient))
if err != nil {
return nil, E.Cause(err, "create realm http client")
}