[dev_finder] sends requests to multiple addresses

Due to a netstack bug, multiple sockets can't join the same multicast
address. As a temporary workaround, sends mDNS requests to multiple
addresses.

Adds 224.0.0.250 in addition to the current default value 224.0.0.251.

Bug: DX-1453  #comment
Change-Id: I783959100f9abf0fc797215c2dc71e32931b7622
diff --git a/cmd/dev_finder/common.go b/cmd/dev_finder/common.go
index 1a3288e..e74d408 100644
--- a/cmd/dev_finder/common.go
+++ b/cmd/dev_finder/common.go
@@ -58,12 +58,14 @@
 	Start(ctx context.Context, port int) error
 }
 
-type newMDNSFunc func() mdnsInterface
+type newMDNSFunc func(address string) mdnsInterface
 
 // Contains common command information for embedding in other dev_finder commands.
 type devFinderCmd struct {
 	// Outputs in JSON format if true.
 	json bool
+	// The mDNS addresses to connect to.
+	mdnsAddrs string
 	// The mDNS ports to connect to.
 	mdnsPorts string
 	// The timeout in ms to either give up or to exit the program after finding at least one
@@ -91,6 +93,7 @@
 
 func (cmd *devFinderCmd) SetCommonFlags(f *flag.FlagSet) {
 	f.BoolVar(&cmd.json, "json", false, "Outputs in JSON format.")
+	f.StringVar(&cmd.mdnsAddrs, "addr", "224.0.0.251,224.0.0.250", "Comma separated list of addresses to issue mDNS queries to.")
 	f.StringVar(&cmd.mdnsPorts, "port", "5353,5356", "Comma separated list of ports to issue mDNS queries to.")
 	f.IntVar(&cmd.timeout, "timeout", 2000, "The number of milliseconds before declaring a timeout.")
 	f.BoolVar(&cmd.localResolve, "local", false, "Returns the address of the interface to the host when doing service lookup/domain resolution.")
@@ -117,11 +120,11 @@
 	return nil, errors.New("unsupported address type")
 }
 
-func (cmd *devFinderCmd) newMDNS() mdnsInterface {
+func (cmd *devFinderCmd) newMDNS(address string) mdnsInterface {
 	if cmd.newMDNSFunc != nil {
-		return cmd.newMDNSFunc()
+		return cmd.newMDNSFunc(address)
 	}
-	return &mdns.MDNS{}
+	return &mdns.MDNS{Address: address}
 }
 
 func (cmd *devFinderCmd) sendMDNSPacket(ctx context.Context, packet mdns.Packet) ([]*fuchsiaDevice, error) {
@@ -132,30 +135,38 @@
 		return nil, fmt.Errorf("invalid timeout value: %v", cmd.timeout)
 	}
 
+	addrs := strings.Split(cmd.mdnsAddrs, ",")
+	var ports []int
+	for _, s := range strings.Split(cmd.mdnsPorts, ",") {
+		p, err := strconv.ParseUint(s, 10, 16)
+		if err != nil {
+			return nil, fmt.Errorf("Could not parse port number %v: %v\n", s, err)
+		}
+		ports = append(ports, int(p))
+	}
+
 	ctx, cancel := context.WithTimeout(ctx, time.Duration(cmd.timeout)*time.Millisecond)
 	defer cancel()
 	errChan := make(chan error)
 	devChan := make(chan *fuchsiaDevice)
-	for _, p := range strings.Split(cmd.mdnsPorts, ",") {
-		m := cmd.newMDNS()
-		m.AddHandler(func(recv net.Interface, addr net.Addr, rxPacket mdns.Packet) {
-			response := mDNSResponse{recv, addr, rxPacket}
-			cmd.mdnsHandler(response, cmd.localResolve, devChan, errChan)
-		})
-		m.AddErrorHandler(func(err error) {
-			errChan <- err
-		})
-		m.AddWarningHandler(func(addr net.Addr, err error) {
-			log.Printf("from: %v warn: %v\n", addr, err)
-		})
-		i, err := strconv.ParseUint(p, 10, 16)
-		if err != nil {
-			return nil, fmt.Errorf("Could not parse port number %v: %v\n", p, err)
+	for _, addr := range addrs {
+		for _, p := range ports {
+			m := cmd.newMDNS(addr)
+			m.AddHandler(func(recv net.Interface, addr net.Addr, rxPacket mdns.Packet) {
+				response := mDNSResponse{recv, addr, rxPacket}
+				cmd.mdnsHandler(response, cmd.localResolve, devChan, errChan)
+			})
+			m.AddErrorHandler(func(err error) {
+				errChan <- err
+			})
+			m.AddWarningHandler(func(addr net.Addr, err error) {
+				log.Printf("from: %v warn: %v\n", addr, err)
+			})
+			if err := m.Start(ctx, p); err != nil {
+				return nil, fmt.Errorf("starting mdns: %v", err)
+			}
+			m.Send(packet)
 		}
-		if err := m.Start(ctx, int(i)); err != nil {
-			return nil, fmt.Errorf("starting mdns: %v", err)
-		}
-		m.Send(packet)
 	}
 
 	devices := make([]*fuchsiaDevice, 0)
diff --git a/cmd/dev_finder/dev_finder_test.go b/cmd/dev_finder/dev_finder_test.go
index 6487491..283a86b 100644
--- a/cmd/dev_finder/dev_finder_test.go
+++ b/cmd/dev_finder/dev_finder_test.go
@@ -77,6 +77,35 @@
 }
 func (m *fakeMDNS) Start(context.Context, int) error { return nil }
 
+func newDevFinderCmd(handler mDNSHandler, answerDomains []string) devFinderCmd {
+	// Because mdnsAddrs have two addresses specified and mdnsPorts have
+	// two ports specified, four MDNS objects are created. To emulate the
+	// case where only one of them responds, create only one fake MDNS
+	// object with answers. The others wouldn't respond at all. See the
+	// Send() method above.
+	mdnsCount := 0
+	return devFinderCmd{
+		mdnsHandler: handler,
+		mdnsAddrs:   "224.0.0.251,224.0.0.250",
+		mdnsPorts:   "5353,5356",
+		timeout:     10,
+		newMDNSFunc: func(addr string) mdnsInterface {
+			mdnsCount++
+			switch mdnsCount {
+			case 1:
+				return &fakeMDNS{
+					answer: &fakeAnswer{
+						ip:      "192.168.0.42",
+						domains: answerDomains,
+					},
+				}
+			default:
+				return &fakeMDNS{}
+			}
+		},
+	}
+}
+
 func compareFuchsiaDevices(d1, d2 *fuchsiaDevice) bool {
 	return cmp.Equal(d1.addr, d2.addr) && cmp.Equal(d1.domain, d2.domain)
 }
@@ -84,34 +113,13 @@
 //// Tests for the `list` command.
 
 func TestListDevices(t *testing.T) {
-	// Because mdnsPorts have two ports specified, two MDNS objects are
-	// created. To emulate the case where only one port responds, create
-	// only one fake MDNS object with answers. The other one wouldn't
-	// respond at all. See the Start() method above.
-	mdnsCount := 0
 	cmd := listCmd{
-		devFinderCmd: devFinderCmd{
-			mdnsHandler: listMDNSHandler,
-			mdnsPorts:   "5353,5356",
-			timeout:     10,
-			newMDNSFunc: func() mdnsInterface {
-				mdnsCount++
-				switch mdnsCount {
-				case 1:
-					return &fakeMDNS{
-						answer: &fakeAnswer{
-							ip: "192.168.0.42",
-							domains: []string{
-								"some.domain",
-								"another.domain",
-							},
-						},
-					}
-				default:
-					return &fakeMDNS{}
-				}
-			},
-		},
+		devFinderCmd: newDevFinderCmd(
+			listMDNSHandler,
+			[]string{
+				"some.domain",
+				"another.domain",
+			}),
 	}
 
 	got, err := cmd.listDevices(context.Background())
@@ -135,34 +143,13 @@
 }
 
 func TestListDevices_domainFilter(t *testing.T) {
-	// Because mdnsPorts have two ports specified, two MDNS objects are
-	// created. To emulate the case where only one port responds, create
-	// only one fake MDNS object with answers. The other one wouldn't
-	// respond at all. See the Start() method above.
-	mdnsCount := 0
 	cmd := listCmd{
-		devFinderCmd: devFinderCmd{
-			mdnsHandler: listMDNSHandler,
-			mdnsPorts:   "5353,5356",
-			timeout:     10,
-			newMDNSFunc: func() mdnsInterface {
-				mdnsCount++
-				switch mdnsCount {
-				case 1:
-					return &fakeMDNS{
-						answer: &fakeAnswer{
-							ip: "192.168.0.42",
-							domains: []string{
-								"some.domain",
-								"another.domain",
-							},
-						},
-					}
-				default:
-					return &fakeMDNS{}
-				}
-			},
-		},
+		devFinderCmd: newDevFinderCmd(
+			listMDNSHandler,
+			[]string{
+				"some.domain",
+				"another.domain",
+			}),
 		domainFilter: "some",
 	}
 
@@ -185,34 +172,13 @@
 //// Tests for the `resolve` command.
 
 func TestResolveDevices(t *testing.T) {
-	// Because mdnsPorts have two ports specified, two MDNS objects are
-	// created. To emulate the case where only one port responds, create
-	// only one fake MDNS object with answers. The other one wouldn't
-	// respond at all. See the Start() method above.
-	mdnsCount := 0
 	cmd := resolveCmd{
-		devFinderCmd: devFinderCmd{
-			mdnsHandler: resolveMDNSHandler,
-			mdnsPorts:   "5353,5356",
-			timeout:     10,
-			newMDNSFunc: func() mdnsInterface {
-				mdnsCount++
-				switch mdnsCount {
-				case 1:
-					return &fakeMDNS{
-						answer: &fakeAnswer{
-							ip: "192.168.0.42",
-							domains: []string{
-								"some.domain.local",
-								"another.domain.local",
-							},
-						},
-					}
-				default:
-					return &fakeMDNS{}
-				}
-			},
-		},
+		devFinderCmd: newDevFinderCmd(
+			resolveMDNSHandler,
+			[]string{
+				"some.domain.local",
+				"another.domain.local",
+			}),
 	}
 
 	got, err := cmd.resolveDevices(context.Background(), "some.domain")