| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- // Copyright 2022 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //go:build darwin
- package local
- import (
- "context"
- "errors"
- "runtime"
- "syscall"
- "unsafe"
- _ "unsafe"
- E "github.com/sagernet/sing/common/exceptions"
- mDNS "github.com/miekg/dns"
- )
- type (
- _C_char = byte
- _C_int = int32
- _C_uchar = byte
- _C_ushort = uint16
- _C_uint = uint32
- _C_ulong = uint64
- _C_struct___res_state = ResState
- _C_struct_sockaddr = syscall.RawSockaddr
- )
- func _C_free(p unsafe.Pointer) { runtime.KeepAlive(p) }
- func _C_malloc(n uintptr) unsafe.Pointer {
- if n <= 0 {
- n = 1
- }
- return unsafe.Pointer(&make([]byte, n)[0])
- }
- const (
- MAXNS = 3
- MAXDNSRCH = 6
- )
- type ResState struct {
- Retrans _C_int
- Retry _C_int
- Options _C_ulong
- Nscount _C_int
- Nsaddrlist [MAXNS]_C_struct_sockaddr
- Id _C_ushort
- Dnsrch [MAXDNSRCH + 1]*_C_char
- Defname [256]_C_char
- Pfcode _C_ulong
- Ndots _C_uint
- Nsort _C_uint
- stub [128]byte
- }
- //go:linkname ResNinit internal/syscall/unix.ResNinit
- func ResNinit(state *_C_struct___res_state) error
- //go:linkname ResNsearch internal/syscall/unix.ResNsearch
- func ResNsearch(state *_C_struct___res_state, dname *byte, class, typ int, ans *byte, anslen int) (int, error)
- //go:linkname ResNclose internal/syscall/unix.ResNclose
- func ResNclose(state *_C_struct___res_state)
- //go:linkname GoString internal/syscall/unix.GoString
- func GoString(p *byte) string
- // doBlockingWithCtx executes a blocking function in a separate goroutine when the provided
- // context is cancellable. It is intended for use with calls that don't support context
- // cancellation (cgo, syscalls). blocking func may still be running after this function finishes.
- // For the duration of the execution of the blocking function, the thread is 'acquired' using [acquireThread],
- // blocking might not be executed when the context gets canceled early.
- func doBlockingWithCtx[T any](ctx context.Context, blocking func() (T, error)) (T, error) {
- if err := acquireThread(ctx); err != nil {
- var zero T
- return zero, err
- }
- if ctx.Done() == nil {
- defer releaseThread()
- return blocking()
- }
- type result struct {
- res T
- err error
- }
- res := make(chan result, 1)
- go func() {
- defer releaseThread()
- var r result
- r.res, r.err = blocking()
- res <- r
- }()
- select {
- case r := <-res:
- return r.res, r.err
- case <-ctx.Done():
- var zero T
- return zero, ctx.Err()
- }
- }
- //go:linkname acquireThread net.acquireThread
- func acquireThread(ctx context.Context) error
- //go:linkname releaseThread net.releaseThread
- func releaseThread()
- func cgoResSearch(hostname string, rtype, class int) (*mDNS.Msg, error) {
- resStateSize := unsafe.Sizeof(_C_struct___res_state{})
- var state *_C_struct___res_state
- if resStateSize > 0 {
- mem := _C_malloc(resStateSize)
- defer _C_free(mem)
- memSlice := unsafe.Slice((*byte)(mem), resStateSize)
- clear(memSlice)
- state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0]))
- }
- if err := ResNinit(state); err != nil {
- return nil, errors.New("res_ninit failure: " + err.Error())
- }
- defer ResNclose(state)
- bufSize := maxDNSPacketSize
- buf := (*_C_uchar)(_C_malloc(uintptr(bufSize)))
- defer _C_free(unsafe.Pointer(buf))
- s, err := syscall.BytePtrFromString(hostname)
- if err != nil {
- return nil, err
- }
- var size int
- for {
- size, _ = ResNsearch(state, s, class, rtype, buf, bufSize)
- if size <= bufSize || size > 0xffff {
- break
- }
- // Allocate a bigger buffer to fit the entire msg.
- _C_free(unsafe.Pointer(buf))
- bufSize = size
- buf = (*_C_uchar)(_C_malloc(uintptr(bufSize)))
- }
- var msg mDNS.Msg
- if size == -1 {
- // macOS's libresolv seems to directly return -1 for responses that are not success responses but are exchanged.
- // However, we still need the response, so we fall back to parsing the entire buffer.
- err = msg.Unpack(unsafe.Slice(buf, bufSize))
- if err != nil {
- return nil, E.New("res_nsearch failure")
- }
- } else {
- err = msg.Unpack(unsafe.Slice(buf, size))
- if err != nil {
- return nil, err
- }
- }
- return &msg, nil
- }
|