blob: 4a5e7491fcba4ba6556a953a9f2753f7d9ddb11c [file] [log] [blame]
// Copyright 2019 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"
"flag"
"fmt"
"log"
"strings"
"github.com/google/subcommands"
"go.fuchsia.dev/tools/net/mdns"
)
const (
fuchsiaService = "_fuchsia._udp.local"
)
type listCmd struct {
devFinderCmd
// Determines whether or not to print the full device info.
fullInfo bool
// Filters domains that match this string when listing devices.
domainFilter string
}
func (*listCmd) Name() string {
return "list"
}
func (*listCmd) Usage() string {
return "list [flags...]\n\nflags:\n"
}
func (*listCmd) Synopsis() string {
return "lists all Fuchsia devices on the network"
}
func (cmd *listCmd) SetFlags(f *flag.FlagSet) {
cmd.SetCommonFlags(f)
f.StringVar(&cmd.domainFilter, "domain-filter", "", "When using the \"list\" command, returns only devices that match this domain name.")
f.BoolVar(&cmd.fullInfo, "full", false, "Print device address and domain")
}
func listMDNSHandler(resp mDNSResponse, localResolve bool, devChan chan<- *fuchsiaDevice, errChan chan<- error) {
for _, a := range resp.rxPacket.Answers {
if a.Class == mdns.IN && a.Type == mdns.PTR {
// DX-1498: Some protection against malformed responses.
if len(a.Data) == 0 {
log.Print("Empty data in response. Ignoring...")
continue
}
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)
continue
}
// This is a bit convoluted: the domain param is being used
// as a "service", and the Data field actually contains the
// domain of the device.
fuchsiaDomain := string(a.Data[1 : nameLength+1])
if localResolve {
recvIP, err := resp.getReceiveIP()
if err != nil {
errChan <- err
return
}
devChan <- &fuchsiaDevice{recvIP, fuchsiaDomain}
continue
}
if ip, err := addrToIP(resp.devAddr); err != nil {
errChan <- fmt.Errorf("could not find addr for %v: %v", resp.devAddr, err)
} else {
devChan <- &fuchsiaDevice{
addr: ip,
domain: fuchsiaDomain,
}
}
}
}
}
func (cmd *listCmd) listDevices(ctx context.Context) ([]*fuchsiaDevice, error) {
listPacket := mdns.Packet{
Header: mdns.Header{QDCount: 1},
Questions: []mdns.Question{
{
Domain: fuchsiaService,
Type: mdns.PTR,
Class: mdns.IN,
Unicast: false,
},
},
}
devices, err := cmd.sendMDNSPacket(ctx, listPacket)
if err != nil {
return nil, fmt.Errorf("sending/receiving mdns packets: %v", err)
}
var filteredDevices []*fuchsiaDevice
for _, device := range devices {
if strings.Contains(device.domain, cmd.domainFilter) {
filteredDevices = append(filteredDevices, device)
}
}
if len(filteredDevices) == 0 {
return nil, fmt.Errorf("no devices with domain matching '%v'", cmd.domainFilter)
}
return filteredDevices, nil
}
func (cmd *listCmd) execute(ctx context.Context) error {
filteredDevices, err := cmd.listDevices(ctx)
if err != nil {
return err
}
if cmd.json {
return cmd.outputJSON(filteredDevices, cmd.fullInfo)
}
return cmd.outputNormal(filteredDevices, cmd.fullInfo)
}
func (cmd *listCmd) Execute(ctx context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
cmd.mdnsHandler = listMDNSHandler
if err := cmd.execute(ctx); err != nil {
log.Print(err)
return subcommands.ExitFailure
}
return subcommands.ExitSuccess
}