|  | // Copyright 2018 The Fuchsia 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 main | 
|  |  | 
|  | /* | 
|  | #include "dnssdfinder.h" | 
|  | #include <errno.h> | 
|  | #include <string.h> | 
|  | */ | 
|  | import "C" | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "fmt" | 
|  | "net" | 
|  | "runtime" | 
|  | "strings" | 
|  | "sync" | 
|  | "sync/atomic" | 
|  | "syscall" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | fuchsiaMDNSServiceMac = "_fuchsia._udp." | 
|  | daemonPollTimeoutMs   = 10 | 
|  | ) | 
|  |  | 
|  | type dnsSDFinder struct { | 
|  | // From finders.go | 
|  | deviceFinderBase | 
|  |  | 
|  | deviceChannel chan *fuchsiaDevice | 
|  | wg            sync.WaitGroup | 
|  | } | 
|  |  | 
|  | type dnsSDRef struct { | 
|  | cptr *C.DNSServiceRef | 
|  | } | 
|  |  | 
|  | type dnsRefAllocator func(cptr unsafe.Pointer) dnsSDError | 
|  |  | 
|  | func defaultDNSRefAlloc(cptr unsafe.Pointer) dnsSDError { | 
|  | return dnsSDError(C.dnsAllocate((*C.DNSServiceRef)(cptr))) | 
|  | } | 
|  |  | 
|  | func newDNSSDRef(allocator dnsRefAllocator) (*dnsSDRef, dnsSDError) { | 
|  | if allocator == nil { | 
|  | allocator = defaultDNSRefAlloc | 
|  | } | 
|  | var cptr C.DNSServiceRef | 
|  | d := &dnsSDRef{} | 
|  | d.cptr = &cptr | 
|  | if err := allocator(unsafe.Pointer(d.cptr)); err != dnsSDNoError { | 
|  | return nil, err | 
|  | } | 
|  | runtime.SetFinalizer(d, freeDNSSDRef) | 
|  | return d, 0 | 
|  | } | 
|  |  | 
|  | func freeDNSSDRef(d *dnsSDRef) { | 
|  | cptr := atomic.SwapPointer( | 
|  | (*unsafe.Pointer)(unsafe.Pointer(d.cptr)), // Converts d.cptr from *C.DNSServiceRef to (void **) so this function works. | 
|  | unsafe.Pointer(C.NULL)) | 
|  | if cptr != unsafe.Pointer(C.NULL) { | 
|  | C.dnsDeallocate(C.DNSServiceRef(cptr)) | 
|  | } | 
|  | } | 
|  |  | 
|  | type pollingFunction func(*dnsSDContext, int) (bool, syscall.Errno) | 
|  |  | 
|  | func defaultPollingFunction(d *dnsSDContext, timeout int) (bool, syscall.Errno) { | 
|  | var errno C.int | 
|  | res := C.dnsPollDaemon(*d.ref.cptr, daemonPollTimeoutMs, &errno) | 
|  | if res < 0 { | 
|  | return false, syscall.Errno(errno) | 
|  | } | 
|  | if res == 0 { | 
|  | return false, 0 | 
|  | } | 
|  | return true, 0 | 
|  | } | 
|  |  | 
|  | type dnsSDContext struct { | 
|  | ref         *dnsSDRef | 
|  | finder      *dnsSDFinder | 
|  | idx         uintptr | 
|  | pollingFunc pollingFunction | 
|  | } | 
|  |  | 
|  | var ( | 
|  | cm           sync.RWMutex | 
|  | clientOffset uintptr | 
|  | contexts     = map[uintptr]*dnsSDContext{} | 
|  | ) | 
|  |  | 
|  | // newDNSSDContext Attempts to allocate a new connection with the DNS-SD daemon, returning nil | 
|  | // and writing an error to the finder's `deviceChannel` if unable to do so, else returns a new | 
|  | // client context. | 
|  | func newDNSSDContext(m *dnsSDFinder, allocator dnsRefAllocator) *dnsSDContext { | 
|  | cm.Lock() | 
|  | defer cm.Unlock() | 
|  | dnsSDRef, err := newDNSSDRef(allocator) | 
|  | if err != dnsSDNoError { | 
|  | go func() { | 
|  | m.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("allocating DNS-SD ref returned %w", err), | 
|  | } | 
|  | }() | 
|  | return nil | 
|  | } | 
|  | ctx := &dnsSDContext{ | 
|  | ref:    dnsSDRef, | 
|  | idx:    clientOffset, | 
|  | finder: m, | 
|  | } | 
|  | contexts[clientOffset] = ctx | 
|  | clientOffset++ | 
|  | return ctx | 
|  | } | 
|  |  | 
|  | func (d *dnsSDContext) pollWrapper() (bool, syscall.Errno) { | 
|  | if d.pollingFunc != nil { | 
|  | return d.pollingFunc(d, daemonPollTimeoutMs) | 
|  | } | 
|  | return defaultPollingFunction(d, daemonPollTimeoutMs) | 
|  | } | 
|  |  | 
|  | // poll polls the daemon, firing off callbacks if necessary. | 
|  | func (d *dnsSDContext) poll() { | 
|  | var results bool | 
|  | var errno syscall.Errno | 
|  | for { | 
|  | results, errno = d.pollWrapper() | 
|  | if errno != 0 { | 
|  | // `Timeout()` is a subset of the checks included in | 
|  | // `Temporary()` and must be checked first. | 
|  | if errno.Timeout() { | 
|  | return | 
|  | } | 
|  | if errno.Temporary() { | 
|  | continue | 
|  | } | 
|  | go func() { | 
|  | d.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("polling dns-sd daemon: %w", errno), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  |  | 
|  | break | 
|  | } | 
|  |  | 
|  | if results { | 
|  | if err := dnsSDError(C.dnsProcessResults(*d.ref.cptr)); err != dnsSDNoError { | 
|  | go func() { | 
|  | d.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("dns-sd process results returned: %w", err), | 
|  | } | 
|  | }() | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // processResults runs an async loop that waits for either the context to expire | 
|  | // or for results to come in from the dns-sd daemon. When completed, marks it as | 
|  | // such in the parent work group. | 
|  | func (d *dnsSDContext) processResults(ctx context.Context) { | 
|  | d.finder.wg.Add(1) | 
|  | ctx, cancel := context.WithTimeout(ctx, d.finder.cmd.timeout) | 
|  | go func() { | 
|  | defer cancel() | 
|  | defer d.finder.wg.Done() | 
|  | defer freeDNSSDRef(d.ref) | 
|  | for { | 
|  | select { | 
|  | case <-ctx.Done(): | 
|  | return | 
|  | default: | 
|  | d.poll() | 
|  | } | 
|  | } | 
|  | }() | 
|  | } | 
|  |  | 
|  | func (m *dnsSDFinder) dnsSDResolve(nodename string) { | 
|  | dctx := newDNSSDContext(m, nil) | 
|  | if dctx == nil { | 
|  | return | 
|  | } | 
|  | resolveString := fmt.Sprintf("%s.%s", nodename, "local") | 
|  | cString := C.CString(resolveString) | 
|  | defer C.free(unsafe.Pointer(cString)) | 
|  | if err := dnsSDError(C.dnsResolve(cString, dctx.ref.cptr, C.bool(m.cmd.ipv4), C.bool(m.cmd.ipv6), unsafe.Pointer(dctx.idx))); err != dnsSDNoError { | 
|  | go func() { | 
|  | dctx.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("received error from dnsResolve: %w", err), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  | dctx.processResults(context.Background()) | 
|  | } | 
|  |  | 
|  | func (m *dnsSDFinder) dnsSDBrowse(ctx context.Context) { | 
|  | dctx := newDNSSDContext(m, nil) | 
|  | if dctx == nil { | 
|  | return | 
|  | } | 
|  | cString := C.CString(fuchsiaMDNSServiceMac) | 
|  | defer C.free(unsafe.Pointer(cString)) | 
|  | if err := dnsSDError(C.dnsBrowse(cString, dctx.ref.cptr, unsafe.Pointer(dctx.idx))); err != dnsSDNoError { | 
|  | go func() { | 
|  | m.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("dnsBrowse: %w", err), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  | dctx.processResults(ctx) | 
|  | } | 
|  |  | 
|  | func getDNSSDContext(idx uintptr) *dnsSDContext { | 
|  | cm.RLock() | 
|  | defer cm.RUnlock() | 
|  | return contexts[idx] | 
|  | } | 
|  |  | 
|  | func (m *dnsSDFinder) list(ctx context.Context, f chan *fuchsiaDevice) error { | 
|  | m.deviceChannel = f | 
|  | m.dnsSDBrowse(ctx) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (m *dnsSDFinder) resolve(ctx context.Context, f chan *fuchsiaDevice, nodenames ...string) error { | 
|  | m.deviceChannel = f | 
|  | for _, nodename := range nodenames { | 
|  | m.dnsSDResolve(nodename) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (m *dnsSDFinder) close() { | 
|  | // Since the mDNS daemon is being polled, the longest this will wait is | 
|  | // daemonPollTimeoutMs milliseconds (and however long it will take to | 
|  | // complete any potential last-millisecond results processing in the | 
|  | // worst case) past the deadline before all worker threads exit. | 
|  | m.wg.Wait() | 
|  | } | 
|  |  | 
|  | func newDNSSDFinder(cmd *devFinderCmd) *dnsSDFinder { | 
|  | return &dnsSDFinder{deviceFinderBase{cmd: cmd}, nil, sync.WaitGroup{}} | 
|  | } | 
|  |  | 
|  | func browseCallback(err dnsSDError, nodename string, dctx *dnsSDContext) { | 
|  | if err != dnsSDNoError { | 
|  | go func() { | 
|  | dctx.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("dns-sd browse callback error: %w", err), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  | dctx.finder.dnsSDResolve(nodename) | 
|  | } | 
|  |  | 
|  | func resolveCallback(err dnsSDError, hostname, ipString string, iface *net.Interface, dctx *dnsSDContext) { | 
|  | if err != dnsSDNoError { | 
|  | go func() { | 
|  | dctx.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("dns-sd browse callback error: %w", err), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  | if net.ParseIP(ipString) == nil { | 
|  | go func() { | 
|  | dctx.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("unable to parse IP received from dns-sd %s", ipString), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  | go func() { | 
|  | fdev := &fuchsiaDevice{ | 
|  | domain: strings.ReplaceAll(hostname, ".local.", ""), | 
|  | addr:   net.ParseIP(ipString), | 
|  | } | 
|  | if iface != nil && (fdev.addr.IsLinkLocalMulticast() || fdev.addr.IsLinkLocalUnicast()) { | 
|  | fdev.zone = iface.Name | 
|  | } | 
|  | if dctx.finder.cmd.localResolve { | 
|  | var err error | 
|  | fdev, err = fdev.outbound() | 
|  | if err != nil { | 
|  | dctx.finder.deviceChannel <- &fuchsiaDevice{err: err} | 
|  | return | 
|  | } | 
|  | } | 
|  | dctx.finder.deviceChannel <- fdev | 
|  | }() | 
|  | } | 
|  |  | 
|  | //////// C Callbacks | 
|  |  | 
|  | //export browseCallbackGoFunc | 
|  | func browseCallbackGoFunc(err C.int, replyName *C.char, idx unsafe.Pointer) { | 
|  | browseCallback(dnsSDError(err), C.GoString(replyName), getDNSSDContext(uintptr(idx))) | 
|  | } | 
|  |  | 
|  | //export resolveCallbackGoFunc | 
|  | func resolveCallbackGoFunc(err C.int, fullname, ip *C.char, zoneIdx uint32, idx unsafe.Pointer) { | 
|  | dctx := getDNSSDContext(uintptr(idx)) | 
|  | var iface *net.Interface | 
|  | if zoneIdx > 0 { | 
|  | var err error | 
|  | iface, err = net.InterfaceByIndex(int(zoneIdx)) | 
|  | if err != nil { | 
|  | go func() { | 
|  | dctx.finder.deviceChannel <- &fuchsiaDevice{ | 
|  | err: fmt.Errorf("error getting iface: %w", err), | 
|  | } | 
|  | }() | 
|  | return | 
|  | } | 
|  | } | 
|  | resolveCallback(dnsSDError(err), C.GoString(fullname), C.GoString(ip), iface, dctx) | 
|  | } |