veyron/lib/netstate,netconfig: move netconfig out of the runtime and create netstate.

netstate is a set of routines for determining current networking state
and making policy based selections from the set of available addresses.

Change-Id: Iba2e20f046ae6018bd14f4fcb66f3d1c17af1518
diff --git a/examples/netconfigwatcher/main.go b/examples/netconfigwatcher/main.go
index b935883..5716d5d 100644
--- a/examples/netconfigwatcher/main.go
+++ b/examples/netconfigwatcher/main.go
@@ -4,7 +4,7 @@
 	"fmt"
 	"log"
 
-	"veyron/runtimes/google/lib/netconfig"
+	"veyron/lib/netconfig"
 )
 
 func main() {
diff --git a/runtimes/google/lib/netconfig/example_test.go b/lib/netconfig/example_test.go
similarity index 88%
rename from runtimes/google/lib/netconfig/example_test.go
rename to lib/netconfig/example_test.go
index 0acef69..56b0cbd 100644
--- a/runtimes/google/lib/netconfig/example_test.go
+++ b/lib/netconfig/example_test.go
@@ -4,7 +4,7 @@
 	"fmt"
 	"log"
 
-	"veyron/runtimes/google/lib/netconfig"
+	"veyron/lib/netconfig"
 )
 
 func ExampleNetConfigWatcher() {
diff --git a/runtimes/google/lib/netconfig/ipaux.go b/lib/netconfig/ipaux.go
similarity index 100%
rename from runtimes/google/lib/netconfig/ipaux.go
rename to lib/netconfig/ipaux.go
diff --git a/runtimes/google/lib/netconfig/ipaux_bsd.go b/lib/netconfig/ipaux_bsd.go
similarity index 100%
rename from runtimes/google/lib/netconfig/ipaux_bsd.go
rename to lib/netconfig/ipaux_bsd.go
diff --git a/runtimes/google/lib/netconfig/ipaux_linux.go b/lib/netconfig/ipaux_linux.go
similarity index 100%
rename from runtimes/google/lib/netconfig/ipaux_linux.go
rename to lib/netconfig/ipaux_linux.go
diff --git a/runtimes/google/lib/netconfig/model.go b/lib/netconfig/model.go
similarity index 100%
rename from runtimes/google/lib/netconfig/model.go
rename to lib/netconfig/model.go
diff --git a/runtimes/google/lib/netconfig/isgloballyroutable.go b/lib/netstate/isgloballyroutable.go
similarity index 90%
rename from runtimes/google/lib/netconfig/isgloballyroutable.go
rename to lib/netstate/isgloballyroutable.go
index 4fa5916..ee6bae5 100644
--- a/runtimes/google/lib/netconfig/isgloballyroutable.go
+++ b/lib/netstate/isgloballyroutable.go
@@ -1,4 +1,4 @@
-package netconfig
+package netstate
 
 import (
 	"net"
@@ -11,7 +11,7 @@
 }
 
 // IsGloballyRoutable returns true if the argument is a globally routable IP address.
-func IsGloballyRoutable(ip net.IP) bool {
+func IsGloballyRoutableIP(ip net.IP) bool {
 	if !ip.IsGlobalUnicast() {
 		return false
 	}
diff --git a/runtimes/google/lib/netconfig/isgloballyroutable_test.go b/lib/netstate/isgloballyroutable_test.go
similarity index 88%
rename from runtimes/google/lib/netconfig/isgloballyroutable_test.go
rename to lib/netstate/isgloballyroutable_test.go
index 3cce082..dd0885a 100644
--- a/runtimes/google/lib/netconfig/isgloballyroutable_test.go
+++ b/lib/netstate/isgloballyroutable_test.go
@@ -1,4 +1,4 @@
-package netconfig
+package netstate
 
 import (
 	"net"
@@ -24,7 +24,7 @@
 	}
 	for _, test := range tests {
 		ip := net.ParseIP(test.ip)
-		if got := IsGloballyRoutable(ip); got != test.want {
+		if got := IsGloballyRoutableIP(ip); got != test.want {
 			t.Fatalf("%s: want %v got %v", test.ip, test.want, got)
 		}
 	}
diff --git a/lib/netstate/netstate.go b/lib/netstate/netstate.go
new file mode 100644
index 0000000..e35e595
--- /dev/null
+++ b/lib/netstate/netstate.go
@@ -0,0 +1,270 @@
+// Package netstate provides routines to obtain the available set of
+// of network addresess, for determining changes to those addresses and for
+// selecting from amongst them according to some set of policies that are
+// implemented by applying simple predicates (functions with names of the form
+// Is<condition> to filter or find the first matching address from a list
+// of addresses. The intent is to make it easy to create policies that do
+// things like 'find the first IPv4 unicast address that is globally routable,
+// failing that use a private IPv4 address, and failing that, an IPv6 address'.
+//
+// A typical usage would be:
+//
+//   state, _ := netstate.GetAccessibleIPs()
+//   first := netstate.First(netstate.IsPublicIPv4)
+//   // first will contain the first public IPv4 address or be nil.
+//
+// The example policy described above would be implemented using a
+// series of calls to First with appropriate predicates.
+//
+// Although most commercial networking hardware supports IPv6, some consumer
+// devices and more importantly many ISPs do not, so routines are provided
+// to allow developers to easily distinguish between the two and to use
+// whichever is appropriate for their product/situation.
+//
+// The term 'accessible' is used to refer to any non-loopback IP address.
+// The term 'public' is used to refer to any globally routable IP address.
+//
+// All IPv6 addresses are intended to be 'public', but any starting with
+// fc00::/7 are (RFC4193) are reserved for private use, but the go
+// net libraries do not appear to recognise this. Similarly fe80::/10
+// (RFC 4291) are reserved for 'site-local' usage, but again this is not
+// implemented in the go libraries. Any developer who needs to distinguish
+// these cases will need to write their own routines to test for them.
+//
+// When using the go net package it is important to remember that IPv6
+// addresses subsume IPv4 and hence in many cases the same internal
+// representation is used for both, thus testing for the length of the IP
+// address is unreliable. The reliable test is to use the net.To4() which
+// will return a non-nil result if can be used as an IPv4 one. Any address
+// can be used as an IPv6 and hence the only reliable way to test for an IPv6
+// address that is not an IPv4 one also is for the To4 call to return nil for
+// it.
+package netstate
+
+import (
+	"fmt"
+	"net"
+	"strings"
+)
+
+type AddrList []net.Addr
+
+func (al AddrList) String() string {
+	r := ""
+	for _, v := range al {
+		r += fmt.Sprintf("(%s) ", v)
+	}
+	return strings.TrimRight(r, " ")
+}
+
+// GetAll gets all of the available addresses on the device, including
+// loopback addresses.
+func GetAll() (AddrList, error) {
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	var all AddrList
+	for _, ifc := range interfaces {
+		addrs, err := ifc.Addrs()
+		if err != nil {
+			continue
+		}
+		all = append(all, addrs...)
+	}
+	return all, nil
+}
+
+// GetAccessibleIPs returns all of the IP addresses on the device that are
+// accessible to other devices - i.e. excluding loopback etc.
+func GetAccessibleIPs() (AddrList, error) {
+	all, err := GetAll()
+	if err != nil {
+		return nil, err
+	}
+	return all.Filter(IsAccessibleIP), nil
+}
+
+type Predicate func(a net.Addr) bool
+
+// Filter returns all of the addresses for which the predicate
+// function is true.
+func (al AddrList) Filter(predicate Predicate) AddrList {
+	r := AddrList{}
+	for _, a := range al {
+		if predicate(a) {
+			r = append(r, a)
+		}
+	}
+	return r
+}
+
+// Filter returns the first address for which the predicate function is true.
+func (al AddrList) First(predicate Predicate) net.Addr {
+	for _, a := range al {
+		if predicate(a) {
+			return a
+		}
+	}
+	return nil
+}
+
+func IsIPNetwork(n string) bool {
+	switch n {
+	case "ip+net", "tcp", "tcp4", "tcp6", "udp":
+		return true
+	default:
+		return false
+
+	}
+}
+
+// AsIP returns its argument as a net.IP if that's possible.
+func AsIP(a net.Addr) net.IP {
+	ipn, ok := a.(*net.IPNet)
+	if ok {
+		return ipn.IP
+	}
+	ipa, ok := a.(*net.IPAddr)
+	if ok {
+		return ipa.IP
+	}
+	switch a.Network() {
+	default:
+		return nil
+	case "ip+net", "tcp", "tcp4", "tcp6", "udp":
+	}
+	return net.ParseIP(a.String())
+}
+
+// IsUnspecified returns true if its argument is an unspecified IP address
+func IsUnspecifiedIP(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil {
+		return ip.IsUnspecified()
+	}
+	return false
+}
+
+// IsLoopback returns true if its argument is a loopback IP address
+func IsLoopbackIP(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
+		return ip.IsLoopback()
+	}
+	return false
+}
+
+// IsAccessible returns true if its argument is an accessible (non-loopback)
+// IP address.
+func IsAccessibleIP(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
+		return !ip.IsLoopback()
+	}
+	return false
+}
+
+type ma struct {
+	n, a string
+}
+
+func (a *ma) Network() string {
+	return a.n
+}
+
+func (a *ma) String() string {
+	return a.a
+}
+
+// AsAddr returns its argument as a net.Addr.
+func AsAddr(network string, a net.IP) net.Addr {
+	return &ma{n: network, a: a.String()}
+}
+
+// IsUnicastIP returns true if its argument is a unicast IP address.
+func IsUnicastIP(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
+		// ipv4 or v6
+		return !(ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
+	}
+	return false
+}
+
+// IsUnicastIPv4 returns true if its argument is a unicast IP4 address
+func IsUnicastIPv4(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && ip.To4() != nil && !ip.IsMulticast() {
+		return !ip.IsUnspecified() && !ip.IsMulticast()
+	}
+	return false
+}
+
+// IsPublicUnicastIPv4 returns true if its argument is a globally routable,
+// public IPv4 unicast address.
+func IsPublicUnicastIPv4(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
+		if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
+			return !ip.IsMulticast()
+		}
+	}
+	return false
+}
+
+// IsUnicastIPv6 returns true if its argument is a unicast IPv6 address
+func IsUnicastIPv6(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && ip.To4() == nil {
+		return !ip.IsUnspecified() && !(ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
+	}
+	return false
+}
+
+// IsUnicastIPv6 returns true if its argument is a globally routable IP6 address
+func IsPublicUnicastIPv6(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil && ip.To4() == nil {
+		if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
+			return true
+		}
+	}
+	return false
+}
+
+// IsPublicUnicastIP returns true if its argument is a global routable IPv4 or 6
+// address.
+func IsPublicUnicastIP(a net.Addr) bool {
+	if ip := AsIP(a); ip != nil {
+		if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
+			return true
+		}
+		if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
+			return true
+		}
+	}
+	return false
+}
+
+func diffAB(a, b AddrList) AddrList {
+	diff := AddrList{}
+	for _, av := range a {
+		found := false
+		for _, bv := range b {
+			if av.Network() == bv.Network() &&
+				av.String() == bv.String() {
+				found = true
+				break
+			}
+		}
+		if !found {
+			diff = append(diff, av)
+		}
+	}
+	return diff
+}
+
+// FindAdded returns the set of interfaces and/or addresses that are
+// present in b, but not in a - i.e. have been added.
+func FindAdded(a, b AddrList) AddrList {
+	return diffAB(b, a)
+}
+
+// FindRemoved returns the set of interfaces and/or addresses that
+// are present in a, but not in b - i.e. have been removed.
+func FindRemoved(a, b AddrList) AddrList {
+	return diffAB(a, b)
+}
diff --git a/lib/netstate/netstate_test.go b/lib/netstate/netstate_test.go
new file mode 100644
index 0000000..262d5e5
--- /dev/null
+++ b/lib/netstate/netstate_test.go
@@ -0,0 +1,252 @@
+package netstate_test
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"reflect"
+	"testing"
+
+	"veyron/lib/netstate"
+)
+
+func TestGet(t *testing.T) {
+	all, err := netstate.GetAll()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	accessible, err := netstate.GetAccessibleIPs()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if len(all) == 0 || len(accessible) == 0 {
+		t.Errorf("expected non zero lengths, not %d and %d", len(all), len(accessible))
+	}
+	if len(accessible) > len(all) {
+		t.Errorf("should never be more accessible addresses than 'all' addresses")
+	}
+	loopback := netstate.FindAdded(accessible, all)
+	if got, want := loopback.Filter(netstate.IsLoopbackIP), loopback; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func mkAddr(n, a string) net.Addr {
+	ip := net.ParseIP(a)
+	fmt.Fprintf(os.Stderr, "%s -> %d\n", a, len(ip))
+	return netstate.AsAddr(n, ip)
+}
+
+func TestAsIP(t *testing.T) {
+	lh := net.ParseIP("127.0.0.1")
+	if got, want := netstate.AsIP(&net.IPAddr{IP: lh}), "127.0.0.1"; got == nil || got.String() != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := netstate.AsIP(&net.IPNet{IP: lh}), "127.0.0.1"; got == nil || got.String() != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := netstate.AsIP(netstate.AsAddr("tcp", lh)), "127.0.0.1"; got == nil || got.String() != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+type hw struct{}
+
+func (*hw) Network() string { return "mac" }
+func (*hw) String() string  { return "01:23:45:67:89:ab:cd:ef" }
+
+func TestPredicates(t *testing.T) {
+	if got, want := netstate.IsUnicastIP(&hw{}), false; got != want {
+		t.Errorf("got %t, want %t", got, want)
+
+	}
+
+	cases := []struct {
+		f func(a net.Addr) bool
+		a string
+		r bool
+	}{
+		{netstate.IsUnspecifiedIP, "0.0.0.0", true},
+		{netstate.IsUnspecifiedIP, "::", true},
+		{netstate.IsUnspecifiedIP, "127.0.0.1", false},
+		{netstate.IsUnspecifiedIP, "::1", false},
+
+		{netstate.IsLoopbackIP, "0.0.0.0", false},
+		{netstate.IsLoopbackIP, "::", false},
+		{netstate.IsLoopbackIP, "127.0.0.1", true},
+		{netstate.IsLoopbackIP, "::1", true},
+
+		{netstate.IsAccessibleIP, "0.0.0.0", false},
+		{netstate.IsAccessibleIP, "::", false},
+		{netstate.IsAccessibleIP, "127.0.0.1", false},
+		{netstate.IsAccessibleIP, "::1", false},
+		{netstate.IsAccessibleIP, "224.0.0.2", true},
+		{netstate.IsAccessibleIP, "fc00:1234::", true},
+		{netstate.IsAccessibleIP, "192.168.1.1", true},
+		{netstate.IsAccessibleIP, "2001:4860:0:2001::68", true},
+
+		{netstate.IsUnicastIP, "0.0.0.0", false},
+		{netstate.IsUnicastIP, "::", false},
+		{netstate.IsUnicastIP, "127.0.0.1", true},
+		{netstate.IsUnicastIP, "::1", true},
+		{netstate.IsUnicastIP, "192.168.1.2", true},
+		{netstate.IsUnicastIP, "74.125.239.36", true},
+		{netstate.IsUnicastIP, "224.0.0.2", false},
+		{netstate.IsUnicastIP, "fc00:1235::", true},
+		{netstate.IsUnicastIP, "ff01::01", false},
+		{netstate.IsUnicastIP, "2001:4860:0:2001::69", true},
+
+		{netstate.IsUnicastIPv4, "0.0.0.0", false},
+		{netstate.IsUnicastIPv4, "::", false},
+		{netstate.IsUnicastIPv4, "127.0.0.1", true},
+		{netstate.IsUnicastIPv4, "::1", false},
+		{netstate.IsUnicastIPv4, "192.168.1.3", true},
+		{netstate.IsUnicastIPv6, "74.125.239.37", false},
+		{netstate.IsUnicastIPv4, "224.0.0.2", false},
+		{netstate.IsUnicastIPv4, "fc00:1236::", false},
+		{netstate.IsUnicastIPv4, "ff01::02", false},
+		{netstate.IsUnicastIPv4, "2001:4860:0:2001::6a", false},
+
+		{netstate.IsUnicastIPv6, "0.0.0.0", false},
+		{netstate.IsUnicastIPv6, "::", false},
+		{netstate.IsUnicastIPv6, "127.0.0.1", false},
+		{netstate.IsUnicastIPv6, "::1", true},
+		{netstate.IsUnicastIPv6, "192.168.1.4", false},
+		{netstate.IsUnicastIPv6, "74.125.239.38", false},
+		{netstate.IsUnicastIPv6, "224.0.0.2", false},
+		{netstate.IsUnicastIPv6, "fc00:1237::", true},
+		{netstate.IsUnicastIPv6, "ff01::03", false},
+		{netstate.IsUnicastIPv6, "2607:f8b0:4003:c00::6b", true},
+
+		{netstate.IsPublicUnicastIP, "0.0.0.0", false},
+		{netstate.IsPublicUnicastIP, "::", false},
+		{netstate.IsPublicUnicastIP, "127.0.0.1", false},
+		{netstate.IsPublicUnicastIP, "::1", false},
+		{netstate.IsPublicUnicastIP, "192.168.1.2", false},
+		{netstate.IsPublicUnicastIP, "74.125.239.39", true},
+		{netstate.IsPublicUnicastIP, "224.0.0.2", false},
+		// Arguably this is buggy, the fc00:/7 prefix is supposed to be
+		// non-routable.
+		{netstate.IsPublicUnicastIP, "fc00:1238::", true},
+		{netstate.IsPublicUnicastIP, "ff01::01", false},
+		{netstate.IsPublicUnicastIP, "2001:4860:0:2001::69", true},
+
+		{netstate.IsPublicUnicastIPv4, "0.0.0.0", false},
+		{netstate.IsPublicUnicastIPv4, "::", false},
+		{netstate.IsPublicUnicastIPv4, "127.0.0.1", false},
+		{netstate.IsPublicUnicastIPv4, "::1", false},
+		{netstate.IsPublicUnicastIPv4, "192.168.1.3", false},
+		{netstate.IsPublicUnicastIPv4, "74.125.239.40", true},
+		{netstate.IsPublicUnicastIPv4, "224.0.0.2", false},
+		{netstate.IsPublicUnicastIPv4, "fc00:1239::", false},
+		{netstate.IsPublicUnicastIPv4, "ff01::02", false},
+		{netstate.IsPublicUnicastIPv4, "2001:4860:0:2001::6a", false},
+
+		{netstate.IsPublicUnicastIPv6, "0.0.0.0", false},
+		{netstate.IsPublicUnicastIPv6, "::", false},
+		{netstate.IsPublicUnicastIPv6, "127.0.0.1", false},
+		{netstate.IsPublicUnicastIPv6, "::1", false},
+		{netstate.IsPublicUnicastIPv6, "192.168.1.4", false},
+		{netstate.IsPublicUnicastIPv6, "74.125.239.41", false},
+		{netstate.IsPublicUnicastIPv6, "224.0.0.2", false},
+		// Arguably this is buggy, the fc00:/7 prefix is supposed to be
+		// non-routable.
+		{netstate.IsPublicUnicastIPv6, "fc00:123a::", true},
+		{netstate.IsPublicUnicastIPv6, "ff01::03", false},
+		{netstate.IsPublicUnicastIPv6, "2607:f8b0:4003:c00::6b", true},
+	}
+	for i, c := range cases {
+		net := "tcp"
+		if got, want := c.f(mkAddr(net, c.a)), c.r; got != want {
+			t.Errorf("#%d: %s %s: got %t, want %t", i+1, net, c.a, got, want)
+		}
+	}
+}
+
+var (
+	a  = mkAddr("tcp4", "1.2.3.4")
+	b  = mkAddr("tcp4", "1.2.3.5")
+	c  = mkAddr("tcp4", "1.2.3.6")
+	d  = mkAddr("tcp4", "1.2.3.7")
+	a6 = mkAddr("tcp6", "2001:4860:0:2001::68")
+	b6 = mkAddr("tcp6", "2001:4860:0:2001::69")
+	c6 = mkAddr("tcp6", "2001:4860:0:2001::70")
+	d6 = mkAddr("tcp6", "2001:4860:0:2001::71")
+)
+
+func TestRemoved(t *testing.T) {
+	al := netstate.AddrList{a, b, c, a6, b6, c6}
+	bl := netstate.AddrList{}
+
+	// no changes.
+	got, want := netstate.FindRemoved(al, al), netstate.AddrList{}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, want %#v", got, want)
+	}
+
+	// missing everything
+	if got, want := netstate.FindRemoved(al, bl), al; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// missing nothing
+	if got, want := netstate.FindRemoved(bl, al), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// remove some addresses
+	bl = netstate.AddrList{a, b, a6, b6}
+	if got, want := netstate.FindRemoved(al, bl), (netstate.AddrList{c, c6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add some addresses
+	bl = netstate.AddrList{a, b, c, a6, b6, c6, d6}
+	if got, want := netstate.FindRemoved(al, bl), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// change some addresses
+	bl = netstate.AddrList{a, b, d, a6, d6, c6}
+	if got, want := netstate.FindRemoved(al, bl), (netstate.AddrList{c, b6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+}
+
+func TestAdded(t *testing.T) {
+	al := netstate.AddrList{a, b, c, a6, b6, c6}
+	bl := netstate.AddrList{}
+
+	// no changes.
+	if got, want := netstate.FindAdded(al, al), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add nothing
+	if got, want := netstate.FindAdded(al, bl), bl; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add everything
+	if got, want := netstate.FindAdded(bl, al), al; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add some addresses
+	bl = netstate.AddrList{a, b, c, d, a6, b6, c6, d6}
+	if got, want := netstate.FindAdded(al, bl), (netstate.AddrList{d, d6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// remove some addresses
+	bl = netstate.AddrList{a, b, c, b6}
+	if got, want := netstate.FindAdded(al, bl), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// change some addresses
+	bl = netstate.AddrList{a, d, c, a6, d6, c6}
+	if got, want := netstate.FindAdded(al, bl), (netstate.AddrList{d, d6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+}
diff --git a/profiles/net/init.go b/profiles/net/init.go
index e38a6f5..6f61d3e 100644
--- a/profiles/net/init.go
+++ b/profiles/net/init.go
@@ -20,10 +20,8 @@
 	"veyron2/config"
 	"veyron2/rt"
 
+	"veyron/lib/netconfig"
 	"veyron/profiles"
-
-	// TODO(cnicolaou): move this to profiles/internal
-	"veyron/runtimes/google/lib/netconfig"
 )
 
 const (
diff --git a/profiles/net/util.go b/profiles/net/util.go
index 721ed29..57d42a5 100644
--- a/profiles/net/util.go
+++ b/profiles/net/util.go
@@ -4,10 +4,12 @@
 	"fmt"
 	"net"
 
-	// TODO(cnicolaou): move this out of the runtimes and into here.
-	"veyron/runtimes/google/lib/netconfig"
+	"veyron/lib/netstate"
 )
 
+// TODO(cnicolaou): this will be removed in a subsequent CL by using the
+// new netstate library.
+
 // ipAndIf is a map of interface name to the set of IP addresses available on
 // that interface.
 type ipAndIf map[string][]net.IP
@@ -112,7 +114,7 @@
 	if ip == nil {
 		return false
 	}
-	return netconfig.IsGloballyRoutable(ip)
+	return netstate.IsGloballyRoutableIP(ip)
 }
 
 func diffAB(a, b ipAndIf) ipAndIf {
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 3135149..54a3193 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -1110,7 +1110,7 @@
 	sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
 	defer sm.Shutdown()
 	ns := newNamespace()
-	pa := func(string) (net.Addr, error) {
+	pa := func(string, []net.Addr) (net.Addr, error) {
 		a := &net.IPAddr{}
 		a.IP = net.ParseIP("1.1.1.1")
 		return a, nil
@@ -1145,7 +1145,7 @@
 	sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
 	defer sm.Shutdown()
 	ns := newNamespace()
-	paerr := func(string) (net.Addr, error) {
+	paerr := func(string, []net.Addr) (net.Addr, error) {
 		return nil, fmt.Errorf("oops")
 	}
 	server, err := InternalNewServer(testContext(), sm, ns, vc.FixedLocalID(serverID), veyron2.PreferredAddressOpt(paerr))
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 5b5b36d..a51942c 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -9,7 +9,8 @@
 	"sync"
 	"time"
 
-	"veyron/runtimes/google/lib/netconfig"
+	"veyron/lib/netstate"
+
 	"veyron/runtimes/google/lib/publisher"
 	inaming "veyron/runtimes/google/naming"
 	isecurity "veyron/runtimes/google/security"
@@ -46,7 +47,7 @@
 	stopped          bool                     // whether the server has been stopped.
 	stoppedChan      chan struct{}            // closed when the server has been stopped.
 	ns               naming.Namespace
-	preferredAddress func(network string) (net.Addr, error)
+	preferredAddress func(network string, addrs []net.Addr) (net.Addr, error)
 	servesMountTable bool
 }
 
@@ -113,57 +114,16 @@
 // preferredIPAddress returns the preferred IP address, which is,
 // a public IPv4 address, then any non-loopback IPv4, then a public
 // IPv6 address and finally any non-loopback/link-local IPv6
-func preferredIPAddress(network string) (net.Addr, error) {
-	interfaces, err := net.Interfaces()
-	if err != nil {
-		return nil, err
+func preferredIPAddress(network string, addrs []net.Addr) (net.Addr, error) {
+	if !netstate.IsIPNetwork(network) {
+		return nil, fmt.Errorf("can't support network %q", network)
 	}
-	var any_ip4, any_ip6, pub_ip4, pub_ip6 net.Addr
-	for _, ifc := range interfaces {
-		addrs, err := ifc.Addrs()
-		if err != nil {
-			continue
+	al := netstate.AddrList(addrs)
+	for _, predicate := range []netstate.Predicate{netstate.IsPublicUnicastIPv4,
+		netstate.IsUnicastIPv4, netstate.IsPublicUnicastIPv6} {
+		if a := al.First(predicate); a != nil {
+			return a, nil
 		}
-		for _, addr := range addrs {
-			ipn, ok := addr.(*net.IPNet)
-			if !ok {
-				continue
-			}
-			ip := ipn.IP
-			if ip == nil || ip.IsUnspecified() || ip.IsLoopback() || ip.IsMulticast() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
-				continue
-			}
-			if network == "tcp" || network == "tcp4" {
-				if t := ip.To4(); t != nil {
-					if any_ip4 == nil {
-						any_ip4 = addr
-					}
-					if pub_ip4 == nil && netconfig.IsGloballyRoutable(t) {
-						pub_ip4 = addr
-					}
-				}
-			}
-			if network == "tcp" || network == "tcp6" {
-				if t := ip.To16(); t != nil {
-					if any_ip6 == nil {
-						any_ip6 = addr
-					}
-					if pub_ip6 == nil && netconfig.IsGloballyRoutable(t) {
-						pub_ip6 = addr
-					}
-				}
-			}
-		}
-	}
-	switch {
-	case pub_ip4 != nil:
-		return pub_ip4, nil
-	case any_ip4 != nil:
-		return any_ip4, nil
-	case pub_ip6 != nil:
-		return pub_ip6, nil
-	case any_ip6 != nil:
-		return any_ip6, nil
 	}
 	return nil, fmt.Errorf("failed to find any usable address for %q", network)
 }
@@ -208,8 +168,11 @@
 		}
 		if ip.IsUnspecified() && s.preferredAddress != nil {
 			// Need to find a usable IP address.
-			if a, err := s.preferredAddress(protocol); err == nil {
-				iep.Address = net.JoinHostPort(a.String(), port)
+			addrs, err := netstate.GetAccessibleIPs()
+			if err == nil {
+				if a, err := s.preferredAddress(protocol, addrs); err == nil {
+					iep.Address = net.JoinHostPort(a.String(), port)
+				}
 			}
 		}
 	}
diff --git a/services/mounttable/lib/neighborhood.go b/services/mounttable/lib/neighborhood.go
index 738c058..1fc8089 100644
--- a/services/mounttable/lib/neighborhood.go
+++ b/services/mounttable/lib/neighborhood.go
@@ -7,7 +7,7 @@
 	"strings"
 
 	"veyron/lib/glob"
-	"veyron/runtimes/google/lib/netconfig"
+	"veyron/lib/netconfig"
 
 	"veyron2/ipc"
 	"veyron2/naming"