Skip to content

Commit a8bd2e2

Browse files
committed
Fix darwin cgo DNS again
1 parent cd4a4e6 commit a8bd2e2

1 file changed

Lines changed: 39 additions & 157 deletions

File tree

dns/transport/local/local_darwin_cgo.go

Lines changed: 39 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,23 @@ package local
44

55
/*
66
#include <stdlib.h>
7+
#include <netdb.h>
78
#include <dns.h>
8-
#include <resolv.h>
99
10-
static void *cgo_dns_open_super() {
11-
return (void *)dns_open(NULL);
12-
}
13-
14-
static void cgo_dns_close(void *opaque) {
15-
if (opaque != NULL) dns_free((dns_handle_t)opaque);
16-
}
17-
18-
static int cgo_dns_search(void *opaque, const char *name, int class, int type,
19-
unsigned char *answer, int anslen) {
20-
dns_handle_t handle = (dns_handle_t)opaque;
10+
static int cgo_dns_search(const char *name, int class, int type,
11+
unsigned char *answer, int anslen, int *out_h_errno) {
12+
dns_handle_t handle = (dns_handle_t)dns_open(NULL);
13+
if (handle == NULL) {
14+
*out_h_errno = NO_RECOVERY;
15+
return -1;
16+
}
2117
struct sockaddr_storage from;
2218
uint32_t fromlen = sizeof(from);
23-
return dns_search(handle, name, class, type, (char *)answer, anslen, (struct sockaddr *)&from, &fromlen);
24-
}
25-
26-
static void *cgo_res_init() {
27-
res_state state = calloc(1, sizeof(struct __res_state));
28-
if (state == NULL) return NULL;
29-
if (res_ninit(state) != 0) {
30-
free(state);
31-
return NULL;
32-
}
33-
return state;
34-
}
35-
36-
static void cgo_res_destroy(void *opaque) {
37-
res_state state = (res_state)opaque;
38-
res_ndestroy(state);
39-
free(state);
40-
}
41-
42-
static int cgo_res_nsearch(void *opaque, const char *dname, int class, int type,
43-
unsigned char *answer, int anslen,
44-
int timeout_seconds,
45-
int *out_h_errno) {
46-
res_state state = (res_state)opaque;
47-
state->retrans = timeout_seconds;
48-
state->retry = 1;
49-
int n = res_nsearch(state, dname, class, type, answer, anslen);
50-
if (n < 0) {
51-
*out_h_errno = state->res_h_errno;
52-
}
19+
h_errno = 0;
20+
int n = dns_search(handle, name, class, type, (char *)answer, anslen,
21+
(struct sockaddr *)&from, &fromlen);
22+
*out_h_errno = h_errno;
23+
dns_free(handle);
5324
return n;
5425
}
5526
*/
@@ -58,7 +29,6 @@ import "C"
5829
import (
5930
"context"
6031
"errors"
61-
"time"
6232
"unsafe"
6333

6434
boxC "github.com/sagernet/sing-box/constant"
@@ -73,125 +43,38 @@ const (
7343
darwinResolverTryAgain = 2
7444
darwinResolverNoRecovery = 3
7545
darwinResolverNoData = 4
76-
77-
darwinResolverMaxPacketSize = 65535
7846
)
7947

80-
var errDarwinNeedLargerBuffer = errors.New("darwin resolver response truncated")
81-
82-
func darwinLookupSystemDNS(name string, class, qtype, timeoutSeconds int) (*mDNS.Msg, error) {
83-
response, err := darwinSearchWithSystemRouting(name, class, qtype)
84-
if err == nil {
85-
return response, nil
86-
}
87-
fallbackResponse, fallbackErr := darwinSearchWithResolv(name, class, qtype, timeoutSeconds)
88-
if fallbackErr == nil || fallbackResponse != nil {
89-
return fallbackResponse, fallbackErr
90-
}
91-
return nil, E.Errors(
92-
E.Cause(err, "dns_search"),
93-
E.Cause(fallbackErr, "res_nsearch"),
94-
)
95-
}
96-
97-
func darwinSearchWithSystemRouting(name string, class, qtype int) (*mDNS.Msg, error) {
98-
handle := C.cgo_dns_open_super()
99-
if handle == nil {
100-
return nil, E.New("dns_open failed")
101-
}
102-
defer C.cgo_dns_close(handle)
103-
104-
cName := C.CString(name)
105-
defer C.free(unsafe.Pointer(cName))
106-
107-
bufSize := 1232
108-
for {
109-
answer := make([]byte, bufSize)
110-
n := C.cgo_dns_search(handle, cName, C.int(class), C.int(qtype),
111-
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)))
112-
if n <= 0 {
113-
return nil, E.New("dns_search failed for ", name)
114-
}
115-
if int(n) > bufSize {
116-
bufSize = int(n)
117-
continue
118-
}
119-
return unpackDarwinResolverMessage(answer[:int(n)], "dns_search")
120-
}
121-
}
122-
123-
func darwinSearchWithResolv(name string, class, qtype int, timeoutSeconds int) (*mDNS.Msg, error) {
124-
state := C.cgo_res_init()
125-
if state == nil {
126-
return nil, E.New("res_ninit failed")
127-
}
128-
defer C.cgo_res_destroy(state)
129-
48+
func darwinLookupSystemDNS(name string, class, qtype int) (*mDNS.Msg, error) {
13049
cName := C.CString(name)
13150
defer C.free(unsafe.Pointer(cName))
13251

133-
bufSize := 1232
134-
for {
135-
answer := make([]byte, bufSize)
136-
var hErrno C.int
137-
n := C.cgo_res_nsearch(state, cName, C.int(class), C.int(qtype),
138-
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
139-
C.int(timeoutSeconds),
140-
&hErrno)
141-
if n >= 0 {
142-
if int(n) > bufSize {
143-
bufSize = int(n)
144-
continue
145-
}
146-
return unpackDarwinResolverMessage(answer[:int(n)], "res_nsearch")
147-
}
148-
response, err := handleDarwinResolvFailure(name, answer, int(hErrno))
149-
if err == nil {
150-
return response, nil
151-
}
152-
if errors.Is(err, errDarwinNeedLargerBuffer) && bufSize < darwinResolverMaxPacketSize {
153-
bufSize *= 2
154-
if bufSize > darwinResolverMaxPacketSize {
155-
bufSize = darwinResolverMaxPacketSize
156-
}
157-
continue
158-
}
159-
return nil, err
52+
answer := make([]byte, 4096)
53+
var hErrno C.int
54+
n := C.cgo_dns_search(cName, C.int(class), C.int(qtype),
55+
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
56+
&hErrno)
57+
if n <= 0 {
58+
return nil, darwinResolverHErrno(name, int(hErrno))
16059
}
161-
}
162-
163-
func unpackDarwinResolverMessage(packet []byte, source string) (*mDNS.Msg, error) {
16460
var response mDNS.Msg
165-
err := response.Unpack(packet)
61+
err := response.Unpack(answer[:int(n)])
16662
if err != nil {
167-
return nil, E.Cause(err, "unpack ", source, " response")
63+
return nil, E.Cause(err, "unpack dns_search response")
16864
}
16965
return &response, nil
17066
}
17167

172-
func handleDarwinResolvFailure(name string, answer []byte, hErrno int) (*mDNS.Msg, error) {
173-
response, err := unpackDarwinResolverMessage(answer, "res_nsearch failure")
174-
if err == nil && response.Response {
175-
if response.Truncated && len(answer) < darwinResolverMaxPacketSize {
176-
return nil, errDarwinNeedLargerBuffer
177-
}
178-
return response, nil
179-
}
180-
return nil, darwinResolverHErrno(name, hErrno)
181-
}
182-
18368
func darwinResolverHErrno(name string, hErrno int) error {
18469
switch hErrno {
18570
case darwinResolverHostNotFound:
18671
return dns.RcodeNameError
187-
case darwinResolverTryAgain:
188-
return dns.RcodeServerFailure
189-
case darwinResolverNoRecovery:
190-
return dns.RcodeServerFailure
19172
case darwinResolverNoData:
19273
return dns.RcodeSuccess
74+
case darwinResolverTryAgain, darwinResolverNoRecovery:
75+
return dns.RcodeServerFailure
19376
default:
194-
return E.New("res_nsearch: unknown error ", hErrno, " for ", name)
77+
return E.New("dns_search: unknown h_errno ", hErrno, " for ", name)
19578
}
19679
}
19780

@@ -209,26 +92,13 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
20992
return t.dhcpTransport.Exchange0(ctx, message, dhcpServers)
21093
}
21194
}
212-
name := question.Name
213-
timeoutSeconds := int(boxC.DNSTimeout / time.Second)
214-
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
215-
remaining := time.Until(deadline)
216-
if remaining <= 0 {
217-
return nil, context.DeadlineExceeded
218-
}
219-
seconds := int(remaining.Seconds())
220-
if seconds < 1 {
221-
seconds = 1
222-
}
223-
timeoutSeconds = seconds
224-
}
22595
type resolvResult struct {
22696
response *mDNS.Msg
22797
err error
22898
}
22999
resultCh := make(chan resolvResult, 1)
230100
go func() {
231-
response, err := darwinLookupSystemDNS(name, int(question.Qclass), int(question.Qtype), timeoutSeconds)
101+
response, err := darwinLookupSystemDNS(question.Name, int(question.Qclass), int(question.Qtype))
232102
resultCh <- resolvResult{response, err}
233103
}()
234104
var result resolvResult
@@ -245,5 +115,17 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
245115
return nil, result.err
246116
}
247117
result.response.Id = message.Id
118+
// Workaround for a bug in Apple libresolv: res_query_mDNSResponder
119+
// (libresolv/res_query.c), used when the resolver has
120+
// DNS_FLAG_FORWARD_TO_MDNSRESPONDER set (typical inside a Network
121+
// Extension), writes:
122+
//
123+
// ans->qr = 1;
124+
// ans->qr = htons(ans->qr);
125+
//
126+
// HEADER.qr is a 1-bit bitfield (<arpa/nameser_compat.h>), so
127+
// htons(1) == 0x0100 gets truncated back to 0, clearing the QR bit.
128+
// Force it on so downstream clients see a valid response.
129+
result.response.Response = true
248130
return result.response, nil
249131
}

0 commit comments

Comments
 (0)