[mdns] Simplify SO_REUSEPORT usage for UDP sockets.

This CL simplifies the function used to create a UDP socket
with both SO_REUSEADDR and SO_REUSEPORT before binding it.

Using the net.ListenConfig type introduced in Go 1.11 allows
one to specify a control function to set socket options before
it is actually bound to an interface/address.

BUG: DX-1664
Change-Id: I6c520342652f1eb763f87383c745eca89cbb94f3
diff --git a/mdns/mdns.go b/mdns/mdns.go
index 807635c..d33c15e 100644
--- a/mdns/mdns.go
+++ b/mdns/mdns.go
@@ -11,12 +11,12 @@
 	"fmt"
 	"io"
 	"net"
-	"os"
 	"strings"
 	"syscall"
 	"unicode/utf8"
 
 	"golang.org/x/net/ipv4"
+	"golang.org/x/sys/unix"
 )
 
 // DefaultPort is the mDNS port required of the spec, though this library is port-agnostic.
@@ -438,7 +438,7 @@
 	Send(buf bytes.Buffer) error
 	Listen(port int) error
 	JoinGroup(iface net.Interface) error
-	ConnectTo(port int, ip net.IP) error
+	ConnectTo(port int, ip net.IP, iface *net.Interface) error
 	ReadFrom(buf []byte) (size int, iface *net.Interface, src net.Addr, err error)
 }
 
@@ -515,11 +515,12 @@
 	return nil
 }
 
-func (c *mDNSConn4) ConnectTo(port int, ip net.IP) error {
-	if len(ip) != 4 {
+func (c *mDNSConn4) ConnectTo(port int, ip net.IP, iface *net.Interface) error {
+	ip4 := ip.To4()
+	if ip4 == nil {
 		return fmt.Errorf("Not a valid IPv4 address: %v", ip)
 	}
-	conn, err := makeUnixIpv4Socket(port, ip.To4())
+	conn, err := makeUdpSocketWithReusePort(port, ip4, iface)
 	if err != nil {
 		return err
 	}
@@ -608,36 +609,37 @@
 	return m.conn4.Send(buf)
 }
 
-func makeUnixIpv4Socket(port int, ip net.IP) (net.PacketConn, error) {
-	fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
-	if err != nil {
-		return nil, fmt.Errorf("creating socket: %v", err)
+func makeUdpSocketWithReusePort(port int, ip net.IP, iface *net.Interface) (net.PacketConn, error) {
+	is_ipv6 := ip.To4() == nil
+	network := "udp4"
+	var zone string
+	if is_ipv6 {
+		network = "udp6"
+		zone = iface.Name
 	}
-	// SO_REUSEADDR and SO_REUSEPORT allows binding to the same port multiple
-	// times which is necessary in the case when there are multiple instances.
-	if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, 0x2 /*SO_REUSEADDR*/, 1); err != nil {
-		syscall.Close(fd)
-		return nil, fmt.Errorf("setting reuse addr: %v", err)
+	address := (&net.UDPAddr{IP: ip, Port: port, Zone: zone}).String()
+
+	// A net.ListenConfig control function to set both SO_REUSEADDR and SO_REUSEPORT
+	// on a given socket fd. Only works on Unix-based systems, not Windows. For portability
+	// an alternative might be to use something like the go-reuseport library.
+	control := func(network, address string, c syscall.RawConn) error {
+		var err error
+		c.Control(func(fd uintptr) {
+			err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
+			if err != nil {
+				return
+			}
+
+			err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
+			if err != nil {
+				return
+			}
+		})
+		return err
 	}
-	if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, 0xf /*SO_REUSEPORT*/, 1); err != nil {
-		syscall.Close(fd)
-		return nil, fmt.Errorf("setting reuse port: %v", err)
-	}
-	// Bind the socket to the specified port.
-	var ipArray [4]byte
-	copy(ipArray[:], []byte(ip))
-	if err := syscall.Bind(fd, &syscall.SockaddrInet4{Addr: ipArray, Port: port}); err != nil {
-		syscall.Close(fd)
-		return nil, fmt.Errorf("binding to %v: %v", ip, err)
-	}
-	// Now make a socket.
-	f := os.NewFile(uintptr(fd), "")
-	conn, err := net.FilePacketConn(f)
-	f.Close()
-	if err != nil {
-		return nil, fmt.Errorf("creating packet conn: %v", err)
-	}
-	return conn, nil
+
+	listenConfig := net.ListenConfig{Control: control}
+	return listenConfig.ListenPacket(context.Background(), network, address)
 }
 
 // Start causes m to start listening for MDNS packets on all interfaces on
@@ -677,7 +679,7 @@
 			if ip == nil || ip.To4() == nil {
 				continue
 			}
-			err := m.conn4.ConnectTo(port, ip.To4())
+			err := m.conn4.ConnectTo(port, ip.To4(), &iface)
 			if err != nil {
 				return fmt.Errorf("creating socket for %v via %v: %v", iface, ip, err)
 			}