Merge lib/netconfig from release.go.x.ref into release.go.x.lib.
diff --git a/netconfig/example_test.go b/netconfig/example_test.go
new file mode 100644
index 0000000..33ab162
--- /dev/null
+++ b/netconfig/example_test.go
@@ -0,0 +1,20 @@
+package netconfig_test
+
+import (
+	"fmt"
+	"log"
+
+	"v.io/x/ref/lib/netconfig"
+)
+
+func ExampleNetConfigWatcher() {
+	w, err := netconfig.NewNetConfigWatcher()
+	if err != nil {
+		log.Fatalf("oops: %s", err)
+	}
+	fmt.Println("Do something to your network. You should see one or more dings.")
+	for {
+		<-w.Channel()
+		fmt.Println("ding")
+	}
+}
diff --git a/netconfig/ipaux_bsd.go b/netconfig/ipaux_bsd.go
new file mode 100644
index 0000000..507f013
--- /dev/null
+++ b/netconfig/ipaux_bsd.go
@@ -0,0 +1,192 @@
+// +build darwin dragonfly freebsd netbsd openbsd
+
+package netconfig
+
+// We connect to the Route socket and parse messages to
+// look for network configuration changes.  This is generic
+// to all BSD based systems (including MacOS).  The net
+// library already has code to parse the messages so all
+// we need to do is look for message types.
+
+import (
+	"errors"
+	"net"
+	"sync"
+	"syscall"
+	"time"
+
+	"v.io/x/lib/vlog"
+)
+
+/*
+#include <sys/socket.h>
+*/
+import "C"
+
+type bsdNetConfigWatcher struct {
+	sync.Mutex
+	t       *time.Timer
+	c       chan struct{}
+	s       int
+	stopped bool
+}
+
+func (w *bsdNetConfigWatcher) Stop() {
+	w.Lock()
+	defer w.Unlock()
+	if w.stopped {
+		return
+	}
+	w.stopped = true
+	syscall.Close(w.s)
+}
+
+func (w *bsdNetConfigWatcher) Channel() chan struct{} {
+	return w.c
+}
+
+// NewNetConfigWatcher returns a watcher that sends a message
+// on the Channel() whenever the config changes.
+func NewNetConfigWatcher() (NetConfigWatcher, error) {
+	s, err := syscall.Socket(C.PF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
+	if err != nil {
+		vlog.Infof("socket failed: %s", err)
+		return nil, err
+	}
+	w := &bsdNetConfigWatcher{c: make(chan struct{}, 1), s: s}
+	go w.watcher()
+	return w, nil
+}
+
+func (w *bsdNetConfigWatcher) ding() {
+	w.Lock()
+	defer w.Unlock()
+	w.t = nil
+	if w.stopped {
+		return
+	}
+	// Don't let us hang in the lock.  The default is safe because the requirement
+	// is that the client get a message after the last config change.  Since this is
+	// a queued chan, we really don't have to stuff anything in it if there's already
+	// something there.
+	select {
+	case w.c <- struct{}{}:
+	default:
+	}
+}
+
+func (w *bsdNetConfigWatcher) watcher() {
+	defer w.Stop()
+
+	// Loop waiting for messages.
+	for {
+		b := make([]byte, 4096)
+		nr, err := syscall.Read(w.s, b)
+		if err != nil {
+			return
+		}
+		msgs, err := syscall.ParseRoutingMessage(b[:nr])
+		if err != nil {
+			vlog.Infof("Couldn't parse: %s", err)
+			continue
+		}
+
+		// Decode the addresses.
+		for _, m := range msgs {
+			switch m.(type) {
+			case *syscall.InterfaceMessage:
+			case *syscall.InterfaceAddrMessage:
+			case *syscall.RouteMessage:
+			default:
+				continue
+			}
+			// Changing networks usually spans many seconds and involves
+			// multiple network config changes.  We add histeresis by
+			// setting an alarm when the first change is detected and
+			// not informing the client till the alarm goes off.
+			// NOTE(p): I chose 3 seconds because that covers all the
+			// events involved in moving from one wifi network to another.
+			w.Lock()
+			if w.t == nil {
+				w.t = time.AfterFunc(3*time.Second, w.ding)
+			}
+			w.Unlock()
+		}
+	}
+
+	w.Stop()
+	w.Lock()
+	close(w.c)
+	if w.t != nil {
+		w.t.Stop()
+	}
+	w.Unlock()
+}
+
+func toIP(sa syscall.Sockaddr) (net.IP, error) {
+	switch v := sa.(type) {
+	case *syscall.SockaddrInet4:
+		return net.IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3]), nil
+	case *syscall.SockaddrInet6:
+		return net.IP(v.Addr[:]), nil
+	}
+	return net.IPv6zero, errors.New("unknown sockaddr ip")
+}
+
+func toIPNet(sa syscall.Sockaddr, msa syscall.Sockaddr) (net.IPNet, error) {
+	var x net.IPNet
+	var err error
+	x.IP, err = toIP(sa)
+	if err != nil {
+		return x, err
+	}
+	switch v := msa.(type) {
+	case *syscall.SockaddrInet4:
+		x.Mask = net.IPv4Mask(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
+		return x, nil
+	case *syscall.SockaddrInet6:
+		x.Mask = net.IPMask(v.Addr[:])
+		return x, nil
+	}
+	return x, errors.New("unknown sockaddr ipnet")
+}
+
+// IPRoutes returns all kernel known routes.  If defaultOnly is set, only default routes
+// are returned.
+func GetIPRoutes(defaultOnly bool) []*IPRoute {
+	var x []*IPRoute
+	rib, err := syscall.RouteRIB(syscall.NET_RT_DUMP, 0)
+	if err != nil {
+		vlog.Infof("Couldn't read: %s", err)
+		return x
+	}
+	msgs, err := syscall.ParseRoutingMessage(rib)
+	if err != nil {
+		vlog.Infof("Couldn't parse: %s", err)
+		return x
+	}
+	for _, m := range msgs {
+		switch v := m.(type) {
+		case *syscall.RouteMessage:
+			addrs, err := syscall.ParseRoutingSockaddr(m)
+			if err != nil {
+				return x
+			}
+			if addrs[0] == nil || addrs[1] == nil || addrs[2] == nil {
+				continue
+			}
+			r := new(IPRoute)
+			if r.Gateway, err = toIP(addrs[1]); err != nil {
+				continue
+			}
+			if r.Net, err = toIPNet(addrs[0], addrs[2]); err != nil {
+				continue
+			}
+			r.IfcIndex = int(v.Header.Index)
+			if !defaultOnly || IsDefaultIPRoute(r) {
+				x = append(x, r)
+			}
+		}
+	}
+	return x
+}
diff --git a/netconfig/ipaux_linux.go b/netconfig/ipaux_linux.go
new file mode 100644
index 0000000..e36b9e5
--- /dev/null
+++ b/netconfig/ipaux_linux.go
@@ -0,0 +1,457 @@
+// +build linux
+
+package netconfig
+
+// We connect to the Netlink Route socket and parse messages to
+// look for network configuration changes.  This is very Linux
+// specific, hence the file name.
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"sync"
+	"syscall"
+	"time"
+	"unsafe"
+
+	"v.io/x/lib/vlog"
+)
+
+/*
+#include <linux/rtnetlink.h>
+*/
+import "C"
+
+// All rtnetlink attributes start with this header.
+type rtAttrHdr C.struct_rtattr
+
+const rtAttrHdrLen = C.sizeof_struct_rtattr
+
+// The address change messages (RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR).
+type ifaddrMsgHdr C.struct_ifaddrmsg
+
+const ifaddrMsgHdrLen = C.sizeof_struct_ifaddrmsg
+
+type rtAttribute fmt.Stringer
+
+type rtAddressMessage struct {
+	name       string
+	hdr        ifaddrMsgHdr
+	attributes []rtAttribute
+}
+
+// Attribute types (see rtnetlink(7))
+type ifaAddress net.IP
+type ifaLocal net.IP
+type ifaBroadcast net.IP
+type ifaAnycast net.IP
+type ifaMulticast net.IP
+type ifaLabel string
+type ifaCacheInfo C.struct_ifa_cacheinfo
+
+const ifaCacheInfoLen = C.sizeof_struct_ifa_cacheinfo
+
+// String routines to make debugging easier.
+func (a ifaAddress) String() string   { return "Address=" + net.IP(a).String() }
+func (a ifaLocal) String() string     { return "Local=" + net.IP(a).String() }
+func (a ifaBroadcast) String() string { return "Braodcast=" + net.IP(a).String() }
+func (a ifaAnycast) String() string   { return "Anycast=" + net.IP(a).String() }
+func (a ifaMulticast) String() string { return "Anycast=" + net.IP(a).String() }
+func (a ifaLabel) String() string     { return "Label=" + string(a) }
+func (a ifaCacheInfo) String() string {
+	return fmt.Sprintf("CacheInfo[preferred %d valid %d cstamp %d tstamp %d]", a.ifa_prefered, a.ifa_valid, a.cstamp, a.tstamp)
+}
+func (m *rtAddressMessage) String() string {
+	return fmt.Sprintf("%s: index %d %v", m.name, m.hdr.ifa_index, m.attributes)
+}
+
+// Address looks for the address attribute in an rtAddressMessage.  If it isn't there, just assume the null address.
+func (m *rtAddressMessage) Address() net.IP {
+	for _, a := range m.attributes {
+		switch a.(type) {
+		case ifaAddress:
+			return net.IP(a.(ifaAddress))
+		}
+	}
+	return net.IPv4zero
+}
+
+func parsertAddressAttribute(b []byte) (rtAttribute, []byte, error) {
+	if len(b) == 0 {
+		return nil, nil, nil
+	}
+	if len(b) < rtAttrHdrLen {
+		return nil, nil, errors.New("attribute too short")
+	}
+	ahdr := (*rtAttrHdr)(unsafe.Pointer(&b[0]))
+	rounded := ((ahdr.rta_len + 3) / 4) * 4
+	if len(b) < int(rounded) {
+		return nil, nil, errors.New("attribute too short")
+	}
+	remaining := b[rounded:]
+	b = b[rtAttrHdrLen:ahdr.rta_len]
+	switch ahdr.rta_type {
+	case C.IFA_ADDRESS:
+		return rtAttribute(ifaAddress(net.IP(b))), remaining, nil
+	case C.IFA_LOCAL:
+		return rtAttribute(ifaLocal(b)), remaining, nil
+	case C.IFA_LABEL:
+		return rtAttribute(ifaLabel(b)), remaining, nil
+	case C.IFA_BROADCAST:
+		return rtAttribute(ifaBroadcast(b)), remaining, nil
+	case C.IFA_ANYCAST:
+		return rtAttribute(ifaAnycast(b)), remaining, nil
+	case C.IFA_CACHEINFO:
+		if len(b) < ifaCacheInfoLen {
+			return nil, nil, errors.New("attribute too short")
+		}
+		return rtAttribute(ifaCacheInfo(*(*C.struct_ifa_cacheinfo)(unsafe.Pointer(&b[0])))), remaining, nil
+	case C.IFA_MULTICAST:
+		return rtAttribute(ifaMulticast(b)), remaining, nil
+	}
+	return nil, remaining, errors.New("unknown attribute")
+}
+
+func parsertAddressMessage(nlm syscall.NetlinkMessage) (*rtAddressMessage, error) {
+	var name string
+	switch nlm.Header.Type {
+	case C.RTM_NEWADDR:
+		name = "RTM_NEWADDR"
+	case C.RTM_DELADDR:
+		name = "RTM_DELADDR"
+	case C.RTM_GETADDR:
+		name = "RTM_GETADDR"
+	default:
+		return nil, fmt.Errorf("unknown message type")
+	}
+	if len(nlm.Data) < ifaddrMsgHdrLen {
+		return nil, errors.New("bad length")
+	}
+	m := &rtAddressMessage{name: name, hdr: *(*ifaddrMsgHdr)(unsafe.Pointer(&nlm.Data[0]))}
+	b := nlm.Data[ifaddrMsgHdrLen:]
+	for {
+		var a rtAttribute
+		var err error
+		a, b, err = parsertAddressAttribute(b)
+		if b == nil {
+			break
+		}
+		if err == nil {
+			m.attributes = append(m.attributes, a)
+		}
+	}
+	return m, nil
+}
+
+// The link change messages (RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK).
+type ifInfoMsgHdr C.struct_ifinfomsg
+
+const ifInfoMsgHdrLen = C.sizeof_struct_ifinfomsg
+
+type rtLinkMessage struct {
+	name       string
+	hdr        ifInfoMsgHdr
+	attributes []rtAttribute
+}
+
+// Attribute types (see rtnetlink(7))
+type iflaAddress []byte
+type iflaBroadcast []byte
+type iflaIFName string
+type iflaMTU uint32
+type iflaLink int
+type iflaQDisc string
+type iflaOperstate int
+type iflaStats C.struct_rtnl_link_stats
+
+const iflaStatsLen = C.sizeof_struct_rtnl_link_stats
+
+// String routines to make debugging easier.
+func (a iflaAddress) String() string   { return fmt.Sprintf("HWAddress=%v", []byte(a)) }
+func (a iflaBroadcast) String() string { return fmt.Sprintf("HWBroadcast=%v", []byte(a)) }
+func (a iflaIFName) String() string    { return "Name=" + string(a) }
+func (a iflaMTU) String() string       { return fmt.Sprintf("MTU=%d", uint32(a)) }
+func (a iflaLink) String() string      { return fmt.Sprintf("Type=%d", int(a)) }
+func (a iflaQDisc) String() string     { return "Qdisc=" + string(a) }
+func (a iflaStats) String() string {
+	return fmt.Sprintf("Stats[rx %d tx %d ...]", a.rx_packets, a.tx_packets)
+}
+func (a iflaOperstate) String() string { return fmt.Sprintf("Operstate=%d", int(a)) }
+func (m *rtLinkMessage) String() string {
+	return fmt.Sprintf("%s: index %d %v", m.name, m.hdr.ifi_index, m.attributes)
+}
+
+func parseRTLinkAttribute(b []byte) (rtAttribute, []byte, error) {
+	if len(b) == 0 {
+		return nil, nil, nil
+	}
+	if len(b) < rtAttrHdrLen {
+		return nil, nil, errors.New("attribute too short")
+	}
+	ahdr := (*rtAttrHdr)(unsafe.Pointer(&b[0]))
+	rounded := ((ahdr.rta_len + 3) / 4) * 4
+	if len(b) < int(rounded) {
+		return nil, nil, errors.New("attribute too short")
+	}
+	remaining := b[rounded:]
+	b = b[rtAttrHdrLen:ahdr.rta_len]
+	switch ahdr.rta_type {
+	case C.IFLA_ADDRESS:
+		return rtAttribute(iflaAddress(b)), remaining, nil
+	case C.IFLA_BROADCAST:
+		return rtAttribute(iflaBroadcast(b)), remaining, nil
+	case C.IFLA_IFNAME:
+		return rtAttribute(iflaIFName(string(b))), remaining, nil
+	case C.IFLA_MTU:
+		return rtAttribute(iflaMTU(*(*C.uint)(unsafe.Pointer(&b[0])))), remaining, nil
+	case C.IFLA_LINK:
+		return rtAttribute(iflaMTU(*(*C.int)(unsafe.Pointer(&b[0])))), remaining, nil
+	case C.IFLA_QDISC:
+		return rtAttribute(iflaQDisc(string(b))), remaining, nil
+	case C.IFLA_STATS:
+		if len(b) < iflaStatsLen {
+			return nil, remaining, errors.New("attribute too short")
+		}
+		return rtAttribute(iflaStats(*(*C.struct_rtnl_link_stats)(unsafe.Pointer(&b[0])))), remaining, nil
+	case C.IFLA_OPERSTATE:
+		return rtAttribute(iflaOperstate(*(*C.int)(unsafe.Pointer(&b[0])))), remaining, nil
+	}
+	return nil, remaining, errors.New("unknown attribute")
+}
+
+func parsertLinkMessage(nlm syscall.NetlinkMessage) (*rtLinkMessage, error) {
+	var name string
+	switch nlm.Header.Type {
+	case C.RTM_NEWLINK:
+		name = "RTM_NEWLINK"
+	case C.RTM_DELLINK:
+		name = "RTM_DELLINK"
+	case C.RTM_GETLINK:
+		name = "RTM_GETLINK"
+	default:
+		return nil, fmt.Errorf("unknown message type")
+	}
+	if len(nlm.Data) < ifInfoMsgHdrLen {
+		return nil, errors.New("bad length")
+	}
+	m := &rtLinkMessage{name: name, hdr: *(*ifInfoMsgHdr)(unsafe.Pointer(&nlm.Data[0]))}
+	b := nlm.Data[ifInfoMsgHdrLen:]
+	for {
+		var a rtAttribute
+		var err error
+		a, b, err = parseRTLinkAttribute(b)
+		if b == nil {
+			break
+		}
+		if err == nil {
+			m.attributes = append(m.attributes, a)
+		}
+	}
+	return m, nil
+}
+
+type rtnetlinkWatcher struct {
+	sync.Mutex
+	t       *time.Timer
+	c       chan struct{}
+	s       int
+	stopped bool
+}
+
+func (w *rtnetlinkWatcher) Stop() {
+	w.Lock()
+	defer w.Unlock()
+	if w.stopped {
+		return
+	}
+	w.stopped = true
+	syscall.Close(w.s)
+}
+
+func (w *rtnetlinkWatcher) Channel() chan struct{} {
+	return w.c
+}
+
+const (
+	GROUPS = C.RTMGRP_LINK | C.RTMGRP_IPV4_IFADDR | C.RTMGRP_IPV4_MROUTE | C.RTMGRP_IPV4_ROUTE | C.RTMGRP_IPV6_IFADDR | C.RTMGRP_IPV6_MROUTE | C.RTMGRP_IPV6_ROUTE | C.RTMGRP_NOTIFY
+)
+
+// NewNetConfigWatcher returns a watcher that wakes up anyone
+// calling the Wait routine whenever the configuration changes.
+func NewNetConfigWatcher() (NetConfigWatcher, error) {
+	s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE)
+	if err != nil {
+		vlog.Infof("netconfig socket failed: %s", err)
+		return nil, err
+	}
+
+	lsa := &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK, Groups: GROUPS}
+	if err := syscall.Bind(s, lsa); err != nil {
+		vlog.Infof("netconfig bind failed: %s", err)
+		return nil, err
+	}
+
+	w := &rtnetlinkWatcher{c: make(chan struct{}, 1), s: s}
+	go w.watcher()
+	return w, nil
+}
+
+func (w *rtnetlinkWatcher) ding() {
+	w.Lock()
+	defer w.Unlock()
+	w.t = nil
+	if w.stopped {
+		return
+	}
+	// Don't let us hang in the lock.  The default is safe because the requirement
+	// is that the client get a message after the last config change.  Since this is
+	// a queued chan, we really don't have to stuff anything in it if there's already
+	// something there.
+	select {
+	case w.c <- struct{}{}:
+	default:
+	}
+}
+
+func (w *rtnetlinkWatcher) watcher() {
+	var newAddrs []net.IP
+	for {
+		rb := make([]byte, 4096)
+		nr, _, err := syscall.Recvfrom(w.s, rb, 0)
+		if err != nil {
+			break
+		}
+		rb = rb[:nr]
+		msgs, err := syscall.ParseNetlinkMessage(rb)
+		if err != nil {
+			vlog.Infof("ParseNetlinkMessage failed: %s", err)
+			continue
+		}
+	L:
+		for _, m := range msgs {
+			if am, err := parsertAddressMessage(m); err == nil {
+				// NOTE(p): We get continuous NEWADDR messages about some
+				// IPv6 addresses in Google corp.  Just ignore duplicate back
+				// to back NEWADDRs about the same addresses.
+				if am.name == "RTM_NEWADDR" {
+					addr := am.Address()
+					for _, a := range newAddrs {
+						if addr.Equal(a) {
+							break L
+						}
+					}
+					newAddrs = append(newAddrs, addr)
+				} else {
+					newAddrs = nil
+				}
+			} else if _, err := parsertLinkMessage(m); err == nil {
+				newAddrs = nil
+			} else {
+				continue
+			}
+			// Changing networks usually spans many seconds and involves
+			// multiple network config changes.  We add histeresis by
+			// setting an alarm when the first change is detected and
+			// not informing the client till the alarm goes off.
+			// NOTE(p): I chose 3 seconds because that covers all the
+			// events involved in moving from one wifi network to another.
+			w.Lock()
+			if w.t == nil {
+				w.t = time.AfterFunc(3*time.Second, w.ding)
+			}
+			w.Unlock()
+		}
+	}
+
+	w.Stop()
+	w.Lock()
+	close(w.c)
+	if w.t != nil {
+		w.t.Stop()
+	}
+	w.Unlock()
+}
+func toIP(a []byte) (net.IP, error) {
+	switch len(a) {
+	case 4:
+		return net.IPv4(a[0], a[1], a[2], a[3]), nil
+	case 16:
+		return net.IP(a), nil
+	}
+	return net.IPv6unspecified, errors.New("unknown ip address len")
+}
+
+// IPRoutes returns all kernel known routes.  If defaultOnly is set, only default routes
+// are returned.
+func GetIPRoutes(defaultOnly bool) []*IPRoute {
+	var iproutes []*IPRoute
+	rib, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC)
+	if err != nil {
+		vlog.Infof("Couldn't read: %s", err)
+		return iproutes
+	}
+	msgs, err := syscall.ParseNetlinkMessage(rib)
+	if err != nil {
+		vlog.Infof("Couldn't parse: %s", err)
+		return iproutes
+	}
+L:
+	for _, m := range msgs {
+		if m.Header.Type != syscall.RTM_NEWROUTE {
+			continue
+		}
+		attrs, err := syscall.ParseNetlinkRouteAttr(&m)
+		if err != nil {
+			continue
+		}
+		r := new(IPRoute)
+		for _, a := range attrs {
+			switch a.Attr.Type {
+			case syscall.RTA_DST:
+				if r.Net.IP, err = toIP(a.Value[:]); err != nil {
+					continue L
+				}
+			case syscall.RTA_GATEWAY:
+				if r.Gateway, err = toIP(a.Value[:]); err != nil {
+					continue L
+				}
+			case syscall.RTA_OIF:
+				r.IfcIndex = int(a.Value[0])
+			case syscall.RTA_PREFSRC:
+				if r.PreferredSource, err = toIP(a.Value[:]); err != nil {
+					continue L
+				}
+			}
+		}
+
+		// There is no RTA_DST attribute if destination is a default gateway.
+		// Set the destination IP with zero IP, if not set yet.
+		if r.Net.IP == nil {
+			if r.Gateway == nil {
+				continue
+			}
+			if r.Gateway.To4() != nil {
+				r.Net.IP = net.IPv4zero
+			} else {
+				r.Net.IP = net.IPv6zero
+			}
+		}
+
+		addrLen := 128
+		if r.Net.IP.To4() != nil {
+			addrLen = 32
+		}
+
+		b := m.Data[:syscall.SizeofRtMsg]
+		a := (*syscall.RtMsg)(unsafe.Pointer(&b[0]))
+		if int(a.Dst_len) > addrLen {
+			continue
+		}
+		r.Net.Mask = net.CIDRMask(int(a.Dst_len), addrLen)
+		if !defaultOnly || IsDefaultIPRoute(r) {
+			iproutes = append(iproutes, r)
+		}
+	}
+	return iproutes
+}
diff --git a/netconfig/ipaux_other.go b/netconfig/ipaux_other.go
new file mode 100644
index 0000000..948c0c7
--- /dev/null
+++ b/netconfig/ipaux_other.go
@@ -0,0 +1,55 @@
+// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd
+// TODO(bprosnitz) Should change for nacl?
+
+package netconfig
+
+// Code to signal a network change every 2 minutes.   We use
+// this for systems where we don't yet have a good way to
+// watch for network changes.
+
+import (
+	"time"
+)
+
+type timerNetConfigWatcher struct {
+	c    chan struct{} // channel to signal confg changes
+	stop chan struct{} // channel to tell the watcher to stop
+}
+
+// Stop any waiter
+func (w *timerNetConfigWatcher) Stop() {
+	w.stop <- struct{}{}
+}
+
+func (w *timerNetConfigWatcher) Channel() chan struct{} {
+	return w.c
+}
+
+func (w *timerNetConfigWatcher) watcher() {
+	for {
+		select {
+		case <-w.stop:
+			close(w.c)
+			return
+		case <-time.NewTimer(2 * time.Minute).C:
+			select {
+			case w.c <- struct{}{}:
+			default:
+			}
+		}
+	}
+}
+
+func NewNetConfigWatcher() (NetConfigWatcher, error) {
+	w := &timerNetConfigWatcher{}
+	w.c = make(chan struct{})
+	w.stop = make(chan struct{}, 1)
+	go w.watcher()
+	return w, nil
+}
+
+func GetIPRoutes(defaultOnly bool) []*IPRoute {
+	// TODO(nlacasse,bprosnitz): Consider implementing? For now return
+	// empty array, since that seems to keep things working.
+	return []*IPRoute{}
+}
diff --git a/netconfig/iproute.go b/netconfig/iproute.go
new file mode 100644
index 0000000..43115e3
--- /dev/null
+++ b/netconfig/iproute.go
@@ -0,0 +1,40 @@
+// package netconfig implements a network configuration watcher.
+// NOTE(p): This is also where we should put any code that changes
+//          network configuration.
+
+package netconfig
+
+import "net"
+
+func isZeroSlice(a []byte) bool {
+	for _, i := range a {
+		if i != 0 {
+			return false
+		}
+	}
+	return true
+}
+
+// IsDefaultRoute returns true if r is a default route, i.e., that it matches any destination address.
+func IsDefaultIPRoute(r *IPRoute) bool {
+	if !r.Net.IP.Equal(net.IPv4zero) && !r.Net.IP.Equal(net.IPv6zero) {
+		return false
+	}
+	return isZeroSlice(r.Net.Mask[:])
+}
+
+// IsDefaultIPv4Route returns true if r is a default IPv4 route.
+func IsDefaultIPv4Route(r *IPRoute) bool {
+	if !r.Net.IP.Equal(net.IPv4zero) && !r.Net.IP.Equal(net.IPv6zero) {
+		return false
+	}
+	return len(r.Net.Mask) == 4 && isZeroSlice(r.Net.Mask[:])
+}
+
+// IsDefaultIPv6Route returns true if r is a default IPv6 route.
+func IsDefaultIPv6Route(r *IPRoute) bool {
+	if !r.Net.IP.Equal(net.IPv4zero) && !r.Net.IP.Equal(net.IPv6zero) {
+		return false
+	}
+	return len(r.Net.Mask) == 16 && isZeroSlice(r.Net.Mask[:])
+}
diff --git a/netconfig/model.go b/netconfig/model.go
new file mode 100644
index 0000000..27898cc
--- /dev/null
+++ b/netconfig/model.go
@@ -0,0 +1,30 @@
+// package netconfig implements a network configuration watcher.
+// NOTE(p): This is also where we should put any code that changes
+//          network configuration.
+
+package netconfig
+
+import (
+	"net"
+)
+
+// NetConfigWatcher sends on channel whenever an interface or interface address
+// is added or deleted.
+type NetConfigWatcher interface {
+	// Stop watching.
+	Stop()
+
+	// A channel that returns an item whenever the network addresses or
+	// interfaces have changed. It is up to the caller to reread the
+	// network configuration in such cases.
+	Channel() chan struct{}
+}
+
+// IPRoute represents a route in the kernel's routing table.
+// Any route with a nil Gateway is a directly connected network.
+type IPRoute struct {
+	Net             net.IPNet
+	Gateway         net.IP
+	PreferredSource net.IP
+	IfcIndex        int
+}