This repository has been archived by the owner on Jul 29, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
watcher_linux.go
107 lines (90 loc) · 2.57 KB
/
watcher_linux.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
//+build linux
package netstate
import (
"context"
"fmt"
"time"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/netlink"
"golang.org/x/sync/errgroup"
)
// deadlineNow is a sentinel value which will cause an immediate timeout to
// the rtnetlink listener.
var deadlineNow = time.Unix(0, 1)
// osWatch is the OS-specific portion of a Watcher's Watch method.
func osWatch(ctx context.Context, notify func(changeSet)) error {
c, err := rtnetlink.Dial(&netlink.Config{
Groups: 0x1, // RTMGRP_LINK (TODO: move to x/sys).
})
if err != nil {
return fmt.Errorf("netstate: watcher failed to dial route netlink: %w", err)
}
defer c.Close()
// Wait for cancelation and then force any pending reads to time out.
var eg errgroup.Group
eg.Go(func() error {
<-ctx.Done()
if err := c.SetReadDeadline(deadlineNow); err != nil {
return fmt.Errorf("netstate: failed to interrupt watcher: %w", err)
}
return nil
})
for {
msgs, _, err := c.Receive()
if err != nil {
if ctx.Err() != nil {
// Context canceled.
return eg.Wait()
}
return fmt.Errorf("netstate: watcher failed to listen for route netlink messages: %w", err)
}
// Received messages; produce a changeSet and notify subscribers.
notify(process(msgs))
}
}
// process handles received route netlink messages and produces a changeSet
// suitable for use with the Watcher.notify method.
func process(msgs []rtnetlink.Message) changeSet {
changes := make(changeSet)
for _, m := range msgs {
// TODO: also inspect other message types for addresses, routes, etc.
switch m := m.(type) {
case *rtnetlink.LinkMessage:
// TODO: inspect message header/type?
// Guard against nil.
if m.Attributes == nil {
continue
}
c, ok := operStateChange(m.Attributes.OperationalState)
if !ok {
// Unrecognized value, nothing to do.
continue
}
iface := m.Attributes.Name
changes[iface] = append(changes[iface], c)
}
}
return changes
}
// operStateChange converts a rtnetlink.OperationalState to a Change value.
func operStateChange(s rtnetlink.OperationalState) (Change, bool) {
switch s {
case rtnetlink.OperStateUnknown:
return LinkUnknown, true
case rtnetlink.OperStateNotPresent:
return LinkNotPresent, true
case rtnetlink.OperStateDown:
return LinkDown, true
case rtnetlink.OperStateLowerLayerDown:
return LinkLowerLayerDown, true
case rtnetlink.OperStateTesting:
return LinkTesting, true
case rtnetlink.OperStateDormant:
return LinkDormant, true
case rtnetlink.OperStateUp:
return LinkUp, true
default:
// Unhandled value, do nothing.
return 0, false
}
}