net: fix slow network interface manipulations

This CL reduces unnecessary network facility lookups introduced
by recent changes below.

changeset: 15798:53a4da6a4f4a
net: return correct point-to-point interface address on linux

changeset: 15799:a81ef8e0cc05
net: set up IPv6 scoped addressing zone for network facilities

Also adds a test case for issue	4839.

Benchmark results on linux/amd64, virtual machine:
benchmark                                 old ns/op    new ns/op    delta
BenchmarkInterfaces-2                         80487        80382   -0.13%
BenchmarkInterfaceByIndex-2                   72013        71391   -0.86%
BenchmarkInterfaceByName-2                    79865        80101   +0.30%
BenchmarkInterfaceAddrs-2                     42071       829677  +1872.09%
BenchmarkInterfacesAndAddrs-2                 35016       607622  +1635.27%
BenchmarkInterfacesAndMulticastAddrs-2       169849       169082   -0.45%
old: 15797:9c3930413c1b, new: tip

Benchmark results on linux/amd64, virtual machine:
benchmark                                 old ns/op    new ns/op    delta
BenchmarkInterfaces-2                         80487        81459   +1.21%
BenchmarkInterfaceByIndex-2                   72013        71512   -0.70%
BenchmarkInterfaceByName-2                    79865        80567   +0.88%
BenchmarkInterfaceAddrs-2                     42071       120108  +185.49%
BenchmarkInterfacesAndAddrs-2                 35016        33259   -5.02%
BenchmarkInterfacesAndMulticastAddrs-2       169849        82391  -51.49%
old: 15797:9c3930413c1b, new: tip+CL7400055

Benchmark results on darwin/amd64:
benchmark                                 old ns/op    new ns/op    delta
BenchmarkInterfaces-2                         34402        34231   -0.50%
BenchmarkInterfaceByIndex-2                   13192        12956   -1.79%
BenchmarkInterfaceByName-2                    34791        34388   -1.16%
BenchmarkInterfaceAddrs-2                     36565        63906  +74.77%
BenchmarkInterfacesAndAddrs-2                 17497        31068  +77.56%
BenchmarkInterfacesAndMulticastAddrs-2        25276        66711  +163.93%
old: 15797:9c3930413c1b, new: tip

Benchmark results on darwin/amd64:
benchmark                                 old ns/op    new ns/op    delta
BenchmarkInterfaces-2                         34402        31854   -7.41%
BenchmarkInterfaceByIndex-2                   13192        12950   -1.83%
BenchmarkInterfaceByName-2                    34791        31926   -8.23%
BenchmarkInterfaceAddrs-2                     36565        42144  +15.26%
BenchmarkInterfacesAndAddrs-2                 17497        17329   -0.96%
BenchmarkInterfacesAndMulticastAddrs-2        25276        24870   -1.61%
old: 15797:9c3930413c1b, new: tip+CL7400055

Update #4234.
Fixes #4839 (again).
Fixes #4866.

R=golang-dev, fullung
CC=golang-dev
https://golang.org/cl/7400055
diff --git a/src/pkg/net/interface.go b/src/pkg/net/interface.go
index ee23570..0713e9c 100644
--- a/src/pkg/net/interface.go
+++ b/src/pkg/net/interface.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification
-
 package net
 
 import "errors"
@@ -66,7 +64,7 @@
 	if ifi == nil {
 		return nil, errInvalidInterface
 	}
-	return interfaceAddrTable(ifi.Index)
+	return interfaceAddrTable(ifi)
 }
 
 // MulticastAddrs returns multicast, joined group addresses for
@@ -75,7 +73,7 @@
 	if ifi == nil {
 		return nil, errInvalidInterface
 	}
-	return interfaceMulticastAddrTable(ifi.Index)
+	return interfaceMulticastAddrTable(ifi)
 }
 
 // Interfaces returns a list of the system's network interfaces.
@@ -86,7 +84,7 @@
 // InterfaceAddrs returns a list of the system's network interface
 // addresses.
 func InterfaceAddrs() ([]Addr, error) {
-	return interfaceAddrTable(0)
+	return interfaceAddrTable(nil)
 }
 
 // InterfaceByIndex returns the interface specified by index.
@@ -98,8 +96,14 @@
 	if err != nil {
 		return nil, err
 	}
