Fix crash on Apple platforms caused by concurrent libresolv calls
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user