-
Notifications
You must be signed in to change notification settings - Fork 1
/
interface_monitor_darwin.go
129 lines (104 loc) · 2.49 KB
/
interface_monitor_darwin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package netmon
import (
"bufio"
"context"
"fmt"
"io"
"os/exec"
"strings"
"time"
"golang.org/x/sync/errgroup"
)
type macosMonitor struct {
}
func newPlatformMonitor() platformMonitor {
return &macosMonitor{}
}
func (m *macosMonitor) run(ctx context.Context, interfaceMonitor *Monitor) error {
g, ctx := errgroup.WithContext(ctx)
// We want scutil to exit cleanly when ctx is canceled, which we can
// achieve by closing stdin. We're not using exec.CommandContext
// because it causes scutil to exit with an error.
cmd := exec.Command("scutil")
stdin, err := cmd.StdinPipe()
if err != nil {
return fmt.Errorf("failed to get scutil stdin pipe: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to get scutil stdout pipe: %w", err)
}
events := make(chan struct{})
g.Go(func() error {
defer stdin.Close()
// TODO: is there a way to get a notification when an interface
// goes from loopback to not loopback?
input := `
n.add State:/Network/Interface
n.add State:/Network/Interface/[^/]+/Link "pattern"
n.add State:/Network/Interface/[^/]+/IPv4 "pattern"
n.add State:/Network/Interface/[^/]+/IPv6 "pattern"
n.watch
`
_, err := io.WriteString(stdin, input)
if err != nil {
return fmt.Errorf("failed to write to scutil stdin: %w", err)
}
<-ctx.Done()
return nil
})
g.Go(func() error {
r := bufio.NewReader(stdout)
for {
line, err := r.ReadString('\n')
if err == io.EOF {
return nil
} else if err != nil {
return fmt.Errorf("failed to read from scutil stdout: %w", err)
}
if strings.Contains(line, "State:/Network/Interface/") {
select {
case events <- struct{}{}:
default:
}
}
}
})
g.Go(func() error {
// We want to batch events together, so we wait after
// receiving an event for other events to accumulate before
// notifying listeners.
var (
notifyTimer *time.Timer
notifyCh <-chan time.Time
pending bool
)
for {
select {
case <-ctx.Done():
if notifyTimer != nil && !notifyTimer.Stop() {
<-notifyTimer.C
}
if pending {
interfaceMonitor.NotifyChange()
}
return nil
case <-events:
pending = true
if notifyTimer == nil {
notifyTimer = time.NewTimer(200 * time.Millisecond)
notifyCh = notifyTimer.C
}
case <-notifyCh:
interfaceMonitor.NotifyChange()
pending = false
notifyCh = nil
notifyTimer = nil
}
}
})
g.Go(func() error {
return cmd.Run()
})
return g.Wait()
}