+	return interfaceByIndex(ift, index)
+}
+
+func interfaceByIndex(ift []Interface, index int) (*Interface, error) {
 	for _, ifi := range ift {
-		return &ifi, nil
+		if index == ifi.Index {
+			return &ifi, nil
+		}
 	}
 	return nil, errNoSuchInterface
 }
diff --git a/src/pkg/net/interface_bsd.go b/src/pkg/net/interface_bsd.go
index 9e74845..f58065a 100644
--- a/src/pkg/net/interface_bsd.go
+++ b/src/pkg/net/interface_bsd.go
@@ -4,8 +4,6 @@
 
 // +build darwin freebsd netbsd openbsd
 
-// Network interface identification for BSD variants
-
 package net
 
 import (
@@ -26,7 +24,12 @@
 	if err != nil {
 		return nil, os.NewSyscallError("route message", err)
 	}
+	return parseInterfaceTable(ifindex, msgs)
+}
+
+func parseInterfaceTable(ifindex int, msgs []syscall.RoutingMessage) ([]Interface, error) {
 	var ift []Interface
+loop:
 	for _, m := range msgs {
 		switch m := m.(type) {
 		case *syscall.InterfaceMessage:
@@ -35,26 +38,28 @@
 				if err != nil {
 					return nil, err
 				}
-				ift = append(ift, ifi...)
+				ift = append(ift, *ifi)
+				if ifindex == int(m.Header.Index) {
+					break loop
+				}
 			}
 		}
 	}
 	return ift, nil
 }
 
-func newLink(m *syscall.InterfaceMessage) ([]Interface, error) {
+func newLink(m *syscall.InterfaceMessage) (*Interface, error) {
 	sas, err := syscall.ParseRoutingSockaddr(m)
 	if err != nil {
 		return nil, os.NewSyscallError("route sockaddr", err)
 	}
-	var ift []Interface
+	ifi := &Interface{Index: int(m.Header.Index), Flags: linkFlags(m.Header.Flags)}
 	for _, sa := range sas {
 		switch sa := sa.(type) {
 		case *syscall.SockaddrDatalink:
 			// NOTE: SockaddrDatalink.Data is minimum work area,
 			// can be larger.
 			m.Data = m.Data[unsafe.Offsetof(sa.Data):]
-			ifi := Interface{Index: int(m.Header.Index), Flags: linkFlags(m.Header.Flags)}
 			var name [syscall.IFNAMSIZ]byte
 			for i := 0; i < int(sa.Nlen); i++ {
 				name[i] = byte(m.Data[i])
@@ -66,10 +71,9 @@
 				addr[i] = byte(m.Data[int(sa.Nlen)+i])
 			}
 			ifi.HardwareAddr = addr[:sa.Alen]
-			ift = append(ift, ifi)
 		}
 	}
-	return ift, nil
+	return ifi, nil
 }
 
 func linkFlags(rawFlags int32) Flags {
@@ -92,11 +96,15 @@
 	return f
 }
 
-// If the ifindex is zero, interfaceAddrTable returns addresses
-// for all network interfaces.  Otherwise it returns addresses
-// for a specific interface.
-func interfaceAddrTable(ifindex int) ([]Addr, error) {
-	tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST, ifindex)
+// If the ifi is nil, interfaceAddrTable returns addresses for all
+// network interfaces.  Otherwise it returns addresses for a specific
+// interface.
+func interfaceAddrTable(ifi *Interface) ([]Addr, error) {
+	index := 0
+	if ifi != nil {
+		index = ifi.Index
+	}
+	tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST, index)
 	if err != nil {
 		return nil, os.NewSyscallError("route rib", err)
 	}
@@ -104,12 +112,26 @@
 	if err != nil {
 		return nil, os.NewSyscallError("route message", err)
 	}
+	var ift []Interface
+	if index == 0 {
+		ift, err = parseInterfaceTable(index, msgs)
+		if err != nil {
+			return nil, err
+		}
+	}
 	var ifat []Addr
 	for _, m := range msgs {
 		switch m := m.(type) {
 		case *syscall.InterfaceAddrMessage:
-			if ifindex == 0 || ifindex == int(m.Header.Index) {
-				ifa, err := newAddr(m)
+			if index == 0 || index == int(m.Header.Index) {
+				if index == 0 {
+					var err error
+					ifi, err = interfaceByIndex(ift, int(m.Header.Index))
+					if err != nil {
+						return nil, err
+					}
+				}
+				ifa, err := newAddr(ifi, m)
 				if err != nil {
 					return nil, err
 				}
@@ -122,7 +144,7 @@
 	return ifat, nil
 }
 
-func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error) {
+func newAddr(ifi *Interface, m *syscall.InterfaceAddrMessage) (Addr, error) {
 	sas, err := syscall.ParseRoutingSockaddr(m)
 	if err != nil {
 		return nil, os.NewSyscallError("route sockaddr", err)
@@ -149,7 +171,7 @@
 				// the interface index in the interface-local or link-
 				// local address as the kernel-internal form.
 				if ifa.IP.IsLinkLocalUnicast() {
-					ifa.Zone = zoneToString(int(ifa.IP[2]<<8 | ifa.IP[3]))
+					ifa.Zone = ifi.Name
 					ifa.IP[2], ifa.IP[3] = 0, 0
 				}
 			}
diff --git a/src/pkg/net/interface_bsd_test.go b/src/pkg/net/interface_bsd_test.go
new file mode 100644
index 0000000..c6e1bf7
--- /dev/null
+++ b/src/pkg/net/interface_bsd_test.go
@@ -0,0 +1,50 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin freebsd netbsd openbsd
+
+package net
+
+import (
+	"fmt"
+	"os/exec"
+)
+
+func (ti *testInterface) setBroadcast(suffix int) {
+	ti.name = fmt.Sprintf("vlan%d", suffix)
+	xname, err := exec.LookPath("ifconfig")
+	if err != nil {
+		xname = "ifconfig"
+	}
+	ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ifconfig", ti.name, "create"},
+	})
+	ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ifconfig", ti.name, "destroy"},
+	})
+}
+
+func (ti *testInterface) setPointToPoint(suffix int, local, remote string) {
+	ti.name = fmt.Sprintf("gif%d", suffix)
+	ti.local = local
+	ti.remote = remote
+	xname, err := exec.LookPath("ifconfig")
+	if err != nil {
+		xname = "ifconfig"
+	}
+	ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ifconfig", ti.name, "create"},
+	})
+	ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ifconfig", ti.name, "inet", ti.local, ti.remote},
+	})
+	ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ifconfig", ti.name, "destroy"},
+	})
+}
diff --git a/src/pkg/net/interface_darwin.go b/src/pkg/net/interface_darwin.go
index edf4d74..83e483b 100644
--- a/src/pkg/net/interface_darwin.go
+++ b/src/pkg/net/interface_darwin.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification for Darwin
-
 package net
 
 import (
@@ -11,11 +9,10 @@
 	"syscall"
 )
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
-	tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST2, ifindex)
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
+	tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST2, ifi.Index)
 	if err != nil {
 		return nil, os.NewSyscallError("route rib", err)
 	}
