blob: 78dc50db598bf59b5b762a9b864c1968c7820bad [file] [log] [blame]
// 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
import (
"context"
"fmt"
"log"
"net"
"strings"
"time"
"go.fuchsia.dev/fuchsia/tools/net/mdns"
"go.fuchsia.dev/fuchsia/tools/net/netboot"
)
type deviceFinderBase struct {
cmd *devFinderCmd
}
type mdnsFinder struct {
deviceFinderBase
}
const fuchsiaMDNSService = "_fuchsia._udp.local"
func parseAnswer(cmd *devFinderCmd, a mdns.Record, resp mDNSResponse) *fuchsiaDevice {
if a.Class == mdns.IN && (a.Type == mdns.A || a.Type == mdns.AAAA) &&
(len(a.Data) == net.IPv4len || len(a.Data) == net.IPv6len) {
localStr := ".local"
strIdx := strings.Index(a.Domain, localStr)
// Determines if ".local" is on the end of the string.
if strIdx == -1 || strIdx != len(a.Domain)-len(localStr) {
return nil
}
// Trims off localStr for proper formatting.
fuchsiaDomain := a.Domain[:strIdx]
if len(fuchsiaDomain) == 0 {
return &fuchsiaDevice{err: fmt.Errorf("fuchsia domain empty: %q", a.Domain)}
}
fdev := &fuchsiaDevice{
addr: net.IP(a.Data),
domain: fuchsiaDomain,
}
fdev.zone = resp.devAddr.(*net.UDPAddr).Zone
if cmd.localResolve {
var err error
fdev, err = fdev.outbound()
if err != nil {
return &fuchsiaDevice{err: err}
}
}
return fdev
}
return nil
}
func listMDNSHandler(cmd *devFinderCmd, resp mDNSResponse, f chan<- *fuchsiaDevice) {
if len(resp.rxPacket.Answers) == 0 {
return
}
a := resp.rxPacket.Answers[0]
if a.Class != mdns.IN || a.Type != mdns.PTR || a.Domain != fuchsiaMDNSService {
return
}
// fxbug.dev/6296: Some protection against malformed responses.
if len(a.Data) == 0 {
log.Print("Empty data in response. Ignoring...")
return
}
nameLength := int(a.Data[0])
if len(a.Data) < nameLength+1 {
log.Printf("Too short data in response. Got %d bytes; expected %d", len(a.Data), nameLength+1)
return
}
for _, a := range resp.rxPacket.Additional {
fdev := parseAnswer(cmd, a, resp)
if fdev != nil {
f <- fdev
}
}
}
func (m *mdnsFinder) list(ctx context.Context, f chan *fuchsiaDevice) error {
listPacket := mdns.Packet{
Header: mdns.Header{QDCount: 1},
Questions: []mdns.Question{
{
Domain: fuchsiaMDNSService,
Type: mdns.PTR,
Class: mdns.IN,
Unicast: false,
},
},
}
return m.cmd.sendMDNSPacket(ctx, listPacket, f)
}
func (m *mdnsFinder) resolve(ctx context.Context, f chan *fuchsiaDevice, domains ...string) error {
// Note: domains is ignored for mdns, and is filtered by the cmd filter callback.
return m.list(ctx, f)
}
func (m *mdnsFinder) close() {}
type netbootFinder struct {
deviceFinderBase
}
func (n *netbootFinder) list(ctx context.Context, f chan *fuchsiaDevice) error {
if !n.cmd.ipv6 {
return fmt.Errorf("netboot finder is ipv6 only")
}
return n.resolve(ctx, f, netboot.NodenameWildcard)
}
func (n *netbootFinder) resolve(ctx context.Context, f chan *fuchsiaDevice, nodenames ...string) error {
if !n.cmd.ipv6 {
return fmt.Errorf("netboot finder is ipv6 only")
}
ctx, cancel := context.WithTimeout(ctx, n.cmd.timeout)
// Timeout isn't really used for this application of the client, so
// just pick an arbitrary timeout.
c := n.cmd.newNetbootClient(time.Second)
t := make(chan *netboot.Target, 1024)
for _, nodename := range nodenames {
cleanup, err := c.StartDiscover(ctx, t, nodename)
if err != nil {
return fmt.Errorf("netboot client setup: %v", err)
}
go func() {
defer cancel()
defer cleanup()
for {
select {
case target := <-t:
if target.Error != nil {
f <- &fuchsiaDevice{
err: target.Error,
}
return
}
zone := ""
if target.Interface != nil {
zone = target.Interface.Name
}
addr := target.TargetAddress
fdev := &fuchsiaDevice{
addr: addr,
domain: target.Nodename,
zone: zone,
}
if n.cmd.localResolve {
var err error
fdev, err = fdev.outbound()
if err != nil {
f <- &fuchsiaDevice{err: err}
return
}
}
f <- fdev
case <-ctx.Done():
return
}
}
}()
}
return nil
}
func (n *netbootFinder) close() {}