mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-11 10:44:41 +00:00
DNS Queries on Windows using DnsQueryEx and asynchronous procedure calls
Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
parent
c0701b130d
commit
17abf201a3
46
cmd/dnsapc/dnsapc.go
Normal file
46
cmd/dnsapc/dnsapc.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"tailscale.com/util/winutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
result, err := winutil.DnsQuery("www.tailscale.com", windows.DNS_TYPE_A, winutil.DNS_QUERY_STANDARD, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
finalStatus := result.QueryStatus
|
||||||
|
fmt.Printf("Query status: %v", finalStatus)
|
||||||
|
if finalStatus != 0 {
|
||||||
|
fmt.Printf(" (%v)\n", windows.Errno(finalStatus))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for rec := result.QueryRecords; rec != nil; rec = rec.Next {
|
||||||
|
name := windows.UTF16PtrToString(rec.Name)
|
||||||
|
fmt.Printf("Record %d: %s, type %v", count, name, rec.Type)
|
||||||
|
switch rec.Type {
|
||||||
|
case windows.DNS_TYPE_A:
|
||||||
|
rd := (*winutil.DNSAData)(unsafe.Pointer(&rec.Data[0]))
|
||||||
|
a := rd.IPv4Address
|
||||||
|
fmt.Printf(" (A): %v.%v.%v.%v\n", a[0], a[1], a[2], a[3])
|
||||||
|
case windows.DNS_TYPE_CNAME:
|
||||||
|
rd := (*windows.DNSPTRData)(unsafe.Pointer(&rec.Data[0]))
|
||||||
|
fmt.Printf(" (CNAME): %s\n", windows.UTF16PtrToString(rd.Host))
|
||||||
|
default:
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Close()
|
||||||
|
}
|
213
util/winutil/apcthread_windows.go
Normal file
213
util/winutil/apcthread_windows.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package winutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
k32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
procWaitForSingleObjectEx = k32.NewProc("WaitForSingleObjectEx")
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitForSingleObjectEx(handle windows.Handle, timeout uint32, alertable bool) (uint32, error) {
|
||||||
|
var ua uintptr
|
||||||
|
if alertable {
|
||||||
|
ua = 1
|
||||||
|
}
|
||||||
|
code, _, err := procWaitForSingleObjectEx.Call(uintptr(handle), uintptr(timeout), ua)
|
||||||
|
code32 := uint32(code)
|
||||||
|
if code32 == windows.WAIT_FAILED {
|
||||||
|
return code32, err
|
||||||
|
}
|
||||||
|
return code32, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
noPendingIdleTimeout = 30000
|
||||||
|
reqChanBufSize = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
type APCChannel chan []reflect.Value
|
||||||
|
|
||||||
|
func MakeAPCChannel() APCChannel {
|
||||||
|
return make(APCChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
type APCRequest interface {
|
||||||
|
Begin() *APCChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
type APCChannelResolver interface {
|
||||||
|
GetChannel([]reflect.Value) *APCChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
type APCCallbackInfo struct {
|
||||||
|
Function reflect.Type
|
||||||
|
Resolver APCChannelResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type apcThread struct {
|
||||||
|
once sync.Once
|
||||||
|
event windows.Handle
|
||||||
|
reqChan chan APCRequest
|
||||||
|
pending map[*APCChannel]struct{}
|
||||||
|
mu sync.RWMutex // protects cbinfo
|
||||||
|
cbinfo map[APCCallbackInfo]uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *apcThread) init() {
|
||||||
|
var err error
|
||||||
|
// Auto-reset event for signaling that new requests are present
|
||||||
|
t.event, err = windows.CreateEvent(nil, 1, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Creating apcThread event: %v", err))
|
||||||
|
}
|
||||||
|
t.reqChan = make(chan APCRequest, reqChanBufSize)
|
||||||
|
t.pending = make(map[*APCChannel]struct{})
|
||||||
|
t.cbinfo = make(map[APCCallbackInfo]uintptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *apcThread) submitWork(req APCRequest) error {
|
||||||
|
// Lazily start the goroutine
|
||||||
|
t.once.Do(func() {
|
||||||
|
go t.run()
|
||||||
|
})
|
||||||
|
t.reqChan <- req
|
||||||
|
// We need to set an event to poke the APC thread into checking t.reqChan.
|
||||||
|
return windows.SetEvent(t.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
var thd apcThread
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
thd.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *apcThread) nextWaitTimeout() uint32 {
|
||||||
|
if len(t.pending) > 0 {
|
||||||
|
return windows.INFINITE
|
||||||
|
} else {
|
||||||
|
return noPendingIdleTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *apcThread) beginRequest(req APCRequest) {
|
||||||
|
// Lock the OS thread before calling Begin, which will initiate the APC
|
||||||
|
// request on the current OS thread.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
apcctx := req.Begin()
|
||||||
|
if apcctx == nil {
|
||||||
|
// Request failed, we don't need to lock anymore.
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
} else {
|
||||||
|
// Save the context so it doesn't get GC'd and we can track pending requests
|
||||||
|
t.pending[apcctx] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run is the goroutine that executes APC requests. It is started lazily, but
|
||||||
|
// once it is running, it remains so for the remainder of the process's lifetime.
|
||||||
|
// Note that it only locks the OS thread while requests are in-flight; once
|
||||||
|
// all requests have been processed, it blocks on t.reqChan without consuming
|
||||||
|
// an OS thread.
|
||||||
|
// (Hi Brad! When this goroutine is 100% idle, it does not lock an OS thread.
|
||||||
|
// Is this acceptable, or do we want additional magic to make the
|
||||||
|
// entire goroutine shut down after an extended period of disuse?)
|
||||||
|
func (t *apcThread) run() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case req := <-t.reqChan:
|
||||||
|
t.beginRequest(req)
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// If nothing is pending, we can safely block indefinitely on the request channel.
|
||||||
|
// Otherwise we need to fall through into blocking on t.event so that we may process APCs.
|
||||||
|
if len(t.pending) == 0 {
|
||||||
|
req := <-t.reqChan
|
||||||
|
t.beginRequest(req)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var waitCode uint32
|
||||||
|
var err error
|
||||||
|
for waitCode, err = waitForSingleObjectEx(t.event, t.nextWaitTimeout(), true); waitCode == windows.WAIT_IO_COMPLETION; {
|
||||||
|
// Drain queued APCs
|
||||||
|
}
|
||||||
|
switch waitCode {
|
||||||
|
case uint32(windows.WAIT_TIMEOUT):
|
||||||
|
// There are no more requests pending, we can just block on t.reqChan now
|
||||||
|
continue
|
||||||
|
case uint32(windows.WAIT_FAILED):
|
||||||
|
panic(fmt.Sprintf("apcThread waitForSingleObjectEx failed: %v", err))
|
||||||
|
default:
|
||||||
|
// There are new requests in the channel.
|
||||||
|
windows.ResetEvent(t.event)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type apcHandler func([]reflect.Value) []reflect.Value
|
||||||
|
|
||||||
|
// makeAPCHandler creates a handler function that an APC will invoke to complete
|
||||||
|
// its request. args contains the APC's arguments, which are then sent to the
|
||||||
|
// channel for processing by the API consumer.
|
||||||
|
func (t *apcThread) makeAPCHandler(resolver APCChannelResolver) apcHandler {
|
||||||
|
return func(args []reflect.Value) []reflect.Value {
|
||||||
|
apcchan := resolver.GetChannel(args)
|
||||||
|
delete(t.pending, apcchan)
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
*apcchan <- args
|
||||||
|
// APCs don't use return values, but we need to return this to satisfy
|
||||||
|
// Go's callback requirements.
|
||||||
|
return []reflect.Value{reflect.ValueOf(uintptr(0))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *apcThread) registerCallback(cb APCCallbackInfo) uintptr {
|
||||||
|
// Common path: Callback is already registered
|
||||||
|
t.mu.RLock()
|
||||||
|
cookie, ok := t.cbinfo[cb]
|
||||||
|
t.mu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path: We need to register
|
||||||
|
t.mu.Lock()
|
||||||
|
// Check again to make sure we didn't lose a race
|
||||||
|
cookie, ok = t.cbinfo[cb]
|
||||||
|
if ok {
|
||||||
|
t.mu.Unlock()
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := t.makeAPCHandler(cb.Resolver)
|
||||||
|
outer := reflect.MakeFunc(cb.Function, handler)
|
||||||
|
cbptr := windows.NewCallback(outer.Interface())
|
||||||
|
t.cbinfo[cb] = cbptr
|
||||||
|
t.mu.Unlock()
|
||||||
|
return cbptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterAPCCallback must be called any time a new type of APC is going to be
|
||||||
|
// submitted. Ideally this would be called only once for each type (via sync.Once).
|
||||||
|
func RegisterAPCCallback(cb APCCallbackInfo) uintptr {
|
||||||
|
return thd.registerCallback(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitAPCWork is the main entry point for submitting work for APC processing.
|
||||||
|
// The APC type must have been previously registered via RegisterAPICallback.
|
||||||
|
func SubmitAPCWork(req APCRequest) error {
|
||||||
|
return thd.submitWork(req)
|
||||||
|
}
|
111
util/winutil/dnsapc_windows.go
Normal file
111
util/winutil/dnsapc_windows.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package winutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Technically this function returns void, but we need it to return uintptr for NewCallback to work
|
||||||
|
func dnsQueryExApc(param uintptr, results *DNSQueryResult) uintptr {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type resolver struct{}
|
||||||
|
|
||||||
|
func (r resolver) GetChannel(args []reflect.Value) *APCChannel {
|
||||||
|
return &((*invoker)(unsafe.Pointer(uintptr(args[0].Uint())))).done
|
||||||
|
}
|
||||||
|
|
||||||
|
type invoker struct {
|
||||||
|
done APCChannel
|
||||||
|
req DNSQueryRequest
|
||||||
|
result DNSQueryResult
|
||||||
|
cancel DNSQueryCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSServerList struct {
|
||||||
|
Family uint16
|
||||||
|
List []DNSAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
apcCallback uintptr
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDnsInvoker(qname string, qtype uint16, qoptions uint64, srvList *DNSServerList, ifaceIdx uint32) (*invoker, error) {
|
||||||
|
once.Do(func() {
|
||||||
|
cbInfo := APCCallbackInfo{reflect.TypeOf(dnsQueryExApc), resolver{}}
|
||||||
|
apcCallback = RegisterAPCCallback(cbInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
var name *uint16
|
||||||
|
var err error
|
||||||
|
if len(qname) > 0 {
|
||||||
|
name, err = windows.UTF16PtrFromString(qname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverList *DNSAddrArray
|
||||||
|
if srvList != nil {
|
||||||
|
serverList = NewDNSAddrArray(srvList.Family, srvList.List)
|
||||||
|
}
|
||||||
|
|
||||||
|
inv := &invoker{done: MakeAPCChannel(),
|
||||||
|
req: DNSQueryRequest{
|
||||||
|
Version: DNS_QUERY_REQUEST_VERSION1,
|
||||||
|
QueryName: name,
|
||||||
|
QueryType: qtype,
|
||||||
|
QueryOptions: qoptions,
|
||||||
|
DNSServerList: serverList,
|
||||||
|
InterfaceIndex: ifaceIdx,
|
||||||
|
QueryCompletionCallback: apcCallback},
|
||||||
|
result: DNSQueryResult{Version: DNS_QUERY_RESULTS_VERSION1}}
|
||||||
|
inv.req.QueryContext = uintptr(unsafe.Pointer(inv))
|
||||||
|
|
||||||
|
return inv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *invoker) Begin() *APCChannel {
|
||||||
|
err := DnsQueryEx(&i.req, &i.result, &i.cancel)
|
||||||
|
if err != DNS_REQUEST_PENDING {
|
||||||
|
i.result.QueryStatus = DNSStatus(uintptr(err.(windows.Errno)))
|
||||||
|
close(i.done)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &i.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *invoker) Wait() {
|
||||||
|
<-i.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *invoker) Cancel() error {
|
||||||
|
return DnsCancelQuery(&i.cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DnsQuery(qname string, qtype uint16, qoptions uint64, srvList *DNSServerList, interfaceIdx uint32) (*DNSQueryResult, error) {
|
||||||
|
inv, err := newDnsInvoker(qname, qtype, qoptions, srvList, interfaceIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed creating DNS invoker: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SubmitAPCWork(inv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed submitting work: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inv.Wait()
|
||||||
|
return &inv.result, nil
|
||||||
|
}
|
161
util/winutil/dnsq_windows.go
Normal file
161
util/winutil/dnsq_windows.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package winutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSAddr struct {
|
||||||
|
maxSa [32]byte /* DNS_ADDR_MAX_SOCKADDR_LENGTH */
|
||||||
|
dnsAddrUserDword [8]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DNSAddr) AsInet4() *windows.SockaddrInet4 {
|
||||||
|
return (*windows.SockaddrInet4)(unsafe.Pointer(&a.maxSa[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DNSAddr) AsInet6() *windows.SockaddrInet6 {
|
||||||
|
return (*windows.SockaddrInet6)(unsafe.Pointer(&a.maxSa[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSAData struct {
|
||||||
|
IPv4Address [4]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSAddrArray struct {
|
||||||
|
MaxCount uint32
|
||||||
|
AddrCount uint32
|
||||||
|
tag uint32
|
||||||
|
Family uint16
|
||||||
|
wreserved uint16
|
||||||
|
flags uint32
|
||||||
|
matchFlag uint32
|
||||||
|
reserved1 uint32
|
||||||
|
reserved2 uint32
|
||||||
|
AddrArray [1]DNSAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We can probably make this more efficient
|
||||||
|
func NewDNSAddrArray(family uint16, addrs []DNSAddr) *DNSAddrArray {
|
||||||
|
numBytes := unsafe.Sizeof(DNSAddrArray{})
|
||||||
|
count := len(addrs)
|
||||||
|
if count > 1 {
|
||||||
|
numBytes += (uintptr(count) - 1) * unsafe.Sizeof(DNSAddr{})
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, numBytes)
|
||||||
|
result := (*DNSAddrArray)(unsafe.Pointer(&buf[0]))
|
||||||
|
result.MaxCount = uint32(count)
|
||||||
|
result.AddrCount = uint32(count)
|
||||||
|
result.Family = family
|
||||||
|
|
||||||
|
dstAddrs := unsafe.Slice(&result.AddrArray[0], count)
|
||||||
|
copy(dstAddrs, addrs)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNS_QUERY_REQUEST_VERSION1 = 1
|
||||||
|
DNS_QUERY_REQUEST_VERSION3 = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNS_QUERY_RESULTS_VERSION1 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNS_QUERY_STANDARD = 0x00000000
|
||||||
|
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 0x00000001
|
||||||
|
DNS_QUERY_USE_TCP_ONLY = 0x00000002
|
||||||
|
DNS_QUERY_NO_RECURSION = 0x00000004
|
||||||
|
DNS_QUERY_BYPASS_CACHE = 0x00000008
|
||||||
|
DNS_QUERY_NO_WIRE_QUERY = 0x00000010
|
||||||
|
DNS_QUERY_NO_LOCAL_NAME = 0x00000020
|
||||||
|
DNS_QUERY_NO_HOSTS_FILE = 0x00000040
|
||||||
|
DNS_QUERY_NO_NETBT = 0x00000080
|
||||||
|
DNS_QUERY_WIRE_ONLY = 0x00000100
|
||||||
|
DNS_QUERY_RETURN_MESSAGE = 0x00000200
|
||||||
|
DNS_QUERY_MULTICAST_ONLY = 0x00000400
|
||||||
|
DNS_QUERY_NO_MULTICAST = 0x00000800
|
||||||
|
DNS_QUERY_TREAT_AS_FQDN = 0x00001000
|
||||||
|
DNS_QUERY_ADDRCONFIG = 0x00002000
|
||||||
|
DNS_QUERY_DUAL_ADDR = 0x00004000
|
||||||
|
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x00100000
|
||||||
|
DNS_QUERY_DISABLE_IDN_ENCODING = 0x00200000
|
||||||
|
DNS_QUERY_APPEND_MULTILABEL = 0x00800000
|
||||||
|
DNS_QUERY_DNSSEC_OK = 0x01000000
|
||||||
|
DNS_QUERY_DNSSEC_CHECKING_DISABLED = 0x02000000
|
||||||
|
DNS_QUERY_RESERVED = 0xf0000000
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSQueryRequest struct {
|
||||||
|
Version uint32
|
||||||
|
QueryName *uint16
|
||||||
|
QueryType uint16
|
||||||
|
QueryOptions uint64
|
||||||
|
DNSServerList *DNSAddrArray
|
||||||
|
InterfaceIndex uint32
|
||||||
|
QueryCompletionCallback uintptr
|
||||||
|
QueryContext uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSCustomServer struct {
|
||||||
|
ServerType uint32
|
||||||
|
Flags uint64
|
||||||
|
Template *uint16
|
||||||
|
MaxSa [32]byte /* DNS_ADDR_MAX_SOCKADDR_LENGTH */
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSQueryRequest3 struct {
|
||||||
|
DNSQueryRequest
|
||||||
|
IsNetworkQueryRequired int32 /* BOOL */
|
||||||
|
RequiredNetworkIndex uint32
|
||||||
|
CCustomServers uint32
|
||||||
|
PCustomServers *DNSCustomServer
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNS_CUSTOM_SERVER_TYPE_UDP = 0x1
|
||||||
|
DNS_CUSTOM_SERVER_TYPE_DOH = 0x2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNS_CUSTOM_SERVER_UDP_FALLBACK = 0x1
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DNS_REQUEST_PENDING windows.Errno = 0x00002522
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSStatus int32
|
||||||
|
|
||||||
|
type DNSQueryResult struct {
|
||||||
|
Version uint32
|
||||||
|
QueryStatus DNSStatus
|
||||||
|
QueryOptions uint64
|
||||||
|
QueryRecords *windows.DNSRecord
|
||||||
|
reserved uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNSFreeFlat = 0
|
||||||
|
DNSFreeRecordList = 1
|
||||||
|
DNSFreeParsedMessageFields = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (qr *DNSQueryResult) Close() error {
|
||||||
|
windows.DnsRecordListFree(qr.QueryRecords, DNSFreeRecordList)
|
||||||
|
qr.QueryRecords = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSQueryCancel struct {
|
||||||
|
// This is defined in C as 32 bytes, but it is also declared with 8 byte alignment
|
||||||
|
reserved [4]uint64
|
||||||
|
}
|
11
util/winutil/mksyscall.go
Normal file
11
util/winutil/mksyscall.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package winutil
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go
|
||||||
|
|
||||||
|
// Note: DO NOT use DnsQueryExW! It *is* exported from dnsapi.dll but is an internal function!
|
||||||
|
//sys DnsQueryEx(request *DNSQueryRequest, result *DNSQueryResult, cancelHandle *DNSQueryCancel) (status error) = dnsapi.DnsQueryEx
|
||||||
|
//sys DnsCancelQuery(cancelHandle *DNSQueryCancel) (status error) = dnsapi.DnsCancelQuery
|
61
util/winutil/zsyscall_windows.go
Normal file
61
util/winutil/zsyscall_windows.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Code generated by 'go generate'; DO NOT EDIT.
|
||||||
|
|
||||||
|
package winutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
moddnsapi = windows.NewLazySystemDLL("dnsapi.dll")
|
||||||
|
|
||||||
|
procDnsCancelQuery = moddnsapi.NewProc("DnsCancelQuery")
|
||||||
|
procDnsQueryEx = moddnsapi.NewProc("DnsQueryEx")
|
||||||
|
)
|
||||||
|
|
||||||
|
func DnsCancelQuery(cancelHandle *DNSQueryCancel) (status error) {
|
||||||
|
r0, _, _ := syscall.Syscall(procDnsCancelQuery.Addr(), 1, uintptr(unsafe.Pointer(cancelHandle)), 0, 0)
|
||||||
|
if r0 != 0 {
|
||||||
|
status = syscall.Errno(r0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DnsQueryEx(request *DNSQueryRequest, result *DNSQueryResult, cancelHandle *DNSQueryCancel) (status error) {
|
||||||
|
r0, _, _ := syscall.Syscall(procDnsQueryEx.Addr(), 3, uintptr(unsafe.Pointer(request)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(cancelHandle)))
|
||||||
|
if r0 != 0 {
|
||||||
|
status = syscall.Errno(r0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user