blob: 74ce88df073cce4b7e286d4b72cb0a0272bb66b8 [file] [log] [blame]
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +02001package main
2
3import (
4 "bufio"
5 "context"
6 "flag"
7 "fmt"
8 "io"
9 "net"
10 "os"
Sergiusz Bazanski208f0052019-04-06 01:28:04 +020011 "sort"
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +020012 "strings"
13 "time"
14
15 "github.com/golang/glog"
16
17 mirko "code.hackerspace.pl/hscloud/go/mirko"
Sergiusz Bazanski2da126c2019-07-21 15:36:27 +020018 hpb "code.hackerspace.pl/hscloud/hswaw/proto"
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +020019)
20
21type lease struct {
22 hardware net.HardwareAddr
23 ip net.IP
24 from time.Time
25 to time.Time
26}
27
28const timeFmt = "2006/01/02 15:04:05"
29
30func parseLeases(f io.Reader, now time.Time) ([]lease, error) {
31 scanner := bufio.NewScanner(f)
32
33 leases := make(map[string]*lease)
34
35 var curAddr *net.IP
36 var curStart *time.Time
37 var curEnd *time.Time
38 var curHW *net.HardwareAddr
39
40 for scanner.Scan() {
41 l := scanner.Text()
42 l = strings.TrimSpace(l)
43 if len(l) < 1 {
44 continue
45 }
46
47 if strings.HasPrefix(l, "#") {
48 continue
49 }
50
51 parts := strings.Fields(l)
52 if len(parts) < 1 {
53 continue
54 }
55
56 if curAddr == nil {
57 switch parts[0] {
58 case "lease":
59 if len(parts) < 3 || parts[2] != "{" {
60 glog.Warningf("invalid lease line %q", l)
61 break
62 }
63
64 ip := net.ParseIP(parts[1])
65 if ip == nil {
66 glog.Warningf("invalid lease line %q: invalid ip")
67 break
68 }
69 curAddr = &ip
70 }
71 } else {
72 parts[len(parts)-1] = strings.TrimRight(parts[len(parts)-1], ";")
73
74 switch parts[0] {
75 case "starts":
76 fallthrough
77 case "ends":
78 if len(parts) != 4 {
79 glog.Warningf("invalid time line %q", l)
80 break
81 }
82 t, err := time.Parse(timeFmt, fmt.Sprintf("%s %s", parts[2], parts[3]))
83 if err != nil {
84 glog.Warningf("invalid time line %q: %v", l, err)
85 break
86 }
87 if parts[0] == "starts" {
88 curStart = &t
89 } else {
90 curEnd = &t
91 }
92 case "hardware":
93 if len(parts) < 2 {
94 glog.Warningf("invalid hardware line %q", l)
95 break
96 }
97 if parts[1] != "ethernet" {
98 break
99 }
100 if len(parts) != 3 {
101 glog.Warningf("invalid hardware ethernet line %q", l)
102 break
103 }
104 hw, err := net.ParseMAC(parts[2])
105 if err != nil {
106 glog.Warningf("invalid hardware ethernet line %q: %v", l, err)
107 break
108 }
109 curHW = &hw
110 case "}":
111 if curStart == nil || curEnd == nil || curHW == nil {
112 glog.V(2).Info("Invalid block for %q, not enough fields", curAddr.String())
113 } else if curEnd.Before(now) {
114 // skip.
115 } else {
116 leases[curHW.String()] = &lease{
117 hardware: *curHW,
118 ip: *curAddr,
119 from: *curStart,
120 to: *curEnd,
121 }
122 }
123 curAddr = nil
124 curStart = nil
125 curEnd = nil
126 curHW = nil
127 }
128 }
129 }
130
131 ret := make([]lease, len(leases))
132 i := 0
133 for _, v := range leases {
134 ret[i] = *v
135 i += 1
136 }
137
138 return ret, nil
139}
140
141type service struct {
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200142 leaseFile string
143 leaseRefreshString string
144 leaseRefresh time.Duration
145 leaseC chan chan []lease
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200146}
147
148func (s *service) work(ctx context.Context) {
149 leases := []lease{}
150
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200151 ticker := time.NewTicker(s.leaseRefresh)
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200152 start := make(chan struct{}, 1)
153 start <- struct{}{}
154
155 work := func() {
156 glog.Infof("Parsing leases...")
157 f, err := os.Open(s.leaseFile)
158 if err != nil {
159 glog.Errorf("Could not open lease file: %v", err)
160 return
161 }
162 l, err := parseLeases(f, time.Now())
163 f.Close()
164 if err != nil {
165 glog.Errorf("Could not parse lease file: %v", err)
166 return
167 }
Sergiusz Bazanski208f0052019-04-06 01:28:04 +0200168 sort.Slice(l, func(i, j int) bool { return string([]byte(l[i].ip)) < string([]byte(l[j].ip)) })
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200169 leases = l
170 glog.Infof("Got %d leases", len(leases))
171 }
172
173 glog.Infof("Worker started.")
174
175 for {
176 select {
177 case <-start:
178 work()
179 case <-ticker.C:
180 work()
181 case c := <-s.leaseC:
182 c <- leases
183 case <-ctx.Done():
184 glog.Infof("Worker quitting.")
185 close(start)
186 return
187 }
188 }
189}
190
191func (s *service) Leases(ctx context.Context, req *hpb.LeasifierLeasesRequest) (*hpb.LeasifierLeasesResponse, error) {
192 c := make(chan []lease)
193 s.leaseC <- c
194 leases := <-c
195
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200196 res := &hpb.LeasifierLeasesResponse{
197 Leases: make([]*hpb.LeasifierLease, len(leases)),
198 }
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200199
200 for i, l := range leases {
201 res.Leases[i] = &hpb.LeasifierLease{
202 PhysicalAddress: l.hardware.String(),
203 IpAddress: l.ip.String(),
204 }
205 }
206
207 return res, nil
208}
209
210func main() {
211 s := &service{
212 leaseC: make(chan chan []lease),
213 }
214
215 flag.StringVar(&s.leaseFile, "lease_file", "/var/db/dhcpd.leases", "Location of leasefile")
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200216 flag.StringVar(&s.leaseRefreshString, "lease_refresh", "1m", "How often to refresh leases")
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200217 flag.Parse()
218
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200219 d, err := time.ParseDuration(s.leaseRefreshString)
220 if err != nil {
221 glog.Exit(err)
222 }
223 s.leaseRefresh = d
224
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200225 m := mirko.New()
226 if err := m.Listen(); err != nil {
227 glog.Exitf("Could not listen: %v", err)
228 }
229
230 hpb.RegisterLeasifierServer(m.GRPC(), s)
231
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200232 s.setupStatusz(m)
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200233 go s.work(m.Context())
234
235 if err := m.Serve(); err != nil {
236 glog.Exitf("Could not run: %v", err)
237 }
238
239 glog.Info("Running.")
240 <-m.Done()
241
242}