@@ -27,8 +24,8 @@
 	for _, m := range msgs {
 		switch m := m.(type) {
 		case *syscall.InterfaceMulticastAddrMessage:
-			if ifindex == 0 || ifindex == int(m.Header.Index) {
-				ifma, err := newMulticastAddr(m)
+			if ifi.Index == int(m.Header.Index) {
+				ifma, err := newMulticastAddr(ifi, m)
 				if err != nil {
 					return nil, err
 				}
@@ -39,7 +36,7 @@
 	return ifmat, nil
 }
 
-func newMulticastAddr(m *syscall.InterfaceMulticastAddrMessage) ([]Addr, error) {
+func newMulticastAddr(ifi *Interface, m *syscall.InterfaceMulticastAddrMessage) ([]Addr, error) {
 	sas, err := syscall.ParseRoutingSockaddr(m)
 	if err != nil {
 		return nil, os.NewSyscallError("route sockaddr", err)
@@ -57,7 +54,7 @@
 			// the interface index in the interface-local or link-
 			// local address as the kernel-internal form.
 			if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() {
-				ifma.Zone = zoneToString(int(ifma.IP[2]<<8 | ifma.IP[3]))
+				ifma.Zone = ifi.Name
 				ifma.IP[2], ifma.IP[3] = 0, 0
 			}
 			ifmat = append(ifmat, ifma.toAddr())
diff --git a/src/pkg/net/interface_freebsd.go b/src/pkg/net/interface_freebsd.go
index af3be4d..1bf5ae7 100644
--- a/src/pkg/net/interface_freebsd.go
+++ b/src/pkg/net/interface_freebsd.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification for FreeBSD
-
 package net
 
 import (
@@ -11,11 +9,10 @@
 	"syscall"
 )
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
-	tab, err := syscall.RouteRIB(syscall.NET_RT_IFMALIST, ifindex)
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
+	tab, err := syscall.RouteRIB(syscall.NET_RT_IFMALIST, ifi.Index)
 	if err != nil {
 		return nil, os.NewSyscallError("route rib", err)
 	}
@@ -27,8 +24,8 @@
 	for _, m := range msgs {
 		switch m := m.(type) {
 		case *syscall.InterfaceMulticastAddrMessage:
-			if ifindex == 0 || ifindex == int(m.Header.Index) {
-				ifma, err := newMulticastAddr(m)
+			if ifi.Index == int(m.Header.Index) {
+				ifma, err := newMulticastAddr(ifi, m)
 				if err != nil {
 					return nil, err
 				}
@@ -39,7 +36,7 @@
 	return ifmat, nil
 }
 
-func newMulticastAddr(m *syscall.InterfaceMulticastAddrMessage) ([]Addr, error) {
+func newMulticastAddr(ifi *Interface, m *syscall.InterfaceMulticastAddrMessage) ([]Addr, error) {
 	sas, err := syscall.ParseRoutingSockaddr(m)
 	if err != nil {
 		return nil, os.NewSyscallError("route sockaddr", err)
@@ -57,7 +54,7 @@
 			// the interface index in the interface-local or link-
 			// local address as the kernel-internal form.
 			if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() {
-				ifma.Zone = zoneToString(int(ifma.IP[2]<<8 | ifma.IP[3]))
+				ifma.Zone = ifi.Name
 				ifma.IP[2], ifma.IP[3] = 0, 0
 			}
 			ifmat = append(ifmat, ifma.toAddr())
diff --git a/src/pkg/net/interface_linux.go b/src/pkg/net/interface_linux.go
index 5c7590b..e66daef 100644
--- a/src/pkg/net/interface_linux.go
+++ b/src/pkg/net/interface_linux.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification for Linux
-
 package net
 
 import (
@@ -37,16 +35,18 @@
 				if err != nil {
 					return nil, os.NewSyscallError("netlink routeattr", err)
 				}
-				ifi := newLink(ifim, attrs)
-				ift = append(ift, ifi)
+				ift = append(ift, *newLink(ifim, attrs))
+				if ifindex == int(ifim.Index) {
+					break loop
+				}
 			}
 		}
 	}
 	return ift, nil
 }
 
-func newLink(ifim *syscall.IfInfomsg, attrs []syscall.NetlinkRouteAttr) Interface {
-	ifi := Interface{Index: int(ifim.Index), Flags: linkFlags(ifim.Flags)}
+func newLink(ifim *syscall.IfInfomsg, attrs []syscall.NetlinkRouteAttr) *Interface {
+	ifi := &Interface{Index: int(ifim.Index), Flags: linkFlags(ifim.Flags)}
 	for _, a := range attrs {
 		switch a.Attr.Type {
 		case syscall.IFLA_ADDRESS:
@@ -88,10 +88,10 @@
 	return f
 }
 
-// If the ifindex is zero, interfaceAddrTable returns addresses
-// for all network interfaces.  Otherwise it returns addresses
-// for a specific interface.
-func interfaceAddrTable(ifindex int) ([]Addr, error) {
+// If the ifi is nil, interfaceAddrTable returns addresses for all
+// network interfaces.  Otherwise it returns addresses for a specific
+// interface.
+func interfaceAddrTable(ifi *Interface) ([]Addr, error) {
 	tab, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC)
 	if err != nil {
 		return nil, os.NewSyscallError("netlink rib", err)
@@ -100,14 +100,22 @@
 	if err != nil {
 		return nil, os.NewSyscallError("netlink message", err)
 	}
-	ifat, err := addrTable(msgs, ifindex)
+	var ift []Interface
+	if ifi == nil {
+		var err error
+		ift, err = interfaceTable(0)
+		if err != nil {
+			return nil, err
+		}
+	}
+	ifat, err := addrTable(ift, ifi, msgs)
 	if err != nil {
 		return nil, err
 	}
 	return ifat, nil
 }
 
-func addrTable(msgs []syscall.NetlinkMessage, ifindex int) ([]Addr, error) {
+func addrTable(ift []Interface, ifi *Interface, msgs []syscall.NetlinkMessage) ([]Addr, error) {
 	var ifat []Addr
 loop:
 	for _, m := range msgs {
@@ -116,58 +124,51 @@
 			break loop
 		case syscall.RTM_NEWADDR:
 			ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
-			ifi, err := InterfaceByIndex(int(ifam.Index))
-			if err != nil {
-				return nil, err
-			}
-			if ifindex == 0 || ifindex == int(ifam.Index) {
+			if len(ift) != 0 || ifi.Index == int(ifam.Index) {
+				if len(ift) != 0 {
+					var err error
+					ifi, err = interfaceByIndex(ift, int(ifam.Index))
+					if err != nil {
+						return nil, err
+					}
+				}
 				attrs, err := syscall.ParseNetlinkRouteAttr(&m)
 				if err != nil {
 					return nil, os.NewSyscallError("netlink routeattr", err)
 				}
-				ifat = append(ifat, newAddr(attrs, ifi, ifam))
+				ifa := newAddr(ifi, ifam, attrs)
+				if ifa != nil {
+					ifat = append(ifat, ifa)
+				}
 			}
 		}
 	}
 	return ifat, nil
 }
 
-func newAddr(attrs []syscall.NetlinkRouteAttr, ifi *Interface, ifam *syscall.IfAddrmsg) Addr {
-	ifa := &IPNet{}
+func newAddr(ifi *Interface, ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) Addr {
 	for _, a := range attrs {
 		if ifi.Flags&FlagPointToPoint != 0 && a.Attr.Type == syscall.IFA_LOCAL ||
 			ifi.Flags&FlagPointToPoint == 0 && a.Attr.Type == syscall.IFA_ADDRESS {
 			switch ifam.Family {
 			case syscall.AF_INET:
-				ifa.IP = IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3])
-				ifa.Mask = CIDRMask(int(ifam.Prefixlen), 8*IPv4len)
+				return &IPNet{IP: IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: CIDRMask(int(ifam.Prefixlen), 8*IPv4len)}
 			case syscall.AF_INET6:
-				ifa.IP = make(IP, IPv6len)
+				ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(ifam.Prefixlen), 8*IPv6len)}
 				copy(ifa.IP, a.Value[:])
-				ifa.Mask = CIDRMask(int(ifam.Prefixlen), 8*IPv6len)
 				if ifam.Scope == syscall.RT_SCOPE_HOST || ifam.Scope == syscall.RT_SCOPE_LINK {
-					ifa.Zone = zoneToString(int(ifam.Index))
+					ifa.Zone = ifi.Name
 				}
+				return ifa
 			}
 		}
 	}
-	return ifa
+	return nil
 }
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
-	var (
-		err error
-		ifi *Interface
-	)
-	if ifindex > 0 {
-		ifi, err = InterfaceByIndex(ifindex)
-		if err != nil {
-			return nil, err
-		}
-	}
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
 	ifmat4 := parseProcNetIGMP("/proc/net/igmp", ifi)
 	ifmat6 := parseProcNetIGMP6("/proc/net/igmp6", ifi)
 	return append(ifmat4, ifmat6...), nil
diff --git a/src/pkg/net/interface_linux_test.go b/src/pkg/net/interface_linux_test.go
index f14d1fe..50d3dc6 100644
--- a/src/pkg/net/interface_linux_test.go
+++ b/src/pkg/net/interface_linux_test.go
@@ -4,7 +4,53 @@
 
 package net
 
-import "testing"
+import (
+	"fmt"
+	"os/exec"
+	"testing"
+)
+
+func (ti *testInterface) setBroadcast(suffix int) {
+	ti.name = fmt.Sprintf("gotest%d", suffix)
+	xname, err := exec.LookPath("ip")
+	if err != nil {
+		xname = "ip"
+	}
+	ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ip", "link", "add", ti.name, "type", "dummy"},
+	})
+	ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ip", "link", "delete", ti.name, "type", "dummy"},
+	})
+}
+
+func (ti *testInterface) setPointToPoint(suffix int, local, remote string) {
+	ti.name = fmt.Sprintf("gotest%d", suffix)
+	ti.local = local
+	ti.remote = remote
+	xname, err := exec.LookPath("ip")
+	if err != nil {
+		xname = "ip"
+	}
+	ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ip", "tunnel", "add", ti.name, "mode", "gre", "local", local, "remote", remote},
+	})
+	ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ip", "tunnel", "del", ti.name, "mode", "gre", "local", local, "remote", remote},
+	})
+	xname, err = exec.LookPath("ifconfig")
+	if err != nil {
+		xname = "ifconfig"
+	}
+	ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+		Path: xname,
+		Args: []string{"ifconfig", ti.name, "inet", local, "dstaddr", remote},
+	})
+}
 
 const (
 	numOfTestIPv4MCAddrs = 14
diff --git a/src/pkg/net/interface_netbsd.go b/src/pkg/net/interface_netbsd.go
index 691d311..c9ce5a7 100644
--- a/src/pkg/net/interface_netbsd.go
+++ b/src/pkg/net/interface_netbsd.go
@@ -2,14 +2,11 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification for NetBSD
-
 package net
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
 	// TODO(mikio): Implement this like other platforms.
 	return nil, nil
 }
diff --git a/src/pkg/net/interface_openbsd.go b/src/pkg/net/interface_openbsd.go
index 3188871..c9ce5a7 100644
--- a/src/pkg/net/interface_openbsd.go
+++ b/src/pkg/net/interface_openbsd.go
@@ -2,14 +2,11 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification for OpenBSD
-
 package net
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
 	// TODO(mikio): Implement this like other platforms.
 	return nil, nil
 }
