blob: aedeb813015dbc6b15bbd36121d7645dd4451447 [file] [log] [blame]
David Why Use Two When One Will Do Presottof6813ec2014-07-11 16:12:54 -07001// +build darwin dragonfly freebsd netbsd openbsd
2
3package netconfig
4
5// We connect to the Route socket and parse messages to
6// look for network configuration changes. This is generic
7// to all BSD based systems (including MacOS). The net
8// library already has code to parse the messages so all
9// we need to do is look for message types.
10
11import (
12 "sync"
13 "syscall"
14 "time"
15 "veyron2/vlog"
16)
17
18/*
19#include <sys/socket.h>
20*/
21import "C"
22
23type bsdNetConfigWatcher struct {
24 sync.Mutex
25 t *time.Timer
26 c chan struct{}
27 s int
28 stopped bool
29}
30
31func (w *bsdNetConfigWatcher) Stop() {
32 w.Lock()
33 defer w.Unlock()
34 if w.stopped {
35 return
36 }
37 w.stopped = true
38 syscall.Close(w.s)
39}
40
41func (w *bsdNetConfigWatcher) Channel() chan struct{} {
42 return w.c
43}
44
45// NewNetConfigWatcher returns a watcher that sends a message
46// on the Channel() whenever the config changes.
47func NewNetConfigWatcher() (NetConfigWatcher, error) {
48 s, err := syscall.Socket(C.PF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
49 if err != nil {
50 vlog.Infof("socket failed: %s", err)
51 return nil, err
52 }
53 w := &bsdNetConfigWatcher{c: make(chan struct{}, 1), s: s}
54 go w.watcher()
55 return w, nil
56}
57
58func (w *bsdNetConfigWatcher) ding() {
59 w.Lock()
60 defer w.Unlock()
61 w.t = nil
62 if w.stopped {
63 return
64 }
65 // Don't let us hang in the lock. The default is safe because the requirement
66 // is that the client get a message after the last config change. Since this is
67 // a queued chan, we really don't have to stuff anything in it if there's already
68 // something there.
69 select {
70 case w.c <- struct{}{}:
71 default:
72 }
73}
74
75func (w *bsdNetConfigWatcher) watcher() {
76 defer w.Stop()
77
78 // Loop waiting for messages.
79 for {
80 b := make([]byte, 4096)
81 nr, err := syscall.Read(w.s, b)
82 if err != nil {
83 return
84 }
85 msgs, err := syscall.ParseRoutingMessage(b[:nr])
86 if err != nil {
87 vlog.Infof("Couldn't parse: %s", err)
88 continue
89 }
90
91 // Decode the addresses.
92 for _, m := range msgs {
93 switch m.(type) {
94 case *syscall.InterfaceMessage:
95 case *syscall.InterfaceAddrMessage:
96 default:
97 continue
98 }
99 // Changing networks usually spans many seconds and involves
100 // multiple network config changes. We add histeresis by
101 // setting an alarm when the first change is detected and
102 // not informing the client till the alarm goes off.
103 // NOTE(p): I chose 3 seconds because that covers all the
104 // events involved in moving from one wifi network to another.
105 w.Lock()
106 if w.t == nil {
107 w.t = time.AfterFunc(3*time.Second, w.ding)
108 }
109 w.Unlock()
110 }
111 }
112
113
114 w.Stop()
115 w.Lock()
116 close(w.c)
117 if w.t != nil {
118 w.t.Stop()
119 }
120 w.Unlock()
121}