xds: Minor improvements to xDS server API and test. (#4026)

diff --git a/xds/internal/test/xds_server_integration_test.go b/xds/internal/test/xds_server_integration_test.go
index 7e1e7f6..3f218eb 100644
--- a/xds/internal/test/xds_server_integration_test.go
+++ b/xds/internal/test/xds_server_integration_test.go
@@ -41,14 +41,13 @@
 	testpb "google.golang.org/grpc/test/grpc_testing"
 	"google.golang.org/grpc/xds"
 	"google.golang.org/grpc/xds/internal/env"
+	"google.golang.org/grpc/xds/internal/testutils"
 	"google.golang.org/grpc/xds/internal/testutils/fakeserver"
 	"google.golang.org/grpc/xds/internal/version"
 )
 
 const (
 	defaultTestTimeout = 10 * time.Second
-	localAddress       = "localhost:9999"
-	listenerName       = "grpc/server?udpa.resource.listening_address=localhost:9999"
 )
 
 type s struct {
@@ -68,7 +67,7 @@
 					Value: func() []byte {
 						l := &v3listenerpb.Listener{
 							// This needs to match the name we are querying for.
-							Name: listenerName,
+							Name: name,
 							ApiListener: &v3listenerpb.ApiListener{
 								ApiListener: &anypb.Any{
 									TypeUrl: version.V2HTTPConnManagerURL,
@@ -151,8 +150,13 @@
 	testpb.RegisterTestServiceServer(server, &testService{})
 	defer server.Stop()
 
+	localAddr, err := testutils.AvailableHostPort()
+	if err != nil {
+		t.Fatalf("testutils.AvailableHostPort() failed: %v", err)
+	}
+
 	go func() {
-		opts := xds.ServeOptions{Network: "tcp", Address: localAddress}
+		opts := xds.ServeOptions{Address: localAddr}
 		if err := server.Serve(opts); err != nil {
 			t.Errorf("Serve(%+v) failed: %v", opts, err)
 		}
@@ -167,10 +171,10 @@
 		if _, err := fs.XDSRequestChan.Receive(ctx); err != nil {
 			t.Errorf("timeout when waiting for listener request: %v", err)
 		}
-		setupListenerResponse(fs.XDSResponseChan, listenerName)
+		setupListenerResponse(fs.XDSResponseChan, fmt.Sprintf("grpc/server?udpa.resource.listening_address=%s", localAddr))
 	}()
 
-	cc, err := grpc.DialContext(ctx, localAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	cc, err := grpc.DialContext(ctx, localAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	if err != nil {
 		t.Fatalf("failed to dial local test server: %v", err)
 	}
diff --git a/xds/internal/testutils/local_address.go b/xds/internal/testutils/local_address.go
new file mode 100644
index 0000000..5ada736
--- /dev/null
+++ b/xds/internal/testutils/local_address.go
@@ -0,0 +1,40 @@
+/*
+ *
+ * Copyright 2020 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package testutils
+
+import "net"
+
+// AvailableHostPort returns a local address to listen on. This will be of the
+// form "host:port", where the host will be a literal IP address, and port
+// must be a literal port number. If the host is a literal IPv6 address it
+// will be enclosed in square brackets, as in "[2001:db8::1]:80.
+//
+// This is useful for tests which need to call the Serve() method on
+// xds.GRPCServer which needs to be passed an IP:Port to listen on, where the IP
+// must be a literal IP and not localhost. This approach will work on support
+// one or both of IPv4 or IPv6.
+func AvailableHostPort() (string, error) {
+	l, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		return "", err
+	}
+	addr := l.Addr().String()
+	l.Close()
+	return addr, nil
+}
diff --git a/xds/server.go b/xds/server.go
index 0b85527..449c27a 100644
--- a/xds/server.go
+++ b/xds/server.go
@@ -22,6 +22,7 @@
 	"context"
 	"fmt"
 	"net"
+	"strconv"
 	"sync"
 
 	"google.golang.org/grpc"
@@ -81,24 +82,24 @@
 // Notice: This type is EXPERIMENTAL and may be changed or removed in a
 // later release.
 type ServeOptions struct {
-	// Network identifies the local network to listen on. The network must be
-	// "tcp", "tcp4", "tcp6".
-	Network string
 	// Address contains the local address to listen on. This should be of the
 	// form "host:port", where the host must be a literal IP address, and port
 	// must be a literal port number. If the host is a literal IPv6 address it
-	// must be enclosed in square brackets, as in "[2001:db8::1]:80. The host
-	// portion can be left unspecified.
+	// must be enclosed in square brackets, as in "[2001:db8::1]:80.
 	Address string
 }
 
 func (so *ServeOptions) validate() error {
-	if net := so.Network; net != "tcp" && net != "tcp4" && net != "tcp6" {
-		return fmt.Errorf("xds: unsupported network type %q for server listener", net)
-	}
-	if _, _, err := net.SplitHostPort(so.Address); err != nil {
+	addr, port, err := net.SplitHostPort(so.Address)
+	if err != nil {
 		return fmt.Errorf("xds: unsupported address %q for server listener", so.Address)
 	}
+	if net.ParseIP(addr) == nil {
+		return fmt.Errorf("xds: failed to parse %q as a valid literal IP address", addr)
+	}
+	if _, err := strconv.Atoi(port); err != nil {
+		return fmt.Errorf("%q is not a valid listener port", port)
+	}
 	return nil
 }
 
@@ -211,7 +212,7 @@
 // Returns a listenerWrapper, which implements the net.Listener interface, that
 // can be passed to grpcServer.Serve().
 func (s *GRPCServer) newListenerWrapper(opts ServeOptions) (*listenerWrapper, error) {
-	lis, err := net.Listen(opts.Network, opts.Address)
+	lis, err := net.Listen("tcp", opts.Address)
 	if err != nil {
 		return nil, fmt.Errorf("xds: failed to listen on %+v: %v", opts, err)
 	}
diff --git a/xds/server_test.go b/xds/server_test.go
index e953162..8769cf3 100644
--- a/xds/server_test.go
+++ b/xds/server_test.go
@@ -21,9 +21,9 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 	"net"
 	"reflect"
-	"strings"
 	"testing"
 	"time"
 
@@ -62,31 +62,32 @@
 			wantErr: true,
 		},
 		{
-			desc:    "unsupported network",
-			opts:    ServeOptions{Network: "foo"},
-			wantErr: true,
-		},
-		{
 			desc:    "bad address",
-			opts:    ServeOptions{Network: "tcp", Address: "I'm a bad IP address"},
+			opts:    ServeOptions{Address: "I'm a bad IP address"},
 			wantErr: true,
 		},
 		{
 			desc:    "no port",
-			opts:    ServeOptions{Network: "tcp", Address: "1.2.3.4"},
+			opts:    ServeOptions{Address: "1.2.3.4"},
 			wantErr: true,
 		},
 		{
-			desc: "empty hostname",
-			opts: ServeOptions{Network: "tcp", Address: ":1234"},
+			desc:    "empty hostname",
+			opts:    ServeOptions{Address: ":1234"},
+			wantErr: true,
+		},
+		{
+			desc:    "localhost",
+			opts:    ServeOptions{Address: "localhost:1234"},
+			wantErr: true,
 		},
 		{
 			desc: "ipv4",
-			opts: ServeOptions{Network: "tcp", Address: "1.2.3.4:1234"},
+			opts: ServeOptions{Address: "1.2.3.4:1234"},
 		},
 		{
 			desc: "ipv6",
-			opts: ServeOptions{Network: "tcp", Address: "[1:2::3:4]:1234"},
+			opts: ServeOptions{Address: "[1:2::3:4]:1234"},
 		},
 	}
 
@@ -228,10 +229,17 @@
 	server := NewGRPCServer()
 	defer server.Stop()
 
+	localAddr, err := xdstestutils.AvailableHostPort()
+	if err != nil {
+		t.Fatalf("testutils.AvailableHostPort() failed: %v", err)
+	}
+
 	// Call Serve() in a goroutine, and push on a channel when Serve returns.
 	serveDone := testutils.NewChannel()
 	go func() {
-		server.Serve(ServeOptions{Network: "tcp", Address: "localhost:0"})
+		if err := server.Serve(ServeOptions{Address: localAddr}); err != nil {
+			t.Error(err)
+		}
 		serveDone.Send(nil)
 	}()
 
@@ -249,9 +257,9 @@
 	if err != nil {
 		t.Fatalf("error when waiting for a ListenerWatch: %v", err)
 	}
-	wantPrefix := "grpc/server?udpa.resource.listening_address=localhost:"
-	if !strings.HasPrefix(name, wantPrefix) {
-		t.Fatalf("LDS watch registered for name %q, wantPrefix %q", name, wantPrefix)
+	wantName := fmt.Sprintf("grpc/server?udpa.resource.listening_address=%s", localAddr)
+	if name != wantName {
+		t.Fatalf("LDS watch registered for name %q, want %q", name, wantName)
 	}
 
 	// Push an error to the registered listener watch callback and make sure
@@ -282,10 +290,17 @@
 	// it after the LDS watch has been registered.
 	server := NewGRPCServer()
 
+	localAddr, err := xdstestutils.AvailableHostPort()
+	if err != nil {
+		t.Fatalf("testutils.AvailableHostPort() failed: %v", err)
+	}
+
 	// Call Serve() in a goroutine, and push on a channel when Serve returns.
 	serveDone := testutils.NewChannel()
 	go func() {
-		server.Serve(ServeOptions{Network: "tcp", Address: "localhost:0"})
+		if err := server.Serve(ServeOptions{Address: localAddr}); err != nil {
+			t.Error(err)
+		}
 		serveDone.Send(nil)
 	}()
 
@@ -304,10 +319,10 @@
 		server.Stop()
 		t.Fatalf("error when waiting for a ListenerWatch: %v", err)
 	}
-	wantPrefix := "grpc/server?udpa.resource.listening_address=localhost:"
-	if !strings.HasPrefix(name, wantPrefix) {
+	wantName := fmt.Sprintf("grpc/server?udpa.resource.listening_address=%s", localAddr)
+	if name != wantName {
 		server.Stop()
-		t.Fatalf("LDS watch registered for name %q, wantPrefix %q", name, wantPrefix)
+		t.Fatalf("LDS watch registered for name %q, wantPrefix %q", name, wantName)
 	}
 
 	// Call Stop() on the server before a listener update is received, and
@@ -334,9 +349,14 @@
 	server := NewGRPCServer()
 	defer server.Stop()
 
+	localAddr, err := xdstestutils.AvailableHostPort()
+	if err != nil {
+		t.Fatalf("testutils.AvailableHostPort() failed: %v", err)
+	}
+
 	serveDone := testutils.NewChannel()
 	go func() {
-		err := server.Serve(ServeOptions{Network: "tcp", Address: "localhost:0"})
+		err := server.Serve(ServeOptions{Address: localAddr})
 		serveDone.Send(err)
 	}()
 
@@ -373,9 +393,14 @@
 	server := NewGRPCServer()
 	defer server.Stop()
 
+	localAddr, err := xdstestutils.AvailableHostPort()
+	if err != nil {
+		t.Fatalf("testutils.AvailableHostPort() failed: %v", err)
+	}
+
 	serveDone := testutils.NewChannel()
 	go func() {
-		err := server.Serve(ServeOptions{Network: "tcp", Address: "localhost:0"})
+		err := server.Serve(ServeOptions{Address: localAddr})
 		serveDone.Send(err)
 	}()