Fix crash on Apple platforms caused by concurrent libresolv calls

This commit is contained in:
世界
2026-06-04 15:12:03 +08:00
parent dc110f51d2
commit 6b490f5cf4
4 changed files with 172 additions and 50 deletions
+14 -3
View File
@@ -40,6 +40,7 @@ type Transport struct {
resolved ResolvedResolver
mdnsTransport adapter.DNSTransport
dhcpTransport dhcpTransport
systemResolver systemResolver
neighborResolver adapter.NeighborResolver
neighborSuffixes []string
}
@@ -50,6 +51,12 @@ type dhcpTransport interface {
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
}
type systemResolver interface {
Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
Reset()
Close() error
}
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
transportDialer, err := dns.NewLocalDialer(ctx, options)
if err != nil {
@@ -91,6 +98,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
}
case adapter.StartStateStart:
if C.IsDarwin {
t.systemResolver = newSystemResolver()
inboundManager := service.FromContext[adapter.InboundManager](t.ctx)
for _, inbound := range inboundManager.Inbounds() {
if inbound.Type() == C.TypeTun {
@@ -127,7 +135,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
}
func (t *Transport) Close() error {
return common.Close(t.resolved, t.dhcpTransport, t.mdnsTransport)
return common.Close(t.resolved, t.dhcpTransport, t.mdnsTransport, t.systemResolver)
}
func (t *Transport) Reset() {
@@ -137,6 +145,9 @@ func (t *Transport) Reset() {
if t.mdnsTransport != nil {
t.mdnsTransport.Reset()
}
if t.systemResolver != nil {
t.systemResolver.Reset()
}
}
func (t *Transport) PreferredDomain(domain string) bool {
@@ -162,7 +173,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
}
if mdns.IsLocalDomain(question.Name) {
if C.IsDarwin {
return t.systemExchange(ctx, message)
return t.systemResolver.Exchange(ctx, message)
}
return t.mdnsTransport.Exchange(ctx, message)
}
@@ -176,7 +187,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
}
}
if t.fallback {
return t.systemExchange(ctx, message)
return t.systemResolver.Exchange(ctx, message)
}
return t.exchange(ctx, message, question.Name)
}
+143 -37
View File
@@ -7,20 +7,14 @@ package local
#include <netdb.h>
#include <dns.h>
static int cgo_dns_search(const char *name, int class, int type,
static int cgo_dns_search(dns_handle_t handle, const char *name, int class, int type,
unsigned char *answer, int anslen, int *out_h_errno) {
dns_handle_t handle = (dns_handle_t)dns_open(NULL);
if (handle == NULL) {
*out_h_errno = NO_RECOVERY;
return -1;
}
struct sockaddr_storage from;
uint32_t fromlen = sizeof(from);
h_errno = 0;
int n = dns_search(handle, name, class, type, (char *)answer, anslen,
(struct sockaddr *)&from, &fromlen);
*out_h_errno = h_errno;
dns_free(handle);
return n;
}
*/
@@ -29,6 +23,8 @@ import "C"
import (
"context"
"errors"
"net"
"sync"
"unsafe"
"github.com/sagernet/sing-box/dns"
@@ -44,40 +40,107 @@ const (
darwinResolverNoData = 4
)
func darwinLookupSystemDNS(name string, class, qtype int) (*mDNS.Msg, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
answer := make([]byte, 4096)
var hErrno C.int
n := C.cgo_dns_search(cName, C.int(class), C.int(qtype),
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
&hErrno)
if n <= 0 {
return nil, darwinResolverHErrno(name, int(hErrno))
}
var response mDNS.Msg
err := response.Unpack(answer[:int(n)])
if err != nil {
return nil, E.Cause(err, "unpack dns_search response")
}
return &response, nil
type darwinDNSHandle struct {
handle C.dns_handle_t
generation uint64
}
func darwinResolverHErrno(name string, hErrno int) error {
switch hErrno {
case darwinResolverHostNotFound:
return dns.RcodeNameError
case darwinResolverNoData:
return dns.RcodeSuccess
case darwinResolverTryAgain, darwinResolverNoRecovery:
return dns.RcodeServerFailure
default:
return E.New("dns_search: unknown h_errno ", hErrno, " for ", name)
// darwinSystemResolver pools libresolv handles. iOS 26.5.1 NULL-derefs in
// libresolv when resolver state is rebuilt by concurrent dns_open(NULL) calls,
// and dns_search corrupts state when two queries share one handle. Handles are
// therefore reused across queries and leased exclusively: each in-flight query
// owns one handle, dns_search runs concurrently on independent handles, while
// dns_open and dns_free are serialized by lifecycleAccess. A leased handle is
// removed from idle and owned by its caller, so Reset/Close only ever free idle
// handles and the caller frees its own handle on release — dns_free never races
// an in-flight dns_search and is never called twice for the same handle.
type darwinSystemResolver struct {
access sync.Mutex
lifecycleAccess sync.Mutex
idle []*darwinDNSHandle
generation uint64
closed bool
}
func newSystemResolver() systemResolver {
return &darwinSystemResolver{}
}
func (r *darwinSystemResolver) acquire() (*darwinDNSHandle, error) {
r.access.Lock()
if r.closed {
r.access.Unlock()
return nil, net.ErrClosed
}
if count := len(r.idle); count > 0 {
handle := r.idle[count-1]
r.idle[count-1] = nil
r.idle = r.idle[:count-1]
r.access.Unlock()
return handle, nil
}
generation := r.generation
r.access.Unlock()
r.lifecycleAccess.Lock()
cHandle := C.dns_open(nil)
r.lifecycleAccess.Unlock()
if cHandle == nil {
return nil, dns.RcodeServerFailure
}
return &darwinDNSHandle{handle: cHandle, generation: generation}, nil
}
func (r *darwinSystemResolver) release(handle *darwinDNSHandle) {
r.access.Lock()
reuse := !r.closed && handle.generation == r.generation
if reuse {
r.idle = append(r.idle, handle)
}
r.access.Unlock()
if !reuse {
r.free(handle)
}
}
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
func (r *darwinSystemResolver) free(handle *darwinDNSHandle) {
r.lifecycleAccess.Lock()
C.dns_free(handle.handle)
r.lifecycleAccess.Unlock()
}
func (r *darwinSystemResolver) Reset() {
r.access.Lock()
if r.closed {
r.access.Unlock()
return
}
r.generation++
idle := r.idle
r.idle = nil
r.access.Unlock()
for _, handle := range idle {
r.free(handle)
}
}
func (r *darwinSystemResolver) Close() error {
r.access.Lock()
if r.closed {
r.access.Unlock()
return nil
}
r.closed = true
idle := r.idle
r.idle = nil
r.access.Unlock()
for _, handle := range idle {
r.free(handle)
}
return nil
}
func (r *darwinSystemResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
question := message.Question[0]
type resolvResult struct {
response *mDNS.Msg
@@ -85,7 +148,7 @@ func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDN
}
resultCh := make(chan resolvResult, 1)
go func() {
response, err := darwinLookupSystemDNS(question.Name, int(question.Qclass), int(question.Qtype))
response, err := r.lookup(question.Name, int(question.Qclass), int(question.Qtype))
resultCh <- resolvResult{response, err}
}()
var result resolvResult
@@ -116,3 +179,46 @@ func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDN
result.response.Response = true
return result.response, nil
}
func (r *darwinSystemResolver) lookup(name string, class, qtype int) (*mDNS.Msg, error) {
handle, err := r.acquire()
if err != nil {
return nil, err
}
response, err := darwinSearch(handle.handle, name, class, qtype)
r.release(handle)
return response, err
}
func darwinSearch(handle C.dns_handle_t, name string, class, qtype int) (*mDNS.Msg, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
answer := make([]byte, 4096)
var hErrno C.int
n := C.cgo_dns_search(handle, cName, C.int(class), C.int(qtype),
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
&hErrno)
if n <= 0 {
return nil, darwinResolverHErrno(name, int(hErrno))
}
var response mDNS.Msg
err := response.Unpack(answer[:int(n)])
if err != nil {
return nil, E.Cause(err, "unpack dns_search response")
}
return &response, nil
}
func darwinResolverHErrno(name string, hErrno int) error {
switch hErrno {
case darwinResolverHostNotFound:
return dns.RcodeNameError
case darwinResolverNoData:
return dns.RcodeSuccess
case darwinResolverTryAgain, darwinResolverNoRecovery:
return dns.RcodeServerFailure
default:
return E.New("dns_search: unknown h_errno ", hErrno, " for ", name)
}
}
+13 -1
View File
@@ -10,6 +10,18 @@ import (
mDNS "github.com/miekg/dns"
)
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
func newSystemResolver() systemResolver {
return &cgoRequiredResolver{}
}
type cgoRequiredResolver struct{}
func (r *cgoRequiredResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
return nil, E.New(`local DNS server requires CGO on darwin, rebuild with CGO_ENABLED=1`)
}
func (r *cgoRequiredResolver) Reset() {}
func (r *cgoRequiredResolver) Close() error {
return nil
}
+2 -9
View File
@@ -2,13 +2,6 @@
package local
import (
"context"
"os"
mDNS "github.com/miekg/dns"
)
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
return nil, os.ErrInvalid
func newSystemResolver() systemResolver {
return nil
}