diff --git a/src/pkg/net/interface_stub.go b/src/pkg/net/interface_stub.go
index d4d7ce9..a4eb731 100644
--- a/src/pkg/net/interface_stub.go
+++ b/src/pkg/net/interface_stub.go
@@ -4,8 +4,6 @@
 
 // +build plan9
 
-// Network interface identification
-
 package net
 
 // If the ifindex is zero, interfaceTable returns mappings of all
@@ -15,16 +13,15 @@
 	return nil, nil
 }
 
-// If the ifindex is zero, interfaceAddrTable returns addresses
-// for all network interfaces.  Otherwise it returns addresses
-// for a specific interface.
-func interfaceAddrTable(ifindex int) ([]Addr, error) {
+// If the ifi is nil, interfaceAddrTable returns addresses for all
+// network interfaces.  Otherwise it returns addresses for a specific
+// interface.
+func interfaceAddrTable(ifi *Interface) ([]Addr, error) {
 	return nil, nil
 }
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
 	return nil, nil
 }
diff --git a/src/pkg/net/interface_unix_test.go b/src/pkg/net/interface_unix_test.go
new file mode 100644
index 0000000..2040d16
--- /dev/null
+++ b/src/pkg/net/interface_unix_test.go
@@ -0,0 +1,141 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package net
+
+import (
+	"os"
+	"os/exec"
+	"runtime"
+	"testing"
+	"time"
+)
+
+type testInterface struct {
+	name         string
+	local        string
+	remote       string
+	setupCmds    []*exec.Cmd
+	teardownCmds []*exec.Cmd
+}
+
+func (ti *testInterface) setup() error {
+	for _, cmd := range ti.setupCmds {
+		if err := cmd.Run(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (ti *testInterface) teardown() error {
+	for _, cmd := range ti.teardownCmds {
+		if err := cmd.Run(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func TestPointToPointInterface(t *testing.T) {
+	switch runtime.GOOS {
+	case "darwin":
+		t.Skipf("skipping read test on %q", runtime.GOOS)
+	}
+	if os.Getuid() != 0 {
+		t.Skip("skipping test; must be root")
+	}
+
+	local, remote := "169.254.0.1", "169.254.0.254"
+	ip := ParseIP(remote)
+	for i := 0; i < 3; i++ {
+		ti := &testInterface{}
+		ti.setPointToPoint(5963+i, local, remote)
+		if err := ti.setup(); err != nil {
+			t.Fatalf("testInterface.setup failed: %v", err)
+		} else {
+			time.Sleep(3 * time.Millisecond)
+		}
+		ift, err := Interfaces()
+		if err != nil {
+			ti.teardown()
+			t.Fatalf("Interfaces failed: %v", err)
+		}
+		for _, ifi := range ift {
+			if ti.name == ifi.Name {
+				ifat, err := ifi.Addrs()
+				if err != nil {
+					ti.teardown()
+					t.Fatalf("Interface.Addrs failed: %v", err)
+				}
+				for _, ifa := range ifat {
+					if ip.Equal(ifa.(*IPNet).IP) {
+						ti.teardown()
+						t.Fatalf("got %v; want %v", ip, local)
+					}
+				}
+			}
+		}
+		if err := ti.teardown(); err != nil {
+			t.Fatalf("testInterface.teardown failed: %v", err)
+		} else {
+			time.Sleep(3 * time.Millisecond)
+		}
+	}
+}
+
+func TestInterfaceArrivalAndDeparture(t *testing.T) {
+	if os.Getuid() != 0 {
+		t.Skip("skipping test; must be root")
+	}
+
+	for i := 0; i < 3; i++ {
+		ift1, err := Interfaces()
+		if err != nil {
+			t.Fatalf("Interfaces failed: %v", err)
+		}
+		ti := &testInterface{}
+		ti.setBroadcast(5682 + i)
+		if err := ti.setup(); err != nil {
+			t.Fatalf("testInterface.setup failed: %v", err)
+		} else {
+			time.Sleep(3 * time.Millisecond)
+		}
+		ift2, err := Interfaces()
+		if err != nil {
+			ti.teardown()
+			t.Fatalf("Interfaces failed: %v", err)
+		}
+		if len(ift2) <= len(ift1) {
+			for _, ifi := range ift1 {
+				t.Logf("before: %v", ifi)
+			}
+			for _, ifi := range ift2 {
+				t.Logf("after: %v", ifi)
+			}
+			ti.teardown()
+			t.Fatalf("got %v; want gt %v", len(ift2), len(ift1))
+		}
+		if err := ti.teardown(); err != nil {
+			t.Fatalf("testInterface.teardown failed: %v", err)
+		} else {
+			time.Sleep(3 * time.Millisecond)
+		}
+		ift3, err := Interfaces()
+		if err != nil {
+			t.Fatalf("Interfaces failed: %v", err)
+		}
+		if len(ift3) >= len(ift2) {
+			for _, ifi := range ift2 {
+				t.Logf("before: %v", ifi)
+			}
+			for _, ifi := range ift3 {
+				t.Logf("after: %v", ifi)
+			}
+			t.Fatalf("got %v; want lt %v", len(ift3), len(ift2))
+		}
+	}
+}
diff --git a/src/pkg/net/interface_windows.go b/src/pkg/net/interface_windows.go
index aa57fab..0759dc2 100644
--- a/src/pkg/net/interface_windows.go
+++ b/src/pkg/net/interface_windows.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Network interface identification for Windows
-
 package net
 
 import (
@@ -129,10 +127,10 @@
 	return ift, nil
 }
 
-// If the ifindex is zero, interfaceAddrTable returns addresses
-// for all network interfaces.  Otherwise it returns addresses
-// for a specific interface.
-func interfaceAddrTable(ifindex int) ([]Addr, error) {
+// If the ifi is nil, interfaceAddrTable returns addresses for all
+// network interfaces.  Otherwise it returns addresses for a specific
+// interface.
+func interfaceAddrTable(ifi *Interface) ([]Addr, error) {
 	ai, err := getAdapterList()
 	if err != nil {
 		return nil, err
@@ -141,11 +139,10 @@
 	var ifat []Addr
 	for ; ai != nil; ai = ai.Next {
 		index := ai.Index
-		if ifindex == 0 || ifindex == int(index) {
+		if ifi == nil || ifi.Index == int(index) {
 			ipl := &ai.IpAddressList
 			for ; ipl != nil; ipl = ipl.Next {
-				ifa := IPAddr{}
-				ifa.IP = parseIPv4(bytePtrToString(&ipl.IpAddress.String[0]))
+				ifa := IPAddr{IP: parseIPv4(bytePtrToString(&ipl.IpAddress.String[0]))}
 				ifat = append(ifat, ifa.toAddr())
 			}
 		}
@@ -153,10 +150,9 @@
 	return ifat, nil
 }
 
-// If the ifindex is zero, interfaceMulticastAddrTable returns
-// addresses for all network interfaces.  Otherwise it returns
-// addresses for a specific interface.
-func interfaceMulticastAddrTable(ifindex int) ([]Addr, error) {
+// interfaceMulticastAddrTable returns addresses for a specific
+// interface.
+func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
 	// TODO(mikio): Implement this like other platforms.
 	return nil, nil
 }