sing-box/dns/transport/local/local_darwin_cgo.go
2026-06-08 08:51:46 +08:00

118 lines
3 KiB
Go

//go:build darwin
package local
/*
#include <stdlib.h>
#include <netdb.h>
#include <dns.h>
static int cgo_dns_search(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;
}
*/
import "C"
import (
"context"
"errors"
"unsafe"
"github.com/sagernet/sing-box/dns"
E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns"
)
const (
darwinResolverHostNotFound = 1
darwinResolverTryAgain = 2
darwinResolverNoRecovery = 3
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
}
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)
}
}
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
question := message.Question[0]
type resolvResult struct {
response *mDNS.Msg
err error
}
resultCh := make(chan resolvResult, 1)
go func() {
response, err := darwinLookupSystemDNS(question.Name, int(question.Qclass), int(question.Qtype))
resultCh <- resolvResult{response, err}
}()
var result resolvResult
select {
case <-ctx.Done():
return nil, ctx.Err()
case result = <-resultCh:
}
if result.err != nil {
var rcodeError dns.RcodeError
if errors.As(result.err, &rcodeError) {
return dns.FixedResponseStatus(message, int(rcodeError)), nil
}
return nil, result.err
}
result.response.Id = message.Id
// Workaround for a bug in Apple libresolv: res_query_mDNSResponder
// (libresolv/res_query.c), used when the resolver has
// DNS_FLAG_FORWARD_TO_MDNSRESPONDER set (typical inside a Network
// Extension), writes:
//
// ans->qr = 1;
// ans->qr = htons(ans->qr);
//
// HEADER.qr is a 1-bit bitfield (<arpa/nameser_compat.h>), so
// htons(1) == 0x0100 gets truncated back to 0, clearing the QR bit.
// Force it on so downstream clients see a valid response.
result.response.Response = true
return result.response, nil
}