Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/sagernet/sing-box

go 1.24.7
go 1.25.6

require github.com/amnezia-vpn/amneziawg-go v0.2.16

Expand Down Expand Up @@ -145,6 +145,7 @@ require (
github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/hiddify/hmrd_multi_resolver_dns v0.0.0-20260429114007-8d809dc33d0e
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/kamstrup/intmap v0.5.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hiddify/hmrd_multi_resolver_dns v0.0.0-20260429114007-8d809dc33d0e h1:GsHpSccRtSFe1l3hq0WtBjqsKjJdU+an2D+uCsXm+xk=
github.com/hiddify/hmrd_multi_resolver_dns v0.0.0-20260429114007-8d809dc33d0e/go.mod h1:7u9Ece+I3gvMFzcpqhROWaykNUyoE21g/fYQMBasAWY=
github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9 h1:KXnaABX8hHmkcL0jbL769hEIGI5+z/DajCrlO+Bkzcc=
github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9/go.mod h1:+8kEfQsZJn7/4aIppVekrSuqhrKjGBIgnacTJkdAlS8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down
6 changes: 6 additions & 0 deletions option/dnstt.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ type DnsttOptions struct {
UdpSharedSocket bool `json:"udp-shared-socket,omitempty"`
UdpTimeout *badoption.Duration `json:"udp-timeout,omitempty"`
UdpWorkers *int `json:"udp-workers,omitempty"`

// SmartPool routes every resolver registered via the dnstt outbound's
// addResolver flow through an internal hmrd_multi_resolver_dns pool
// (deadline-aware failover, AIMD throttling, recovery probing) instead
// of using each resolver as its own tunnel.
SmartPool bool `json:"smart_pool,omitempty"`
}
31 changes: 26 additions & 5 deletions protocol/hiddify/dnstt/dnstt_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,39 @@ import (

func (c *Outbound) addResolver(resolver dnstt.Resolver) {
c.mu.Lock()
// for i := 0; i < c.options.TunnelPerResolver; i++ {
c.resolvers = append(c.resolvers, resolver)
c.tunnels = append(c.tunnels, nil)
c.mutlitunnel = nil
// }
if c.mdMgr != nil {
// Smart pool mode: register the resolver as a multidns upstream.
// c.resolvers stays at the single virtual resolver set up in
// NewOutbound, so the existing tunnel keeps running — the pool
// rotates upstreams behind it. No need to invalidate
// c.mutlitunnel here (unlike the legacy path, which has to
// rebuild the tunnel when its resolver list changes).
_, _ = c.mdMgr.AddResolverURL(resolverURL(resolver))
} else {
// for i := 0; i < c.options.TunnelPerResolver; i++ {
c.resolvers = append(c.resolvers, resolver)
c.tunnels = append(c.tunnels, nil)
// }
c.mutlitunnel = nil
}
c.mu.Unlock()
if !c.IsReady() {
c.started = 1
c.logger.InfoContext(c.ctx, "initial resolver ", resolver.ResolverAddr)
monitoring.Get(c.ctx).TestNow(c.Tag())
}
}

// resolverURL renders a dnstt.Resolver as the URL form multidns parses.
// UDP resolvers are bare host:port (multidns defaults to UDP/53); DoT gets
// the "dot://" prefix; DoH is already a full https:// URL.
func resolverURL(r dnstt.Resolver) string {
switch r.ResolverType {
case dnstt.ResolverTypeDOT:
return "dot://" + r.ResolverAddr
default:
return r.ResolverAddr
}
}

func (c *Outbound) openStreamImp(ctx context.Context) (net.Conn, error) {
Expand Down
17 changes: 17 additions & 0 deletions protocol/hiddify/dnstt/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync"
"time"

multidns "github.com/hiddify/hmrd_multi_resolver_dns"
dnstt "github.com/net2share/vaydns/client"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
Expand Down Expand Up @@ -46,6 +47,8 @@ type Outbound struct {
resolve bool
tunnel_index int

mdMgr *multidns.Manager // smart pool, set when options.SmartPool

options option.DnsttOptions
}

Expand Down Expand Up @@ -110,6 +113,17 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
options: options,
}

if options.SmartPool {
mgr, addr, err := multidns.StartLocal(multidns.Options{})
if err != nil {
return nil, err
}
out.mdMgr = mgr
out.resolvers = []dnstt.Resolver{{ResolverType: dnstt.ResolverTypeUDP, ResolverAddr: addr}}
out.tunnels = []*dnstt.Tunnel{nil}
logger.InfoContext(ctx, "dnstt smart_pool listening on ", addr)
}

return out, nil

}
Expand Down Expand Up @@ -158,5 +172,8 @@ func (c *Outbound) Close() error {
t.Close()
}
}
if c.mdMgr != nil {
_ = c.mdMgr.Close()
}
return nil
}