Signal network changes.
Change-Id: I990aebd73b9fd353fbae12712bb3dcb62a66a130
diff --git a/examples/netconfigwatcher/main.go b/examples/netconfigwatcher/main.go
new file mode 100644
index 0000000..b935883
--- /dev/null
+++ b/examples/netconfigwatcher/main.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "veyron/runtimes/google/lib/netconfig"
+)
+
+func main() {
+ 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/runtimes/google/lib/netconfig/example_test.go b/runtimes/google/lib/netconfig/example_test.go
new file mode 100644
index 0000000..0acef69
--- /dev/null
+++ b/runtimes/google/lib/netconfig/example_test.go
@@ -0,0 +1,20 @@
+package netconfig_test
+
+import (
+ "fmt"
+ "log"
+
+ "veyron/runtimes/google/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/runtimes/google/lib/netconfig/ipaux.go b/runtimes/google/lib/netconfig/ipaux.go
new file mode 100644
index 0000000..ffade53
--- /dev/null
+++ b/runtimes/google/lib/netconfig/ipaux.go
@@ -0,0 +1,47 @@
+// +build plan9 windows
+
+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.c = make(chan struct{})
+ w.stop = make(chan struct{}, 1)
+ go w.watcher()
+ return w, nil
+}
diff --git a/runtimes/google/lib/netconfig/ipaux_bsd.go b/runtimes/google/lib/netconfig/ipaux_bsd.go
new file mode 100644
index 0000000..aedeb81
--- /dev/null
+++ b/runtimes/google/lib/netconfig/ipaux_bsd.go
@@ -0,0 +1,121 @@
+// +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 (
+ "sync"
+ "syscall"
+ "time"
+ "veyron2/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:
+ 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()
+}
diff --git a/runtimes/google/lib/netconfig/ipaux_linux.go b/runtimes/google/lib/netconfig/ipaux_linux.go
new file mode 100644
index 0000000..9e90003
--- /dev/null
+++ b/runtimes/google/lib/netconfig/ipaux_linux.go
@@ -0,0 +1,371 @@
+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"
+ "veyron2/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_IPV6_IFADDR | 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()
+}
diff --git a/runtimes/google/lib/netconfig/model.go b/runtimes/google/lib/netconfig/model.go
new file mode 100644
index 0000000..c65afb6
--- /dev/null
+++ b/runtimes/google/lib/netconfig/model.go
@@ -0,0 +1,17 @@
+// package netconfig implements a network configuration watcher.
+// NOTE(p): This is also where we should put any code that changes
+// network configuration.
+
+package netconfig
+
+// 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{}
+}